169 lines
5.1 KiB
Lua
169 lines
5.1 KiB
Lua
-- NAME: Flappy Bird
|
|
-- DESC: Tap to flap, avoid pipes
|
|
|
|
-- Game states
|
|
local STATE_MENU = 0
|
|
local STATE_PLAYING = 1
|
|
local STATE_GAME_OVER = 2
|
|
|
|
-- Game constants
|
|
local BIRD_SIZE = 8
|
|
local BIRD_GRAVITY = 0.3
|
|
local BIRD_FLAP_POWER = -7
|
|
local PIPE_WIDTH = 20
|
|
local PIPE_GAP = 50
|
|
local PIPE_SPEED = 3
|
|
local SPAWN_RATE = 80 -- Frames between pipe spawns
|
|
|
|
function init()
|
|
game.vars.state = STATE_MENU
|
|
game.vars.score = 0
|
|
game.vars.frame_count = 0
|
|
|
|
-- Bird
|
|
game.vars.bird_y = game.height() / 2
|
|
game.vars.bird_vel = 0
|
|
|
|
-- Pipes (array of {x, gap_y})
|
|
game.vars.pipes = {}
|
|
game.vars.last_pipe_frame = 0
|
|
|
|
-- Enable continuous updates
|
|
game.set_frame_updates(true)
|
|
|
|
print("Flappy Bird 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 flap input
|
|
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then
|
|
game.vars.bird_vel = BIRD_FLAP_POWER
|
|
end
|
|
|
|
-- Update physics on frame tick
|
|
if event.type == INPUT.FRAME_TICK then
|
|
update_bird()
|
|
update_pipes()
|
|
check_collisions()
|
|
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 - 40, game.height() / 2 - 30, "FLAPPY BIRD", true, 2)
|
|
renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true, 2)
|
|
|
|
elseif state == STATE_PLAYING then
|
|
-- Draw bird (convert to integer)
|
|
renderer.circle(20, math.floor(game.vars.bird_y + 0.5), BIRD_SIZE, true, true)
|
|
|
|
-- Draw pipes
|
|
for i = 1, #game.vars.pipes do
|
|
local pipe = game.vars.pipes[i]
|
|
|
|
-- Top pipe
|
|
renderer.rect(pipe.x, 0, PIPE_WIDTH, pipe.gap_y, true, true)
|
|
|
|
-- Bottom pipe
|
|
local bottom_start = pipe.gap_y + PIPE_GAP
|
|
renderer.rect(pipe.x, bottom_start, PIPE_WIDTH, game.height() - bottom_start, true, true)
|
|
end
|
|
|
|
-- Draw score
|
|
renderer.text_scaled(10, 10, "Score: " .. tostring(game.vars.score), true, 2)
|
|
|
|
elseif state == STATE_GAME_OVER then
|
|
renderer.text_scaled(game.width() / 2 - 40, game.height() / 2 - 30, "GAME OVER", true, 2)
|
|
renderer.text_scaled(game.width() / 2 - 30, game.height() / 2, "Score: " .. tostring(game.vars.score), true, 2)
|
|
renderer.text_scaled(game.width() / 2 - 60, game.height() / 2 + 20, "Tap to Restart", true, 2)
|
|
end
|
|
end
|
|
|
|
function update_bird()
|
|
-- Apply gravity
|
|
game.vars.bird_vel = game.vars.bird_vel + BIRD_GRAVITY
|
|
game.vars.bird_y = game.vars.bird_y + game.vars.bird_vel
|
|
|
|
-- Clamp to screen (game over if hit top/bottom)
|
|
if game.vars.bird_y - BIRD_SIZE < 0 or game.vars.bird_y + BIRD_SIZE > game.height() then
|
|
game.vars.state = STATE_GAME_OVER
|
|
end
|
|
end
|
|
|
|
function update_pipes()
|
|
game.vars.frame_count = game.vars.frame_count + 1
|
|
|
|
-- Spawn new pipe
|
|
if game.vars.frame_count - game.vars.last_pipe_frame >= SPAWN_RATE then
|
|
local gap_y = math.random(30, game.height() - PIPE_GAP - 30)
|
|
table.insert(game.vars.pipes, {x = game.width(), gap_y = gap_y})
|
|
game.vars.last_pipe_frame = game.vars.frame_count
|
|
end
|
|
|
|
-- Move and remove off-screen pipes
|
|
for i = #game.vars.pipes, 1, -1 do
|
|
local pipe = game.vars.pipes[i]
|
|
pipe.x = pipe.x - PIPE_SPEED
|
|
|
|
-- Score when pipe passes bird
|
|
if pipe.x == 20 then
|
|
game.vars.score = game.vars.score + 1
|
|
end
|
|
|
|
-- Remove if off-screen
|
|
if pipe.x < -PIPE_WIDTH then
|
|
table.remove(game.vars.pipes, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
function check_collisions()
|
|
-- Check collision with pipes
|
|
for i = 1, #game.vars.pipes do
|
|
local pipe = game.vars.pipes[i]
|
|
|
|
-- Bird hitbox: circle at (20, bird_y) with radius BIRD_SIZE
|
|
-- Check if within pipe's X range
|
|
if 20 + BIRD_SIZE > pipe.x and 20 - BIRD_SIZE < pipe.x + PIPE_WIDTH then
|
|
-- Check Y collision
|
|
if game.vars.bird_y - BIRD_SIZE < pipe.gap_y or
|
|
game.vars.bird_y + BIRD_SIZE > pipe.gap_y + PIPE_GAP then
|
|
game.vars.state = STATE_GAME_OVER
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function reset_game()
|
|
game.vars.score = 0
|
|
game.vars.bird_y = game.height() / 2
|
|
game.vars.bird_vel = 0
|
|
game.vars.pipes = {}
|
|
game.vars.frame_count = 0
|
|
game.vars.last_pipe_frame = 0
|
|
end
|