8-Bit Geek

Telengard Remastered

The latest version is Telengard Remastered v1.1

The Original

Telengard was one of the first games I ever played on the Commodore 64. It's a pure dungeon delver with no story, no quests, and no frills. You get 50 levels of procedurally generated dungeon terrain, 20 kinds of monsters in random encounters, and more treasure than you can possibly carry. The goal is quite simple, kill monsters, gather treasure, and make a run for the surface to cash in your treasure and experience points to level up. You get more and more powerful which enables to you explore deeper and deeper dungeon levels. The fun ends only when the real world interferes. Bills for adults and homework for kids are about the only things that can pull you away from this game.

Telengard started out as game ported from Daniel Lawrence's DND, a BASIC game written to run on TOPS-10 systems which he later ported to a PDP-11 at Purdue University. The initial version was ported to the Commodore PET, with later ports hitting the Apple II, Atari 800, TRS-80, IBM PC, and Commodore 64. When Telengard was ported to the Commodore 64, it was substantially enhanced with the newer graphics and sound capabilities of the machine. Where other ports mostly used standard ASCII characters to display the monsters and dungeon features, the C64 version used sprites, allowing a better overall experience. Coupled with the improved sound capabilities of the SID chip, Telengard on the C64 is widely regarded to be the best version of the game.

Remastering Telengard

When I started teaching myself programming, I had very few resources to help me. I relied mostly on other people's code to learn how to do things. Telengard was one of the very few BASIC programs I had so I used it extensively as a learning resource. It also became the first game I ever modified. My first modification was to change the number of levels from 50 to 200 matching the X and Y dimensions to create a dungeon that was 200x200x200 in size. I quickly realized that the item bonus progression was too slow for more than 50 levels. Even with the most powerful items, the weakest creatures on level 200 of the dungeon still kill you in one shot. So I modified the item bonus progression. I always wanted to enhance the graphics of the game but never got to it . . .

. . . until now. I saw the release of DDI telengard and was inspired by it to work on the version of Telengard that I always wanted. DDI's release is a good release, but it wasn't quite what I was envisioning so I decided to get to work. I ported a few DDI features to my version, like keyword highlighting and using RUN/STOP as a pause feature. But for the most part, the features are ones that I've always wanted. The goal was to enhance the graphics, speed up the dungeon rendering, and improve the user interface without changing the overall feel of the game. Below, I'll go over some of the major changes I've made. The complete list of changes is available in a text file included with the download.

The User Interface

The user interface is pretty indicative of the time. The character sheet is rendered as white text on a black background. All of the field names and values being the same color makes the information you're looking for a little difficult to find. So the character sheet now has field names in medium gray and most values in white. Hit points and spell points are in green and purple respectively to help them stand out even more. The character name is in blue as well.

Speaking of the character name, it's also longer. It used to be that there was only room for 7 letters in the character name. If you wanted to be able to save your character to disk and not have it deleted automatically when he died, you had to use the prefix, "sv" in the name. That really only left you with 5 characters for the name. I always hated that so I devised a system that allows 16 characters (max filename length on the Commodore 64) for character names. It handles the "sv" prefix behind the scenes to keep that feature alive.

Loading a character used to require you to remember the name of the character on disk and manually type it in. If you couldn't remember it, you had to reset the computer and load the directory to see what the character filenames were. Then you had to reload the game and manually type it in. To prevent that, I wrote some code that will search for characters on disk and present them in a simple menu so you can select the character you want to play.

I've always disliked that the uppercase-only character set was the default on the Commodore 64. Text is much more readable with proper capitalization. So I switched the character set to use the upper and lower case characters. It left plenty of room for the character graphics I needed and made reading the screen much more comfortable.

The keyboard interface is pretty simple. It uses the standard 10 character buffer available in the C64 by default and you simply press keys that indicate your commands. One of the things that has always annoyed me about this is that sometimes you hit a key more than once by accident and it gets picked up by the buffer. Other times, the slow pace of the game makes you want to press a key to get ahead of the game and often times, you anticipate the wrong action and you give the wrong command. To eliminate those problems, I clear the keyboard buffer before reading key presses. It does have the negative side effect of not being able to queue up commands quickly. In the case of casting spells, I often find myself typing too fast without the buffering to help. But for the most part, I think it works well.

Performance

Telengard was written in BASIC so it's not high performance software. Since it's a turn-based game with mostly static graphics, it doesn't need to be. But there was always one aspect of the game's performance that irked me and probably many others. Every time you moved from one location to another, the dungeon would go black and you'd wait while each wall of the dungeon was redrawn . . . slowly. This wasn't uncommon for the time. Firing up Telengard and watching those walls being drawn one brick at a time smacks me with a nostalgia bat. But if I want full on nostalgia, I can play the original version of the game any time I want. So nostalgia was kicked aside for blazing fast performance. To improve the rendering speed, I rewrote the rendering routines in machine language.

When I was almost done with the remastering of the game, I became aware of Mayday's release of Telengard. It has some interesting features, like a beautiful loading screen, a tribute to Daniel Lawrence, and a high score screen. It was also compiled. So instead of interpreting BASIC at runtime, it was the same BASIC code pre-compiled into machine language to run at machine language speeds. But the rendering was still too slow for me. One of the problems with this is that the BASIC compiler can't understand the algorithm the programmer is using. It can only dutifully compile the code. This means it essentially skips the runtime interpretation part of BASIC, but everything else is exactly the same.

BASIC uses 32-bit floating point math for all calculations. Even if you define two integers, add them, and store the result into an integer, BASIC has to first convert the integers into floating point values, perform floating point math, and convert the result back to an integer. There is no floating point hardware to accelerate this on the C64 so this is all done in software. And software-based 32-bit floating point math on a 1MHz 8-bit processor is very time consuming. Even the conversion between integers and floating point values is pretty time intensive.

Since the rendering routines are almost entirely math, compiling BASIC for these still results in slower than desired rendering performance. When I looked through the rendering routines, I quickly saw that this was essentially a BASIC recreation of machine language code. There was a lot of bit masking and bit shifting going on. These are things the 6502 microprocessor does extremely well and extremely fast. BASIC has no equivalent for some of them so things like bit shifting have to be emulated by dividing by powers of two. Of course, that means integers being converted to floating point numbers, divided, and converted back to integers. Division is the slowest floating point operation in BASIC and it was being done a lot. These types of issues keep compiled BASIC from being fast enough for my needs. So I recreated the rendering algorithm as a native machine language routine to maximize the speed.

Because this is procedurally generated and the seeds for the calculations are actually floating point vales, I still have to use some floating point BASIC routines. But the vast majority of the floating point math was eliminated in the conversion to machine language. The result was rendering so fast that it would render faster than a single frame refresh on the display. I didn't think I would get that speed but was I ecstatic! It didn't take long to realize that this was a bad thing. Pressing a key to move west results in the text "WEST" being displayed at the command prompt. It stays there for as long as the screen is being rendered and then it is replaced with an empty command prompt line. As fast as the screen we being refreshed, you could only see a blip on the screen where you once saw the command you typed. If you meant to go west but hit the key to go north, you might not realize it, especially since the dungeon is rendered based on what the character sees. You might move into a location with no context as to where you came from. Without knowing what direction you hit, you could get lost very fast. So I decided to code a 1 second delay between displaying the text and rendering the dungeon.

Smaller performance boosts were gained without the use of machine language. Because of the unidirectional linked-list nature of BASIC, a loop at the end of the code is very slow to execute. But if you move that same loop to the front of the code, it executes very fast. There are also performance boosts to be gained by moving GOTO and GOSUB targets to the front of code. If you're on line 1000 and your target is on line 1200, there isn't much of a problem in performance. But if your target is on line 999 or lower, then BASIC has to start searching for the target line number from the beginning of your code. Most of the GOSUB targets were at the end of the code instead of at the beginning. I moved the most commonly called routines and loops to the front to gain a significant performance boost.

I also crunched the code to use the smallest line numbers possible. Not only does this save memory, but it offers a minor performance boost because BASIC has to parse line numbers one character at a time. If line 15000 is moved to line 20, I've just saved 3 characters of parsing every single time the code is called. It doesn't seem like much, but these minor gains are amplified by the fact that some of these routines are called thousands or even tens of thousands of times in a single game session.

