qbert.lua - split out common functionality into hopper.lua

This commit is contained in:
Nick Stokoe
2022-07-02 18:53:35 +01:00
parent 2367bf83cf
commit 3750a2fdf6
2 changed files with 207 additions and 200 deletions

207
qbert.lua
View File

@@ -1,211 +1,18 @@
mkHopper = require "hopper"
local height = 10
local width = height
local jumpHeight = 10
local jumpDist = 20
local jumpDirs = {
-- { se-axis, sw-axis, path x-multiplier, path reverse }
ne = {0, -1, -1, true},
nw = {-1, 0, 1, true},
se = {1, 0, 1, false},
sw = {0, 1, -1, false},
}
--[[
Jump equation, assuming y is up the screen and gravity down, chosen so
that vy and ay are +ve:
y = y0 - vy*t + ay*t*t (A)
x = x0 + vx*t (B)
Jump starts at t0, y0 and x0.
Jump ends at tend, yend and xend.
Jump peak is at ypeak.
These are all known.
We want to infer vx, ay and vy.
We can get vx thus:
xend = x0 + vx*tend
vx = (xend - x0) / tend (C)
Next to get ay and vy. For simplicity, let:
yend2 = yend - y0
ypeak2 = ypeak - y0
Using (A) to get vy:
y = y0 - vy*t + ay*t*t
vy*t = y0 - y + ay*t*t
vy*t = ay*t*t - yend2
vy = (ay*t*t - yend2)/t
vy = ay*t - yend2/t (D)
Using (D) at time tend:
vy = ay*tend - yend2/tend (E)
Using (D) at time tpeak:
vy = ay*tpeak - ypeak2/tpeak (F)
Unfortunately we still don't know tpeak, but we can eliminate it.
Knowing that dy/dt = 0 means:
dy/dt = -vy + 2*ay*t = 0
By definition the time at this point is tpeak, so:
vy = 2*ay*tpeak
tpeak = 0.5*vy/ay
So eliminating tpeak in (F):
vy = ay*tpeak - ypeak2/tpeak = ay*(0.5*vy/ay) - ypeak2/(0.5*vy/ay)
vy = 0.5*vy - ypeak2*ay/(0.5*vy)
vy = 0.5*vy - 2*ypeak2*ay/vy
2*vy = vy - 4*ypeak2*ay/vy
4*ypeak2*ay/vy = vy - 2*vy
4*ypeak2*ay/vy = -vy
4*ypeak2*ay = -vy^2
ay = -0.25*vy^2/ypeak2 (G)
vy = sqrt(-4*ypeak2*ay)
vy = 2*sqrt(-ypeak2*ay) (H)
Eliminating vy using (H) and (E):
vy = 2*sqrt(-ypeak2*ay) = ay*tend - yend2/tend
-4*ypeak2*ay = (ay*tend - yend2/tend)^2
-4*ypeak2*ay = ay^2*tend^2 + yend2^2/tend^2 - 2*ay*tend*yend2/tend
0 = ay^2*tend^2 + yend2^2/tend^2 - 2*ay*yend2 + 4*ypeak2*ay
0 = ay^2*tend^2 + 4*ypeak2*ay - 2*ay*yend2 + yend2^2/tend^2
0 = ay^2*tend^2 + ay*(4*ypeak2 - 2*yend2) + yend2^2/tend^2
This is a quadratic of the form:
0 = a*ay^2 + b*ay + c
Using the quadratic solution equation:
ay = (-b +/- sqrt(b^2 - 4*a*c)/(2*a)
With
a = tend^2
b = 4*ypeak2 - 2*yend2
c = yend2^2/tend^2
Having calculated ay, we can get vy from
vy = ay*tend - yend2/tend (E)
]]--
local function newJumpPath(jumpDist, jumpHeight)
local steps = 5
local vx = jumpDist/steps
local x0 = 0
local y0 = 0
local ypeak = -jumpHeight/2
local yend = jumpHeight
local ypeak2 = ypeak - y0
local yend2 = yend - y0
local tend = 5
local a = tend^2
local b = 4*ypeak2 - 2*yend2
local c = yend2^2/tend^2
local ay = 0.5*(-b + math.sqrt(b^2 - 4*a*c))/a
local vy = ay*tend - yend2/tend
local path = {}
-- print(vy, ay) # DEBUG
for t = 0,tend do
table.insert(path, {x0 + vx*t,
y0 - vy*t + ay*t*t})
end
--[[ DEBUG
for i in ipairs(path) do
print (i, path[i][1], path[i][2])
end
]]--
return path
local draw = function(x, y, se, sw)
love.graphics.setColor(128,128,0);
love.graphics.circle("fill", x, y, height)
end
local frameDuration = 0.1 -- millis
local function time2frame(time)
return 1 + math.floor(time / frameDuration)
end
local function jumpDelta(frame, dir, path)
local _, _, mx, isReverse = unpack(jumpDirs[dir])
local ox, oy = unpack(path[1]) -- path offset
if isReverse then -- reverse the path sequence
frame = 1 + #path - frame
ox, oy = unpack(path[#path])
end
local dx, dy = unpack(path[frame])
return dx*mx-ox*mx, dy-oy
local update = function(dt, se, sw, jumpTime, jumpFrame, jumpDir)
end
-- constructor
return function(arena)
assert(arena ~= nil, "arena must not be nil")
local jumpTime = nil
local jumpDir = "se"
local jumpFrame = 1
local se, sw = 0, 0
local jumpPath = newJumpPath(arena.getColWidth(), arena.getRowHeight())
return {
getWidth = function()
return width
end,
getHeight = function()
return height
end,
jump = function(dir)
if jumpTime ~= nil then
return -- already jumping
end
-- print("jump",dir) -- DEBUG
if jumpDirs[dir] then -- it's a valid direction
jumpTime = 0
jumpDir = dir
end
end,
draw = function()
local cx, cy = arena.qpos2Coords(se, sw)
local dx, dy = jumpDelta(jumpFrame, jumpDir, jumpPath)
love.graphics.setColor(128,128,0);
love.graphics.circle("fill", cx+dx, cy+dy, height)
end,
update = function(dt)
if jumpTime ~= nil then
jumpTime = jumpTime + dt
jumpFrame = time2frame(jumpTime)
if jumpFrame > #jumpPath then
jumpTime = nil -- finished
jumpFrame = 1
-- update qpos (se, sw)
local dse, dsw = unpack(jumpDirs[jumpDir])
-- print("update ", se, sw, dse, dsw) -- DEBUG
se = se + dse
sw = sw + dsw
end
end
end,
}
local self = mkHopper(arena, draw, update)
return self
end