2

I'm trying to learn some Clojure. I made a very basic model of the kids game "chutes and ladders". When a player gets a score greater or equal to 100, the game is over. Right now this only works for only player.

start game with "(play player1)".

I'm stuck on how I would go about making it multiplayer, having to use recrusion and no "foreach" statement.
How could I modify this to be multi-player?

(defn roll []
 (+ 1 (rand-int 6)))

(def chutes { 10 5, 12 3, 55 38, 77 69})
(def ladders { 12 16, 10 25, 20 55, 77 91})

(defn apply_chutes [player]
 (if (contains? chutes @player)
  (do (reset! player (chutes @player))
      (println "down chute! " @player))))

(defn apply_ladders [player]
 (if (contains? ladders @player)
  (do (reset! player (ladders @player))
  (println "up ladder! " @player))))

(defn move [player]
 (do(swap! player + (roll))
  (println "p: " @player)
  (apply_chutes player)
  (apply_ladders player)
    player))
  (if 
("done")))

(defn play [player]
 (move player)
 (if (>= @player 100)
  (println "done")
  (play player)))

(def player1 (atom 0))
megakorre
  • 2,213
  • 16
  • 23
Rob Buhler
  • 2,469
  • 4
  • 27
  • 35
  • 1
    Are you asking about Clojure, recursion or game design? Make your question for precise, describing exact problem, please. – ffriend Jan 08 '12 at 02:35
  • Not really game design, although I would appreciate any functional programming tips - I want to know how to make this work for multiple players, but stop when the first player breaks 100. Specifically the "play" function - modifying it to work for multiple players. I would think I should use something like doseq or the higher order function map, but how do I stop iterating through the sequence of players when the game ends? Should I be using recursion and calling the "play" function for a different player each time? Does that help some? I know it's not real specific. – Rob Buhler Jan 08 '12 at 12:19

1 Answers1

3

Ok, I will not provide you with complete code, but better with high-level design with regards to functional programming and in particular looping/recursion in Clojure.

Let's start with a very important concept in functional programming - immutability. Functional programs try to avoid any change of state of the system. Rather than changing the state, programs are to produce new state. For example, if you have vector v1 = [1, 2, 3, 4] and want to insert 5 at the end of it, you don't destroy v1, but rather produce v2 = [1, 2, 3, 4, 5] (v1 stays in the memory unchanged). See this question for more details on immutability in functional programming.

Having this, the best way to go is to create special variable state, that will hold overall state of the game.

Next thing to consider is looping. Again, in functional programming loop concept is almost replaced with recursion. Loops and recursion are very often similar - both allow you to repeat some code many times before it terminates. In most imperative programming languages (e.g. Python, Java) recursion leads to stack growth, but in functional languages there's very popular conception of tail recursion. If you are not familiar with this concept, I highly recommend you learning it. For now I only say, that tail recursion can occur only at tail position (last statement in control flow) and it doesn't lead to stack growth and thus may be used as loop construct.

In Clojure tail recursion is organized with recur keyword:

(defn play [state ...]
   ...
   (recur new-state ...))

Here, we define play function with parameter state and call it recursively with keyword recur at the last line (we can call it as (play new-state ...) too, but in this case JVM won't optimize code to be tail recursive). new-state is defined somewhere in the function body and stands exactly for what it means - new state of a game.

Finally, you want to make your game multiplayer, that is, change current player after each iteration. With loop/recursion it may be easily achieved just by interchanging players:

(defn play [state current-player next-player]
   ;; do play with current-player and compute new-state
   (recur new-state next-player current-player))

Notice, that in recur call players interchanged their positions, and thus next player became current player and vice versa in the new call of play.

Having this, you should be able to translate your code to a new version.

Community
  • 1
  • 1
ffriend
  • 27,562
  • 13
  • 91
  • 132
  • It does not feel natural yet for me to think in terms of recursion how to manage state in clojure. – Rob Buhler Jan 08 '12 at 17:50
  • @Taylor: now you have state in global variables `chutes` and `ladders`. What you should do is to put them into map `{:ladders ladders, :chutes chutes}` and pass this map to all functions that need access to the state. To create new state do something like `(let [new-state (assoc old-state :chutes new-chutes :ladders new-ladders)] ...)`. Also make functions, that change one of variables, just to return this variable, e.g. make `apply-ladders` to take state and old player and return just new player, without modifying anything in the state or player. You should end up without any `vars`. – ffriend Jan 08 '12 at 18:33