Back to the index of Compute II

COMPUTE II ISSUE 3 / AUGUST/SEPTEMBER 1980 / PAGE 42

Fast Graphics On The OSI C1P

Charles L. Stanford

I am sure that almost without exception, OSI C1P and Superboard II owners get a major part of their computer fun from games. The relative ease of graphics programming using the game symbols in the Character Generator ROM allows even the novice BASIC programmer a lot of freedom for creating exciting and fast moving figures. However, even OSI's very fast BASIC still leaves a lot to be desired when more than a few dozen screen locations are changed, resulting in distracting image changes and in slow execution of commands.

This article will present a “cookbook” version of a machine language graphics subroutine. The BASIC user with little or no background in Machine or Assembly language programming will be able to use the material presented here to “plug in” his own graphics. The more advanced programmer will be able to adapt the concepts shown to complicated, interactive graphics games and simulations.

The use of graphics in games

While many exciting and worthwhile games are played without graphics (such as most versions of Starwars, Star Trek, Quest, etc.), most family fun games revolve around the manipulation of graphics figures or objects on the screen. Six Gun Shootout, Tank, Lunar Lander, and many others are “made” by their graphics interactions. But a game like Six Gun written solely in BASIC is slowed down so badly by the time required to POKE two gunfighters and a few cacti that it loses a lot of its Oomph! Even a near-instantaneous screen routine (instead of scrolling) only helps a little when 50 or 100 or more POKE'S are needed every time a move is made.

The method presented in this article does have a few limitations, mainly in the opportunity for typing errors when entering the many DATA statements, and in the need to carefully plan and structure each and every character before starting to enter the program. But the results are really worth it. Every program I've written since devising this method has had to be slowed down with time delay loops, which is sure better than the alternative.

The USR Function

The OSI Graphics Reference Manual and the BASIC in ROM Manual are ridiculously brief in describing the use of the USR function. On the other hand, there isn't that much knowledge needed for an elementary application such as this one. In general, you will need to be capable of converting Hex numbers to Decimal quickly and accurately; the only other requirement is to be able to write a fairly structured BASIC program.

