30. Designing a small class — Homework solutions

The .lua solution files are in exercises/30/homework/solutions/.

Problem 1 — Counter class

Worked solution.

local Counter = {}
Counter.__index = Counter

function Counter.new()
    return setmetatable({ count = 0 }, Counter)
end
function Counter:increment() self.count = self.count + 1 end
function Counter:get() return self.count end
function Counter:reset() self.count = 0 end

local c = Counter.new()
c:increment(); c:increment(); c:increment()
print(c:get())   -- 3
c:reset()
print(c:get())   -- 0

Problem 2 — Clamped health

How to think about it. Enforce "stay between 0 and max" inside heal and damage, so no caller can push health out of range.

Worked solution.

local Health = {}
Health.__index = Health

function Health.new(max)
    return setmetatable({ max = max, current = max }, Health)
end
function Health:heal(n)
    self.current = self.current + n
    if self.current > self.max then self.current = self.max end
end
function Health:damage(n)
    self.current = self.current - n
    if self.current < 0 then self.current = 0 end
end
function Health:get() return self.current end

local h = Health.new(100)
h:damage(30); print(h:get())   -- 70
h:heal(1000); print(h:get())   -- 100  (capped at max)
h:damage(9999); print(h:get()) -- 0    (floored at 0)

Problem 3 — Light switch

Worked solution.

local Switch = {}
Switch.__index = Switch

function Switch.new()
    return setmetatable({ on = false }, Switch)
end
function Switch:toggle() self.on = not self.on end
function Switch:isOn() return self.on end

local s = Switch.new()
print(s:isOn())   -- false
s:toggle(); print(s:isOn())   -- true
s:toggle(); print(s:isOn())   -- false

self.on = not self.on flips the boolean each call — the whole switch in one line.

Challenge — Stack with a guard

Problem. :pop() on an empty stack returns nil instead of crashing.

Worked solution.

local Stack = {}
Stack.__index = Stack

function Stack.new()
    return setmetatable({ items = {} }, Stack)
end
function Stack:push(v) table.insert(self.items, v) end
function Stack:pop() return table.remove(self.items) end
function Stack:size() return #self.items end

local s = Stack.new()
s:push("a"); s:push("b")
print(s:size())   -- 2
print(s:pop())    -- b
print(s:pop())    -- a
print(s:pop())    -- nil   (empty, but no crash)
print(s:size())   -- 0

table.remove on an empty list returns nil instead of erroring, so the guard is built in — but know why it is safe.

Done?

That is the end of Part 6. You can build classes, make many, inherit, override, and design a clean, safe public surface. Part 6 has two mini-projects: the Inventory System and a Monster Battle, both built from cooperating objects.