Spacewar 40 - More controls.
Some more controls
Last time we added a control limiting ship top speed, one of a few we need for tuning game playability. We encountered a bit of roughness in the code. The issues include:
- We have a local storage object, which amounts to a table we can save values in and get them back. Tozier thinks of it as a toaster note. No, wait, poster note. Anyway …
- We have defined some values “at compile time”, as statements in classes but outside functions. These execute when the program is first run.
- There are some initialization times. There is
Main.setup
function that runs once when the program starts. Pressing the reset button on the Codea console runs this. I do not know whether it reloads the compile time values, but I doubt it. It would be easy to check … - There is a “Run” button among our console buttons. That calls the
Main.newGame()
function, which you would think would start a new game but see below. - It is possible, in principle, that the game control sliders and buttons could remain on screen, although we set everything off-screen with Run. See below again.
Our big but quick discovery was that we didn’t like where the screen setting and the universe init and the newGame were all spread all over. We have moved them all together:
function newGame()
displayMode(FULLSCREEN_NO_BUTTONS)
U:init()
U.sun = Sun()
Ship1 = Ship(1)
Ship(2)
end
(You’re probably wondering why we are saving Ship1. The answer is in our cute little quick-kill automate
function:
function automate()
Missile(Ship1, vec2(0,10))
Ship1.heading = Ship1.heading + 90
Missile(Ship1, vec2(0,10))
end
We just needed a name for it so we could make it fire missiles. I think we should make it a local but right now maybe we’re draining the swamp.
Anyway, now our game control parameters look like this:
function setup()
displayMode(STANDARD)
parameter.action("Automate", function()
automate()
end)
parameter.action("Run", function()
newGame()
end)
parameter.integer("VMax", 2, 20)
parameter.action("Save", function()
saveLocalData("VMax", VMax)
end)
VMax = readLocalData("VMax")
end
We note that this function now has at least two responsibilities: it sets up the parameter things on the screen, and it reads in our (only) saved value. These responsibilities might want to be broken out soon.
The bigger question, and the reason I’m typing here, is that we need to decide when our parameters should take effect. Should they just be accessed at setup, or should they be live all the time? Right now, our only value, VMax, is used like this:
function Ship:adjustedVelocity(vel, heading, accel)
local proposed = vel + accel:rotate(math.rad(heading))
proposed = proposed + U:gravity(self)
self.MaxSpeed = VMax
return U:limitSpeed(self.MaxSpeed, proposed)
end
We’ve changed U:limitSpeed to accept the object’s maximum speed as a direct parameter. That’s good. But we are still keeping that max speed in the Ship instance and in the Ship class, as well as in the parameter/global VMax. Tozier suggests just removing all the Ship.MaxSpeed
references, and I agree … but the Fragment still has the old MaxSpeed functionality, which will result in an odd form of duplication, two same things done two different ways. We’ll ignore that concern for now and address it either when we play with Fragment speed or clean it up in post.1
So we agree to remove all references to the Ship’s MaxSpeed and we do so.
function Ship:adjustedVelocity(vel, heading, accel)
local proposed = vel + accel:rotate(math.rad(heading))
proposed = proposed + U:gravity(self)
return U:limitSpeed(VMax, proposed)
end
Now the topic I’ve been trying to bang on about is when the parameters will be used: will it be at setup or on the fly? The one we have is accessed on the fly and we like it. I propose that we allow the parameters to be accessed at run time, and as convenient. This may encourage us to move some of the class variables out, as we did with Ship.MaxSpeed
, or to a new location.
Since we have a save button, we can play until we like it and then save:
IMAGE HERE
At last …
We can do another parameter. We don’t like the turn rate and we don’t have enough thrust.2
Turning first, then thrust.
Ship = class()
Ship.MaxMissiles = 50
Ship.TicksBetweenMissiles = U.MissileKillDistance + 1
Ship.ThrustAmount = vec2(0,0.02)
Ship.TurnAmount = 1
Ship.MaxDamage = 5
function Ship:move()
local turn
local thrust
if self.controls.left.pressed and not self.controls.right.pressed then turn = Ship.TurnAmount
elseif self.controls.right.pressed and not self.controls.left.pressed then turn = -Ship.TurnAmount
else turn = 0 end
self.heading = self.heading + turn
if self.controls.thrust.pressed then thrust = Ship.ThrustAmount else thrust = vec2(0,0) end
self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
self.pos = U:clip_to_screen(self.pos + self.vel)
if self.controls.fire.pressed then self:fireMissile() end
end
function Ship:adjustedVelocity(vel, heading, accel)
local proposed = vel + accel:rotate(math.rad(heading))
proposed = proposed + U:gravity(self)
return U:limitSpeed(VMax, proposed)
end
Since in adjustedVelocity
we convert heading to radians, we know that heading is in degrees, and since we are adding our TurnAmount
to it, TurnAmount
is also in degrees. One degree per sixtieth of a second makes for six seconds turning clear around. That’s centuries in the life of a spaceship. Let’s try a range of … 1 through 12, which will allow a full turn in half a second, surely too fast.
Therefore, we remove the Ship.TurnAmount
, replacing like this:
function Ship:move()
local turn
local thrust
if self.controls.left.pressed and not self.controls.right.pressed then turn = Turn
elseif self.controls.right.pressed and not self.controls.left.pressed then turn = -Turn
else turn = 0 end
self.heading = self.heading + turn
if self.controls.thrust.pressed then thrust = Ship.ThrustAmount else thrust = vec2(0,0) end
self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
self.pos = U:clip_to_screen(self.pos + self.vel)
if self.controls.fire.pressed then self:fireMissile() end
end
Together with the parameter definitions, which are now:
-- Main
function setup()
displayMode(STANDARD)
parameter.action("Automate", function()
automate()
end)
parameter.action("Run", function()
newGame()
end)
parameter.integer("VMax", 2, 20)
parameter.integer("Turn", 1, 12)
parameter.action("Save", function()
saveLocalData("VMax", VMax)
saveLocalData("Turn", Turn)
end)
VMax = readLocalData("VMax")
Turn = readLocalData("Turn")
end
We encountered an interesting situation there. The setup()
code is a bit confusing, because the parameter definitions just define buttons: they don’t do anything. The final two lines of code, however, do something: they read the current values of our saved values into the parameters, initializing them to our saved values. Unless there are no saved values! Then, that parameter will be set to nil. The parameter buttons on the screen don’t reflect this problem, but the code will likely blow up. Operationally, what we need to do when we first set up a new one of these parameters is to ensure it has a value in the local store. We can do this by carefully sliding its slider about and then saving, or we could take some simple step to initialize it once and then remove the init code. We don’t have a solution. For now we’re going to try to be careful.
However, note that “we’ll be more careful” has not always worked out in the past as often as we might wish. This is a danger sign and we should write ourselves a note to do something better. We’ll be careful to write a note …
One more thing while we’re thinking about it. Look again at the Ship:move()
function:
function Ship:move()
local turn
local thrust
if self.controls.left.pressed and not self.controls.right.pressed then turn = Turn
elseif self.controls.right.pressed and not self.controls.left.pressed then turn = -Turn
else turn = 0 end
self.heading = self.heading + turn
if self.controls.thrust.pressed then thrust = Ship.ThrustAmount else thrust = vec2(0,0) end
self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
self.pos = U:clip_to_screen(self.pos + self.vel)
if self.controls.fire.pressed then self:fireMissile() end
end
This code looks unlike any other code we can remember writing. There’s something weird about it. Another thing to remember. Now we have two things to remember and I can’t recall what the first one was. Anyone got a card or a place to write stuff down?
Bag it, let’s do thrust:
Ship = class()
Ship.MaxMissiles = 50
Ship.TicksBetweenMissiles = U.MissileKillDistance + 1
Ship.ThrustAmount = vec2(0,0.02)
Ship.MaxDamage = 5
Thrust here is cleverly a vector but in fact it is really just an increment to be applied in the +Y direction of the ship. Because we drew them nose upward to begin with. Since acceleration is added every 1/60 second, it is 1.2 coordinate points (not really pixels because we’re scaled) per second per second. Or something. Anyway, let’s turn it into a simple linear parameter, ranging between, oh, 0.01 to 0.2 for now, probably that’ll be too much.
function Ship:move()
local turn
local thrust
if self.controls.left.pressed and not self.controls.right.pressed then turn = Turn
elseif self.controls.right.pressed and not self.controls.left.pressed then turn = -Turn
else turn = 0 end
self.heading = self.heading + turn
if self.controls.thrust.pressed then thrust = vec2(0, ThrustAmount) else thrust = vec2(0,0) end
self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
self.pos = U:clip_to_screen(self.pos + self.vel)
if self.controls.fire.pressed then self:fireMissile() end
end
Note that we moved the vector creation inside move()
and decided to call the parameter ThrustAmount. We implement it thusly:
-- Main
function setup()
displayMode(STANDARD)
parameter.action("Automate", function()
automate()
end)
parameter.action("Run", function()
newGame()
end)
parameter.integer("VMax", 2, 20)
parameter.integer("Turn", 1, 12)
parameter.number("ThrustAmount", 0.01, 0.20)
parameter.action("Save", function()
saveLocalData("VMax", VMax)
saveLocalData("Turn", Turn)
saveLocalData("ThrustAmount", ThrustAmount)
end)
VMax = readLocalData("VMax")
Turn = readLocalData("Turn")
ThrustAmount = readLocalData("ThrustAmount")
end
Some quick testing but not playing gives us these starting values:
IMAGE HERE
Next time we’ll play the game a bit and tune these. We have the odd look of Ship:move()
to worry about, and the fact that Main.setup()
looks confusing, and the necessity to remember to swipe and save new parameters. All those are for next time, and times after next time. Here’s the code:
--# Fragment
Fragment = class()
Fragment.MaxSpeed = 8
Fragment.types = {}
Fragment.types[1] = function()
line(0,7,-3,-8)
line(-3,-8, 1, -4)
line(1,-4, 0,7)
end
Fragment.types[2] = function()
line(-2,2, 3,6)
line(3,6, 3,-2)
line(3,-2, -4,-4)
line(-4,-4, -2,2)
end
Fragment.types[3] = function()
line(-4,1, 3,3)
line(3,3, 3,1)
line(3,1, -1,-3)
line(-1,-3, -5,-3)
line(-5,-3, -4,1)
end
Fragment.types[4] = function()
line(-5,2, -1,2)
line(-1,2, 3,6)
line(3,6, 6,-2)
line(6,-2, -6,-2)
line(-6,-2, -5,2)
end
function Fragment:init(fragmentNumber, ship)
self.type = fragmentNumber
self.cannotCollide = true
self.pos = ship.pos
local speed = 1 + math.random()
local outwardVelocity = vec2(0,speed):rotate(math.random()*math.pi*2)
self.vel = ship.vel + outwardVelocity
self.angularVelocity = math.random()*10-5
self.heading = 0
self.timeOut = math.random(200,300)
U:addObject(self)
end
function Fragment:move()
self.vel = self:adjustedVelocity(self.vel)
self.pos = U:clip_to_screen(self.pos + self.vel)
self.heading = self.heading + self.angularVelocity
end
function Fragment:adjustedVelocity(vel)
proposedV = vel + U:gravity(self)
return U:limitSpeed(self.MaxSpeed, proposedV)
end
function Fragment:draw()
self.timeOut = self.timeOut - 1
if self.timeOut < 0 then self:die() end
pushMatrix()
pushStyle()
translate(self.pos:unpack())
rotate(self.heading)
self:drawFragment(self.type)
popStyle()
popMatrix()
end
function Fragment:drawFragment(type)
strokeWidth(2)
if self.types[type] then self.types[type]() else
pushStyle()
fill(255)
text("?",0,0)
popStyle()
end
end
function Fragment:hitBy(anObject)
self:die()
end
function Fragment:die()
U:kill(self)
end
--# Sun
Sun = class()
function Sun:init()
self.pos = vec2(WIDTH/2, HEIGHT/2)
self.name = "Sun"
self.damageDealt = 100
U:addObject(self)
U:addTouched(self)
end
function Sun:draw()
pushMatrix()
pushStyle()
strokeWidth(2)
translate(self.pos:unpack())
stroke(216, 168, 48, 255)
fill(216, 168, 48, 255)
ellipse(0,0,10)
popStyle()
popMatrix()
end
function Sun:move()
end
function Sun:hitBy(anObject)
end
function Sun:touched(touch)
if touch.state == BEGAN and self:nearMe(touch) then
displayMode(STANDARD)
setup()
end
end
function Sun:nearMe(touch)
return self.pos:dist(vec2(touch.x,touch.y)) < 100
end
--# Universe
-- HAVE YOU PUSHED TO GITHUB TODAY?
Universe = class()
function Universe:init()
self.Adds = {}
self.Drawn = {}
self.Removes = {}
self.Touched = {}
self.MissileKillDistance = 20
self.GravitationalConstant = 200
self.Tick = 0
end
function Universe:draw()
U.Tick = U.Tick + 1
self:updateUniverse()
self:moveAll()
self:interactAll()
self:drawAll()
end
function Universe:touched(touch)
for _,obj in ipairs(self.Touched) do
obj:touched(touch)
end
end
-- helper functions
function Universe:addInNewObjects()
for _, obj in ipairs(self.Adds) do
self.Drawn[obj] = obj
end
self.Adds = {}
end
function Universe:addObject(anObject)
table.insert(self.Adds, anObject)
end
function Universe:addTouched(anObject)
table.insert(self.Touched, anObject)
end
function Universe:clip_to_screen(vec)
return vec2(vec.x%WIDTH, vec.y%HEIGHT)
end
function Universe:colliding(obj1, obj2)
if obj1 == nil then return false end
if obj2 == nil then return false end
if obj1.cannotCollide then return false end
if obj2.cannotCollide then return false end
if obj1 == obj2 then return false end
local d = obj1.pos:dist(obj2.pos)
local result = d < U.MissileKillDistance
return result
end
function Universe:drawAll()
background(40, 40, 50)
for _, obj in pairs(U.Drawn) do
obj:draw()
end
end
function Universe:gravity(anObject)
local directionToSun = (U.sun.pos - anObject.pos):normalize()
local distSquared = anObject.pos:distSqr(U.sun.pos)
return U.GravitationalConstant*directionToSun/distSquared
end
function Universe:interactAll()
for _, a in pairs(U.Drawn) do
for _, b in pairs(U.Drawn) do
if U:colliding(a,b) then
a:hitBy(b)
end
end
end
end
function Universe:kill(anObject)
table.insert(self.Removes, anObject)
end
function Universe:limitSpeed(max, vel)
local speed = vel:len()
if speed > max then
vel = vel*max/speed
end
return vel
end
function Universe:moveAll()
for _, obj in pairs(U.Drawn) do
obj:move()
end
end
function Universe:removeDeadObjects()
for _, obj in ipairs(self.Removes) do
self.Drawn[obj] = nil
end
self.Removes = {}
end
function Universe:updateUniverse()
self:removeDeadObjects()
self:addInNewObjects()
end
--# Button
-- HAVE YOU PUSHED TO GITHUB TODAY?
Button = class()
function Button:init(x, y, w2, h2)
self.name = string.format("Button %f %f", x, y)
self.pos = vec2(x,y)
self.width = w2
self.height = h2
self.pressed = false
self.capturedID = nil
self.cannotCollide = true
U:addTouched(self)
U:addObject(self)
end
function Button:draw()
pushStyle()
pushMatrix()
rectMode(RADIUS)
translate(self.pos:unpack())
strokeWidth(1)
stroke(255)
fill(self:fillColor())
rect(0,0, self.width, self.height)
popMatrix()
popStyle()
end
function Button:fillColor()
if (self.pressed) then
return color(0, 255, 0, 255)
else
return color(255,0,0,26)
end
end
function Button:touched(touch)
self.capturedID = self:getCapture(touch)
self.pressed = self:getPressed(touch)
end
function Button:getCapture(touch)
if touch.state == BEGAN and self:insideMe(touch) and not self.capturedID then
return touch.id
elseif touch.state == ENDED and self.capturedID == touch.id then
return nil
else
return self.capturedID
end
end
function Button:getPressed(touch)
if not self.capturedID then
return false
elseif self.capturedID ~= touch.id then
return self.pressed
else
return self:insideMe(touch)
end
end
function Button:insideMe(touch)
local x = touch.x
local y = touch.y
local myLeft = self.pos.x - self.width
local myRight = self.pos.x + self.width
local myBottom = self.pos.y - self.height
local myTop = self.pos.y + self.height
local insideX = x >= myLeft and x <= myRight
local insideY = y >= myBottom and y <= myTop
return insideX and insideY
end
function Button:move()
-- don't
end
--# Main
-- HAVE YOU PUSHED TO GITHUB TODAY?
-- S3 Spacewar
-- The Universe
U = Universe()
function setup()
displayMode(STANDARD)
parameter.action("Automate", function()
automate()
end)
parameter.action("Run", function()
newGame()
end)
parameter.integer("VMax", 2, 20)
parameter.integer("Turn", 1, 12)
parameter.number("ThrustAmount", 0.01, 0.20)
parameter.action("Save", function()
saveLocalData("VMax", VMax)
saveLocalData("Turn", Turn)
saveLocalData("ThrustAmount", ThrustAmount)
end)
VMax = readLocalData("VMax")
Turn = readLocalData("Turn")
ThrustAmount = readLocalData("ThrustAmount")
end
function automate()
Missile(Ship1, vec2(0,10))
Ship1.heading = Ship1.heading + 90
Missile(Ship1, vec2(0,10))
end
function draw()
U:draw()
end
function touched(touchid)
U:touched(touchid)
end
function newGame()
--displayMode(FULLSCREEN_NO_BUTTONS)
U:init()
U.sun = Sun()
Ship1 = Ship(1)
Ship(2)
end
--[[
function allFunctions(aClass)
names = {}
for k,v in pairs(aClass) do
print(k,v)
if type(v) == "function" then table.insert(names,k) end
end
table.sort(names)
ans = ""
for i,n in pairs(names) do
ans = ans.."\n"..n
end
print(ans)
end
]]--
--# Missile
-- HAVE YOU PUSHED TO GITHUB TODAY?
Missile = class()
Missile.count = 0
function Missile:init(ship, testVel, startPos)
local myVel = testVel or vec2(0,1)
self.name = "missile " .. Missile.count
self.damageDealt = 1
self.hitpoints = 1
Missile.count = Missile.count + 1
self.pos = ship.pos + (startPos or self:aWaysOutInFront(ship))
local velocity = myVel:rotate(math.rad(ship.heading))
self.vel = ship.vel + velocity
self.maxDistance = WIDTH*1.4
self.distance = 0
U:addObject(self)
end
function Missile:move()
self.distance = self.distance + self.vel:len()
if self.distance > self.maxDistance then
self:die()
return
end
self.pos = U:clip_to_screen(self.pos + self.vel)
end
function Missile:draw()
pushMatrix()
pushStyle()
strokeWidth(1)
stroke(255,255,255,255)
fill(255,255,255,255)
translate(self.pos.x, self.pos.y)
ellipse(0,0,5)
popStyle()
popMatrix()
end
function Missile:aWaysOutInFront(ship)
local aWays = vec2(0, U.MissileKillDistance*1.5)
return aWays:rotate(math.rad(ship.heading))
end
function Missile:hitBy(anObject)
self.hitpoints = self.hitpoints - (anObject.damageDealt or 0)
if self.hitpoints <= 0 then
self:die()
end
end
function Missile:die()
U:kill(self)
end
--# Ship
-- HAVE YOU PUSHED TO GITHUB TODAY?
Ship = class()
Ship.MaxMissiles = 50
Ship.TicksBetweenMissiles = U.MissileKillDistance + 1
Ship.MaxDamage = 5
function Ship:init(shipNumber)
self.name = string.format("Ship %d", shipNumber)
self.missileLoad = Ship.MaxMissiles
self.timeLastFired = -Ship.TicksBetweenMissiles
self.shipNumber = shipNumber
self.hitpoints = Ship.MaxDamage
self.damageDealt = 5
self.pos = self:initialPosition()
self.heading = self:initialHeading()
self.vel = vec2(0,0)
self.controls = {
left = self:controlButton(85, HEIGHT-50, 85, 50),
right = self:controlButton(50, HEIGHT-185, 50, 85),
thrust = self:controlButton(50, 185, 50, 85),
fire = self:controlButton(85, 50, 85, 50)
}
U:addObject(self)
end
function Ship:controlButton(x,y, w2, h2)
if self.shipNumber == 2 then
return Button(x,y, w2, h2)
else
return Button(WIDTH-x,HEIGHT-y, w2, h2)
end
end
function Ship:initialPosition()
if self.shipNumber == 1 then
return vec2(WIDTH/2, HEIGHT/2 + 200)
else
return vec2(WIDTH/2, HEIGHT/2 - 200)
end
end
function Ship:initialHeading()
if self.shipNumber == 1 then
return 90
else
return 270
end
end
function Ship:move()
local turn
local thrust
if self.controls.left.pressed and not self.controls.right.pressed then turn = Turn
elseif self.controls.right.pressed and not self.controls.left.pressed then turn = -Turn
else turn = 0 end
self.heading = self.heading + turn
if self.controls.thrust.pressed then thrust = vec2(0, ThrustAmount) else thrust = vec2(0,0) end
self.vel = self:adjustedVelocity(self.vel, self.heading, thrust)
self.pos = U:clip_to_screen(self.pos + self.vel)
if self.controls.fire.pressed then self:fireMissile() end
end
function Ship:fireMissile()
if self.missileLoad > 0 and U.Tick - self.timeLastFired >= Ship.TicksBetweenMissiles then
self.timeLastFired = U.Tick
self.missileLoad = self.missileLoad - 1
Missile(self)
end
end
function Ship:draw()
pushMatrix()
pushStyle()
strokeWidth(2)
translate(self.pos:unpack())
rotate(self.heading)
line(0, 15, 5, -15)
line(5, -15, -5, -15)
line(-5, -15, 0, 15)
popStyle()
popMatrix()
end
function Ship:adjustedVelocity(vel, heading, accel)
local proposed = vel + accel:rotate(math.rad(heading))
proposed = proposed + U:gravity(self)
return U:limitSpeed(VMax, proposed)
end
function Ship:hitBy(anObject)
self.hitpoints = self.hitpoints - (anObject.damageDealt or 0)
if self.hitpoints <= 0 then
self:die()
end
end
function Ship:die()
self:explode()
U:kill(self)
end
function Ship:explode()
for i = 1, 5 do
Fragment(i, self)
end
end
--# TestButtonCU
-- HAVE YOU PUSHED TO GITHUB TODAY?
function testSpacewarButton()
CodeaUnit.detailed = false
local dSave
local tSave
local insideEndTouch = { id=1, state=ENDED, x=100, y=100 }
local insideBeganTouch = { id=3, state=BEGAN, x=100, y=100 }
local outsideBeganTouch = { id=4, state=BEGAN, x=151, y=100 }
local closeThree = { id=3, state=ENDED, x=200, y=200 }
local movingFinger = { id=5, state=BEGAN, x = 100, y=100}
local button
_:describe("Spacewar Button Test", function()
_:before(function()
dSave = U.Drawn
tSave = U.Touched
U.Drawn = {}
U.Touched = {}
button = Button(100, 100)
end)
_:after(function()
U.Drawn = dSave
U.Touched = tSave
end)
_:test("No global button", function()
_:expect(_G["button"]).is(nil)
end)
_:test("Inside Circle", function()
_:expect(button:insideMe(insideEndTouch)).is(true)
_:expect(button:insideMe(outsideBeganTouch)).is(false)
end)
_:test("Initial Button Capture", function()
button:touched(insideBeganTouch)
_:expect(button.capturedID).is(3)
button:touched(outsideBeganTouch)
_:expect(button.capturedID).is(3)
_:expect(button.pressed).is(true)
end)
_:test("Sequence Test", function()
button:touched(closeThree)
_:expect(button.capturedID).is(nil)
button:touched(movingFinger)
_:expect(button.capturedID).is(5)
_:expect(button.pressed).is(true)
movingFinger.state=MOVING
button:touched(movingFinger)
_:expect(button.pressed).is(true)
movingFinger.x = 200
button:touched(movingFinger)
_:expect(button.pressed).is(false) -- no longer pressed
_:expect(button.capturedID).is(5) -- but retains capture
end)
_:test("Two Fingers", function()
button:touched(insideBeganTouch)
_:expect(button.capturedID).is(3)
_:expect(button.pressed).is(true)
button:touched(outsideBeganTouch)
_:expect(button.capturedID).is(3)
_:expect(button.pressed).is(true)
end)
end)
end
--# TestControlPlacement
-- HAVE YOU PUSHED TO GITHUB TODAY?
function testControlPlacement()
local dSave
local tSave
_:describe("Control Placement", function()
_:before(function()
dSave = U.Drawn
tSave = U.Touched
U.Drawn = {}
U.Touched = {}
end)
_:after(function()
U.Drawn = dSave
U.Touched = tSave
end)
_:test("hookup", function()
_:expect("hookup").is("hookup")
end)
_:test("right ship buttons", function()
local rs = Ship(1)
-- _:expect(rs.controls.left.pos.y).is(200)
_:expect(rs.controls.left.pos.x).is(WIDTH - 85)
end)
_:test("left ship buttons", function()
local ls = Ship(2)
-- _:expect(ls.controls.left.pos.y).is(HEIGHT-200)
_:expect(ls.controls.left.pos.x).is(85)
end)
end)
end
--# TestKill
-- HAVE YOU PUSHED TO GITHUB TODAY?
function testKill()
_:describe("Missiles Kill", function()
_:before(function()
dSave = U.Drawn
tSave = U.Touched
U.Drawn = {}
U.Touched = {}
end)
_:after(function()
U.Drawn = dSave
U.Touched = tSave
end)
_:test("All exist", function()
Ship(1)
Ship(2)
U:updateUniverse()
local found = {}
for _, obj in pairs(U.Drawn) do
found[obj] = obj.name
end
_:expect(found).has("Ship 1")
_:expect(found).has("Ship 2")
end)
_:test("Ship no longer kills self by firing missile", function()
local ship1 = Ship(1)
Ship(2)
Missile(ship1)
U:updateUniverse()
U:interactAll()
local found = {}
for _, obj in pairs(U.Drawn) do
found[obj] = obj.name
end
_:expect(found).has("Ship 1")
_:expect(found).has("Ship 2")
end)
end)
end
--# TestPairs
-- HAVE YOU PUSHED TO GITHUB TODAY?
function testPairs()
local items = {a = "a", b = "b", c = "c"}
_:describe("Test Skipping Pairs", function()
_:test("Find nine without considering formers", function()
local count = 0
for _, obj in pairs(items) do
for _, other in pairs(items) do
count = count + 1
end
end
_:expect(count).is(9)
end)
_:test("Find six (three choose two) with considering formers", function()
local count = 0
local considered = {}
for _, obj in pairs(items) do
for _, other in pairs(items) do
if considered[other] and considered[other][obj] then
-- skipped
else
if not considered[obj] then considered[obj] = {} end
considered[obj][other] = true
count = count + 1
end
end
end
_:expect(count).is(6)
end)
end)
end