Difference between revisions of "Eleanor's Final Project"

From CSclasswiki
Jump to: navigation, search
(Schematic)
Line 337: Line 337:
 
</source>
 
</source>
 
*NOTE: To use game with a different remote, follow tutorial at link on IRremote usage to find that remote's individual codes, then substitute for given codes
 
*NOTE: To use game with a different remote, follow tutorial at link on IRremote usage to find that remote's individual codes, then substitute for given codes
 +
 +
=Part 3: Space Invaders=
 +
 +
===Project Proposal===
 +
Space Invaders is a well known arcade game from 1978, information on it can be found at this Wikipedia page: https://en.wikipedia.org/wiki/Space_Invaders
 +
the basic tenants of the game include rows of descending enemies who shoot at a player at the bottom of the screen. The player can move left and right to evade the enemies shots, with barriers to duck behind. The player attempts to shoot all the enemies without getting shot themselves and before the enemies can reach the bottom of the screen.
 +
This implementation involves yet again using the IRremote signal to move the player left and right, and the pushbutton to fire.
 +
===Helpful Links===
 +
Same as part 2
 +
===Libraries===
 +
Same as part 2
 +
===Working Notes===
 +
-The same as the snake game, linkedlists will be super important for this application, as it involves a lot of tracking, updating, removing, and dynamically resizing lists for enemy pixels, enemy and player shots, and the barriers
 +
- barriers can withstand 3 shots before being destroyed, can be destroyed by player as well
 +
- first big problem encountered was with the pushbutton. At first, I was doing the timing the same as the snake game, implementing a 1s delay at the end of the loop to make the game playable. This wasn't working for the firing because it would only check once per loop if the button was pressed at the exact moment it was checked, not if it had been pressed during the loop
 +
-overcame this by doing the delay more dynamically, entire loop runs as a for loop updating every 2ms to check if the button has been fired. I was then able to dynamically time the shooting for enemies and players by having the updateShot called at variable intervals
 +
- expensive to check all necessary lists in nested for loops each iteration, but I figure it can't be super expensive because at most each shooting list will have maybe 3 entries at a time, so I figured it wasn't terribly inefficient.
 +
- main issue in game was with timing everything correctly and having enemies fire with correct timing.
 +
===Schematics===
 +
[[File:part3schematic.png | 651px| center ]]

Revision as of 17:26, 7 May 2020

Project Proposal

I am choosing to do the Arduino only option. My project idea is to utilize the 8x8 grid and the joystick to create the game Snake, and for Part 3 to use the 8x8 grid, the joystick, and the button to recreate space invaders.

Part 1: Name Print

The name printing aspect was a fairly straightforward introduction to using the 8x8 LED screen

Helpful Links

Video Tutorial with links to example code: https://www.youtube.com/watch?v=TOuKnOG8atk

Libraries

LedControl: https://randomnerdtutorials.com/guide-for-8x8-dot-matrix-max7219-with-arduino-pong-game/

Schematic

*All schematics using photos from https://www.arduino.cc/en/Tutorial/MultiSerialMega and https://educ8s.tv/arduino-8x8-led-matrix-tutorial/
Pt1scheme.png

Code

#include <LedControl.h>

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

//obtained binary letter values from : https://create.arduino.cc/projecthub/SAnwandter1/programming-8x8-led-matrix-23475a
byte A[] = {  B00000000,B00111100,B01100110,B01100110,B01111110,B01100110,B01100110,B01100110};
byte E[] = {B00000000,B00111100,B00100000,B00111000,B00100000,B00100000,B00111100,B00000000};
byte L[] = {B00000000,B00100000,B00100000,B00100000,B00100000,B00100000,B00111100,B00000000};
byte R[] = {B00000000,B00111000,B00100100,B00100100,B00111000,B00100100,B00100100,B00000000};
byte N[] = {B00000000,B00100010,B00110010,B00101010,B00100110,B00100010,B00000000,B00000000};
byte O[] = {B00000000,B00111100,B01000010,B01000010,B01000010,B01000010,B00111100,B00000000};


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(){ 

    printByte(E);
     
    delay(1000);

    printByte(L);
    
    delay(1000);

    printByte(E);    

    delay(1000);
   
    printByte(A);    

    delay(1000);
    printByte(N);    

    delay(1000);
    printByte(O);    

    delay(1000);
    printByte(R);    

    delay(1000);
   
    lc.clearDisplay(0);
    
    delay(1000);
}


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


Part 2: Addition of Sensor - Snake Game

Description of Project

The inspiration for the project was the Snake genre of videogames, an overview of which is given here: https://en.wikipedia.org/wiki/Snake_(video_game_genre)

The basic rules of the game consist of a player-controlled pixel with a tail that grows in length by a single pixel each time the snake consumes an "enemy" pixel. The snake cannot self-intersect or fall off the edge of the map.

