Horserace is a multi-bettor horse racing simulation for up to eight players, where each bettor picks a horse before the race begins. The program uses two UDG characters — a horse-and-jockey figure (UDG-M) and a track marker (UDG-A) — defined via READ/DATA loops at startup. Race movement is driven by a random number (1–8) that selects one of eight horses to advance each iteration, using a computed GO TO of the form (r*30)+360 to dispatch to the correct horse’s print routine. The track is drawn using PLOT/DRAW commands to create a bordered rectangle, with horizontal lane lines rendered via PRINT AT. An extended bugle call fanfare is played from DATA statements before the race begins, and a winning jingle loop plays when any horse reaches column 32.
Program Structure
The program is organized into several distinct phases, controlled by GO SUB and GO TO branching:
- Lines 10–180: Initialization — UDG definition, screen setup, title display, and border drawing.
- Lines 190–320: Track layout rendering and bugle fanfare playback from DATA.
- Lines 330–620: Main race loop — random horse selection and position advancement.
- Lines 630–680: Border-draw subroutine, winner announcement, and restart.
- Lines 690–870: Bet entry screen — collects bettor count and each bettor’s horse selection.
- Line 880: SAVE with auto-run.
Machine Code Usage
Lines 20–40 and 60 invoke machine code via RANDOMIZE USR FN a(). The function FN a() (line 20) computes the address of the machine code by reading the system variables at addresses 23635–23636 (the PROG pointer, giving the start of BASIC) and adding 5, which points into the embedded binary data stored in the REM statement at line 10. Different POKE values into system variables 23728, 23729, and 23723 are set before each call, presumably to parameterize the routine for different tasks (title rendering on first call, a secondary display action on the second call).
UDG Definitions
Two UDGs are defined at lines 140–170:
- UDG-M: A horse-and-jockey sprite, defined by DATA
0,0,134,127,62,60,86,169. Used in the horse stringh$and in the title display. - UDG-A: A track post/marker, defined by DATA
0,24,24,154,127,60,60,60. Used in lane separator lines printed by the track layout loop.
The horse display string h$ is assembled at line 180 as CHR$ 17 + CHR$ 5 + "_" + UDG-M, combining an INK color attribute byte (INK 5) with an underscore and the horse UDG for a colored jockey-on-track appearance.
Race Dispatch via Computed GO TO
The core race mechanic (lines 360–620) uses a computed GO TO to select which horse advances each tick:
- Line 360:
LET r = INT(RND*8+1)picks a horse number 1–8. - Line 380:
GO TO (r*30)+360jumps to lines 390, 420, 450, 480, 510, 540, 570, or 600 for horses 1–8 respectively. - Each horse block prints
h$at the appropriate row and increments the horse’s position variable, then loops back to line 360.
This is an efficient dispatch table implemented entirely in BASIC, avoiding a chain of IF/THEN comparisons.
Track Layout and Graphics
The track is drawn at lines 200–250. A STEP 2 loop (lines 200–220) prints lane numbers and the UDG-A track marker string across rows 2–16. A vertical finish line is plotted pixel-by-pixel at x=248 (line 240). The border rectangle subroutine at line 630 uses four chained DRAW commands to form a closed rectangle.
Bugle Fanfare
The racing call fanfare (lines 260–320) reads pairs of duration and pitch values from DATA, scales duration by dividing by 18, and plays each note with BEEP. The sentinel value 999,999 triggers a short PAUSE before exiting to the race start. The DATA spans two lines (300–310) representing two phrases of the bugle call.
Betting System
Lines 700–870 implement the pre-race betting phase. The program accepts 1–8 bettors via INPUT into b$, validated by string comparison (b$<"1" OR b$>"8") and a length check (LEN b$>1). Each bettor then picks a horse number, stored in w$ with the same validation pattern. Bet selections are displayed with colored PRINT attributes but are not stored beyond the screen — there is no array or variable tracking each bettor’s pick for post-race comparison.
Notable Techniques and Idioms
- String comparison for numeric range validation (
b$<"1" OR b$>"8") is a common Spectrum BASIC idiom that works correctly for single-digit inputs. RESTORE 150andRESTORE 300reset the DATA pointer to specific lines, allowing the UDG data and fanfare data to be re-read on restart.- The OVER 1 PRINT technique at lines 90, 100, and 250 XORs color attributes onto the track background without disturbing underlying characters.
- BORDER changes synchronize with BEEP during the race (line 370–371), giving a light-show effect tied to horse movement.
Bugs and Anomalies
- Variable
ireused as loop variable and horse: Variableiis used as a FOR loop counter in lines 90–100 and also as a horse position variable at line 350 (LET i=a), but horseiis never subsequently advanced or printed — the race only handles horsesathroughh(eight horses), making variableiat line 350 a vestigial assignment with no effect. - Winner jingle uses variable
xfor pitch: Line 660 referencesxin BEEP calls, butxat this point holds the last value from the UDG definition loop (0–7), making the jingle pitches semi-arbitrary rather than intentional musical notes. - Bettor picks not evaluated: The program announces which horse number wins (variable
r) but never comparesrto the stored bettor inputs (w$), so no winner among the bettors is declared. - Line 680 RESTORE 150 then GO TO 10: Restarting at line 10 re-runs UDG setup and machine code calls, then falls through to the bet entry at line 700 again — this is intentional as a full restart, though the RESTORE at line 680 is redundant since GO SUB 140 at line 30 will read the same DATA regardless (the RESTORE at line 860 covers the fanfare data reset).
Source Code
10 REM !VAL \~!CODE \RETURN (RETURN ( THEN ▞ THEN AND *U\>"PEEK #(IF RESTORE !LEN \N!@RESTORE !VAL \~NEW o>& THEN % THEN IF gSTR$ RESTORE !VAL \~LLIST RETURN 8RETURN 8"TAB \LLIST RESTORE ~= OR RETURN [UDG-A]8p[UDG-A]STR$ XCOPY OR [UDG-A]o> THEN % THEN IF gSTR$ RESTORE !ATTR \~LLIST RETURN (rCAT >2AT \RESTORE ~W!ATTR \~RETURN z({*TAB \ OR STR$ r#s+:AT \RETURN (%RETURN xRETURN RESTORE !CODE \ THEN FLLIST OR LPRINT PIGO SUB BSTR$ LPRINT PIGO SUB BSTR$ LINE "TAB \LLIST #:AT \=2AT \ POINT !CODE \ THEN F*TAB \(PIGO SUB BA▄PIGO SUB BATTR \RETURN #"TAB \LLIST #>"PEEK >=DATA ▌! THEN ? THEN THEN -SAVE THEN ? THEN THEN ,SAVE T]!ATTR \~RETURN (FLASH OR RESTORE ~VERIFY *TAB \r#s+"TAB \LLIST #STR$ CONTINUE *TAB \RETURN PIGO SUB B"TAB \LLIST #>"PEEK >=[UDG-B]xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
20 DEF FN a()= PEEK 23635+256* PEEK 23636+5
30 POKE 23660,30:CLS :GO SUB 140
40 POKE 23728,8:POKE 23729,3:POKE 23723,3:RANDOMIZE USR FN a()
50 BORDER 7:BEEP 2,15:REM "[UDG-M] HORSERACE [UDG-M]"
60 POKE 23728,20:POKE 23729,4:POKE 23723,2:RANDOMIZE USR FN a()
70 REM PAPER 2;"PRESS ANY KEY TO BEGIN!"
80 GO SUB 630:PLOT 0,88:DRAW 255,0:PLOT 0,119:DRAW 255,0:PRINT AT 0,7;"© R.J. Cunningham"
90 FOR i=1 TO 6:PRINT AT i,0; OVER 1; PAPER 2; BRIGHT 1,,:NEXT i
100 FOR i=11 TO 21:PRINT AT i,0; OVER 1; PAPER 4; BRIGHT 1,,:NEXT i
110 REM l/n 1-6 large print; l/n7-14 opening graphics
120 REM l/n 15-40 udg graphics
130 PAUSE 0:GO TO 700
140 FOR x=0 TO 7:READ n:POKE USR "[UDG-M]"+x,n:NEXT x
150 DATA 0,0,134,127,62,60,86,169
160 FOR x=0 TO 7:READ n:POKE USR "[UDG-A]"+x,n:NEXT x
170 DATA 0,24,24,154,127,60,60,60
180 LET H$= CHR$ 17+ CHR$ 5+"_[UDG-M]":RETURN
190 REM track layout
200 FOR x=2 TO 16 STEP 2
210 PRINT AT x,0;x/2;"_[UDG-A]_____________________________"
220 NEXT x
230 GO SUB 630
240 FOR y=31 TO 159:PLOT 248,y:NEXT y
250 FOR i=0 TO 21:PRINT AT i,0; OVER 1; PAPER 5,,:NEXT i
260 REM racing bugle sound
270 READ d,p:LET d=d/18
280 IF P=999 THEN PAUSE 30:GO TO 330
290 BEEP d,p:GO TO 270
300 DATA 3,1,2,6,2,10,3,13,1,13,1,13,2,13,3,10,1,10,1,10,3,10,3,6,2,10,2,6,5,1,4,69,4,69
310 DATA 3,1,2,6,2,10,3,13,1,13,1,13,2,13,3,10,1,10,1,10,1,10,2,1,2,1,2,1,3,6
320 DATA 999,999
330 PRINT AT 21,9; PAPER 2; INK 9;"THEY'RE OFF!"
340 REM horserace program
350 RANDOMIZE :LET a=3:LET b=a:LET c=a:LET d=a:LET e=a:LET f=a:LET g=a:LET h=a:LET i=a
360 LET r= INT (RND*8+1)
370 BEEP .05,r:BORDER r-1
380 GO TO (r*30)+360
390 PRINT AT 2,a-1;h$
400 LET a=a+1:IF a=32 THEN GO TO 640
410 GO TO 360
420 PRINT AT 4,b-1;h$
430 LET b=b+1:IF b=32 THEN GO TO 640
440 GO TO 360
450 PRINT AT 6,c-1;h$
460 LET c=c+1:IF c=32 THEN GO TO 640
470 GO TO 360
480 PRINT AT 8,d-1;h$
490 LET d=d+1:IF d=32 THEN GO TO 640
500 GO TO 360
510 PRINT AT 10,e-1;h$
520 LET e=e+1:IF e=32 THEN GO TO 640
530 GO TO 360
540 PRINT AT 12,f-1;h$
550 LET f=f+1:IF f=32 THEN GO TO 640
560 GO TO 360
570 PRINT AT 14,g-1;h$
580 LET g=g+1:IF g=32 THEN GO TO 640
590 GO TO 360
600 PRINT AT 16,h-1;h$
610 LET h=h+1:IF h=32 THEN GO TO 640
620 GO TO 360
630 DRAW 255,0:DRAW 0,175:DRAW -255,0:DRAW 0,-175:RETURN
640 PRINT AT 18,4;".....The winner is ";r;"!!!"
650 PRINT AT r*2,0; OVER 1; BRIGHT 1,,
660 FOR a=1 TO 10:BEEP .5,x-1:BEEP .5,x-5:BEEP .5,x-3:BEEP .5,x-10:PAUSE 10:BEEP .5,x-10:BEEP .5,x-3:BEEP .5,x-1:BEEP .8,x-5
670 PRINT AT 21,2;"PRESS ENTER TO RACE AGAIN!!!"
680 PAUSE 1800:CLS :RESTORE 150:GO TO 10
690 REM bet entries
700 BEEP 1,10:CLS :BORDER 6
710 GO SUB 630
720 PRINT AT 1,7; PAPER 6;"TREVI RACE TRACK"
730 PRINT " There are eight horses in this"'" race."
740 PRINT :PRINT " Pick the"; PAPER 6;" TREVI "; PAPER 7;"entry and you"'" HAVE YOURSELF A WINNER!"
750 INPUT "OK! HOW MANY BETTORS? NO MORE THAN EIGHT PLEASE!";b$:BEEP .1,25
760 IF b$<"1" OR b$>"8" THEN GO TO 750
770 IF LEN b$>1 THEN GO TO 750
780 PRINT
790 FOR a=1 TO VAL b$
800 PRINT PAPER 5;a; " Bets horse#:";
810 INPUT w$:IF LEN w$>1 THEN GO TO 810
820 IF W$<"1" OR W$>"8" THEN GO TO 810
830 BEEP .2,15:PRINT INK 7; PAPER 2; BRIGHT 1;A;" BETS HORSE # ";W$
840 NEXT a
850 PRINT :PRINT AT 21,8; INK 7; PAPER 2;"ALL BETS DOWN!"
860 PAUSE 100:RESTORE 300:CLS
870 GO TO 190
880 SAVE "horserace" LINE 10Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.