Skip to content

DimaOanaTeodora/Snake-game

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🐍 Snake 🐍

Snake game using Arduino UNO and 8x8 led matrix

Introduction to Robotics @Unibuc

🏋️‍♀️ How to play the game

IMG_20211219_140929

How to play the game?

🏋️‍♀️ Game goal

Goal is to eat the food and get a new high score. The game ends when the snake eats himself or collides with the obstacles/walls.

⚙️ Menu

You can scroll through the menus moving the joystcik up/down. You can choose an option from the menu moving the joystick right. Menu design:

- MAIN MENU
  - START - choose to start the game
  - HIGH SCORE
  - SETTINGS (MENU)
      - DIFFICULTY
        - LOW/MEDIUM/HIGH
      - SOUND
       - MUTE/UNMUTE
      - LCD CONTRAST 
        - 1/2/3
      - LCD BRIGHTNESS
        - 1/2/3
      - MATRIX BRIGHTNESS 
        - 1/2/3
      - BACK (TO MAIN MENU)
  - ABOUT 

⚖️ Score and difficulty

The score depends on the difficulty:
 - LOW: 1 point/level
 - MEDIUM: 2 points/level
 - HIGH: 5 points/level

The movement speed of the snake increases once at 4 levels. In case of a new high score beeing reached, the player name and the score are written into the EEPROM memory of Arduino. You can see them in the Main menu -> HIGH SCORE section.

🗺️ App flow

Snake

🏗️ Quick rundown

Hardware components:

  • Arduino UNO
  • 16x2 LCD
  • joystick
  • active buzzer
  • 8x8 led matrix
  • two breadboards
  • wires
  • 10UF electrolytic capacitor
  • 100UF electrolytic capacitor
  • 0.033UF ceramic capacitor
  • 10k resistor

The application is divided into 14 headers, each corresponding to a part of the game.

When Arduino is starting, the EEPROM values are loaded for: LCD brightness, LCD contrast, matrix brightness, mute/unmute the sound and game difficulty level. A welcome message will appear on the screen for 1.5s and after that, the main menu is loaded on the LCD. The joystick input values are read continuously in loop() through readFromJoystick(); method. There are 5 functions designed to test the direction of the joystick during the game in Joystick.h file. The delay(millis); function is used only when the game blocking is allowed: the automatic scrolling in the ABOUT and HIGH SCORE sections. The rest of the application uses the millis(); function, along with different values: 0.15s for food blinking, 0.5s for heart animation, 0.25s for menu movement, 0.11s for the initial speed of the snake.

Utility.h

void changeState() {
  // changing states during the entire app
  if (!gameHasStarted) {
    // menu && settings
    if (millis() - lastMoved > moveMenuInterval) {
      connectMenus();
      lastMoved = millis();
    }
  } else {
    if (congratsScreen) {
      //the game is over but we don't have a new high score
      switchHeart();
      if (millis() - lastMoved > moveMenuInterval) {
        exitCongratsScreen();
        lastMoved = millis();
      }
    } else if (congratsHighScoreScreen) {
      //the game is over && we have a new high score
      switchHeart();
      if (millis() - lastMoved > moveMenuInterval) {
        exitCongratsHighScoreScreen();
        lastMoved = millis();
      }
    } else if (enteringPlayerName) {
      //we have a new high score
      if (millis() - lastMoved > moveMenuInterval) {
        changePlayerName();
        lastMoved = millis();
      }
    } else if (playAgainScreen) {
      //game over
      //play again or go to menu
      if (millis() - lastMoved > moveMenuInterval) {
        answerPlayAgain();
        lastMoved = millis();
      }
    } else {
      // during the game
      blinkingFood();
      if (millis() - lastMoved > moveGameInterval) {
        updateSnakePosition();
        showSnake();
        lastMoved = millis();
      }
    }
  }
}

Several boolean variables ensure the interchanging between LCD screens and game states: congratsHighScoreScreen, congratsScreen, playAgainScreen, gameHasStarted, mainMenuOpened, subMainMenuOpened, animationHeart, settingsMenuOpened, subSettingsMenuOpened. For each part of the menu that requires scrolling (horizontal or vertical) there is a function called changeX(value);, where X is the name of the screen that it corresponds, through which the new value is displayed. So, only one part of the screen is rendered each time, not the entire LCD.