When the USR function is called by a line such as 10 X = USR(X), the program goes to the machine language program subroutine pointed to by the data at memory locations $000A and $000B (Decimal #11 and #12). The 6502 processor needs a 16 bit address to jump to a subroutine. But each memory location only has 8 bits. So the processor reads the eight lower (right-most) bits of the address from the lower of two adjacent memory addresses, and the eight higher (left-most) bits from the higher. Thus, a machine language routine at address $0222 would be called if memory locations $000A and $000B held $22 and $02 respectively. These numbers would be inserted in these two RAM locations by a line in BASIC such as 20 POKE 11,34 : POKE 12,2. Note that the Hex numbers have to be converted to their Decimal equivalents for BASIC. As you can see, many machine programs starting at as many different memory locations could be called, merely by changing the data at $000A and $000B before calling the USR function. I'll describe how to write and insert the actual machine language subroutines a bit later in this article.

After a machine language subroutine is called, it acts very similarly to a BASIC program, in that each instruction is executed in order until a RETURN is encountered. It then returns to the next instruction after the JUMP. The RETURN in machine language is the RTS instruction, which is Hex $60 (or Decimal #96).

The advantage of all this is, of course, based in the fact that machine language is somewhere around 1000 times faster than BASIC for most equivalent functions. This feature is particularly useful for graphics programming.

Machine language programming

Writing programs in machine language, especially without the use of an Assembler, is a bit tedious. But the end result is often worth the effort. In this case, you, the reader, will use the program itself exactly as presented here, only changing a table which will hold whichever graphics figures needed for your particular game.

The action of the subroutine is quite simple. After a Screen Clear (note that the first eight lines of Listing 1 are as shown in my previous article in Compute II Number 1), a series of graphics characters and their locations relative to a fixed point are read from memory and written to the TV screen.

The routine makes use of one of the 6502 processor's more efficient addressing modes, Zero Page. Line 023A uses the instruction LDA-0,Y, which loads the accumulator with the data residing at a memory location equal to the value of the address pointed to by FE (lo byte) and FF (hi byte) plus the contents of the Y register. These two memory locations at the top of Zero page were chosen as they are easy to remember, and are not used for any other purpose by BASIC or the Monitor. Since the Y register is an eight-bit register, only screen locations between the base address and the base address plus 255 can be called.



Looking at the flow chart (Figure 1), you can see that the first data loaded is the lo byte, then the hi byte, of the screen location. This information is in the table for this particular game, and is deposited within the program to give a start location on the screen for the first graphics figure. Next, the offset of the first character is loaded into the X register, and a check is made to see if the figure is ended. I have selected $FE (254) as a signal that part of the figure is completed, and $FF (255) for the end of the routine. If this data is neither, a graphics symbol will be leaded into the accumulator. This symbol is then deposited into the video refresh memory at the location previously loaded plus the offset in the X register. This may all sound a bit complicated, but that doesn't make any difference to you, the programmer. The program will take care of itself if the proper data is presented from the symbol table in the correct sequence.

Developing the screen image

The screen image is developed by following five easy steps, starting with a copy of the C1P Screen Grid. Being a railroad fan, I chose enact the “Great Train Collision” as a demonstration program. As you can see from Figure 2a, the OSI graphics symbols allow for a very reasonable steam locomotive with tender. After you have drawn whatever figures you need for your game to your satisfaction, enter the screen offsets of all locations, starting at a fixed point at the upper left corner of each figure, as shown in Figure 2b. Finally, write in the character generator code as shown in Figure 2c. The last step is to enter the data into a table as shown in Listing 2. Note that each figure is preceded by a screen address (lo byte first), and ends in either FE or FF. The last figure in the table must be FF (255) which causes the subroutine to return to BASIC.



Converting the Machine Language to BASIC

There are several methods of entering machine or assembly language programs through the use of BASIC programs, including direct entry through the monitor. There are also several ways to save combination BASIC-machine language programs to tape (see Daniel Schwartz' article in Compute II issue 1). However, I prefer the somewhat tedious but easy to debug and modify DATA-POKE method, This is hard to enter error-free, and causes a significant delay on program initiation, but it has the advantage of being more readily understandable, and allows your routines to be changed easily.

You can see that lines 100 through 145 in Listing 3 enter the machine language subroutine into memory, while lines 148 through 199 are the table of offsets and characters for the figures. This is to allow the use of this sequence with other graphics figures of your own choosing. Note also that the subroutine is inserted into RAM at $0222 (#546) in page 2. The page 2 memory from $0222 to $02FA is not used by BASIC, and is thus a good free place for this use. However, make sure your figures don't extend above $02FA. If so, you'll have to locate the table in the top of RAM, necessitating memory protection at Cold Start. The subroutine can still reside at $0222.

Animating the Figures

Once the graphics subroutine and the figure table are in RAM, the animation routines can be written in BASIC. The addresses needed to animate the demonstration figures are as follows:

   HEX      DEC    DESCRIPTION
000A
000B
11
12
Pointer to the starting address of the USR subroutine.
00FE
00FF
254
255
Pointer to the starting address of the first graphics figure to be called.
0222 546 Actual starting address of the USR subroutine, including screen clear.
0238 568 Actual starting address of the USR subroutine without screen clear.
0260
0261
608
609
Actual starting address of the first graphics figure. These locations hold the video RAM reference address, low byte first, for the figure.
029D
029E
669
670
Actual starting address of the second graphics figure. Also hold the video RAM starting address for this figure.

It is important to remember that the last two locations will vary depending on the location in RAM chosen to store the table, and according to the length of the figures in the table. The first four sets of addresses will remain the same for all programs.

In the demonstration program, memory location #12 always holds a #2, but the data in location #11 is changed back and forth between #34 and #56; this alternately inserts and omits the screen clear routine at the beginning of the machine language subroutine. If your program only has one figure, or if you end all figures but the last with #254, location #11 would stay at #34. Either method works equally well; chose the alternative which provides easiest animation in BASIC.

Note that if each figure ends with a #254, the data at memory location #254 will not change. But ending the first or any intermediate figure with #255 terminates the subroutine (see flow chart), and the only way to reach the next or subsequent figures is to change the pointer at #254 and #255.

As previously mentioned, the first two numbers in the table at the beginning of each figure are the video RAM address of the upper left hand corner of the figure. Since the video RAM of the C1P contains four pages of 256 bytes each, starting at $D000, the first number in the table at the start of each figure can vary from 0 to 253 (remember, 254 and 255 are signals), and the second can be $D0 through $D3 (#208 through #211). By POKEing different numbers into these locations, the figures can be made to move around the screen, It's better to use the actual first location of each figure in the original DATA statements, to avoid a jerk at the start of the program. After that, any desired location can be POKE'd into these addresses.

In lines 25 and 35 of Listing 2, variables A and B are established as the low bytes of the screen locations for the two figures. Then, in the subroutine starting at line 50, they are POKE'd to 608 and 669 respectively, and then incremented and decremented and POKE'd again to move the characters across the screen toward each other. To make the demonstration program more realistic, the engines are moved across the screen on different lines, and then reappear on a collision course. This is done by line 35, which changes A and B and also changes the high byte of the right locomotive to #209 from 210, moving it higher on the screen. Variable C determines how far across the screen each figure will move.

Finally, the routines at lines 200 to 399 give a rough representation of an explosion at the point of collision. I leave to the reader the exercise of adding this to the machine language table. It's not that hard!

Summary

In order, the steps for creating your own program using fast machine language graphics are as follows:

  1. Draw a representation of your selected characters, using the characters in the Graphics Reference Manual as elements.

  2. In an equivalent number of spaces, enter the offsets of the screen address, starting at a point above and to the left of the figures. If the number of the offset exceeds 253, just split the figure into two or more parts and treat each as a separate entity. They can be recombined in the BASIC program.

  3. Likewise, enter the graphics character codes in the equivalent spaces on the grid.

  4. Create a table which starts with the screen location of the figure (low byte first, then high byte); contains, alternately, the offset and code of each character; and ends in #254 or 255. If the figure ends in 254, the subroutine will continue with the next figure in the table. A #255 terminates the subroutine and returns to BASIC.

  5. Note the starting addresses of each figure, for later use in creating the animation in BASIC.

  6. Convert both the subroutine and the table into DATA statements. Note that if the table goes past memory location #608 + 144 (#752), it will be necessary to move all or part of it to another location in RAM, such as top of memory.

  7. Finally, enter the DATA statements and their POKE loops into an appropriate location in your BASIC program, in a manner similar to lines 100-199 of Listing 2. Then proceed with animation as in lines 25-99.

  8. Debugging hints: always save to tape as you go (a program crash is more likely in machine language, and all that tough typing will be lost); insert a BREAK after DATA loops, then use the Monitor to verify the machine language program entry, by single stepping starting at address $0222; insert timing loops at lines 22, 27, 32, and 37 to slow down action if there is a Bug in BASIC.

Some additional notes

Another interesting program I've written using this method is Six Gun Shootout. It has two gunfighters (one facing the other) and three cacti. After each shot, the cacti change locations at random. The program ran very slowly when written solely with BASIC POKE's to the screen, but is as fast as you would ever want with machine language graphics. I'll cover this program in a later article, together with instructions for attaching simple up-down-shoot joysticks to save wear and tear on the keyboard.



Listing 1. Machine Language Subroutine
0222 A0 00     546 160 0        LDY-IMM $00
0224 A9 20     548 169 32       LDA-IMM $20
0226 99 00 D3  550 153 0   211  STA-Y
0229 99 00 D2  553 153 0   210  STA-Y
022C 99 00 D1  556 153 0   209  STA-Y
022F 99 00 D0  559 153 0   208  STA-Y
0232 C8        562 200          INY
0233 D0 F1     563 208 241      BNE $0226
0235 EA EA EA  565 234 234 234  NOP NOP NOP
0238 A0 00     568 160 0        LDY-IMM $00
023A B1 FE     570 177 254      LDA-0,Y      LDA Lo Byte Scr Loc
023C 8D 56 02  572 141 86 2     STA-ABS
023F C8        575 200          INY
0240 B1 FE     576 177 254      LDA-0,Y      LDA Hi Byte Scr Loc
0242 8D 57 02  578 141 87  2    STA-ABS
0245 C8        581 200          INY
0246 B1 FE     582 172 254      LDA-0,Y      LDA w/Char Offset
0248 AA        584 170          TAX          A to X Register
0249 C8        585 200          INY
024A E0 FE     586 224 254      CPX-IMM      X=254? End Figure
024C F0 EC     588 240 236      BEQ          Branch to 023A
024E E0 FF     590 224 255      CPX-IMM      X=255? End Routine
0250 F0 08     592 240 8        BEQ          Branch To 025A
0252 B1 FE     594 177 254      LDA-0,Y      Load Character
0254 C8        596 200          INY
0255 9D 44 D1  597 157 68  209  STA-ABS,X    Char. to Screen
0258 D0 EC     600 208 236      BNE          Get another Character
025A 60 EA EA  602 96  234 234  RTS NOP NOP  End Routine, Return
025D EA EA EA  605 234 234 234  NOP NOP NOP


Listing 2. Graphics Table

0260 9B D1   608 155 209      028A 46 A1   650  70 161      02B3 25 A1   691  37 161
0262 01 02   610   1   2      028C 47 A1   652  71 161      02B5 26 A1   693  38 161
0264 03 A7   612   3 167      028E 48 A1   654  72 161      02B7 27 A1   695  39 161
0266 04 9D   614   4 157      0290 60 B0   656  96 176      02B9 28 A7   697  40 167
0268 05 A1   616   5 161      0292 61 E0   658  97 224      02BB 40 A1   699  64 161
026A 08 A7   618   8 167      0294 62 E2   660  98 225      02BD 41 A1   701  65 161
026C 20 A5   620  32 165      0296 63 E2   662  99 266      02BF 42 A1   703  66 161
026E 21 A1   622  33 161      0298 66 E2   664 102 226      02C1 43 80   705  67 128
0270 22 A1   624  34 161      029A 68 E2   666 104 226      02C3 44 A1   707  68 161
0272 23 A1   626  35 161      029C FF      668 255          02C5 45 A1   709  69 161
0274 24 A1   628  36 161      029D 83 D1   669 131 209      02C7 46 A1   711  70 161
0276 25 9B   630  37 155      029F 00 A5   671   0 165      02C9 47 A1   713  71 161
0278 26 B0   632  38 176      02A1 03 A1   673   3 161      02CB 48 A8   715  72 168
027A 27 A1   634  39 161      02A3 04 9C   675   4 156      02CD 60 E2   717  96 226
027C 28 A1   636  40 161      02A5 05 A7   677   5 165      02CF 62 E2   719  98 226
027E 40 A6   638  64 166      02A7 07 02   679   7   2      02D1 65 E2   721 101 226
0280 41 A1   640  65 161      02A9 20 A1   681  32 161      02D3 66 E0   723 102 224
0282 42 A1   642  66 161      02AB 21 A1   683  33 161      02D5 67 E1   725 103 225
0284 43 A1   644  67 161      02AD 22 B2   685  34 178      02D7 68 B2   727 104 178
0286 44 A1   646  68 161      02AF 23 9B   687  35 155      02D9 FF      729 255
0288 45 80   648  69 128      02B1 24 A1   689  36 161