qbert.lua - split out common functionality into hopper.lua
This commit is contained in:
200
hopper.lua
Normal file
200
hopper.lua
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
-- constructor
|
||||||
|
return function(arena, draw, update)
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
draw(cx+dx, cy+dy, se, sw)
|
||||||
|
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
|
||||||
|
update(dt, se, sw, jumpTime, jumpFrame, jumpDir)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
207
qbert.lua
207
qbert.lua
@@ -1,211 +1,18 @@
|
|||||||
|
|
||||||
|
mkHopper = require "hopper"
|
||||||
|
|
||||||
local height = 10
|
local height = 10
|
||||||
local width = height
|
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},
|
|
||||||
}
|
|
||||||
|
|
||||||
--[[
|
local draw = function(x, y, se, sw)
|
||||||
|
love.graphics.setColor(128,128,0);
|
||||||
Jump equation, assuming y is up the screen and gravity down, chosen so
|
love.graphics.circle("fill", x, y, height)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local frameDuration = 0.1 -- millis
|
local update = function(dt, se, sw, jumpTime, jumpFrame, jumpDir)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- constructor
|
-- constructor
|
||||||
return function(arena)
|
return function(arena)
|
||||||
assert(arena ~= nil, "arena must not be nil")
|
local self = mkHopper(arena, draw, update)
|
||||||
local jumpTime = nil
|
return self
|
||||||
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,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user