The snake is saved into two arrays, one for the row coordinates and one for the column coordinates: snakeRow and snakeCol. The initial length is 2, and the head is random generated. The head is on the last position of the arrays(snakeLength - 1) and the tail is on the first position.

The snake is moved on the game board as follows:

  1. updateSnakePosition() - calculates the next row/column on the board depending on the joystick input
  2. moveGame(nextPosition, directionRow) - checks if it has food to eat and if the snake is dead
  3. moveTheSnake(nextPosition, directionRow) - calculates the new coordinates for the snake

nextPosition represents the next row if the directionRow is true, or the next column if the directionRow is false.

When a food is eaten, the length of the snake is increased by one unit and the new tail is generated by the method below:

GamePlay.h

void updateSnake() {
  ...
  
  snakeLength ++;
  for (int i = snakeLength - 1; i > 0; i--) {
    snakeRow[i] = snakeRow[i - 1];
    snakeCol[i] = snakeCol[i - 1];
  }

  tailRow = snakeRow[1];
  tailCol = snakeCol[1];
  tailRow2 = snakeRow[2];
  tailCol2 = snakeCol[2];

  if (tailRow2 == tailRow) {
    if (tailCol < tailCol2) {
      // right
      snakeRow[0] = tailRow;
      tailCol --;
      if (tailCol < minMatrixValue) {
        tailCol = maxMatrixValue;
      }
      snakeCol[0] = tailCol;
    } else {
      // left
      snakeRow[0] = tailRow;
      tailCol ++;
      if (tailCol > maxMatrixValue) {
        tailCol = minMatrixValue;
      }
      snakeCol[0] = tailCol;
    }
  } else if (tailCol2 == tailCol) {
    if (tailRow < tailRow2) {
      // under
      tailRow --;
      if (tailRow < minMatrixValue) {
        tailRow = maxMatrixValue;
      }
      snakeRow[0] = tailRow;
      snakeCol[0] = tailCol;
    } else {
      // above
      tailRow ++;
      if (tailRow > maxMatrixValue) {
        tailRow = minMatrixValue;
      }
      snakeRow[0] = tailRow;
      snakeCol[0] = tailCol;
    }
  }
}

Game levels:

  1. LOW: no walls, just random generated food/level
  2. MEDIUM: random 4/3 walls generated at level 1 and random generated food/level
  3. HIGH: fixed corner walls nd random generated food/level

LOW MEDIUM HIGH

The walls, food and the head snake are random generated using random(minInterval, maxInterval); and randomSeed(analogread(0));, because it's important for a sequence of values generated by random() to differ at the beginning of each new game.

The game saves the last 3 high scores along with the player's nicknames (maximum 4 letters/nickname). These are displayed in the Main menu -> HIGH SCORE section. It also saves the LCD contrast/brightness, sound, difficulty level and matrix settings from the settings menu in the EEPROM memory, so that they can be loaded when the Arduino board is restarted.

EEPROM schema

The new values of the name and the high score are saved in the EEPROM by shifting the memory byte by byte to make room for the new values.

Memory.h

void writeNewStringNameToEEPROM(String player1) {
 ...
 
 // player1's name
 for (int i = 0; i < len1; i++) {
   EEPROM.update(11 + i, player1[i]);
 }
 // player2's name
 for (int i = 0; i < len2; i++) {
   EEPROM.update(11 + len1 + i, player2[i]);
 }
 // player3's name
 for (int i = 0; i < len3; i++) {
   EEPROM.update(11 + len1 + len2 + i, player3[i]);
 }
}

Matrix animations Each matrix animation displayed is defined as an array of bytes (ex: three[] = {B00000000, B00111100, B00100000, B00111000, B00100000, B00100100, B00111100, B00000000};). During the game the updateMatrixDisplay(const byte matrix[]); function is called for displaying the animations on the matrix.

Matrix.h

void updateMatrixDisplay(const byte matrix[]) {
 // show the icons on the matrix display
 lc.clearDisplay(0);
 for (int row = 0; row < matrixSize; row++) {
   lc.setRow(0, row, matrix[row]);
 }
}
  1. Geeting message

  1. Moving through the menu

  1. Starting a game

GAME SET GO!

  1. Game over

  1. Start again?

About

Snake game using Arduino UNO and 8x8 led matrix

Topics

Resources

License

Stars

Watchers

Forks