Tetris/Go

Revision as of 20:32, 10 February 2020 by PureFox (talk | contribs) (Added linked page)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Library: raylib-go


This is a translation of the sample Tetris Game written in C for the original raylib library of which raylib-go is a Go wrapper.

The arrow keys control the game as follows:

  • Left - moves the active piece to the left.
  • Right - moves the active piece to the right.
  • Up - rotates the active piece.
  • Down - hard drops the active piece.


Pressing the P key pauses the game and pressing it again resumes the game where it left off.

As currently written, the game only as one level (more than enough for me!).

The C game uses gray for all 7 pieces but I have changed this so that the active piece and the incoming (aka preview) piece display in the standard colors. However, when the active piece hits the bottom its color changes to gray as before. <lang go>package main

import (

   "fmt"
   rl "github.com/gen2brain/raylib-go/raylib"
   "math/rand"
   "time"

)

const (

   SQUARE_SIZE             = 20
   GRID_HORIZONTAL_SIZE    = 12
   GRID_VERTICAL_SIZE      = 20
   LATERAL_SPEED           = 10
   TURNING_SPEED           = 12
   FAST_FALL_AWAIT_COUNTER = 30
   FADING_TIME             = 33

)

type GridSquare int

const (

   EMPTY GridSquare = iota
   MOVING
   FULL
   BLOCK
   FADING

)

var (

   screenWidth  = int32(800)
   screenHeight = int32(450)
   gameOver = false
   pause    = false
   // These variables keep track of the active piece position
   piecePositionX = 0
   piecePositionY = 0
   // These variables record the active and incoming piece colors
   pieceColor         = rl.Gray
   incomingPieceColor = rl.Gray
   // Statistics
   level = 1
   lines = 0
   // Based on level
   gravitySpeed = 30

)

// Matrices var (

   grid          [GRID_HORIZONTAL_SIZE][GRID_VERTICAL_SIZE]GridSquare
   piece         [4][4]GridSquare
   incomingPiece [4][4]GridSquare

)

// Game parameters var (

   fadingColor  = rl.Gray
   beginPlay    = true
   pieceActive  = false
   detection    = false
   lineToDelete = false

)

// Counters var (

   gravityMovementCounter  = 0
   lateralMovementCounter  = 0
   turnMovementCounter     = 0
   fastFallMovementCounter = 0
   fadeLineCounter         = 0

)

// Initialize game variables func InitGame() {

   // Initialize game statistics
   level = 1 // Always stays at this level in the current program
   lines = 0
   fadingColor = rl.Gray
   piecePositionX = 0
   piecePositionY = 0
   pieceColor = rl.Gray
   incomingPieceColor = rl.Gray
   pause = false
   beginPlay = true
   pieceActive = false
   detection = false
   lineToDelete = false
   // Counters
   gravityMovementCounter = 0
   lateralMovementCounter = 0
   turnMovementCounter = 0
   fastFallMovementCounter = 0
   fadeLineCounter = 0
   gravitySpeed = 30
   // Initialize grid matrices
   for i := 0; i < GRID_HORIZONTAL_SIZE; i++ {
       for j := 0; j < GRID_VERTICAL_SIZE; j++ {
           if (j == GRID_VERTICAL_SIZE-1) || (i == 0) ||
               (i == GRID_HORIZONTAL_SIZE-1) {
               grid[i][j] = BLOCK
           } else {
               grid[i][j] = EMPTY
           }
       }
   }
   // Initialize incoming piece matrices
   for i := 0; i < 4; i++ {
       for j := 0; j < 4; j++ {
           incomingPiece[i][j] = EMPTY
       }
   }

}

// Update game (one frame) func UpdateGame() {

   if !gameOver {
       if rl.IsKeyPressed('P') {
           pause = !pause
       }
       if !pause {
           if !lineToDelete {
               if !pieceActive {
                   // Get another piece
                   pieceActive = CreatePiece()
                   // We leave a little time before starting the fast falling down
                   fastFallMovementCounter = 0
               } else { // Piece falling
                   // Counters update
                   fastFallMovementCounter++
                   gravityMovementCounter++
                   lateralMovementCounter++
                   turnMovementCounter++
                   // We make sure to move if we've pressed the key this frame
                   if rl.IsKeyPressed(rl.KeyLeft) || rl.IsKeyPressed(rl.KeyRight) {
                       lateralMovementCounter = LATERAL_SPEED
                   }
                   if rl.IsKeyPressed(rl.KeyUp) {
                       turnMovementCounter = TURNING_SPEED
                   }
                   // Fall down
                   if rl.IsKeyDown(rl.KeyDown) &&
                       fastFallMovementCounter >= FAST_FALL_AWAIT_COUNTER {
                       // We make sure the piece is going to fall this frame
                       gravityMovementCounter += gravitySpeed
                   }
                   if gravityMovementCounter >= gravitySpeed {
                       // Basic falling movement
                       CheckDetection(&detection)
                       // Check if the piece has collided with another piece or with the boundaries
                       ResolveFallingMovement(&detection, &pieceActive)
                       // Check if we completed a line and, if so, erase the line
                       // and pull down the the lines above
                       CheckCompletion(&lineToDelete)
                       gravityMovementCounter = 0
                   }
                   // Move laterally at player's will
                   if lateralMovementCounter >= LATERAL_SPEED {
                       // Update the lateral movement and, if successful, reset the lateral counter
                       if !ResolveLateralMovement() {
                           lateralMovementCounter = 0
                       }
                   }
                   // Turn the piece at player's will
                   if turnMovementCounter >= TURNING_SPEED {
                       // Update the turning movement and reset the turning counter
                       if ResolveTurnMovement() {
                           turnMovementCounter = 0
                       }
                   }
               }
               // Game over logic
               for j := 0; j < 2; j++ {
                   for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
                       if grid[i][j] == FULL {
                           gameOver = true
                       }
                   }
               }
           } else {
               // Animation when deleting lines
               fadeLineCounter++
               if fadeLineCounter%8 < 4 {
                   fadingColor = rl.Maroon
               } else {
                   fadingColor = rl.Gray
               }
               if fadeLineCounter >= FADING_TIME {
                   DeleteCompleteLines()
                   fadeLineCounter = 0
                   lineToDelete = false
                   lines++
               }
           }
       }
   } else if rl.IsKeyPressed(rl.KeyEnter) {
       InitGame()
       gameOver = false
   }

}

// Draw game (one frame) func DrawGame() {

   rl.BeginDrawing()
   rl.ClearBackground(rl.RayWhite)
   if !gameOver {
       // Draw gameplay area
       var offset rl.Vector2
       offset.X = float32(screenWidth/2 - GRID_HORIZONTAL_SIZE*SQUARE_SIZE/2 - 50)
       offset.Y = float32(screenHeight/2 - (GRID_VERTICAL_SIZE-1)*SQUARE_SIZE/2 + SQUARE_SIZE*2)
       offset.Y -= 50 // NOTE: Hard-coded position!
       controller := offset.X
       for j := 0; j < GRID_VERTICAL_SIZE; j++ {
           for i := 0; i < GRID_HORIZONTAL_SIZE; i++ {
               // Draw each square of the grid
               ox, oy := int32(offset.X), int32(offset.Y)
               if grid[i][j] == EMPTY {
                   rl.DrawLine(ox, oy, ox+SQUARE_SIZE, oy, rl.LightGray)
                   rl.DrawLine(ox, oy, ox, oy+SQUARE_SIZE, rl.LightGray)
                   rl.DrawLine(ox+SQUARE_SIZE, oy, ox+SQUARE_SIZE,
                       oy+SQUARE_SIZE, rl.LightGray)
                   rl.DrawLine(ox, oy+SQUARE_SIZE, ox+SQUARE_SIZE,
                       oy+SQUARE_SIZE, rl.LightGray)
                   offset.X += SQUARE_SIZE
               } else if grid[i][j] == FULL {
                   rl.DrawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, rl.Gray)
                   offset.X += SQUARE_SIZE
               } else if grid[i][j] == MOVING {
                   rl.DrawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, pieceColor)
                   offset.X += SQUARE_SIZE
               } else if grid[i][j] == BLOCK {
                   rl.DrawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, rl.LightGray)
                   offset.X += SQUARE_SIZE
               } else if grid[i][j] == FADING {
                   rl.DrawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, fadingColor)
                   offset.X += SQUARE_SIZE
               }
           }
           offset.X = controller
           offset.Y += SQUARE_SIZE
       }
       // Draw incoming piece (hard-coded)
       offset.X = 500
       offset.Y = 45
       controler := offset.X
       for j := 0; j < 4; j++ {
           for i := 0; i < 4; i++ {
               ox, oy := int32(offset.X), int32(offset.Y)
               if incomingPiece[i][j] == EMPTY {
                   rl.DrawLine(ox, oy, ox+SQUARE_SIZE, oy, rl.LightGray)
                   rl.DrawLine(ox, oy, ox, oy+SQUARE_SIZE, rl.LightGray)
                   rl.DrawLine(ox+SQUARE_SIZE, oy, ox+SQUARE_SIZE,
                       oy+SQUARE_SIZE, rl.LightGray)
                   rl.DrawLine(ox, oy+SQUARE_SIZE, ox+SQUARE_SIZE,
                       oy+SQUARE_SIZE, rl.LightGray)
                   offset.X += SQUARE_SIZE
               } else if incomingPiece[i][j] == MOVING {
                   rl.DrawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, incomingPieceColor)
                   offset.X += SQUARE_SIZE
               }
           }
           offset.X = controler
           offset.Y += SQUARE_SIZE
       }
       ox, oy := int32(offset.X), int32(offset.Y)
       rl.DrawText("INCOMING:", ox, oy-100, 10, rl.Gray)
       rl.DrawText(fmt.Sprintf("LINES:      %04d", lines), ox, oy+20, 10, rl.Gray)
       if pause {
           rl.DrawText("GAME PAUSED", screenWidth/2-rl.MeasureText("GAME PAUSED", 40)/2,
               screenHeight/2-40, 40, rl.Gray)
       }
   } else {
       rl.DrawText("PRESS [ENTER] TO PLAY AGAIN", int32(rl.GetScreenWidth()/2)-
           rl.MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2,
           int32(rl.GetScreenHeight()/2)-50, 20, rl.Gray)
   }
   rl.EndDrawing()

}

