Initial
This commit is contained in:
332
src/games/pong.cpp
Normal file
332
src/games/pong.cpp
Normal file
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* @file pong.cpp
|
||||
* @author techniccontroller (mail[at]techniccontroller.com)
|
||||
* @brief Class implementation for pong game
|
||||
* @version 0.1
|
||||
* @date 2022-03-06
|
||||
*
|
||||
* @copyright Copyright (c) 2022
|
||||
*
|
||||
* main code from https://elektro.turanis.de/html/prj041/index.html
|
||||
*
|
||||
*/
|
||||
#include "pong.h"
|
||||
|
||||
/**
|
||||
* @brief Construct a new Pong:: Pong object
|
||||
*
|
||||
*/
|
||||
Pong::Pong()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Pong:: Pong object
|
||||
*
|
||||
* @param myledmatrix pointer to LEDMatrix object, need to provide gridAddPixel(x, y, col), gridFlush()
|
||||
* @param mylogger pointer to UDPLogger object, need to provide a function log_string(message)
|
||||
*/
|
||||
Pong::Pong(LEDMatrix * matrix, UDPLogger * logger)
|
||||
{
|
||||
_ledmatrix = matrix;
|
||||
_logger = logger;
|
||||
_gameState = GAME_STATE_END;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run main loop for one cycle
|
||||
*
|
||||
*/
|
||||
void Pong::loopCycle()
|
||||
{
|
||||
switch (_gameState)
|
||||
{
|
||||
case GAME_STATE_INIT:
|
||||
initGame(2);
|
||||
break;
|
||||
case GAME_STATE_RUNNING:
|
||||
updateBall();
|
||||
updateGame();
|
||||
break;
|
||||
case GAME_STATE_END:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: UP for given player
|
||||
*
|
||||
* @param playerid id of player {0, 1}
|
||||
*/
|
||||
void Pong::ctrlUp(uint8_t playerid)
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME)
|
||||
{
|
||||
_playerMovement[playerid] = PADDLE_MOVE_DOWN; // need to swap direction as field is rotated 180deg
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: DOWN for given player
|
||||
*
|
||||
* @param playerid id of player {0, 1}
|
||||
*/
|
||||
void Pong::ctrlDown(uint8_t playerid)
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME)
|
||||
{
|
||||
_playerMovement[playerid] = PADDLE_MOVE_UP; // need to swap direction as field is rotated 180deg
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: NONE for given player
|
||||
*
|
||||
* @param playerid id of player {0, 1}
|
||||
*/
|
||||
void Pong::ctrlNone(uint8_t playerid)
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME)
|
||||
{
|
||||
_playerMovement[playerid] = PADDLE_MOVE_NONE;
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize a new game
|
||||
*
|
||||
* @param numBots number of bots {0, 1, 2} -> two bots results in animation
|
||||
*/
|
||||
void Pong::initGame(uint8_t numBots)
|
||||
{
|
||||
(*_logger).log_string("Pong: init with " + String(numBots) + " Bots");
|
||||
resetLEDs();
|
||||
_lastButtonClick = millis();
|
||||
|
||||
_numBots = numBots;
|
||||
|
||||
_ball.x = 1;
|
||||
_ball.y = (Y_MAX / 2) - (PADDLE_WIDTH / 2) + 1;
|
||||
_ball_old.x = _ball.x;
|
||||
_ball_old.y = _ball.y;
|
||||
_ballMovement[0] = 1;
|
||||
_ballMovement[1] = -1;
|
||||
_ballDelay = BALL_DELAY_MAX;
|
||||
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
_paddles[PLAYER_1][i].x = 0;
|
||||
_paddles[PLAYER_1][i].y = (Y_MAX / 2) - (PADDLE_WIDTH / 2) + i;
|
||||
_paddles[PLAYER_2][i].x = X_MAX - 1;
|
||||
_paddles[PLAYER_2][i].y = _paddles[PLAYER_1][i].y;
|
||||
}
|
||||
|
||||
_gameState = GAME_STATE_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update ball position
|
||||
*
|
||||
*/
|
||||
void Pong::updateBall()
|
||||
{
|
||||
bool hitBall = false;
|
||||
if ((millis() - _lastBallUpdate) < _ballDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastBallUpdate = millis();
|
||||
toggleLed(_ball.x, _ball.y, LED_TYPE_OFF);
|
||||
|
||||
// collision detection for player 1
|
||||
if (_ballMovement[0] == -1 && _ball.x == 1)
|
||||
{
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
if (_paddles[PLAYER_1][i].y == _ball.y)
|
||||
{
|
||||
hitBall = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collision detection for player 2
|
||||
if (_ballMovement[0] == 1 && _ball.x == X_MAX - 2)
|
||||
{
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
if (_paddles[PLAYER_2][i].y == _ball.y)
|
||||
{
|
||||
hitBall = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hitBall == true)
|
||||
{
|
||||
_ballMovement[0] *= -1;
|
||||
if (_ballDelay > BALL_DELAY_MIN)
|
||||
{
|
||||
_ballDelay -= BALL_DELAY_STEP;
|
||||
}
|
||||
}
|
||||
|
||||
_ball.x += _ballMovement[0];
|
||||
_ball.y += _ballMovement[1];
|
||||
|
||||
if (_ball.x <= 0 || _ball.x >= X_MAX - 1)
|
||||
{
|
||||
endGame();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ball.y <= 0 || _ball.y >= Y_MAX - 1)
|
||||
{
|
||||
_ballMovement[1] *= -1;
|
||||
}
|
||||
|
||||
toggleLed(_ball.x, _ball.y, LED_TYPE_BALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game over, draw ball red
|
||||
*
|
||||
*/
|
||||
void Pong::endGame()
|
||||
{
|
||||
(*_logger).log_string("Pong: Game ended");
|
||||
_gameState = GAME_STATE_END;
|
||||
toggleLed(_ball.x, _ball.y, LED_TYPE_BALL_RED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update paddle position and check for game over
|
||||
*
|
||||
*/
|
||||
void Pong::updateGame()
|
||||
{
|
||||
if ((millis() - _lastDrawUpdate) < GAME_DELAY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastDrawUpdate = millis();
|
||||
|
||||
// turn off paddle LEDs
|
||||
for (uint8_t p = 0; p < PLAYER_AMOUNT; p++)
|
||||
{
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
toggleLed(_paddles[p][i].x, _paddles[p][i].y, LED_TYPE_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
// move _paddles
|
||||
for (uint8_t p = 0; p < PLAYER_AMOUNT; p++)
|
||||
{
|
||||
uint8_t movement = getPlayerMovement(p);
|
||||
if (movement == PADDLE_MOVE_UP && _paddles[p][PADDLE_WIDTH - 1].y < (Y_MAX - 1))
|
||||
{
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
_paddles[p][i].y++;
|
||||
}
|
||||
}
|
||||
if (movement == PADDLE_MOVE_DOWN && _paddles[p][0].y > 0)
|
||||
{
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
_paddles[p][i].y--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show paddle LEDs
|
||||
for (uint8_t p = 0; p < PLAYER_AMOUNT; p++)
|
||||
{
|
||||
for (uint8_t i = 0; i < PADDLE_WIDTH; i++)
|
||||
{
|
||||
toggleLed(_paddles[p][i].x, _paddles[p][i].y, LED_TYPE_PADDLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next movement of paddle from given player
|
||||
*
|
||||
* @param playerId id of player {0, 1}
|
||||
* @return uint8_t movement {UP, DOWN, NONE}
|
||||
*/
|
||||
uint8_t Pong::getPlayerMovement(uint8_t playerId)
|
||||
{
|
||||
uint8_t action = PADDLE_MOVE_NONE;
|
||||
if (playerId < _numBots)
|
||||
{
|
||||
// bot moves paddle
|
||||
int8_t ydir = _ball_old.y - _ball.y;
|
||||
int8_t diff = _paddles[playerId][PADDLE_WIDTH / 2].y - _ball.y + ydir * 0.5;
|
||||
// no movement if ball moves away from paddle or no difference between ball and paddle
|
||||
if (diff == 0 || (_ballMovement[0] > 0 && playerId == 0) || (_ballMovement[0] < 0 && playerId == 1))
|
||||
{
|
||||
action = PADDLE_MOVE_NONE;
|
||||
}
|
||||
else if (diff > 0)
|
||||
{
|
||||
action = PADDLE_MOVE_DOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
action = PADDLE_MOVE_UP;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
action = _playerMovement[playerId];
|
||||
_playerMovement[playerId] = PADDLE_MOVE_NONE;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the led matrix (turn all leds off)
|
||||
*
|
||||
*/
|
||||
void Pong::resetLEDs()
|
||||
{
|
||||
(*_ledmatrix).flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Turn on LED on matrix
|
||||
*
|
||||
* @param x x position of led
|
||||
* @param y y position of led
|
||||
* @param type type of pixel {PADDLE, BALL_RED, BALL, OFF}
|
||||
*/
|
||||
void Pong::toggleLed(uint8_t x, uint8_t y, uint8_t type)
|
||||
{
|
||||
uint32_t color;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case LED_TYPE_PADDLE:
|
||||
color = LEDMatrix::color_24bit(0, 80, 80);
|
||||
break;
|
||||
case LED_TYPE_BALL_RED:
|
||||
color = LEDMatrix::color_24bit(120, 0, 0);
|
||||
break;
|
||||
case LED_TYPE_BALL:
|
||||
color = LEDMatrix::color_24bit(0, 100, 0);
|
||||
break;
|
||||
case LED_TYPE_OFF:
|
||||
default:
|
||||
color = LEDMatrix::color_24bit(0, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
(*_ledmatrix).grid_add_pixel(x, y, color);
|
||||
}
|
||||
315
src/games/snake.cpp
Normal file
315
src/games/snake.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* @file snake.cpp
|
||||
* @author techniccontroller (mail[at]techniccontroller.com)
|
||||
* @brief Class implementation of snake game
|
||||
* @version 0.1
|
||||
* @date 2022-03-05
|
||||
*
|
||||
* @copyright Copyright (c) 2022
|
||||
*
|
||||
* main code from https://elektro.turanis.de/html/prj099/index.html
|
||||
*
|
||||
*/
|
||||
#include "snake.h"
|
||||
|
||||
/**
|
||||
* @brief Construct a new Snake:: Snake object
|
||||
*
|
||||
*/
|
||||
Snake::Snake()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Snake:: Snake object
|
||||
*
|
||||
* @param myledmatrix pointer to LEDMatrix object, need to provide gridAddPixel(x, y, col), gridFlush()
|
||||
* @param mylogger pointer to UDPLogger object, need to provide a function logString(message)
|
||||
*/
|
||||
Snake::Snake(LEDMatrix * matrix, UDPLogger * logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_ledmatrix = matrix;
|
||||
_gameState = GAME_STATE_END;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run main loop for one cycle
|
||||
*
|
||||
*/
|
||||
void Snake::loopCycle()
|
||||
{
|
||||
switch (_gameState)
|
||||
{
|
||||
case GAME_STATE_INIT:
|
||||
initGame();
|
||||
break;
|
||||
case GAME_STATE_RUNNING:
|
||||
updateGame();
|
||||
break;
|
||||
case GAME_STATE_END:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: UP
|
||||
*
|
||||
*/
|
||||
void Snake::ctrlUp()
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameState == GAME_STATE_RUNNING)
|
||||
{
|
||||
(*_logger).log_string("Snake: UP");
|
||||
_userDirection = DIRECTION_DOWN; // need to swap direction as field is rotated 180deg
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: DOWN
|
||||
*
|
||||
*/
|
||||
void Snake::ctrlDown()
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameState == GAME_STATE_RUNNING)
|
||||
{
|
||||
_logger->log_string("Snake: DOWN");
|
||||
_userDirection = DIRECTION_UP; // need to swap direction as field is rotated 180deg
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: RIGHT
|
||||
*
|
||||
*/
|
||||
void Snake::ctrlRight()
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameState == GAME_STATE_RUNNING)
|
||||
{
|
||||
_logger->log_string("Snake: RIGHT");
|
||||
_userDirection = DIRECTION_LEFT; // need to swap direction as field is rotated 180deg
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger control: LEFT
|
||||
*
|
||||
*/
|
||||
void Snake::ctrlLeft()
|
||||
{
|
||||
if (millis() > _lastButtonClick + DEBOUNCE_TIME && _gameState == GAME_STATE_RUNNING)
|
||||
{
|
||||
_logger->log_string("Snake: LEFT");
|
||||
_userDirection = DIRECTION_RIGHT; // need to swap direction as field is rotated 180deg
|
||||
_lastButtonClick = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the led matrix (turn all leds off)
|
||||
*
|
||||
*/
|
||||
void Snake::resetLEDs()
|
||||
{
|
||||
_ledmatrix->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize a new game
|
||||
*
|
||||
*/
|
||||
void Snake::initGame()
|
||||
{
|
||||
_logger->log_string("Snake: init");
|
||||
resetLEDs();
|
||||
_head.x = 0;
|
||||
_head.y = 0;
|
||||
_food.x = -1;
|
||||
_food.y = -1;
|
||||
_wormLength = MIN_TAIL_LENGTH;
|
||||
_userDirection = DIRECTION_LEFT;
|
||||
_lastButtonClick = millis();
|
||||
|
||||
for (int i = 0; i < MAX_TAIL_LENGTH; i++)
|
||||
{
|
||||
_tail[i].x = -1;
|
||||
_tail[i].y = -1;
|
||||
}
|
||||
updateFood();
|
||||
_gameState = GAME_STATE_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update game representation
|
||||
*
|
||||
*/
|
||||
void Snake::updateGame()
|
||||
{
|
||||
if ((millis() - _lastDrawUpdate) > GAME_DELAY)
|
||||
{
|
||||
_logger->log_string("Snake: update game");
|
||||
toggleLed(_tail[_wormLength - 1].x, _tail[_wormLength - 1].y, LED_TYPE_OFF);
|
||||
switch (_userDirection)
|
||||
{
|
||||
case DIRECTION_RIGHT:
|
||||
if (_head.x > 0)
|
||||
{
|
||||
_head.x--;
|
||||
}
|
||||
break;
|
||||
case DIRECTION_LEFT:
|
||||
if (_head.x < X_MAX - 1)
|
||||
{
|
||||
_head.x++;
|
||||
}
|
||||
break;
|
||||
case DIRECTION_DOWN:
|
||||
if (_head.y > 0)
|
||||
{
|
||||
_head.y--;
|
||||
}
|
||||
break;
|
||||
case DIRECTION_UP:
|
||||
if (_head.y < Y_MAX - 1)
|
||||
{
|
||||
_head.y++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (isCollision() == true)
|
||||
{
|
||||
endGame();
|
||||
return;
|
||||
}
|
||||
|
||||
updateTail();
|
||||
|
||||
if (_head.x == _food.x && _head.y == _food.y)
|
||||
{
|
||||
if (_wormLength < MAX_TAIL_LENGTH)
|
||||
{
|
||||
_wormLength++;
|
||||
}
|
||||
updateFood();
|
||||
}
|
||||
|
||||
_lastDrawUpdate = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game over, draw _head red
|
||||
*
|
||||
*/
|
||||
void Snake::endGame()
|
||||
{
|
||||
_gameState = GAME_STATE_END;
|
||||
toggleLed(_head.x, _head.y, LED_TYPE_BLOOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update _tail led positions
|
||||
*
|
||||
*/
|
||||
void Snake::updateTail()
|
||||
{
|
||||
for (unsigned int i = _wormLength - 1; i > 0; i--)
|
||||
{
|
||||
_tail[i].x = _tail[i - 1].x;
|
||||
_tail[i].y = _tail[i - 1].y;
|
||||
}
|
||||
_tail[0].x = _head.x;
|
||||
_tail[0].y = _head.y;
|
||||
|
||||
for (unsigned int i = 0; i < _wormLength; i++)
|
||||
{
|
||||
if (_tail[i].x > -1)
|
||||
{
|
||||
toggleLed(_tail[i].x, _tail[i].y, LED_TYPE_SNAKE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update _food position (generate new one if found)
|
||||
*
|
||||
*/
|
||||
void Snake::updateFood()
|
||||
{
|
||||
bool found = true;
|
||||
do
|
||||
{
|
||||
found = true;
|
||||
_food.x = random(0, X_MAX);
|
||||
_food.y = random(0, Y_MAX);
|
||||
for (unsigned int i = 0; i < _wormLength; i++)
|
||||
{
|
||||
if (_tail[i].x == _food.x && _tail[i].y == _food.y)
|
||||
{
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
} while (found == false);
|
||||
toggleLed(_food.x, _food.y, LED_TYPE_FOOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check for collisison between snake and border or itself
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool Snake::isCollision()
|
||||
{
|
||||
if (_head.x < 0 || _head.x >= X_MAX)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (_head.y < 0 || _head.y >= Y_MAX)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (unsigned int i = 1; i < _wormLength; i++)
|
||||
{
|
||||
if (_tail[i].x == _head.x && _tail[i].y == _head.y)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Turn on LED on matrix
|
||||
*
|
||||
* @param x x position of led
|
||||
* @param y y position of led
|
||||
* @param type type of pixel {SNAKE, OFF, FOOD, BLOOD}
|
||||
*/
|
||||
void Snake::toggleLed(uint8_t x, uint8_t y, uint8_t type)
|
||||
{
|
||||
uint32_t color;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case LED_TYPE_SNAKE:
|
||||
color = LEDMatrix::color_24bit(0, 100, 100);
|
||||
break;
|
||||
case LED_TYPE_OFF:
|
||||
color = LEDMatrix::color_24bit(0, 0, 0);
|
||||
break;
|
||||
case LED_TYPE_FOOD:
|
||||
color = LEDMatrix::color_24bit(0, 150, 0);
|
||||
break;
|
||||
case LED_TYPE_BLOOD:
|
||||
default:
|
||||
color = LEDMatrix::color_24bit(150, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
_ledmatrix->grid_add_pixel(x, y, color);
|
||||
}
|
||||
680
src/games/tetris.cpp
Normal file
680
src/games/tetris.cpp
Normal file
@@ -0,0 +1,680 @@
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
Reference in New Issue
Block a user