Difference between revisions of "Eleanor's Final Project"
(→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
Contents
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/
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
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.