Tetris/Wren

From Rosetta Code
Translation of: Go
Library: raylib-wren

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()