clojure-gamedev

NoahTheDuke 2020-12-01T14:30:38.007500Z

Hey all, I’m experimenting with rewriting my game to be “pure”/stateless: pass in the current game state and player action to a function, get the next state back. Are there any resources out there for structuring the code so the number of helper functions doesn’t explode?

NoahTheDuke 2020-12-01T14:31:48.009100Z

Right now the game is built on passing a state atom around and doing a fair amount of swap!ing inside the functions

2020-12-01T18:54:23.010900Z

It depends a lot on how your game is architected. One technique I’ve seen is to skip atoms entirely, and make a single recursive “run game loop” function that takes the entire state as an argument.

2020-12-01T18:55:21.011100Z

https://prog21.dadgum.com/23.html Article on “purely functional retrogames,” by an 80s console game programmer

aaron-santos 2020-12-01T19:00:33.013500Z

For my roguelike I went passing in gamestate and producing a new gamestate as output. It works fairly well, but I can see how some may not be interested in adding an additional parameter to many functions.

NoahTheDuke 2020-12-01T19:15:06.015400Z

well, we already pass around the game state atom because it's a web game so there's a map of game-id to game atom, and when a player makes a move, we grab their atom and pass it into the corresponding action function (along with any other relevant stuff), so i don't mind passing around state as the first variable in all functions lol, and because it's a turn-based card game, we don't need the "run game loop"

NoahTheDuke 2020-12-01T19:16:53.017300Z

i'm more asking about, like, how to handle when a bunch of things are changed in a single place: "draw 2 cards" means we have to update both card objects with the new locations, trigger "player 1 drew 2 cards" event handlers, move the card objects from the deck to the hand, write the message "player 1 drew 2 cards" to the game log, etc

NoahTheDuke 2020-12-01T19:18:01.018600Z

this is pretty easy when using an atom, because like a classic variable, i can just pass it around and not have to nest let bindings, or even just call swap! manually to change a flag

2020-12-01T19:18:28.019300Z

It sounds like the overhead of an atom is maybe not worth it, if it’s all single threaded and you don’t mind passing it around everywhere?

NoahTheDuke 2020-12-01T19:18:34.019500Z

@paul.legato i'll read that article, thanks for the link

NoahTheDuke 2020-12-01T19:19:23.021400Z

yeah, it's all single-threaded, and because it's a web game, rendering is handled by clojurescript/reagent client-side

2020-12-01T19:19:37.021700Z

One way to make it pure functional involves discarding the notion of “objects.” It’s all just one big map, with deeper maps nested in it. Every bit of state is somewhere in that map.

2020-12-01T19:20:03.022100Z

The game loop is (from that article):

repeat forever {
   get user input
   process one frame
   draw everything on the screen
   wait until a frame's worth of time has elapsed
}

2020-12-01T19:20:54.023Z

get user input returns “player pressed the draw 2 cards button”. process one frame takes that + the current huge state map as its arguments, and returns a new state (with 2 cards now drawn). Draw updates the screen to show two cards drawn, etc.

2020-12-01T19:21:49.023400Z

This should work well with reagent-type reactive UI rendering. It’s a very similar structure.

2020-12-01T19:22:30.024300Z

The map that tells us the state of the cards doesn’t have to know where they are drawn on the screen. That’s the UI renderer’s job. Likewise, the UI renderer doesn’t need to know how two more cards got added to the state map.

2020-12-01T19:23:43.025100Z

Similarly, there are no event handlers in this model. That is all driven reactively, changing the state map by returning a new state map from some “process one frame” function.

2020-12-01T19:26:14.027100Z

e.g. initial state:

{:deck [ {:10 :hearts} {:J :clubs} ... ]
 :player_hand [{:k :hearts} ... ]}
Call (process-frame current-state user-input), get a new state back like:
{:deck [ {:10 :hearts} ... ]
 :player_hand [{:k :hearts} {:J :clubs} ... ]}

NoahTheDuke 2020-12-01T19:35:57.027400Z

yeah, that sounds about right