Codea Game - 3
Let’s take a look at what we have so far, and see what the code is trying to say. Does it seem like early days for that? Maybe not … why not start thinking right away? :)
-- Game Spike
function setup()
frame = Frame(50, 100, 400, 300)
end
function draw()
background(40, 40, 50)
strokeWidth(5)
frame:draw()
end
Nothing to see here, right?
Wrong. We know that there are polylines being built somewhere inside Frame, in the draw() method actually. Here in setup() we should be setting up the environment, and our idea of Frame is that it can frame anything. So our objects probably want to be built under control of setup() and then the framing structure should be built up. We’ll keep that in mind as we work. It may be too soon to settle in, but it’s never too soon to start nudging things in the right direction.
Frame = class()
function Frame:init(x, y, width, height)
self.x = x
self.y = y
self.height = height
self.width = width
end
function Frame:draw()
pushMatrix()
stroke(255, 255, 255, 255)
translate(self.x, self.y)
rect(0, 0, self.width, self.height)
translate(5,5)
scale(self.width/500, self.height/500)
strokeWidth(5/(self.width/500))
local poly1 = Polyline({0,0}, {75,100}, {400,300})
local poly2 = Polyline({0,0}, {75,75}, {400,250})
stroke(0, 255, 4, 255)
poly1:draw()
stroke(255, 0, 0, 255)
poly2:draw()
popMatrix()
end
The frame knows its location x y. I’m wondering if it should. Might be OK, but I note that I’m not sure. We’ll see if the code tells us more later.
The draw function is building the Polylines on the fly. That has to be wrong. We probably want the Frame to have a table of things to draw. I’m inclined to push in that direction, which might also help us push world creation higher up.
I like that we are telling the Polylines to draw themselves: that’s good OO by my lights. I am concerned by the fact that Frame:draw() is deciding the color. The Polylines should know what color to be, it seems to me.
I can see some options for that: add color to Polyline, make a ColoredPolyline subclass, or make a ColorWrapper that sets color on whatever is inside. We’ll think about that and try something.
Also, Frame:draw() is doing several things, mostly in line. Probably time for ComposedMethod. Also, I note that it is pushing the transform matrix but not the other draw parms. That’s not good practice, because it changes things. We could wind up with the wrong color outside or something.
Polyline = class()
function Polyline:init(...)
self.polyline = arg
end
function Polyline:draw()
local p = self.polyline
for i = 2, #p do
local start, finish = p[i-1], p[i]
line(start[1], start[2], finish[1], finish[2])
end
end
Hmm. I don’t see much wrong with this. Must be missing something. :)
OK, what to do? I think I’ll spend a few minutes cleaning up Frame.
But first I’d like to mention a continuing concern: how do we test this stuff? I am a proponent of Test-Driven Development (TDD), yet so far I’ve not written a single test. Fact is, I don’t know of a good way to test-drive a drawing program with automated tests. It is possible, though a pain, to lock down a simple graphical program with tests once it’s done, but when building it up, I just don’t know a way. You’ll notice that I proceed in tiny steps and check the output often. Tiny steps always give me good feedback and confidence, and in the case of graphics, look and see is the best I know.
Later on, I expect that we’ll have some objects that actually do things. I’ll try to treat those more in the TDD style. Call me on it if I don’t.
Anyway, let’s clean up some stuff, starting with Frame. First, let’s move the Polyline creation to the init:
function Frame:init(x, y, width, height)
self.x = x
self.y = y
self.height = height
self.width = width
self.poly1 = Polyline({0,0}, {75,100}, {400,300})
self.poly2 = Polyline({0,0}, {75,75}, {400,250})
end
This just sets up two member variables, which we display in draw():
function Frame:draw()
pushMatrix()
stroke(255, 255, 255, 255)
translate(self.x, self.y)
rect(0, 0, self.width, self.height)
translate(5,5)
scale(self.width/500, self.height/500)
strokeWidth(5/(self.width/500))
stroke(0, 255, 4, 255)
self.poly1:draw()
stroke(255, 0, 0, 255)
self.poly2:draw()
popMatrix()
end
I run this, and sure enough it still works. what else? The color setting seems odd. The things drawn should know what color to be. The simplest thing I can imagine is to add color to the Polyline. I’ve mentioned some other possibilities, I think: a color wrapper, or a ColoredPolyline class. It’s way too early for that. We’re just trying to get the functionality in roughly the right spots, to get the shape of the design right. So I think I’ll just give the Polyline a color setter. Not quite right, but better. If the code continues to get better, we’re doing our job.
function Frame:init(x, y, width, height)
self.x = x
self.y = y
self.height = height
self.width = width
self.poly1 = Polyline({0,0}, {75,100}, {400,300})
self.poly1:setColor(0,255,0)
self.poly2 = Polyline({0,0}, {75,75}, {400,250})
self.poly2:setColor(255,0,0)
end
And of course I remove the stroke calls from draw():
function Frame:draw()
pushMatrix()
stroke(255, 255, 255, 255)
translate(self.x, self.y)
rect(0, 0, self.width, self.height)
translate(5,5)
scale(self.width/500, self.height/500)
strokeWidth(5/(self.width/500))
self.poly1:draw()
self.poly2:draw()
popMatrix()
end
First, I ran it this way, to see that Polyline didn’t understand setColor. Poor man’s TDD. Then I added setColor:
function Polyline:setColor(red,green,blue)
self.red = red
self.green = green
self.blue = blue
end
And I ran it again. Sure enough, no errors, and the lines are white. Perfect, just what I expected, because I’m not using the color yet. Now to do that. Also, I remember that as written, we’re not saving the color state, so I will do that also. Too big a bite? I think not. Let’s see.
function Polyline:draw()
pushStyle()
stroke(self.red,self.green,self.blue,255)
local p = self.polyline
for i = 2, #p do
local start, finish = p[i-1], p[i]
line(start[1], start[2], finish[1], finish[2])
end
popStyle()
end
And it looks right again! Woot, and all that. Now what? Is there more we should do before wrapping up this article?
I see two things. First, the draw() in Frame has a bunch of setup stuff. That could be pulled out into a separate method, and I would do that. The other issue is more aimed at design. Right now, the Frame knows to set up its contents, the Polylines. That should really be done outside, in the Main setup.
Frame, it seems to me, should just have a collection of things to draw, and draw them willy-nilly. One design decision is whether it should be given the whole collection at once, or given things one at a time. I think it’ll be more convenient for users to do it one at a time. So let’s do that.
Step one, I’ll give Frame its collection and let it add the lines, then I’ll switch to adding them from outside:
Frame = class()
function Frame:init(x, y, width, height)
self.x = x
self.y = y
self.height = height
self.width = width
self.drawables = {}
local poly1 = Polyline({0,0}, {75,100}, {400,300})
poly1:setColor(0,255,0)
local poly2 = Polyline({0,0}, {75,75}, {400,250})
poly2:setColor(255,0,0)
self:addDrawable(poly1,poly2)
end
function Frame:addDrawable(...)
for i,v in ipairs(arg) do
table.insert(self.drawables,v)
end
end
function Frame:draw()
pushMatrix()
stroke(255, 255, 255, 255)
translate(self.x, self.y)
rect(0, 0, self.width, self.height)
translate(5,5)
scale(self.width/500, self.height/500)
strokeWidth(5/(self.width/500))
for i,v in ipairs(self.drawables) do
v:draw()
end
popMatrix()
end
We run this and it looks good. Now we just lift the Polyline creation and adding code out of Frame, and move it up to Main setup:
function Frame:init(x, y, width, height)
self.x = x
self.y = y
self.height = height
self.width = width
self.drawables = {}
end
-- Main
function setup()
frame = Frame(50, 100, 400, 300)
local poly1 = Polyline({0,0}, {75,100}, {400,300})
poly1:setColor(0,255,0)
local poly2 = Polyline({0,0}, {75,75}, {400,250})
poly2:setColor(255,0,0)
frame:addDrawable(poly1,poly2)
end
Voila! Our Polyline understands its own color, our Frame works with an arbitrary collection of things that can draw themselves, and our whole - if tiny - system is set up in the Main, not in the operational classes.
No muss, no fuss, and no edit involved more than a few lines of change, mostly cut and paste to move things around. Could we have thought more - and likely harder - and gotten it this good all in one go? Maybe. You’re pretty smart, and so am I. But why would we? This way we had the program working from the beginning, and it never stopped working while it just got better and better.
To me, this is the way to do things. Try it, and maybe you’ll find some ideas that help you.
Now that the design feels more nearly right to me, let’s try something more about the guts of the system next time. I hope you’ll come along. Comments are open, so please ask questions or offer ideas if you have them.