This program is a demonstration showcase for a sprite utility called “OPEN SPRITE,” written by Eric Boisvert and published by Byte Power in 1987. It activates a machine code sprite engine via RANDOMIZE USR 65112, then uses PRINT #3 as the sprite output channel, passing AT row,col and CHR$ n to position and select sprite images. The demo presents three animated scenes — a title screen with colored sprites, a train simulation, an aquarium simulation, and a boxing match — all driven by the sprite system alongside regular PRINT statements. Sprites are controlled via POKEs to addresses such as 65286, 65292, 65298, and 65304, which appear to set clipping boundaries (left, right, top, bottom) for the sprite display area. The program loads its machine code payload separately as “DEMO CODE” starting at address 62414, and a speech-bubble style text scroller at line 1020 animates dialogue by sliding strings through a fixed-width window using substring indexing.
Program Analysis
Program Structure
The program is organized into a title/splash section, a main narrative sequence, and several self-contained demo scenes, all glued together with a reusable dialogue subroutine. Control flows roughly as follows:
- Lines 10–80: Initialization, machine code setup, title screen with colored sprites, pause.
- Lines 90–210: Introduction sequence — the “host” character (Eric) slides in and delivers explanatory text.
- Lines 220–370: Train demo — a train sprite scrolls across the screen with smoke/wheel animation.
- Lines 380–490: Aquarium simulation — fish sprites move left and right with boundary wrapping.
- Lines 500–600: Boxing match simulation — two sprites exchange punches in a loop.
- Lines 610–990: Final sequence — sprite attributes demonstrated, clipping boundary showcase, ceiling gag, sign-off.
- Lines 1000–1110: Subroutines —
GO SUB 1000draws the paper-7 background window;GO SUB 1020/GO SUB 1030run the speech-bubble text animator. - Lines 9998–9999: Bootstrap loader and SAVE block.
Machine Code Integration
The sprite engine is loaded as a separate binary (“DEMO CODE”) at address 62414 and activated at line 30 with RANDOMIZE USR 65112. This call opens the sprite system and patches channel 3 (PRINT #3) so that subsequent output to that channel is interpreted as sprite commands rather than text. Two additional machine code entry points appear later:
RANDOMIZE USR 65370— called from the dialogue scroller (line 1070/1090) to scroll or clear the speech-bubble area.
Lines 35–36 perform a 16-bit address poke to store the value 62414 at address 65236/65237, presumably patching a jump or data pointer inside the machine code:
LET a=62414: LET b=65236
POKE b,a-256*INT(a/256): POKE b+1,INT(a/256)
This is a standard low/high byte split technique to write a 16-bit address into two consecutive memory locations without using assembly.
PRINT #3 Sprite Command Protocol
Once the sprite channel is open, PRINT #3 accepts a mini command language via CHR$ codes. Observed usage suggests the following protocol:
| Sequence | Meaning |
|---|---|
CHR$ 16; CHR$ n | Set ink/color index to n |
AT row, col | Set sprite position |
CHR$ n (1–33+) | Select and draw sprite number n |
CHR$ 14 | Draw standing/default host sprite |
CHR$ 15 / CHR$ 16 | Animation frames (walk/erase cycle) |
CHR$ 17 | Place sprite at off-screen/ceiling row 255 |
CHR$ 21; CHR$ 1 | Speech bubble sprite |
CHR$ 11 | Clear/erase sprite at position |
The use of CHR$ 16 and CHR$ 17 mirrors the standard Spectrum embedded color control codes, suggesting the sprite engine intercepts and reinterprets them before they reach the display file.
Clipping Boundary POKEs
The sprite engine exposes four boundary registers via POKE:
| Address | Role |
|---|---|
65286 | Left boundary (column) |
65292 | Right boundary (column) |
65298 | Top boundary (row) |
65304 | Bottom boundary (row) |
Lines 740 and 840 use these to confine sprite movement to a highlighted rectangle on screen, visually demonstrating the clipping feature as part of the demo’s tutorial narrative.
Speech-Bubble Text Scroller (Lines 1020–1110)
The dialogue subroutine at line 1020 implements a smooth 7-character sliding window over the message string. The string is padded at both ends (" "+s$+" "), then a FOR loop extracts s$(x TO x+6) for display at the bubble position, creating a horizontal scroll effect. A secondary cursor (cx, cy) advances through the remaining characters via PRINT INK 8; PAPER 8 — invisible ink on invisible paper — apparently to keep the Spectrum’s system PRINT position synchronized with the sprite channel output so that SCREEN$ interrogation at line 1080 returns meaningful data. The check IF SCREEN$(CX-1,CY)<>" " detects whether the cursor has reached occupied screen content and bumps the row accordingly.
Aquarium Simulation (Lines 400–490)
Three fish are tracked in a DIM F(3,5) array where each row stores: sprite number, row position, column position, direction (+1/-1), and current column offset. Fish moving off either edge have their direction reset to zero (F(X,4)=0), at which point the re-initialization branch at line 450 assigns new random values. The column value is stored via POKE 23681 and retrieved with PEEK 23681 — this is the system variable SEED (RAND seed) being repurposed as a temporary signed-byte register, exploiting the fact that POKEing a value ≥128 stores it as a negative offset when read back, enabling off-screen left positioning.
Signed Coordinate Trick
The use of POKE 23681, x followed by PRINT #3; AT row, PEEK 23681 appears throughout the program (lines 260, 470). Since BASIC variables hold floating-point numbers, negative column values cannot be passed directly to AT. By poking into a byte location and peeking it back, negative values wrap to 256-offset equivalents (e.g., -1 → 255), which the sprite engine presumably interprets as off-screen-left positions, enabling smooth entry from the right and exit to the left.
Bootstrap Loader (Lines 9998–9999)
Line 9998 performs a CLEAR 62000 to protect the machine code area, loads “DEMO CODE” to address 62414, then RUNs the program from line 10. Line 9999 saves both the BASIC program (with auto-run pointing to 9998) and the machine code block as a companion file. This two-file distribution pattern is standard practice for BASIC+machine code software of this era.
Notable Anomalies and Points of Interest
- Line 1010 uses
PRINT AT x,0; PAPER 7; INK 0,,:— the double comma afterINK 0is unusual; the second comma likely suppresses a newline or passes an empty parameter to a stream, though on a standard Spectrum this would be a syntax oddity worth testing. - Line 600:
IF PEEK 23560=0 THEN NEXT X: PAUSE 60— address 23560 is the FLAGS system variable. Polling bit 0 (or the whole byte for zero) here appears to be waiting for a keypress or interrupt condition set by the machine code, acting as a frame-synchronization or “press any key” gate. - Line 880:
PRINT #3;AT 255,15;CHR$ 17— row 255 is far outside the visible screen, used intentionally to move the host sprite off-screen instantly as the “ceiling gag” setup. - The variable
Iis used in a fractional form (LET i=6: ... LET i=i-1: IF i=0 THEN LET i=6) at lines 970–980, cycling through ink colors for the spider/ceiling sprite as a simple animation loop.
Content
Source Code
10 REM SPRITE DEMO
20 BORDER 0: INK 7: PAPER 0: CLS
30 RANDOMIZE USR 65112: REM OPEN SPRITE
35 LET a=62414: LET b=65236
36 POKE b,a-256*INT (a/256): POKE b+1,INT (a/256)
40 FOR I=1 TO 7: FOR X=1 TO 6: PRINT #3;CHR$ 16;CHR$ I;AT 5,4+X*2;CHR$ X;: NEXT X: NEXT I
50 FOR I=1 TO 6: FOR X=7 TO 10: PRINT #3;CHR$ 16;CHR$ I;AT 9,X*3-6;CHR$ X;
60 PRINT AT 15,4; INK I;"WRITTEN BY ERIC BOISVERT";AT 17,3;"COPYRIGHT ©1987 BYTE POWER"
70 NEXT X: NEXT I
80 PAUSE 300: CLS
90 REM MAIN
100 GO SUB 1000
110 FOR X=32 TO 15 STEP -1
120 PRINT #3;AT 10,X;CHR$ 16;: PAUSE 5: PRINT #3;AT 10,X;CHR$ 15;: PAUSE 5: NEXT X
130 LET cx=16: LET cy=0: PRINT #3;AT 10,15;CHR$ 14: PAUSE 60: LET bx=6: LET by=17
140 LET S$="Hi, I'm Eric, your host for the next few minutes...": GO SUB 1020
150 LET S$="I wrote this demo program to show you how to use the SPRITE utility...": GO SUB 1020
160 FOR X=15 TO 2 STEP -1
170 PRINT #3;AT 10,X;CHR$ 16;: PAUSE 5: PRINT #3;AT 10,X;CHR$ 15;: PAUSE 5: NEXT X
180 PRINT #3;AT 10,2;CHR$ 14;: LET by=4
190 LET s$="First I would like to define what a SPRITE is.": GO SUB 1020
200 LET s$="A sprite is simply a graphic like a UDG but it can be much bigger...I am a perfect example of sprite and so is this..."
210 GO SUB 1020
220 REM TRAIN DEMO
230 GO SUB 1000: PLOT 0,130: DRAW 255,10
240 PRINT #3;AT 3,4;CHR$ 31;#3;AT 2,27;CHR$ 31;#3;AT 7,5;CHR$ 32;#3;AT 8,27;CHR$ 32;
250 FOR x=0 TO 31 STEP 4: PRINT #3;CHR$ 17;CHR$ 4;AT 14,x;CHR$ 30;: NEXT x
260 FOR t=1 TO 3: FOR x=32 TO -15 STEP -1: POKE 23681,x: PRINT #3;CHR$ 16;CHR$ 2;AT 12,PEEK 23681;CHR$ 29;: PAUSE 5: IF x=10 THEN PAUSE 60
270 IF t=3 AND x=1 THEN GO TO 290
280 NEXT x: NEXT t
290 FOR X=32 TO 16 STEP -1
300 PRINT #3;AT 11,X;CHR$ 16;: PAUSE 5: PRINT #3;AT 11,X;CHR$ 15;: PAUSE 5: NEXT X
310 PRINT #3;AT 11,16;CHR$ 14;: LET bx=7: LET by=18
320 LET s$="Hi again... As you may already have noticed, this image was mostly formed with sprites of different size, color, and shape...": GO SUB 1020
330 OVER 1: FOR x=1 TO 10: PRINT #3;AT 7,8;CHR$ 12;#3;AT 3,6;CHR$ 12;#3;AT 12,13;CHR$ 12;#3;AT 12,18;CHR$ 12;: PAUSE 10
340 PRINT #3;AT 7,8;CHR$ 12;#3;AT 3,6;CHR$ 12;#3;AT 12,13;CHR$ 12;#3;AT 12,18;CHR$ 12;: PAUSE 10: NEXT x: OVER 0
350 LET s$="Yes, even me!": GO SUB 1020
360 LET s$="Now that you have a good idea of what is a sprite...Watch the next few examples...": GO SUB 1020
370 PAUSE 180
380 REM AQUARIUM DEMO
390 PAPER 0: INK 7: CLS
400 POKE 23560,0: POKE 23672,0: POKE 23673,0
410 FOR X=1 TO 5: PRINT #3;CHR$ 16;CHR$ 4;AT 18,X*5;CHR$ 24;: NEXT X
420 DIM F(3,5): PRINT AT 21,0; PAPER 4;TAB 6;"AQUARIUM SIMULATION",
430 RESTORE 440: FOR Y=1 TO 3: FOR X=1 TO 5: READ F(Y,X): NEXT X: NEXT Y
440 DATA 20,8,4,1,4,21,14,3,-1,27,23,3,6,1,26
450 FOR X=1 TO 3: IF F(X,4)<>0 THEN GO TO 470
460 LET F(X,1)=INT (RND*4)+20: LET F(X,2)=5+INT (RND*10): LET F(X,3)=INT (RND*4)+4: LET F(X,4)=1: LET F(X,5)=-5: IF F(X,1)=21 OR F(X,1)=22 THEN LET F(X,5)=32: LET F(X,4)=-1
470 POKE 23681,F(X,5): PRINT #3;CHR$ 16;CHR$ F(X,3);AT F(X,2),PEEK 23681;CHR$ F(X,1);
480 LET F(X,5)=F(X,5)+F(X,4): IF F(X,5)>31 OR F(X,5)<-4 THEN LET F(X,4)=0
490 IF PEEK 23673<10 AND PEEK 23560=0 THEN NEXT X: GO TO 450
500 REM BOXING SIMULATION
510 POKE 23560,0: CLS
520 PRINT PAPER 6; INK 0;AT 2,7;TAB 24;AT 3,7;" LE BOXING MATCH ";AT 4,7;TAB 24;
530 PRINT AT 20,0; PAPER 4;TAB 5;"BOXING MATCH SIMULATION",
540 PRINT AT 12,4; INK 2;"▗";AT 12,27;"▖": FOR X=13 TO 16: PRINT AT X,4; INK 3;"▐";AT X,27;"▌": NEXT X: PRINT AT 17,4; PAPER 2;TAB 28;
550 FOR X=1 TO 8
560 PRINT #3;AT 11,13;CHR$ 25;#3;AT 11,16;CHR$ 27;: PAUSE 1+INT (RND*45)
570 FOR T=1 TO 10
580 PRINT #3;AT 11,13;CHR$ 26;#3;AT 11,15;CHR$ 28;: PAUSE 5: PRINT #3;AT 11,13;CHR$ 25;#3;AT 11,16;CHR$ 27;: PAUSE 5: NEXT T
590 IF X=1 THEN PRINT AT 21,0; PAPER 5; FLASH 1;TAB 9;"WELL ALMOST...",
600 IF PEEK 23560=0 THEN NEXT X: PAUSE 60
610 REM GO ON...
620 CLS : GO SUB 1000
630 FOR X=1 TO 50: PRINT #3;AT 10,15;CHR$ 33;#3;AT 10,15;CHR$ 14;: NEXT X
640 FOR X=1 TO 50: FOR Y=16 TO 14 STEP -2: PRINT #3;AT 10,15;CHR$ Y;: NEXT Y: NEXT X
650 LET cx=16: LET bx=6: LET S$="Gee Wiz, appearing from nowhere is tougher than I thought...": GO SUB 1020
660 LET I=0: FOR x=1 TO 10: PRINT #3;CHR$ 16;CHR$ I;AT 2,x*3-2;CHR$ x;: LET I=I+1: IF I=5 THEN LET I=0
670 NEXT X
680 LET s$="Sprites can be printed INVERSE, FLASH, BRIGHT, OVER and in any COLORS...": GO SUB 1020
690 LET s$="The sprite utility uses a normal PRINT #3 function so it should be fairly easy to learn.": GO SUB 1020
700 LET s$="A typical example is... PRINT #3;AT 10,10;CHR$ 1": GO SUB 1020
710 LET s$="This would print at position 10,10 the sprite no. 1": GO SUB 1020
720 LET s$="You can also limit the printing area by poking the maximum positions possible, lets see an example...": GO SUB 1020
730 PRINT AT 5,8; PAPER 5;TAB 24: FOR x=6 TO 13: PRINT AT x,8; PAPER 5;" ";AT x,23;" ": NEXT x: PRINT AT x,7; PAPER 4;TAB 25
740 POKE 65286,9: POKE 65292,23
750 FOR X=15 TO 5 STEP -1
760 PRINT #3;AT 10,X;CHR$ 16;: PAUSE 5: PRINT #3;AT 10,X;CHR$ 15;: PAUSE 5: NEXT X
770 LET i=2: FOR X=23 TO 5 STEP -1
780 PRINT #3;CHR$ 16;CHR$ i;AT 9,x;CHR$ 19;: PAUSE 5
790 PRINT #3;CHR$ 16;CHR$ i;AT 9,x;CHR$ 18;: PAUSE 5: LET i=i+1: IF i=6 THEN LET i=0
800 NEXT x
810 FOR X=23 TO 13 STEP -1
820 PRINT #3;AT 11,X;CHR$ 16;: PAUSE 5: PRINT #3;AT 11,X;CHR$ 15;: PAUSE 5: NEXT X: LET bx=7: LET by=15
830 LET s$="You can do the same for the top and the bottom...": GO SUB 1030
840 POKE 65298,6: POKE 65304,14
850 FOR x=1 TO 3: PRINT #3;AT 3+x,11;CHR$ 14;#3;AT 14-x,19;CHR$ 14;: PAUSE 10: NEXT x
860 FOR x=3 TO 0 STEP -1: PRINT #3;AT 3+x,11;CHR$ 14;#3;AT 14-x,19;CHR$ 14;AT 6+x,11;" ";AT 13-x,19;" ": PAUSE 10: NEXT x
870 POKE 65298,0: GO SUB 1000
880 PRINT #3;AT 255,15;CHR$ 17;#3;AT 10,15;CHR$ 14;
890 PAUSE 60: LET bx=6: LET by=17: LET s$="Oh! Oh! What's that on the ceiling?": GO SUB 1020: PAUSE 50
900 FOR x=0 TO 10: PRINT #3;AT x,15;CHR$ 17;: NEXT x
910 FOR x=6 TO 0 STEP -1: PRINT #3;CHR$ 16;CHR$ x;AT 5,15;CHR$ 13;: PAUSE 5: NEXT x
920 FOR x=1 TO 20: PRINT #3;CHR$ 21;CHR$ 1;AT 5,15;CHR$ 13;: PAUSE 10: NEXT x
930 FOR X=32 TO 18 STEP -1
940 PRINT #3;AT 10,X;CHR$ 16;: PAUSE 5: PRINT #3;AT 10,X;CHR$ 15;: PAUSE 5: NEXT X
950 PRINT #3;AT 10,18;CHR$ 14;: LET bx=6: LET by=20
960 LET i=6: LET S$="That was just a joke! I'm safe and sound back at BYTE POWER's headquarters... See you next month!": GO SUB 1020: POKE 23560,0
970 PRINT #3;CHR$ 16;CHR$ INT i;AT 5,15;CHR$ 13;: LET i=i-1: IF i=0 THEN LET i=6
980 IF PEEK 23560=0 THEN GO TO 970
990 STOP
1000 REM WINDOW
1010 FOR X=0 TO 14: PRINT AT x,0; PAPER 7; INK 0,,: NEXT X: PAPER 7: INK 0: RETURN
1020 REM SAY SOMETHING!
1030 POKE 65286,0: POKE 65292,32: POKE 65298,0: POKE 65304,22
1040 PRINT #3;CHR$ 21;CHR$ 1;AT BX-1,BY-1;CHR$ 11;
1050 PRINT #1;AT 1,0; INK 0; PAPER 8,,
1060 LET s$=" "+s$+" "
1070 FOR x=1 TO LEN s$-7: PRINT AT bx,by;s$(x TO x+6);: PRINT INK 8; PAPER 8;AT cx,cy;s$(x+7);: LET cy=cy+1: IF cy=32 THEN LET cy=0: LET cx=cx+1: IF cx>21 THEN LET cx=21: RANDOMIZE USR 65370: PRINT #1;AT 1,0; INK 0; PAPER 8,,
1080 PAUSE 5: NEXT X: LET CY=0: LET CX=CX+1: IF SCREEN$ (CX-1,CY)<>" " THEN LET CX=CX+1
1090 LET CY=0: IF CX>21 THEN LET CX=CX-1: RANDOMIZE USR 65370: PRINT #1;AT 1,0; INK 0; PAPER 8,,: GO TO 1090
1100 PRINT #3;CHR$ 21;CHR$ 1;AT BX-1,BY-1;CHR$ 11;
1110 PAUSE 30: RETURN
9998 CLEAR 62000: LOAD "DEMO CODE"CODE : RUN
9999 SAVE "DEMO" LINE 9998: SAVE "DEMO CODE"CODE 62414,2978
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.