Incremental Progress

One afternoon each week I volunteer in my kid’s classroom teaching math. They are working on standard model multiplication and occasionally trip over number places. It seems so intuitive to me, but of course it’s something that must be learned. Happily this has given me some experience with trying to code a decimal system display.

One of their worksheets require placing all the tiles of the digits 0-9 into places in some mathematical functions, like a sort of arithmetic Sudoku and one ah-ha moment I often see is the realization that the “0” can be placed to the left of other digits without changing the amount. Computers are of course not native base-10 counters either, so in order to emulate a mechanical display, they need a little help understanding place values as well.

Here’s the bit of script I landed on to ensure that the display always shows 4 digits, regardless of the count.

	local countString = tostring(count)
	 
	-- generate leading 0's
	if count < 1000 then
		countString = "0" .. countString
	end
	if count < 100 then
		countString = "0" .. countString
	end
	if  count < 10 then
		countString = "0" .. countString
	end

That takes care of ensuring there’s always 4 digits even when the count is under 1,000, but rather than simply have the display update instantly when changing it, I plan to use script to animate them moving like the tumblers on a real tally counter. To accomplish this, I’ll need to be able to move each digit place independently, instead of drawing the entire string at once.

I can extract each character as a substring (since it is a string read left-to-right thousands are index 1, and ones are index 4), then I just need to tell it to draw those characters individually. After a long time working with visual scripting and editors, my hand is itching to drag elements around on the screen to place them, but of course it doesn’t work that way. I have to instruct the Playdate exactly what to draw where every frame. Still an interesting challenge, though!

To draw them, and to draw any digits on the “tumbler” that are incoming from underneath (when increasing) or above (when decreasing), I at first tried merely adding or subtracting 1 from that place digit, but of course Lua views them as integers instead of single digits, so I needed to describe using decimal place values, i.e. that counting above 9 or below 0 loops the digit around again and affects the next highest place value as well. Since the font is precisely 100 pixels wide, I can position it horizontally on the screen by subtracting 1 from the index and multiplying that by 100 (thousands at index 1 draw at x=0, and ones at index 4 draw at x=300).

if playdate.buttonIsPressed("down") then
-- Iterate through the number places from right to left
for i = 4, 1, -1 do	
 if isDigitChanging then		
  -- if it's counting down past zero, make the oncoming number a 9
  if (string.sub (countString, i, i)) - 1 < 0 then
   numberFont:drawText("0",((i-1)* 100), 100 + verticalOffsets[i])
   numberFont:drawText("9",((i-1)* 100), verticalOffsets[i])
   -- otherwise make the oncoming number 1 less and indicate this is the last digit to change
  else
   numberFont:drawText((string.sub (countString, i, i)),((i-1)* 100), 100 + verticalOffsets[i])
   numberFont:drawText((string.sub (countString, i, i)) - 1,((i-1)* 100), verticalOffsets[i])
   isDigitChanging = false
  end
  else
   -- if this digit should not change, just draw it
   numberFont:drawText((string.sub (countString, i, i)),((i-1)* 100),100)
  end
  updateVerticalOffset( i )
 end
end

The array of vertical offsets are what drives the animation. On my mechanical counter, presing the plunger a little moves the tumbler a little, and past a certain point, the tumbler snaps into the new position. On the Playdate, the buttons have no travel, so I can’t replicate this feeling exactly, but having the action of changing the count divided into button pressed/held/released events can still imitate a certain amount of mechanical “chunkiness”.

So instead, when the button is pressed and held, it will move the character in the appropriate direction up to a maximum amount (accelerating using Euler’s number, which is a satisfying go-to constant for such things). Releasing it instantly changes the value and centers it in the window again. If I had curves at my disposal I might add a slight bounce to it, but I think coding up a curve is a little more than I’m willing to tackle at the moment, and this happens quickly enough to feel pretty good.

function updateVerticalOffset(i)
	local multiplier = 2.71828
	if verticalOffsets[i] * multiplier >= verticalOffsetMax then
		verticalOffsets[i] = verticalOffsetMax
	else
		verticalOffsets[i] = verticalOffsets[i] * multiplier
	end
end
The result when incrementing the count using a button press.
I also wish I knew how to make animated GIFs from the Playdate simulator, but I think you get the idea.

After that, I just need to hide the vertically offset characters, so that it appears one is viewing these tumblers through a window. I will admit this is probably no the most optimal way to do it, but I simply drew a big white rectangle over the top and bottom of the count display. In a commercial project, I’d probably get some help with a way to display this stuff in a more performant manner, but it should be sufficient for my purposes.

Now my next challenge will be to get a similar animated behavior when using the crank to change the numbers!