Files
basic1/games/lua_examples/air_hockey.lua
Adolfo Reyna 50793ac535 feat: add final 4 games for basic1 console
- 2048: Grid merging, directional movement, score tracking, win/draw detection
- Tic-Tac-Toe: Minimax AI opponent, perfect play, win detection
- Lunar Lander: Gravity + thrust physics, fuel management, landing validation
- Air Hockey: Refined paddle physics, puck acceleration, goal detection

All games tested for state transitions, collision logic, and win conditions.
Suite now complete with 10 classic games ready for SD card deployment.
2026-02-12 19:40:42 -05:00

190 lines
6.7 KiB
Lua

-- NAME: Air Hockey
-- DESC: Fast-paced 2-player hockey
-- Game states
local STATE_MENU = 0
local STATE_PLAYING = 1
local STATE_GAME_OVER = 2
-- Game constants
local PADDLE_WIDTH = 6
local PADDLE_HEIGHT = 35
local PUCK_RADIUS = 3
local MAX_SCORE = 7
local PUCK_SPEED = 4
function init()
game.vars.state = STATE_MENU
-- Left paddle (player 1)
game.vars.paddle_left_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)
game.vars.paddle_left_score = 0
-- Right paddle (player 2)
game.vars.paddle_right_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)
game.vars.paddle_right_score = 0
-- Puck
game.vars.puck_x = game.width() / 2
game.vars.puck_y = game.height() / 2
game.vars.puck_vel_x = PUCK_SPEED
game.vars.puck_vel_y = 1
-- Enable continuous updates
game.set_frame_updates(true)
print("Air Hockey 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 via touch
if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.TOUCH_MOVE then
if event.x < game.width() / 2 then
-- Left paddle
game.vars.paddle_left_y = math.max(0, math.min(game.height() - PADDLE_HEIGHT, event.y - PADDLE_HEIGHT / 2))
else
-- Right paddle
game.vars.paddle_right_y = math.max(0, math.min(game.height() - PADDLE_HEIGHT, event.y - PADDLE_HEIGHT / 2))
end
end
-- Update physics on frame tick
if event.type == INPUT.FRAME_TICK then
update_puck()
check_collisions()
-- Check win
if game.vars.paddle_left_score >= MAX_SCORE or game.vars.paddle_right_score >= MAX_SCORE then
game.vars.state = STATE_GAME_OVER
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 - 35, game.height() / 2 - 30, "AIR HOCKEY", true)
renderer.text(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true)
renderer.text(game.width() / 2 - 60, game.height() / 2 + 20, "First to " .. tostring(MAX_SCORE), true)
elseif state == STATE_PLAYING or state == STATE_GAME_OVER then
-- Draw center line
for y = 0, game.height(), 4 do
renderer.pixel(game.width() / 2, y, true)
end
-- Draw goal areas (top/bottom highlights)
renderer.line(0, 5, game.width(), 5, true, 1)
renderer.line(0, game.height() - 5, game.width(), game.height() - 5, true, 1)
-- Draw paddles
renderer.rect(5, game.vars.paddle_left_y, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)
renderer.rect(game.width() - 5 - PADDLE_WIDTH, game.vars.paddle_right_y, PADDLE_WIDTH, PADDLE_HEIGHT, true, true)
-- Draw puck
renderer.circle(game.vars.puck_x, game.vars.puck_y, PUCK_RADIUS, true, true)
-- Draw scores
renderer.text(game.width() / 2 - 30, 5, tostring(game.vars.paddle_left_score), true)
renderer.text(game.width() / 2 + 20, 5, tostring(game.vars.paddle_right_score), true)
if state == STATE_GAME_OVER then
local winner = game.vars.paddle_left_score > game.vars.paddle_right_score and "Player 1" or "Player 2"
renderer.text(game.width() / 2 - 50, game.height() / 2 - 20, "GAME OVER", true)
renderer.text(game.width() / 2 - 40, game.height() / 2, winner .. " Wins!", true)
renderer.text(game.width() / 2 - 60, game.height() / 2 + 20, "Tap to Menu", true)
end
end
end
function update_puck()
-- Move puck
game.vars.puck_x = game.vars.puck_x + game.vars.puck_vel_x
game.vars.puck_y = game.vars.puck_y + game.vars.puck_vel_y
-- Bounce off top/bottom
if game.vars.puck_y - PUCK_RADIUS < 0 or game.vars.puck_y + PUCK_RADIUS > game.height() then
game.vars.puck_vel_y = -game.vars.puck_vel_y
game.vars.puck_y = math.max(PUCK_RADIUS, math.min(game.height() - PUCK_RADIUS, game.vars.puck_y))
end
-- Goal: left side
if game.vars.puck_x < 0 then
game.vars.paddle_right_score = game.vars.paddle_right_score + 1
reset_puck()
end
-- Goal: right side
if game.vars.puck_x > game.width() then
game.vars.paddle_left_score = game.vars.paddle_left_score + 1
reset_puck()
end
end
function check_collisions()
-- Left paddle collision
if game.vars.puck_x - PUCK_RADIUS < 5 + PADDLE_WIDTH then
if game.vars.puck_y > game.vars.paddle_left_y and game.vars.puck_y < game.vars.paddle_left_y + PADDLE_HEIGHT then
if game.vars.puck_vel_x < 0 then
game.vars.puck_vel_x = -game.vars.puck_vel_x + 0.5 -- Speed up slightly
-- Add spin
local hit_pos = (game.vars.puck_y - game.vars.paddle_left_y) / PADDLE_HEIGHT
game.vars.puck_vel_y = (hit_pos - 0.5) * 6
end
end
end
-- Right paddle collision
if game.vars.puck_x + PUCK_RADIUS > game.width() - 5 - PADDLE_WIDTH then
if game.vars.puck_y > game.vars.paddle_right_y and game.vars.puck_y < game.vars.paddle_right_y + PADDLE_HEIGHT then
if game.vars.puck_vel_x > 0 then
game.vars.puck_vel_x = -game.vars.puck_vel_x - 0.5 -- Speed up slightly
-- Add spin
local hit_pos = (game.vars.puck_y - game.vars.paddle_right_y) / PADDLE_HEIGHT
game.vars.puck_vel_y = (hit_pos - 0.5) * 6
end
end
end
end
function reset_puck()
game.vars.puck_x = game.width() / 2
game.vars.puck_y = game.height() / 2
game.vars.puck_vel_x = PUCK_SPEED * (math.random() > 0.5 and 1 or -1)
game.vars.puck_vel_y = (math.random() - 0.5) * 2
end
function reset_game()
game.vars.paddle_left_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)
game.vars.paddle_left_score = 0
game.vars.paddle_right_y = (game.height() / 2) - (PADDLE_HEIGHT / 2)
game.vars.paddle_right_score = 0
reset_puck()
end