CSC270 Team 6 project 2019

From CSclasswiki
Jump to: navigation, search

Overview

Project Description

Our project is a game of snake on a 8x8 LED matrix with an LCD screen to display the score. The snake is controlled by 4 direction buttons mounted on a handheld board, and there is also a start/reset button that is used to start the game, or to reset and start a new game if one is currently in progress. It also tracks and displays high scores.

The project proposal can be found here.

Our development process is recorded in our progress log.

Maddy Fox and Grace Moore, Spring 2019

Materials and Methods

We coded in Python for the Raspberry Pi and in C for the Arduino.

Hardware

We used:

Libraries

  • gpiozero for input and output to and from the Pi
  • LiquidCrystal for the LCD display accessible through Arduino IDE
  • pySerial for serial communication from the Pi
  • LedControl for the LED matrix accessible through Arduino IDE

Installation Procedures

PySerial:

sudo apt-get update
sudo apt-get install python-pip
sudo pip install pySerial

LedControl, LiquidCrystal:

Followed this tutorial to download into Arduino IDE

Final Results

Our final project is a working game of Snake using 5 buttons (4 directional and 1 to restart the game), an 8x8 LED matrix, a LCD board, the Arduino MEGA 2560, and Raspberry Pi 3. The buttons are wired to the Raspberry Pi and the Pi is connected to the Arduino through the serial USB port. The Pi sends a byte containing a character to the Arduino which is read by the Arduino to determine the movement of the snake. When the snake successfully runs into 'food', the LED matrix tells the Arduino and the score is updated. The score along with some other messages about the game are displayed on the LCD board. The LCD board also shows the highest score reached since that program was last run.

We had to replace the joystick for buttons because we were missing a MCP3008 chip (analog-to-digital converter) so we could not connect the joystick to the Pi. The buttons are also now connected to the Pi because we decided that one way communication was easier than sending information from the Arduino to the Pi back to the Arduino. In the future, if we wanted to, we could easily make the game have different modes of difficultly in which the easy mode would allow the snake to wrap around the board and the harder mode would prevent the snake from hitting the borders of the board. Additionally, we could try to set up the high score to save even if the Arduino program is re-run.

FinalSchematic.png
Block diagram of wiring connections


FinalWiring.jpeg
Final Setup of Game

Resources

A few project guides we used:

Progress Log

4/18/19

Today we worked on getting the snake to wrap around the the LED board instead of the game ending when it hit the edge. We did this by learning more about the code from Hackster and commenting it so we know what is going on.

We also re-wired the LED matrix and the joystick so that it was cleaner and attached the LED matrix and joystick to a breadboard with rubber bands. We then brought in the Pi and thought about how to attach the joystick to it. After some Googling, we realized that we do not have one of the parts to use the joystick with the Pi so we made the decision to switch to using buttons to control the movement of the snake. We wired up 5 buttons to the Pi, 4 to control the snake and 1 to start the game, and tested them.

We then moved on to trying to set up a connection between the Arduino and the Pi. We found a tutorial and began to work through the steps. We installed the pip package and then the pySerial module to the Pi. We successfully set up a connection that sent information from the Arduino to the Pi. Next, class we will work in the opposite direction.

We used the commands below to install the pip package and the pySerial module on the Pi:

sudo apt-get update
sudo apt-get install python-pip
sudo pip install pySerial

4/23/19

Today we worked on getting the Arduino and the Pi to send information back and forth to each other. While we could get each to send and read data, we had some issues with figuring out what type of data we should be sending. For instance, we were trying to get Pi to read a message from the Arduino, recognize it matched a phrase in the code, have it ask the user for a number, send the number to the Arduino, and have the Arduino read it and flash an LED the number of times specified (as per the Sunfounder page). First, we had issues with the Pi matching the message to the string in the code, and then once we got that working, we had issues with it recognizing the user input as a number and writing it to the Arduino. Eventually we got the Pi matching the string, asking for user input, and writing the input to the Arduino. However the Arduino still wasn't blinking the right amount of times, and we couldn't print to the serial monitor to test because the Arduino can't be connected to one of our laptops while it runs because it is connected to the pi instead.

We also started writing code to take in data from the buttons connected to the pi and transmit it to the Arduino. However, before we can really test this, we have to figure out some way for the Arduino to have a serial monitor and a connection to the pi at the same time. Tomorrow we would like to try downloading the Arduino IDE on the pi and running it from there, or alternately chopping open a usb cable and manually wiring another serial connection using jumper cables.

LEDMatrix.jpg
Button and LED matrix initial wiring

4/24/29

Today we installed the Arduino IDE on the Pi. We then went into the config.txt file using nano to change the window display so we could access the tool bar. We tried to add a library that we need to run our game to the Pi but we kept getting the same error.

4/25/19

