Soviet Bloc Game (C version)

From Alnwlsn - Projects Repository
Revision as of 20:43, 27 May 2019 by Alnwlsn (talk | contribs)
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.

Theory of Operation

The Code

  1. define pfsizeX 10
  2. 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;

  1. 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)

  1. 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


}