Faithful recreation of Atari Asteroids (1979) with vector wireframe aesthetic. Auto-detects Omarchy system theme and font on launch. Features: - Inertia physics (zero friction), rotate/thrust/fire/hyperspace controls - 3 asteroid sizes that split on destroy (large→medium→small) - Large and small UFO saucers with AI (random vs aimed shooting) - Screen wrapping for ship/asteroids/saucers, bullets expire at edges - Ship death fragments, explosion particles - Iconic heartbeat that speeds up as wave clears - Wave progression (4→11 asteroids, speed ramps) - 3 lives, extra life every 10k points - Persistent high scores with 3-letter initial entry - Procedural sound effects (beat, thrust, fire, explosions, saucer drone) - Full-screen scaling, system font detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
85 lines
2 KiB
Lua
85 lines
2 KiB
Lua
local World = {
|
|
GAME_W = 1024,
|
|
GAME_H = 768,
|
|
visibleH = 768,
|
|
scale = 1,
|
|
offsetX = 0,
|
|
offsetY = 0,
|
|
state = "title",
|
|
wave = 1,
|
|
score = 0,
|
|
highScore = 0,
|
|
lives = 3,
|
|
nextExtraLife = 10000,
|
|
gameOverTimer = 0,
|
|
screenW = 0,
|
|
screenH = 0,
|
|
respawnTimer = 0,
|
|
waveTimer = 0,
|
|
}
|
|
|
|
function World.resize(w, h)
|
|
if not w or not h then w, h = love.graphics.getDimensions() end
|
|
World.screenW = w
|
|
World.screenH = h
|
|
World.scale = w / World.GAME_W
|
|
World.visibleH = h / World.scale
|
|
World.offsetX = 0
|
|
World.offsetY = h - (World.GAME_H * World.scale)
|
|
end
|
|
|
|
function World.ensureScale()
|
|
local w, h = love.graphics.getDimensions()
|
|
if w ~= World.screenW or h ~= World.screenH then
|
|
World.resize(w, h)
|
|
local Fonts = require("rendering.fonts")
|
|
Fonts.init(World.scale)
|
|
end
|
|
end
|
|
|
|
function World.toGame(sx, sy)
|
|
local gx = (sx - World.offsetX) / World.scale
|
|
local gy = (sy - World.offsetY) / World.scale
|
|
return gx, gy
|
|
end
|
|
|
|
function World.toScreen(gx, gy)
|
|
return gx * World.scale + World.offsetX, gy * World.scale + World.offsetY
|
|
end
|
|
|
|
function World.visibleTop()
|
|
return World.GAME_H - World.visibleH
|
|
end
|
|
|
|
function World.wrapX(x)
|
|
return (x % World.GAME_W + World.GAME_W) % World.GAME_W
|
|
end
|
|
|
|
function World.wrapY(y)
|
|
return (y % World.GAME_H + World.GAME_H) % World.GAME_H
|
|
end
|
|
|
|
function World.wrappedDist(x1, y1, x2, y2)
|
|
local dx = x2 - x1
|
|
local dy = y2 - y1
|
|
if math.abs(dx) > World.GAME_W / 2 then
|
|
dx = dx - World.GAME_W * (dx > 0 and 1 or -1)
|
|
end
|
|
if math.abs(dy) > World.GAME_H / 2 then
|
|
dy = dy - World.GAME_H * (dy > 0 and 1 or -1)
|
|
end
|
|
return dx, dy, math.sqrt(dx*dx + dy*dy)
|
|
end
|
|
|
|
function World.addScore(points)
|
|
World.score = World.score + points
|
|
while World.score >= World.nextExtraLife do
|
|
World.lives = World.lives + 1
|
|
World.nextExtraLife = World.nextExtraLife + 10000
|
|
end
|
|
if World.score > World.highScore then
|
|
World.highScore = World.score
|
|
end
|
|
end
|
|
|
|
return World
|