Tennis

Developer(s): Phillip Sproston
Type: Program
Platform(s): TS 2068
Tags: Game

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:

PortPlayerDirection
IN 65022Player A (left)Up (keys 1–5)
IN 63486Player A (left)Down (keys A–G)
IN 49150Player B (right)Up (keys 6–0)
IN 61438Player 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/\a for A, \b/\a for B)
  • Stance 2: Neutral — single UDG (\k for A, \l for 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:

  1. Direct hit at the player’s row: the player loses a point (ball hits the player body)
  2. Top-edge hit when in backhand stance (AS=1): returns the ball
  3. 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

Appears On

One of a series of library tapes compiled from multiple user groups.

Related Products

Related Articles

Related Content

Image Gallery

Tennis

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.

Scroll to Top