Files
2022-07-06 14:21:26 +08:00

243 lines
6.9 KiB
C++

/******************************************************************************
* M5Snake : Game board management *
* ------------------------------- *
* Manage the game board (storage in memory and display *
* Author: Olivier Staquet *
* Last version available on https://github.com/ostaquet/M5Snake *
*****************************************************************************/
#include "GameBoard.h"
/**
* Initialize
*/
void GameBoardClass::begin(uint8_t _max_game_cycles) {
// Keep the number of game cycles
max_game_cycles = _max_game_cycles;
current_game_cycle = 0;
// Init the board at blank
for (uint8_t x = 0; x < board_width; x++) {
for (uint8_t y = 0; y < board_height; y++) {
board_data[x][y] = BLOCK_STATUS_EMPTY;
board_changes[x][y] = 0;
}
}
// Set screen to blank
M5.Lcd.fillScreen(BLACK);
}
/**
* Refresh display
*/
void GameBoardClass::refresh() {
// Check where there are some changes
for (uint8_t x = 0; x < board_width; x++) {
for (uint8_t y = 0; y < board_height; y++) {
// Check the cell
if (board_changes[x][y] > 0) {
// There is a change...
drawChange(x, y);
// Reset the change tracking
board_changes[x][y] = 0;
}
}
}
}
/**
* Put the head of the snake on the board and go on right
*/
void GameBoardClass::startSnake() {
// Define the middle of the screen
setCell(board_width / 2, board_height / 2, BLOCK_STATUS_HEAD);
// Define the direction
setDirection(DIRECTION_RIGHT);
}
/**
* Make the snake move on the board
* Return boolean true if OK, false if game over
*/
bool GameBoardClass::moveSnake() {
// Check if it is a cycle to move
if (current_game_cycle < max_game_cycles) {
// Wait for the next cycle
current_game_cycle++;
return true;
} else {
// Reset the game cycle
current_game_cycle = 0;
}
// Add 1 to all current block with between 1 and 512
// to keep count of the movement of the snake (1 = head, 2 = 2nd block after
// head...)
for (uint8_t x = 0; x < board_width; x++) {
for (uint8_t y = 0; y < board_height; y++) {
if (board_data[x][y] < BLOCK_STATUS_CHERRY &&
board_data[x][y] != BLOCK_STATUS_EMPTY) {
board_data[x][y] = board_data[x][y] + 1;
}
}
}
// Next move to be defined
int8_t next_block_x = current_head_x;
int8_t next_block_y = current_head_y;
// Define the next move of the head
switch (current_direction) {
case DIRECTION_UP:
next_block_y = current_head_y - 1;
break;
case DIRECTION_RIGHT:
next_block_x = current_head_x + 1;
break;
case DIRECTION_DOWN:
next_block_y = current_head_y + 1;
break;
case DIRECTION_LEFT:
next_block_x = current_head_x - 1;
break;
}
// Check if the move is valid...
// Check the limit of the board for X
if (next_block_x < 0 || next_block_x >= board_width) {
return false;
}
// Check the limit of the board for Y
if (next_block_y < 0 || next_block_y >= board_height) {
return false;
}
// Check if there is a cherry on the cell (if not a cherry, remove the last
// block of the tail
if (board_data[next_block_x][next_block_y] != BLOCK_STATUS_CHERRY) {
removeTail();
}
// Check if there is another part of the snake
if (board_data[next_block_x][next_block_y] > BLOCK_STATUS_EMPTY &&
board_data[next_block_x][next_block_y] < BLOCK_STATUS_CHERRY) {
return false;
}
// OK, move the head of the snake
setCell(next_block_x, next_block_y, BLOCK_STATUS_HEAD);
return true;
}
/**
* Identify and remove the tail (last block of the snake)
*/
void GameBoardClass::removeTail() {
uint16_t greatest_value = 0;
uint8_t tail_x = 0;
uint8_t tail_y = 0;
// Find the cell with the biggest value (it is the tail)
for (uint8_t x = 0; x < board_width; x++) {
for (uint8_t y = 0; y < board_height; y++) {
if (board_data[x][y] < BLOCK_STATUS_CHERRY) {
if (board_data[x][y] > greatest_value) {
tail_x = x;
tail_y = y;
greatest_value = board_data[x][y];
}
}
}
}
// Remove the tail
setCell(tail_x, tail_y, BLOCK_STATUS_EMPTY);
}
/**
* Get the max score
*/
uint16_t GameBoardClass::getMaxScore() {
uint16_t greatest_value = 0;
// Find the cell with the biggest value (it is the tail)
for (uint8_t x = 0; x < board_width; x++) {
for (uint8_t y = 0; y < board_height; y++) {
if (board_data[x][y] < BLOCK_STATUS_CHERRY) {
if (board_data[x][y] > greatest_value) {
greatest_value = board_data[x][y];
}
}
}
}
return greatest_value - 1;
}
/**
* Draw the change of one cell
*/
void GameBoardClass::drawChange(uint8_t x, uint8_t y) {
// Compute box position
uint16_t pos_x = x * BLOCK_SIZE;
uint16_t pos_y = y * BLOCK_SIZE;
// Depending on the content of the cell, draw the box
switch (board_data[x][y]) {
case BLOCK_STATUS_EMPTY:
M5.Lcd.fillRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
break;
case BLOCK_STATUS_CHERRY:
M5.Lcd.fillRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
M5.Lcd.fillCircle(pos_x + BLOCK_SIZE / 2, pos_y + BLOCK_SIZE / 2,
BLOCK_SIZE / 2 - 1, RED);
break;
default:
M5.Lcd.drawRect(pos_x, pos_y, BLOCK_SIZE, BLOCK_SIZE, BLACK);
M5.Lcd.fillRect(pos_x + 1, pos_y + 1, BLOCK_SIZE - 2,
BLOCK_SIZE - 2, WHITE);
break;
}
}
/**
* Add a ramdom cherry on the board
*/
void GameBoardClass::addCherry() {
uint8_t pos_x = random(0, board_width);
uint8_t pos_y = random(0, board_height);
while (board_data[pos_x][pos_y] != BLOCK_STATUS_EMPTY) {
pos_x = random(0, board_width);
pos_y = random(0, board_height);
}
setCell(pos_x, pos_y, BLOCK_STATUS_CHERRY);
}
/**
* Set direction
*/
void GameBoardClass::setDirection(uint8_t direction) {
current_direction = direction;
}
/**
* Set a value in a cell
*/
void GameBoardClass::setCell(uint8_t x, uint8_t y, uint16_t status) {
board_data[x][y] = status;
board_changes[x][y] = 1;
if (status == BLOCK_STATUS_HEAD) {
current_head_x = x;
current_head_y = y;
}
}
GameBoardClass GameBoard;