Soviet Bloc Game (C version)

From Alnwlsn - Projects Repository
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 (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

  
}