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.
This commit is contained in:
189
games/lua_examples/air_hockey.lua
Normal file
189
games/lua_examples/air_hockey.lua
Normal file
@@ -0,0 +1,189 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user