Make a digital clock in Mini Micro
Tags: programming, miniscript, minimicro, animation
Published: 2024-07-03 15:44:52

Today let's do a little project in Mini Micro that makes an old-school digital clock.  This will illustrate pulling apart a sprite sheet, animation by drawing directly to a PixelDisplay (rather than using actual sprites), and the `dateTime` module to get the current time.

## The digits image

Among the built-in images in Mini Micro is [/sys/pics/digits.png](https://github.com/JoeStrout/minimicro-sysdisk/blob/master/sys/pics/digits.png).  It looks like this:

![/sys/pics/digits.png](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2n0jyg1i5z1cm4g02j27.png)

Against a white background, this is pretty confusing, isn't it?  If you view it against a black background — for example, by doing `view "/sys/pics/digits.png"` in Mini Micro — it looks like this:

![/sys/pics/digits.png against a black background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5rvr5m0qwsdf4jo2wdet.png)

OK, now we can see that it has the digits 0 through 9, plus the letters A-F (handy if we wanted to make a [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) display).  But why are there two apparently-identical rows of digits?  The answer becomes clear if we view against a different background color, neither black nor white.  [Try](https://miniscript.org/MiniMicro/#playnow) this:

```
gfx.clear color.blue
gfx.drawImage file.loadImage("/sys/pics/digits.png")
gfx.scale = 4
```

![/sys/pics/digits.png, scaled up against a blue background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tahij78x4mmnma2b8jm2.png)

Aha!  Now we can see why there are two rows.  The top row has dark segments for the "off" bits of each digit, while in the bottom row, those areas are transparent.  Which you use will depend on how you want it to look.

For today's project, we'll use the top row, which will have the subtle but realistic dark segments.  This will allow us to simply overdraw a new digit on top of an old one, without having to erase first (and it also looks cool, if the background color is not pure black).  We'll also be using only the first ten digits; we will ignore A through F today.

## Pulling apart a sprite sheet

Even though we're not actually using sprites today, a big image that contains a bunch of smaller image is still called a "sprite sheet" (or sometimes an "atlas", but I prefer the former term).  So let's begin by loading digits.png and then pulling out the ten digits we need, as we would with any other sprite sheet.

Launch Mini Micro, if you haven't already (or use the [web version](https://miniscript.org/MiniMicro/#playnow) if you're OK with not saving your work at the end).  Use the `edit` command to launch the editor, and type in this code:

```
digitImage = file.loadImage("/sys/pics/digits.png")
dw = 10; dh = 18  // digit width and height
digits = []
for i in range(0,9)
	digits.push digitImage.getImage(i * dw, dh, dw, dh)
end for
```

This loads the image into a variable called `digitImage`, and then prepares a list (`digits`) with the individual digit images.  Each of these is `dw` pixels wide, and `dh` pixels tall.  We're getting the top row because the second parameter to `getImage` is `dh` rather than `0` — remember, coordinates in Mini Micro almost always count from the bottom up.  So the bottom row starts at Y = 0, and the top row starts at Y = 18 (or `dh`).

Run this code, and then test it by entering `view digits[4]` on the command line.  You should see a little digital 4 in the center of the screen.

## Some helper functions

Our goal is to draw a time string like "08:22:45".  We'll draw this one character at a time.  So, let's make two helper functions: one to draw a digit, and one to draw a colon.  `edit` your program again, and add the following.

```
drawDigit = function(value=0, col=0)
	x = col * dw
	y = 0
	gfx.drawImage digits[value], x, y, dw, dh,
	  0, 0, dw, dh, gfx.color
end function

drawColon = function(col=0)
	x = col * dw
	y = 0
	gfx.fillEllipse x + dw/2 - 1, y+5, 2, 2
	gfx.fillEllipse x + dw/2 - 1, y+12, 2, 2
end function
```

Both of these start by calculating the X and Y position to draw at, given the column position (`col`) where we want the character to appear.  `drawDigit` calls `gfx.drawImage` to do its work.  If we didn't care about tinting the digits, we could just do:

```
     gfx.drawImage digits[value], x, y
```

But we want to support drawing colored digits, so it can look like old-school red or green LED clocks.  So we have to supply all nine parameters to `[drawImage](https://miniscript.org/wiki/PixelDisplay.drawImage)`, just to get to the last one, which is the tint color.  (MiniScript does not support passing arguments by name, but only by position.)

The `drawColon` function starts by computing X and Y as well, and then just calls `fillEllipse` twice, once for each dot of the colon.  (Feel free to play around with the size and position of these dots later to make it your own!)

If you run this code, and then do `clear` to clear the screen, you should be able to enter `drawDigit` to make a little 0 appear at the bottom of the screen.  Also try `drawColon 1` to make a colon appear next to it (in column 1).

## Drawing the current time

To get the current time, go back to the top of your code and insert:

```
import "dateTime"
```

This loads the `dateTime` module (found in /sys/lib), which gives us access to the current date and time.  Then scroll to the bottom of your program, and add this function:

```
drawTime = function(time)
	if time == null then time = dateTime.now[-8:]
	for i in time.indexes
		if time[i] == ":" then
			drawColon i
		else
			drawDigit time[i].val, i
		end if
	end for
end function
```

This draws the given time string, or if you don't give it one, it gets the current time as the last 8 digits of `dateTime.now`.  Then it loops over that string, calling the `drawColon` and `drawDigit` functions we defined before.

Run this, `clear` the screen, and then enter `drawTime`.  You should see the current time appear in the lower-left corner.  Almost done!

## The main program

Finally, with all these helper functions in hand, we are ready to create the main program.  `edit` your code, and add this to the bottom:

```
clear
//gfx.scale = 8
gfx.scrollX = -150; gfx.scrollY = -350
gfx.color = color.red
while true
	drawTime
	wait
end while
```

This starts by clearing the screen, and configuring the main PixelDisplay (aka `gfx`) with a red color, and a scroll position.  (Instead of scrolling the display we could have also changed the calculation of X and Y in our draw methods — feel free to experiment!)  There is also a commented out line, `gfx.scale = 8`.  If you want to zoom in and have a closer look at your digits, feel free to uncomment that line.

After all that setup, the main loop is simple: it just loops forever, calling `drawTime` every second.

![Animated GIF of clock display](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rlvkdgkitacnnxl4nco3.gif)

When you run it, you should see a display like the above.  (Press Control-C to break out of the program.)

## Taking it further

This was a fun little exercise in pulling apart a sprite sheet, making some drawing helpers, and getting the current time.  But the same digits and drawing method could be used for lots of other purposes.  Some ideas:

• Make a countdown timer for a bomb in a bomb-defusing game
• Make a doomsday clock showing days, hours, and minutes to the next election
• Show game score in a retro digital display
• Make a CPU emulator (like [CHIP-8](https://github.com/JoeStrout/minimicro-chip8)) with digital hexadecimal displays for its internal state

These are just a few ideas.  Do you have any others?  Questions about anything presented here?  Post them in the comments below.  Happy coding!