A guideline compliant (as far as I can tell) Tetris clone made with SDL2.
Initially the game was going to be implemented with Gloss, which is a wonderful library, I really like it. The main issue I ran into is that Gloss programs, no matter how simple or complex, use a lot of processing, literally making my CPU's fan spin hard. I don't know if that's a Windows thing or whatnot, but I ended up going with SDL2, with which I can avoid this issue.
This is called V2
because the first version was my Gloss attempt.
stack build
should do it! I'm not sure how to compile this project with cabal
as I don't really use it, pull requests are welcome though!
To execute, just run stack exec HetrisV2-exe
.
Make sure SDL2, as well as SDL2 Mixer and SDL2 Font, are installed on your machine or compilation will fail. To do so on Windows, I used MSYS2 as it felt like the easiest way.
Taken from the guideline
- Up arrow and X: rotate 90° clockwise
- Space: hard drop
- Shift and C: hold
- Ctrl and Z: rotate 90° counterclockwise
- Esc and F1: pause
- Left and right arrows: movement
- Down arrow: soft drop and lock delay cancel.
This is also present on the game's Help screen (located in its main menu).
The game is still not completely implemented, a few things are missing from the guideline, specifically:
- Different modes, like marathon and ultra modes.
- Polishing in general.
Feel free to open issues (or PRs) about any non-compliance you find.
At the top level, the directories are app
, which contains the source code for the game, and assets
, which has the images, sounds and fonts the game uses.
The Types
module contains all relevant types used for the game. If you want to know what a specific type looks like, that's very likely the place to look.
As for the actual code organization, as I mentioned before, I really like Gloss's API. So much so, in fact, that I made something similar to its play
function and adapted it to be used with SDL2. It's called mainLoop
(located in the UtilsSDL
module). For those who are not familiar with that, it basically means that our mainLoop
takes a bunch of arguments, the most important of which are:
- An input function that changes the state of our game in response to things like keyboard presses and mouse movement, with the type:
SDL.EventPayload -> world -> IO (Maybe world)
- A tick function which has a delta time argument, which changes the state of our game in response to time, with the type:
Double -> world -> IO (Maybe world)
- A render function, responsible for printing the current state of our game to the screen, with the type:
SDL.Renderer -> world -> IO ()
world
in that context is a type variable. The input
and tick
functions both use Maybe
to indicate whether the game should be closed by returning Nothing
or keep running by returning a new world
.
In the case of our game world = MainState
(as defined in the Types
module). The input
, tick
and render
function declarations that run our game can be found inside the Main
module.
Speaking of MainState
, it contains all assets loaded at startup as well as a sum type that controls the current "context" of the game (I called it mainPhase
but it probably deserves a more fitting name). This is what that sum type looks like:
data MainStatePhase
= CountingDown CountingDownState
| Game GameState
| Paused PauseState
The important one in there is Game
, that's when our game is actually running. The other two are related to the pause function and game start. Each of those has its own module for its set of (input, tick, render)
functions (CountingDown
, Game
and Paused
respectively).
The idea is that as the game grows, more entries will be added to MainStatePhase
. For example, when a main menu gets added, a new entry will show up there for it. The same thing for a Help screen or a Credits screen and so on. Each of these will have their own set of (input, tick, render)
functions.
The actual game rules are pretty complex, so it's probably not worth explaining them here (check the guideline for more detailed information on that).
Images are all .bmp, sounds are all .wav and fonts, .tff. That's about it.