Files
basic1/games/lua_examples/tetris.lua
2026-02-13 15:31:21 -05:00

315 lines
9.0 KiB
Lua

-- NAME: Tetris
-- DESC: Stack falling blocks, clear lines
-- Game states
local STATE_MENU = 0
local STATE_PLAYING = 1
local STATE_GAME_OVER = 2
-- Game constants
local GRID_WIDTH = 10
local GRID_HEIGHT = 20
local CELL_SIZE = 8
local SPAWN_RATE = 30 -- Frames before piece drops
-- Tetromino shapes (4 orientations each)
local TETROMINOS = {
-- I piece
{
{{0, 0}, {1, 0}, {2, 0}, {3, 0}},
{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
{{0, 0}, {1, 0}, {2, 0}, {3, 0}},
{{0, 0}, {0, 1}, {0, 2}, {0, 3}}
},
-- O piece
{
{{0, 0}, {1, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {0, 1}, {1, 1}}
},
-- T piece
{
{{1, 0}, {0, 1}, {1, 1}, {2, 1}},
{{1, 0}, {0, 1}, {1, 1}, {1, 2}},
{{0, 1}, {1, 1}, {2, 1}, {1, 2}},
{{1, 0}, {1, 1}, {2, 1}, {1, 2}}
},
-- S piece
{
{{1, 0}, {2, 0}, {0, 1}, {1, 1}},
{{0, 0}, {0, 1}, {1, 1}, {1, 2}},
{{1, 0}, {2, 0}, {0, 1}, {1, 1}},
{{0, 0}, {0, 1}, {1, 1}, {1, 2}}
},
-- Z piece
{
{{0, 0}, {1, 0}, {1, 1}, {2, 1}},
{{1, 0}, {0, 1}, {1, 1}, {0, 2}},
{{0, 0}, {1, 0}, {1, 1}, {2, 1}},
{{1, 0}, {0, 1}, {1, 1}, {0, 2}}
},
-- J piece
{
{{0, 0}, {0, 1}, {1, 1}, {2, 1}},
{{1, 0}, {2, 0}, {1, 1}, {1, 2}},
{{0, 1}, {1, 1}, {2, 1}, {2, 0}},
{{1, 0}, {1, 1}, {0, 2}, {1, 2}}
},
-- L piece
{
{{2, 0}, {0, 1}, {1, 1}, {2, 1}},
{{1, 0}, {1, 1}, {1, 2}, {2, 2}},
{{0, 1}, {1, 1}, {2, 1}, {0, 0}},
{{0, 0}, {1, 0}, {1, 1}, {1, 2}}
}
}
function init()
game.vars.state = STATE_MENU
game.vars.score = 0
game.vars.level = 1
game.vars.lines = 0
-- Grid (0 = empty, 1 = filled)
game.vars.grid = {}
for y = 1, GRID_HEIGHT do
game.vars.grid[y] = {}
for x = 1, GRID_WIDTH do
game.vars.grid[y][x] = 0
end
end
-- Current piece
game.vars.piece = nil
game.vars.piece_x = 0
game.vars.piece_y = 0
game.vars.piece_type = 0
game.vars.piece_rotation = 0
-- Animation
game.vars.frame_count = 0
game.vars.clear_rows = {}
game.vars.clearing = false
-- Enable continuous updates
game.set_frame_updates(true)
print("Tetris initialized")
end
function update(event)
local state = game.vars.state
if state == STATE_MENU then
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then
reset_game()
game.vars.state = STATE_PLAYING
return true
end
elseif state == STATE_PLAYING then
-- Handle input
if event.type == INPUT.TOUCH_DOWN then
if event.x < game.width() / 2 then
-- Left side: move left
if can_move(game.vars.piece, game.vars.piece_x - 1, game.vars.piece_y, game.vars.piece_rotation) then
game.vars.piece_x = game.vars.piece_x - 1
end
else
-- Right side: move right
if can_move(game.vars.piece, game.vars.piece_x + 1, game.vars.piece_y, game.vars.piece_rotation) then
game.vars.piece_x = game.vars.piece_x + 1
end
end
return true
end
-- Update physics on frame tick
if event.type == INPUT.FRAME_TICK then
game.vars.frame_count = game.vars.frame_count + 1
if game.vars.clearing then
-- Clear animation
if game.vars.frame_count % 10 == 0 then
clear_lines()
game.vars.clearing = false
end
else
-- Drop piece
if game.vars.frame_count >= SPAWN_RATE then
game.vars.frame_count = 0
if can_move(game.vars.piece, game.vars.piece_x, game.vars.piece_y + 1, game.vars.piece_rotation) then
game.vars.piece_y = game.vars.piece_y + 1
else
-- Lock piece
lock_piece()
-- Check for complete lines
local complete = check_complete_lines()
if #complete > 0 then
game.vars.clear_rows = complete
game.vars.clearing = true
else
spawn_piece()
end
end
end
end
return true
end
elseif state == STATE_GAME_OVER then
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then
game.vars.state = STATE_MENU
return true
end
end
return false
end
function draw()
renderer.clear(false) -- Black background
local state = game.vars.state
if state == STATE_MENU then
renderer.text_scaled(game.width() / 2 - 20, game.height() / 2 - 30, "TETRIS", true, 2)
renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true, 2)
elseif state == STATE_PLAYING or state == STATE_GAME_OVER then
-- Draw grid
local start_x = 20
local start_y = 15
for y = 1, GRID_HEIGHT do
for x = 1, GRID_WIDTH do
if game.vars.grid[y][x] == 1 then
renderer.rect(start_x + (x - 1) * CELL_SIZE, start_y + (y - 1) * CELL_SIZE, CELL_SIZE, CELL_SIZE, true, true)
end
end
end
-- Draw current piece
if game.vars.piece then
for _, block in ipairs(game.vars.piece) do
local x = start_x + (game.vars.piece_x + block[1]) * CELL_SIZE
local y = start_y + (game.vars.piece_y + block[2]) * CELL_SIZE
renderer.rect(x, y, CELL_SIZE, CELL_SIZE, true, true)
end
end
-- Draw score
renderer.text_scaled(game.width() - 50, 10, "Score: " .. tostring(game.vars.score), true, 2)
renderer.text_scaled(game.width() - 50, 20, "Lines: " .. tostring(game.vars.lines), true, 2)
if state == STATE_GAME_OVER then
renderer.text_scaled(game.width() / 2 - 40, game.height() / 2, "GAME OVER", true, 2)
renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, "Tap to Menu", true, 2)
end
end
end
function spawn_piece()
game.vars.piece_type = math.random(1, #TETROMINOS)
game.vars.piece_rotation = 1
game.vars.piece = TETROMINOS[game.vars.piece_type][game.vars.piece_rotation]
game.vars.piece_x = 3
game.vars.piece_y = 0
-- Check if game over
if not can_move(game.vars.piece, game.vars.piece_x, game.vars.piece_y, game.vars.piece_rotation) then
game.vars.state = STATE_GAME_OVER
end
end
function lock_piece()
if not game.vars.piece then return end
for _, block in ipairs(game.vars.piece) do
local x = game.vars.piece_x + block[1] + 1
local y = game.vars.piece_y + block[2] + 1
if y >= 1 and y <= GRID_HEIGHT and x >= 1 and x <= GRID_WIDTH then
game.vars.grid[y][x] = 1
end
end
end
function can_move(piece, x, y, rotation)
if not piece then return false end
for _, block in ipairs(piece) do
local grid_x = x + block[1] + 1
local grid_y = y + block[2] + 1
if grid_x < 1 or grid_x > GRID_WIDTH or grid_y < 1 or grid_y > GRID_HEIGHT then
return false
end
if game.vars.grid[grid_y][grid_x] == 1 then
return false
end
end
return true
end
function check_complete_lines()
local complete = {}
for y = 1, GRID_HEIGHT do
local full = true
for x = 1, GRID_WIDTH do
if game.vars.grid[y][x] == 0 then
full = false
break
end
end
if full then
table.insert(complete, y)
end
end
return complete
end
function clear_lines()
for _, y in ipairs(game.vars.clear_rows) do
-- Remove line
table.remove(game.vars.grid, y)
-- Add empty line at top
table.insert(game.vars.grid, 1, {})
for x = 1, GRID_WIDTH do
game.vars.grid[1][x] = 0
end
end
game.vars.score = game.vars.score + (#game.vars.clear_rows * 100)
game.vars.lines = game.vars.lines + #game.vars.clear_rows
game.vars.clear_rows = {}
spawn_piece()
end
function reset_game()
game.vars.score = 0
game.vars.level = 1
game.vars.lines = 0
game.vars.frame_count = 0
game.vars.clearing = false
-- Clear grid
for y = 1, GRID_HEIGHT do
for x = 1, GRID_WIDTH do
game.vars.grid[y][x] = 0
end
end
spawn_piece()
end