Lumines is a block-dropping game similar to Tetris, where the player uses 2x2 tetrominos containing tiles of two different colors to attempt to make completely filled rectangular shapes of the same color on the playing field, with larger shapes earning more points.
Like Tetris, Lumines proceeds via clock events called “ticks”. At every tick, the currently active block moves down one row, if it can. If it can’t move down, it becomes affixed to the board, and a new piece is generated at the top of the board. The game ends when the game board is in an invalid state after a tick.
Users can interact with the game by shifting, rotating, or dropping the block. The controls are:
- [w] -> rotate left (counterclockwise)
- [s] -> rotate right (clockwise)
- [a] -> shift left
- [d] -> shift right
- [space] -> drop to bottom
For this exercise, we have implemented an event-loop that drives the game and handles keyboard input, a graphic module that deals with rendering the game onto the screen, and the logic for handling the “sweeper” for the game.
You will have to implement the logic that keeps the game state correct and updates things based on those inputs!
Feel free to play around with the working solutions demo before getting started. It will be helpful to understand the game’s mechanisms when working on these exercises!
We have implemented a simple event-loop that drives the game and handles graphic rendering as well as keyboard input in lumines.ml. Feel free to take a look to get a sense of how the game engine works under the hood.
Here, we provide an overview of some important parts of the code. Even for the files that we don’t cover here, it might be helpful to glance at its interface file to get a better sense of all of the pieces.
A [Filled_square.t] represents a square on the board that is currently occupied by a square. Make sure to read over filled_square.mli and familiarize yourself with the different states of a [Filled_square.t] and what they represent.
A [Sweeper.t] contains the driving logic for clearing the board. It is already implemented, and will take care of calling the functions that will remove squares from the board. Go ahead and glance at sweeper.mli to see its interface.
A [Board.t] represents the grid of squares that constitutes the main plaing area, in which blocks can move around. It also contains the “landscape” of blocks that have fallen to the bottom and become affixed.
A board is implemented as a 2-dimensional array containing [Filled_square.t option]s, which are [None] if the square is empty, and otherwise [Some filled_square].
Make sure to take a look at board.mli to understand its structure.
A [Moving_piece.t] is a single tetromino (2x2 block) that is currently “active” (i.e. moving down) on the board. It will handle the logic for rotating the block.
Make sure to take a look at moving_piece.mli to understand its structure.
A [Game.t] represents the entire game state, including the current [moving_piece] and its position relative to the board, the [board], and some additional metadata. It handles the movement of the [moving_piece] relative to the board. It also holds a [Sweeper.t].
Make sure to take a look at game.mli to understand its structure.
Please note! [Game.t] treats the origin of the [Board.t] as the lower left corner!
The functions for you to implement are in
To compile the tests, run:
$ dune runtest
To run the game, run:
$ dune exec bin/lumines.exe
A suggested ordering for working through this (though feel free to do in a different order if you prefer) is:
- [ ] moving_piece.ml: [rotate_left]
- [ ] moving_piece.ml: [rotate_right]
You can test the functions in phase 1 by running the game and rotating the piece even if you don’t yet have a way to move the piece on the board.
Once you have these two functions, you’ll be able run the game and see pieces fall down on the board.
After completing phase 3, you can run the game and move pieces around.
You should now be able to play lumines!
Once your game is working, there are many fun extensions that you can try to implement!
Some examples, for inspiration:
- calculate and display a score
- make the game speed up over time
- change the color scheme after a certain number of blocks have been cleared
- add blocks that have different abilities (e.g. one that clears adjacent blocks)