Ended up updating it to use lazy sequences.

https://gist.github.com/887256

Already feels much better! I do still have a bit of state with the
tracking of which frame we're on but I'll certainly take that over the
glob of atoms before.

Next up is handling some of the more hairy bits with user input and
the like just to see how that would map, and getting multiple balls
running. Hopefully I can even get away from the lazy sequence method.

Thanks for the help!
-Brandon

On Apr 14, 11:19 pm, Brandon Ferguson <bnfergu...@gmail.com> wrote:
> Holy crap that's a lot to digest! Thanks! A lot of ideas in here to
> play with. For me and my purposes with Processing the big challenge
> has been the fact that I have little say about the draw loop. It just
> fires (or I can call it manually with a redraw, but then it just
> executes whatever is in that function). I had a nice recursive
> function with no state in the beginning but the first fire of draw
> would set it off and it'd draw a bunch of the screen and I'd never see
> anything after that from it (cause it'd never return and we'd not get
> to the next draw call/frame). Think there might be ways around that
> just not sure yet.
>
> Thanks again, and I'll certainly post with an update with where I get
> (probably won't be able to dive into it again until the weekend sadly :
> (  ).
>
> On Apr 14, 1:00 pm, Ken Wesson <kwess...@gmail.com> wrote:
>
>
>
>
>
>
>
> > On Wed, Apr 13, 2011 at 1:22 PM, Brandon Ferguson <bnfergu...@gmail.com> 
> > wrote:
> > > I'm not sure if this is the place to ask this but I've been struggling
> > > with a few things in the world of Clojure. I've been using Processing
> > > to learn Clojure (since I'm somewhat familiar with Processing) but the
> > > tough part has been dealing with things like x, y positions of objects
> > > without keeping some global state (I'm not sure if it's even
> > > possible).
>
> > If you're currently using atoms, you already have a function to
> > compute the new position from the old, say, next-position, which you
> > use with something like (swap! ball-pos next-position).
>
> > Now consider this: (iterate next-position initial-ball-pos). That
> > evaluates to an (infinite!) sequence of ball positions. You could have
> > an animation loop step along this sequence, render a frame, wait,
> > step, render, wait, etc. until the ESC key is hit (or whatever).
>
> > For multiple balls that might interact with one another, a given
> > ball's next position becomes a function of the other ball positions
> > and not just its own. So you end up with:
>
> > (iterate next-world-state initial-world-state)
>
> > with world states being e.g. maps of ball-IDs to ball-positions or
> > something. Obviously, other kinds of changeable world state can be
> > included, too, e.g. Pong paddle positions, or the locations and amount
> > of damage taken by Arkanoid bricks.
>
> > This works until you get interactive (e.g. letting a human player
> > control a paddle or something). At that point, iterate becomes a bit
> > icky because the function becomes impure (as it polls the input
> > devices).
>
> > Then you probably just want two components:
>
> > 1. A Swing GUI that captures mouse and keyboard events in the game's
> > JPanel as well as rendering the frames there.
> > 2. A game loop.
>
> > The simplest case uses the Swing EDT as the game loop, with a Timer
> > triggering game updates. But that requires mutable state for the game
> > state to persist between Timer events.
>
> > The other case keeps most of the game state immutable, but requires a
> > bit of mutability to pass input to the game from the GUI:
>
> > 1. The Swing event handlers post input messages to a
> > LinkedBlockingQueue, or update atoms or refs holding the current
> > position of the mouse and state (down or up) of relevant mouse buttons
> > and keyboard keys.
>
> > 2. The game loop runs in its own thread and looks something like this:
>
> > (loop [foo bar other game state variables]
> >   ...
> >     (recur new-foo new-bar new-other ...)...)
>
> > The game checks the input state, or drains the LinkedBlockingQueue, or
> > whatever when it comes around to updating the player's position.
>
> > One way to do it is just to have a set of game world objects:
>
> > (loop [time (System/currentTimeMillis)
> >        world (hash-set (cons (new-player) (generate-initial-world-state)))]
> >   (let [world (map (partial update-position world) world)
> >         new-objects (mapcat new-things world)
> >         damages (reduce (partial merge-with +) {} (map do-damage world))
> >         world (concat
> >                 new-objects
> >                 (remove dead?
> >                   (map (partial apply-damages damages) world)))]
> >     (SwingUtilities/invokeAndWait
> >       (fn []
> >         (let [g (get-the-jpanel's-double-buffer-graphics)]
> >           (doseq [obj world] (draw-on! obj g)))
> >         (flip-the-jpanel's-double-buffer!)))
> >     (if (some is-player? world)
> >       (let [elapsed (- (System/currentTimeMillis) time)
> >             sl (- *ms-per-frame* elapsed)]
> >         (if (> sl 0) (Thread/sleep sl))
> >         (recur (System/currentTimeMillis) world))
> >       world)))
>
> > Here, new-player generates a new player object and
> > generate-initial-world-state generates a seq of other objects for the
> > initial world-state. The update-position function takes the world and
> > a game object and runs the latter's ai, returning a new game object
> > representing the old one's changed position (and possibly other state
> > changes). The world is passed in so that collision checks can be done
> > when needed.
>
> > The new-things function allows some game objects to potentially
> > generate more (e.g. shooting projectiles); it can return nil if the
> > object hasn't spawned anything, or a seq of new things. For instance,
> > if the player shoots a rocket, it may return [(new-rocket player-x
> > player-y player-dx player-dy)]; when the rocket hits something and
> > explodes, it may itself return (repeatedly *num-shards*
> > #(generate-shrapnel rocket-x rocket-y)).
>
> > The do-damage function takes a game object and returns a map of zero
> > or more other game objects that are damaged by that game object. This
> > usually involves collision, which is handled in update-position, so
> > update-position needs to cache the damage information for do-damage to
> > retrieve. For instance, that rocket's update-position can discover
> > that it has collided with something and set the rocket's health to
> > zero as well as store a map of things within the blast radius and the
> > damage they should take. The new-things function sees the rocket's
> > health at zero and spawns decorative shrapnel objects, since it just
> > exploded; these will draw themselves and move for a short time in a
> > straight line but that's it. The do-damage function just retrieves the
> > map stored during update-position.
>
> > The apply-damages function takes a damages map and a world object,
> > looks that object up in the map, if not found returns it unchanged,
> > otherwise returns a copy with reduced health. The dead? predicate just
> > sees if a game object's health is zero or lower.
>
> > Then there's draw-on!, which takes an object and a Graphics. Without
> > that you'd be playing blind. It and flip-the-jpanel's-double-buffer!
> > are the only impure functions here, besides the player object's
> > update-position AI which needs to poll the keyboard/mouse state info.
>
> > So there's a basic skeleton for a functional game loop. The loop exits
> > if there's no player object in the world, which happens if all player
> > objects get nailed by the dead? predicate, so quitting can be
> > implemented by having the player's update-position check for a quit
> > keypress and set the player's health to zero (by returning a player
> > object with no health) if necessary.
>
> > The loop returns the final world-state, so it can be checked to decide
> > what kind of game-over screen to display. (If the game has a victory
> > condition, when this is detected the game can "kill" the player and
> > also put something in the world-state to allow victory to be
> > distinguished from quit or the player actually died.) You can also add
> > a surrounding loop to allow multiple lives. Pause can simply pause in
> > the middle of the player's update AI; the GUI won't hang since it's
> > not the EDT. The pause would of course replace the UI appropriately
> > until unpaused, and check periodically for the unpause (or resort to
> > Java's wait/notify to avoid a polling loop and use less CPU).
>
> > Of course, the above is somewhat naive. In particular the world state
> > is just an unordered seq of objects. This will scale poorly when the
> > game world has a big and complex scrollable map, or very many objects
> > to check for collisions. Then you'll want something smarter, with
> > "geography", such as an interval tree on the x coordinates or
> > something.
>
> > Note that a lot of game objects would display, but not do anything
> > (e.g. explosion and shrapnel graphics, particles, etc.); there should
> > be a decorative? predicate and collision checks can use (remove
> > decorative? world) to skip the things that should not collide with the
> > target. (The decorative? objects' own update AI presumably completely
> > eschews collision checks.) Other objects might not be visible, while
> > having an effect, e.g. an in-game timer that causes a timed effect.
> > If, for instance, new enemies are to be spawned randomly, this thing
> > can have a no-op draw-on! and a no-op update-position, but its
> > new-things can occasionally return a non-empty collection. Other
> > timers are better embedded elsewhere; e.g. if the player picks up an
> > invulnerability powerup, the time remaining can be stored in the
> > player object, or even the time when it will wear off. The latter
> > makes things very simple:
>
> > (let [invulnerable? (> 0 (- (:invulnerable-until this)
> > (System/currentTimeMillis)))]
> >   ...)
>
> > with invulnerability pickup working simply by making the return value
> > use (+ System/currentTimeMillis 5000) or whatever as the
> > :invulnerable-until key's value.
>
> > There's a few other things that could be done. For example, there's
> > nothing in there about sound effects, though since that's part of the
> > game's "display" perhaps sound can be handled with graphics in
> > draw-on!. That isolates more side effects into this bang-function.
> > (Music can be cued in the player's draw-on!; it
>
> ...
>
> read more »

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to