game · Tutorial
[TUTORIAL] How to make the WALLED game step by step – Part 3
Written in 2016 for the first KeyChainino (ATtiny84, 30 LEDs). On the KeyChainino 144 games are made differently: see How to create your own game.
In the previous tutorial, we learned how to create the walls and their gates for the WALLED game.
Today we talk about the collisions between the man and the walls, and how to keep the game’s score.
Step 1: Start, End and Reset the Game
Before checking the collision between man and walls, we have to prepare the functions and the variables to start and end the game. After the end we need to reset the game variables, so we need to create that function too.
At the top of the sketch, after the wall variables and before the ISR(TIM1_OVF_vect) function, insert the gameStarted variable that tells whether the game has started.
... byte wallGateXPosition = random(0, MATRIX_COL); //randomized wall's gate //Game variables bool gameStarted = false; //indicates if the game is started ISR(TIM1_OVF_vect) { // timer1 overflow interrupt service routine ...
Now, we need to modify the first part of the ISR(TIM1_OVF_vect) function in this way:
ISR(TIM1_OVF_vect) { // timer1 overflow interrupt service routine cli(); //disable interrupt TCNT1 = 65406; //THIS PART IS USED TO UPDATE THE WALL POSITION //if game is started change wall position if (gameStarted) { //it is a counter used to update the wall position after it reach //the wallUpdatePositionSpeed value because timer interrupt is to fast wallUpdatePositionCounter++; if (wallUpdatePositionCounter > wallUpdatePositionSpeed) { updateWallPosition(); wallUpdatePositionCounter = 0; } } ...
We have added a check on the variable gameStarted: if it is true – the game has started – we increase the wallUpdatePositionCounter so that the wall can fall. If it is false, the game has ended and the wall stops falling.
Now we set the gameStarted variable to true at the end of the setup() function:
... gameStarted = true; //Start the game }
After that, we can create these two functions after the loop() function:
void game() { updateManPosition(); //update the man position by checking buttons } void endGame() { clearMatrix(); //clear all the LEDs delay(500); goSleep(); //sleep to reduce power resetGame(); //reset game variables }
The game() function only calls the updateManPosition() function.
The endGame() function clears the matrix, calls the goSleep() function and then it resets the game with the resetGame() function that we are going to write:
void resetGame() { //reset all game variables to the start condition clearMatrix(); delay(300); manXPosition = 2; wallGateXPosition = random(0, MATRIX_COL); wallYPosition = 0; //top screen wallUpdatePositionCounter = 0; wallUpdatePositionSpeed = wallSpeed; //show wall for (byte x = 0; x < MATRIX_COL; x++) { if (x != wallGateXPosition) { matrixState[wallYPosition][x] = 1; } } gameStarted = true; }
This function resets all the game variables to their original states and sets gameStarted to true again.
After that we need to modify the loop() function in this way:
void loop() { if (gameStarted) { //if game is started game(); //go to game function } else { //else end the game endGame(); //to to end game } }
So, every loop we check if the game has started. If it is, we call the game() function. Otherwise we call the endGame() function.
Here we are.
If you upload the sketch now, you will not notice anything new compared to the previous tutorial sketch.
That’s because our game never ends.
How do we end this game? By adding the collision between the man and the wall.
Every time we have a collision our game ends.
Step 2: Check Collisions
To detect a collision between our man and the wall that falls down, we need to check the wall‘s Y position, the man‘s X position and the gate‘s X position.
If the wall‘s Y position is the same as the man‘s Y position – so the bottom line – and the man isn’t in the same X position as the gate, we have a collision and the game ends.
Otherwise, if the wall has the same Y position as the man and the man’s X position is the same as the gate’s X position, we increase the score.
To check this we only need to add this code at the top of the updateWallPosition() function:
void updateWallPosition() { //checkCollision if (wallYPosition == MATRIX_ROW - 2) { //if wall is in the same man's Y position //NOTE: we check if the wallYposition is in the position MATRIX_ROW - 2 and // not on MATRIX_ROW - 1 because we increment the wallYPosition after this check if (manXPosition == wallGateXPosition) { //if man passes the wall //increase score } else { gameStarted = false; //esle we end the game } } ...
NOTE: we check if the wallYPosition is egual to MATRIX_ROW - 2 and not MATRIX_ROW - 1 because we increment the wallYPosition after this check and so we need to check the collision on the previous ROW.
Now, if you upload the sketch, you can notice that, if the wall touches the man where there isn’t a gate, the game stops.
If you want to start the game again, you need to press the two buttons at the same time.
Now it’s time to add the score!
Step 3: Score
To increase the game score we need to increase a variable – called score – when the man goes through the gate.
First, we need to declare the score variable at the top of the sketch after the declaration of the gameStarted variable.
//Game variables bool gameStarted = false; //indicates if the game is started byte score = 0; //game score MAX 255 for byte
Now we can edit the previous checkCollision code in the updateWallPosition() function in this way:
//checkCollision if (wallYPosition == MATRIX_ROW - 2) { //if wall is in the same man's Y position //NOTE: we check if the wallYposition is in the position MATRIX_ROW - 2 and // not on MATRIX_ROW - 1 because we increment the wallYPosition after this check if (manXPosition == wallGateXPosition) { //if man passes the wall score++; //increase score } else { gameStarted = false; //esle we end the game } }
Every time the man goes through the wall’s gate, we increase the score.
After that, when we have a collision and so the game ends, we need to show the score. To do that, first we need to create the matrix of the numbers.
At the top of the sketch, after the declaration of the score variable, add:
//NUMBERS used in score stored in FLASH in order to reduce RAM size const PROGMEM bool one[MATRIX_ROW][MATRIX_COL] = { {0, 0, 0, 1, 0, 0}, {0, 0, 1, 1, 0, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 0, 0} }; const PROGMEM bool two[MATRIX_ROW][MATRIX_COL] = { {0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 0} }; const PROGMEM bool three[MATRIX_ROW][MATRIX_COL] = { {0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 1, 1, 0, 0} }; const PROGMEM bool four[MATRIX_ROW][MATRIX_COL] = { {0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0}, {0, 0, 1, 0, 1, 0}, {0, 1, 1, 1, 1, 1}, {0, 0, 0, 0, 1, 0} }; const PROGMEM bool five[MATRIX_ROW][MATRIX_COL] = { {0, 0, 1, 1, 1, 0}, {0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 1, 1, 0, 0} }; const PROGMEM bool six[MATRIX_ROW][MATRIX_COL] = { {0, 0, 0, 1, 1, 0}, {0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 1, 0}, {0, 0, 1, 0, 1, 0}, {0, 0, 1, 1, 1, 0} }; const PROGMEM bool seven[MATRIX_ROW][MATRIX_COL] = { {0, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0} }; const PROGMEM bool eight[MATRIX_ROW][MATRIX_COL] = { {0, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, {0, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, {0, 0, 1, 1, 0, 0} }; const PROGMEM bool nine[MATRIX_ROW][MATRIX_COL] = { {0, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, {0, 0, 1, 1, 1, 0}, {0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 0} }; const PROGMEM bool zero[MATRIX_ROW][MATRIX_COL] = { {0, 0, 1, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, {0, 1, 0, 0, 1, 0}, {0, 1, 0, 0, 1, 0}, {0, 0, 1, 1, 0, 0} };
These matrices are stored in the flash memory to reduce the microcontroller’s RAM usage. We can use this trick because these matrices are constant.
After that, we can write the following functions after the resetGame() function:
void showScore(byte scoreNumber) { clearMatrix(); char scoreChar[5]; //char were to put the score number //converting the score to scoreChar String str = String(scoreNumber) + ' '; str.toCharArray(scoreChar, 5); for (char c = 0; scoreChar[c] != '\0'; c++) { for (int col = MATRIX_COL - 1; col >= 0; col--) { // we start to display the charter matrix from right to left for (byte i = 0; i < MATRIX_COL; i++) { //put the charter into the matrixState for (byte j = 0; j < MATRIX_ROW; j++) { //as usual if (i >= col) { //if the number of col(i) is higher than the scrolling col, we show the correct charter according to charterToShow var. writeCharter(scoreChar[c], i, j, col); } else { //else, if col (i) is less than col, we shift the matrixState matrixState[j][i] = matrixState[j][i + 1]; } } } delay(150); } } } //show the number according to the matrix number void writeCharter(char charterToShow, byte i, byte j, byte col) { if (charterToShow == '0') { matrixState[j][i] = (bool*)pgm_read_byte(&(zero[j][i - col])); } else if (charterToShow == '1') { matrixState[j][i] = (bool*)pgm_read_byte(&(one[j][i - col])); } else if (charterToShow == '2') { matrixState[j][i] = (bool*)pgm_read_byte(&(two[j][i - col])); } else if (charterToShow == '3') { matrixState[j][i] = (bool*)pgm_read_byte(&(three[j][i - col])); } else if (charterToShow == '4') { matrixState[j][i] = (bool*)pgm_read_byte(&(four[j][i - col])); } else if (charterToShow == '5') { matrixState[j][i] = (bool*)pgm_read_byte(&(five[j][i - col])); } else if (charterToShow == '6') { matrixState[j][i] = (bool*)pgm_read_byte(&(six[j][i - col])); } else if (charterToShow == '7') { matrixState[j][i] = (bool*)pgm_read_byte(&(seven[j][i - col])); } else if (charterToShow == '8') { matrixState[j][i] = (bool*)pgm_read_byte(&(eight[j][i - col])); } else if (charterToShow == '9') { matrixState[j][i] = (bool*)pgm_read_byte(&(nine[j][i - col])); } else if (charterToShow == ' ') { //SYMBOLS FOR SPACE matrixState[j][i] = 0; } }
The first function – showScore(byte scoreNumber) – is used to show the number in scrolling mode.
When the game ends, we pass the score variable to this function. Then the function clears the matrixState.
Then it converts the score into a char variable.
Then a for loop scrolls the numbers until the scoreChar variable reaches the “end of string character - \0 “.
The function showScore() takes the number to show from the writeCharter() function that changes the matrixState according to the number – charterToShow – passed to it.
Now we can call the showScore() function in the endGame() function, by editing it in this way:
void endGame() { showScore(score); //shows the score number clearMatrix(); //clear all the LEDs delay(500); goSleep(); //sleep to reduce power resetGame(); //reset game variables }
Next we need to clear the score in the resetGame() function. Add score = 0; before gameStarted = true;
... score = 0; gameStarted = true; }
You can now upload the sketch to KeyChainino to see the score.
You can download the complete sketch for this third tutorial here.
That’s all for today!
In the next tutorial we add some aesthetic touches and increase the speed of the walls after a number of walls have spawned.
Stay Tuned!