My work today mainly involved planning out Serpent’s features in more detail. I decided on the screens that would be featured and, on the options screen, what gameplay parameters can be tweaked. In my Day 0 post I referred to these parameters as a “Bastion-style difficulty system”, but from now on I’ll be referring to them as mutators — ways to change the game. A rough outline of what I came up with is available at
spec.txt in the git repository.
Once I’d decided on the game rules and the relationships that would exist between various screens of the game, I wrote a small description of them in Idris. (This doesn’t involve any implementation yet, just stating relationships and enumerating the actions available from each screen.) You can see the results of this in
Spec.idr in the git repository (the relevant definitions are
Serpent), but this part merits further explanation:
The standard way to do game programming (or anything else that you might think of as having a “main loop”) in a functional language is like this. You define a data type for storing the game state, and instead of a traditional main loop you have a function from game states to game states. (In Haskell-like languages, this might return an
IO GameState instead, or it might be a pure function that also takes a representation of the user input state like I used in Pong.) And then you just recursively call this function forever, and that’s the equivalent of a game loop.
The method encouraged by chapter 6 of this Effects tutorial is a refinement of the standard one. Instead of having all game states be the same type, you introduce an additional layer of categorization: a game state that represents the player looking at the main menu might be a different type from one that represents a player in the middle of a game. The reason for this is to encode the notion that some actions only make sense in certain situations: you can only “turn left” if you’re in the middle of actually playing a game and haven’t paused. The Effects library lets us make these kinds of statements known to the compiler.
Here’s the categorization of game states I ended up using, and a brief explanation of my reasoning:
Playing, which represents actually being in a game.
Playingstates are further divided into paused and unpaused, which I represent as
Playing Falserespectively. In unpaused states, the player can pause or issue commands to the snake; in paused states, the options are unpausing, restarting, or quitting to the main menu. I thought of storing extra information in this category, such as the length of the snake and how many free spaces or pieces of food were on the board, but I decided against it for the moment because the way these things change is heavily influenced by mutators. I might factor these into the categorization if I find a neat way of doing so.
- Next, the
MainMenu. Here, the player can click one of a set number of buttons, such as “new game” or “change mutators”. Since I have a pretty good idea of what the buttons are going to be, this category doesn’t have any internal structure. All game states representing the main menu have the same type.
Menus (of which my current design calls for only one, the mutator menu, but there could conceivably also be a sound/graphics options menu) are further sub-categorized by the set of inputs available, where an “input” might be a named toggleable button or numerical slider. The available actions are then to update one of the inputs (the first half of
Spec.idris concerned with describing the different ways to do this) or to exit the menu with or without saving.
- Finally, the
GameOverscreen. Like the
MainMenu, game-over states are not further categorized: from any game over screen, the available actions are to restart or quit to the main menu.
None of this says anything about what information any kind of game state should contain, or how to apply one of the actions once the player has chosen one to reach another game state — so that’s what I’ll be doing tomorrow.