The Apple 2 (usually written Apple ][
) computer was one of the first widely popular home computers. At the time, most home computers came as kits and included no keyboard or text display; you got some switches for input and blinky lights for output, and if you really wanted to spend some cash, you got a teletype machine to go with it.
So when Steve Wozniak designed an affordable home computer that actually came with a full keyboard and glowing green screen, people got pretty excited! The first computer in my house was actually a TI-99/4A (on sale for $50 at K-Mart), but we quickly outgrew that, and got an Apple //e, which served us well for years.
Recently I found myself feeling a bit nostalgic for that old green screen, and decided to see if I could recapture some of its look and feel in Mini Micro. Mini Micro itself is a "neo-retro" virtual computer, but its screen has much higher resolution (960x640) compared to the early Apples (280 x 192). This is reflected in the appearance of the text, which on Mini Micro, is normally quite sharp:
Each character on a Mini Micro text display is 24 pixels high, whereas on the Apple ][, characters were only 8 pixels high.
The Apple ][ Text Font
Somewhere on the Internet I found this image containing all the characters the Apple ][ could display:
This image is 16 columns wide, and 8 rows high, for a total of 128 characters — the complete ASCII character set. There's nothing tricky about the arrangement; each character is 8 pixels wide and high, and they're in ASCII order, starting at the top left and proceeding row by row.
In short... this is set up exactly like a tile set you might use with Mini Micro's TileDisplay!
...in a TileDisplay
To display text using characters from this image, I launched Mini Micro, reset
to a clean state, and used edit
to enter this code:
clear
text.row = 0
display(4).mode = displayMode.tile
td = display(4)
td.tileSet = file.loadImage("AppleIIChars.png")
td.extent = [45,26] // columns and rows of text
srcW = td.tileSet.width / 16
srcH = td.tileSet.height / 8
td.tileSetTileSize = [srcW, srcH]
td.cellSize = [24, 24] // size of each character on screen
td.overlap = [3,0] // 3-pixel horizontal overlap
td.scrollX = -7; td.scrollY = -7
td.clear "@".code
Why not fire up Mini Micro yourself, and follow along?
This code sets display 4 to "tile" mode, loads the AppleIIChars image as the tile set, and tells Mini Micro how big the source tiles are, and how big those should appear on screen. I also apply a small horizontal overlap, since even though the cells are 8x8 in the image, the Apple actually used only 7 pixels (width) per character. Finally, I set scrollX and scrollY to center the tiles on the screen, since there is a bit of extra space around the edges that we can't quite use.
The line td.clear "@".code
tells the TileDisplay to clear to the at-sign symbol. (The code
method gets the ASCII code for the first character of the string it's called on.) The result looks like this:
Not a bad start!
Making an a2
module
Next, I wanted some code that lets me start manipulating this TileDisplay with Applesoft-like commands. To keep things neat, I put these functions into a map called a2
— that way it's easy to tell the difference between a2.print
and regular print
.
But the first function we want is something to clear the screen, and in Applesoft, that was called HOME (because it both cleared the screen and returned the cursor to its home position in the top-left corner). So:
a2 = {}
a2.home = function()
td.clear
a2.row = td.extent[1] - 1
a2.col = 0
xrange = range(0, td.extent[0])
yrange = range(0, td.extent[1])
td.setCellTint xrange, yrange, "#22CC22"
end function
a2.home
Notice the other trick this code does: it tints the entire display green (color "#22CC22"), to match the appearance of the original Apple green screens.
If you're following along at home, and run your program at this point, it will appear to be doing not much — the screen just clears, and you're left with a standard Mini Micro prompt. Our tile display is there, but it's invisible. We need a bit more code!
a2.nextLine = function()
self.col = 0
self.row = self.row - 1
end function
a2.putchar = function(c)
i = c.code
if i > 127 then i = i - 128
if c != char(13) then td.setCell self.col, self.row, i
if c == char(13) or self.col == td.extent[0] then
self.nextLine
else
self.col = self.col + 1
end if
end function
a2.print = function(s)
for c in s
a2.putchar c
end for
a2.putchar char(13)
end function
Now we're getting somewhere! After running this code, the screen is still blank, but you can now type something like:
a2.print "Hello!"
and it should appear in nice blocky text at the top of the screen!
Much Printing
I finished my demo program with this code:
a2.print "Hello world!"
a2.print "Welcome to the Apple II ""font"""
a2.print "(really a TileDisplay)."
a2.print
a2.print "Very much like the Apple II display, except"
a2.print "a little bigger (45x26 rather than 40x24)."
a2.print "The quick brown fox jumps over the lazy dog."
a2.print "Neat, huh?"
...and the result looks like this:
Of course you can print whatever text you like!
Regular, or Extra Crispy?
My one complaint with this result was that the text looked too crisp! The displays of those days did not have nice clean square pixels. Instead, each pixel was a glowing little blob of light, and because of how a CRT display works, these blobs ran together much better horizontally than vertically.
Because we're drawing the characters three times bigger than their definition in the tile set, we have an opportunity to recapture some of that extra flavor. But to do so, we need to make the image three times bigger, and modify it a bit.
I first tried to do this in an image editor. Scaling the image up 3X is easy enough, but then I wanted to add a bit of glow horizontally, and darken every third row to give the appearance of scan lines. And I couldn't figure out how to do that in the image app. So, I ended up doing it with MiniScript code instead! I wrote this separate program:
clear
gfx.scale = 3
img = file.loadImage("AppleIIChars.png")
gfx.drawImage img, 0, 0, img.width*3, img.height*3
// Add a bit of horizontal bleed
for x in range(0, img.width*3)
for y in range(0, img.height*3)
c = gfx.pixel(x,y)
if c != "#000000FF" then continue
if gfx.pixel(x+1, y) == "#FFFFFFFF" or
gfx.pixel(x-1, y) == "#FFFFFFFF" then
gfx.setPixel x,y, "#AAAAAAFF"
end if
end for
end for
// Then add scan lines
for x in range(0, img.width*3)
for y in range(0, img.height*3, 3)
c = gfx.pixel(x,y)
if c == "#000000FF" then continue
gfx.setPixel x, y, color.lerp(c, "#000000FF", 0.25)
end for
end for
img = gfx.getImage(0, 0, img.width*3, img.height*3)
file.saveImage "AppleIICharsBig.png", img
This draws the original image to the gfx
display, three times bigger — and that display itself is scaled 3X just to make it easier to see what's going on (so each character now appears 9X bigger than the original image). Then we have a loop that adds some horizontal bleed, by looking for black pixels next to a white pixel, and changing those to gray. And then a second loop which darkens (using color.lerp
) every third row. Finally, the result is saved out to a new file, "AppleIICharsBig.png", which comes out like this:
Going back to our first program, to use this new tile set, we only need to change the line that loads the image:
td.tileSet = file.loadImage("AppleIICharsBig.png")
And now when we run it, we get:
Beautiful!
Taking it Farther
My
a2.nextLine
method does not handle scrolling the text up when it gets to the bottom of the screen (row 0). Can you make it do that?If you really want to drive down nostalgia lane, how about hooking this display up to MiniBASIC?
We baked our scanlines into the character images, which is fine if we're only displaying text... but what if we draw graphics? Can you think of a way that you could make scanlines appear on all display types, including sprites and pixel graphics?
Have fun, and happy coding!
Top comments (0)