The final idea ended up utilizing the IR sensor in the Elegoo kit controlling the snake using the up/down/side arrow keys on a typical Comcast TV remote.

Libraries

LinkedList: https://github.com/ivanseidel/LinkedList IRremote : given by Elegoo kit included libraries IRremoteInt: given by Elegoo kit included libraries

Working Notes / Thought Process

-Tried to use vectors, cant use in arduino. Originally wanted to use some sort of hashmap to make lookup for enemy spawning faster, problem is that we need to make sure the enemy won’t spawn on the snake. -Instead I know the longest the snake could be is 64 units so it won’t be super inefficient to store the presized array. -Will just need to track size to find head and tail. -array wont work because I need to be able to erase from the tail, so the presized nature would make that inconvenient -use a linkedlist instead -couldn’t get array to work inside linked list, will just add row, col instead as separate ints - originally intended to use joystick sensor, but this ended up seeming inconvenient as well and not as true to the function of the joystick which can record a variety of positions between x = 0 --> 1024 and y = 0 --> 1024, allowing for less restricted movement - fun idea that works better with snake would be to use IR remote - IR remote included with Elegoo kit yields inconsistent results (same button shows multiple codes) so I decided to use my TV remote which I figured would be slightly more reliable

Helpful Links and Why They Are Helpful

Pong Game: https://randomnerdtutorials.com/guide-for-8x8-dot-matrix-max7219-with-arduino-pong-game/ - this link was helpful as it showed how a single LED could be manipulated, also showed that game simulation was possible using the Arduino and 8x8 matrix

IRremote Project: https://create.arduino.cc/projecthub/ingo-lohs/first-test-super-starterkit-from-elegoo-infrared-ab4272 - this link was helpful for digging into the function and usage of the IR remote sensor included with the Elegoo kit

How to Reset Arduino in Software: https://www.instructables.com/id/two-ways-to-reset-arduino-in-software/ - this link was helpful as I wanted to be able to have the game reset itself if the player lost. It didn't really matter if it was a software or hardware fix, but the software approach was easy enough and obviously included no additional hardware

Schematic

Part2schematic.png

Code

/*
 Created by Eleanor Donaher
*/

#include "LedControl.h"
#include <LinkedList.h>
#include "binary.h"
#include  <IRremote.h>
#include <IRremoteInt.h>

/*
 DIN connects to pin 12
 CLK connects to pin 11
 CS connects to pin 10 
*/
//setup led
LedControl lc=LedControl(12,11,10,1);
int receiver = 7; //setup IR Reciever
IRrecv irrecv(receiver);
decode_results results; //instance of decode results
//enemy x and y
int enemy_x = 1;
int enemy_y = 2;
LinkedList<int> snake_position = LinkedList<int>();
 //boolean to report collision (initially set to true so enemy will randomly spawn)

boolean collision = false;
boolean headCollision = false; 
//direction snake is going, initially right
// (up = 0, down = 1, left = 2, right = 3)
int snake_dir = 3;
//findrow = x/10
//findcol = x%10
int gameDelay = 400;


void setup() {
  Serial.begin(9600);
  //start reciever
  irrecv.enableIRIn();
  lc.shutdown(0,false);
  // Set brightness to a medium value
  lc.setIntensity(0,8);
  // Clear the display
  lc.clearDisplay(0); 

  //set snake's initial position

  //head
  snake_position.add(31);
  //tail
  snake_position.add(30);
  //render initial snake

  lc.setLed(0,3,1,1);
  lc.setLed(0, 3, 0, 1);
  //render initial enemy
  lc.setLed(0,1,2, 1);
  
}
void(* resetFunc) (void) = 0;//declare reset function at address 0


