Created
July 22, 2019 23:03
-
-
Save terryspitz/7c1d6ae7569145d276aba5a740233268 to your computer and use it in GitHub Desktop.
Clone https://github.com/MarginallyClever/ArduinoStarterKit/blob/master/LED8x8tetris/LED8x8tetris/LED8x8tetris.ino for ESP8266 and 8x8 full color LED matrix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//------------------------------------------------------------------------------ | |
// 8x8 single color LED Tetris for Arduino UNO and a joystick breakout. | |
// [email protected] 2015-01-22 | |
//------------------------------------------------------------------------------ | |
// Copyright at end of file. | |
// please see http://www.github.com/MarginallyClever/ArduinoStarterKit for more information. | |
// | |
// The LED grid is a red 8x8. | |
// The Arduino is an UNO. | |
// The joystick is a xinda ps3/xbox style clickable stick on a breakout board. | |
// | |
// Place the grid so the lights are facing you and the nub on one edge is on the | |
// bottom. On the back should be two horizontal rows of pins. the top row, from | |
// left to right, connects to arduino digital pins 9-2. The bottom row of pins, | |
// from left to right, connect to digital pins 13-10 and analog pins 2-5. | |
// | |
// The joystick has 5 pins. joystick 5v and GND go to the same pins on arduino. | |
// VRx goes to A0. VRy goes to A1. SW goes to arduino digital pin 1. | |
// | |
// While the SW pin is connected you will not be able to use Serial.*, because | |
// Serial *also* uses pins 0 and 1. | |
//-------------------------------------------------------------------------------- | |
// CONSTANTS | |
//-------------------------------------------------------------------------------- | |
// size of the LED grid | |
#define GRID_W (8) | |
#define GRID_H (8) | |
// max size of each tetris piece | |
#define PIECE_W (4) | |
#define PIECE_H (4) | |
#define JOYSTICK_DEAD_ZONE (20) | |
#define JOYSTICK_BUTTON (1) | |
#define JOYSTICK_X (0) | |
#define JOYSTICK_Y (1) | |
#include <RGBLEDMatrix.h> | |
#include <RGBColor.h> | |
RGBLEDMatrix leds(8,8, RGBLEDMatrix::RGB_GROUPS, LOW, HIGH); | |
// 1 color drawings of each piece in each rotation. | |
// Each piece is max 4 wide, 4 tall, and 4 rotations. | |
const char piece_I[] = { | |
0,0,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,1,1, | |
0,1,0,0, | |
0,1,0,0, | |
0,1,0,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,1,1, | |
0,1,0,0, | |
0,1,0,0, | |
0,1,0,0, | |
0,1,0,0, | |
}; | |
const char piece_L1[] = { | |
0,1,0,0, | |
0,1,0,0, | |
0,1,1,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,1,0, | |
1,0,0,0, | |
0,0,0,0, | |
1,1,0,0, | |
0,1,0,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,0,1,0, | |
1,1,1,0, | |
0,0,0,0, | |
0,0,0,0, | |
}; | |
const char piece_L2[] = { | |
0,1,0,0, | |
0,1,0,0, | |
1,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,0,0,0, | |
1,1,1,0, | |
0,0,0,0, | |
0,1,1,0, | |
0,1,0,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,1,0, | |
0,0,1,0, | |
0,0,0,0, | |
}; | |
const char piece_T[] = { | |
0,0,0,0, | |
1,1,1,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,1,0,0, | |
1,1,0,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
0,1,0,0, | |
1,1,1,0, | |
0,0,0,0, | |
0,1,0,0, | |
0,1,1,0, | |
0,1,0,0, | |
0,0,0,0, | |
}; | |
const char piece_S1[] = { | |
1,0,0,0, | |
1,1,0,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
0,1,1,0, | |
1,1,0,0, | |
0,0,0,0, | |
1,0,0,0, | |
1,1,0,0, | |
0,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
0,1,1,0, | |
1,1,0,0, | |
0,0,0,0, | |
}; | |
const char piece_S2[] = { | |
0,1,0,0, | |
1,1,0,0, | |
1,0,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,0,0, | |
0,1,1,0, | |
0,0,0,0, | |
0,1,0,0, | |
1,1,0,0, | |
1,0,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,0,0, | |
0,1,1,0, | |
0,0,0,0, | |
}; | |
const char piece_O[] = { | |
1,1,0,0, | |
1,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,0,0, | |
1,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,0,0, | |
1,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
1,1,0,0, | |
1,1,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
}; | |
// how many kinds of pieces | |
#define NUM_PIECE_TYPES (7) | |
// An array of pointers! | |
const char *pieces[NUM_PIECE_TYPES] = { | |
piece_S1, | |
piece_S2, | |
piece_L1, | |
piece_L2, | |
piece_O, | |
piece_T, | |
piece_I, | |
}; | |
//-------------------------------------------------------------------------------- | |
// GLOBALS | |
//-------------------------------------------------------------------------------- | |
// this is how arduino remembers what the button was doing in the past, | |
// so arduino can tell when it changes. | |
int old_button=0; | |
// so arduino can tell when user moves sideways | |
int old_px = 0; | |
// so arduino can tell when user tries to turn | |
int old_i_want_to_turn=0; | |
// this is how arduino remembers the falling piece. | |
int piece_id; | |
int piece_rotation; | |
int piece_x; | |
int piece_y; | |
// order of pieces coming up | |
char piece_sequence[NUM_PIECE_TYPES]; | |
char sequence_i=NUM_PIECE_TYPES; | |
// this controls how fast the player can move. | |
long last_move; | |
long move_delay=200; //ms | |
// this controls when the piece automatically falls. | |
long last_drop; | |
long drop_delay=500; // 500ms = 2 times a second | |
// this is how arduino remembers where pieces are on the grid. | |
char grid[8*8]; | |
//-------------------------------------------------------------------------------- | |
// METHODS | |
//-------------------------------------------------------------------------------- | |
// grid contains the arduino's memory of the game board, including the piece that is falling. | |
void draw_grid() { | |
int x, y; | |
for(y=0;y<GRID_H;++y) { | |
for(x=0;x<GRID_W;++x) { | |
RGBColorType color = RGBColor::fromRGB(255*grid[y*GRID_W+x], 0, 0); | |
leds.writePixel(7-y, x, color); | |
} | |
} | |
} | |
void choose_new_piece() { | |
if( sequence_i >= NUM_PIECE_TYPES ) { | |
// list exhausted | |
int i,j, k; | |
for(i=0;i<NUM_PIECE_TYPES;++i) { | |
do { | |
// pick a random piece | |
j = rand() % NUM_PIECE_TYPES; | |
// make sure it isn't already in the sequence. | |
for(k=0;k<i;++k) { | |
if(piece_sequence[k]==j) break; // already in sequence | |
} | |
} while(k<i); | |
// not in sequence. Add it. | |
piece_sequence[i] = j; | |
} | |
// rewind sequence counter | |
sequence_i=0; | |
} | |
// get the next piece in the sequence. | |
piece_id = piece_sequence[sequence_i++]; | |
// always start the piece top center. | |
piece_y=-4; // -4 squares off the top of the screen. | |
piece_x=3; | |
// always start in the same orientation. | |
piece_rotation=0; | |
} | |
void erase_piece_from_grid() { | |
int x, y; | |
const char *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | |
for(y=0;y<PIECE_H;++y) { | |
for(x=0;x<PIECE_W;++x) { | |
int nx=piece_x+x; | |
int ny=piece_y+y; | |
if(ny<0 || ny>GRID_H) continue; | |
if(nx<0 || nx>GRID_W) continue; | |
if(piece[y*PIECE_W+x]==1) { | |
grid[ny*GRID_W+nx]=0; // zero erases the grid location. | |
} | |
} | |
} | |
} | |
void add_piece_to_grid() { | |
int x, y; | |
const char *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | |
for(y=0;y<PIECE_H;++y) { | |
for(x=0;x<PIECE_W;++x) { | |
int nx=piece_x+x; | |
int ny=piece_y+y; | |
if(ny<0 || ny>GRID_H) continue; | |
if(nx<0 || nx>GRID_W) continue; | |
if(piece[y*PIECE_W+x]==1) { | |
grid[ny*GRID_W+nx]=1; // zero erases the grid location. | |
} | |
} | |
} | |
} | |
// Move everything down 1 space, destroying the old row number y in the process. | |
void delete_row(int y) { | |
int x; | |
for(;y>0;--y) { | |
for(x=0;x<GRID_W;++x) { | |
grid[y*GRID_W+x] = grid[(y-1)*GRID_W+x]; | |
} | |
} | |
// everything moved down 1, so the top row must be empty or the game would be over. | |
for(x=0;x<GRID_W;++x) { | |
grid[x]=0; | |
} | |
} | |
void remove_full_rows() { | |
int x, y, c; | |
for(y=0;y<GRID_H;++y) { | |
// count the full spaces in this row | |
c = 0; | |
for(x=0;x<GRID_W;++x) { | |
if( grid[y*GRID_W+x] > 0 ) c++; | |
} | |
if(c==GRID_W) { | |
// row full! | |
delete_row(y); | |
} | |
} | |
} | |
void try_to_move_piece_sideways() { | |
// what does the joystick angle say | |
int dx = map(analogRead(0),0,1023,500,-500); | |
int new_px = 0; | |
// is the joystick really being pushed? | |
if(dx> JOYSTICK_DEAD_ZONE) { | |
new_px=1; | |
} | |
if(dx<-JOYSTICK_DEAD_ZONE) { | |
new_px=-1; | |
} | |
if(new_px!=old_px && piece_can_fit(piece_x+new_px,piece_y,piece_rotation)==1) { | |
piece_x+=new_px; | |
} | |
old_px = new_px; | |
} | |
void try_to_rotate_piece() { | |
int i_want_to_turn=0; | |
// what does the joystick button say | |
int new_button = digitalRead(1); | |
// if the button state has just changed AND it is being let go, | |
if( new_button > 0 && old_button != new_button ) { | |
i_want_to_turn=1; | |
} | |
old_button=new_button; | |
// up on joystick to rotate | |
int dy = map(analogRead(1),0,1023,-500,500); | |
if(dy<-JOYSTICK_DEAD_ZONE) i_want_to_turn=1; | |
if(i_want_to_turn==1 && i_want_to_turn != old_i_want_to_turn) { | |
// figure out what it will look like at that new angle | |
int new_pr = ( piece_rotation + 1 ) % 4; | |
// if it can fit at that new angle (doesn't bump anything) | |
if(piece_can_fit(piece_x,piece_y,new_pr)) { | |
// then make the turn. | |
piece_rotation = new_pr; | |
} | |
} | |
old_i_want_to_turn = i_want_to_turn; | |
} | |
// can the piece fit in this new location? | |
int piece_can_fit(int px,int py,int pr) { | |
if( piece_off_edge(px,py,pr) ) return 0; | |
if( piece_hits_rubble(px,py,pr) ) return 0; | |
return 1; | |
} | |
int piece_off_edge(int px,int py,int pr) { | |
int x,y; | |
const char *piece = pieces[piece_id] + (pr * PIECE_H * PIECE_W); | |
for(y=0;y<PIECE_H;++y) { | |
for(x=0;x<PIECE_W;++x) { | |
int nx=px+x; | |
int ny=py+y; | |
if(ny<0) continue; // off top, don't care | |
if(piece[y*PIECE_W+x]>0) { | |
if(nx<0) return 1; // yes: off left side | |
if(nx>=GRID_W ) return 1; // yes: off right side | |
} | |
} | |
} | |
return 0; // inside limits | |
} | |
int piece_hits_rubble(int px,int py,int pr) { | |
int x,y; | |
const char *piece = pieces[piece_id] + (pr * PIECE_H * PIECE_W); | |
for(y=0;y<PIECE_H;++y) { | |
int ny=py+y; | |
if(ny<0) continue; | |
for(x=0;x<PIECE_W;++x) { | |
int nx=px+x; | |
if(piece[y*PIECE_W+x]>0) { | |
if(ny>=GRID_H ) return 1; // yes: goes off bottom of grid | |
if(grid[ny*GRID_W+nx]==1 ) return 1; // yes: grid already full in this space | |
} | |
} | |
} | |
return 0; // doesn't hit | |
} | |
void game_over() { | |
int x,y; | |
while(1) { | |
// Your homework: add a 'game over' animation here, then film it and tweet it to @marginallyc. | |
leds.fillScreen(DARK_BLUE_COLOR); | |
// click the button? | |
if(digitalRead(1)==0) { | |
// restart! | |
setup(); | |
return; | |
} | |
} | |
} | |
void try_to_drop_piece() { | |
erase_piece_from_grid(); | |
if(piece_can_fit(piece_x,piece_y+1,piece_rotation)) { | |
piece_y++; // move piece down | |
add_piece_to_grid(); | |
} else { | |
// hit something! | |
// put it back | |
add_piece_to_grid(); | |
remove_full_rows(); | |
if(game_is_over()==1) { | |
game_over(); | |
} | |
// game isn't over, choose a new piece | |
choose_new_piece(); | |
} | |
} | |
void try_to_drop_faster() { | |
int y = map(analogRead(1),0,1023,-500,500); | |
if(y>JOYSTICK_DEAD_ZONE) { | |
// player is holding joystick down, drop a little faster. | |
try_to_drop_piece(); | |
} | |
} | |
void react_to_player() { | |
erase_piece_from_grid(); | |
try_to_move_piece_sideways(); | |
try_to_rotate_piece(); | |
add_piece_to_grid(); | |
try_to_drop_faster(); | |
} | |
// can the piece fit in this new location | |
int game_is_over() { | |
int x,y; | |
const char *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | |
for(y=0;y<PIECE_H;++y) { | |
for(x=0;x<PIECE_W;++x) { | |
int ny=piece_y+y; | |
int nx=piece_x+x; | |
if(piece[y*PIECE_W+x]>0) { | |
if(ny<0) return 1; // yes: off the top! | |
} | |
} | |
} | |
return 0; // not over yet... | |
} | |
// called once when arduino reboots | |
void setup() { | |
int i; | |
leds.setup(); | |
leds.startScanning(); | |
for(int rgb=0; rgb<16; ++rgb) { | |
leds.fillScreen(RGBColorType(1)<<rgb); | |
leds.loop(); | |
delay(100); | |
} | |
// set up joystick button | |
pinMode(1,INPUT); | |
digitalWrite(1,HIGH); | |
// make sure arduino knows the grid is empty. | |
for(i=0;i<GRID_W*GRID_H;++i) { | |
grid[i]=0; | |
} | |
// make the game a bit more random - pull a number from space and use it to 'seed' a crop of random numbers. | |
randomSeed(analogRead(D0)); | |
// get ready to start the game. | |
choose_new_piece(); | |
// start the game clock after everything else is ready. | |
last_move = millis(); | |
last_drop = last_move; | |
} | |
// called over and over after setup() | |
void loop() { | |
// the game plays at one speed, | |
if(millis() - last_move > move_delay ) { | |
last_move = millis(); | |
react_to_player(); | |
draw_grid(); | |
} | |
// ...and drops the falling block at a different speed. | |
if(millis() - last_drop > drop_delay ) { | |
last_drop = millis(); | |
try_to_drop_piece(); | |
draw_grid(); | |
} | |
// when it isn't doing those two things, it's redrawing the grid. | |
leds.loop(); | |
} | |
/** | |
* This file is part of ArduinoStarterKit. | |
* | |
* ArduinoStarterKit is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* ArduinoStarterKit is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with Arduino Timer Interrupt. If not, see <http://www.gnu.org/licenses/>. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment