Lua Bowling. Now a Frame!!
I did a bit more refinement on the Lua bowling example from last time. First, I package the rolls. Then a Frame appears!
Although I rather like the trick of eating the front of the rolls list, consuming one frame at a time, we are editing the real rolls table, which means that when the scoring is over, the rolls are gone. Lua passes references around, not copies, as you’d hope. Destroying the roll list is probably bad. I could copy it, but that seems a bit naff. Instead, I decided to build a little “rolll handler”.
We have built a similar capability to this in other bowling implementations. The idea is that instead of going for roll[1] to get the first roll of the frame, we have an offset integer telling us where the current frame starts, so we fetch roll[1 + offset]. Simple enough idea. It is easy enough to do it in line, and often I would do. Today, no.
The iLua editor is basically an iPad editor. This means that there’s no mouse, no rapid grab-cut-paste to be had for moving things around. I am finding that the infelicities of the editor are causing me to be a bit less incremental. In a language with a better editor, I might build the offset in at one location, then move it to a better one, refactoring. Here, I chose to take bigger bites. Have a glance at all the changes, then meet me below:
-- bowling09.lua -- eliminate destruction of rolls -- introduce rollHandler -- bowling function frameScore(rh) base = rh.roll(1) + rh.roll(2) if rh.roll(1) == 10 or base == 10 then base = base + rh.roll(3) end return base end function removeFrame(rh) rh.step(frameSize(rh)) end function frameSize(rh) if rh.roll(1) == 10 then return 1 else return 2 end end function rollHandler(rollList) local rh = {} rh.rolls = rollList rh.offset = 0 rh.roll = function (n) return rh.rolls[n + rh.offset] end rh.step = function(n) rh.offset = rh.offset + n end return rh end function score(rollList) local rh = rollHandler(rollList) total = 0 for frame = 1,10 do total = total + frameScore(rh) removeFrame(rh) end return total end -- assert function beginTests() print "begin tests" asserts = 0 successes = 0 failures = 0 end function endTests() print( asserts .. " asserts, " .. successes .. " successes " .. failures .. " failures") end function assertEquals(expected, actual) asserts = asserts + 1 if expected ~= actual then print ( "expected " .. expected .. ", was " .. actual) failures = failures + 1 else successes = successes + 1 end end beginTests() zeros = {0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,} assertEquals(0, score(zeros)) opens = {} for frame = 1,10 do opens[#opens+1] = 4 opens[#opens+1] = 5 end assertEquals(90, score( opens)) opens = {} for frame = 1,5 do opens[#opens+1] = 4 opens[#opens+1] = 5 opens[#opens+1] = 3 opens[#opens+1] = 5 end assertEquals(85, score( opens)) -- spare spare = { 4,5, 5,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5} assertEquals(95, score(spare)) -- strike strike = { 4,5, 10, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5} assertEquals(100, score(strike)) -- perfect perfect = { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 } assertEquals( 300, score(perfect)) endTests()
Let’s start with a look at the score() function. Everything else just unwinds from there. Score gets this new object, rh, a rollHandler. Then it calls its usual suspects, frameScore and a new guy, removeFrame, which are rewritten to use a rollHander. removeFrame is just a quick combination of the call to step() and frameSize() from the previous version, done to express intention better.
The rest of the changes to the callers were straightforward. Instead of saying rolls[i] to the rolls list, we have to say roll(i) to the rollHandler, and so on.
But why? What’s this rollHandler thing? I think of it as an object. Lua thinks of it as a table. In the table, there are several fields. Let’s look at the rollHandler function separately:
function rollHandler(rollList) local rh = {} rh.rolls = rollList rh.offset = 0 rh.roll = function (n) return rh.rolls[n + rh.offset] end rh.step = function(n) rh.offset = rh.offset + n end return rh end
What’s this all about? It’s simple, really. No, really. We declare an empty local table, rh. We set its rolls attribute to the rolls list. This could have been written as rh[“rolls”] = rollList. A Lua table is an associative array, so that now, under the key “rolls”, we have the list rollList. The rh.rolls syntax is just syntactic sugar. I’m using it here because I’m thinking of these table elements as if they were fields or methods in an object definition.
We define an offset field and set it to zero. Then we define an anonymous function and store it in the roll attribute. The roll attribute contains a function! The function just returns the appropriate element of the rolls list, namely rolls[n+offset].
And we define the step function to move the offset along. All the other logic for frame size and score is left in the scoring algorithm for now. This can and will be improved. For now, all we have is a little package that holds our rolls and increments to the frame position we are looking at. The package, “rollHander” encapsulates the rolls list, an offset, and a couple of functions that allow us to fetch the rolls we want, and to step the list along.
At this point, rollHandler doesn’t know anything about bowling. It is just a slightly smarter than usual array. We’ll fix that. This was big enough as it stood.
I did these changes pretty much in a batch. As you can see, there weren’t many. By renaming the parameters to the functions from rolls to rh, I ensured that the compiler would help me find anything I missed. Even so, this change is larger than I’d like to do, and if we had several modules going it might be pretty tricky.
I have no defense against these observations. This was the best I knew how to do at the time. It only took a bit of time, even with the execrable iPad editor, so it was pretty safe. Just not as safe as I’d like.
You’ll notice that I also added in the perfect game test, just to give myself a bit of joy.
From rollHandler to frame
We clearly have a modularity issue with rollHandler. He’s a pretty dumb object, and all kinds of things known about the frame are left in the main scoring algorithm. So it is pretty obvious that the rollHandler could be, and should be smarter, moving some of the frame logic into rollHandler, leaving the game scoring algorithm in the main loop. Here’s what I did:
-- bowling10.lua -- make rollHandler into a "frame" -- break out rollHandler / frame lexically for now -- frame function firstFrame(rollList) local frame = {} frame.rolls = rollList frame.offset = 0 frame.roll = function (n) return frame.rolls[n + frame.offset] end frame.step = function(n) frame.offset = frame.offset + n end frame.frameSize = function() return frame.roll(1) == 10 and 1 or 2 end frame.nextFrame = function() frame.step(frame.frameSize()) end frame.score = function() base = frame.roll(1) + frame.roll(2) if frame.roll(1) == 10 or base == 10 then base = base + frame.roll(3) end return base end return frame end -- bowling function score(rollList) local frame = firstFrame(rollList) total = 0 for frameNumber = 1,10 do total = total + frame.score() frame.nextFrame() end return total end -- assert function beginTests() print "begin tests" asserts = 0 successes = 0 failures = 0 end function endTests() print( asserts .. " asserts, " .. successes .. " successes " .. failures .. " failures") if asserts == successes then print "GREEN BAR!" else print "RED BAR!" end end function assertEquals(expected, actual) asserts = asserts + 1 if expected ~= actual then print ( "expected " .. expected .. ", was " .. actual) failures = failures + 1 else successes = successes + 1 end end -- bowling tests beginTests() zeros = {0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,} assertEquals(0, score(zeros)) opens = {} for frame = 1,10 do opens[#opens+1] = 4 opens[#opens+1] = 5 end assertEquals(90, score( opens)) opens = {} for frame = 1,5 do opens[#opens+1] = 4 opens[#opens+1] = 5 opens[#opens+1] = 3 opens[#opens+1] = 5 end assertEquals(85, score( opens)) -- spare spare = { 4,5, 5,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5} assertEquals(95, score(spare)) -- strike strike = { 4,5, 10, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5, 4,5} assertEquals(100, score(strike)) -- perfect perfect = { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 } assertEquals( 300, score(perfect)) endTests()
Again, let’s start at score(). These changes are pretty simple: instead of calling our own functions, we forward the call for the (frame) score to the frame, and then tell the frame to go to the next frame. That’s a pretty standard delegation pattern. The juice is in the new function, firstFrame, which is like the rollHandler function grown up a bit. Have a look at it:
-- frame function firstFrame(rollList) local frame = {} frame.rolls = rollList frame.offset = 0 frame.roll = function (n) return frame.rolls[n + frame.offset] end frame.step = function(n) frame.offset = frame.offset + n end frame.frameSize = function() return frame.roll(1) == 10 and 1 or 2 end frame.nextFrame = function() frame.step(frame.frameSize()) end frame.score = function() base = frame.roll(1) + frame.roll(2) if frame.roll(1) == 10 or base == 10 then base = base + frame.roll(3) end return base end return frame end
This starts out mostly the same as rollHandler did, with the new name firstFrame. That was a compromise name. I would have preferred just “frame” but that word appears so often in the text, and the iLua editor is so weak, that I just picked a new name. So shoot me.
Basically then I just moved the frame.score function up from the main area, as well as the other new ones, nextFrame and frameSize.
I think we can call this result a “frame object” with some justice, and I believe it is pretty close to decent Lua style. It’s not a class, but it seems certainly to be an object.
I also added a RED BAR / GREEN BAR shout-out to the assert, just for fun.
Next step? I’m not sure. Possibly learn how to break these things into separate files.
Anyway it has been fun for me, and I hope for you. Stay tuned. Comments are on.