--- title: "Pong" id: 70815 type: "computer_media" slug: "pong" url: "http://localhost/computer_media/pong/" markdown_url: "http://localhost/computer_media/pong.md" published_at: "2026-08-23T15:08:03+00:00" modified_at: "2026-08-23T22:37:25+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/08/pong.png" alt: "Pong screen" excerpt: "A two-player Pong clone with a circular UDG ball sprite, ATTR-based collision detection, and five-goal match play — all in BASIC." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Arcade" slug: "arcade" taxonomy: "genre" url: "http://localhost/type/arcade/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Pong%20%28198x%29%28-%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2026/08/pong.png" alt: "Pong screen" media_type_tags: "Arcade" --- # Pong This two-player Pong implementation uses a UDG (User Defined Graphic) character to render a circular ball sprite by poking a filled-circle bitmap (60,126,255,255,255,255,126,60) into the UDG “A” slot. Both players control paddles with keyboard keys: 1/Q for the left paddle and 0/P for the right, with the ball bouncing off paddles and walls detected via ATTR reads on the screen cells. The ball’s horizontal and vertical direction vectors are stored in variables w and z, and collision is checked by reading the ink/paper attribute at the ball’s next position. Scores are tracked in gl and gr, and the first player to reach five goals wins, triggering a flash display and a play-again prompt. *** ### Program Structure The program is organized into clearly labeled sections using REM comments at round-numbered lines: - **Lines 1000–1020:** Initialization — calls UDG setup, instructions, and game setup routines. - **Lines 2000–2170:** Main game loop — ball movement, player input, collision detection, and scoring trigger. - **Lines 3000–3060:** Paddle movement subroutines for both players. - **Lines 4000–4100:** Scoring, win detection, flash display, and play-again logic. - **Lines 9000–9220:** UDG definition (for character “A”), court drawing, and variable initialization. - **Lines 9500–9595:** Instructions screen, and a second UDG definition routine (for character “a”). ### UDG Ball Sprite The ball is drawn using a User Defined Graphic character. Two separate routines define UDGs: the subroutine at line 9586 reads BIN literals from DATA statements at lines 9570–9584 and POKEs them into `USR "a"` (lowercase), while the subroutine at line 9020 reads decimal values from the DATA at line 9050 and POKEs them into `USR "A"` (uppercase). Both encode the same filled-circle pattern (60, 126, 255, 255, 255, 255, 126, 60). The ball is rendered in the main loop using `PRINT INK 1;AT y,b;"\a"`, referencing the lowercase UDG. ### Collision Detection via ATTR Rather than tracking paddle positions mathematically for every collision check, the program uses the `ATTR` function to read the color attribute of the screen cell at the ball’s projected next position. The value `64` (PAPER 0, BRIGHT 0, INK 0 — black on black) is used to paint paddles and boundary walls, so any collision is detected by checking `IF ATTR (y+z,b)=64` (vertical) or `IF ATTR (y+z,b+w)=64` (horizontal). This elegantly unifies paddle and wall collision in two lines. ### Ball Movement and Direction Vectors The ball’s position is stored in `y` (row) and `b` (column). Direction is encoded as `z` (vertical, ±1) and `w` (horizontal, ±1 initialized to -1 so the ball always starts moving left). At the start of each round, the ball’s row is randomized and direction is randomized for both axes. After each frame the direction vectors are simply added to the position, and reversed on collision. ### Paddle Control and Boundary Checking Each player’s paddle occupies two rows tracked by top/bottom variable pairs: `lt`/`lb` (left) and `rt`/`rb` (right). Movement subroutines erase the old paddle cells (printing spaces), update both row variables together, and redraw the paddle in PAPER 0. Boundary guards prevent the paddle leaving the playfield: upward movement is blocked when `lt>1` and downward when `lb<20`. | Key | Action | | --- | --- | | `1` | Left paddle up | | `Q` | Left paddle down | | `0` | Right paddle up | | `P` | Right paddle down | ### Court Drawing The playing court is drawn entirely with PAPER 0 (black) print cells. The top and bottom borders are full rows of spaces at rows 0 and 21, and the left and right walls are built in a FOR loop (lines 9180–9200) printing single black cells at columns 0 and 31 for rows 1–8 and 14–21, leaving a gap in the middle for the score area. Paddles are placed at column 2 (left) and column 29 (right), one cell inside the walls. ### Scoring and Win Condition When the ball’s column reaches 0 or 31 (line 2160), the program jumps to line 4000. `gl` accumulates left-player goals and `gr` right-player goals. Both scores are displayed with FLASH and BRIGHT at row 4. First to five goals triggers a “ANY KEY TO PLAY AGAIN” prompt; accepting resets both scores and restarts the round, but does not redraw the court — only re-randomizing the ball, which could leave visual artifacts if paddles have moved far from their starting positions. ### Notable Bugs and Anomalies - Line 4030 is reached by fall-through from the `IF b=0` branch at line 4000 when `b=31` (right wall), but this only works because the right-goal branch sets `b=1` rather than resetting it to 30 — consistent with the ball restarting near the right side. - The `POKE 23658,8` at line 1020 sets the system variable FLAGS2 to enable CAPS LOCK, ensuring keys like Q and P are detected in uppercase without the player needing to set it manually. - The INPUT at line 9560 uses `LINE A$` to accept any input including an empty ENTER press, serving as a clean “press Enter to continue” prompt without needing INKEY$ polling. - Line 3060 (`RETURN`) is never reached by normal control flow, as each active subroutine in lines 3020–3050 has its own `RETURN` at its end, and the main loop only calls these subroutines when the corresponding key is pressed. ## Source Code ``` 1000 REM ***PONG*** 1010 GO SUB 9586 1020 POKE 23658,8: GO SUB 9500: GO SUB 9000 2000 REM SET BALL ROLLING 2020 LET b=30: LET w=-1 2030 LET y=INT (RND*8+1) 2040 IF RND<.5 THEN LET y=y+12 2050 LET z=1 2060 IF RND<.5 THEN LET z=-z 2070 PRINT INK 1;AT y,b;"\a" 2080 IF INKEY$="1" THEN GO SUB 3020: GO TO 2120 2090 IF INKEY$="Q" THEN GO SUB 3030: GO TO 2120 2100 IF INKEY$="0" THEN GO SUB 3040: GO TO 2120 2110 IF INKEY$="P" THEN GO SUB 3050 2120 PRINT AT y,b;" " 2130 IF ATTR (y+z,b)=64 THEN LET z=-z: BEEP .1,10 2140 IF ATTR (y+z,b+w)=64 THEN LET w=-w: BEEP .1,10 2150 LET y=y+z: LET b=b+w 2160 IF b=0 OR b=31 THEN GO TO 4000 2170 GO TO 2070 3000 REM MOVE BAT 3020 IF lt>1 THEN PRINT AT lt,2;" ";AT lb,2;" ": LET lt=lt-1: LET lb=lb-1: PRINT PAPER 0;AT lt,2;" ";AT lb,2;" ": RETURN 3030 IF lb<20 THEN PRINT AT lt,2;" ";AT lb,2;" ": LET lt=lt+1: LET lb=lb+1: PRINT PAPER 0;AT lt,2;" ";AT lb,2;" ": RETURN 3040 IF rt>1 THEN PRINT AT rt,29;" ";AT rb,29;" ": LET rt=rt-1: LET rb=rb-1: PRINT PAPER 0;AT rt,29;" ";AT rb,29;" ": RETURN 3050 IF rb<20 THEN PRINT AT rt,29;" ";AT rb,29;" ": LET rt=rt+1: LET rb=rb+1: PRINT PAPER 0;AT rt,29;" ";AT rb,29;" ": RETURN 3060 RETURN 4000 IF b=0 THEN LET gr=gr+1: LET b=30: GO TO 4040 4030 LET gl=gl+1: LET b=1 4040 PRINT FLASH 1; BRIGHT 1; PAPER 6; INK 0;AT 4,5;gl;AT 4,25;gr: IF gl=5 OR gr=5 THEN GO TO 4080 4050 FOR n=1 TO 500: NEXT n 4060 PRINT AT 4,5;" ";AT 4,25;" " 4070 GO TO 2030 4080 PRINT FLASH 1;AT 10,5;"ANY KEY TO PLAY AGAIN.": FOR n=1 TO 500: NEXT n 4090 IF INKEY$="" THEN GO TO 4090 4100 PRINT AT 10,5;" ";AT 4,5;" ";AT 4,25;" ": LET gl=0: LET gr=0: GO TO 2030 9000 REM USR 9020 FOR n=0 TO 7 9030 READ a: POKE USR "A"+n,a 9040 NEXT n 9050 DATA 60,126,255,255,255,255,126,60 9100 LET lt=10: LET lb=11 9110 LET rt=10: LET rb=11 9120 BORDER 2: PAPER 5: BRIGHT 1: INK 0: CLS 9130 PRINT PAPER 0;AT lt,2;" " 9140 PRINT PAPER 0;AT lb,2;" " 9150 PRINT PAPER 0;AT rt,29;" " 9160 PRINT PAPER 0;AT rb,29;" " 9170 PRINT PAPER 0;AT 0,0;" ";AT 21,0;" " 9180 FOR n=1 TO 8 9190 PRINT PAPER 0;AT n,0;" ";AT n+13,0;" ";AT n,31;" ";AT n+13,31;" " 9200 NEXT n 9210 LET gl=0: LET gr=0 9220 RETURN 9500 REM INSTRUCTIONS 9520 BORDER 1: PAPER 1: INK 0: CLS 9530 PRINT PAPER 6; BRIGHT 1;AT 1,12;" PONG " 9540 PAPER 7 9550 PRINT AT 4,0;" This is a 2 player game. ";AT 10,0;" Left player -- 1 for up ";AT 12,16;" Q down";AT 16,0;"Right player -- 0 for up ";AT 18,16;" P down " 9560 INPUT "ENTER TO PLAY "; LINE A$: RETURN 9570 DATA BIN 00111100 9572 DATA BIN 01111110 9574 DATA BIN 11111111 9576 DATA BIN 11111111 9578 DATA BIN 11111111 9580 DATA BIN 11111111 9582 DATA BIN 01111110 9584 DATA BIN 00111100 9586 FOR k=0 TO 7 9588 READ n: POKE USR "a"+k,n 9590 NEXT k 9595 RETURN ```