Files
PicoCalc/Bin/PicoCalc SD/lua/asteroids.lua
2026-03-01 15:25:55 +08:00

261 lines
5.9 KiB
Lua

-- asteroids game
-- written by maple "mavica" syrup <maple@maple.pet>
local poly_ship = {{0, -10}, {5, 5}, {0, 0}, {-5, 5}, {0, -10}}
local poly_thrust = {{-2.5, 2,5}, {0, 4}, {2.5, 2.5}}
local poly_shot = {{0, 0}, {0, -5}}
local poly_rock = {{-1,0},{-0.8,-0.6},{-0.4,-0.5},
{0,-1},{0.5,-0.9},{0.3,-0.4},{0.8,-0.5},
{1,0},{0.7,0.4},{0.7,0.6},{0.3,0.8},{0,0.7},
{-0.3,0.9},{-0.7,0.8},{-0.7,0.5},{-1,0}}
local bg = colors.fromRGB(0,0,0)
local fg = colors.fromRGB(0,255,0)
local width = 320
local height = 320
local max_shots = 5
local shot_speed = 7
local rock_speed = 0.6
local rock_size = 30
local rock_step = 10
local player = {
x=width/2,
y=height/2,
accel = 0.06,
vx=0,
vy=0,
ang=0,
cdown=0,
inv=0
}
local shots = {}
local rocks = {}
local score = 0
local lives = 3
local last_frame = 0
local game_over = false
local function sign(number)
return (number > 0 and 1) or (number == 0 and 0) or -1
end
local function distance(x1, y1, x2, y2)
return math.sqrt((x2-x1)^2+(y2-y1)^2)
end
local function draw_rotated(shape, x, y, ang, scale, color)
processed = {}
local sin = math.sin(ang) * scale
local cos = math.cos(ang) * scale
for _,v in pairs(shape) do
local px = x + v[1] * cos - v[2] * sin
local py = y + v[1] * sin + v[2] * cos
table.insert(processed,math.floor(px+0.5))
table.insert(processed,math.floor(py+0.5))
end
draw.polygon(processed, color)
end
local shot_meta = {
erase = function(self)
draw_rotated(poly_shot, self.x, self.y, self.ang, 1, bg)
end,
update = function(self, k)
self.x = self.x + self.vx
self.y = self.y + self.vy
if self.x > width or self.x < 0 or self.y > height or self.y < 0 then
self:erase()
table.remove(shots,k)
else
for rockid,rock in pairs(rocks) do
if distance(self.x, self.y, rock.x, rock.y) < rock.scale then
rock:hit(rockid,self,k)
end
end
end
end,
draw = function(self)
draw_rotated(poly_shot, self.x, self.y, self.ang, 1, fg)
end
}
local rock_meta = {} --hoist
local rock_create = function(x, y, ang, size)
rock = {
x = x,
y = y,
ang = ang,
vx = math.sin(ang) * rock_speed,
vy = -math.cos(ang) * rock_speed,
scale = size
}
setmetatable(rock, { __index = rock_meta })
table.insert(rocks, rock)
end
local function level_init()
for i=1,4 do
rock_create(math.random(30,width-30), math.random(30,height-30), math.random()*6.28, 30)
end
player.inv = 50
end
rock_meta = {
erase = function(self)
draw_rotated(poly_rock, self.x, self.y, self.ang, self.scale, bg)
end,
update = function(self, k)
self.x = (self.x + self.vx) % width
self.y = (self.y + self.vy) % height
if player.inv == 0 and distance(self.x, self.y, player.x, player.y) < self.scale then
player:hit()
end
end,
hit = function(self, k, shot, sk)
sound.playPitch(1, 0.5, sound.drums.snare2)
self:erase()
score = score + ((rock_size + rock_step) - self.scale)
rock_speed = rock_speed + 0.06
if self.scale > rock_step then
rock_create(self.x, self.y, shot.ang - math.random()*3.14, self.scale-rock_step)
rock_create(self.x, self.y, shot.ang + math.random()*3.14, self.scale-rock_step)
end
table.remove(rocks, k)
table.remove(shots, sk)
if #rocks == 0 then
rock_speed = rock_speed - 0.6
score = score + 500
level_init()
end
end,
draw = function(self)
draw_rotated(poly_rock, self.x, self.y, self.ang, self.scale, fg)
end
}
function player:fire()
if self.cdown == 0 and #shots < max_shots then
sound.playPitch(1, 2, sound.drums.tom)
shot = {
x = self.x,
y = self.y,
ang = self.ang,
vx = math.sin(self.ang) * shot_speed,
vy = -math.cos(self.ang) * shot_speed,
}
setmetatable(shot, { __index = shot_meta })
table.insert(shots, shot)
self.cdown = 8
end
end
function player:erase()
draw_rotated(poly_ship, self.x, self.y, self.ang, 1, bg)
draw_rotated(poly_thrust, self.x, self.y, self.ang, 1, bg)
end
function player:update()
local angle_d = (keys.getState(keys.right) and 0.15 or 0) - (keys.getState(keys.left) and 0.15 or 0)
self.ang = self.ang + angle_d
if keys.getState(keys.up) then
self.vx = (self.vx + math.sin(self.ang) * self.accel)
self.vy = (self.vy - math.cos(self.ang) * self.accel)
elseif keys.getState(keys.down) then
self.vx = (self.vx - sign(self.vx) * self.accel)
self.vy = (self.vy - sign(self.vy) * self.accel)
end
self.x = (self.x + self.vx) % width
self.y = (self.y + self.vy) % height
if self.cdown > 0 then
self.cdown = self.cdown - 1
else
if keys.getState(keys.enter) then
self:fire()
end
end
if self.inv > 0 then self.inv = self.inv - 1 end
end
function player:hit()
sound.playPitch(1, 0.4, sound.drums.snare1)
if lives == 0 then game_over = true return end
lives = lives - 1
self.x = width/2
self.y = height/2
self.vx = 0
self.vy = 0
self.ang = 0
self.inv = 50
end
function player:draw()
if math.floor(self.inv / 3) % 2 == 0 then
draw_rotated(poly_ship, self.x, self.y, self.ang, 1, fg)
end
if keys.getState(keys.up) then
draw_rotated(poly_thrust, self.x, self.y, self.ang, 1, fg)
end
end
local function hud_draw()
draw.text(15,15," ",fg,bg)
draw.text(15,15,score,fg,bg)
for i = 0, 3 do
local color = i < lives and fg or bg
draw_rotated(poly_ship, width - 20 - 20*i, 20, 0, 1, color)
end
end
term.clear()
--draw.enableBuffer(true)
draw.rectFill(0, 0, width, height, bg)
level_init()
while true do
if keys.getState(keys.esc) or game_over then break end
local now = os.clock()
if now >= last_frame + 0.05 then
last_frame = now
-- erase
player:erase()
for _,v in pairs(shots) do v:erase() end
for _,v in pairs(rocks) do v:erase() end
-- move player
player:update()
for k,v in pairs(shots) do v:update(k) end
for k,v in pairs(rocks) do v:update(k) end
--draw.clear()
hud_draw()
player:draw()
for _,v in pairs(shots) do v:draw() end
for _,v in pairs(rocks) do v:draw() end
--draw.blitBuffer()
collectgarbage()
end
end
draw.enableBuffer(false)
if game_over then
term.clear()
print("game over!")
print("final score: "..score)
end