680 lines
18 KiB
C++
680 lines
18 KiB
C++
/**
|
|
* @file tetris.cpp
|
|
* @author techniccontroller (mail[at]techniccontroller.com)
|
|
* @brief Class implementation for tetris game
|
|
* @version 0.1
|
|
* @date 2022-03-05
|
|
*
|
|
* @copyright Copyright (c) 2022
|
|
*
|
|
* main tetris code originally written by Klaas De Craemer, Ing. David Hrbaty
|
|
*
|
|
*/
|
|
#include "tetris.h"
|
|
|
|
Tetris::Tetris()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @brief Construct a new Tetris:: Tetris object
|
|
*
|
|
* @param myledmatrix pointer to LEDMatrix object, need to provide gridAddPixel(x, y, col), draw_on_matrix(), gridFlush() and printNumber(x,y,n,col)
|
|
* @param mylogger pointer to UDPLogger object, need to provide a function log_string(message)
|
|
*/
|
|
Tetris::Tetris(LEDMatrix *myledmatrix, UDPLogger *mylogger)
|
|
{
|
|
_logger = mylogger;
|
|
_ledmatrix = myledmatrix;
|
|
_gameStatet = GAME_STATE_READY;
|
|
}
|
|
|
|
/**
|
|
* @brief Run main loop for one cycle
|
|
*
|
|
*/
|
|
void Tetris::loopCycle()
|
|
{
|
|
switch (_gameStatet)
|
|
{
|
|
case GAME_STATE_READY:
|
|
|
|
break;
|
|
case GAME_STATE_INIT:
|
|
tetrisInit();
|
|
|
|
break;
|
|
case GAME_STATE_RUNNING:
|
|
// If brick is still "on the loose", then move it down by one
|
|
if (_activeBrick.enabled)
|
|
{
|
|
// move faster down when allow drop
|
|
if (_allowdrop)
|
|
{
|
|
if (millis() > _droptime + 50)
|
|
{
|
|
_droptime = millis();
|
|
shiftActiveBrick(DIR_DOWN);
|
|
printField();
|
|
}
|
|
}
|
|
|
|
// move down with regular speed
|
|
if ((millis() - _prevUpdateTime) > (_brickSpeed * _speedtetris / 100))
|
|
{
|
|
_prevUpdateTime = millis();
|
|
shiftActiveBrick(DIR_DOWN);
|
|
printField();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_allowdrop = false;
|
|
// Active brick has "crashed", check for full lines
|
|
// and create new brick at top of field
|
|
checkFullLines();
|
|
newActiveBrick();
|
|
_prevUpdateTime = millis(); // Reset update time to avoid brick dropping two spaces
|
|
}
|
|
break;
|
|
case GAME_STATE_PAUSED:
|
|
|
|
break;
|
|
case GAME_STATE_END:
|
|
// at game end show all bricks on field in red color for 1.5 seconds, then show score
|
|
if (_tetrisGameOver == true)
|
|
{
|
|
_tetrisGameOver = false;
|
|
(*_logger).log_string("Tetris: end");
|
|
everythingRed();
|
|
_tetrisshowscore = millis();
|
|
}
|
|
|
|
if (millis() > (_tetrisshowscore + RED_END_TIME))
|
|
{
|
|
resetLEDs();
|
|
_score = _nbRowsTotal;
|
|
showscore();
|
|
_gameStatet = GAME_STATE_READY;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger control: START (& restart)
|
|
*
|
|
*/
|
|
void Tetris::ctrlStart()
|
|
{
|
|
if (millis() > _lastButtonClick + DEBOUNCE_TIME)
|
|
{
|
|
_lastButtonClick = millis();
|
|
_gameStatet = GAME_STATE_INIT;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger control: PAUSE/PLAY
|
|
*
|
|
*/
|
|
void Tetris::ctrlPlayPause()
|
|
{
|
|
if (millis() > _lastButtonClick + DEBOUNCE_TIME)
|
|
{
|
|
_lastButtonClick = millis();
|
|
if (_gameStatet == GAME_STATE_PAUSED)
|
|
{
|
|
(*_logger).log_string("Tetris: continue");
|
|
|
|
_gameStatet = GAME_STATE_RUNNING;
|
|
}
|
|
else if (_gameStatet == GAME_STATE_RUNNING)
|
|
{
|
|
(*_logger).log_string("Tetris: pause");
|
|
|
|
_gameStatet = GAME_STATE_PAUSED;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger control: RIGHT
|
|
*
|
|
*/
|
|
void Tetris::ctrlRight()
|
|
{
|
|
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameStatet == GAME_STATE_RUNNING)
|
|
{
|
|
_lastButtonClick = millis();
|
|
shiftActiveBrick(DIR_RIGHT);
|
|
printField();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger control: LEFT
|
|
*
|
|
*/
|
|
void Tetris::ctrlLeft()
|
|
{
|
|
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameStatet == GAME_STATE_RUNNING)
|
|
{
|
|
_lastButtonClick = millis();
|
|
shiftActiveBrick(DIR_LEFT);
|
|
printField();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger control: UP (rotate)
|
|
*
|
|
*/
|
|
void Tetris::ctrlUp()
|
|
{
|
|
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameStatet == GAME_STATE_RUNNING)
|
|
{
|
|
_lastButtonClick = millis();
|
|
rotateActiveBrick();
|
|
printField();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger control: DOWN (drop)
|
|
*
|
|
*/
|
|
void Tetris::ctrlDown()
|
|
{
|
|
// longer debounce time, to prevent immediate drop
|
|
if (millis() > _lastButtonClickr + DEBOUNCE_TIME * 5 && _gameStatet == GAME_STATE_RUNNING)
|
|
{
|
|
_allowdrop = true;
|
|
_lastButtonClickr = millis();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set game speed
|
|
*
|
|
* @param i new speed value
|
|
*/
|
|
void Tetris::setSpeed(int32_t i)
|
|
{
|
|
_logger->log_string("setSpeed: " + String(i));
|
|
_speedtetris = -10 * i + 150;
|
|
}
|
|
|
|
/**
|
|
* @brief Clear the led matrix (turn all leds off)
|
|
*
|
|
*/
|
|
void Tetris::resetLEDs()
|
|
{
|
|
_ledmatrix->flush();
|
|
_ledmatrix->draw_on_matrix_instant();
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize the tetris game
|
|
*
|
|
*/
|
|
void Tetris::tetrisInit()
|
|
{
|
|
(*_logger).log_string("Tetris: init");
|
|
|
|
clearField();
|
|
_brickSpeed = INIT_SPEED;
|
|
_nbRowsThisLevel = 0;
|
|
_nbRowsTotal = 0;
|
|
_tetrisGameOver = false;
|
|
|
|
newActiveBrick();
|
|
_prevUpdateTime = millis();
|
|
|
|
_gameStatet = GAME_STATE_RUNNING;
|
|
}
|
|
|
|
/**
|
|
* @brief Draw current field representation to led matrix
|
|
*
|
|
*/
|
|
void Tetris::printField()
|
|
{
|
|
int x, y;
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{
|
|
for (y = 0; y < MATRIX_HEIGHT; y++)
|
|
{
|
|
uint8_t activeBrickPix = 0;
|
|
if (_activeBrick.enabled)
|
|
{ // Only draw brick if it is enabled
|
|
// Now check if brick is "in view"
|
|
if ((x >= _activeBrick.xpos) && (x < (_activeBrick.xpos + (_activeBrick.siz))) && (y >= _activeBrick.ypos) && (y < (_activeBrick.ypos + (_activeBrick.siz))))
|
|
{
|
|
activeBrickPix = (_activeBrick.pix)[x - _activeBrick.xpos][y - _activeBrick.ypos];
|
|
}
|
|
}
|
|
if (_field.pix[x][y] == 1)
|
|
{
|
|
_ledmatrix->grid_add_pixel(x, y, _field.color[x][y]);
|
|
}
|
|
else if (activeBrickPix == 1)
|
|
{
|
|
_ledmatrix->grid_add_pixel(x, y, _activeBrick.col);
|
|
}
|
|
else
|
|
{
|
|
_ledmatrix->grid_add_pixel(x, y, 0x000000);
|
|
}
|
|
}
|
|
}
|
|
_ledmatrix->draw_on_matrix_instant();
|
|
}
|
|
|
|
/* *** Game functions *** */
|
|
/**
|
|
* @brief Spawn new (random) brick
|
|
*
|
|
*/
|
|
void Tetris::newActiveBrick()
|
|
{
|
|
uint8_t selectedBrick = 0;
|
|
static uint8_t lastselectedBrick = 0;
|
|
|
|
// choose random next brick, but not the same as before
|
|
do
|
|
{
|
|
selectedBrick = random(7);
|
|
} while (lastselectedBrick == selectedBrick);
|
|
|
|
// Save selected brick for next round
|
|
lastselectedBrick = selectedBrick;
|
|
|
|
// every brick has its color, select corresponding color
|
|
uint32_t selectedCol = _brickLib[selectedBrick].col;
|
|
// Set properties of brick
|
|
_activeBrick.siz = _brickLib[selectedBrick].siz;
|
|
_activeBrick.yOffset = _brickLib[selectedBrick].yOffset;
|
|
_activeBrick.xpos = MATRIX_WIDTH / 2 - _activeBrick.siz / 2;
|
|
_activeBrick.ypos = BRICKOFFSET - _activeBrick.yOffset;
|
|
_activeBrick.enabled = true;
|
|
|
|
// Set color of brick
|
|
_activeBrick.col = selectedCol;
|
|
// _activeBrick.color = _colorLib[1];
|
|
|
|
// Copy pix array of selected Brick
|
|
uint8_t x, y;
|
|
for (y = 0; y < MAX_BRICK_SIZE; y++)
|
|
{
|
|
for (x = 0; x < MAX_BRICK_SIZE; x++)
|
|
{
|
|
_activeBrick.pix[x][y] = (_brickLib[selectedBrick]).pix[x][y];
|
|
}
|
|
}
|
|
|
|
// Check collision, if already, then game is over
|
|
if (checkFieldCollision(&_activeBrick))
|
|
{
|
|
_tetrisGameOver = true;
|
|
_gameStatet = GAME_STATE_END;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check collision between bricks in the field and the specified brick
|
|
*
|
|
* @param brick brick to be checked for collision
|
|
* @return boolean true if collision occured
|
|
*/
|
|
boolean Tetris::checkFieldCollision(struct Brick *brick)
|
|
{
|
|
uint8_t bx, by;
|
|
uint8_t fx, fy;
|
|
for (by = 0; by < MAX_BRICK_SIZE; by++)
|
|
{
|
|
for (bx = 0; bx < MAX_BRICK_SIZE; bx++)
|
|
{
|
|
fx = brick->xpos + bx;
|
|
fy = brick->ypos + by;
|
|
if ((brick->pix[bx][by] == 1) && (_field.pix[fx][fy] == 1))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Check collision between specified brick and all sides of the playing field
|
|
*
|
|
* @param brick brick to be checked for collision
|
|
* @return boolean true if collision occured
|
|
*/
|
|
boolean Tetris::checkSidesCollision(struct Brick *brick)
|
|
{
|
|
// Check vertical collision with sides of field
|
|
uint8_t bx, by;
|
|
uint8_t fx; //, fy; /* Patch */
|
|
for (by = 0; by < MAX_BRICK_SIZE; by++)
|
|
{
|
|
for (bx = 0; bx < MAX_BRICK_SIZE; bx++)
|
|
{
|
|
if (brick->pix[bx][by] == 1)
|
|
{
|
|
fx = brick->xpos + bx; // Determine actual position in the field of the current pix of the brick
|
|
// fy = brick->ypos + by; /* Patch */
|
|
if (fx < 0 || fx >= MATRIX_WIDTH)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Rotate current active brick
|
|
*
|
|
*/
|
|
void Tetris::rotateActiveBrick()
|
|
{
|
|
// Copy active brick pix array to temporary pix array
|
|
uint8_t x, y;
|
|
Brick tmpBrick;
|
|
for (y = 0; y < MAX_BRICK_SIZE; y++)
|
|
{
|
|
for (x = 0; x < MAX_BRICK_SIZE; x++)
|
|
{
|
|
tmpBrick.pix[x][y] = _activeBrick.pix[x][y];
|
|
}
|
|
}
|
|
tmpBrick.xpos = _activeBrick.xpos;
|
|
tmpBrick.ypos = _activeBrick.ypos;
|
|
tmpBrick.siz = _activeBrick.siz;
|
|
|
|
// Depending on size of the active brick, we will rotate differently
|
|
if (_activeBrick.siz == 3)
|
|
{
|
|
// Perform rotation around center pix
|
|
tmpBrick.pix[0][0] = _activeBrick.pix[0][2];
|
|
tmpBrick.pix[0][1] = _activeBrick.pix[1][2];
|
|
tmpBrick.pix[0][2] = _activeBrick.pix[2][2];
|
|
tmpBrick.pix[1][0] = _activeBrick.pix[0][1];
|
|
tmpBrick.pix[1][1] = _activeBrick.pix[1][1];
|
|
tmpBrick.pix[1][2] = _activeBrick.pix[2][1];
|
|
tmpBrick.pix[2][0] = _activeBrick.pix[0][0];
|
|
tmpBrick.pix[2][1] = _activeBrick.pix[1][0];
|
|
tmpBrick.pix[2][2] = _activeBrick.pix[2][0];
|
|
// Keep other parts of temporary block clear
|
|
tmpBrick.pix[0][3] = 0;
|
|
tmpBrick.pix[1][3] = 0;
|
|
tmpBrick.pix[2][3] = 0;
|
|
tmpBrick.pix[3][3] = 0;
|
|
tmpBrick.pix[3][2] = 0;
|
|
tmpBrick.pix[3][1] = 0;
|
|
tmpBrick.pix[3][0] = 0;
|
|
}
|
|
else if (_activeBrick.siz == 4)
|
|
{
|
|
// Perform rotation around center "cross"
|
|
tmpBrick.pix[0][0] = _activeBrick.pix[0][3];
|
|
tmpBrick.pix[0][1] = _activeBrick.pix[1][3];
|
|
tmpBrick.pix[0][2] = _activeBrick.pix[2][3];
|
|
tmpBrick.pix[0][3] = _activeBrick.pix[3][3];
|
|
tmpBrick.pix[1][0] = _activeBrick.pix[0][2];
|
|
tmpBrick.pix[1][1] = _activeBrick.pix[1][2];
|
|
tmpBrick.pix[1][2] = _activeBrick.pix[2][2];
|
|
tmpBrick.pix[1][3] = _activeBrick.pix[3][2];
|
|
tmpBrick.pix[2][0] = _activeBrick.pix[0][1];
|
|
tmpBrick.pix[2][1] = _activeBrick.pix[1][1];
|
|
tmpBrick.pix[2][2] = _activeBrick.pix[2][1];
|
|
tmpBrick.pix[2][3] = _activeBrick.pix[3][1];
|
|
tmpBrick.pix[3][0] = _activeBrick.pix[0][0];
|
|
tmpBrick.pix[3][1] = _activeBrick.pix[1][0];
|
|
tmpBrick.pix[3][2] = _activeBrick.pix[2][0];
|
|
tmpBrick.pix[3][3] = _activeBrick.pix[3][0];
|
|
}
|
|
else
|
|
{
|
|
_logger->log_string("Tetris: Brick size error");
|
|
}
|
|
|
|
// Now validate by checking collision.
|
|
// Collision possibilities:
|
|
// - Brick now sticks outside field
|
|
// - Brick now sticks inside fixed bricks of field
|
|
// In case of collision, we just discard the rotated temporary brick
|
|
if ((!checkSidesCollision(&tmpBrick)) && (!checkFieldCollision(&tmpBrick)))
|
|
{
|
|
// Copy temporary brick pix array to active pix array
|
|
for (y = 0; y < MAX_BRICK_SIZE; y++)
|
|
{
|
|
for (x = 0; x < MAX_BRICK_SIZE; x++)
|
|
{
|
|
_activeBrick.pix[x][y] = tmpBrick.pix[x][y];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Shift brick left/right/down by one if possible
|
|
*
|
|
* @param dir direction to be shifted
|
|
*/
|
|
void Tetris::shiftActiveBrick(int dir)
|
|
{
|
|
// Change position of active brick (no copy to temporary needed)
|
|
if (dir == DIR_LEFT)
|
|
{
|
|
_activeBrick.xpos--;
|
|
}
|
|
else if (dir == DIR_RIGHT)
|
|
{
|
|
_activeBrick.xpos++;
|
|
}
|
|
else if (dir == DIR_DOWN)
|
|
{
|
|
_activeBrick.ypos++;
|
|
}
|
|
|
|
// Check position of active brick
|
|
// Two possibilities when collision is detected:
|
|
// - Direction was LEFT/RIGHT, just revert position back
|
|
// - Direction was DOWN, revert position and fix block to field on collision
|
|
// When no collision, keep _activeBrick coordinates
|
|
if ((checkSidesCollision(&_activeBrick)) || (checkFieldCollision(&_activeBrick)))
|
|
{
|
|
if (dir == DIR_LEFT)
|
|
{
|
|
_activeBrick.xpos++;
|
|
}
|
|
else if (dir == DIR_RIGHT)
|
|
{
|
|
_activeBrick.xpos--;
|
|
}
|
|
else if (dir == DIR_DOWN)
|
|
{
|
|
_activeBrick.ypos--; // Go back up one
|
|
addActiveBrickToField();
|
|
_activeBrick.enabled = false; // Disable brick, it is no longer moving
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Copy active pixels to field, including color
|
|
*
|
|
*/
|
|
void Tetris::addActiveBrickToField()
|
|
{
|
|
uint8_t bx, by;
|
|
uint8_t fx, fy;
|
|
for (by = 0; by < MAX_BRICK_SIZE; by++)
|
|
{
|
|
for (bx = 0; bx < MAX_BRICK_SIZE; bx++)
|
|
{
|
|
fx = _activeBrick.xpos + bx;
|
|
fy = _activeBrick.ypos + by;
|
|
|
|
if (fx >= 0 && fy >= 0 && fx < MATRIX_WIDTH && fy < MATRIX_HEIGHT && _activeBrick.pix[bx][by])
|
|
{ // Check if inside playing field
|
|
// _field.pix[fx][fy] = _field.pix[fx][fy] || _activeBrick.pix[bx][by];
|
|
_field.pix[fx][fy] = _activeBrick.pix[bx][by];
|
|
_field.color[fx][fy] = _activeBrick.col;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Move all pix from the field above startRow down by one. startRow is overwritten
|
|
*
|
|
* @param startRow
|
|
*/
|
|
void Tetris::moveFieldDownOne(uint8_t startRow)
|
|
{
|
|
if (startRow == 0)
|
|
{ // Topmost row has nothing on top to move...
|
|
return;
|
|
}
|
|
uint8_t x, y;
|
|
for (y = startRow - 1; y > 0; y--)
|
|
{
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{
|
|
_field.pix[x][y + 1] = _field.pix[x][y];
|
|
_field.color[x][y + 1] = _field.color[x][y];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check if a line is complete
|
|
*
|
|
*/
|
|
void Tetris::checkFullLines()
|
|
{
|
|
int x, y;
|
|
int minY = 0;
|
|
for (y = (MATRIX_HEIGHT - 1); y >= minY; y--)
|
|
{
|
|
uint8_t rowSum = 0;
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{
|
|
rowSum = rowSum + (_field.pix[x][y]);
|
|
}
|
|
if (rowSum >= MATRIX_WIDTH)
|
|
{
|
|
// Found full row, animate its removal
|
|
_activeBrick.enabled = false;
|
|
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{
|
|
_field.pix[x][y] = 0;
|
|
printField();
|
|
delay(100);
|
|
}
|
|
// Move all upper rows down by one
|
|
moveFieldDownOne(y);
|
|
y++;
|
|
minY++;
|
|
printField();
|
|
delay(100);
|
|
|
|
_nbRowsThisLevel++;
|
|
_nbRowsTotal++;
|
|
if (_nbRowsThisLevel >= LEVELUP)
|
|
{
|
|
_nbRowsThisLevel = 0;
|
|
_brickSpeed = _brickSpeed - SPEED_STEP;
|
|
if (_brickSpeed < 200)
|
|
{
|
|
_brickSpeed = 200;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Clear field
|
|
*
|
|
*/
|
|
void Tetris::clearField()
|
|
{
|
|
uint8_t x, y;
|
|
for (y = 0; y < MATRIX_HEIGHT; y++)
|
|
{
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{
|
|
_field.pix[x][y] = 0;
|
|
_field.color[x][y] = 0;
|
|
}
|
|
}
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{ // This last row is invisible to the player and only used for the collision detection routine
|
|
_field.pix[x][MATRIX_HEIGHT] = 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Color all bricks on the field red
|
|
*
|
|
*/
|
|
void Tetris::everythingRed()
|
|
{
|
|
int x, y;
|
|
for (x = 0; x < MATRIX_WIDTH; x++)
|
|
{
|
|
for (y = 0; y < MATRIX_HEIGHT; y++)
|
|
{
|
|
uint8_t activeBrickPix = 0;
|
|
if (_activeBrick.enabled)
|
|
{ // Only draw brick if it is enabled
|
|
// Now check if brick is "in view"
|
|
if ((x >= _activeBrick.xpos) && (x < (_activeBrick.xpos + (_activeBrick.siz))) && (y >= _activeBrick.ypos) && (y < (_activeBrick.ypos + (_activeBrick.siz))))
|
|
{
|
|
activeBrickPix = (_activeBrick.pix)[x - _activeBrick.xpos][y - _activeBrick.ypos];
|
|
}
|
|
}
|
|
if (_field.pix[x][y] == 1)
|
|
{
|
|
_ledmatrix->grid_add_pixel(x, y, RED);
|
|
}
|
|
else if (activeBrickPix == 1)
|
|
{
|
|
_ledmatrix->grid_add_pixel(x, y, RED);
|
|
}
|
|
else
|
|
{
|
|
_ledmatrix->grid_add_pixel(x, y, 0x000000);
|
|
}
|
|
}
|
|
}
|
|
_ledmatrix->draw_on_matrix_instant();
|
|
}
|
|
|
|
/**
|
|
* @brief Draw score to led matrix
|
|
*
|
|
*/
|
|
void Tetris::showscore()
|
|
{
|
|
uint32_t color = LEDMatrix::color_24bit(255, 170, 0);
|
|
_ledmatrix->flush();
|
|
if (_score > 9)
|
|
{
|
|
_ledmatrix->print_number(2, 3, _score / 10, color);
|
|
_ledmatrix->print_number(6, 3, _score % 10, color);
|
|
}
|
|
else
|
|
{
|
|
_ledmatrix->print_number(4, 3, _score, color);
|
|
}
|
|
_ledmatrix->draw_on_matrix_instant();
|
|
} |