feat: add 6 lua games for basic1 console
- Pong: 2-player paddle and ball game with spin mechanics - Flappy Bird: gravity physics, obstacle avoidance - Breakout: paddle control, brick grid, collision detection - Simon Says: sequence memory, animation timing - Memory Match: pair matching, flip animations, grid layout - Tetris: falling blocks, grid system, line clearing - Asteroids: vector math, rotation, projectiles, enemy spawning All games follow API conventions with state machines, touch input, frame-based animation, and persistent game.vars state management.
This commit is contained in:
314
games/lua_examples/tetris.lua
Normal file
314
games/lua_examples/tetris.lua
Normal file
@@ -0,0 +1,314 @@
|
||||
-- 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(game.width() / 2 - 20, game.height() / 2 - 30, "TETRIS", true)
|
||||
renderer.text(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true)
|
||||
|
||||
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(game.width() - 50, 10, "Score: " .. tostring(game.vars.score), true)
|
||||
renderer.text(game.width() - 50, 20, "Lines: " .. tostring(game.vars.lines), true)
|
||||
|
||||
if state == STATE_GAME_OVER then
|
||||
renderer.text(game.width() / 2 - 40, game.height() / 2, "GAME OVER", true)
|
||||
renderer.text(game.width() / 2 - 50, game.height() / 2 + 20, "Tap to Menu", true)
|
||||
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
|
||||
Reference in New Issue
Block a user