//track if spawn failed
boolean failedSpawn = false;
void loop(){
  
  //reset collisions to false
  if(!failedSpawn){
    collision = false;
}
  
  headCollision = false;
  //record previous head
  int prev_head_position = snake_position.get(0);
  int prev_head_x = prev_head_position/10;
  int prev_head_y = prev_head_position%10;

  //check for reciever input
  if (irrecv.decode(&results)){
    //down
    if(results.value==1511671869){
      if(snake_dir!=0){
        snake_dir=1;
      }
    }
    if(results.value==741245183){
      if(snake_dir!=1){
        snake_dir=0;
      }
    }
    if(results.value==2507695210){
      if(snake_dir!=3){
        snake_dir=2;
      }
    }
    if(results.value==3692585004){
      if(snake_dir!=2){
        snake_dir=3;
      }
    }
    irrecv.resume(); //recieve next value
  }
//update snake position
  int new_snake_head;
  if(snake_dir == 0){
    new_snake_head = prev_head_position-10; 
  }
  if(snake_dir == 1){
    new_snake_head = prev_head_position+10;
  }
  if(snake_dir == 2){
    new_snake_head = prev_head_position-1;
  }
  if(snake_dir == 3){
    new_snake_head = prev_head_position+1;
  }
 //add new head to snake
  snake_position.unshift(new_snake_head);
  //light new LED
  int new_snake_x = new_snake_head/10;
  int new_snake_y = new_snake_head%10;
  lc.setLed(0, new_snake_x, new_snake_y, 1);

 //check for collision
  if(new_snake_x == enemy_x && new_snake_y == enemy_y){
    //collision detected
    collision = true;
    headCollision = true;
  }

 //if there was not a head collision, unlight tail LED and remove it from list
  if(!headCollision){
    int snake_tail = snake_position.pop();
    int tail_x = snake_tail/10;
    int tail_y = snake_tail%10;
    lc.setLed(0, tail_x, tail_y, 0);
 }

 //if snake falls off board, game over
  if(new_snake_x > 7 || new_snake_x <0 || new_snake_y > 7 || new_snake_y < 0){
    //you lose sucker!!!
     // sad face
    byte sf[8]= {B00111100,B01000010,B10100101,B10000001,B10011001,B10100101,B01000010,B00111100};

    //you lose sucker!!!
    // Display sad face
    lc.setRow(0,0,sf[0]);
    lc.setRow(0,1,sf[1]);
    lc.setRow(0,2,sf[2]);
    lc.setRow(0,3,sf[3]);
    lc.setRow(0,4,sf[4]);
    lc.setRow(0,5,sf[5]);
    lc.setRow(0,6,sf[6]);
    lc.setRow(0,7,sf[7]);
    delay(1000);
    resetFunc(); //call reset
  }

  //if snake self-intersects, game over
  for(int i = 1; i<snake_position.size(); i++){
    int snake_xy = snake_position.get(i); 
    if(new_snake_head == snake_xy){
      //also game over sucker!!!  
        // sad face
      byte sf[8]= {B00111100,B01000010,B10100101,B10000001,B10011001,B10100101,B01000010,B00111100};
  
      //you lose sucker!!!
      // Display sad face
      lc.setRow(0,0,sf[0]);
      lc.setRow(0,1,sf[1]);
      lc.setRow(0,2,sf[2]);
      lc.setRow(0,3,sf[3]);
      lc.setRow(0,4,sf[4]);
      lc.setRow(0,5,sf[5]);
      lc.setRow(0,6,sf[6]);
      lc.setRow(0,7,sf[7]);
      delay(1000);
      resetFunc(); //call reset 
    }
  }//end self int check



  //if collision, respawn enemy
  if(collision == 1){   
    failedSpawn = false;
    //lc.setLed(0, enemy_x, enemy_y, 0);
    enemy_x = random(8);
    enemy_y = random(8);
    collision = false;
    for(int i =0; i<snake_position.size(); i++){
      int snake_xy = snake_position.get(i);
      int snake_x = snake_xy/10;
      int snake_y = snake_xy%10;
      //if the enemy is on top of the snake
      if(enemy_x==snake_x && enemy_y==snake_y){
        failedSpawn = true;
        collision=true;
      }//
    }//endpositionCheckfor
  //update enemy rendering
  lc.setLed(0, enemy_x, enemy_y, 1);
  }//end collisionIf
  //
  delay(gameDelay);
}
  • NOTE: To use game with a different remote, follow tutorial at link on IRremote usage to find that remote's individual codes, then substitute for given codes

Part 3: Space Invaders

Project Proposal

Space Invaders is a well known arcade game from 1978, information on it can be found at this Wikipedia page: https://en.wikipedia.org/wiki/Space_Invaders the basic tenants of the game include rows of descending enemies who shoot at a player at the bottom of the screen. The player can move left and right to evade the enemies shots, with barriers to duck behind. The player attempts to shoot all the enemies without getting shot themselves and before the enemies can reach the bottom of the screen. This implementation involves yet again using the IRremote signal to move the player left and right, and the pushbutton to fire.

Helpful Links

Same as part 2

Libraries

Same as part 2

Working Notes

-The same as the snake game, linkedlists will be super important for this application, as it involves a lot of tracking, updating, removing, and dynamically resizing lists for enemy pixels, enemy and player shots, and the barriers - barriers can withstand 3 shots before being destroyed, can be destroyed by player as well - first big problem encountered was with the pushbutton. At first, I was doing the timing the same as the snake game, implementing a 1s delay at the end of the loop to make the game playable. This wasn't working for the firing because it would only check once per loop if the button was pressed at the exact moment it was checked, not if it had been pressed during the loop -overcame this by doing the delay more dynamically, entire loop runs as a for loop updating every 2ms to check if the button has been fired. I was then able to dynamically time the shooting for enemies and players by having the updateShot called at variable intervals - expensive to check all necessary lists in nested for loops each iteration, but I figure it can't be super expensive because at most each shooting list will have maybe 3 entries at a time, so I figured it wasn't terribly inefficient. - main issue in game was with timing everything correctly and having enemies fire with correct timing.

Schematics

Part3schematic.png