Curses is a venerable API for interacting with computer terminals on a per-cell basis. What follows are some quick and unfinished notes about using the PDCurses implementation with C terminal programs in Windows 10 while I dabble with the library.

Many Curses functions return ERR (-1) upon failure. My notes do not cover error handling for all of these cases, because the tutorials I find on the net don’t seem to either. Nonetheless you may want to catch these error codes if you are writing something more serious.


Curses resources

Pradeep Padala’s venerable NCurses Tutorial

Invisible Island NCurses FAQ

Curses Questions and Answers on Stackoverflow

(Note: ncurses is for POSIX systems. PDCurses is a different implementation with support for Windows.)


Adding PDCurses to a Code::Blocks project

I seem to forget how to do this every time I reinstall Windows. There is video walkthrough by Cymonsgames here.


Startup and Shutdown

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "curses.h" // Get Psyched!

int main() {
  initscr();        // Start Curses.
  start_color();    // Initialize color.

  raw();            // Accept raw keyboard input, so you don't have to
                    // strike Enter after every keypress.

  noecho();         // Don't echo user input back to the screen.

  keypad( stdscr, TRUE ); // Accept arrow keys, function keys, etc as input.

  endwin();         // When you are done, free Curses resources and return
                    // to normal command prompt behavior.

  return 0;



Printing text and drawing individual characters

Curses has a suite of printing functions, from printf()-style variable length formatted strings, to plotting individual characters. I use mvprintw() and mvaddch() most often, along with attron() and attroff() to control color and brightness.

 mvprintw( 10, 10, "Hello, World" ); // Print text (Y, X, printf-style
                                     // formatted string).

 attron( A_REVERSE );                // Reverse the FG / BG colors going
                                     // forward.

 mvaddch( 10, 22, '!' );             // Draw a single character.

 attroff( A_REVERSE );               // Disable reversed attribute going
                                     // forward.

 refresh();                          // Force screen update.

 getch();                            // Wait for user input.



With raw(), getch() will wait for the user to press a key, and return a key code. You can use getch() alone without an assignment to pause the program until any key is pressed. Many keys can be checked by their char literal value or escape sequence (like “\t” for the tab key), and PDCurses defines many macros to represent non-typewriter keys, such as KEY_LEFT, KEY_UP, etc, for the arrow keys.

int input = 0;

while( input != toupper('q') ) {
    input = getch();

    clear();             // wipe screen of previous contents
    mvprintw( "You pressed key %c, code %d.", input, input );

PDCurses doesn’t seem to have macros for the number pad when Numlock is on, so you will have to define those yourself.

Some keys generate multi-byte scancodes, particularly the Escape key. You may need to call getch() again to confirm. (Esc is scancode 27 followed by ERR (-1)).


Cursor control

The blinking cursor moves around as Curses draws and prints to the screen. If you want, you can manually position the cursor at the end of your update routine, or disable it.

curs_set(0); // 0: Don't show
             // 1: Blinking underline
             // 2: Blinking square.

move(y, x);  // Specifically move the cursor to a
             // coordinate.

Color Pairs

Though Curses does define macros to represent eight colors (0 through 7 — COLOR_BLACK, COLOR_WHITE etc), you can’t pass these to the Curses drawing functions directly. Instead, you initialize a series of color pairs, and pass the index of that pair as an attribute. If you want to address all 8 foreground and 8 background colors independently, you can initialize 64 pairs with each combination.

(Note: Color Pair 0 is already initialized, and reserved for monochrome mode.)

int cursesIndexColorPairs(void) {
    int n_colors = 8;
    int color_index_start = 1;
    int i;

    for( i = 0; i < n_colors * n_colors; i++ ) {
        if( init_pair( color_index_start + i, i % n_colors, i / n_colors ) == ERR) {
            return ERR;
    return 0;


Bright and Dark colors


The Windows command prompt supports legacy IBM PC Text Mode colors. There are 16 colors total, but they are accessed as 8 base colors, plus a brightness flag. The foreground and background of a given character get their own color and brightness parameters. In DOS, if the background color has a brightness flag, then the foreground color will blink on and off and the background intensity will be unchanged. A non-fullscreen Windows command prompt will just draw the background color as bright.

PDCurses will draw a character with a bright foreground if A_BOLD is passed as an attribute, and it will draw with a bright background if A_BLINK is passed, and both if they are passed sequentially or binary OR’d together. I can’t imagine A_BLINK serving as a bright background would be portable on other systems, but it’s the only attribute I could find that did so on Windows 10.


#define N_COLORS 8

int colorGetPair(int fg, int bg) {
  // return monochrome (pair 0) if requested colors are out of range
  if(fg < 0 || fg > N_COLORS || bg < 0 || bg > N_COLORS ) {
    return 0; 
  else {
    return 1 + fg + (bg * N_COLORS);

void colorSet( int fg, int bg, int fg_intensity, int bg_blink ) {
  if( fg_intensity ) {
    attron( A_BOLD );
  else {
    attroff( A_BOLD );

  if( bg_blink ) {
    attron( A_BLINK );
  else {
    attroff( A_BLINK );

  attron( COLOR_PAIR( colPair( fg, bg ) ) );

int main() {

  // ... snip
  colorSet( COLOR_MAGENTA, COLOR_CYAN, 1, 1 );
  mvprintw( 0, 0, "\"We need more searing Magenta and Cyan,\"\n said the CGA Project Lead." );

  // ... snip



getch() timeout via halfdelay()

halfdelay() will add a timeout to getch(), measured in tenths of a second, if no user input is received. It can be used as a 10FPS sleep timer for animations, while still allowing user intervention. Use nocbreak() to disable the timeout.


Screen Dimensions

Upon calling initscr(), the display dimensions are stored in two variables: COLS (width) and LINES (height). These variables do not seem to update upon resizing the window, but if you change the size settings, exit and restart the program, the new dimensions will be detected. My understanding is that other Curses environments are able to handle runtime screen resizing.


Windows 10 command prompt blanking on resize

If you find the Windows 10 command prompt is blanking out the display upon resizing the window:

  • Right-click the window title bar and choose Properties
  • Goto the Layout tab
  • Uncheck Wrap text output on resize




With some caveats, you can basically drop in the SDL version of pdcurses.dll, along with v1.2 of sdl.dll in place of the command line version. Screen dimensions are 80×25 cells by default — to adjust, it seems you need to change environment variables pdc_lines and pdc_cols. You can do this within your program prior to init_scr() with putenv(), or you can set the environment variables temporarily within a command prompt with the SET command.

putenv() :

putenv( "pdc_lines=20" );
putenv( "pdc_cols=20" );

// ...


Windows SET:

C:\set pdc_lines=20

C:\set pdc_cols=20



In SDL mode, you can drop in a new monospace bitmapped font with new dimensions — upon calling initscr(), PDCurses will search for and use pdcfont.bmp in the working directory. The PDCurses page on SourceForge has a set of example fonts. The font layout is Code Page 437. (Update: I’m having issues changing the foreground color when using a custom font — maybe I’ve missed a setting somewhere.)


Todo List for this post

  • Extended Character Sets (AFAIK no-go in SDL mode, but Code Page 437’s symbols should be available.)
  • Portability between PDCurses and NCurses
  • Mouse support: Curses defines structures and functions for reading mouse events. If this actually works in PDCurses and Windows 10, I would assume the command prompt application’s own mouse interactions (pausing everything to allow highlighting and copying) would interfere. But maybe the SDL port would be better for that. Will try it sometime later.
  • Setup of NCurses on Ubuntu
  • refresh() : Pradeep’s NCurses tutorial says that drawing is queued and refresh() updates the screen, however it seems that draw updates show up just fine in my programs without it. Maybe this doesn’t apply to my specific OS, or to PDCurses, or I’m missing a setting somewhere.
  • clear() : clear() wipes the screen. I have found on Win10 that it sometimes causes flicker. TODO investigate.
  • Modifying terminal colors: I can’t get this to work.
  • Curses Windowing: I haven’t touched Curses’s own windowing system. It seems to be intended for non-overlapping screen sectors.
  • Project Pluto’s PDCurses fork is a fuller implementation. This looks awesome and I need to dig into it sometime.
  • Foreground color change not working for me when I use a custom font in SDL mode.

(e: 6/Nov/2016: Screen dimensions)
(e: 11/Nov/2016: Input, Screen dimensions. SDL stuff)
(e: 3/Feb/2017: Color attribute wrapper, wording)
(e: 11/Feb/2017: Wording; corrected pdcfont.bmp; added putenv(); added a note that foreground color change isn’t working for me when using custom fonts with the SDL DLL.)

Thrift Log: Microsoft 1381 Webcam (LifeCam VX-2000)



I’ve been trying to figure out what to do for pictures in these thrift store posts. I’m one of those weirdos who doesn’t have a smartphone — nothing wrong with them, but I’ve never been able to get over the initial hurdle of cost, combined with their throwaway nature. I didn’t have a cellphone of any kind, period, until my old job put me on a weekend on-call rotation. And though my flip-phone (which is basically just a big pocket-watch that I have to charge, considering how often I use it) does indeed have a built-in camera, there appears to be no way for me to transfer those photos to another device.

The 1381 webcam is frankly pretty obsolete, but it works with Windows 10, and will suffice for the purposes of this blog. Not too bad for three bucks.

Edit 28/Nov/2016: I caved, and ordered an Android smartphone to help with my current job. It was not three bucks 🙂

Thrift Log: Intellivision TV Play Power


Paid six bucks. Disappointing unit for a variety of reasons.



The Intellivision had a crazy dial-and-keypad controller with side buttons. It supported plastic overlays for game-specific control instructions, and the dial could detect 16 directions of movement. Some would argue that the Intellivision controller was overly complicated and a pain to use, but it’s a huge part of the system’s character.

This Plug-and-Play unit uses a pretty standard layout with a d-pad, thumbstick and some face buttons. However, the directional controls are very stiff and unresponsive, and the thumbstick above the d-pad also behaves like a d-pad — not capable of detecting more than 8 directions.

So while it’s a concern that the unit doesn’t actually resemble an Intellivision controller, it’s overshadowed by not even being a competent controller.



The underlying architecture isn’t Intellivision: the games featured are reprogrammed for clone NES hardware, and if you look closely at the games, you can spot NES graphical artifacts like map palette corruption at the edge of the screen during 8-way scrolling (Hover Force).


Turn off your high-beams

Lastly, the unit has a red LED power indicator that beams in your eyes while you play.


Included games

I see there is a 10-game version that looks more like a Sega Genesis controller. The one I picked up has 25 games. Some of them are pretty cool, but the shoddy controls and inaccurate representations of the included games are a problem.

THUNDER CASTLE (missing its soundtrack!)


Not recommended.

Thrift Log – Microsoft SideWinder Plug & Play Game Pad (X04-97702)


This USB controller works out of the box with Windows 10, which is nice, considering that the box says “Designed for Windows 98”. Wikipedia says the release year was 2000.

The grip feels pretty decent, but I don’t know what to think of its directional pad. It’s a weird, smooth, inverted bump that can be pushed down in addition to being nudged in the cardinal and diagonal directions. The travel downwards is not a control or button. Honestly, I’m not sure why it does that.


I had to grab one of these just for the camp factor. Have you seen those SideWinder marketing videos?

SideWinder FreeStyle Pro

SideWinder Dual Strike

SideWinder Strategic Commander

SideWinder Force Feedback Wheel

Granted, this Plug-and-Play unit is pretty spartan and ordinary in comparison. I probably dropped too much on it ($8, which is approaching the cost of an iBuffalo USB SNES pad clone), but I’ll keep it plugged in for a week and try some games with it and report back on whether it’s any good.

Update: So I played some Shovel Knight and Umihara Kawase with this controller. The right-side action buttons are A-OK, as are the left and right shoulder buttons. As for the directional pad, my left thumb feels like it’s been through a workout. Since the d-pad has a concave indentation (maybe better described as a crater), the edges of your thumb are constantly being squeezed against a circular ridge of plastic. The dpad is also rotated slightly clockwise, and it’s not always clear where straight down or straight up are by feeling alone. The controller has transparent plastic, and you can see that the d-pad component is mounted to a larger PCB disc which is responsible for pushing down on the contacts. It’s almost like a short joystick that’s been recessed into the controller. Weird.

Unfortunately, the dpad really sabotages this controller.

Thrift Log – Microsoft Sidewinder X6 Keyboard


Found for about $6 CAD. I ended up grabbing it out of curiosity for the weird dials, numerous macro keys, and the Microsoft Sidewinder branding.

Sidewinder was Microsoft’s pre-XBox, “extreme” gaming-oriented peripheral brand, and they slapped the name onto various input devices. The Wikipedia article on Sidewinder goes into some detail on the history, cancellation, resurrection and re-cancellation of the brand. Some of the controller designs are pretty out-there. This keyboard hails from around 2008.

The Sidewinder X6 has a column of macro keys along the left side, a bunch of macro keys above the Funtion Key row, and two curious dials on the top-right. The underlying mechanism is a rubber dome membrane. To use the macro keys, you need to install Microsoft’s Mouse and Keyboard Center software.

So, why two dials? One is for volume, and the other… drum roll … dims the red and amber lighting beneath the keys.


(Re: the brighter light coming from the ‘S’ key: somebody scraped away the paint on that key for whatever reason.)

Where’s the number pad? The number pad is a separate component that can be connected to either the left or right side of the keyboard. I like the idea of a modular keyboard, but the downside is that the components can be lost: the unit I found didn’t come with this proprietary attachment.


I don’t really have a use for the macro keys, but gimmicks aside, it’s a decent backup USB keyboard. One person on Amazon really didn’t like the placement of the Esc key (on standard layouts it appears above tilde (~), but on the X6 it’s over further to the left).

Thrift Log – Cicero Pentium 4 PC


(This is too big for me to photograph in a convenient manner, but here is the Cicero badge on the front for posterity.)

Cicero is an old Future Shop / Best Buy brand, used to sell PCs from other manufacturers. I picked this up yesterday because I have a thrift store addiction the motherboard has an AGP video slot (I’ve got an AGP card lying around with an S-Video port that I want to try sometime), and the case seems to be standard ATX with decent build quality. It cost about $10 CAD.


Stats upon acquiring:

Core Components

  • Case: F-IWF2-03
    • I couldn’t find any relevant webpages on this part number, but it seems to be a standard ATX mid-tower. Build date 9/28/2001.
  • Motherboard: Gigabyte GA-8IDML REV 2.0
  • CPU: Intel Pentium 4 @ 1.5 GHz
  • Memory: 1 x Spectek 256MB PC133 SDRAM
  • PSU: TurboLink ATX-CW420W (Standard ATX)


  • Hard Disk: Maxtor D540X-4K (PATA, 3.5″, 40 GB)
  • 3.5″ floppy disk drive: Alps Electric DF354H090F
  • CD-R/RW: LG CED-8120B (PATA)


  • Video Card: NVidia NVTNT2MA


  • Wi-Fi: D-Link AirPlus G CDWLG510..B1 [sic]
    • Detachable antenna is cracked
  • Modem: AMKO MDP3900V-U(B)


  • OS: Windows XP Professional Edition
    • Whoever previously had this system wiped the hard disk and performed a fresh install of Windows XP, but didn’t activate it, and didn’t install the correct version for the serial key sticker on the box. Wiped and reinstalled.


With only 256 MB of RAM, using Windows XP on this machine is not too pleasant. Gigabyte lists Windows 98 drivers for this motherboard, so maybe Win98SE would be a good fit. The case is a good candidate for gutting, maybe for a low-end server, though ventilation could be an issue for anything producing a lot of heat.


Edit 19/Sept/2016: 3.5″ Floppy Drive, not 3.25″

Cave Noire


Konami’s 1991 Cave Noire is a bite-sized dungeon crawler for the original Game Boy. Released in Japan only, it seems to have come and gone without much notice, outside of a small following on the internet. With minimal story and randomized dungeons, it can be considered a cut-down take on the Roguelike genre, with an emphasis on evading monsters.


Cave Noire has four dungeons with 10 difficulty levels each. Each dungeon uses mostly the same creatures and chamber data, but they have unique background tilesets, and the win conditions are different.

Once a dungeon’s win conditions are met, you need to find an escape door and get out alive. Completing difficulty level 1 of a dungeon will let you play level 2, and so on, up to the tenth level (called M). You win the game by completing difficulty level 6 of every dungeon, but you can continue beyond that, up to M if you want.

You can travel down a floor by using a staircase tile, or by dropping down a pit, but you cannot return to a previous floor. The dungeons vary between short floors with one or two chambers, and larger floors with up to about 8 chambers. Monster and item information is persistent for as long as you are on a floor, so if you dropped a health potion in a previous room, it will still be there on your return. While the dungeons do have a bottom floor, it is very unlikely that you will ever get there before you die or fulfill your win conditions.


cn_quest_1Dungeon 1: Defeat Monsters

You’re given a quota of monsters to destroy. On higher levels, this quota gets pretty high, and you need to pick your battles carefully.


cn_quest_2Dungeon 2: Collect Gold

Strangely, gold has no purpose in Cave Noire outside of this one quest — there is nothing to spend it on. Gold is found in treasure chests, lying on the ground in some rooms, and is occasionally dropped by the tougher enemies when defeated. Larger batches of gold are found on lower levels of the dungeon.


cn_quest_3Dungeon 3: Collect Orbs (Goblets)

Pick up a certain number of orb / goblet items and escape the dungeon. This might be the most challenging quest, because the orbs that you collect will occupy your inventory slots, squeezing out useful spells and potions. On the ‘M’ difficulty, the required orbs will take up your entire inventory.


cn_quest_4Dungeon 4: Rescue Fairies

Locate keys in the dungeon, and then use them to free captive Fairies. Rescuing a Fairy will destroy all enemies and partially remove fog from the screen.



Cave Noire features monsters with variable sizes, such as dragons that occupy four tiles, and different movement patterns. Some of them chase the player, while others patrol vertically or horizontally, or move in a small circle, or hug the walls.

Monsters will attack when you are adjacent to them, or retaliate after you strike them first. But you can also evade monsters indefinitely, even if they are right behind you, as long as you keep moving.

loop1 loop2



Periodically, you will find yourself on a dark or foggy floor. The fog masks all terrain hazards, and enemies are harder to identify (they’re drawn as a pair of cliche “evil eyes” when on a fog tile). When you move, any fog around your eight neighboring cells will be lifted. You have to tread carefully through fog so as not to fall down a hole (travel to next floor and take 4 damage), fall into lava (instant death), or get cornered by a monster.

Some resources on the game:

Monster GIFs

To the best of my knowledge, this is every enemy in the game:

cn_enemy_stationarycn_enemy_zombie cn_enemy_spider cn_enemy_skeleton cn_enemy_knight cn_enemy_minotaur

cn_enemy_mimic cn_enemy_ghost cn_enemy_gas cn_enemy_grim_reaper cn_pointer cn_fire

cn_crab_small  cn_enemy_centipede cn_enemy_stump

cn_enemy_big_bat cn_goblins_framerate_fix cn_enemy_giant_enemy_crab

cn_serpent cyclops

 cn_enemy_hydra  cn_enemy_big_guy_recropped

cn_enemy_big_dragon_white_framerate_fix cn_enemy_dragon_dark

cn_dragon_more cn_another_dragon


(e 24/Sept/2016: wording)
(e 18/Nov/2017: wording)
(e 2/May/2018: translated manual link)



XMPlay is a free and lightweight media player for Windows, with support for a wealth of custom audio formats and Winamp-compatible plugins.

One of XMPlay’s best interface features is configurable global-focus shortcut keys. You can configure volume up/down, skip/rewind, and next/previous shortcuts which work while your focus is on another program, even when in Full Screen mode. I like to use it for playing and controlling podcasts while doing something else.


One thing XMPlay doesn’t seem to handle intuitively is numbered files without leading zeroes. For example, loading a set of 12 songs labelled:


The order will be:

And so on. But if the files are tagged appropriately, you can sort them by metadata such as track number by right-clicking on the question mark button in the playlist window.


MIDI Soundfont support

XMPlay’s native MIDI plugin can be configured with soundfonts to sound nicer than the standard Windows MIDI playback. A pretty decent soundfont (Chorium) is provided on the plugin’s download page.

Other soundfonts:

¥Weeds¥ General MIDI Soundfont by Rich Nagel. A nice alternative to Chorium.

Regression FM – Makes everything sound like a Sega Genesis game. I can’t find the author’s website anymore, but the Regression FM Youtube channel has a download link for the soundfont under the ‘About’ page.

OPL3-FM 128M by Zandro Reveille – FM synth timbres by The Fat Man, sampled from an ISA Sound Blaster 16. Can be found on the author’s website or on Github.

Vintage Dreams by Ian Wilson – Vintage synthesizer theme, can be found on this webpage.

(e 11/Nov/2016: Images, sorting by metadata)
(e 13/Nov/2016: OPL3-FM 128M)


Vintage PC Notes

These are some scattered notes about vintage PC configuration and maintenance, mostly related to a Dell OptiPlex GX1 (Pentium III CPU) that I adopted from work. Subject to change, no warranty, etc, etc.


Windows 98SE: Installation

To install Windows 98, you need both the installation CD, and a boot disk. The boot environment lets you partition and format the hard drive. After this, you can run SETUP.EXE on the CD-ROM drive from this environment to start the installation.

Amazingly, Microsoft still has an extensive TechNet article outlining the Win98 install steps online.

If you need a floppy disk drive to create the boot floppy, external USB 3.5″ drives are still available if you look for them.


Windows 98SE: Start in DOS Mode

See here: http://www.computerhope.com/issues/ch000132.htm


IDE (Parallel ATA) Hard Drives

One ribbon cable supports two drives, and one drive needs to be designated the Master, and the other the Slave. I don’t think there’s any speed or priority difference between the two, it’s just a way to enumerate resources. Alternately, they can both be set to Cable Select mode, in which case the BIOS will figure out enumeration on its own, but both drives and the BIOS must support this setting.

The ribbon cable connector which is furthest away from the others is for the motherboard. It shouldn’t matter which of the two remaining connectors is used for the master or slave — the jumpers define which is which.

Master/Slave/Cable Select has to be configured by hand on each drive to match what the corresponding settings are in the motherboard BIOS. Unfortunately, the jumper settings vary by manufacturer and drive model. If there is not a clear diagram on the drive label indicating which pins are for which setting, look for any kind of jumper layout indicator on the drive stickers, such as “J50”. In this case, the J50 setting for Master is to stick the jumper on the two far-left pins.

Floppy disk drives

Floppies usually have their own ribbon / bus with fewer lines and a small twist on one end. The twisted end connects to the drive.


CD-ROM / DVD-ROM drives

CD/DVD readers have an additional audio passthrough port to the motherboard for passing CD Audio.


RAM DIMM / SODIMM Identification

See here: http://www.rcramer.com/tech/hardware/ram.shtml


Dell OptiPlex GX1 – Slowdown and Speedup keyboard shortcut

This PC has a nice “turbo key” style speed shortcut. Hit CTRL + ALT + \ or CTRL + ALT + # (varies by OptiPlex model) to toggle between a slow mode and normal operating speed.

This can be done at any time in DOS, and in Windows if you have a DOS Prompt in focus or full-screen (Real Mode). The slow mode is very slow, but it can remedy issues such as ISA Sound Blaster cards not playing audio correctly because the CPU is too fast.

Thanks to Vogons user retrofanatic for making me aware of this — the only other way to do it is to reboot and change a setting in the BIOS.


Dell OptiPlex GX1, Ubuntu Server 15: Blank display (no signal) after BIOS startup

My understanding is that old video hardware sometimes gets confused by the graphical video mode now used in the GRUB bootloader. You can try forcing GRUB to start in console mode instead.

To force console mode:

  • Use the Ubuntu installation disc to enter rescue mode. Choose the root partition and enter the terminal.
  • sudo vim /etc/default/grub
  • :colorscheme evening if you are having a hard time seeing the text.
  • Press insert to switch to Edit Mode.
  • Add nomodeset in the GRUB_CMDLINE_LINUX_DEFAULT= field.
  • Uncomment the GRUB_TERMINAL=console line.
  • Save your changes — press Esc to leave edit mode, then type :wq to save and quit.
  • Back at the terminal, update the GRUB bootloader information with sudo update-grub. After this is completed, type exit to return to the rescue prompts and reboot the system.

(Sources: Ubuntu Forums – “Thread: Out of frequency, Dell Optiplex GX 1”Ask Ubuntu – “How do I set ‘nomodeset’ after I’ve already installed Ubuntu?”)



ZZT is a sort of action-puzzle-adventure mashup for MS-DOS, the debut title from Tim Sweeney’s Epic MegaGames in 1991. Technically obsolete before it was even released, it uses Code Page 437 for graphics, the PC speaker for sound, and has a very silly and abstract tone.

ZZT is a difficult and unforgiving game that frequently tests the player’s patience, but it’s also pretty charming and mysterious. At a glance, it sort of resembles an ASCII Roguelike, or Apogee Software’s Kroz series. While Kroz uses a level-based progression, ZZT is more open-ended, with persistent screens that you can revisit.

ZZT enjoyed a cult following thanks to its built-in level editor and scripting language. Any user could build their own scenarios, even with the Shareware version.



The original ZZT Worlds

Tim Sweeney made four game worlds for ZZT:

TOWN.ZZT – The Town of ZZT
DUNGEONS.ZZT – The Dungeons of ZZT
CAVES.ZZT – The Caves of ZZT
CITY.ZZT – Underground City of ZZT

The Town of ZZT is the Shareware episode, and the others could be purchased through mail-order. All of the episodes are freeware now.



The Town of ZZT covers nearly all of the engine’s built-in functionality, and sets the tone for what to expect from other ZZT worlds. In it, you are tasked with finding several purple keys to unlock the ZZT Palace. These keys are scattered throughout the game world, and you’re free to track them down in nonlinear order.

Town is a mix of simple action, mazes (sometimes dark mazes, requiring a torch), and slightly more serious block-pushing puzzles. The enemies are kind of random and difficult to shoot, but the puzzle pieces are predictable, for the most part. There are boulders that can be pushed, but not pulled; sliders that can only be moved in two directions; pushers that shove anything in their way forward, and several other single-cell game pieces that can be combined together into fragile puzzles that necessitate a quit-and-reload if you mess them up. Town is very unforgiving, and the expectation is that you are making new save files whenever you feel even remotely at risk of losing.

There is not really any town in the Town of ZZT. Puzzles and action sequences are just there, because why not? Your reward for breaking into the ZZT Palace is … more ZZT. If and when you make it to the end, the game acknowledges your victory, and that’s pretty much that.

As for the other episodes, they are very much in the same vein. Dungeons of ZZT is a little heavier on action, Caves uses many dark areas and has some tougher slider puzzles, and City feels a little more like an adventure game (just a little), with increased emphasis on NPC interaction and some more object scripting.

Running ZZT Today

In the modern day, how does one take advantage of this opportunity for adventure, puzzles, savescumming and swearing?

Archive.org Browser-Based Emulator  — This is probably the easiest way to play ZZT right now. Just click the link, and voila. All of the original episodes are packed together, and you can find a plethora of other ZZT world files by searching Archive.org. The main downside is that your save files might not persist between browser sessions.

DOSBoxDOSBox works well with ZZT, and can be installed directly onto a variety of platforms. ZZT itself can be downloaded from the archive.org link above, as well. Some notes follow:

  • Just a heads up: the volume of DOSBox’s simulated PC Speaker is very loud by default.
  • To make input more responsive, you can increase the default CPU cycles from 3000 to about 10000 — use CTRL+F11 and CTRL+F12 to adjust on the fly. Cycle count is shown in the title bar.
  • There are alternate builds of DOSBox other than the common stable 0.74. Lately I’ve been using DOSBox Daum, which has some benefits:
    • 0.74 has an issue with keyboard input, where releasing one key will cause all keyboard keys to be released. This makes navigating around corners difficult. Other DOS games affected include Betrayal at Krondor. DOSBox Daum doesn’t have this problem.
    • You can toggle aspect correction straight from the menu, so you can view games from the era of 4:3 CRT monitors a little more like they would have looked at the time. You can then turn this off if it’s making things hard to see.
    • You can maximize the window, and toggling fullscreen mode also appears to be faster than plain 0.74.

Native MS-DOS or 32-Bit Windows – The most authentic, but least practical solution. The last systems capable of running ZZT directly were 32-bit Windows machines (and even they use an emulator behind the scenes).

Also, two ZZT supersets / emulators now exist: Chris Allen’s ZZT Ultra and SaxxonPike’s Lyon. I haven’t dug into them much yet, but they look promising.


3rd Party ZZT Games


I started writing some reviews of ZZT games here, but my ZZT nostalgia binge is drawing to a close for now, not to be recharged for another few years. The list I was working on had significant overlap with existing recommendations on the web, so I’m going to take the easy way out and just link to those instead.

As mentioned above, most ZZT games have been uploaded to Archive.org, and can be played directly within your browser. Just go to Archive.org, and search for the game’s title, plus ZZT. If the games lag in your browser, try hitting CTRL+F12 to increase the speed of the emulated CPU. If they still lag, you can download the games from the same page, run them in DOSBox, and try hammering CTRL+F12 again.

Other Links

Worlds of ZZT is a social media preservation effort by Dr. Dos. Follow the worldsofzzt Twitter bot to pepper your feed with randomly-selected ZZT game screenshots.