As said previously, every tetrimino has four blocks. Another thing to note is that they can rotate. So for example you have this tetrimino:
It can also rotate in the three following positions:
Theoretically, every tetrimino should have four states, but in reality, not all of them do. For example, this one has no transformation so to speak:
And these three only have two states:
We have two ways of handling these rotations: using matrix rotation or storing the different states. To have a code that's easy to read and update, I picked the second option, but don't hesitate to try using matrix on your own, it could help you learn a lot of new things!
So first, let's write down a struct for tetriminos:
struct Tetrimino { states: Vec<Vec<Vec<u8>>>, x: isize, y: usize, current_state: u8, }
Everything seems fine except this line:
states: Vec<Vec<Vec<u8>>>,
Pretty ugly, right? Let's make it look a bit better by using type aliasing!
So what is our states field representing? Simply a list of states. Each state represents a piece's transformation. I suppose it's a bit hard to understand all of this. Let's write an example:
vec![vec![1, 1, 0, 0], vec![1, 1, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0]]
In here, 0 means the block is empty, otherwise, it's a tetrimino block. So from reading this code, I suppose you could guess that we were representing the square:
In case you wondered, we have four lines with four blocks because the biggest tetrimino has a height (or a width, depending the transformation) of four:
This isn't mandatory (we could make it fit the form of each tetrimino), but it makes our lives easier, so why not?
Coming back to our type aliasing: a piece is basically a vector or vector of numbers. It's long to write it down every time, so let's alias it as follows:
type Piece = Vec<Vec<u8>>;
Now we can rewrite the states field declaration as follows:
states: Vec<Piece>,
Way better and more explicit, right? But since we'll be using those states as well, why not alias them too?
type States = Vec<Piece>;
And now our states field declaration becomes:
states: States,
Let's explain the other fields (just in case):
struct Tetrimino { states: States, x: isize, y: usize, current_state: u8, }
A little explanation of this struct:
- states (if you didn't already understand it) is the list of possible states of the tetrimino
- x is the x position of the tetrimino
- y is the y position of the tetrimino
- current_state is the state in which the tetrimino is currently
Ok, so far so good. Now how should we handle the creation of this type generically? We don't want to rewrite this for every tetrimino. This is where traits kick in!