/****************************************************************************** * M5Snake * * ------- * * Snake game for M5Stack-Core with Gameboy Keyboard faces * * Author: Olivier Staquet * * Last version available on https://github.com/ostaquet/M5Snake * ****************************************************************************** * MIT License * * Copyright (c) 2020 Olivier Staquet * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ #include #include "GameboyInput.h" #include "Power.h" #include "GameBoard.h" #define GAME_STATUS_INIT 0x00 #define GAME_STATUS_MENU 0x10 #define GAME_STATUS_START_GAME 0x20 #define GAME_STATUS_GAME 0x21 #define GAME_STATUS_GAMEOVER 0x30 #define GAME_CYCLES 3 // Define the number of cycle before moving the snake /****************************************************************************** * Global variables *******************************************************************************/ // Global game status uint8_t game_status = GAME_STATUS_INIT; void gameLoop(); void fromInitToMenu(); void fromMenuToGame(); void fromGameToGameOver(); /****************************************************************************** * Initialize components *******************************************************************************/ void setup() { // Set game status game_status = GAME_STATUS_INIT; // Initialize M5Stack (LCD = true, SD = false, Serial = true, I2C = true) M5.begin(true, false, true, true); // Show that we are alive M5.Lcd.setBrightness(100); M5.Lcd.fillScreen(BLACK); M5.Lcd.setTextSize(2); M5.Lcd.println(F("M5Snake by Olivier Staquet")); // Disable white noise on speaker // (according to https://community.m5stack.com/topic/61/noise-on-speaker/15) dacWrite(25,0); // Start the Gameboy faces controller GameboyInput.begin(); // Show battery management Power.begin(); Power.adaptChargeMode(); M5.Lcd.print(F("Battery level ")); int8_t batt = Power.getBatteryLevel(); if(batt >= 0) { M5.Lcd.print(batt); M5.Lcd.println(F("%")); } else { M5.Lcd.println(F("unavailable")); } // Wait some time delay(1000); } /****************************************************************************** * Main loop *******************************************************************************/ void loop() { // Depending on status, run the routine switch(game_status) { // Init phase (over) case GAME_STATUS_INIT : fromInitToMenu(); break; // Start menu case GAME_STATUS_MENU : if(GameboyInput.getActivity() == GAMEBOY_KEY_START) { fromMenuToGame(); } break; // Game in progress case GAME_STATUS_GAME : gameLoop(); break; // Game over case GAME_STATUS_GAMEOVER : delay(3000); fromInitToMenu(); break; } // By default, always adapt the charge mode for each cycle Power.adaptChargeMode(); // Cycle delay delay(25); } /****************************************************************************** * Routine for the game loop (Snake moving and eating cherry depending on input *******************************************************************************/ void gameLoop() { // Check the activity from the input switch(GameboyInput.getActivity()) { case GAMEBOY_KEY_UP : GameBoard.setDirection(DIRECTION_UP); break; case GAMEBOY_KEY_RIGHT : GameBoard.setDirection(DIRECTION_RIGHT); break; case GAMEBOY_KEY_DOWN : GameBoard.setDirection(DIRECTION_DOWN); break; case GAMEBOY_KEY_LEFT : GameBoard.setDirection(DIRECTION_LEFT); break; } // Move the snake if(!GameBoard.moveSnake()) { // If cannot move the snake -> game over... fromGameToGameOver(); } else { // Add a cherry some time... if(random(0,15 * GAME_CYCLES) == 0) { GameBoard.addCherry(); } GameBoard.refresh(); } } /****************************************************************************** * Routine from GAME_STATUS_INIT to GAME_STATUS_MENU *******************************************************************************/ void fromInitToMenu() { // Clear the screen M5.Lcd.setBrightness(100); M5.Lcd.fillScreen(BLACK); // Show the title M5.Lcd.setTextSize(5); M5.Lcd.setTextColor(RED); M5.Lcd.drawString(F("M5Snake"), (M5.Lcd.width() - M5.Lcd.textWidth(F("M5Snake"))) / 2, M5.Lcd.height() / 4); M5.Lcd.setTextSize(1); M5.Lcd.setTextColor(WHITE); M5.Lcd.drawString(F("by Olivier Staquet"), (M5.Lcd.width() - M5.Lcd.textWidth(F("by Olivier Staquet"))) / 2, M5.Lcd.height() / 2); // Show "Press start" M5.Lcd.setTextSize(2); M5.Lcd.setTextColor(WHITE); M5.Lcd.drawString(F("Press START to continue"), (M5.Lcd.width() - M5.Lcd.textWidth(F("Press START to continue"))) / 2, (M5.Lcd.height() / 4) * 3); game_status = GAME_STATUS_MENU; } /****************************************************************************** * Routine from GAME_STATUS_MENU to GAME_STATUS_GAME *******************************************************************************/ void fromMenuToGame() { // Init the board game GameBoard.begin(GAME_CYCLES); // Define the head of the snake GameBoard.startSnake(); GameBoard.refresh(); game_status = GAME_STATUS_GAME; } /****************************************************************************** * Routine from GAME_STATUS_GAME to GAME_STATUS_GAMEOVER *******************************************************************************/ void fromGameToGameOver() { // Clear the screen M5.Lcd.setBrightness(100); M5.Lcd.fillScreen(RED); M5.Lcd.setTextSize(5); M5.Lcd.setTextColor(BLACK); M5.Lcd.drawString(F("GAME OVER"), (M5.Lcd.width() - M5.Lcd.textWidth(F("GAME OVER"))) / 2, M5.Lcd.height() / 4); // Max score... M5.Lcd.setTextSize(2); M5.Lcd.setTextColor(WHITE); M5.Lcd.drawString(F("Score"), (M5.Lcd.width() - M5.Lcd.textWidth(F("Score"))) / 2, M5.Lcd.height() / 2); M5.Lcd.setTextSize(4); M5.Lcd.setTextColor(WHITE); M5.Lcd.drawString(String(GameBoard.getMaxScore()), (M5.Lcd.width() - M5.Lcd.textWidth(String(GameBoard.getMaxScore()))) / 2, (M5.Lcd.height() / 4) * 3); game_status = GAME_STATUS_GAMEOVER; }