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 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
--- 
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 clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to