A Trash-80 Christmas
Back in 2022 I decided I wanted to create a digital Christmas card that I could send out via email. To make the project more interesting, I decided to make an animated card for the first computer I used seriously — a Tandy (Radio Shack) TRS-80.
The TRS-80 was where I saw the program that really got me interested in programming. The code was in the Level 1 BASIC manual as an example, titled On A Snowy Evening. It displayed the Robert Frost poem line by line, accompanied by white pixel snowflakes that gradually collected on screen. You can see what it looked like in a YouTube video. As the manual put it, “Who says computers only make stuffy mathematical calculations and are not for folks who appreciate the better things? If this one doesn’t grab you, nothing will.”
It did. That program made me realize that computers could be a new means of communication, that they could be used for new kinds of art. All my subsequent hobby programming was towards those goals.
So here’s the story of how I went back to that era and built a retro TRS-80 Christmas card in 2022.
The hardware #
To explain some of the challenges of this project, I’ll start with an overview of the TRS-80 hardware.
While the Apple II always gets the credit for being one of the first and most important personal computers, the TRS-80 launched just two months later, in August 1977. The Apple II launched at an entry level price of $1,298, but the TRS-80 was just $599.95 for a complete system — computer, monitor, and a cassette deck for storage. Converting to 2025 dollars, that’s an eye-watering $6,957 for an Apple II, and a still-expensive $3,216 for a TRS-80. The Apple II was even more expensive in the UK, because until the 1990s Apple inflated their UK prices by 20% or more.
Sure, the Apple II had color graphics where the TRS-80 was monochrome, but a lot of families could never dream of affording Apple prices. There was also the fact that you could walk into any Radio Shack store (Tandy in the UK) and see — and maybe buy — a TRS-80, whereas to get an Apple II you’d have to go to a specialist computer store.
Here’s an old photo of my TRS-80, complete with the cassette deck used for loading and saving programs:

That was a Level 1 TRS-80. It came with 4 KiB of RAM, and had a Zilog Z80 CPU running at 1.774MHz. To compare, I have a device here that runs at 1,590 MHz and has 1 GiB of RAM. That’s 262,000 times as much memory and about 900 times the speed. It’s my wristwatch.
Graphics? Oh yeah, the TRS-80 had graphics. 128 pixels horizontally by 48 pixels vertically. Two colors, black or white, because the screen was a converted black and white television.
The screen layout was actually 64 by 16 characters. Each character cell could be set to display a character code from 128 to 191, in which case it would display a set of 2×3 pixels. The character code determined which pixels were on and which were off. You could have either pixels or regular ASCII characters, but you couldn’t mix both in one cell of the screen layout. Also, the text characters appeared in the upper two thirds of the character cells, so you needed to account for that when trying to align pixels and text.
Here’s how a few characters might be arranged on screen:
Here’s a BASIC program to print something like the above:
10 PRINT "TRS-80"
20 PRINT "GFX";CHR$(152);CHR$(178);CHR$(131)
There were also keywords SET and UNSET in TRS-80 BASIC to make it easy to draw pixels using their coordinates on the screen, rather than having to calculate ASCII codes. The downside, of course, is that it was slower to draw individual pixels.
Since 64×16 characters is 1024, the TRS-80 screen corresponds to 1 KiB of video RAM.
Well… kinda.
The machine actually had 1024 7-bit bytes of video RAM. It had to be static RAM for the video controller, and static RAM was expensive, so to save money Radio Shack left out bit 6. That meant no characters in the range 60 to 7F hex, which meant no lower case. If you paid around $100 (plus installation) Radio Shack would upgrade your TRS-80 to have an extra 1024 bits of static RAM and an updated character generator, and you could get lower case.
There was no sound card. Some games would produce sound by sending data to the cassette port, producing square waves that you could hear if you plugged in an amplifier. I decided I’d skip trying to do anything with sound.
The programming #
I don’t have my TRS-80 any more, so I was going to have to use an emulator. That was fine, because nobody I planned to send the card to had a TRS-80 either. There’s a JavaScript TRS-80 emulator you can run in any reasonably modern web browser, so my plan was to give people the URL of a page that would load up the emulator and start whatever program I came up with for the actual card. With that in mind I sat down with trs80gp, a more full-featured emulator, and started work.
The first thing I discovered was that I had forgotten just how slow a 1970s computer is. I had memories of games written in BASIC, and how tough it was to make them fast enough to be playable unless they were really simple. Most commercial software of the day was written in machine code to get it to be fast enough. I’d written some Z80 machine code back when I was a teenager, but I couldn’t remember anything much about it, except that I hadn’t enjoyed it. This whole exercise was supposed to be fun, so I wanted to write the program in BASIC.
Here’s the TRS-80 emulator running at authentic speed, running a small BASIC program that prints the character set:

Clearly speed was going to be a problem. However, with 4 KiB of RAM I could fit a screenful of pre-encoded character data into my program and send it directly to the screen, bypassing the need for slow pixel-by-pixel plotting. So I decided I would draw a static image, then add a bit of animation as a finishing touch.
The first step was to put together an image of a Christmas tree, and maybe some gift-wrapped presents. Back in the day the TRS-80 manual included some handy graph paper for working out how to draw images, but I thought I’d cheat and use some modern graphics software so I could easily tweak the image until it looked good.
There was a problem with that plan, though: if you look carefully at the images above, you’ll notice that TRS-80 pixels are rectangular, about twice as tall as they are wide. That might seem weird now, but it was pretty common for computers to have rectangular pixels in the ’70s and ’80s. By the ’90s, though, pretty much everyone had settled on square pixels. There’s special-purpose pixel art software available, but I was unable to find any that would handle rectangular pixels. Photoshop will, so that you can use it to design DVD menus and TV graphics, but I wasn’t about to sign up for a subscription for that.
I decided it was time for some programming, but not in BASIC. I wrote a Go program to read in a PNG file, read pairs of lines of pixels, and calculate the equivalent single lines of TRS-80 pixels. The code then worked out the ASCII codes for the appropriate six pixel blocks, assembled them into strings, and wrote the result out as BASIC PRINT statements, one per line of characters.
The TRS-80 does have one trick for doing this kind of thing more efficiently: the characters from 193 up to 255 can be used to compress empty space in strings. Printing character 193 gives you a space, 194 gives you two spaces, 195 gives you three spaces, and so on. Basically, run-length encoding, but only for spaces. I made my Go code use that feature.
I loaded the resulting BASIC code into the emulator, ran it, and stared at the result. It looked pretty awful. After a while I realized the problem: TRS-80 pixels aren’t quite 1:2 ratio, they’re somewhere in between 1:1 and 1:2. My cunning plan wasn’t going to work. I gave up and got used to imagining how things would look when they were stretched vertically, and drawing squashed bitmaps. It took a while, but I eventually ended up with something acceptable.

I wanted the finished card to have snow falling, so next I came up with a very simple snow animation algorithm and experimented to see how many snowflakes (pixels) the TRS-80 could animate in BASIC at an acceptable frame rate.
The answer was: about six. My dreams of full-screen animation died.
In the grand tradition of technical constraints leading to design constraints, I decided I’d draw a small window frame to the right of the tree and animate some slowly falling snow in the window. That left me with a lot of empty space left to fill, so I went back to the PNG file, drew in the window frame, and added some fancy text saying Merry Christmas in a cursive font. There then followed a lot of tweaking and testing. It turns out that modern cursive fonts don’t look good when they’re 10 pixels high, so I basically ended up redrawing the text pixel by pixel.
Finally I had something I thought looked good. There was space at the top for greeting text, and space at the bottom for a signoff.

Which brings me to the next minor complication: I’d decided I wanted my digital Christmas cards to be unique, each one personally addressed to its recipients. No problem, I made my Go code start with a template that contained the bulk of the BASIC code. It would calculate and insert the static image data, add in custom greeting and signoff from a CSV file, and then write the result to a file.
I started putting together a spreadsheet of recipients, and it was at that point that I remembered that some of the recipients were Jewish, and others were the kind of nonbelievers who might object to being wished a Merry Christmas. Uh-oh.
I thought about whether I could come up with a whole new Hanukkah variant with a dreidel or a menorah. I didn’t fancy my chances of drawing a dreidel with recognizable Hebrew characters using TRS-80 graphics. A menorah would logically have required animated flames, which would have meant entirely new animation code that would need to use precalculated flame graphics to be fast enough. I’d basically be going back to square one.
Eventually I decided that I could probably get away with a variant that had a neutral Season’s Greetings message but still had the tree. I made a new bitmap and hacked the Go code to switch bitmaps based on a flag in the spreadsheet data. The compression left the graphics taking up about 780 bytes, with the entire program coming out to about 1,600 bytes.
The end result #
At last I was able to run the Go code and have it generate 55 custom digital Christmas cards — well, holiday cards — each with a unique filename generated from a hash. I put the files in a directory alongside the JavaScript TRS-80 emulator, uploaded it all to my web server, and made sure I could make a card load and run by specifying the hash as a query string in the URL. I also had to fix the CSS to make the emulator window size itself appropriately on both desktop and mobile browsers, which was honestly the most frustrating part of the whole exercise.
The end result looked something like this, if you imagine the snowflake pixels slowly drifting down:

The final step was to write some more code — in Ruby this time — to send an email to each recipient with their individual URL.
So that’s the story. Overall, it was an interesting trip down memory lane, and a reminder of just how far computers have come since the late 1970s. But while today’s computers are a much more pleasant experience, we’ve also lost things — you can’t just turn a computer on and start programming any more, and the amount you have to know to draw some pixels on the screen on a modern computer is completely ludicrous.
I didn’t make a digital card for 2023 or 2024, but I’m making one this year. It’s going to be something completely different.