The Graphics

Having grown up on Commodore 64 games, I knew the graphics of Telengard could be better. Daniel opted to use a simple sprite system. One sprite was the player. The other sprite was the monster or room feature. To get multiple colors, he used multi-color sprites but of course that halves the horizontal resolution. Well, the good ol' Commodore 64 has 8 sprites and with good use of raster interrupts, more can be displayed on screen. I didn't need more than 8 so I didn't have to write an interrupt handler. But the 8 sprites allows me to put more colors in the monsters and room features. Daniel had already set the VIC-II chip to bank 4 to use a custom character set. So I had ample room for sprite data under the I/O, Kernel ROM, and BASIC ROM in the existing bank. Since he already setup three custom characters for the brick walls and was modifying characters on the fly for the teleportation effect, I could piggyback on his character set to make more of my own.

Since I could get multiple colors from multiple sprites, I could use high resolution sprites for more detail. And there was a lot of potential in the custom character set to supplement the sprites since the graphics were mostly static. So I opted to make the player character one high resolution sprite with custom characters to flesh out the colors and details. The only time the character moves is when he is raised by an elevator or drops into a pit. In those cases, there is only one room feature sprite in use so I switch the character graphics out for two sprites to animate the character. Otherwise, the character only uses one sprite.

The monsters and room features use a variable number of sprites. Some are simple and use 3 sprites. Some are more complex and use 5 sprites with character graphics. Some sprites are multi-colored and some are high resolution. I wrote a sprite handler with a lookup table so monsters and room features could simply be looked up by an indexed value and all settings for sprites could be copied from the table into the VIC-II chip.

The goal of new graphics was to enhance the existing graphics while keeping the original atmosphere of the game. I wasn't looking to showcase any artistic talent that I don't have. So I imported the graphics into GIMP and modified them to keep the same basic concept for each. I split each one into multiple layers so I could easily layout the sprite data in the Commodore 64. There were a few I couldn't quite make work, like the coins and altar. Those I made from scratch because I couldn't find a way to modify the existing ones to look good. I also replaced the Inn entirely. I used the brick graphics from the dungeon as walls and made new sprites and character graphics for smoke, roof, road, and the sign. I also use all 8 sprites including the one for the character in that scene. Most of my custom character graphics were made for the Inn.

Originally I wrote the new sprite handler in BASIC, but even for a game like this, it was too slow. So I rewrote it in machine language and it's nice and zippy. This completely replaces the sprite handler Daniel had in the BASIC code.

Gameplay

I made sure the bonus progressions of items found in the dungeon was a little more in line with the level of the dungeon you're on. I reduced the frequency of random treasure popping up. And I put part of my original dungeon level expansion in, but took it out to 250 levels instead 200. Where the original game boasted 2,000,000 rooms, this variant has 10,000,000 rooms! Lots to see here, folks! Keep in mind that the original 50 levels are still the same. The algorithm and seed values for procedurally generating the dungeon are still the same. The only variables that affect the dungeon layout and room features are your X, Y, and Z coordinates. So adding levels doesn't change the original levels at all.

Even though I really didn't want to change the gameplay that much, I did also implement a system that looks like it was partially implemented in the save character feature. There were entries in each save character file that were unused by the game. It looked like they were an extension of the existing system to track current character location. I suspect that Daniel intended to implement a history feature but I don't know for sure. I use that space to store the last 100 unique locations you've visited. I then use that data to prevent treasure from randomly popping up in those locations. It should encourage you to go explore the dungeon instead of standing around the stairs to the Inn and wait until treasure randomly appears in the square. And yes, I coded it intelligently enough to track the last 100 unique locations, not the locations of your last 100 turns. So you can't just stand in one square for 100 turns and wipe out all history of the previous locations. If your location is in the history already, it will not be re-added to it. You'll still have to get out and look for adventure.

Epilogue

I shared an office with Daniel when we worked together at Purdue University. We spent some significant time talking about Telengard and I really wish I could have gotten this done before he died. I'd like to think he'd enjoy this remastering of his game that I grew up playing. But at the very least I hope everyone else can enjoy it.