Soviet Bloc Game (C version)
Jump to navigation
Jump to search
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.
Objectives:
- Playfield the same size as GB version
- in color?
- same or similar scoring system to GB version
- same rotation style as GB version
- easily portable to different platforms (that are C like)
- somewhat documented
Theory of Operation
The Code (Arduino version)
#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 }