Files
M5Stack/examples/Face/Snake_Gameboy/GameBoard.cpp
T
2020-07-28 15:36:55 +08:00

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;