Quick summary: ClojureScript + Babylon.JS on the frontend and Clojure for the backend, coordination via Websockets.
The point about lack of tooling for Babylon.JS is interesting: a lot of times I see people wanting to release games but spending most of the time building tools... sometimes all of the time. It is a delicate balance!
> The point about lack of tooling for Babylon.JS is interesting: a lot of times I see people wanting to release games but spending most of the time building tools... sometimes all of the time. It is a delicate balance!
Ah we all know what we really want to do - build tools!
A game just makes the journey a tad more interesting.
Or, we want to make a game, but we also want to make the base a reusable game engine, so we can later make another game with it, and this way we can also make simpler tech demos on the way to impress our friends (real or on social media), and because of that we need to do this properly, and wait I remember the Virtual File System in StarCraft was fascinating to me as a kid, let's do something like it, and wow did you see this new ambient occluding path traced cube marched screen space voxel protrusion demo, I absolutely need to implement this for my game, and...
... this is how you end up making three skeletons of a game engine and never making that game itself, before running out of childhood and having to switch to doing webshit for a living to pay for mortgage.
This is bonkers and so cool that you did this solo. Loaded right up on my iPhone 13, prompted me to turn my phone to landscape and I was running around in a 3d world shooting spells at other players - really great work and surprisingly polished for being a solo project.
Thanks for taking the leap by making a game with Clojure!
I’m a aspiring gamedev and my focus so far has been working bottom up with C and friends. But I do love Lisp and especially Clojure, I’m really hopeful now that Jank is gonna be a full time project this year.
I am the same way with shooters: I play all games with inverted Y. It's just about personal preference and what you grew up playing. I believe the difference is whether you conceptualize mouse/joystick motions as moving your eyes vs pivoting your head.
normal: move joystick up = move eyes up
inverted: move joystick up = push top of head down
I found some old gaming instincts kicking in when I asked myself: did I get a frag?
This is a fun little romp. Very Quake-esque in terms of mechanics (though the floaty jumps have more of a Tribes feel). Excellent job putting it all together in a Lisp... even if that Lisp is Clojure! I like to think of Lisp as a mind-tool for creatives, a way to turn thoughts into code so quickly it can keep up with the highly iterative creative process for other endeavors and enhance the reach of people working in other digital media besides code. It's probably less relevant in that capacity today than it was in the past, though.
I have a question if you don't mind. I don't know clojure at all, and I may be misreading things, but from this comment on your site:
> Game development is fundamentally an art of state management. States are everywhere, and managing numerous unrelated systems in harmony is a challenging task. While Clojure’s immutability by default offers many advantages, it also introduces complexity. To handle the intricate state management required for game development, I had to create my own abstractions. Writing a custom DSL (domain-specific language) became a necessity, but it wasn’t easy.
And then this comment:
> In Wizard Masters, all game data resides in a global game database—a single large hashmap. The fields referenced in the :what block (e.g., :pointer-locked?, :player/ground?) are keys in this global hashmap.
Please don't take this the wrong way but you've essentially just worked around clojures functional immutable style and invented global state in a hashmap, right?
This is a common idiom, and does not necessarily imply mutation.
Even in cases where you often do have mutation, say in databases, the benefits of a functional approach lie having pure functions between mutations rather than one big ball of Xs sometimes mutatingYs via Zs.
Here[0] is a wonderful talk that demonstrates this in practice.
Interesting to see that I already had an account at CrazyGames. It wasn't loading from your URL, but I saw that it's CG so when I logged on with my account my Firefox played ball (I got ABP, Ublock, NoScript, PrivacyBadger, LARGE hosts file, so 'some' websites are broken ;)
Cool game, fast. Someone dominated me for 4 mins and then I decided to switch to Fortnite :)
Clojure programmers are a different breed. They're actually doing cool things with Lisp while other niche languages just talk a good talk. I'm staying away though, it's hard enough finding paid work in more mainstream stacks.
hmmm seems buggy, player is aiming at the sky continuously and I can't get it to make it aim at a normal level more than a microseconds with the mouse.
I don't think that's a great example, as I never heard of "range anxiety" until EVs started to become popular, and it was applied to and admitted by exclusively owners of EVs and also E-bikes.
But it's 2025, it was enough to downvote and ignore the GP comment... Even responding with a dismissive https://www.thejach.com/imgs/lisp_parens.png is too much in current year. Another comment mentions paredit/parinfer, I'm not exactly masochistic but I hate tools that automatically type more than indentation for me so I don't use them, but I also basically never think about counting parens.
I earn my paycheck in Clojure and have for about 10 years. In my experience if your code ends with ))))))))))) you're doing it wrong. This is a code smell for me.
Instead of (qux (baz (bar (foo x)))) use other tools:
- Big picture: is this code overly procedural? If so, is it possible to make it declarative and functional?
- Line-level picture: one of the thread macros, functional composition, etc. will make this more readable. For example: (-> x foo bar baz qux).
...you do know no s-expression editor makes you count or type those out manually, right? It's all handled by paredit or parinfer, unless you're a masochist.
In college Intro to CS was taught with Racket (a lisp) and we even had to write code with pen and paper during our exams. Got really good at quickly visually matching parentheses which is still helpful to me today in non-lisps. (But given that the handwritten code would never be run, you could also just fudge it and write a bunch of )))))'s at the end and hope the TA grading it wouldn't count them either)
Quick summary: ClojureScript + Babylon.JS on the frontend and Clojure for the backend, coordination via Websockets.
The point about lack of tooling for Babylon.JS is interesting: a lot of times I see people wanting to release games but spending most of the time building tools... sometimes all of the time. It is a delicate balance!
Congratulations on releasing! Very cool project.
> The point about lack of tooling for Babylon.JS is interesting: a lot of times I see people wanting to release games but spending most of the time building tools... sometimes all of the time. It is a delicate balance!
Ah we all know what we really want to do - build tools!
A game just makes the journey a tad more interesting.
Or, we want to make a game, but we also want to make the base a reusable game engine, so we can later make another game with it, and this way we can also make simpler tech demos on the way to impress our friends (real or on social media), and because of that we need to do this properly, and wait I remember the Virtual File System in StarCraft was fascinating to me as a kid, let's do something like it, and wow did you see this new ambient occluding path traced cube marched screen space voxel protrusion demo, I absolutely need to implement this for my game, and...
... this is how you end up making three skeletons of a game engine and never making that game itself, before running out of childhood and having to switch to doing webshit for a living to pay for mortgage.
Ask me how I know.
I love the saying "running out of childhood". Going to look for places to use that.
This post was physically painful to me, and also makes me paranoid that someone else has access to my /mnt/old_projects filesystem.
Me and you brother. Me and you.
Great post! "Running out of childhood", and "webshit": I have to remember these...
This is bonkers and so cool that you did this solo. Loaded right up on my iPhone 13, prompted me to turn my phone to landscape and I was running around in a 3d world shooting spells at other players - really great work and surprisingly polished for being a solo project.
I'd say relatively unsurprising for a Clojure project, props to the OP.
Author here, thank you all for taking the time to read about my journey and, of course, for playing my game.
I’m very glad that you liked it!
Thanks for taking the leap by making a game with Clojure!
I’m a aspiring gamedev and my focus so far has been working bottom up with C and friends. But I do love Lisp and especially Clojure, I’m really hopeful now that Jank is gonna be a full time project this year.
Please include a little animated GIF or something that gives us a taste of what it looks like!
Game's linked at the top of the blog[0], and loads in the browser, but agree a gif would be nice.
[0]: https://wizardmasters.io/
PLEASE PLEASE PLEASE option to invert mouse Y-axis.
It looks fantastic, but I can only play if down on mouse is up on view.
lol, it's a shooter, not a flight sim!
I am the same way with shooters: I play all games with inverted Y. It's just about personal preference and what you grew up playing. I believe the difference is whether you conceptualize mouse/joystick motions as moving your eyes vs pivoting your head.
normal: move joystick up = move eyes up
inverted: move joystick up = push top of head down
I found some old gaming instincts kicking in when I asked myself: did I get a frag?
This is a fun little romp. Very Quake-esque in terms of mechanics (though the floaty jumps have more of a Tribes feel). Excellent job putting it all together in a Lisp... even if that Lisp is Clojure! I like to think of Lisp as a mind-tool for creatives, a way to turn thoughts into code so quickly it can keep up with the highly iterative creative process for other endeavors and enhance the reach of people working in other digital media besides code. It's probably less relevant in that capacity today than it was in the past, though.
I played a ton of Quake and Tribes 2 I felt very at home not sure if it was intentional but definitely an awesome result.
I have a question if you don't mind. I don't know clojure at all, and I may be misreading things, but from this comment on your site:
> Game development is fundamentally an art of state management. States are everywhere, and managing numerous unrelated systems in harmony is a challenging task. While Clojure’s immutability by default offers many advantages, it also introduces complexity. To handle the intricate state management required for game development, I had to create my own abstractions. Writing a custom DSL (domain-specific language) became a necessity, but it wasn’t easy.
And then this comment:
> In Wizard Masters, all game data resides in a global game database—a single large hashmap. The fields referenced in the :what block (e.g., :pointer-locked?, :player/ground?) are keys in this global hashmap.
Please don't take this the wrong way but you've essentially just worked around clojures functional immutable style and invented global state in a hashmap, right?
This is a common idiom, and does not necessarily imply mutation.
Even in cases where you often do have mutation, say in databases, the benefits of a functional approach lie having pure functions between mutations rather than one big ball of Xs sometimes mutatingYs via Zs.
Here[0] is a wonderful talk that demonstrates this in practice.
0. https://youtu.be/vK1DazRK_a0?si=tmXr4NxDpZs48Omz
Interesting to see that I already had an account at CrazyGames. It wasn't loading from your URL, but I saw that it's CG so when I logged on with my account my Firefox played ball (I got ABP, Ublock, NoScript, PrivacyBadger, LARGE hosts file, so 'some' websites are broken ;)
Cool game, fast. Someone dominated me for 4 mins and then I decided to switch to Fortnite :)
Clojure programmers are a different breed. They're actually doing cool things with Lisp while other niche languages just talk a good talk. I'm staying away though, it's hard enough finding paid work in more mainstream stacks.
Holy cow, I've been playing for 20 minutes without realising. This is amazing!
This was so much fun! Great work! Really brought me back to Quake 3!
hmmm seems buggy, player is aiming at the sky continuously and I can't get it to make it aim at a normal level more than a microseconds with the mouse.
> player is aiming at the sky continuously
Same here :(
I'm guessing this wasn't tested (at all?) in Firefox? That's unfortunate.
Works fine for me on Firefox (Windows).
Ditto, on Linux.
Hi Ertu! Great to see your work again! Keep it up!
Was surprised that it loaded on my phone, nice work!
Game is actually really good! Was this inspired by Spellbreak per chance?
Edit: I see you mention it in your blog post. I enjoyed it just as much!
Cool! Even though my computer is seemingly too underpowered to play it. Anyway, do you plan to make it a big game?
I guess no, if it performs really well I might consider it.
I really hope that you will succeed! Maybe you'll want to find a way to earn money with it though
Pretty cool, but it seems I crashed it to a grey screen. Even game counter stopped. I was battling someone, was fun though.
How did you learn how to make games? Any books you recommend? Or github projects you learned from?
I had a lot of fun, thanks for sharing!
The settings don't appear to allow y-axis inversion, but maybe I missed it.
Incredible job! Hopped in a bit and got some great kills. Reminds me of quake
20k 0d stuck in a pillar and killed by a bot
That is seriously impressive. Well done!
Damn this is a good game.
Looks quite nice.
whaaaa this is too good!
How many closing parenthesis do you have on a last line?
Things that terrify ICE car drivers about electric vehicles: Range.
Things actual EV drivers rarely worry about: Range.
Things that terrify non-Lisp programmers about Lisp: Parentheses.
Things actual Lisp programmers rarely worry about: Parentheses.
Bit of a selection bias there, people with range as a concern don’t become EV drivers, same goes for people terrified of parentheses.
I don't think that's a great example, as I never heard of "range anxiety" until EVs started to become popular, and it was applied to and admitted by exclusively owners of EVs and also E-bikes.
But it's 2025, it was enough to downvote and ignore the GP comment... Even responding with a dismissive https://www.thejach.com/imgs/lisp_parens.png is too much in current year. Another comment mentions paredit/parinfer, I'm not exactly masochistic but I hate tools that automatically type more than indentation for me so I don't use them, but I also basically never think about counting parens.
I earn my paycheck in Clojure and have for about 10 years. In my experience if your code ends with ))))))))))) you're doing it wrong. This is a code smell for me.
Instead of (qux (baz (bar (foo x)))) use other tools:
- Big picture: is this code overly procedural? If so, is it possible to make it declarative and functional?
- Line-level picture: one of the thread macros, functional composition, etc. will make this more readable. For example: (-> x foo bar baz qux).
As many as needed for the level of nesting?
...you do know no s-expression editor makes you count or type those out manually, right? It's all handled by paredit or parinfer, unless you're a masochist.
It's just not something any lisper thinks about.
In college Intro to CS was taught with Racket (a lisp) and we even had to write code with pen and paper during our exams. Got really good at quickly visually matching parentheses which is still helpful to me today in non-lisps. (But given that the handwritten code would never be run, you could also just fudge it and write a bunch of )))))'s at the end and hope the TA grading it wouldn't count them either)
Average Northeastern student.
The indentation of the lines is what matters not the number of parentheses on the last lines.
If you want to have closing parentheses on separate lines the code simply becomes to long.
I didn't understand this when I started writing Lisp, but that style has its advantages and is supported by the editors.
Exactly as many as opening! (I mean what else did you expect?) :)