/****************************************************************************** * 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;