Compare commits

...

5 Commits

Author SHA1 Message Date
Nick Stokoe
e75f2917c3 ball.lua - implement a random visiting ball 2022-07-02 23:00:27 +01:00
Nick Stokoe
cb49e2b933 main.lua, etc. - use a sorted list of mobs
The mobs are characters to animate.  They're sorted by screen depth,
so that nearer ones render over farther ones.
2022-07-02 22:59:14 +01:00
Nick Stokoe
1cfc037283 main.lua - rename constructors to mk... from new... 2022-07-02 18:54:23 +01:00
Nick Stokoe
3750a2fdf6 qbert.lua - split out common functionality into hopper.lua 2022-07-02 18:53:35 +01:00
Nick Stokoe
2367bf83cf nominally working hopping demo 2022-07-02 18:35:17 +01:00
5 changed files with 396 additions and 0 deletions

86
arena.lua Normal file
View File

@@ -0,0 +1,86 @@
local sideHeight = 35
local width = 55
local topHeight = 20
local polys = {
{
0, 0, width/2, topHeight/2,
0, topHeight, -width/2, topHeight/2,
},
{
0, topHeight, width/2, topHeight/2,
width/2, topHeight/2 + sideHeight, 0, topHeight + sideHeight,
},
{
0, topHeight, -width/2, topHeight/2,
-width/2, topHeight/2 + sideHeight, 0, topHeight + sideHeight,
},
}
return function(size, discs, topCol, rightCol, leftCol)
assert(size > 0)
for disc in pairs(discs) do
assert(disc >= -size and disc <= size)
end
local colours = {topCol, rightCol, leftCol}
local function drawCube(x, y)
love.graphics.push()
love.graphics.translate(x, y)
for ix, poly in ipairs(polys) do
love.graphics.setColor(colours[ix])
love.graphics.polygon("fill", poly)
end
love.graphics.pop()
end
local function getHeight()
return size*(topHeight/2 + sideHeight) + topHeight/2
end
local x = love.graphics.getWidth()/2
local y = love.graphics.getHeight()/2 - getHeight()/2;
local function qpos2Coords(se, sw)
return x + se * width/2 - sw * width/2,
y + (se + sw)*(topHeight/2+sideHeight)
end
return {
getDepth = function() return -1; end,
getWidth = function()
return size*width*2
end,
getHeight = getHeight,
getSize = function() return size; end,
getCubeSideHeight = function() return sideHeight; end,
getCubeTopHeight = function() return topHeight; end,
getCubeHeight = function() return sideHeight+topHeight; end,
getCubeWidth = function() return width; end,
getRowHeight = function() return sideHeight+topHeight/2; end,
getColWidth = function() return width/2; end,
qpos2Coords = qpos2Coords,
draw = function()
for se = 0, size-1 do
for sw = 0, size-1 do
if (se + sw < size) then
local x, y = qpos2Coords(se, sw)
drawCube(x, y)
end
end
end
end,
update = function(dt)
end,
}
end

23
ball.lua Normal file
View File

@@ -0,0 +1,23 @@
local mkHopper = require "hopper"
local dirs = {"se", "sw", "ne", "nw"}
local height = 10;
return function(arena)
local self
local function update(dt, se, sw)
if se+sw >= arena.getSize() then
self.dead = true
return
end
local dir = dirs[math.random(2)]
self.jump(dir)
end
local function draw(x, y )
love.graphics.setColor(128,0,0);
love.graphics.circle("fill", x, y, height)
end
self = mkHopper(arena, draw, update)
return self
end

202
hopper.lua Normal file
View File

@@ -0,0 +1,202 @@
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 {
getDepth = function() return sw+se; 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)
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

67
main.lua Normal file
View File

@@ -0,0 +1,67 @@
local mkArena = require "arena"
local mkQbert = require "qbert"
local mkBall = require "ball"
local arena = mkArena(
7,
{3, -3},
{255, 0, 0}, {0, 255, 0}, {0, 0, 255}
)
local time = 0
local qbert = mkQbert(
arena
)
local mobs = { qbert, arena }
function love.load()
end
function love.draw()
for ix, mob in ipairs(mobs) do
mob.draw()
end
end
function love.update(dt)
time = time + dt
if time > 10 then
time = 0
table.insert(mobs, mkBall(arena))
end
if love.keyboard.isDown('i') then
qbert.jump('ne')
end
if love.keyboard.isDown('u') then
qbert.jump('nw')
end
if love.keyboard.isDown('k') then
qbert.jump('se')
end
if love.keyboard.isDown('j') then
qbert.jump('sw')
end
if love.keyboard.isDown('escape') then
love.event.quit(0)
end
for ix, mob in ipairs(mobs) do
mob.update(dt)
end
local mobs2 = {}
for ix, mob in ipairs(mobs) do
if not mob.dead then
mobs2[#mobs2+1] = mob
end
end
table.sort(mobs2, function(a, b) return a.getDepth() < b.getDepth(); end)
mobs = mobs2
end

18
qbert.lua Normal file
View File

@@ -0,0 +1,18 @@
mkHopper = require "hopper"
local height = 10
local width = height
local draw = function(x, y, se, sw)
love.graphics.setColor(128,128,0);
love.graphics.circle("fill", x, y, height)
end
local update = function(dt, se, sw, jumpTime, jumpFrame, jumpDir)
end
-- constructor
return function(arena)
local self = mkHopper(arena, draw, update)
return self
end