All games had the same issue: renderer.circle() requires integer arguments, but float calculations produced non-integer coordinates. Fixed in all games using math.floor(x + 0.5) for proper rounding: - pong.lua: Ball position - air_hockey.lua: Puck position - asteroids.lua: Asteroid positions - ball.lua: Ball and trail positions, velocity line - breakout.lua: Ball position - flappy_bird.lua: Bird Y position - counter.lua: Last touch marker position - snake.lua: Food position (center calculation) - tic_tac_toe.lua: O circle center position Also fixed floating-point coordinate calculations in ball and line drawing to ensure all coordinates are integers.
222 lines
7.4 KiB
Lua
222 lines
7.4 KiB
Lua
-- NAME: Breakout
|
|
-- DESC: Break bricks with bouncing ball and paddle
|
|
|
|
-- Game states
|
|
local STATE_MENU = 0
|
|
local STATE_PLAYING = 1
|
|
local STATE_GAME_OVER = 2
|
|
local STATE_LEVEL_COMPLETE = 3
|
|
|
|
-- Game constants
|
|
local PADDLE_WIDTH = 40
|
|
local PADDLE_HEIGHT = 6
|
|
local BALL_RADIUS = 4
|
|
local BRICK_WIDTH = 16
|
|
local BRICK_HEIGHT = 6
|
|
local BRICK_COLS = 16
|
|
local BRICK_ROWS = 5
|
|
local BRICK_START_Y = 20
|
|
|
|
function init()
|
|
game.vars.state = STATE_MENU
|
|
game.vars.score = 0
|
|
game.vars.lives = 3
|
|
|
|
-- Paddle
|
|
game.vars.paddle_x = (game.width() / 2) - (PADDLE_WIDTH / 2)
|
|
|
|
-- Ball
|
|
game.vars.ball_x = game.width() / 2
|
|
game.vars.ball_y = game.height() - 30
|
|
game.vars.ball_vel_x = 2
|
|
game.vars.ball_vel_y = -3
|
|
|
|
-- Bricks (true = exists, false = broken)
|
|
game.vars.bricks = {}
|
|
game.vars.bricks_remaining = 0
|
|
|
|
-- Enable continuous updates
|
|
game.set_frame_updates(true)
|
|
|
|
print("Breakout 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 paddle input
|
|
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then
|
|
game.vars.paddle_x = math.max(0, math.min(game.width() - PADDLE_WIDTH, event.x - PADDLE_WIDTH / 2))
|
|
end
|
|
|
|
-- Update physics on frame tick
|
|
if event.type == INPUT.FRAME_TICK then
|
|
update_ball()
|
|
check_brick_collisions()
|
|
|
|
-- Check win
|
|
if game.vars.bricks_remaining <= 0 then
|
|
game.vars.state = STATE_LEVEL_COMPLETE
|
|
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
|
|
|
|
elseif state == STATE_LEVEL_COMPLETE 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 - 30, game.height() / 2 - 30, "BREAKOUT", true)
|
|
renderer.text(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true)
|
|
|
|
elseif state == STATE_PLAYING then
|
|
-- Draw bricks
|
|
for row = 1, BRICK_ROWS do
|
|
for col = 1, BRICK_COLS do
|
|
local idx = (row - 1) * BRICK_COLS + col
|
|
if game.vars.bricks[idx] then
|
|
local x = (col - 1) * BRICK_WIDTH
|
|
local y = BRICK_START_Y + (row - 1) * BRICK_HEIGHT
|
|
renderer.rect(x, y, BRICK_WIDTH, BRICK_HEIGHT, true, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Draw paddle
|
|
renderer.rect(game.vars.paddle_x, game.height() - PADDLE_HEIGHT - 2, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)
|
|
|
|
-- Draw ball (convert to integers)
|
|
renderer.circle(math.floor(game.vars.ball_x + 0.5), math.floor(game.vars.ball_y + 0.5), BALL_RADIUS, true, true)
|
|
|
|
-- Draw score and lives
|
|
renderer.text(5, 5, "Score: " .. tostring(game.vars.score), true)
|
|
renderer.text(game.width() - 50, 5, "Lives: " .. tostring(game.vars.lives), true)
|
|
|
|
elseif state == STATE_GAME_OVER then
|
|
renderer.text(game.width() / 2 - 40, game.height() / 2 - 20, "GAME OVER", true)
|
|
renderer.text(game.width() / 2 - 30, game.height() / 2, "Score: " .. tostring(game.vars.score), true)
|
|
renderer.text(game.width() / 2 - 60, game.height() / 2 + 20, "Tap to Menu", true)
|
|
|
|
elseif state == STATE_LEVEL_COMPLETE then
|
|
renderer.text(game.width() / 2 - 50, game.height() / 2 - 20, "LEVEL COMPLETE!", true)
|
|
renderer.text(game.width() / 2 - 30, game.height() / 2, "Score: " .. tostring(game.vars.score), true)
|
|
renderer.text(game.width() / 2 - 60, game.height() / 2 + 20, "Tap to Menu", true)
|
|
end
|
|
end
|
|
|
|
function update_ball()
|
|
-- Move ball
|
|
game.vars.ball_x = game.vars.ball_x + game.vars.ball_vel_x
|
|
game.vars.ball_y = game.vars.ball_y + game.vars.ball_vel_y
|
|
|
|
-- Bounce off walls
|
|
if game.vars.ball_x - BALL_RADIUS < 0 or game.vars.ball_x + BALL_RADIUS > game.width() then
|
|
game.vars.ball_vel_x = -game.vars.ball_vel_x
|
|
game.vars.ball_x = math.max(BALL_RADIUS, math.min(game.width() - BALL_RADIUS, game.vars.ball_x))
|
|
end
|
|
|
|
-- Bounce off top
|
|
if game.vars.ball_y - BALL_RADIUS < 0 then
|
|
game.vars.ball_vel_y = -game.vars.ball_vel_y
|
|
game.vars.ball_y = BALL_RADIUS
|
|
end
|
|
|
|
-- Check paddle collision
|
|
if game.vars.ball_y + BALL_RADIUS > game.height() - PADDLE_HEIGHT - 2 then
|
|
if game.vars.ball_x > game.vars.paddle_x and game.vars.ball_x < game.vars.paddle_x + PADDLE_WIDTH then
|
|
if game.vars.ball_vel_y > 0 then
|
|
game.vars.ball_vel_y = -game.vars.ball_vel_y
|
|
|
|
-- Add spin based on hit position
|
|
local hit_pos = (game.vars.ball_x - game.vars.paddle_x) / PADDLE_WIDTH
|
|
game.vars.ball_vel_x = (hit_pos - 0.5) * 4
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fall off bottom = lose life
|
|
if game.vars.ball_y > game.height() then
|
|
game.vars.lives = game.vars.lives - 1
|
|
if game.vars.lives <= 0 then
|
|
game.vars.state = STATE_GAME_OVER
|
|
else
|
|
reset_ball()
|
|
end
|
|
end
|
|
end
|
|
|
|
function check_brick_collisions()
|
|
for row = 1, BRICK_ROWS do
|
|
for col = 1, BRICK_COLS do
|
|
local idx = (row - 1) * BRICK_COLS + col
|
|
if game.vars.bricks[idx] then
|
|
local brick_x = (col - 1) * BRICK_WIDTH
|
|
local brick_y = BRICK_START_Y + (row - 1) * BRICK_HEIGHT
|
|
|
|
-- Simple AABB collision with ball
|
|
if game.vars.ball_x + BALL_RADIUS > brick_x and
|
|
game.vars.ball_x - BALL_RADIUS < brick_x + BRICK_WIDTH and
|
|
game.vars.ball_y + BALL_RADIUS > brick_y and
|
|
game.vars.ball_y - BALL_RADIUS < brick_y + BRICK_HEIGHT then
|
|
|
|
-- Destroy brick
|
|
game.vars.bricks[idx] = false
|
|
game.vars.bricks_remaining = game.vars.bricks_remaining - 1
|
|
game.vars.score = game.vars.score + 10
|
|
|
|
-- Bounce ball (simple: vertical bounce)
|
|
game.vars.ball_vel_y = -game.vars.ball_vel_y
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function reset_ball()
|
|
game.vars.ball_x = game.vars.paddle_x + PADDLE_WIDTH / 2
|
|
game.vars.ball_y = game.height() - 30
|
|
game.vars.ball_vel_x = 2
|
|
game.vars.ball_vel_y = -3
|
|
end
|
|
|
|
function reset_game()
|
|
game.vars.score = 0
|
|
game.vars.lives = 3
|
|
game.vars.paddle_x = (game.width() / 2) - (PADDLE_WIDTH / 2)
|
|
|
|
-- Create brick grid
|
|
game.vars.bricks = {}
|
|
game.vars.bricks_remaining = BRICK_ROWS * BRICK_COLS
|
|
for i = 1, BRICK_ROWS * BRICK_COLS do
|
|
game.vars.bricks[i] = true
|
|
end
|
|
|
|
reset_ball()
|
|
end
|