// Unload game variables func UnloadGame() {

   // Nothing to do here

}

// Update and Draw (one frame) func UpdateDrawFrame() {

   UpdateGame()
   DrawGame()

}

func CreatePiece() bool {

   piecePositionX = (GRID_HORIZONTAL_SIZE - 4) / 2
   piecePositionY = 0
   // If the game is starting and we are going to create the first piece,
   // we create an extra one
   if beginPlay {
       GetRandomPiece()
       beginPlay = false
   }
   // We assign the incoming piece to the actual piece
   for i := 0; i < 4; i++ {
       for j := 0; j < 4; j++ {
           piece[i][j] = incomingPiece[i][j]
       }
   }
   pieceColor = incomingPieceColor
   // We assign a random piece to the incoming one
   GetRandomPiece()
   // Assign the piece to the grid
   for i := piecePositionX; i < piecePositionX+4; i++ {
       for j := 0; j < 4; j++ {
           if piece[i-piecePositionX][j] == MOVING {
               grid[i][j] = MOVING
           }
       }
   }
   return true

}

func GetRandomPiece() {

   random := rand.Intn(7) // 0 to 6 inclusive
   for i := 0; i < 4; i++ {
       for j := 0; j < 4; j++ {
           incomingPiece[i][j] = EMPTY
       }
   }
   switch random {
   case 0: // O (Square)
       incomingPiece[1][1] = MOVING
       incomingPiece[2][1] = MOVING
       incomingPiece[1][2] = MOVING
       incomingPiece[2][2] = MOVING
       incomingPieceColor = rl.Yellow
   case 1: // L
       incomingPiece[1][0] = MOVING
       incomingPiece[1][1] = MOVING
       incomingPiece[1][2] = MOVING
       incomingPiece[2][2] = MOVING
       incomingPieceColor = rl.Blue
   case 2: // J (Inverted L)
       incomingPiece[1][2] = MOVING
       incomingPiece[2][0] = MOVING
       incomingPiece[2][1] = MOVING
       incomingPiece[2][2] = MOVING
       incomingPieceColor = rl.Brown
   case 3: // I (Straight)
       incomingPiece[0][1] = MOVING
       incomingPiece[1][1] = MOVING
       incomingPiece[2][1] = MOVING
       incomingPiece[3][1] = MOVING
       incomingPieceColor = rl.SkyBlue
   case 4: // T (Cross cut)
       incomingPiece[1][0] = MOVING
       incomingPiece[1][1] = MOVING
       incomingPiece[1][2] = MOVING
       incomingPiece[2][1] = MOVING
       incomingPieceColor = rl.Purple
   case 5: // S
       incomingPiece[1][1] = MOVING
       incomingPiece[2][1] = MOVING
       incomingPiece[2][2] = MOVING
       incomingPiece[3][2] = MOVING
       incomingPieceColor = rl.Green
   case 6: // Z (Inverted S)
       incomingPiece[1][2] = MOVING
       incomingPiece[2][2] = MOVING
       incomingPiece[2][1] = MOVING
       incomingPiece[3][1] = MOVING
       incomingPieceColor = rl.Red
   }

}

