Tetris/Wren
Appearance
< Tetris
See Go entry for notes on playing.
import "raylib" for Raylib as rl, Color, Key, Vector2
import "random" for Random
import "./fmt" for Fmt
var SQUARE_SIZE = 20
var GRID_HORIZONTAL_SIZE = 12
var GRID_VERTICAL_SIZE = 20
var LATERAL_SPEED = 10
var TURNING_SPEED = 12
var FAST_FALL_AWAIT_COUNTER = 30
var FADING_TIME = 33
var EMPTY = 0
var MOVING = 1
var FULL = 2
var BLOCK = 3
var FADING = 4
var screenWidth = 800
var screenHeight = 450
var gameOver = false
var pause = false
// These variables keep track of the active piece position
var piecePositionX = 0
var piecePositionY = 0
// These variables record the active and incoming piece colors
var pieceColor = Color.gray
var incomingPieceColor = Color.gray
// Statistics
var level = 1
var lines = 0
// Based on level
var gravitySpeed = 30
// Matrices
var grid = List.filled(GRID_HORIZONTAL_SIZE, null)
for (i in 0...grid.count) grid[i] = List.filled(GRID_VERTICAL_SIZE, 0)
var piece = List.filled(4, null)
var incomingPiece = List.filled(4, null)
for (i in 0..3) {
piece[i] = List.filled(4, 0)
incomingPiece[i] = List.filled(4, 0)
}
// Game parameters
var fadingColor = Color.gray
var beginPlay = true
var pieceActive = false
var detection = false
var lineToDelete = false
// Counters
var gravityMovementCounter = 0
var lateralMovementCounter = 0
var turnMovementCounter = 0
var fastFallMovementCounter = 0
var fadeLineCounter = 0
// Other variables
var keys = Key.keyboard
var rand = Random.new()
// Initialize game variables
var InitGame = Fn.new {
// Initialize game statistics
level = 1 // Always stays at this level in the current program
lines = 0
fadingColor = Color.gray
piecePositionX = 0
piecePositionY = 0
pieceColor = Color.gray
incomingPieceColor = Color.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 in 0...GRID_HORIZONTAL_SIZE) {
for (j in 0...GRID_VERTICAL_SIZE) {
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 in 0..3) {
for (j in 0..3) incomingPiece[i][j] = EMPTY
}
}
// Update game (one frame)
var UpdateGame = Fn.new {
if (!gameOver) {
if (rl.isKeyPressed(keys["p"])) pause = !pause
if (!pause) {
if (!lineToDelete) {
if (!pieceActive) {
// Get another piece
pieceActive = CreatePiece.call()
// We leave a little time before starting the fast falling down
fastFallMovementCounter = 0
} else { // Piece falling
// Counters update
fastFallMovementCounter = fastFallMovementCounter + 1
gravityMovementCounter = gravityMovementCounter + 1
lateralMovementCounter = lateralMovementCounter + 1
turnMovementCounter = turnMovementCounter + 1
// We make sure to move if we've pressed the key this frame
if (rl.isKeyPressed(keys["left"]) || rl.isKeyPressed(keys["right"])) {
lateralMovementCounter = LATERAL_SPEED
}
if (rl.isKeyPressed(keys["up"])) {
turnMovementCounter = TURNING_SPEED
}
// Fall down
if (rl.isKeyDown(keys["down"]) &&
fastFallMovementCounter >= FAST_FALL_AWAIT_COUNTER) {
// We make sure the piece is going to fall this frame
gravityMovementCounter = gravityMovementCounter + gravitySpeed
}
if (gravityMovementCounter >= gravitySpeed) {
// Basic falling movement
CheckDetection.call()
// Check if the piece has collided with another piece or with the boundaries
ResolveFallingMovement.call()
// Check if we completed a line and, if so, erase the line
// and pull down the the lines above
CheckCompletion.call()
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.call()) lateralMovementCounter = 0
}
// Turn the piece at player's will
if (turnMovementCounter >= TURNING_SPEED) {
// Update the turning movement and reset the turning counter
if (ResolveTurnMovement.call()) turnMovementCounter = 0
}
}
// Game over logic
for (j in 0..1) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
if (grid[i][j] == FULL) gameOver = true
}
}
} else {
// Animation when deleting lines
fadeLineCounter = fadeLineCounter + 1
if (fadeLineCounter%8 < 4) {
fadingColor = Color.maroon
} else {
fadingColor = Color.gray
}
if (fadeLineCounter >= FADING_TIME) {
DeleteCompleteLines.call()
fadeLineCounter = 0
lineToDelete = false
lines = lines + 1
}
}
}
} else if (rl.isKeyPressed(keys["enter"])) {
InitGame.call()
gameOver = false
}
}
// Draw game (one frame)
var DrawGame = Fn.new {
rl.beginDrawing()
rl.clearBackground(Color.white)
if (!gameOver) {
// Draw gameplay area
var offset = Vector2.new()
offset.x = (screenWidth/2).floor - (GRID_HORIZONTAL_SIZE*SQUARE_SIZE/2).floor - 50
offset.y = (screenHeight/2).floor - ((GRID_VERTICAL_SIZE-1)*SQUARE_SIZE/2).floor + SQUARE_SIZE*2
offset.y = offset.y - 50 // NOTE: Hard-coded position!
var controller = offset.x
for (j in 0...GRID_VERTICAL_SIZE) {
for (i in 0...GRID_HORIZONTAL_SIZE) {
// Draw each square of the grid
var ox = offset.x.truncate
var oy = offset.y.truncate
if (grid[i][j] == EMPTY) {
rl.drawLine(ox, oy, ox+SQUARE_SIZE, oy, Color.lightGray)
rl.drawLine(ox, oy, ox, oy+SQUARE_SIZE, Color.lightGray)
rl.drawLine(ox+SQUARE_SIZE, oy, ox+SQUARE_SIZE, oy+SQUARE_SIZE, Color.lightGray)
rl.drawLine(ox, oy+SQUARE_SIZE, ox+SQUARE_SIZE, oy+SQUARE_SIZE, Color.lightGray)
offset.x = offset.x + SQUARE_SIZE
} else if (grid[i][j] == FULL) {
rl.drawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, Color.gray)
offset.x = offset.x + SQUARE_SIZE
} else if (grid[i][j] == MOVING) {
rl.drawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, pieceColor)
offset.x = offset.x + SQUARE_SIZE
} else if (grid[i][j] == BLOCK) {
rl.drawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, Color.lightGray)
offset.x = offset.x + SQUARE_SIZE
} else if (grid[i][j] == FADING) {
rl.drawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, fadingColor)
offset.x = offset.x + SQUARE_SIZE
}
}
offset.x = controller
offset.y = offset.y + SQUARE_SIZE
}
// Draw incoming piece (hard-coded)
offset.x = 500
offset.y = 45
var controler = offset.x
for (j in 0..3) {
for (i in 0..3) {
var ox = offset.x.truncate
var oy = offset.y.truncate
if (incomingPiece[i][j] == EMPTY) {
rl.drawLine(ox, oy, ox+SQUARE_SIZE, oy, Color.lightGray)
rl.drawLine(ox, oy, ox, oy+SQUARE_SIZE, Color.lightGray)
rl.drawLine(ox+SQUARE_SIZE, oy, ox+SQUARE_SIZE, oy+SQUARE_SIZE, Color.lightGray)
rl.drawLine(ox, oy+SQUARE_SIZE, ox+SQUARE_SIZE, oy+SQUARE_SIZE, Color.lightGray)
offset.x = offset.x + SQUARE_SIZE
} else if (incomingPiece[i][j] == MOVING) {
rl.drawRectangle(ox, oy, SQUARE_SIZE, SQUARE_SIZE, incomingPieceColor)
offset.x = offset.x + SQUARE_SIZE
}
}
offset.x = controler
offset.y = offset.y + SQUARE_SIZE
}
var ox = offset.x.truncate
var oy = offset.y.truncate
rl.drawText("INCOMING:", ox, oy-100, 10, Color.gray)
rl.drawText(Fmt.swrite("LINES: $04d", lines), ox, oy+20, 10, Color.gray)
if (pause) {
var text = "GAME PAUSED"
rl.drawText(text,
(screenWidth/2).floor - text.count * 8,
(screenHeight/2).floor-40, 40, Color.gray)
}
} else {
var text = "PRESS [ENTER] TO PLAY AGAIN"
rl.drawText(text,
(rl.screenWidth/2).floor - text.count * 8,
(rl.screenHeight/2).floor-50, 20, Color.gray)
}
rl.endDrawing()
}
// Unload game variables
var UnloadGame = Fn.new {
// Nothing to do here
}
// Update and Draw (one frame)
var UpdateDrawFrame = Fn.new {
UpdateGame.call()
DrawGame.call()
}
var CreatePiece = Fn.new {
piecePositionX = ((GRID_HORIZONTAL_SIZE - 4) / 2).floor
piecePositionY = 0
// If the game is starting and we are going to create the first piece,
// we create an extra one
if (beginPlay) {
GetRandomPiece.call()
beginPlay = false
}
// We assign the incoming piece to the actual piece
for (i in 0..3) {
for (j in 0..3) piece[i][j] = incomingPiece[i][j]
}
pieceColor = incomingPieceColor
// We assign a random piece to the incoming one
GetRandomPiece.call()
// Assign the piece to the grid
for (i in piecePositionX..piecePositionX+3) {
for (j in 0..3) {
if (piece[i-piecePositionX][j] == MOVING) grid[i][j] = MOVING
}
}
return true
}
var GetRandomPiece = Fn.new {
var random = rand.int(7) // 0 to 6 inclusive
for (i in 0..3) {
for (j in 0..3) incomingPiece[i][j] = EMPTY
}
if (random == 0) { // O (Square)
incomingPiece[1][1] = MOVING
incomingPiece[2][1] = MOVING
incomingPiece[1][2] = MOVING
incomingPiece[2][2] = MOVING
incomingPieceColor = Color.yellow
} else if (random == 1) { // L
incomingPiece[1][0] = MOVING
incomingPiece[1][1] = MOVING
incomingPiece[1][2] = MOVING
incomingPiece[2][2] = MOVING
incomingPieceColor = Color.blue
} else if (random == 2) { // J (Inverted L)
incomingPiece[1][2] = MOVING
incomingPiece[2][0] = MOVING
incomingPiece[2][1] = MOVING
incomingPiece[2][2] = MOVING
incomingPieceColor = Color.brown
} else if (random == 3) { // I (Straight)
incomingPiece[0][1] = MOVING
incomingPiece[1][1] = MOVING
incomingPiece[2][1] = MOVING
incomingPiece[3][1] = MOVING
incomingPieceColor = Color.skyBlue
} else if (random == 4) { // T (Cross cut)
incomingPiece[1][0] = MOVING
incomingPiece[1][1] = MOVING
incomingPiece[1][2] = MOVING
incomingPiece[2][1] = MOVING
incomingPieceColor = Color.purple
} else if (random == 5) { // S
incomingPiece[1][1] = MOVING
incomingPiece[2][1] = MOVING
incomingPiece[2][2] = MOVING
incomingPiece[3][2] = MOVING
incomingPieceColor = Color.green
} else if (random == 6) { // Z (Inverted S)
incomingPiece[1][2] = MOVING
incomingPiece[2][2] = MOVING
incomingPiece[2][1] = MOVING
incomingPiece[3][1] = MOVING
incomingPieceColor = Color.red
}
}
var ResolveFallingMovement = Fn.new {
// If we've finished moving this piece, we stop it
if (detection) {
for (j in GRID_VERTICAL_SIZE-2..0) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
if (grid[i][j] == MOVING) {
grid[i][j] = FULL
detection = false
pieceActive = false
}
}
}
} else { // We move down the piece
for (j in GRID_VERTICAL_SIZE-2..0) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
if (grid[i][j] == MOVING) {
grid[i][j+1] = MOVING
grid[i][j] = EMPTY
}
}
}
piecePositionY = piecePositionY + 1
}
}
var ResolveLateralMovement = Fn.new {
var collision = false
// Piece movement
if (rl.isKeyDown(keys["left"])) { // Move left
// Check if it's possible to move to left
for (j in GRID_VERTICAL_SIZE-2..0) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
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 in GRID_VERTICAL_SIZE-2..0) {
// We check the matrix from left to right
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
// Move everything to the left
if (grid[i][j] == MOVING) {
grid[i-1][j] = MOVING
grid[i][j] = EMPTY
}
}
}
piecePositionX = piecePositionX - 1
}
} else if (rl.isKeyDown(keys["right"])) { // Move right
// Check if it's possible to move to right
for (j in GRID_VERTICAL_SIZE-2..0) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
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 in GRID_VERTICAL_SIZE-2..0) {
// We check the matrix from right to left
for (i in GRID_HORIZONTAL_SIZE-1..1) {
// Move everything to the right
if (grid[i][j] == MOVING) {
grid[i+1][j] = MOVING
grid[i][j] = EMPTY
}
}
}
piecePositionX = piecePositionX + 1
}
}
return collision
}
var ResolveTurnMovement = Fn.new {
// Input for turning the piece
if (rl.isKeyDown(keys["up"])) {
var aux = 0
var next = false
// Check all turning possibilities
if (grid[piecePositionX+3][piecePositionY] == MOVING &&
grid[piecePositionX][piecePositionY] != EMPTY &&
grid[piecePositionX][piecePositionY] != MOVING) {
next = true
} else if (grid[piecePositionX+3][piecePositionY+3] == MOVING &&
grid[piecePositionX+3][piecePositionY] != EMPTY &&
grid[piecePositionX+3][piecePositionY] != MOVING) {
next = true
} else if (grid[piecePositionX][piecePositionY+3] == MOVING &&
grid[piecePositionX+3][piecePositionY+3] != EMPTY &&
grid[piecePositionX+3][piecePositionY+3] != MOVING) {
next = true
} else if (grid[piecePositionX][piecePositionY] == MOVING &&
grid[piecePositionX][piecePositionY+3] != EMPTY &&
grid[piecePositionX][piecePositionY+3] != MOVING) {
next = true
} else if (grid[piecePositionX+1][piecePositionY] == MOVING &&
grid[piecePositionX][piecePositionY+2] != EMPTY &&
grid[piecePositionX][piecePositionY+2] != MOVING) {
next = true
} else if (grid[piecePositionX+3][piecePositionY+1] == MOVING &&
grid[piecePositionX+1][piecePositionY] != EMPTY &&
grid[piecePositionX+1][piecePositionY] != MOVING) {
next = true
} else if (grid[piecePositionX+2][piecePositionY+3] == MOVING &&
grid[piecePositionX+3][piecePositionY+1] != EMPTY &&
grid[piecePositionX+3][piecePositionY+1] != MOVING) {
next = true
} else if (grid[piecePositionX][piecePositionY+2] == MOVING &&
grid[piecePositionX+2][piecePositionY+3] != EMPTY &&
grid[piecePositionX+2][piecePositionY+3] != MOVING) {
next = true
} else if (grid[piecePositionX+2][piecePositionY] == MOVING &&
grid[piecePositionX][piecePositionY+1] != EMPTY &&
grid[piecePositionX][piecePositionY+1] != MOVING) {
next = true
} else if (grid[piecePositionX+3][piecePositionY+2] == MOVING &&
grid[piecePositionX+2][piecePositionY] != EMPTY &&
grid[piecePositionX+2][piecePositionY] != MOVING) {
next = true
} else if (grid[piecePositionX+1][piecePositionY+3] == MOVING &&
grid[piecePositionX+3][piecePositionY+2] != EMPTY &&
grid[piecePositionX+3][piecePositionY+2] != MOVING) {
next = true
} else if (grid[piecePositionX][piecePositionY+1] == MOVING &&
grid[piecePositionX+1][piecePositionY+3] != EMPTY &&
grid[piecePositionX+1][piecePositionY+3] != MOVING) {
next = true
} else if (grid[piecePositionX+1][piecePositionY+1] == MOVING &&
grid[piecePositionX+1][piecePositionY+2] != EMPTY &&
grid[piecePositionX+1][piecePositionY+2] != MOVING) {
next = true
} else if (grid[piecePositionX+2][piecePositionY+1] == MOVING &&
grid[piecePositionX+1][piecePositionY+1] != EMPTY &&
grid[piecePositionX+1][piecePositionY+1] != MOVING) {
next = true
} else if (grid[piecePositionX+2][piecePositionY+2] == MOVING &&
grid[piecePositionX+2][piecePositionY+1] != EMPTY &&
grid[piecePositionX+2][piecePositionY+1] != MOVING) {
next = true
} else if (grid[piecePositionX+1][piecePositionY+2] == MOVING &&
grid[piecePositionX+2][piecePositionY+2] != EMPTY &&
grid[piecePositionX+2][piecePositionY+2] != MOVING) {
next = true
}
if (!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
}
for (j in GRID_VERTICAL_SIZE-2..0) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
if (grid[i][j] == MOVING) grid[i][j] = EMPTY
}
}
for (i in piecePositionX..piecePositionX+3) {
for (j in piecePositionY..piecePositionY+3) {
if (piece[i-piecePositionX][j-piecePositionY] == MOVING) grid[i][j] = MOVING
}
}
return true
}
return false
}
var CheckDetection = Fn.new {
for (j in GRID_VERTICAL_SIZE-2..0) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
if (grid[i][j] == MOVING && (grid[i][j+1] == FULL || grid[i][j+1] == BLOCK)) {
detection = true
return
}
}
}
}
var CheckCompletion = Fn.new {
for (j in GRID_VERTICAL_SIZE-2..0) {
var calculator = 0
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
// Count each square of the line
if (grid[i][j] == FULL) calculator = calculator + 1
// Check if we completed the whole line
if (calculator == GRID_HORIZONTAL_SIZE-2) {
lineToDelete = true
calculator = 0
// Mark the completed line
for (z in 1...GRID_HORIZONTAL_SIZE-1) grid[z][j] = FADING
}
}
}
}
var DeleteCompleteLines = Fn.new() {
for (j in GRID_VERTICAL_SIZE-2..0) {
while (grid[1][j] == FADING) {
for (i in 1...GRID_HORIZONTAL_SIZE-1) {
grid[i][j] = EMPTY
}
var j2 = j - 1
while (j2 >= 0) {
for (i2 in 1...GRID_HORIZONTAL_SIZE-1) {
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
}
}
j2 = j2 - 1
}
}
}
}
rl.initWindow(screenWidth, screenHeight, "Tetris")
InitGame.call()
rl.targetFPS = 60
while (!rl.windowShouldClose) { // Detect window close button or ESC key
UpdateDrawFrame.call()
}
UnloadGame.call()
rl.closeWindow()