From 3750a2fdf65ef75007e74c71343ffb875ec70d22 Mon Sep 17 00:00:00 2001 From: Nick Stokoe Date: Sat, 2 Jul 2022 18:53:35 +0100 Subject: [PATCH] qbert.lua - split out common functionality into hopper.lua --- hopper.lua | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++ qbert.lua | 207 ++--------------------------------------------------- 2 files changed, 207 insertions(+), 200 deletions(-) create mode 100644 hopper.lua diff --git a/hopper.lua b/hopper.lua new file mode 100644 index 0000000..60b47d6 --- /dev/null +++ b/hopper.lua @@ -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 diff --git a/qbert.lua b/qbert.lua index daff190..e77096a 100644 --- a/qbert.lua +++ b/qbert.lua @@ -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