cnake: A snake game in clojurescript

451 views
Skip to first unread message

Joaquín Oltra Hernández

unread,
Jan 30, 2014, 4:23:59 AM1/30/14
to clojur...@googlegroups.com
Hi!

I've mostly finished a game I've been writing the last couple of weeks. It is a snake game written with clojurescript and canvas. It also uses core.async for coordination and communication between the UI and the game logic.

Inline image 1

It is my first clojure/script program (I've dabbled with the language a lot before not really doing anything). Here is the demo:

It is a bit more complex than it could be for that kind of game but I wanted to experiment with certain ideas and core.async.

A bit about the architecture:
The game is separated in UI and Game logic.

The UI instantiates the game logic, and they are separated by an input channel (commands that the game receives) and an ouput channel (notifos that the game logic spits).

Some of the whys are:
With the commands I'm am able to directly map and filter key events to commands and pipe those to the game via chan (which results in a pretty elegant solution).
https://github.com/joakin/cnake/blob/master/src/cnake/ui.cljs#L205

With the notifos, it would be easy for example to react to events of the game (not just reading its state to paint), for example to play sounds when moved, or when a pill is eaten (thats on the TODO)
https://github.com/joakin/cnake/blob/master/src/cnake/game.cljs#L190
https://github.com/joakin/cnake/blob/master/src/cnake/ui.cljs#L123

With this separation it should be easy to implement a different UI without touching the game logic at all, and just by sending commands and listening to notifications, which is pretty cool and simple.

Another interesting thing (in my opinion) is that the game coordination and state is enclosed in a state machine in the game/game! function that mutates state in the fn listening to the commands and recuring. That way the rest of the game is implemented through pure functions that get data, modify it and return it.

The code is fairly commented, and it is not very long, 200 lines each ui and game file.

This is my first complete program in clojurescript (and also using canvas btw). I would love to get feedback on code style, structure, idioms, or whatever else.

Cheers

1-small.png

adrian...@mail.yu.edu

unread,
Jan 30, 2014, 11:21:49 AM1/30/14
to clojur...@googlegroups.com
Congrats! Welcome to the wonderful world of ClojureScript. =)

The game itself works well, restarting easily with no bugs that I encountered if you die, so I have no suggestions there.

As for the code, I suggest (but this is really a matter of preference after all) that you move your static resources into a "resources" directory.

It's automatically added to the classpath by Leiningen so it's become a fairly standard way of organizing static resources used by a Clojure project. Example layouts are abound; here's one of my projects for example: https://github.com/aamedina/processing.cljs

Aside from that, I noticed you used docstrings for function calls but not for defs - def too can take an optional docstring at the first argument.

I think your "game!" loop was well defined, very good job!

Overall, I'd say this is a fantastic first foray into ClojureScript. You got to use a lot of different cool bits of what it has to offer - interop with the host (by using canvas), core.async for coordinating asynchronous I/O, and it does what you set out for it to do well.

Adrian

Jonas Enlund

unread,
Feb 1, 2014, 5:00:53 AM2/1/14
to clojur...@googlegroups.com
Hi

Nice. I have also created a snake game (not as complete as yours) in ClojureScript if you want to take a look and compare approaches: http://cljsfiddle.net/fiddle/jonase.snake

Jonas

On Thursday, January 30, 2014 11:23:59 AM UTC+2, Joaquín Oltra wrote:
> Hi!
>
> I've mostly finished a game I've been writing the last couple of weeks. It is a snake game written with clojurescript and canvas. It also uses core.async for coordination and communication between the UI and the game logic.
>
>
>
>
>
>

Joaquín Oltra

unread,
Feb 3, 2014, 4:09:58 AM2/3/14
to clojur...@googlegroups.com
Thanks!

I did not know that you could do docstrings in def, I'll definitely change it. For the statics in resources/ I'll leave them there since it is a really small project, but I'll keep it in mind for future projects. One thing to note is that the mies template puts the statics in the root of the project, so people like me may feel like that's the usual thing.

I see that in your project you are using npm and grunt for the web tasks. That's very cool, and it is something I mean to experiment a bit further down the road. Is there any reason for keeping the config files (package.json and Gruntfile.js) in the resources folders instead of in the root folder?

Joaquín Oltra

unread,
Feb 3, 2014, 4:17:34 AM2/3/14
to clojur...@googlegroups.com
Very cool, a simple readable example. The self-collide? is a smart little function, I did that using sets.

(defn self-collide? [snake]
(not= (count snake)
(count (distinct snake))))


There is a great writeup about writing a multiplayer game with clojurescript and core.async. Really interesting architecture and pretty similar to what I got to. I may be of interest to some:
http://ragnard.github.io/2013/10/01/clojurecup-pong-async.html

Reply all
Reply to author
Forward
0 new messages