Title Image

Part 8: Displays and Interfaces

In this installment, we'll have a look at the displays, interfaces, and controls of the CRPG.


Displays

It may come as a surprise to some of you, but displays and interfaces are some of the most complicated, messy, time-consuming and memory-consumptive pieces of code to write. You thought the map processing algorithm was nasty? Ha! Displays and controls make that seem like a cake-walk.

The main problem is that for the computer to display information in a human-readable form is not a trivial task. Every display, while seemingly simple, is very complicated to put together. I had somewhat anticipated this when allocating memory blocks in my designs, but it was still a shock to see it playing out.

Let's look at a small example, a very simple task that's done frequently: displaying a word variable as an integer on-screen for human viewing:

* Integer to string function. Uses ADDNUM as a subroutine. Returns 0 to 65535.
* Pre-byte length string in NUMWRK array
* R3 - Value to calculate
DNUM   DATA VWS,DNUM1
DNUM1  MOV  @>0006(R13),R3
       LI   R0,NUMWRK+1
       CLR  R1
       MOV  R3,R3
       JEQ  DNEND
DN0    CLR  R2
       CLR  R5
       LI   R6,10000
DN1    DIV  R6,R2
       DIV  @W10,R5
       MOV  R5,R6
       CLR  R5
       MOV  R2,R2
       JNE  DN2
       MOV  R1,R1
       JNE  DN2
       CI   R6,1
       JEQ  DNEND
       JMP  DN1
DN2    BL   @ADDNUM
       CI   R6,1
       JNE  DN1
DNEND  MOV  R3,R2
       BL   @ADDNUM
       SLA  R1,8
       MOVB R1,@NUMWRK
       RTWP

* Companion function for DNUM, adds number to string
ADDNUM SLA  R2,8
       AB   @OFFSET,R2
       MOVB R2,*R0+
       INC  R1
       CLR  R2
       RT
Quite a bit of code just to print a single 16-bit value on screen! I use a BLWP to access it because the amount of computations and register usage is high enough I don't want to try and tailor it so it doesn't destroy important values in the calling routine. I use my video register workspace for it since there's no video access occuring during the number-crunching.

This particular function doesn't consider the numbers signed... that's because for the most part, all the values I'm working with are positive, and having a negative variant was just taking up code space. On a vintage system, space generally wins over flexibility on your subroutines.

Another bit of problem code was the headers for the player statistic screens. The top two lines were a little collage of graphics at each end of two lines crossing the screen. The player name is displayed on the first line, centered, and the player's level and class is on the second line, also centered.

The graphic embellishments I'll get to in a moment. For now, let's consider how would you do this in, for example, Extended BASIC? The code may be something like this:
100 REM PNAME$ contains player name
101 REM PCLASS$ contains player class
102 REM PLV contains player level
200 C=INT((28-LEN(PNAME$))/2):: DISPLAY AT(1,C):PNAME$
210 S$="LEVEL "&STR$(PLV)&" "&PCLASS$ :: C=INT((28-LEN(S$))/2):: DISPLAY AT(2,C):S$
Not that bad. Now let's look at the assembly equivalent:
* Draw header information of stat screens
* @PACT - Player number
HEADER MOV  R11,*R10+
       MOV  @STACHR,R4
       SLA  R4,5
       LI   R0,PNAMES
       A    R4,R0
       LI   R1,WORK
       LI   R2,32
       BLWP @VMBR
* Write name
       MOV  @ACTSCR,R0
       LI   R1,WORK+1
       MOVB @WORK,R2
       SRL  R2,8
       LI   R3,32
       S    R2,R3
       SRL  R3,1
       A    R3,R0
       BLWP @VMBW
       MOVB @HIBUFF,R3
       SRL  R3,8
       BLWP @DNUM
       MOVB @B6,@STRING
       LI   R3,UITXT+6
       LI   R4,STRING+1
       LI   R5,5
       BL   @CPYSTR
       MOVB @SPACE,*R4+
       AB   @NUMWRK,@STRING
       AB   @B1,@STRING
       LI   R3,NUMWRK+1
       MOVB @NUMWRK,R5
       SRL  R5,8
       BL   @CPYSTR
       MOVB @SPACE,*R4+
       AB   @WORK+16,@STRING
       LI   R3,WORK+17
       MOVB @WORK+16,R5
       SRL  R5,8
       BL   @CPYSTR
       MOV  @ACTSCR,R0
       AI   R0,32
       LI   R1,STRING+1
       MOVB @STRING,R2
       SRL  R2,8
       LI   R3,32
       S    R2,R3
       SRL  R3,1
       A    R3,R0
       BLWP @VMBW
       B    @SUBRET

* String copy subroutine
* R3 - Source
* R4 - Destination
* R5 - Length
CPYSTR MOVB *R3+,*R4+
       DEC  R5
       JNE  CPYSTR
       RT
Yikes, that's a lot more work! It's over 50 lines of code, and around 200 bytes of codespace. And that's just to write a little header! We can't afford a lot of code like this, not at all. We can also see why interpretive languages have their definite uses.

So why is it so large? One issue is the disparate nature of the information being displayed. The player name and class is read in from a data store in VDP memory, and stored in a local buffer. The word "level" is stored in a CPU memory array of common in-game text elsewhere. And the player level must be converted to a string, which is in, yet again, another buffer in memory. I decided, to make the video access as minimal as possible, to copy each part into one large buffer before writing it to the screen.

Sadly, this is also one of those tasks where it's just not possible to cut this down very much. The problem lies in dynamics. Since the player names and classes are going to be of a variable size, the code is going to be more complicated to make the necessary adjustments.

The problem extends to more than just the header. If you want to do all your text writing iteratively in assembly, every single VDP write to the screen requires loading three registers with values, followed by a BLWP. This is an average of 16-24 bytes of code-space for every call. So, if you had ten pieces of data to write, coupled with ten text labels for them, that's 20 calls, or around 320-480 bytes. Ouch!

To solve the codespace problem, I used data arrays which contain screen coordinates in a prescribed order. This lets me use a looping structure to output data to the screen using less codespace. A single screen address only occupies 2 bytes, and since I reuse the same lines for the register loads and call, a lot of the waste disappears. And fortunately, the looping and video calls don't impact performance at all.

For example, if we look at some code that puts together one of my statistic screens:
STAORD BYTE 1,2,3,4,9,58,48,49,51,57
STAPOS DATA 167,182,199,214,294,373,428,460,522,585
       DATA 262,277,358
       DATA 309,438,470,534,598
* Stat screen #00 - Player Statistics
SSCR00 MOV  @STACHR,R8
       SLA  R8,5
       CLR  R4
       CLR  R5
* Write 'direct' values to screen
S00A   MOVB @STAORD(R4),R1
       SRL  R1,8
       MOVB @HIBUFF(R1),R3
       SRL  R3,8
       BLWP @DNUM
       MOV  @ACTSCR,R0
       A    @STAPOS(R5),R0
       LI   R1,NUMWRK+1
       MOVB @NUMWRK,R2
       SRL  R2,8
       BLWP @VMBW
       INCT R5
       INC  R4
       CI   R4,9
       JLE  S00A
The HIBUFF array contains player data, which the STAPOS array contains pointers to various player data elements. Each value is extracted, converted to a displayable integer, and then output into a position on screen as dictated by the placement in the array.

Some elements, like item names, are actually not stored in memory. It's a total waste to have those resident at all times, since they're only needed in narrow contexts. This does, mean, though, that the "item" screen takes a little longer to load, because it has to load each item name from disk individually. We're still talking fractions of a second, but it's enough that I added a "black-out" effect between statistic screens. This has the effect of letting the player know the computer DID react to his keystroke. It can be annoying when you press a key and something doesn't immediately happen.

What about the text labels, you ask? We could do them the same way, storing them in a CPU data array and then writing them out using another array of positions. I had, however, another idea...

Screen Images

So now you may ask, what about the little graphic headers? Well, I initially was going to draw them on screen, but this, as you can imagine, consumed even more code-space for a trivial task. So then I stored it in a 64-byte data array in CPU memory to just write out in one pass.

Then I got really clever, and decided, why not just template as much as possible into memory images that I can just read directly into a video screen buffer?

There's two things to consider before using screen images for this purpose:

The answer for me to all of these is "yes". About the only reason I hadn't considered it before was the potential lag caused by loading screen-images from disk. Also, I'm fairly certain that none of my screens would take 768 bytes of codespace to write out. However, it's 768 bytes of CPU memory, which I don't have, verses a kilobyte on disk, which I do have.

One consideration also is that you don't NEED to use memory-images for screens. You could store the screens in one large file, broken up into 128-byte records. Six concurrent records become one screen. However, this isn't quite as elegant as a memory-image, since you would have to copy each segment out of VDP to CPU, and then back to VDP again. And it's not necessary, since I have sufficient file count and disk space to stick with memory-images.

Let's take a look at some of the templates:

Stat Screen #1 Stat Screen #2
Stat Screen #3 Stat Screen #4
Stat Screen #5 Stat Screen #6
I wrote myself a little screen editor to make these up from the game's character sets, and save them to disk. It also lets me edit them easily later.


User Interface

One small regret I have is how frugal I have to be with text in-game. The fact is, every scrap of text takes up room, and feedback to the player will be very minimalist. I've noticed that the Ultima games, for example, seem to have quite a bit of feedback in their little text window when you're playing. I wish I could be that wordy, but I'd rather have the game features over excessive prose.

I have to avoid excessively-sized words and phrases like "%NAME% strikes the foul creature for %VALUE% points of damage!" in lieu of "%NAME% hits for %VALUE% damage". I'll have to rely on the icon system to tell the player what happened, rather than say "%NAME% is poisoned!". I'll also use the demo as a means to determine, from play-testers, where further feedback is absolutely critical.

So, some of my UI elements are:

Let's look at each element individually.

Key Controls

Key control just makes sense... there is not that many input devices for the TI besides the keyboard, and it's a stock part of the console. Joysticks really don't fit a CRPG, being more for action games. And a mouse is an uncommon to rare item on a TI, and requires a completely different approach to the interface. Not to mention that I don't even have the source code for the driver!

On a side-note, here's the easy way to deal with the caps issue in assembly: Just insert a bit of code like this:

B97    BYTE 97
B32    BYTE 32
*
* Key's been pressed and in @KEYVAL
       CB   @B97,@KEYVAL
       JGT  KEY1
       SB   @B32,@KEYVAL
* Key actions
KEY1   NOP
Not coincidentally, the upper and lower-case alphabets are separated by exactly 32 characters in the ASCII table. So all you need to do is check if a character is equal or above 97 ('a'), and if so, subtract 32 from the value, and then you need only check one set of letters. The one flaw with this technique is that it will make the characters from 123+ coorespond to different codes... only a concern if you really have to have brackets or control characters.

I had one unexpected request from a future player of my game... he wanted the option to remap the keys so he could use the W-A-S-D keys to move about instead of E-S-D-X. For those of you who don't do a lot of modern gaming, that's the key set typically used by FPS games for moving forwards, backwards, or side-stepping left or right.

Normally I wouldn't mind offering such an option, but alas, with a vintage game, having reprogrammable keys is hard to offer. That codespace needed to remap keys could be used for other features, after all.

Truthfully, though, my key system is actually quite re-programmable if you don't mind changing some source code. So I may just offer instructions on changing keys if someone really wants it. Or offering a version with them re-mapped to another popular configuration.

Turn-Based verses Real-Time

The turn-based nature of the game is also inherent in the UI design. The process is pretty simple:

However, there's some real-time elements to consider.

If you're on a boat on moving water, the game will count down an internal clock and force your boat to move, if you don't take any action. I could have left out a turn advancement with this, but if I do that, boats could be used to avoid encounters with monsters because they're essentially frozen until you step off the boat.

I also intend that mob movement will be calculated during the interrupts. This is so that response time is minimized. If monster movements are only calculated AFTER the player has gone, this could add up to just enough to cause a bit of a pause. By taking care of this in interrupts, the system is actually calculating their moves while the player is deliberating his own actions.

Of course, this may make it possible for the player to move quickly and actually outrun the mobs simply because they haven't had a chance to decide what to do yet! This part of the code will need some testing when it's written.

Input Functions

The input functionality is something I eliminated early. A bit of background story...

When I was in college, I had a Java course over the summer. Ten weeks was crammed into six weeks, and just to make things interesting, our teacher was not familiar with Java at all, having been handed a book a week or so prior. He was game to learn, though, and threw out a number of minor programming ideas to try and do in Java.

A rather funny one was to write a simple "wait until a key is pressed, then continue" function. This is easily doable in BASIC on any platform, and in C/C++ using the getc() function. To our surprise, though, Java simply didn't have the low-level access necessary to do this; the best we could do was have a blinking cursor that would advance if you pressed any character and THEN pressed enter.

A particular one that was gruesome, though, was an input style command in the Java command window. I don't think the Java devs put much effort into command-window controls, and as such, there's no key-driven input class (or wasn't when I was working with it) native to the platform. Our textbooks usually supplied these, using various KeyInputStream and Buffer commands, and were fairly complex for a novice Java programmer. Eventually our instructor decided it was too difficult to ask us to write one of our own.

An input function is really a challenge to implement in assembly. You have to have a buffer for the input, implement a blinking cursor, track the cursor position, allowing you to move back and forth, consider every keystroke to see if it's a "viable" character or not, and detect when the ENTER key is pressed so you know the user is finished.

The worst part of an input function, though, is interpreting the string entered by the user. Giving the user that level of control means he could literally enter ANYTHING. You'll have to do string comparisons to see if the user entered this or that. It's a huge amount of work, and for what? How many times does the user need that kind of flexibility in the game?

Consider Tunnels of Doom. Selecting a character involves typing out part of their name. If you have two characters with similar names, you have to type out far enough that it knows which one you mean. It has to do a string comparison on all four names to find the right player. I wonder why Kevin Kinney didn't just do "Select player (1-#)" and assign numbers to the characters? Maybe GPL makes string comparisons easier, I don't know.

The only area I see an input function being used in the game is when you're entering a character's name at character creation time. And this isn't part of the game engine, it's a seperate program. So I don't need to write one for the game itself.

Mundane Tasks

Probably one of the worst things a CRPG's UI can do is make ordinary tasks a trial. It's up there with online games that have horrible installation and setup processes. There's no quicker way to drive off a potential player.

I think the nastiest example I can think of is the buying interface in Final Fantasy on the NES console. It did not allow the player to specify a quantity of items; everything had to be purchased one at a time. With items like potions, it was a nightmare of pressing buttons and moving the controller over and over and over... You could get Carpal Tunnel from doing it too long. It didn't help that the game guides all advised you to buy "99 of everything". Argh!

This is an extreme case, though. There are less onerous cases that can be equally bad.

When I was writing the travel portion of the engine, I had planned to have "portrait" screens, which would be used to give a more graphical look at objects, and a variety of options to interact with them.

For example, a fountain would have a nice graphic representation of a flowing fountain (using the animated water tiles and other bits) and an invitation for the party to drink, along with the results of it. Another is a door, which will offer options for the player to interact with it.

However, in light of my recent discoveries of how much codespace having that level of interactivity takes, I've been asking myself if maybe a simpler approach is needed. What about if when you move into a door, the following things are done:

This sounds complicated, but since the computer is doing nearly all the work without having to display data or prompt for information, it actually doesn't take up nearly as much codespace as having an interactive screen up with the same options. The principal trouble I see is when more than one player is capable of an action... that means extra time to check all players and find the most skilled. Fortunately, doors are one of the most complicated objects in the game, so I don't think the others will be quite so much trouble to implement.

In particular, I want to minimize the number of interaction options by which the computer has to inform the player that it's not possible. I mean, let's be honest... if you had no chance to do it, why were you offered the option in the first place?

The computer is capable of calculating what you can and can't do prior to offering options. I think the main reason many CRPG's of the past didn't do this was they didn't want to pre-calculate things; they only checked things after the user made the choice. This may give a greater illusion of freedom of choice, but it also makes for a lot more error-checking and testing. It speeds up the game considerably when you know and trust that if you could do it, the option would have been offered.


Conclusion

Next time, we'll take an in-depth look at the combat engine, which I'm working on now. Should be a fun one!