-- 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