Tetris/Go: Difference between revisions
(Added linked page) |
m (Corrected an external link.) |
||
Line 1: | Line 1: | ||
{{libheader|raylib-go}} |
{{libheader|raylib-go}} |
||
<br> |
<br> |
||
This is a translation of the sample [https://github.com/raysan5/raylib/blob/master/games/tetris.c |
This is a translation of the sample [https://github.com/raysan5/raylib/blob/master/games/tetris.c 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: |
The arrow keys control the game as follows: |
Revision as of 20:37, 10 February 2020
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>