func ResolveFallingMovement(detection, pieceActive *bool) {

   // If we've finished moving this piece, we stop it
   if *detection {
       for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
           for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
               if grid[i][j] == MOVING {
                   grid[i][j] = FULL
                   *detection = false
                   *pieceActive = false
               }
           }
       }
   } else { // We move down the piece
       for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
           for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
               if grid[i][j] == MOVING {
                   grid[i][j+1] = MOVING
                   grid[i][j] = EMPTY
               }
           }
       }
       piecePositionY++
   }

}

func ResolveLateralMovement() bool {

   collision := false
   // Piece movement
   if rl.IsKeyDown(rl.KeyLeft) { // Move left
       // Check if it's possible to move to left
       for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
           for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
               if grid[i][j] == MOVING {
                   // Check if we are touching the left wall or
                   // we have a full square at the left
                   if i-1 == 0 || grid[i-1][j] == FULL {
                       collision = true
                   }
               }
           }
       }
       // If able, move left
       if !collision {
           for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
               // We check the matrix from left to right
               for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
                   // Move everything to the left
                   if grid[i][j] == MOVING {
                       grid[i-1][j] = MOVING
                       grid[i][j] = EMPTY
                   }
               }
           }
           piecePositionX--
       }
   } else if rl.IsKeyDown(rl.KeyRight) { // Move right
       // Check if it's possible to move to right
       for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
           for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
               if grid[i][j] == MOVING {
                   // Check if we are touching the right wall or
                   // we have a full square at the right
                   if i+1 == GRID_HORIZONTAL_SIZE-1 || grid[i+1][j] == FULL {
                       collision = true
                   }
               }
           }
       }
       // If able, move right
       if !collision {
           for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
               // We check the matrix from right to left
               for i := GRID_HORIZONTAL_SIZE - 1; i >= 1; i-- {
                   // Move everything to the right
                   if grid[i][j] == MOVING {
                       grid[i+1][j] = MOVING
                       grid[i][j] = EMPTY
                   }
               }
           }
           piecePositionX++
       }
   }
   return collision

}

