Soviet Bloc Game (C version): Difference between revisions
(Created page with "While thinking about ways to make my TRS-80 model I more useable in 2019, I realized that I could not easily find a version of Tetris for it (probably because the TRS-80 came...") |
No edit summary |
||
Line 1: | Line 1: | ||
While thinking about ways to make my TRS-80 model I more useable in 2019, I realized that I could not easily find a version of Tetris for it (probably because the TRS-80 came out about 10ish years before Tetris was invented), so I thought I might try to write my own after watching an episode of ''the 8-bit Guy'' where | While thinking about ways to make my TRS-80 model I more useable in 2019, I realized that I could not easily find a version of Tetris for it (probably because the TRS-80 came out about 10ish years before Tetris was invented), so I thought I might try to write my own after watching an episode of ''the 8-bit Guy'' where David does the same. I used to play the Gameboy version of Tetris all the time on my Ti-nSpire calculator thanks to the nspire hacking scene and an emulator port, so that's the style of Tetris I will try to recreate. | ||
This ended up being a sort of 24 hour challenge, where I created a color version of Tetris with basic animations, scoring, and levels, just like the GameBoy version. | |||
==Theory of Operation== | |||
==The Code== | |||
<code> | |||
#define pfsizeX 10 | |||
#define pfsizeY 22 //note that top 4 lines are not drawn | |||
uint8_t playfield[pfsizeX][pfsizeY]; //the game area | |||
uint8_t pieceC[4][4]; //piece container | |||
int8_t pieceCX; //location of upper left corner of piece container | |||
int8_t pieceCY; | |||
uint8_t pieceT; //type of piece | |||
uint8_t pieceR; //rotation of piece | |||
uint8_t nextpieceT; | |||
uint8_t nextpieceR; | |||
uint8_t dcontrol; //locks in control every frame | |||
uint8_t rcontrol; | |||
uint8_t lastdcontrol=0; //for "debouncing" of inputs | |||
uint8_t lastrcontrol=0; | |||
uint8_t drepeatframe=0; | |||
#define drepeatframes 3 //wait _ frames before repeatedly going in one direction | |||
uint8_t dropframe=0; //counter for number of frames between block drops | |||
uint8_t level=0; //LEVEL decreases frame drop from 20 frames to 0 frames (levels 0 to 20) | |||
boolean ngame=0; //when set, starts new game | |||
uint16_t lines=0; //NUMBER OF LINES CLEARED | |||
uint32_t score=0; //TOTAL SCORE (using NES rules) | |||
uint8_t fdrop; //number of blocks that piece has been fast dropped | |||
uint8_t lslvi=0; //lines since level increase (when this gets to 10, increase the level) | |||
#include <U8g2lib.h> | |||
U8G2_ST7920_128X64_1_HW_SPI u8g2(U8G2_R0, /* CS=*/ 12, /* reset=*/ 8); | |||
void dispscreen(){ | |||
u8g2.firstPage(); | |||
do { | |||
for(uint8_t j=4; j<pfsizeY; j++){ //draw screen | |||
for(uint8_t i=0; i<pfsizeX; i++){ | |||
if(playfield[i][j]!=0){ | |||
u8g2.drawBox(5*(j-4),5*(pfsizeX-1)-5*i,6,6); | |||
}else{ | |||
u8g2.drawFrame(5*(j-4),5*(pfsizeX-1)-5*i,6,6); | |||
//if(j==3){u8g2.drawLine(5*j,5*(pfsizeX-1)-5*i,5*j+5,5*(pfsizeX-1)-5*i+5);} | |||
} | |||
}} | |||
uint8_t temppieceT=pieceT; | |||
uint8_t temppieceR=pieceR; | |||
pieceT=nextpieceT; | |||
pieceR=nextpieceR; | |||
loadpiece(); | |||
for(uint8_t j=0; j<4; j++){ //draw next piece | |||
for(uint8_t i=0; i<4; i++){ | |||
if(pieceC[i][j]!=0){ | |||
u8g2.drawBox(5*(j+pfsizeY-3),5*(pfsizeX-1)-5*i,6,6); | |||
}else{ | |||
u8g2.drawFrame(5*(j+pfsizeY-3),5*(pfsizeX-1)-5*i,6,6); | |||
} | |||
}} | |||
pieceT=temppieceT; | |||
pieceR=temppieceR; | |||
loadpiece(); | |||
u8g2.setFont(u8g2_font_6x10_tf); | |||
char zbuffer[32]; | |||
sprintf(zbuffer, "N%d", lines); | |||
u8g2.drawStr(0,60, zbuffer); | |||
sprintf(zbuffer, "L%d", level); | |||
u8g2.drawStr(25,60, zbuffer); | |||
sprintf(zbuffer, "S%d", score); | |||
u8g2.drawStr(50,60, zbuffer); | |||
} while( u8g2.nextPage() ); | |||
} | |||
void controls(){ | |||
if(digitalRead(A3)==0){ngame=1;} | |||
if(dcontrol==0){ | |||
if(analogRead(A7)<10){dcontrol=4;} | |||
if(analogRead(A7)>1014){dcontrol=6;} | |||
if(analogRead(A6)>1014){dcontrol=8;} | |||
if(analogRead(A6)<10){dcontrol=2;} | |||
if(dcontrol==0){drepeatframe=0;} | |||
if(dcontrol==lastdcontrol){ //short delay before fast motion | |||
if(drepeatframe<=drepeatframes){ | |||
drepeatframe++; | |||
dcontrol=3; //lockout if within lockout period | |||
} | |||
} | |||
} | |||
if(rcontrol==0){ | |||
if(digitalRead(A4)==0){rcontrol=1;} | |||
if(digitalRead(A5)==0){rcontrol=2;} | |||
if(rcontrol==lastrcontrol){rcontrol=3;} | |||
} | |||
} | |||
void clearControls(){ | |||
if(rcontrol!=3){lastrcontrol=rcontrol;} | |||
if(dcontrol!=3){lastdcontrol=dcontrol;} | |||
rcontrol=0; | |||
dcontrol=0; | |||
} | |||
void draw(){ | |||
loadpiece(); //load piece into the piece carrier | |||
for(uint8_t i=0; i<pfsizeX; i++){ //clear any cells with active piece parts (will be written again with new pieceC | |||
for(uint8_t j=0; j<pfsizeY; j++){ | |||
if(playfield[i][j]<=7){playfield[i][j]=0;} | |||
} | |||
} | |||
for(uint8_t i=0; i<4; i++){ //copy active piece onto the playfield | |||
for(uint8_t j=0; j<4; j++){ | |||
if(pieceCX+i>=0&&pieceCX+i<pfsizeX&&pieceCY+j>=0&&pieceCY+j<pfsizeY){//check if piece segment can be drawn on screen | |||
if(pieceC[i][j]!=0){playfield[i+pieceCX][j+pieceCY]=pieceC[i][j];} | |||
} | |||
} | |||
} | |||
} | |||
boolean checkCollide(){ //move the piece carrier first, then check if anything collides | |||
loadpiece(); //load piece into the piece carrier | |||
boolean nonvalidity=0; | |||
for(uint8_t i=0; i<4; i++){ //run through all piece carrier cells | |||
for(uint8_t j=0; j<4; j++){ | |||
if(pieceCX+i>=0&&pieceCX+i<pfsizeX&&pieceCY+j>=0&&pieceCY+j<pfsizeY){ //check if piece carrier segment can be drawn on screen | |||
if(pieceC[i][j]!=0&&playfield[i+pieceCX][j+pieceCY]>7){ //if both background and nonzero piece carrier segment collide | |||
nonvalidity=1; | |||
} | |||
}else{ //this segment of PC can't be drawn on the screen | |||
if(pieceC[i][j]!=0){ //a filled in segment would be drawn offscreen | |||
nonvalidity=1; | |||
} | |||
} | |||
} | |||
} | |||
return nonvalidity; | |||
} | |||
void piece2bg(){ | |||
for(uint8_t i=0; i<4; i++){ //copy active piece onto the screen | |||
for(uint8_t j=0; j<4; j++){ | |||
if(pieceC[i][j]!=0){playfield[i+pieceCX][j+pieceCY]=pieceC[i][j]+8;} //copy the piece into the playfield/background | |||
}} | |||
nextpiece(); | |||
} | |||
void nextpiece(){//generate the next piece and move PC back to the top | |||
pieceT=nextpieceT; | |||
pieceR=nextpieceR; | |||
nextpieceT = rand()%7 + 1; | |||
nextpieceR = rand()%4 + 1; | |||
pieceCY=0; //move piece carrier back to the top of screen | |||
pieceCX=3; | |||
} | |||
void newgamemon(){ | |||
if(ngame==0){return;} | |||
Serial.println(F("NEW GAME")); | |||
ngame=0; | |||
for(uint8_t i=0; i<pfsizeX; i++){ //clear any cells with active piece parts (will be written again with new pieceC | |||
for(uint8_t j=0; j<pfsizeY; j++){ | |||
playfield[i][j]=0; | |||
} | |||
} | |||
lines=0; | |||
level=0; | |||
score=0; | |||
lslvi=0; | |||
fdrop=0; | |||
nextpiece(); | |||
nextpiece(); | |||
} | |||
void loadpiece(){ //hardcoded all pieces | |||
switch(pieceT){ | |||
case 1: //long one | |||
switch(pieceR){ | |||
case 1: | |||
case 3: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=1; pieceC[1][2]=1; pieceC[2][2]=1; pieceC[3][2]=1; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 2: | |||
case 4: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=1; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=1; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=0; pieceC[2][2]=1; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=1; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
case 2: //backwards L | |||
switch(pieceR){ | |||
case 1: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=2; pieceC[1][2]=2; pieceC[2][2]=2; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=2; pieceC[3][3]=0; | |||
break; | |||
case 2: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=2; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=2; pieceC[2][2]=0; pieceC[3][2]=0; | |||
pieceC[0][3]=2; pieceC[1][3]=2; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 3: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=2; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=2; pieceC[1][2]=2; pieceC[2][2]=2; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 4: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=2; pieceC[2][1]=2; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=2; pieceC[2][2]=0; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=2; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
case 3: //L | |||
switch(pieceR){ | |||
case 1: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=3; pieceC[1][2]=3; pieceC[2][2]=3; pieceC[3][2]=0; | |||
pieceC[0][3]=3; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 2: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=3; pieceC[1][1]=3; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=3; pieceC[2][2]=0; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=3; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 3: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=3; pieceC[3][1]=0; | |||
pieceC[0][2]=3; pieceC[1][2]=3; pieceC[2][2]=3; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 4: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=3; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=3; pieceC[2][2]=0; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=3; pieceC[2][3]=3; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
case 4: //s shape | |||
switch(pieceR){ | |||
case 1: | |||
case 3: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=4; pieceC[2][2]=4; pieceC[3][2]=0; | |||
pieceC[0][3]=4; pieceC[1][3]=4; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 2: | |||
case 4: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=4; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=4; pieceC[2][2]=4; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=4; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
case 5: //T shape | |||
switch(pieceR){ | |||
case 1: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=5; pieceC[1][2]=5; pieceC[2][2]=5; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=5; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 2: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=5; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=5; pieceC[1][2]=5; pieceC[2][2]=0; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=5; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 3: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=5; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=5; pieceC[1][2]=5; pieceC[2][2]=5; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
case 4: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=5; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=5; pieceC[2][2]=5; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=5; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
case 6: //reverse s | |||
switch(pieceR){ | |||
case 1: | |||
case 3: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0; | |||
pieceC[0][2]=6; pieceC[1][2]=6; pieceC[2][2]=0; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=6; pieceC[2][3]=6; pieceC[3][3]=0; | |||
break; | |||
case 2: | |||
case 4: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=6; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=6; pieceC[2][2]=6; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=6; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
case 7: //square | |||
switch(pieceR){ | |||
default: | |||
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0; | |||
pieceC[0][1]=0; pieceC[1][1]=7; pieceC[2][1]=7; pieceC[3][1]=0; | |||
pieceC[0][2]=0; pieceC[1][2]=7; pieceC[2][2]=7; pieceC[3][2]=0; | |||
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0; | |||
break; | |||
} | |||
break; | |||
} | |||
} | |||
void setup(){ | |||
Serial.begin(115200); | |||
u8g2.begin(); | |||
pinMode(A4, INPUT_PULLUP); | |||
pinMode(A5, INPUT_PULLUP); | |||
pinMode(A3, INPUT_PULLUP); //new game button | |||
ngame=1; | |||
newgamemon(); | |||
} | |||
void loop() { | |||
for(uint32_t h=0;h<=5;h++){//frame delay //DELAY SECTION (BETWEEN FRAMES) | |||
delay(1); | |||
controls(); | |||
newgamemon(); | |||
} | |||
boolean droppiece=0; | |||
dropframe++; | |||
if(dropframe>=(20-level)){ | |||
dropframe=0; | |||
droppiece=1; | |||
} | |||
if(rcontrol==1){ | |||
pieceR++; if(pieceR>=5){pieceR=1;} //try to rotate piece | |||
if(checkCollide()){ | |||
pieceR--; if(pieceR<=0){pieceR=4;} //undo rotation | |||
} | |||
} | |||
if(rcontrol==2){ | |||
pieceR--; if(pieceR<=0){pieceR=4;} | |||
if(checkCollide()){ | |||
pieceR++; if(pieceR>=5){pieceR=1;} | |||
} | |||
} | |||
if(dcontrol==4){ | |||
pieceCX--; //try and see what happens if we move the piece left | |||
if(checkCollide()){//piece move is not valid | |||
pieceCX++; //take piece back | |||
} | |||
} | |||
if(dcontrol==6){ | |||
pieceCX++; //try and see what happens if we move the piece left | |||
if(checkCollide()){//piece move is not valid | |||
pieceCX--; //take piece back | |||
} | |||
} | |||
if(dcontrol==8){ | |||
pieceCY--; //try and see what happens if we move the piece up | |||
if(checkCollide()){//piece move is not valid | |||
pieceCY++; //take piece back | |||
} | |||
} | |||
if(dcontrol==2||droppiece==1){ | |||
pieceCY++; //try and see what happens if we move the piece down | |||
if(!(drepeatframe<=drepeatframes)){ //is in fast mode | |||
fdrop++; | |||
}else{ | |||
fdrop=0; | |||
} | |||
if(checkCollide()){//piece move is not valid | |||
pieceCY--; //take piece back | |||
score+=fdrop; //add # of fast dropped blocks to score | |||
fdrop=0; | |||
piece2bg(); //copy piece to background and reset to next piece | |||
} | |||
} | |||
draw(); | |||
dispscreen(); | |||
clearControls(); | |||
//check for line clears | |||
boolean clearline[pfsizeY]; | |||
uint8_t clearedlines=0; | |||
uint8_t templines=0; | |||
for(uint8_t j=4; j<pfsizeY; j++){ | |||
clearline[j]=1; //assume line is cleared | |||
for(uint8_t i=0; i<pfsizeX; i++){ | |||
if(playfield[i][j]<=7){clearline[j]=0;break;} //line is not full | |||
} | |||
clearedlines+=clearline[j]; | |||
templines+=clearline[j]; | |||
} | |||
if(clearedlines>0){//breifly animate the cleared lines, then clear them | |||
for(uint8_t f=0; f<=6; f++){ | |||
for(uint8_t j=4; j<pfsizeY; j++){ | |||
if(clearline[j]==1){ | |||
for(uint8_t i=0; i<pfsizeX; i++){ | |||
if(f%2==0){playfield[i][j]=8;}else{playfield[i][j]=0;} | |||
} | |||
} | |||
} | |||
draw(); | |||
dispscreen(); | |||
delay(200); | |||
} | |||
for(uint8_t j=4; j<pfsizeY; j++){ //accutally clear the lines | |||
if(clearline[j]==1){ | |||
for(uint8_t t=j; t>=4; t--){ | |||
for(uint8_t i=0; i<pfsizeX; i++){ | |||
playfield[i][t]=playfield[i][t-1]; | |||
} | |||
} | |||
} | |||
} | |||
lines+=templines; //add number of lines to lines counter | |||
switch(templines){ //calculate score | |||
case 1: | |||
score+=40*(level+1); | |||
break; | |||
case 2: | |||
score+=100*(level+1); | |||
break; | |||
case 3: | |||
score+=300*(level+1); | |||
break; | |||
default: | |||
score+=1200*(level+1); | |||
break; | |||
} | |||
for(uint8_t i=1; i<=templines; i++){ //see if the level needs increasing | |||
lslvi++; | |||
if(lslvi>=10){lslvi=0;level++;} | |||
if(level>=20){level=20;} | |||
} | |||
} | |||
//check gameover (scan through line 3 and see if there are any non-active pieces in it) | |||
for(uint8_t i=0; i<pfsizeX; i++){ | |||
if(playfield[i][3]>7){ | |||
//*********************GAME OVER******************** | |||
Serial.println(F("GAME OVER")); | |||
while(ngame==0){ | |||
controls(); | |||
} | |||
newgamemon(); | |||
} | |||
} | |||
//check for completed lines | |||
} | |||
</code> |
Revision as of 20:43, 27 May 2019
While thinking about ways to make my TRS-80 model I more useable in 2019, I realized that I could not easily find a version of Tetris for it (probably because the TRS-80 came out about 10ish years before Tetris was invented), so I thought I might try to write my own after watching an episode of the 8-bit Guy where David does the same. I used to play the Gameboy version of Tetris all the time on my Ti-nSpire calculator thanks to the nspire hacking scene and an emulator port, so that's the style of Tetris I will try to recreate.
This ended up being a sort of 24 hour challenge, where I created a color version of Tetris with basic animations, scoring, and levels, just like the GameBoy version.
Theory of Operation
The Code
- define pfsizeX 10
- define pfsizeY 22 //note that top 4 lines are not drawn
uint8_t playfield[pfsizeX][pfsizeY]; //the game area
uint8_t pieceC[4][4]; //piece container
int8_t pieceCX; //location of upper left corner of piece container
int8_t pieceCY;
uint8_t pieceT; //type of piece
uint8_t pieceR; //rotation of piece
uint8_t nextpieceT;
uint8_t nextpieceR;
uint8_t dcontrol; //locks in control every frame
uint8_t rcontrol;
uint8_t lastdcontrol=0; //for "debouncing" of inputs
uint8_t lastrcontrol=0;
uint8_t drepeatframe=0;
- define drepeatframes 3 //wait _ frames before repeatedly going in one direction
uint8_t dropframe=0; //counter for number of frames between block drops
uint8_t level=0; //LEVEL decreases frame drop from 20 frames to 0 frames (levels 0 to 20)
boolean ngame=0; //when set, starts new game
uint16_t lines=0; //NUMBER OF LINES CLEARED
uint32_t score=0; //TOTAL SCORE (using NES rules)
uint8_t fdrop; //number of blocks that piece has been fast dropped
uint8_t lslvi=0; //lines since level increase (when this gets to 10, increase the level)
- include <U8g2lib.h>
U8G2_ST7920_128X64_1_HW_SPI u8g2(U8G2_R0, /* CS=*/ 12, /* reset=*/ 8);
void dispscreen(){
u8g2.firstPage();
do {
for(uint8_t j=4; j<pfsizeY; j++){ //draw screen
for(uint8_t i=0; i<pfsizeX; i++){
if(playfield[i][j]!=0){
u8g2.drawBox(5*(j-4),5*(pfsizeX-1)-5*i,6,6);
}else{
u8g2.drawFrame(5*(j-4),5*(pfsizeX-1)-5*i,6,6);
//if(j==3){u8g2.drawLine(5*j,5*(pfsizeX-1)-5*i,5*j+5,5*(pfsizeX-1)-5*i+5);}
}
}}
uint8_t temppieceT=pieceT;
uint8_t temppieceR=pieceR;
pieceT=nextpieceT;
pieceR=nextpieceR;
loadpiece();
for(uint8_t j=0; j<4; j++){ //draw next piece
for(uint8_t i=0; i<4; i++){
if(pieceC[i][j]!=0){
u8g2.drawBox(5*(j+pfsizeY-3),5*(pfsizeX-1)-5*i,6,6);
}else{
u8g2.drawFrame(5*(j+pfsizeY-3),5*(pfsizeX-1)-5*i,6,6);
}
}}
pieceT=temppieceT;
pieceR=temppieceR;
loadpiece();
u8g2.setFont(u8g2_font_6x10_tf);
char zbuffer[32];
sprintf(zbuffer, "N%d", lines);
u8g2.drawStr(0,60, zbuffer);
sprintf(zbuffer, "L%d", level);
u8g2.drawStr(25,60, zbuffer);
sprintf(zbuffer, "S%d", score);
u8g2.drawStr(50,60, zbuffer);
} while( u8g2.nextPage() );
}
void controls(){
if(digitalRead(A3)==0){ngame=1;}
if(dcontrol==0){
if(analogRead(A7)<10){dcontrol=4;}
if(analogRead(A7)>1014){dcontrol=6;}
if(analogRead(A6)>1014){dcontrol=8;}
if(analogRead(A6)<10){dcontrol=2;}
if(dcontrol==0){drepeatframe=0;}
if(dcontrol==lastdcontrol){ //short delay before fast motion
if(drepeatframe<=drepeatframes){
drepeatframe++;
dcontrol=3; //lockout if within lockout period
}
}
}
if(rcontrol==0){
if(digitalRead(A4)==0){rcontrol=1;}
if(digitalRead(A5)==0){rcontrol=2;}
if(rcontrol==lastrcontrol){rcontrol=3;}
}
}
void clearControls(){
if(rcontrol!=3){lastrcontrol=rcontrol;}
if(dcontrol!=3){lastdcontrol=dcontrol;}
rcontrol=0;
dcontrol=0;
}
void draw(){
loadpiece(); //load piece into the piece carrier
for(uint8_t i=0; i<pfsizeX; i++){ //clear any cells with active piece parts (will be written again with new pieceC
for(uint8_t j=0; j<pfsizeY; j++){
if(playfield[i][j]<=7){playfield[i][j]=0;}
}
}
for(uint8_t i=0; i<4; i++){ //copy active piece onto the playfield
for(uint8_t j=0; j<4; j++){
if(pieceCX+i>=0&&pieceCX+i<pfsizeX&&pieceCY+j>=0&&pieceCY+j<pfsizeY){//check if piece segment can be drawn on screen
if(pieceC[i][j]!=0){playfield[i+pieceCX][j+pieceCY]=pieceC[i][j];}
}
}
}
}
boolean checkCollide(){ //move the piece carrier first, then check if anything collides
loadpiece(); //load piece into the piece carrier
boolean nonvalidity=0;
for(uint8_t i=0; i<4; i++){ //run through all piece carrier cells
for(uint8_t j=0; j<4; j++){
if(pieceCX+i>=0&&pieceCX+i<pfsizeX&&pieceCY+j>=0&&pieceCY+j<pfsizeY){ //check if piece carrier segment can be drawn on screen
if(pieceC[i][j]!=0&&playfield[i+pieceCX][j+pieceCY]>7){ //if both background and nonzero piece carrier segment collide
nonvalidity=1;
}
}else{ //this segment of PC can't be drawn on the screen
if(pieceC[i][j]!=0){ //a filled in segment would be drawn offscreen
nonvalidity=1;
}
}
}
}
return nonvalidity;
}
void piece2bg(){
for(uint8_t i=0; i<4; i++){ //copy active piece onto the screen
for(uint8_t j=0; j<4; j++){
if(pieceC[i][j]!=0){playfield[i+pieceCX][j+pieceCY]=pieceC[i][j]+8;} //copy the piece into the playfield/background
}}
nextpiece();
}
void nextpiece(){//generate the next piece and move PC back to the top
pieceT=nextpieceT;
pieceR=nextpieceR;
nextpieceT = rand()%7 + 1;
nextpieceR = rand()%4 + 1;
pieceCY=0; //move piece carrier back to the top of screen
pieceCX=3;
}
void newgamemon(){
if(ngame==0){return;}
Serial.println(F("NEW GAME"));
ngame=0;
for(uint8_t i=0; i<pfsizeX; i++){ //clear any cells with active piece parts (will be written again with new pieceC
for(uint8_t j=0; j<pfsizeY; j++){
playfield[i][j]=0;
}
}
lines=0;
level=0;
score=0;
lslvi=0;
fdrop=0;
nextpiece();
nextpiece();
}
void loadpiece(){ //hardcoded all pieces
switch(pieceT){
case 1: //long one
switch(pieceR){
case 1:
case 3:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=1; pieceC[1][2]=1; pieceC[2][2]=1; pieceC[3][2]=1;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 2:
case 4:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=1; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=1; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=0; pieceC[2][2]=1; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=1; pieceC[3][3]=0;
break;
}
break;
case 2: //backwards L
switch(pieceR){
case 1:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=2; pieceC[1][2]=2; pieceC[2][2]=2; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=2; pieceC[3][3]=0;
break;
case 2:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=2; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=2; pieceC[2][2]=0; pieceC[3][2]=0;
pieceC[0][3]=2; pieceC[1][3]=2; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 3:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=2; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=2; pieceC[1][2]=2; pieceC[2][2]=2; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 4:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=2; pieceC[2][1]=2; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=2; pieceC[2][2]=0; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=2; pieceC[2][3]=0; pieceC[3][3]=0;
break;
}
break;
case 3: //L
switch(pieceR){
case 1:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=3; pieceC[1][2]=3; pieceC[2][2]=3; pieceC[3][2]=0;
pieceC[0][3]=3; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 2:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=3; pieceC[1][1]=3; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=3; pieceC[2][2]=0; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=3; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 3:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=3; pieceC[3][1]=0;
pieceC[0][2]=3; pieceC[1][2]=3; pieceC[2][2]=3; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 4:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=3; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=3; pieceC[2][2]=0; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=3; pieceC[2][3]=3; pieceC[3][3]=0;
break;
}
break;
case 4: //s shape
switch(pieceR){
case 1:
case 3:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=4; pieceC[2][2]=4; pieceC[3][2]=0;
pieceC[0][3]=4; pieceC[1][3]=4; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 2:
case 4:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=4; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=4; pieceC[2][2]=4; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=4; pieceC[3][3]=0;
break;
}
break;
case 5: //T shape
switch(pieceR){
case 1:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=5; pieceC[1][2]=5; pieceC[2][2]=5; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=5; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 2:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=5; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=5; pieceC[1][2]=5; pieceC[2][2]=0; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=5; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 3:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=5; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=5; pieceC[1][2]=5; pieceC[2][2]=5; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0;
break;
case 4:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=5; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=5; pieceC[2][2]=5; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=5; pieceC[2][3]=0; pieceC[3][3]=0;
break;
}
break;
case 6: //reverse s
switch(pieceR){
case 1:
case 3:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=0; pieceC[3][1]=0;
pieceC[0][2]=6; pieceC[1][2]=6; pieceC[2][2]=0; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=6; pieceC[2][3]=6; pieceC[3][3]=0;
break;
case 2:
case 4:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=0; pieceC[2][1]=6; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=6; pieceC[2][2]=6; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=6; pieceC[2][3]=0; pieceC[3][3]=0;
break;
}
break;
case 7: //square
switch(pieceR){
default:
pieceC[0][0]=0; pieceC[1][0]=0; pieceC[2][0]=0; pieceC[3][0]=0;
pieceC[0][1]=0; pieceC[1][1]=7; pieceC[2][1]=7; pieceC[3][1]=0;
pieceC[0][2]=0; pieceC[1][2]=7; pieceC[2][2]=7; pieceC[3][2]=0;
pieceC[0][3]=0; pieceC[1][3]=0; pieceC[2][3]=0; pieceC[3][3]=0;
break;
}
break;
}
}
void setup(){
Serial.begin(115200);
u8g2.begin();
pinMode(A4, INPUT_PULLUP);
pinMode(A5, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP); //new game button
ngame=1;
newgamemon();
}
void loop() {
for(uint32_t h=0;h<=5;h++){//frame delay //DELAY SECTION (BETWEEN FRAMES)
delay(1);
controls();
newgamemon();
}
boolean droppiece=0;
dropframe++;
if(dropframe>=(20-level)){
dropframe=0;
droppiece=1;
}
if(rcontrol==1){
pieceR++; if(pieceR>=5){pieceR=1;} //try to rotate piece
if(checkCollide()){
pieceR--; if(pieceR<=0){pieceR=4;} //undo rotation
}
}
if(rcontrol==2){
pieceR--; if(pieceR<=0){pieceR=4;}
if(checkCollide()){
pieceR++; if(pieceR>=5){pieceR=1;}
}
}
if(dcontrol==4){
pieceCX--; //try and see what happens if we move the piece left
if(checkCollide()){//piece move is not valid
pieceCX++; //take piece back
}
}
if(dcontrol==6){
pieceCX++; //try and see what happens if we move the piece left
if(checkCollide()){//piece move is not valid
pieceCX--; //take piece back
}
}
if(dcontrol==8){
pieceCY--; //try and see what happens if we move the piece up
if(checkCollide()){//piece move is not valid
pieceCY++; //take piece back
}
}
if(dcontrol==2||droppiece==1){
pieceCY++; //try and see what happens if we move the piece down
if(!(drepeatframe<=drepeatframes)){ //is in fast mode
fdrop++;
}else{
fdrop=0;
}
if(checkCollide()){//piece move is not valid
pieceCY--; //take piece back
score+=fdrop; //add # of fast dropped blocks to score
fdrop=0;
piece2bg(); //copy piece to background and reset to next piece
}
}
draw();
dispscreen();
clearControls();
//check for line clears
boolean clearline[pfsizeY];
uint8_t clearedlines=0;
uint8_t templines=0;
for(uint8_t j=4; j<pfsizeY; j++){
clearline[j]=1; //assume line is cleared
for(uint8_t i=0; i<pfsizeX; i++){
if(playfield[i][j]<=7){clearline[j]=0;break;} //line is not full
}
clearedlines+=clearline[j];
templines+=clearline[j];
}
if(clearedlines>0){//breifly animate the cleared lines, then clear them
for(uint8_t f=0; f<=6; f++){
for(uint8_t j=4; j<pfsizeY; j++){
if(clearline[j]==1){
for(uint8_t i=0; i<pfsizeX; i++){
if(f%2==0){playfield[i][j]=8;}else{playfield[i][j]=0;}
}
}
}
draw();
dispscreen();
delay(200);
}
for(uint8_t j=4; j<pfsizeY; j++){ //accutally clear the lines
if(clearline[j]==1){
for(uint8_t t=j; t>=4; t--){
for(uint8_t i=0; i<pfsizeX; i++){
playfield[i][t]=playfield[i][t-1];
}
}
}
}
lines+=templines; //add number of lines to lines counter
switch(templines){ //calculate score
case 1:
score+=40*(level+1);
break;
case 2:
score+=100*(level+1);
break;
case 3:
score+=300*(level+1);
break;
default:
score+=1200*(level+1);
break;
}
for(uint8_t i=1; i<=templines; i++){ //see if the level needs increasing
lslvi++;
if(lslvi>=10){lslvi=0;level++;}
if(level>=20){level=20;}
}
}
//check gameover (scan through line 3 and see if there are any non-active pieces in it)
for(uint8_t i=0; i<pfsizeX; i++){
if(playfield[i][3]>7){
//*********************GAME OVER********************
Serial.println(F("GAME OVER"));
while(ngame==0){
controls();
}
newgamemon();
}
}
//check for completed lines
}