In the previously tutorial, we learn how create the Walls and their gates of the WALLED game.
Today we talk about the collisions between the man and the walls and how to make the score of the game.
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 allow the start and the end of the game. After the End we need to reset the game variables and so we need to create also this function.
At the top of the sketch, after the walls variable and before the ISR(TIM1_OVF_vect)
function, insert the gameStarted
variable that indicates if the game is 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 the check of the variable gameStarted
: if this variable is true, so the game is started, we increase the wallUpdatePositionCounter
in order to allow the wall’s fall. If this variable is false, it means that the game is ended and the wall stops it fall.
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 this 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 its original states and puts the 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 is stared. 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 than the previous tutorial sketch.
This because our game never ends.
How to 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 of the man‘s Y position – so the bottom line – and the man isn’t in the same X position of the gates, we have a collision and the game ends.
Otherwise if the wall has the same Y position of the man’s and the man’s X position is the same of the gate’s X position, we increase the score.
To check these 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 touch 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 button 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 a 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 matrixes are stored in the flash memory to reduce the microcontroller’s RAM usage. We can use this trick because these matrixes 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. After, the function clears the matrixState.
Then it converts the score into a char variable.
After, a for cycle is used to scroll the numbers until the scoreChar
variable reaches the “end of line charter - \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 edit 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.
It’s all for today!
In the next tutorial we add some aesthetical parts and we increase the speed of the walls after a number of walls spawned.
Stay Tuned!