Tetris/Go
< Tetris
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 has 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.
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()
}