This program implements a two-player tennis game rendered using UDG (User Defined Graphics) characters for the players and ball. The court is drawn using PLOT and DRAW commands, including arced lines for an opening title screen that suggests a perspective view of a tennis court. Player one uses keyboard rows 1–5 and A–G, while player two uses 6–0 and H–Enter, read via direct port IN instructions (65022, 63486, 49150, 61438) for responsive input without INKEY$ overhead. The paddle UDGs are multi-character composites with separate graphics for forehand (AS=1), neutral (AS=2), and backhand (AS=3) stances, which also affect the ball’s rebound angle. Ball physics use a fractional vertical velocity (V) stored as a real number, with BX and BX incremented each frame and INT used to convert to screen coordinates for collision detection.
Program Analysis
Program Structure
The program is organized into clearly delineated subroutine blocks:
- Lines 110–130: Initialization and title screen call
- Lines 200–260: Setup — winning score input, court drawing, variable initialization
- Lines 300–890: Main game loop — player movement, ball movement, collision detection
- Lines 1000–1200: Ball continuation and return-shot physics
- Lines 2000–2150: Title screen drawing with arc-heavy PLOT/DRAW graphics
- Lines 6000–6099: UDG definition via READ/POKE loop
- Lines 7000–7110: Serve routine
- Lines 9000–9060: Instructions screen
- Lines 9400–9700: Win detection, fanfare, and replay
The main loop is not a conventional FOR/NEXT loop but a GO TO chain: line 300 calls the move-A-and-B subroutine (line 400), then conditionally the serve subroutine (line 7000), then jumps to line 800 for ball movement, which ultimately returns to line 300 via GO TO.
Keyboard Input via IN Ports
Rather than using INKEY$, the program reads keyboard ports directly with IN instructions, which is faster and allows both players to be polled in the same frame:
| Port | Player | Direction |
|---|---|---|
IN 65022 | Player A (left) | Up (keys 1–5) |
IN 63486 | Player A (left) | Down (keys A–G) |
IN 49150 | Player B (right) | Up (keys 6–0) |
IN 61438 | Player B (right) | Down (keys H–Enter) |
The port values are the standard keyboard half-row addresses. The result of the IN call is used directly in arithmetic: LET A=A-(IN 65022)+(IN 63486). When a key on the relevant half-row is pressed, the IN value is non-zero (typically 1 after the bitwise result is implicitly coerced), providing a +1 or -1 nudge to the player’s row position. This is a well-known Spectrum technique for fast simultaneous key reads.
Player Graphics and Stance System
Each player has three stances tracked by variables AS (player A stance) and BS (player B stance), ranging from 1 to 3:
- Stance 1: Backhand — two-character UDG pair (
\b/\afor A,\b/\afor B) - Stance 2: Neutral — single UDG (
\kfor A,\lfor B) - Stance 3: Forehand — two-character UDG pair (
\c/\d)
Stance is updated each frame based on movement direction: moving down increments stance toward 3 (forehand), moving up decrements toward 1 (backhand). This gives a visual representation of the player’s swing direction and also influences the rebound angle calculation (lines 866, 869, 910, 915).
Ball Physics
The ball position is stored as real numbers BX (horizontal) and BY (vertical). Each frame, H (horizontal velocity, ±1) and V (vertical velocity, a fraction) are added. INT is used to convert to character cell positions for display and collision. Vertical velocity is set on each return shot as LET V=M/26 where M = 6+INT(RND*11)-A (or B), introducing randomness based on the player’s row position, giving different angles depending on where the paddle connects. Wall bouncing at rows 2 and 18 simply negates V.
UDG Definitions
Line 6050 defines 11 UDGs (A–L, skipping E) using a nested READ/POKE loop. Each UDG is 8 bytes written to USR A$+V. The data in lines 6054–6076 includes the character label as the first READ element, followed by 8 pixel bytes. Note that the letter A appears as a bare variable reference (not in quotes) in some DATA lines (e.g., DATA "A",0,56,108,84,108,56,16,A), where A at that point in execution still holds 0 from initialization, effectively inserting a zero byte — this is an intentional memory trick rather than a bug, though it relies on A being 0 at line 6050 execution time.
Title Screen Graphics
The subroutine at line 2000 draws an elaborate title screen using PLOT/DRAW and a series of arcs from DATA statements. Lines 2040–2070 draw curved shapes suggesting a perspective view of a court with players. The DATA block at lines 2090–2130 stores 18 PLOT/DRAW coordinate sets read in the loop at line 2080. A sentinel value of 999 at line 2140 terminates the DATA but is never actually checked — the loop count of 18 controls termination instead.
The large PRINT strings at lines 2010–2020 use block graphic escape sequences (\:: = █, \ = space) to render a large “TENNIS” logo in block graphics.
Collision Detection
Collision is checked when BX is at column 2 (player A) or 29 (player B). There are three collision cases per player:
- Direct hit at the player’s row: the player loses a point (ball hits the player body)
- Top-edge hit when in backhand stance (AS=1): returns the ball
- Bottom-edge hit when in forehand stance (AS=3): returns the ball
If none of these match, the ball continues past (BX goes to 0 or 31), triggering a point for the opponent at lines 860 and 900.
Notable Anomaly
Line 7040 (IF R<=.5 THEN LET V=-6/26) lacks a leading IF S=2 THEN guard. This means that during a player A serve (S=1), if R<=.5, line 7040 will still overwrite V regardless, making the conditional at line 7030 redundant — the serve always executes line 7040. The serve still works because line 7035 sets an initial V for S=2 and line 7040 may immediately overwrite it, but for S=1 the initial V set in lines 7020–7030 is always overwritten by line 7040 when R<=.5. This is a minor logic bug that affects serve trajectory consistency for player A.
Content
Source Code
110 REM TENNIS by P.SPROSTON
125 BORDER 4: PAPER 4: INK 0: CLS
130 GO SUB 2000
200 PRINT AT 19,0;,,
210 INPUT "WHAT DO YOU WANT AS THE WINNING SCORE ";WS
215 IF WS<=0 THEN INPUT "DONT BE SILLY -- PRESS ENTER";Y$: GO TO 210
220 CLS : PLOT 40,144: DRAW 168,0: DRAW 0,-113: DRAW -168,0: DRAW 0,113: DRAW 0,-15: DRAW 168,0: DRAW 0,-83: DRAW -168,0: DRAW 42,0: DRAW 0,82: DRAW 0,-41
230 DRAW 82,0: DRAW 0,41: DRAW 0,-82: PLOT 123,152: DRAW 0,-129: DRAW 2,0: DRAW 0,129: DRAW -2,0
240 LET AP=0: LET BP=0
250 LET A=10: LET B=10: REM PS
255 LET AS=2: LET BS=2: REM SHOT
260 LET S=1+INT (RND*2)
270 IF S=1 THEN LET H=+.5
280 IF S=2 THEN LET H=-.5
300 REM CONTROL
310 GO SUB 400: REM MOVE A+B
330 IF S<>0 THEN GO SUB 7000: REM SERVE
340 GO TO 800
399 REM PRINT SCORE
400 PRINT AT 0,1;"Pts:";AP;AT 0,25;"Pts:";BP
410 IF AP>WS-1 OR BP>WS-1 THEN GO TO 9500
449 REM MOVE A
450 LET OA=A
460 LET A=A-(IN 65022)+(IN 63486)
462 IF A<2 THEN LET A=2
464 IF A>19 THEN LET A=19
465 LET AS=AS+(OA<A AND AS<3)-(OA>A AND AS>1)
469 PRINT AT OA+1,2;" ";AT OA,2;" ";AT OA-1,2;" "
470 IF AS=1 THEN PRINT AT A,2;"\b";AT A-1,2;"\a"
471 IF AS=2 THEN PRINT AT A,2;"\k"
472 IF AS=3 THEN PRINT AT A,2;"\c";AT A+1,2;"\d"
550 REM MOVE B
560 LET OB=B
570 LET B=B-(IN 49150)+(IN 61438)
572 IF B<2 THEN LET B=2
574 IF B>19 THEN LET B=19
575 LET BS=BS+(OB<B AND BS<3)-(OB>B AND BS>1)
578 PRINT AT OB+1,29;" ";AT OB,29;" ";AT OB-1,29;" "
580 IF BS=3 THEN PRINT AT B,29;"\c";AT B+1,29;"\d"
582 IF BS=2 THEN PRINT AT B,29;"\l"
584 IF BS=1 THEN PRINT AT B,29;"\b";AT B-1,29;"\a"
595 RETURN
600 IF S<>0 THEN GO SUB 7000: REM SERVE
790 REM MOVE BALL
800 PRINT OVER 1;AT INT BY,INT BX;"\f"
820 LET BX=BX+H
830 LET BY=BY+V
840 IF BY>18 OR BY<2 THEN LET BY=BY-V: LET V=V*-1
845 IF BX<26 AND BX>3 THEN GO TO 1000
850 IF BX>25 THEN GO TO 900
852 REM CHECK A
860 IF BX<0 THEN LET BP=BP+1: BEEP .03,0: LET AS=2: LET BS=2: LET S=2: GO TO 300
863 IF INT BY=A AND BX=2 THEN PRINT AT A-1,2;" ";AT A,2;" ";AT A+1,2;" ": LET BP=BP+1: LET S=2: PRINT AT A,1;"\g\h": BEEP .1,-10: FOR C=1 TO 100: NEXT C: PRINT AT A,1;" ": GO TO 300
866 IF A-1=INT BY AND BX=2 AND AS=1 THEN BEEP .03,20: GO TO 1100
869 IF A+1=INT BY AND BX=2 AND AS=3 THEN BEEP .03,20: GO TO 1100
890 GO TO 1000
895 REM CHECK B
900 IF BX>31 THEN LET AP=AP+1: BEEP .03,0: LET AS=2: LET BS=2: LET S=1: GO TO 1100
905 IF INT BY=B AND INT BX=29 THEN PRINT AT B-1,29;" ";AT B,29;" ";AT B+1,29;" ": LET AP=AP+1: LET S=1: PRINT AT B,29;"\i\j": BEEP .1,-10: FOR C=1 TO 100: NEXT C: PRINT AT B,29;" ": GO TO 300
910 IF B-1=INT BY AND BX=29 AND BS=1 THEN BEEP .03,25: GO TO 1200
915 IF B+1=INT BY AND BX=29 AND BS=3 THEN BEEP .03,25: GO TO 1200
1000 PRINT OVER 1;AT INT BY,INT BX;"\f"
1050 GO TO 300
1100 LET H=1: LET M=6+INT (RND*11)-A: LET V=M/26: GO TO 300
1200 LET H=-1: LET M=6+INT (RND*11)-B: LET V=M/26: GO TO 300
1900 REM SCREEN
2000 REM
2010 PRINT ''"\::\::\::\::\::\ \::\::\::\::\ \::\ \ \::\ \ \::\ \ \::\ \::\::\::\::\::\ \::\::\::\::\ \ \::\ \ \ \::\ \ \ \ \::\::\ \::\ \ \::\::\ \::\ \ \::\ \ \ \::\ \ \ \ \ \::\ \ \ \::\::\::\ \ \::\::\ \::\ \ \::\::\ \::\ \ \::\ \ \::\::\::\::"
2020 PRINT "\ \ \::\ \ \ \::\ \ \ \ \::\::\ \::\ \ \::\::\ \::\ \ \ \::\ \ \ \ \ \ \::\ \ \::\ \ \ \::\ \ \ \ \::\ \::\::\ \ \::\ \::\::\ \ \ \::\ \ \ \ \ \ \::\ \ \::\ \ \ \::\ \ \ \ \::\ \::\::\ \ \::\ \::\::\ \ \ \::\ \ \ \ \ \ \::\ \ \::\ \ \ \::\::\::\::\ \::\ \ \::\ \ \::\ \ \::\ \::\::\::\::\::\ \::\::\::\::"
2025 PLOT 0,0: DRAW 0,175: DRAW 255,0: DRAW 0,-175: DRAW -255,0
2030 CIRCLE 50,50,27
2040 PLOT 35,72: DRAW -10,-23,-4
2050 PLOT 74,40: DRAW -13,-15,1.3
2060 PLOT 255,90: DRAW -170,-90,1
2070 PLOT 255,75: DRAW -150,-75,1
2075 RESTORE 2090
2080 FOR i=1 TO 18: READ A
2085 READ B: READ C: READ D: PLOT A,B: DRAW C,D: NEXT i
2090 DATA 245,74,10,-7,230,73,25,-20,215,70,40,-35
2095 DATA 199,67,56,-49,185,63,70,-63
2100 DATA 172,56,63,-56,158,49,55,-49
2110 DATA 145,40,46,-40,132,30,36,-30
2120 DATA 122,20,24,-20,112,10,12,-10
2122 DATA 108,05,70,55,122,0,94,72
2124 DATA 144,0,96,74,166,0,89,68
2126 DATA 188,0,67,53,210,0,45,37
2130 DATA 233,0,22,20,252,0,3,3
2140 DATA 999
2150 PRINT #1;"PRESS ANY KEY": PAUSE 0: CLS : GO SUB 9000
6000 REM UDGs
6050 RESTORE 6050: FOR C=1 TO 11: READ A$: FOR V=0 TO 7: READ A: POKE USR A$+V,A: NEXT V: NEXT C
6054 DATA "A",0,56,108,84,108,56,16,A
6056 DATA "B",16,A,152,132,188,252,56,0
6058 DATA "C",0,56,127,121,65,48,16,A
6060 DATA "D",A,A,56,108,84,108,56,0
6064 DATA "F",A,A,A,16,56,16,0,A
6066 DATA "G",7,5,7,16,41,47,41,16
6068 DATA "H",0,192,90,148,16,240,143,97
6070 DATA "I",134,241,15,8,41,90,3,0
6072 DATA "J",8,148,244,148,8,224,160,224
6074 DATA "K",7,5,71,242,238,66,60,0
6076 DATA "L",A,60,66,119,79,226,160,224
6099 RETURN
7000 PRINT AT A-1,2;" ";AT A,2;" ";AT A+1,2;" "
7005 PRINT AT B-1,29;" ";AT B,29;" ";AT B+1,29;" "
7010 LET A=10: LET B=10: LET AS=2: LET BS=2
7015 GO SUB 400
7020 IF S=1 THEN LET H=1: LET BS=2: LET BX=3: LET BY=10: LET R=RND: IF R>.5 THEN LET V=6/26
7030 IF S=1 THEN IF R<=.5 THEN LET V=-6/26
7035 IF S=2 THEN LET H=-1: LET AS=2: LET BX=28: LET BY=10: LET R=RND: IF R>.5 THEN LET V=6/26
7040 IF R<=.5 THEN LET V=-6/26
7050 PRINT OVER 1;AT BY,BX;"\f"
7080 IF S=1 THEN PRINT AT 20,1; FLASH 1;"SERVICE-press Q": IF INKEY$<>"Q" AND INKEY$<>"q" THEN GO TO 7080
7090 IF S=2 THEN PRINT AT 20,16; FLASH 1;"SERVICE-press P": IF INKEY$<>"P" AND INKEY$<>"p" THEN GO TO 7090
7100 PRINT AT 20,0;,,
7110 LET S=0: RETURN
8990 REM INSTRUCTIONS
9000 PRINT AT 1,12;"TENNIS"
9010 PRINT TAB 5;"-By PHILLIP SPROSTON-"
9020 PRINT '" A GAME FOR TWO PLAYERS": PRINT '" HIT THE BALL WITH A FOREHAND OR BACKHAND": PRINT '" PLAYER ONE--LEFT"
9030 PRINT " KEYS 1-5=UP"'" A-G=DOWN"
9040 PRINT '" PLAYER TWO--RIGHT"'" KEYS 6-0=UP"'" H-ENTER=DOWN"
9045 PRINT '" MAKE SURE THE BALL DOES NOT HIT YOUR PLAYER OR YOU LOSE"
9050 PRINT AT 19,10;"PLEASE WAIT"
9060 RETURN
9400 REM WON
9500 CLS
9510 PRINT AT 5,4;"PLAYER ";
9520 IF AP>BP THEN PRINT "A";: LET X=AP-BP
9530 IF AP<BP THEN PRINT "B";: LET X=BP-AP
9540 IF X>1 THEN PRINT " WINS BY ";X;" POINTS"
9550 IF X=1 THEN PRINT " WINS BY ";X;" POINT"
9560 FOR C=-20 TO 22 STEP 2: BEEP .03,ABS C: NEXT C
9570 FOR C=1 TO 20: NEXT C: FOR C=1 TO 3: BEEP .2,-20: NEXT C: BEEP .6,-24
9600 PRINT ''''''" Q to QUIT or any other to PLAY"
9605 IF INKEY$<>"" THEN GO TO 9605
9610 PAUSE 0
9620 IF INKEY$="Q" OR INKEY$="q" THEN BORDER 7: PAPER 7: CLS : PRINT AT 10,8;"START THE TAPE": LOAD ""
9700 GO TO 200
9998 SAVE "Tennis" LINE 1
9999 VERIFY ""
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

