1
votes

I'm making a programming game to teach myself Clojure. Basically it is a server that polls clients for actions with JSON over HTTP.

Currently I have whole game state as a vector atom that contains individual player state maps. The server updates game changes into these player states and polls clients for their desired next action.

I've read some stuff on atoms and agents and others but I haven't quite figured them out yet.

My question is: How should I change the data structure or its storing mechanism and how should I do the polling and other updating so that they would not interfere with each other or run cleanly at the same time?

I figured out that the polling should probably be done with agents (am I right?). And maybe I should add a watcher that updates returned value to the player state which is managed in server.

I've also thought that to enable this I should change the game state into a map so that I can easily access individual player states.

(def game-state {"player1" {:stuff {...} :action a1}
                 "player2" {:stuff {...} :action a3}})

Should the game state be comprised of player states as atoms, like this: {"player1" atom "player2" atom}

... or something else?

On top of this all there is a visualiser HTML/JavaScript(AngularJS) page that regularly polls the game state from the server.

Currently as I don't have threaded polling everything else is stuck while a slow client thinks its next action (test case).

Any opinions and advice on how to do this correctly in Clojure is appreciated.

ps. I haven't included a database because I felt that storing data over game sessions was not needed, but if using a DB makes this easier or more correct I could probably use some in-memory DB.

1
I'd actually use core.async for something like this, and plain vanilla atoms for managing state. And use as few atoms as possible; in this case, you need only one: def game-state (atom {:player1 ... :player2 ...})Charles Duffy
Agents, generally speaking, are the Right Tool only when you want them to encapsulate state managed by a thread -- if all you want is a thread, just use a thread; Rich himself has stated that using Java standard-library threading and thread-pool abstractions is the correct and idiomatic thing when they're the best fit for a task.Charles Duffy
By the way, I'd strongly suggest putting The Joy of Clojure on your reading list; it spends a lot of time and focus on idiom and choosing the right tool for a given job.Charles Duffy
Thanks for the tips. Actually I'm waiting for the latest edition of The Joy of Clojure to come out of print...Ari P

1 Answers

0
votes

I finally got around to do this.

I ended up changing the game state into a map. I left it as an atom as suggested.

For client polling I defined a thread pool

(def ^ExecutorService poller-thread-pool (Executors/newCachedThreadPool))

And as Clojure functions are Runnable I just call the function that requests action from client and then updates the game state for that particular player.

(let [player-keys-and-states (seq game-state)
      threads (map request-and-update player-keys-and-states)
      tasks (map #(.submit poller-thread-pool %) threads)]
    (dorun tasks))

Dorun is required to start each thread/function since map returns a lazy sequence.

Edit

I had to change this so that only one thread updates the values in game-state since it ended up in strange states when those poller threads changed it too. I added a new map for the pollers to update and then have the "main" thread read for that map and update the game-state with the values there.