func ResolveTurnMovement() bool {

   // Input for turning the piece
   if rl.IsKeyDown(rl.KeyUp) {
       aux := GridSquare(0)
       // Check all turning possibilities
       if grid[piecePositionX+3][piecePositionY] == MOVING &&
           grid[piecePositionX][piecePositionY] != EMPTY &&
           grid[piecePositionX][piecePositionY] != MOVING {
           goto next
       }
       if grid[piecePositionX+3][piecePositionY+3] == MOVING &&
           grid[piecePositionX+3][piecePositionY] != EMPTY &&
           grid[piecePositionX+3][piecePositionY] != MOVING {
           goto next
       }
       if grid[piecePositionX][piecePositionY+3] == MOVING &&
           grid[piecePositionX+3][piecePositionY+3] != EMPTY &&
           grid[piecePositionX+3][piecePositionY+3] != MOVING {
           goto next
       }
       if grid[piecePositionX][piecePositionY] == MOVING &&
           grid[piecePositionX][piecePositionY+3] != EMPTY &&
           grid[piecePositionX][piecePositionY+3] != MOVING {
           goto next
       }
       if grid[piecePositionX+1][piecePositionY] == MOVING &&
           grid[piecePositionX][piecePositionY+2] != EMPTY &&
           grid[piecePositionX][piecePositionY+2] != MOVING {
           goto next
       }
       if grid[piecePositionX+3][piecePositionY+1] == MOVING &&
           grid[piecePositionX+1][piecePositionY] != EMPTY &&
           grid[piecePositionX+1][piecePositionY] != MOVING {
           goto next
       }
       if grid[piecePositionX+2][piecePositionY+3] == MOVING &&
           grid[piecePositionX+3][piecePositionY+1] != EMPTY &&
           grid[piecePositionX+3][piecePositionY+1] != MOVING {
           goto next
       }
       if grid[piecePositionX][piecePositionY+2] == MOVING &&
           grid[piecePositionX+2][piecePositionY+3] != EMPTY &&
           grid[piecePositionX+2][piecePositionY+3] != MOVING {
           goto next
       }
       if grid[piecePositionX+2][piecePositionY] == MOVING &&
           grid[piecePositionX][piecePositionY+1] != EMPTY &&
           grid[piecePositionX][piecePositionY+1] != MOVING {
           goto next
       }
       if grid[piecePositionX+3][piecePositionY+2] == MOVING &&
           grid[piecePositionX+2][piecePositionY] != EMPTY &&
           grid[piecePositionX+2][piecePositionY] != MOVING {
           goto next
       }
       if grid[piecePositionX+1][piecePositionY+3] == MOVING &&
           grid[piecePositionX+3][piecePositionY+2] != EMPTY &&
           grid[piecePositionX+3][piecePositionY+2] != MOVING {
           goto next
       }
       if grid[piecePositionX][piecePositionY+1] == MOVING &&
           grid[piecePositionX+1][piecePositionY+3] != EMPTY &&
           grid[piecePositionX+1][piecePositionY+3] != MOVING {
           goto next
       }
       if grid[piecePositionX+1][piecePositionY+1] == MOVING &&
           grid[piecePositionX+1][piecePositionY+2] != EMPTY &&
           grid[piecePositionX+1][piecePositionY+2] != MOVING {
           goto next
       }
       if grid[piecePositionX+2][piecePositionY+1] == MOVING &&
           grid[piecePositionX+1][piecePositionY+1] != EMPTY &&
           grid[piecePositionX+1][piecePositionY+1] != MOVING {
           goto next
       }
       if grid[piecePositionX+2][piecePositionY+2] == MOVING &&
           grid[piecePositionX+2][piecePositionY+1] != EMPTY &&
           grid[piecePositionX+2][piecePositionY+1] != MOVING {
           goto next
       }
       if grid[piecePositionX+1][piecePositionY+2] == MOVING &&
           grid[piecePositionX+2][piecePositionY+2] != EMPTY &&
           grid[piecePositionX+2][piecePositionY+2] != MOVING {
           goto next
       }
       aux = piece[0][0]
       piece[0][0] = piece[3][0]
       piece[3][0] = piece[3][3]
       piece[3][3] = piece[0][3]
       piece[0][3] = aux
       aux = piece[1][0]
       piece[1][0] = piece[3][1]
       piece[3][1] = piece[2][3]
       piece[2][3] = piece[0][2]
       piece[0][2] = aux
       aux = piece[2][0]
       piece[2][0] = piece[3][2]
       piece[3][2] = piece[1][3]
       piece[1][3] = piece[0][1]
       piece[0][1] = aux
       aux = piece[1][1]
       piece[1][1] = piece[2][1]
       piece[2][1] = piece[2][2]
       piece[2][2] = piece[1][2]
       piece[1][2] = aux
   next:
       for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
           for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
               if grid[i][j] == MOVING {
                   grid[i][j] = EMPTY
               }
           }
       }
       for i := piecePositionX; i < piecePositionX+4; i++ {
           for j := piecePositionY; j < piecePositionY+4; j++ {
               if piece[i-piecePositionX][j-piecePositionY] == MOVING {
                   grid[i][j] = MOVING
               }
           }
       }
       return true
   }
   return false

}

func CheckDetection(detection *bool) {

   for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
       for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
           if grid[i][j] == MOVING && (grid[i][j+1] == FULL || grid[i][j+1] == BLOCK) {
               *detection = true
           }
       }
   }

}

func CheckCompletion(lineToDelete *bool) {

   for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
       calculator := 0
       for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
           // Count each square of the line
           if grid[i][j] == FULL {
               calculator++
           }
           // Check if we completed the whole line
           if calculator == GRID_HORIZONTAL_SIZE-2 {
               *lineToDelete = true
               calculator = 0
               // Mark the completed line
               for z := 1; z < GRID_HORIZONTAL_SIZE-1; z++ {
                   grid[z][j] = FADING
               }
           }
       }
   }

}

func DeleteCompleteLines() {

   for j := GRID_VERTICAL_SIZE - 2; j >= 0; j-- {
       for grid[1][j] == FADING {
           for i := 1; i < GRID_HORIZONTAL_SIZE-1; i++ {
               grid[i][j] = EMPTY
           }
           for j2 := j - 1; j2 >= 0; j2-- {
               for i2 := 1; i2 < GRID_HORIZONTAL_SIZE-1; i2++ {
                   if grid[i2][j2] == FULL {
                       grid[i2][j2+1] = FULL
                       grid[i2][j2] = EMPTY
                   } else if grid[i2][j2] == FADING {
                       grid[i2][j2+1] = FADING
                       grid[i2][j2] = EMPTY
                   }
               }
           }
       }
   }

}

func main() {

   rand.Seed(time.Now().UnixNano())
   rl.InitWindow(screenWidth, screenHeight, "Tetris")
   InitGame()
   rl.SetTargetFPS(60)
   for !rl.WindowShouldClose() { // Detect window close button or ESC key
       UpdateDrawFrame()
   }
   UnloadGame()
   rl.CloseWindow()

}</lang>