Today we FINALLY got the Pi and the Arduino to communicate with a serial port. We had to upload the Arduino code from a separate laptop instead of the Pi because we still have not figured out how to run the LEDControl library on the Pi. We edited the Arduino code so that it takes in a character from the Pi and the sets the vector of movement for the LED snake on the board. We also set up a reset button so after the game ends it can be started again.

We also managed to set up the LCD screen following this tutorial to display the score and some messages about the game. Our code can be seen in the Arduino Code section.

FinalSetUp.jpg
Final wiring setup before soldering

4/29/19

Today we added a high score feature. The LCD screen now displays the high score in addition to the player's score during gameplay. It also displays a celebratory message if you beat it. The high score variable updates at the end of each game as needed.

5/5/19

Today we worked on getting the Python program to run when the Pi reboots. We used this tutorial to do so. It works, but it takes a while. The Arduino restarts 3 times for every Pi restart, and the buttons don't work until the final restart. The whole process takes about 35 seconds.

We also went through and made some minor organizational changes to the Arduino code, such as creating an updateLCD function instead of having multiple lines to clear, set the cursor, and rewrite each time.

Finally, we mounted the Arduino, Pi, LED matrix, and lcd screen on a sheet of plexiglass and soldered the buttons onto a little board with long wires. This way, the plexiglass can go inside of a display case, but the player can hold the little board with the buttons.

Final Code

Arduino Code

/**
 * Snake
 * Implementation of the classic Snake game 
 * where the snake moves constantly and the player
 * has to avoid intersections with itself.
 * When the snake eats food it grows and speeds up a little bit.
 * 
 * Updated by Maddy Fox and Grace Moore
 * Original written by Sergey Royz and Hunter Buzzell
 * Upload to Arduino via serial port to run
 */

#include <LedControl.h>
#include <LiquidCrystal.h>

/* Display PINs */
#define CLK     8
#define CS      9
#define DIN     10

#define SIZE    8
#define ADVANCE_DELAY 20

/* Set up snake and food variables */
int snake[SIZE*SIZE][2];
int length;
int food[2], v[2];
int score = 0;
int high_score = 0;
bool is_game_over = false;
long current_time; //Part of speeding up snake
long prev_advance; //Part of speeding up snake
int blink_count; //Blinks 3 times when game over
const short messageSpeed = 5;

/* LCD Board PINs */
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

/* Set up LED matrix */
LedControl lc = LedControl(DIN, CLK, CS, 1);

/* Initialize game with one food and small snake */
void init_game() {
  prev_advance = current_time = 0;
  blink_count = 3;
  int half = SIZE / 2;
  length = SIZE / 3;

  for (int i = 0; i < length; i++) {
      snake[i][0] = half - 1;
      snake[i][1] = half + i;
  }

  food[0] = half + 1;
  food[1] = half - 1;

  v[0] = 0;
  v[1] = -1;

  // display player's score and high score
  updateLCD( "Score: ", 0 );
  lcd.print(score);
  
  updateLCD( "High Score: ", 1 );
  lcd.print(high_score);
}

/* Update LED matrix */
void render() {
  for (int i = 0; i < length; i++) {
    lc.setLed(0, snake[i][0], snake[i][1], 1);
  }
  lc.setLed(0, food[0], food[1], 1);
}

/* Clear LED matrix */
void clearScreen() {
  for (int x = 0; x < SIZE; x++) {
    for (int y = 0; y < SIZE; y++) {
      lc.setLed(0, x, y, 0);
    }
  }
}

/* Update LCD board */
void updateLCD( char *message, int pos) {
  if (pos == 0) {
    lcd.setCursor(0,0);
    lcd.print(message);
  }

  else if (pos == 1) {
    lcd.setCursor(0,1);
    lcd.print(message);
  }
}

/**
 * moves the snake forward
 * returns true if the game is over
 */
bool advance() {
  int head[2] = {snake[0][0] + v[0], snake[0][1] + v[1]};

  // if you go off the top of the board, loop back around to the bottom
  if (head[0] < 0) {
    snake[0][0] = SIZE;
  }
  // if you go off the bottom of the board, loop back around to the top
  if (head[0] >= SIZE) {
    snake[0][0] = -1;
  }

  // if you go off the left of the board, loop back around to the right
  if (head[1] < 0) {
    snake[0][1] = SIZE;
  }

  // if you go off the right of the board, loop back around to the left
  if (head[1] >= SIZE) {
    snake[0][1] = -1;
  }

  // if snake eats itself, it dies
  for (int i = 0; i < length; i++) {
      if (snake[i][0] == head[0] && snake[i][1] == head[1]) {
        delay(1000);
        showGameOverMessage();
        return true;
      }
  }
  
  // if the snake eats food then it gets bigger
  bool grow = (head[0] == food[0] && head[1] == food[1]);
  if (grow) {
      length++;  
      randomSeed(current_time);    
      food[0] = random(SIZE);
      food[1] = random(SIZE);
      
      // Update score
      score = score + 1;
  }

  // update score on LCD
  updateLCD("Your Score: ", 0);
  lcd.print(score);

  // wrap snake around the LED matrix
  for (int i = length - 1; i >= 0; i--) {
      snake[i + 1][0] = (snake[i][0]+SIZE) % SIZE; // modulus is to deal with wrapping, +SIZE is to elimate -1 modulus issue
      snake[i + 1][1] = (snake[i][1]+SIZE) % SIZE;
  }

  // Move snake
  snake[0][0] += v[0];
  snake[0][1] += v[1];
  return false;
}

void setup() {
  Serial.begin(9600);
  lcd.begin(16, 2);

  // Set up LED control
  lc.shutdown(0, false);
  lc.setIntensity(0, 8);

  // Initialize game
  updateLCD("    SNAKE!    ", 0);
  updateLCD(" May take a sec", 1);
  delay(5000);
  lcd.clear();
  
  init_game();
  render();
}

void loop() {
  // if the game is over
  if (!is_game_over) {
    clearScreen();
    render();

    if (current_time - prev_advance > ADVANCE_DELAY) {
      is_game_over = advance(); // advance will return true if game over, false if not
      prev_advance = current_time;    
    }
  } else {
    //Game over -> blink 3 times
    while (blink_count > 0) {
      clearScreen();
      delay(300);
      render();
      delay(300);
      blink_count--;     
         
    }
  }
  if (Serial.available()) {
    int piInput = Serial.read();
    readControls(piInput);
    Serial.flush();
  }
  current_time++;
}

void restart() {
  lcd.clear();
  score = 0;  
  init_game();
  is_game_over = false;
}

/* handles button input */
void readControls(int piInput) {
  char direct = (char) piInput;
  //If left button, move left
  if (direct == 'l') {
    v[0] = 0;
    v[1] = -1;
  }
  //If right button, move right
  if (direct == 'r') {
    v[0] = 0;
    v[1] = 1;
  }
  //If top button, move up
  if (direct == 'u') {
    v[0] = -1;
    v[1] = 0;
  }
  //If bottom button, move down
  if (direct == 'd') {
    v[0] = 1;
    v[1] = 0;
  }

  if (direct == 's') {
    restart();
  }

}

const PROGMEM bool gameOverMessage[8][90] = {
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,1,0,1,1,1,0,1,1,0,0,0,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,1,1,0,0,1,1,0,1,1,1,1,1,1,1,0,1,1,0,0,0,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,1,1,0,0,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0}

}; 

/* Show game over message */
void showGameOverMessage() {
  // update LCD to display final score
  lcd.clear();
  updateLCD("   Game Over!   ", 0);
  updateLCD("Final Score: ", 1);
  lcd.print(score);

  // update high score
  if (score > high_score) {
    high_score = score;

    lcd.clear();
    updateLCD("New High Score!", 0);
    updateLCD("High Score: ", 1);
    lcd.print(high_score);
  }

  // update LED matrix
  for (int d = 0; d < sizeof(gameOverMessage[0]) - 7; d++) {
    for (int col = 0; col < 8; col++) {
      delay(messageSpeed);
      for (int row = 0; row < 8; row++) {
        // this reads the byte from the PROGMEM and displays it on the screen
        lc.setLed(0, row, col, pgm_read_byte(&(gameOverMessage[row][col + d])));
      }
    }
  }

  // update LCD to play again message
  lcd.clear();
  updateLCD("  Press Button  ", 0);
  updateLCD(" To Play Again ", 1);
}

Pi Code

#!/usr/bin/env python

# Maddy and Grace
# Transmits button data from Pi to Arduino
# CSC270 4/23/19
#
# Wiring:
#   up button -> 11
#   down button -> 13
#   left button -> 15
#   right button -> 8
#   start button -> 10
#   USB -> Arduino

from gpiozero import Button
import serial

port = "/dev/ttyACM0" # Make sure Arduino is port ACM0
rate = 9600 # Rate should match Arduino's rate

s1 = serial.Serial(port, rate)
s1.flushInput()

upButton = Button(17)
downButton = Button(27)
leftButton = Button(22)
rightButton = Button(14)
startButton = Button(15)

# Messages to transmit to Arduino
upmsg = 'u'
downmsg = 'd'
leftmsg = 'l'
rightmsg = 'r'
startmsg = 's'

# Constantly be checking for button presses
while(True):
    if (upButton.is_pressed):
        upButton.wait_for_release(); # Ensure msg sent once per button press
        s1.write(upmsg.encode()) # Send msg to Arduino
        
    if (downButton.is_pressed):
        downButton.wait_for_release()
        s1.write(downmsg.encode())
        
    if (leftButton.is_pressed):
        leftButton.wait_for_release()
        s1.write(leftmsg.encode())
        
    if (rightButton.is_pressed):
        rightButton.wait_for_release()
        s1.write(rightmsg.encode())
        
    if (startButton.is_pressed):
        startButton.wait_for_release()
        s1.write(startmsg.encode())

pause()