mirror of
https://github.com/m5stack/M5Stack.git
synced 2026-05-20 10:06:46 -07:00
237 lines
6.2 KiB
C++
237 lines
6.2 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;
|