Tetris/Wren
See Go entry for notes on playing. <lang ecmascript>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()</lang>