#include <LiquidCrystal.h>
#include <LedControl.h>
#define BUZZER 9
// Game States
#define STATE_START_SCREEN_1 0
#define STATE_START_SCREEN_2 1
#define STATE_SINGLE_PLAYER 2
#define STATE_TWO_PLAYER 3
#define STATE_MODE 4
#define STATE_MODE_sel 5
#define JOY_X1 A1
#define JOY_Y1 A2
#define JOY_SW1 52
#define JOY_X2 A3
#define JOY_Y2 A4
#define JOY_SW2 53
#define UNUSED_ANALOG 0 // for random seed
#define ANOTHER_UNUSED_ANALOG 1
#define BLOCK_COUNT1 (sizeof(blocks1) / sizeof(blocks1[0]))
#define BLOCK_COUNT2 (sizeof(blocks2) / sizeof(blocks2[0]))
bool singleplayer=false;
bool twoplayers=false;
bool easy = false;
bool medium = true;
bool hard = false;
int blocks;
int rowsUpdated = 0;
int rowsUpdated2 = 0;
LedControl lc = LedControl(11, 10, 12, 1);
LedControl lc2 = LedControl(15, 16, 17, 1);
LiquidCrystal lcd = LiquidCrystal(2, 3, 4, 5, 6, 7);
int gameState;
int MODEState;
struct point {
int x;
int y;
};
int blocks1[][4][8] = {
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0}
},
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}
},
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 1, 2, 1, 0, 0, 0},
},
{
{0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 1, 2, 1, 0, 0, 0},
},
{
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
},
{
{0, 0, 0, 0, 1, 1, 0, 0},
{0, 0, 0, 1, 2, 0, 0, 0},
},
{
{0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 1, 2, 1, 0, 0, 0},
},
{
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 1, 0, 0},
},
};
int blocks2[][4][8] = {
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0}
},
{
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
},
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0}
},
{
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
},
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0}
},
{
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
},
{
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 2, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0}
},
{
{0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0},
},
};
void(* resetFunc) (void) = 0;
static int moving[12][8];
static int moving2[12][8];
static int stationary[8][8];
static int stationary2[8][8];
static unsigned long lastUpdate;
static unsigned long lastUpdate2;
static unsigned long updateInterval = 1000;
static unsigned long lastInput;
static unsigned long lastInput2;
static const unsigned long inputDelay = 120;
static int score;
static int score2;
static int level = 1;
static int level2 = 1;
void setup() {
// put your setup code here, to run once:
lc.shutdown(0, false);
lc.setIntensity(0, 1);
lc2.shutdown(0, false);
lc2.setIntensity(0, 1);
pinMode(UNUSED_ANALOG, INPUT);
pinMode(JOY_X1, INPUT_PULLUP);
pinMode(JOY_Y1, INPUT_PULLUP);
pinMode(JOY_SW1, INPUT_PULLUP);
pinMode(JOY_X2, INPUT_PULLUP);
pinMode(JOY_Y2, INPUT_PULLUP);
pinMode(JOY_SW2, INPUT_PULLUP);
randomSeed(analogRead(UNUSED_ANALOG));
queueNewBlock();
randomSeed(analogRead(ANOTHER_UNUSED_ANALOG));
queueNewBlock2();
lcd.begin(16, 2);
Serial.begin(9600);
gameState=STATE_START_SCREEN_1;
}
void loop() {
switch (gameState) {
case STATE_START_SCREEN_1:
lcd.setCursor(1, 0);
lcd.print("single Player");
lcd.setCursor(12, 1);
lcd.print("next");
if(digitalRead(JOY_SW1) == LOW){
singleplayer = true;
twoplayers = false;
gameState = STATE_SINGLE_PLAYER;
}
else if(analogRead(JOY_X1) > 850){
delay(200);
gameState = STATE_START_SCREEN_2;
}
break;
case STATE_START_SCREEN_2:
lcd.setCursor(1, 0);
lcd.print("double Players");
lcd.setCursor(12, 1);
lcd.print("next");
lcd.setCursor(0, 1);
lcd.print("back");
if(digitalRead(JOY_SW1) == LOW){
singleplayer = false;
twoplayers = true;
gameState = STATE_TWO_PLAYER;
}
else if(analogRead(JOY_X1) < 150){
lcd.clear();
gameState = STATE_START_SCREEN_1;
}
else if(analogRead(JOY_X1) > 850){
lcd.clear();
gameState = STATE_MODE;
}
break;
case STATE_SINGLE_PLAYER:
doplayerone();
if(easy){
if(score > 10000){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("are you orphan?");
delay(2000);
lcd.setCursor(0, 0);
lcd.print("play harder OK?");
delay(2000);
resetFunc();
}
}
break;
case STATE_TWO_PLAYER:
doplayertwo();
doplayerone();
break;
case STATE_MODE:
lcd.setCursor(6, 0);
lcd.print("MODE");
lcd.setCursor(0, 1);
lcd.print("back");
if(analogRead(JOY_X1) < 150) {
delay(200);
gameState = STATE_START_SCREEN_2;
}
else if(digitalRead(JOY_SW1) == LOW){
delay(200);
lcd.clear();
gameState = STATE_MODE_sel;
}
break;
case STATE_MODE_sel:
if(easy == true){
lcd.setCursor(0, 0);
lcd.print("easy");
lcd.setCursor(5, 1);
lcd.print("medium");
lcd.setCursor(12, 1);
lcd.print("hard");
if(analogRead(JOY_X1) > 850){
delay(200);
lcd.clear();
easy = false;
medium = true;
hard = false;
}
if (digitalRead(JOY_SW1) == LOW){
delay(200);
lcd.clear();
gameState = STATE_START_SCREEN_1;
}
}
else if(medium == true){
lcd.setCursor(0, 1);
lcd.print("easy");
lcd.setCursor(5, 0);
lcd.print("medium");
lcd.setCursor(12, 1);
lcd.print("hard");
if(analogRead(JOY_X1) > 850){
delay(200);
lcd.clear();
easy = false;
medium = false;
hard = true;
}
if(analogRead(JOY_X1) < 150){
delay(200);
lcd.clear();
easy = true;
medium = false;
hard = false;
}
if (digitalRead(JOY_SW1) == LOW){
delay(200);
lcd.clear();
gameState = STATE_START_SCREEN_1;
}
}
else if(hard == true){
lcd.setCursor(0, 1);
lcd.print("easy");
lcd.setCursor(5, 1);
lcd.print("medium");
lcd.setCursor(12, 0);
lcd.print("hard");
if(analogRead(JOY_X1) < 150){
delay(200);
lcd.clear();
easy = false;
medium = true;
hard = false;
}
if (digitalRead(JOY_SW1) == LOW){
delay(200);
lcd.clear();
gameState = STATE_START_SCREEN_1;
}
}
break;
}
if(hard){
updateInterval =500;
}
else{
updateInterval =1000;
}
}
void doplayerone(){
if (isGameOver()) {
gameoverscreen();
}
unsigned long currentMillis = millis();
if (currentMillis - lastInput > inputDelay) {
handleInput();
lastInput = currentMillis;
}
if (currentMillis - lastUpdate > updateInterval) {
updateState();
renderLcd();
lastUpdate = currentMillis;
}
render();
}
void doplayertwo(){
if (isGameOver()) {
gameoverscreen();
}
unsigned long currentMillis2 = millis();
if (currentMillis2 - lastInput2 > inputDelay) {
handleInput2();
lastInput2 = currentMillis2;
}
if (currentMillis2 - lastUpdate2 > updateInterval) {
updateState2();
renderLcd();
lastUpdate2 = currentMillis2;
}
render2();
}
void gameoverscreen(){
if(singleplayer == true){
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Game Over!");
lcd.setCursor(2, 1);
lcd.print("Score: ");
lcd.print(score);
for (int t = 0; t < 3; t++) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
lc.setLed(0, i, j, true);
}
}
delay(500);
render();
delay(500);
}
delay(3000);
resetFunc(); }
if(twoplayers == true){
if(score > score2){
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Player1 win!");
for (int t = 0; t < 3; t++) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
lc.setLed(0, i, j, true);
}
}
delay(500);
render();
render2();
delay(500);
}
delay(3000);
resetFunc();
}
if(score < score2){
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Player2 win!");
for (int t = 0; t < 3; t++) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
lc2.setLed(0, i, j, true);
}
}
delay(500);
render();
render2();
delay(500);
}
delay(3000);
resetFunc();
}
else
lcd.clear();
lcd.setCursor(6, 0);
lcd.print("tie!");
for (int t = 0; t < 3; t++) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
lc.setLed(0, i, j, true);
lc2.setLed(0, i, j, true);
}
}
delay(500);
render();
render2();
delay(500);
}
delay(3000);
resetFunc();
}
}
void handleInput() {
Serial.print("Switch: ");
Serial.println(digitalRead(JOY_SW1));
Serial.print("X: ");
Serial.println(analogRead(JOY_X1));
Serial.print("Y: ");
Serial.println(analogRead(JOY_Y1));
if ( analogRead(JOY_X1) > 850) {
transformMoving(-1, 0);
} else if ( analogRead(JOY_X1) < 150) {
transformMoving(1, 0);
} else if ( analogRead(JOY_Y1) < 150) {
rotate90();
} else if ( analogRead(JOY_Y1) > 850) {
lastUpdate -= updateInterval;
}
}
void handleInput2() {
Serial.print("Switch: ");
Serial.println(digitalRead(JOY_SW2));
Serial.print("X: ");
Serial.println(analogRead(JOY_X2));
Serial.print("Y: ");
Serial.println(analogRead(JOY_Y2));
if ( analogRead(JOY_X2) > 850) {
transformMoving2(-1, 0);
} else if ( analogRead(JOY_X2) < 150) {
transformMoving2(1, 0);
} else if ( analogRead(JOY_Y2) < 150) {
rotate90_2();
} else if ( analogRead(JOY_Y2) > 850) {
lastUpdate2 -= updateInterval;
}
}
void updateState() {
if (isMovingAtBottom()) {
handleAtBottom();
queueNewBlock();
}
transformMoving(0, -1);
updateRows();
}
void updateState2() {
if (isMovingAtBottom2()) {
handleAtBottom2();
queueNewBlock2();
}
transformMoving2(0, -1);
updateRows2();
}
void render() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
lc.setLed(0, i, j, moving[i + 4][j] | stationary[i][j]);
}
}
}
void render2() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
lc2.setLed(0, i, j, moving2[i + 4][j] | stationary2[i][j]);
}
}
}
void renderLcd() {
if(singleplayer == true){
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Level: ");
lcd.print(level);
lcd.setCursor(2, 1);
lcd.print("Score: ");
lcd.print(score);
}
if( twoplayers == true){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("level:");
lcd.print(level);
lcd.setCursor(0, 1);
lcd.print("P1: ");
lcd.print(score);
lcd.setCursor(8, 0);
lcd.print("level:");
lcd.print(level2);
lcd.setCursor(8, 1);
lcd.print("P2: ");
lcd.print(score2);
}
}
void updateRows() {
for (int i = 0; i < 8; i++) {
int validRow = 1;
for (int j = 0; j < 8; j++) {
if (!stationary[i][j]) {
validRow = 0;
}
}
if (validRow) {
tone(BUZZER, 1000);
delay(50);
noTone(BUZZER);
rowsUpdated++;
if(rowsUpdated > 0 && rowsUpdated % 2 ==0){
level++;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
stationary2[i][j] = stationary2[i + 1][j];
}
}
for (int j = 0; j < 8; j++) {
stationary2[0][j] = 0;
}
int randomIndex = random(0, 8);
for (int j = 0; j < 8; j++) {
if (j == randomIndex) {
stationary2[7][j] = 0;
} else {
stationary2[7][j] = random(1, 3);
}
}
}
score = score + level*40 ;
for (int j = 0; j < 8; j++) {
stationary[i][j] = 0;
}
int updated[12][8];
for (int k = i; k >= 0; k--) {
for (int l = 0; l < 8; l++) {
if (k - 1 >= 0) {
updated[k][l] = stationary[k - 1][l];
}
}
}
for (int k = i + 1; k < 12; k++) {
for (int l = 0; l < 8; l++) {
updated[k][l] = stationary[k][l];
}
}
for (int k = 0; k < 8; k++) {
for (int l = 0; l < 8; l++) {
stationary[k][l] = updated[k][l];
}
}
}
}
}
void updateRows2() {
for (int i = 0; i < 8; i++) {
int validRow = 1;
for (int j = 0; j < 8; j++) {
if (!stationary2[i][j]) {
validRow = 0;
}
}
if (validRow) {
tone(BUZZER, 500);
delay(50);
noTone(BUZZER);
rowsUpdated2++;
if(rowsUpdated2 > 0 && rowsUpdated2 % 2 ==0){
level2++;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
stationary[i][j] = stationary[i + 1][j];
}
}
for (int j = 0; j < 8; j++) {
stationary[0][j] = 0;
}
int randomIndex = random(0, 8);
for (int j = 0; j < 8; j++) {
if (j == randomIndex) {
stationary[7][j] = 0;
} else {
stationary[7][j] = random(1, 3);
}
}
}
score2 = score2 + level2*40 ;
for (int j = 0; j < 8; j++) {
stationary2[i][j] = 0;
}
int updated[12][8];
for (int k = i; k >= 0; k--) {
for (int l = 0; l < 8; l++) {
if (k - 1 >= 0) {
updated[k][l] = stationary2[k - 1][l];
}
}
}
for (int k = i + 1; k < 12; k++) {
for (int l = 0; l < 8; l++) {
updated[k][l] = stationary2[k][l];
}
}
for (int k = 0; k < 8; k++) {
for (int l = 0; l < 8; l++) {
stationary2[k][l] = updated[k][l];
}
}
}
}
}
void queueNewBlock() {
if (easy) {
int randomBlock = random(BLOCK_COUNT2 - 1);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
moving[i][j] = blocks2[randomBlock][i][j];
}
}
} else {
int randomBlock = random(BLOCK_COUNT1 - 1);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
moving[i][j] = blocks1[randomBlock][i][j];
}
}
}
}
void queueNewBlock2() {
if (easy) {
int randomBlock = random(BLOCK_COUNT2 - 1);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
moving2[i][j] = blocks2[randomBlock][i][j];
}
}
} else {
int randomBlock = random(BLOCK_COUNT1 - 1);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
moving2[i][j] = blocks1[randomBlock][i][j];
}
}
}
}
int isMovingAtBottom() {
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
if (moving[i][j] && i == 11 || (moving[i][j] && i >= 4 && i + 1 < 12 && stationary[i - 4 + 1][j])) {
return 1;
}
}
}
return 0;
}
int isMovingAtBottom2() {
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
if (moving2[i][j] && i == 11 || (moving2[i][j] && i >= 4 && i + 1 < 12 && stationary2[i - 4 + 1][j])) {
return 1;
}
}
}
return 0;
}
int isGameOver() {
for (int j = 0; j < 8; j++) {
if (stationary[0][j]||stationary2[0][j]) {
return 1;
}
}
return 0;
}
void handleAtBottom() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
stationary[i][j] = stationary[i][j] || moving[i + 4][j];
moving[i + 4][j] = 0;
}
}
}
void handleAtBottom2() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
stationary2[i][j] = stationary2[i][j] || moving2[i + 4][j];
moving2[i + 4][j] = 0;
}
}
}
void rotate90() {
struct point pivot = { -1, -1};
int updatedArr[12][8];
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
updatedArr[i][j] = 0;
if (moving[i][j] == 2) {
pivot.y = i;
pivot.x = j;
}
}
}
if (pivot.y == -1 || pivot.x == -1) {
return;
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
if (moving[i][j] == 1) {
struct point toCoordinate = {j - pivot.x, i - pivot.y};
struct point transformedCoordinate = { -toCoordinate.y, toCoordinate.x};
struct point transformedGrid = {transformedCoordinate.x + pivot.x, transformedCoordinate.y + pivot.y};
if (transformedGrid.x >= 8 || transformedGrid.x < 0 || transformedGrid.y >= 12 || transformedGrid.y < 0 || (transformedGrid.y >= 4 && stationary[transformedGrid.y - 4][transformedGrid.x])) {
return;
}
updatedArr[transformedGrid.y][transformedGrid.x] = 1;
} else if (moving[i][j] == 2) {
updatedArr[i][j] = 2;
}
}
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
moving[i][j] = updatedArr[i][j];
}
}
}
void rotate90_2() {
struct point pivot = { -1, -1};
int updatedArr[12][8];
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
updatedArr[i][j] = 0;
if (moving2[i][j] == 2) {
pivot.y = i;
pivot.x = j;
}
}
}
if (pivot.y == -1 || pivot.x == -1) {
return;
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
if (moving2[i][j] == 1) {
struct point toCoordinate = {j - pivot.x, i - pivot.y};
struct point transformedCoordinate = { -toCoordinate.y, toCoordinate.x};
struct point transformedGrid = {transformedCoordinate.x + pivot.x, transformedCoordinate.y + pivot.y};
if (transformedGrid.x >= 8 || transformedGrid.x < 0 || transformedGrid.y >= 12 || transformedGrid.y < 0 || (transformedGrid.y >= 4 && stationary2[transformedGrid.y - 4][transformedGrid.x])) {
return;
}
updatedArr[transformedGrid.y][transformedGrid.x] = 1;
} else if (moving2[i][j] == 2) {
updatedArr[i][j] = 2;
}
}
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
moving2[i][j] = updatedArr[i][j];
}
}
}
void transformMoving2(int x, int y) {
y = -y;
int updatedArr[12][8];
int xValid = 1;
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
updatedArr[i][j] = 0;
}
}
for (int i = 0; i < 12 && xValid; i++) {
for (int j = 0; j < 8 && xValid; j++) {
if (moving2[i][j]) {
if (j + x >= 8 || j + x < 0) {
xValid = 0;
}
}
}
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
if (moving2[i][j]) {
if (i + y < 12 && i + y >= 0) {
if (xValid) {
if (i >= 4 && stationary2[i - 4 + y][j + x]) {
return;
}
updatedArr[i + y][j + x] = moving2[i][j];
} else {
updatedArr[i + y][j] = moving2[i][j];
}
}
}
}
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
moving2[i][j] = updatedArr[i][j];
}
}
}
void transformMoving(int x, int y) {
y = -y;
int updatedArr[12][8];
int xValid = 1;
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
updatedArr[i][j] = 0;
}
}
for (int i = 0; i < 12 && xValid; i++) {
for (int j = 0; j < 8 && xValid; j++) {
if (moving[i][j]) {
if (j + x >= 8 || j + x < 0) {
xValid = 0;
}
}
}
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
if (moving[i][j]) {
if (i + y < 12 && i + y >= 0) {
if (xValid) {
if (i >= 4 && stationary[i - 4 + y][j + x]) {
return;
}
updatedArr[i + y][j + x] = moving[i][j];
} else {
updatedArr[i + y][j] = moving[i][j];
}
}
}
}
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 8; j++) {
moving[i][j] = updatedArr[i][j];
}
}
}