270b Final Project

From CSclasswiki
Jump to: navigation, search

Proposal - 2020-04-24

The Goal: An Arduino only project that combines the 64-LED matrix display, joystick, and LCD display to make a mini snake game (example: https://playsnake.org/)

I will be working individually.

Materials:

Arduino 64-LED matrix display Joystick LCD display

Steps:

  1. Get familiar with the parts:
    1. Program the 64-Led matrix to display my name
    2. Practice using each of the parts individually
  2. Connect the 3 parts
  3. Do research on snake game implementation or similar implementation tutorials
  4. Code the game
  5. Testing

Part One - 2020-04-28

Today I sucessfully completed Part 1 of the final project, which was to connect the 64-LED matrix display to my Arduino, and program it to make it display my name.

I began by watching a video of someone programming the 8x8 LED matrix.

I then used the following schematic from this page to wire my 8x8 matrix to the Arduino.

Led-matrix.png

I then modified the code from the same page as referenced above to properly display my name. It is important to note I had to download the LedControl library from this github page and put it into my Arduino library. I also got the characters from a seperate tutorial page

#include <LedControl.h>

int DIN = 12;
int CS =  11;
int CLK = 10;

byte a[8]= {B00000000,B00111100,B01100110,B01100110,B01111110,B01100110,B01100110,B01100110};
byte l[8]= {B00000000,B00100000,B00100000,B00100000,B00100000,B00100000,B00111100,B00000000};
byte i[8]= {B00000000,B00111000,B00010000,B00010000,B00010000,B00010000,B00111000,B00000000};
byte e[8]= {B00000000,B00111100,B00100000,B00111000,B00100000,B00100000,B00111100,B00000000};
byte s[8]= {B00000000,B00111100,B00100000,B00111100,B00000100,B00000100,B00111100,B00000000};
byte h[8]= {B00000000,B00100100,B00100100,B00111100,B00100100,B00100100,B00100100,B00000000};
byte g[8]= {B00000000,B00111110,B00100000,B00100000,B00101110,B00100010,B00111110,B00000000};
byte blank[8]= {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 


LedControl lc=LedControl(DIN,CLK,CS,0);

void setup(){
 lc.shutdown(0,false);       //The MAX72XX is in power-saving mode on startup
 lc.setIntensity(0,15);      // Set the brightness to maximum value
 lc.clearDisplay(0);         // and clear the display
}

void loop(){ 
    
    printAli();
   
    lc.clearDisplay(0);
    
    delay(1000);
}

void printAli()
{
  printByte(a);
  delay(1000);
  printByte(l);
  delay(1000);
  printByte(i);
  delay(1000);
  printByte(blank);
  delay(1000);
  printByte(e);
  delay(1000);
  printByte(s);
  delay(1000);
  printByte(h);
  delay(1000);
  printByte(g);
  delay(1000);
  printByte(h);
  delay(1000);
  printByte(i);
  delay(1000);
}

void printByte(byte character [])
{
  int i = 0;
  for(i=0;i<8;i++)
  {
    lc.setRow(0,i,character[i]);
  }
}

Part Two - 2020-04-28

For Part Two I added the LCD display along with the 8x8 LED Matrix and had it display the number of times my full name had printed on the Matrix. I used the following wiring schematic from the Arduino Tutorials to add the LCD display to the Arduino along with the Matrix. The only exception was that I used pins 8 and 9 instead of pins 11 and 12. This was because, as you can see in the wiring schematic in Part 1, I had already used pins 11 and 12 to connect the 8x8 Matrix.

LCD Base bb Schem.png

I then added the following lines of code to my original file:

#include <LiquidCrystal.h>

int count = 0;

LiquidCrystal lcd(9, 8, 5, 4, 3, 2);

void setup(){

 lcd.begin(16, 2);
 lcd.print("# of times name");
}

void loop(){ 
    lcd.setCursor(0, 1);
    lcd.print("printed: ");
    lcd.print(count);

    count = count+1;
}

The final outcome was successful! The below picture shows an example of what the system and output looked like.

Part-two.jpg


Part Three - 2020-04-28

For Part Three I integrated the 8x8 Matrix with the joystick. My goal was to have one dot on the Matrix lit up, and have that dot move following the joystick's direction. I used the same wiring schematic from Part 1 for the Matrix, and used the following wiring schematic from the Arduino Documentation for the Joystick

Joystick.jpg

I used a youtube video to better understand my goal, and got the following code from the video as well.


int UD = 0;
int LR = 0;

int DIN = 12;
int CS =  11;
int CLK = 10;


LedControl lc=LedControl(DIN,CLK,CS,0);


void setup() {
  Serial.begin(9600);

  lc.shutdown(0,false);// turn off power saving, enables display
  lc.setIntensity(0,8);// sets brightness (0~15 possible values)
  lc.clearDisplay(0);// clear screen

}

void loop() {
  UD = analogRead(A0);
  LR = analogRead(A1);
  char x_translate = map(LR, 1021, 0, 7, 0); //This maps the values//
  char y_translate = map(UD, 1021, 0, 0, 7);
  lc.clearDisplay(0);
  lc.setLed(0,x_translate,y_translate,true);  
  delay(250);
}

Overall I had success with this code. Ultimately it mapped the lit LED to the same position on the Matrix as my joystick was.

Note: I am already anticipating some problems I may have in Part 4 with reading the values from my 360 degree joystick into a strictly horizontal and vertical direction, as well as how to properly time and space my calls to read the joystick value in order to keep movement to one LED space at a time rather than jumping all over the Matrix. Once I figure out how to tackle these problems I think I will be well on my way to completing Part 4.

Part Four - 2020-05-04

For Part Four I started by looking up online tutorials. The main one I used is shown below, but other sources include the Arduino Community Create site and this Instructables tutorial .



Once I had an idea of what the game should look like I began wiring all my components together. I essentially kept all the same wiring from my previous parts, but this time i had the Matrix wired at the same time as both the joystick and the LCD. The full wiring schematic is pictured below. Please note that in order for the orientation of the joystick and Matrix to be correct both must be used vertically as shown in the diagram.

Part4.png


I then tested out my previous programs to make sure that all the connections were working. While doing this I actually discovered that some of my wires had gone bad, and I had to swap them out. I then got started on the code. I started by combining my set up from previous parts with Snake Game code from the previous video.

This involved mostly modifying and simplifying the code. I read through it all and made sure I understood it and then was able to make modifications. Some examples of changes I made are:

  1. Removing the potentiometer and replaced it with hardcoded snake speed.
  2. Altering the game so that it would end if the snake hit the border for added difficulty
    1. A side effect of this was that I had to alter how the snake was initially created to make sure no one would lose immediately upon starting the game
  3. Replacing the Matrix writing with LCD screen writing to give players instructions and score information
  4. Cleaning up and simplifying the code so that it only included parts I understood and could read easier for new users

The final result was:

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

static const short joystickX = A0;   // joystick X axis pin
static const short joystickY = A1;   // joystick Y axis pin
static const short joystickVCC = 15; 
static const short joystickGND = 14; 

static const short CLK = 10;   
static const short CS  = 11;  
static const short DIN = 12; 

const short initialSnakeLength = 3;

LedControl matrix(DIN, CLK, CS, 1);
LiquidCrystal lcd(9, 8, 5, 4, 3, 2);

struct Point {
  int row = 0, col = 0;
  Point(int row = 0, int col = 0): row(row), col(col) {}
};

struct Coordinate {
  int x = 0, y = 0;
  Coordinate(int x = 0, int y = 0): x(x), y(y) {}
};

bool win = false;
bool gameOver = false;

Point snake;

// food is not anywhere yet
Point food(-1, -1);

// construct with default values in case the user turns off the calibration
Coordinate joystickHome(500, 500);

// snake parameters
int snakeLength = initialSnakeLength; // choosed by the user in the config section
int snakeSpeed = 300; // will be set according to potentiometer value, cannot be 0
int snakeDirection = 0; // if it is 0, the snake does not move

// direction constants
const short up     = 1;
const short right  = 2;
const short down   = 3; // 'down - 2' must be 'up'
const short left   = 4; // 'left - 2' must be 'right'

// threshold where movement of the joystick will be accepted
const int joystickThreshold = 160;


// snake body segments storage
int gameboard[8][8] = {};


void setup() {
  pinMode(joystickVCC, OUTPUT);
  pinMode(joystickGND, OUTPUT);
  matrix.shutdown(0, false);
  matrix.setIntensity(0, 8);
  matrix.clearDisplay(0);

  randomSeed(analogRead(A5));
  snake.row = 4;
  snake.col = 1;
  lcd.clear();
  Serial.begin(9600);  // set the same baud rate on your Serial Monitor
  lcd.begin(16, 2);
  lcd.print("Move joystick up");
  lcd.setCursor(0, 1);
  lcd.print("to begin game");
  delay(2000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Snake Game");
  lcd.setCursor(0,1);
  lcd.print("By Ali Eshghi");
}


void loop() {
  generateFood();    // if there is no food, generate one
  scanJoystick();    // watches joystick movements & blinks with food
  calculateSnake();  // calculates snake parameters
  handleGameStates();
}






void generateFood() {
  if (food.row == -1 || food.col == -1) {
    if (snakeLength >= 64) {
      win = true;
      return; // prevent the food generator from running, in this case it would run forever, because it will not be able to find a pixel without a snake
    }

    // generate food until it is in the right position
    do {
      food.col = random(8);
      food.row = random(8);
    } while (gameboard[food.row][food.col] > 0);
  }
}


// watches joystick movements & blinks with food
void scanJoystick() {
  int previousDirection = snakeDirection; // save the last direction
  long timestamp = millis();

  while (millis() < timestamp + snakeSpeed) {

    // determine the direction of the snake
    analogRead(joystickY) < joystickHome.y - joystickThreshold ? snakeDirection = up    : 0;
    analogRead(joystickY) > joystickHome.y + joystickThreshold ? snakeDirection = down  : 0;
    analogRead(joystickX) < joystickHome.x - joystickThreshold ? snakeDirection = left  : 0;
    analogRead(joystickX) > joystickHome.x + joystickThreshold ? snakeDirection = right : 0;

    // ignore directional change by 180 degrees (no effect for non-moving snake)
    snakeDirection + 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0;
    snakeDirection - 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0;

    // intelligently blink with the food
    matrix.setLed(0, food.row, food.col, millis() % 100 < 50 ? 1 : 0);
  }
}


// calculate snake movement data
void calculateSnake() {
  switch (snakeDirection) {
    case up:
      snake.row--;
      fixEdge();
      matrix.setLed(0, snake.row, snake.col, 1);
      break;

    case right:
      snake.col++;
      fixEdge();
      matrix.setLed(0, snake.row, snake.col, 1);
      break;

    case down:
      snake.row++;
      fixEdge();
      matrix.setLed(0, snake.row, snake.col, 1);
      break;

    case left:
      snake.col--;
      fixEdge();
      matrix.setLed(0, snake.row, snake.col, 1);
      break;

    default: // if the snake is not moving, exit
      return;
  }

  // if there is a snake body segment, this will cause the end of the game (snake must be moving)
  if (gameboard[snake.row][snake.col] > 1 && snakeDirection != 0) {
    gameOver = true;
    return;
  }

  // check if the food was eaten
  if (snake.row == food.row && snake.col == food.col) {
    food.row = -1; // reset food
    food.col = -1;

    // increment snake length
    snakeLength++;

    // increment all the snake body segments
    for (int row = 0; row < 8; row++) {
      for (int col = 0; col < 8; col++) {
        if (gameboard[row][col] > 0 ) {
          gameboard[row][col]++;
        }
      }
    }
  }

  // add new segment at the snake head location
  gameboard[snake.row][snake.col] = snakeLength + 1; // will be decremented in a moment

  // decrement all the snake body segments, if segment is 0, turn the corresponding led off
  for (int row = 0; row < 8; row++) {
    for (int col = 0; col < 8; col++) {
      // if there is a body segment, decrement it's value
      if (gameboard[row][col] > 0 ) {
        gameboard[row][col]--;
      }

      // display the current pixel
      matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1);
    }
  }
}


// causes the snake to appear on the other side of the screen if it gets out of the edge
void fixEdge() {
  if (snake.col < 0){
    gameOver = true;
  }
  if (snake.col > 7){
    gameOver = true;
  }
  if (snake.row < 0){
    gameOver = true;
  }
  if (snake.row > 7){
    gameOver = true;
  }
}


void handleGameStates() {
  if (gameOver || win) {
    unrollSnake();

    if (gameOver){
      lcd.clear();
      lcd.print("Game Over!");
      lcd.setCursor(0, 1);
      lcd.print("Score: ");
      lcd.print(snakeLength);
      
      delay(5000);

      lcd.clear();
      lcd.print("Move joystick up");
      lcd.setCursor(0, 1);
      lcd.print("to play again");

      delay(2000);
      lcd.clear();
      lcd.print("Snake Game");
      lcd.setCursor(0, 1);
      lcd.print("By Ali Eshghi");
      
      
    }
    else if (win) {
      lcd.print("You win!");
    }

    // re-init the game
    
    win = false;
    gameOver = false;
    snake.row = random(8);
    snake.col = random(8);
    food.row = -1;
    food.col = -1;
    snakeLength = initialSnakeLength;
    snakeDirection = 0;
    memset(gameboard, 0, sizeof(gameboard[0][0]) * 8 * 8);
    matrix.clearDisplay(0);
  }
}


void unrollSnake() {
  // switch off the food LED
  matrix.setLed(0, food.row, food.col, 0);

  delay(800);

  // flash the screen 5 times
  for (int i = 0; i < 5; i++) {
    // invert the screen
    for (int row = 0; row < 8; row++) {
      for (int col = 0; col < 8; col++) {
        matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 1 : 0);
      }
    }

    delay(20);

    // invert it back
    for (int row = 0; row < 8; row++) {
      for (int col = 0; col < 8; col++) {
        matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1);
      }
    }

    delay(50);

  }

  delay(600);

  for (int i = 1; i <= snakeLength; i++) {
    for (int row = 0; row < 8; row++) {
      for (int col = 0; col < 8; col++) {
        if (gameboard[row][col] == i) {
          matrix.setLed(0, row, col, 0);
          delay(100);
        }
      }
    }
  }
}

Finally, here is a video of my working game:

Finalpic.jpg