Here's an example of using a state monad for updating a position. The state
goes into a simple map and there's a function to add coordinates.
(def init {:position [100 100] :st :st0 :keys-held #{:left}})
(defn v+ [v1 v2] (vec (map + v1 v2)))
The state monad can compute a value and maintain some arbitrary state. In
this case, the statements produce side-effects but nothing is actually
computed, which is fine. You'd want to model each statement to encapsulate
some action and return a State instance. For simplicity, the function move
mixes the conditional statement with the then-branch, but that can be
easily separated later:
;; shameless plug
(use 'blancas.morph.core
'blancas.morph.monads)
(defn move [key v]
(monad [held (gets :keys-held)]
(if (held key)
(modify-state #(update-in % [:position] v+ v))
(state :empty-stmt))))
This function takes the key that was pressed. If it's one held in storage,
the state will be modified pretty much the way it was before; otherwise the
statement evaluates into a state instance whose value is the empty
statement.
Each of the following elements models a conditional statement: if x do this:
(def stmts [(move :left [-10 0])
(move :right [ 10 0])
(move :up [ 0 -10])
(move :down [ 0 10])])
This "runs" the sequence of monads and returns the value of the resulting
state (not the value since it's not computing anything:
(exec-state (seqm stmts) init)
;; {:position [90 100], :st :st0, :keys-held #{:left}}
Here we do the same but then change the key and re-evaluate the statements.
The combinators seqm and >> are similar; seqm takes a collection.
(exec-state (>> (seqm stmts)
(modify-state assoc :keys-held #{:down})
(seqm stmts))
init)
;; {:position [90 110], :st :st0, :keys-held #{:down}}
This shows a computation; say you want to compute: [50 50] + [12 -5]
For this you'd write a new version of v+ that takes "monadic" args (boxed
in a State). As above, the (monad) macro binds the results of monads to the
variables, as in a let. Then wraps the result in a state:
(defn v+ [v1 v2]
(monad [x v1 y v2]
(state (vec (map + x y)))))
(This function could take simple vectors, but in a real use case you'd be
taking expressions, not just values.)
Now you can get the result like so:
(eval-state (v+ (state [50 50]) (state [12 -5])) init)
;; [62 45]
If you want both the computed value and the finate state you can get them
both in a Pair:
(run-state (v+ (state [50 50]) (state [12 -5])) init)
;; Pair([62 45],{:position [100 100], :st :st0, :keys-held #{:left}})
On Monday, February 11, 2013 12:10:24 PM UTC-8, JvJ wrote:
>
> I'm writing a simple game engine in Clojure, and each game object supplies
> its own unique update function, which takes the original object (a map of
> properties) and returns an updated version. However, writing the updates
> is somewhat cumbersome because each line of code has to return either the
> original or updated object. I'd like to see if I can clean up this code,
> possibly by using monads (which I don't understand very well). Does anyone
> have any advice? Thanks (Code examples below)
>
> The pseudocode for what i want to do looks something like this:
>
> if left key is held
> g.position += [-10 0]
> if right key is held
> g.position += [10 0]
> if up key is held
> g.position += [0 -10]
> if down key is held
> g.position += [0 10]
> if q is pressed
> fire event {:type :dialogue, :text "Hello"}
> if space is pressed
> g.switchstate(:s2)
>
>
> But the code I ended up writing is this mess:
>
>
> (fn [g]
> (-> g
> (#(if (@*keys-held* :left)
> (update-in % [:position] v+ [-10 0])
> %))
> (#(if (@*keys-held* :right)
> (update-in % [:position] v+ [10 0]) %))
> (#(if (@*keys-held* :up)
> (update-in % [:position] v+ [0 -10]) %))
> (#(if (@*keys-held* :down)
> (update-in % [:position] v+ [0 10]) %))
> (#(if (@*keys-pressed* \q)
> (do (fire-event {:type :dialogue
> :text "Hello!"})
> %)
> %))
> (#(if (@*keys-pressed* :space)
> (do (comment (println "spaced!"))
> (switch-state % :s2)) %))))
>
--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.