Skip to content

Instantly share code, notes, and snippets.

@lzralbu
Last active May 19, 2023 21:02
Show Gist options
  • Save lzralbu/3d05e49ccc7e59c1426bdbcb9816d526 to your computer and use it in GitHub Desktop.
Save lzralbu/3d05e49ccc7e59c1426bdbcb9816d526 to your computer and use it in GitHub Desktop.
Lua/LÖVE code for Flat Bird, yet another clone of Flappy Bird but with minimalist art.
---------------------------
-- some useful constants --
---------------------------
local CANVAS_WIDTH = 450
local CANVAS_HEIGHT = 600
local colors = {
WHITE = { 1, 1, 1 },
SKY_BLUE = { 135 / 255, 206 / 255, 235 / 255 },
GREEN = { 0, 0.75, 0 },
ORANGE = { 1, 165 / 255, 0 },
BROWN = { 0.58, 0.29, 0 }
}
local GRAVITY = 120
----------
-- bird --
----------
local bird = {}
local function birdInit()
bird.width = 25
bird.height = 25
bird.x = CANVAS_WIDTH / 2 - 50
bird.y = (CANVAS_HEIGHT - bird.height) / 2
bird.min_speed = 25
bird.max_speed = 250
bird.speed = bird.min_speed
bird.bump_height = 25
bird.color = colors.ORANGE
bird.alive = true
end
local function birdReset()
bird.y = (CANVAS_HEIGHT - bird.height) / 2
bird.speed = bird.min_speed
bird.alive = true
end
local function birdUpdate(dt)
bird.speed = bird.speed + GRAVITY * dt
if bird.speed > bird.max_speed then
bird.speed = bird.max_speed
end
bird.y = bird.y + bird.speed * dt
end
-----------
-- pipes --
-----------
local pipes = {}
local function pipesInit()
pipes.clock = 0 -- how much time(in seconds) has elapsed since the last pipe was generated
pipes.gen_rate = 3 -- how much time(in seconds) the game should waiting before generating another pipe
end
local function pipesReset()
pipes.clock = 0
while #pipes > 0 do
table.remove(pipes, 1)
end
end
local function pipeCreate()
local pipe = {}
pipe.height1 = math.random(100, CANVAS_HEIGHT - 250)
pipe.empty_space = 100
pipe.width = 50
pipe.height2 = CANVAS_HEIGHT - pipe.height1 - pipe.empty_space
pipe.x = CANVAS_WIDTH
pipe.y = 0
pipe.speed = -100
pipe.color = colors.GREEN
pipe.behind_bird = false
return pipe
end
local function pipesUpdate(dt)
pipes.clock = pipes.clock + dt
if pipes.clock > pipes.gen_rate then
pipes.clock = 0
table.insert(pipes, pipeCreate())
end
-- move all the pipes a bit to the left
for k, pipe in ipairs(pipes) do
pipe.x = pipe.x + pipe.speed * dt
end
-- count how many pipes are out of screen
local dead_pipes_count = 0
for k, pipe in ipairs(pipes) do
if pipe.x < -pipe.width then
dead_pipes_count = dead_pipes_count + 1
else
break
end
end
-- remove each of the first dead_pipes_count pipes
for _ = 1, dead_pipes_count do
table.remove(pipes, 1)
end
end
----------------
-- scoreboard --
----------------
local scoreboard = {}
local function scoreboardInit()
scoreboard.current_score = 0
scoreboard.highest_score = 0
scoreboard.color = colors.WHITE
scoreboard.font = love.graphics.newFont(36)
scoreboard.x = (CANVAS_WIDTH - scoreboard.font:getWidth('0')) / 2
scoreboard.y = 30
end
local function scoreboardReset()
scoreboard.current_score = 0
scoreboard.x = (CANVAS_WIDTH - scoreboard.font:getWidth('0')) / 2
end
local function scoreboardUpdate(dt)
local bird_center_x = bird.x + bird.width / 2
for k, pipe in ipairs(pipes) do
local pipe_center_x = pipe.x + pipe.width / 2
if pipe_center_x < bird_center_x and not pipe.behind_bird then
scoreboard.current_score = scoreboard.current_score + 1
if scoreboard.current_score > scoreboard.highest_score then
scoreboard.highest_score = scoreboard.current_score
end
pipe.behind_bird = true
end
end
scoreboard.x = (CANVAS_WIDTH -
scoreboard.font:getWidth(
tostring(scoreboard.current_score)
)) / 2
end
-----------
-- floor --
-----------
local floor = {}
local function floorInit()
floor.width = CANVAS_WIDTH
floor.height = 50
floor.x = 0
floor.y = CANVAS_HEIGHT - floor.height
floor.color = colors.BROWN
end
---------------------------
-- some helper functions --
---------------------------
local game_over_clock = 0
local game_over_duration = 3
local function gameInit()
birdInit()
pipesInit()
scoreboardInit()
floorInit()
end
local function gameReset()
birdReset()
pipesReset()
scoreboardReset()
game_over_clock = 0
end
-- detect intersection between two rectangles
local function hasIntersection(x1, y1, w1, h1, x2, y2, w2, h2)
return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1
end
--------------------
-- LÖVE callbacks --
--------------------
function love.load()
math.randomseed(os.time())
gameInit()
love.graphics.setFont(scoreboard.font)
end
function love.update(dt)
if bird.alive then
birdUpdate(dt)
pipesUpdate(dt)
-- check collision between bird and floor
if hasIntersection(
bird.x, bird.y, bird.width, bird.height, floor.x, floor.y,
floor.width, floor.height
) then
bird.alive = false
end
-- check collision between bird and pipes
for k, pipe in ipairs(pipes) do
if hasIntersection(
bird.x, bird.y, bird.width, bird.height, pipe.x, pipe.y,
pipe.width, pipe.height1
) or hasIntersection(
bird.x, bird.y, bird.width, bird.height, pipe.x,
pipe.y + pipe.height1 + pipe.empty_space, pipe.width,
pipe.height2
) then
bird.alive = false
end
end
if not bird.alive then
return
end
scoreboardUpdate(dt)
else
game_over_clock = game_over_clock + dt
if game_over_clock > game_over_duration then
gameReset()
end
end
end
function love.draw()
-- draw sky
love.graphics.clear(colors.SKY_BLUE)
-- draw bird
love.graphics.setColor(bird.color)
love.graphics.rectangle('fill', bird.x, bird.y, bird.width, bird.height)
-- draw pipes
for k, pipe in ipairs(pipes) do
love.graphics.setColor(pipe.color)
love.graphics
.rectangle('fill', pipe.x, pipe.y, pipe.width, pipe.height1)
love.graphics.rectangle(
'fill', pipe.x, pipe.y + pipe.height1 + pipe.empty_space,
pipe.width, pipe.height2
)
end
-- draw scoreboard
love.graphics.setColor(scoreboard.color)
love.graphics.print(
tostring(scoreboard.current_score), scoreboard.x, scoreboard.y
)
-- draw floor
love.graphics.setColor(floor.color)
love.graphics.rectangle('fill', floor.x, floor.y, floor.width, floor.height)
-- draw game over message if bird is dead
if not bird.alive then
love.graphics.setColor(scoreboard.color)
local line1 = "GAME OVER"
love.graphics.print(
line1, (CANVAS_WIDTH - scoreboard.font:getWidth(line1)) / 2,
CANVAS_HEIGHT / 3
)
local line2 = "Best: " .. tostring(scoreboard.highest_score)
love.graphics.print(
line2, (CANVAS_WIDTH - scoreboard.font:getWidth(line2)) / 2,
CANVAS_HEIGHT / 3 + scoreboard.font:getHeight()
)
end
end
function love.keypressed(key, scancode, isrepeat)
if (scancode == 'space' or scancode == 'up' or scancode == 'w') and
bird.alive then
bird.y = bird.y - bird.bump_height
bird.speed = bird.min_speed
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment