PAIRS is a memory matching game that displays ten pairs of high-resolution pictures and then conceals them, challenging the player to match all pairs in the fewest attempts. Each picture is a 3×3 grid of UDG (User Defined Graphic) characters, with nine UDGs (characters 144–152, accessed as “\a” through “\i”) dynamically reloaded each turn from an 11×72 DATA array to render different images. The game uses direct screen attribute POKEs at address 22595 to flash and highlight selected cells, converting row/column input into a position index via a custom subroutine at line 540. A lowest-score tracker persists across sessions within a single run, and a 20-character string S$ tracks which pairs have been matched. Card positions are randomized each game by shuffling both picture identity and screen location through independent string-encoded bitmaps.
Program Analysis
Program Structure
The program is organized into several functional regions, with initialization at lines 600–800, the main game loop at lines 50–470, input handling at lines 270–370, and utility subroutines at lines 520–570. Data for eleven picture sprites, screen coordinates, attribute offsets, and UDG column offsets are stored in DATA statements from line 1000 onward.
- Lines 5–40: Variable and array declarations, initial constants.
- Lines 50–230: Screen setup, UDG loading, grid drawing, and countdown display.
- Lines 240–410: Main input loop — prompt, validate, match-check, reveal/hide.
- Lines 420–470: End-of-game routine, lowest-score tracking, restart.
- Lines 520–570: Subroutines — cell reveal (520), cell fill (530), row/column decode (540).
- Lines 600–800: Initialization — data load, randomization of picture/position assignments.
- Lines 1000–2050: DATA blocks for 11 sprite bitmaps, screen AT coordinates, and attribute table offsets.
- Lines 9000–9030: Save/verify utility, separate from the game.
UDG Sprite System
Each picture occupies a 3×3 grid of UDG characters (UDGs \a through \i, characters 144–152). The sprite data for each picture consists of 9 UDGs × 8 rows = 72 bytes, stored in the array A(12,72). At line 70, the inner loop writes bytes into the UDG area using POKE USR CHR$(143+M)+L, A(CODE G$(K)-64, L*9+M), cycling through all 8 pixel rows for each of the 9 UDGs. Since only one set of 9 UDGs exists, the program redefines them before printing each picture in the reveal phase (lines 60–120), relying on the fact that PRINT AT renders immediately from current UDG definitions.
Screen Attribute Manipulation
Rather than using PRINT for color changes during gameplay, the program directly POKEs the ZX Spectrum attribute file. The base address Z=22595 is the start of the attribute area for the playfield. The arrays T(20) and U(9) hold row offsets and column offsets respectively, read from DATA at lines 2040–2050. To color a cell, a nested loop at lines 210 and 260 iterates over all 20 positions and 9 UDG columns, writing attribute bytes directly: POKE Z+T(Y)+U(X), W. This is considerably faster than re-printing with PAPER/INK attributes for 20 locations.
Randomization and State Encoding
The randomization at lines 700–790 uses three string variables as bitmaps. F$ (length 11) tracks which of the 11 available pictures have been selected; E$ (length 20) tracks which of the 20 screen positions have been assigned. Each selected picture is encoded into G$ (the picture-to-draw string, length 10) and its two position assignments into D$ (a 20-character string where pairs occupy positions X*2-1 and X*2). All encodings use CHR$(64+N) so that values 1–20 map to characters ‘A’–’T’, enabling CODE to recover the index.
The matched-pair state is similarly tracked in S$, a 20-character string where S$(P)="1" marks position P as already matched. The end-of-game check at line 405 scans all 20 positions in S$ for any unmatched entry.
Row/Column Input Decoding (Lines 540–570)
The subroutine decodes a two-character input like "82" into a position index 1–20. The grid has columns 1–5 and rows 6–9, giving 20 cells. The logic in lines 540–550 separates the two characters: digits 6–9 are treated as the row (stored in A) and digits 1–5 as the column (stored in B). The position is then computed as P = 5*(A-6)+B. The use of Boolean arithmetic — e.g., (VAL X$(1) AND (CODE X$(1)>53 AND CODE X$(1)<58)) — extracts the digit conditionally without IF statements, exploiting the Spectrum’s AND returning its left operand or 0.
Grid Drawing
The grid of rectangles is drawn at lines 140–170 using PLOT/DRAW. Two nested loops step X from 23 to 200 and Y from 160 down to 40, each step being ±40 pixels (one UDG-height unit of 3 characters). Each cell border is a 27×27 pixel square drawn with four DRAW calls. The variables A=-40 and B=-A=40 are used as step values to avoid repeating the literal.
Notable Techniques and Idioms
- Countdown display at line 230 uses an inner
FOR Y=1 TO 250: NEXT Ybusy-wait delay loop for each second. POKE 23609,110at line 15 sets the keyboard repeat delay (REPDEL system variable), likely to prevent accidental key repetition during INPUT.- The
PAPER 6: INK 6at line 50 with subsequent cell-coloring POKEs uses a “hidden” ink technique: setting both PAPER and INK to the same color renders UDGs invisible while leaving their positions addressable. - Line 340 uses a compound Boolean expression in the IF condition to test both orderings of a pair match:
(D$(X*2-1)=CHR$(SA+64) AND D$(X*2)=CHR$(SB+64))+(D$(X*2-1)=CHR$(SB+64) AND D$(X*2)=CHR$(SA+64)), summing two Boolean values so that either ordering triggers the match. - Line 530 (unreachable via normal flow) appears to be an orphaned debug subroutine for coloring a single cell, never called from the main loop.
Data Layout
| DATA line(s) | Content | Size |
|---|---|---|
| 1000 | Ship sprite (UDG bytes) | 72 bytes |
| 1010 | Helicopter sprite | 72 bytes |
| 1020 | Bus sprite | 72 bytes |
| 1030 | Train engine sprite | 72 bytes |
| 1040 | Tree sprite | 72 bytes |
| 1050 | Car sprite | 72 bytes |
| 1060 | Television set sprite | 72 bytes |
| 1080 | Flying goose sprite | 72 bytes |
| 1090 | Tank sprite | 72 bytes |
| 2000 | Clock sprite | 72 bytes |
| 2010 | Apple sprite | 72 bytes |
| 2020 | Screen row AT values for 20 positions | 20 values |
| 2030 | Screen column AT values for 20 positions | 20 values |
| 2040 | Attribute row offsets T(20) | 20 values |
| 2050 | Attribute column offsets U(9) | 9 values |
Bugs and Anomalies
- The
DIM A(12,72)at line 30 allocates 12 rows, but only 11 sprites are read (lines 610:FOR X=1 TO 11), leaving row 12 unused. - The DATA at line 2000 is labeled REM
**CLOCK**at line 1099 but the actual clock data follows at 2000 — suggesting an earlier editing rearrangement left the REM displaced from its data block. - Line 530 (
FOR X=1 TO 9: POKE Z+T(P)+U(X),57: NEXT X: RETURN) is never called from anywhere in the program. It is preceded bySTOPat line 529 and follows the main subroutine at 520, making it dead code. - At line 310,
x$(lowercase) is used as the INPUT variable, but X$ (uppercase) is tested at line 320. On the Spectrum, BASIC is case-insensitive for variable names, so this is not a runtime error, but the inconsistency is notable.
Content
Source Code
5 REM "PAIRS"
10 RESTORE 1000
15 POKE 23609,110
20 BORDER 4: PAPER 5: INK 1
25 LET LS=100: LET TT=0: LET TS=0
30 DIM A(12,72): DIM Q(20): DIM R(20): DIM T(20): DIM U(9)
40 GO TO 600
50 PAPER 6: INK 6: CLS : PRINT AT 0,8; PAPER 7; INK 1; INVERSE 1;" PAIRS "
60 FOR K=1 TO 10: PAPER K/2: INK K/2: FOR L=0 TO 7: FOR M=1 TO 9
70 POKE USR CHR$ (143+M)+L,A(CODE G$(K)-64,L*9+M)
80 NEXT M: NEXT L
90 LET X=CODE D$(K*2-1)-64: LET Y=CODE D$(K*2)-64
100 PRINT AT Q(X),R(X);"\a\b\c";AT Q(X)+1,R(X);"\d\e\f";AT Q(X)+2,R(X);"\g\h\i";
110 PRINT PAPER 6; INK 6;AT Q(Y),R(Y);"\a\b\c";AT Q(Y)+1,R(Y);"\d\e\f";AT Q(Y)+2,R(Y);"\g\h\i";
120 NEXT K
125 PAPER 7: INK 1
130 LET A=-40: LET B=-A
140 FOR Y=160 TO 40 STEP A
150 FOR X=23 TO 200 STEP B
160 PLOT X,Y: DRAW 27,0: DRAW 0,-27: DRAW -27,0: DRAW 0,27
170 NEXT X: NEXT Y
200 LET W=57+INT (RND*3): LET Z=22595
210 FOR X=1 TO 9: FOR Y=1 TO 20: POKE Z+T(Y)+U(X),W: NEXT Y: NEXT X
220 PRINT PAPER 6; INK 0;AT 1,4;"1";AT 1,9;"2";AT 1,14;"3";AT 1,19;"4";AT 1,24;"5";AT 3,0;"6";AT 8,0;"7";AT 13,0;"8";AT 18,0;"9"; PAPER 2; INK 7;AT 6,27;"TRIES"; PAPER 1;AT 9,27;"SCORE"
230 FOR X=5 TO 0 STEP -1: PRINT AT 18,30; INK 7; PAPER 2; FLASH 1;X;: FOR Y=1 TO 250: NEXT Y: NEXT X: PRINT AT 18,30; PAPER 6;" "
240 PRINT AT 20,0; INVERSE 1;"ENTER ROW/COLUMN NUMBERS TO TRY FOR A MATCHING PAIR OF PICTURES"
250 LET W=18: LET Z=22595
260 FOR X=1 TO 20: FOR Y=1 TO 9: POKE Z+T(X)+U(Y),W: NEXT Y: NEXT X
270 INPUT INVERSE 1;"ENTER FIRST PICTURE(eg82)";X$: IF X$="" THEN GO TO 270
280 IF LEN X$<>2 OR X$(1)<"1" OR X$(1)>"9" OR X$(2)<"1" OR X$(2)>"9" THEN GO TO 270
290 GO SUB 540
300 IF P<1 OR P>20 THEN GO TO 270
302 IF S$(P)="1" THEN GO TO 270
305 LET Q=P: LET SA=P
307 FOR X=1 TO 9: POKE Z+T(P)+U(X),40: NEXT X
310 INPUT FLASH 1; INK 2;"ENTER NEXT PICTURE(eg82)";x$;: IF X$="" THEN GO TO 310
320 IF LEN X$<>2 OR X$(1)<"1" OR X$(1)>"9" OR X$(2)<"1" OR X$(2)>"9" THEN GO TO 310
330 GO SUB 540: IF P<1 OR P>20 THEN GO TO 310
332 IF S$(P)="1" THEN GO TO 310
335 FOR X=1 TO 9: POKE Z+T(P)+U(X),40: NEXT X
340 LET SB=P: FOR X=1 TO 10: IF (D$(X*2-1)=CHR$ (SA+64) AND D$(X*2)=CHR$ (SB+64))+(D$(X*2-1)=CHR$ (SB+64) AND D$(X*2)=CHR$ (SA+64)) THEN LET V=57: LET TT=TT+1: LET TS=TS+1: PRINT AT 7,29;TT;AT 10,29;TS: LET S$(SA)="1": LET S$(SB)="1": GO TO 400
350 NEXT X
360 LET TT=TT+1: PRINT AT 7,29;TT;: LET V=W
400 GO SUB 520
405 FOR X=1 TO 20: IF S$(X)<>"1" THEN GO TO 270
410 NEXT X
420 REM **END OF GAME ROUTINE**
430 IF LS>TT THEN LET LS=TT
439 REM ** changed 440 ***
440 PRINT AT 20,0;" ";AT 20,0; INVERSE 1;"YOU TOOK ";TT;" TRIES THIS TIME. LOWEST SCORE THIS SESSION= ";LS;
450 INPUT FLASH 1;"PRESS <ENTER> FOR NEXT GAME";X$
460 LET TT=0: LET TS=0
470 GO TO 640
519 REM **WRONG GUESS ROUTINE**
520 FOR X=1 TO 9: POKE Z+T(Q)+U(X),V: NEXT X: FOR X=1 TO 9: POKE Z+T(P)+U(X),V: NEXT X: RETURN
529 STOP
530 FOR X=1 TO 9: POKE Z+T(P)+U(X),57: NEXT X: RETURN
539 REM **ROW/COLUMN INPUT**
540 LET A=0: LET B=0: LET A=(VAL X$(1) AND (CODE X$(1)>53 AND CODE X$(1)<58))+(VAL X$(2) AND (CODE X$(2)>53 AND CODE X$(2)<58))
550 LET B=(VAL X$(1) AND (CODE X$(1)>48 AND CODE X$(1)<54))+(VAL X$(2) AND (CODE X$(2)>48 AND CODE X$(2)<54))
560 LET P=5*(A-6)+B
570 RETURN
598 STOP
599 REM **INITIALISATION**
600 CLS : PRINT AT 0,8; PAPER 7; INK 1; INVERSE 1;" P A I R S "
605 PRINT AT 2,9; PAPER 7; INK 4; FLASH 1;"INITIALISING"; FLASH 0; INVERSE 1; INK 1;AT 4,0;" THIS PROGRAM DISPLAYS TEN PAIRS OF HIGH RESOLUTION PICTURES AND THEN CONCEALS THEM FROM VIEW. ";AT 9,0;" THE OBJECT OF THE GAME IS TO MATCH ALL TEN PAIRS IN THE LOW- EST POSSIBLE NUMBER OF TRIES. "
610 FOR X=1 TO 11: FOR Y=1 TO 72: READ A(X,Y): NEXT Y: NEXT X
620 FOR X=1 TO 20: READ Q(X): NEXT X: FOR X=1 TO 20: READ R(X): NEXT X
630 FOR X=1 TO 20: READ T(X): NEXT X: FOR X=1 TO 9: READ U(X): NEXT X
640 LET D$="00000000000000000000": LET S$=D$: LET E$=D$: LET F$="00000000000": LET G$=F$( TO 10)
695 PRINT AT 15,4; PAPER 7; INK 2; FLASH 1;"NEXT GAME IN 25 SECONDS"
700 FOR X=1 TO 10
710 LET N=1+INT (RND*11)
720 IF F$(N)="1" THEN GO TO 710
730 LET F$(N)="1"
740 LET NA=1+INT (RND*20)
745 IF E$(NA)="1" THEN GO TO 740
750 LET E$(NA)="1": LET G$(X)=CHR$ (64+N)
760 LET NB=1+INT (RND*20)
770 IF E$(NB)="1" THEN GO TO 760
780 LET E$(NB)="1": LET D$(X*2-1)=CHR$ (64+NA): LET D$(X*2)=CHR$ (64+NB)
790 NEXT X
800 GO TO 50
998 STOP
999 REM **SHIP**
1000 DATA 0,0,0,0,0,8,15,255,224,0,0,0,3,128,8,0,0,0,0,0,0,3,128,8,0,0,0,0,0,0,15,255,8,0,0,0,0,0,0,15,255,8,0,0,0,0,0,0,63,255,252,0,0,0,0,0,8,63,255,248,0,0,0,0,0,8,31,255,240,0,0,0
1009 REM *HELICOPTER**
1010 DATA 0,0,0,0,7,128,0,255,252,0,0,0,0,12,192,0,0,0,0,0,0,96,24,96,0,0,0,0,0,0,127,240,112,0,0,0,0,0,0,111,255,224,0,0,0,0,127,240,3,255,192,0,0,0,0,2,0,0,127,128,0,0,0,0,2,0,0,6,4,0,0,0
1019 REM *BUS**
1020 DATA 0,0,0,0,0,0,60,1,224,0,0,0,63,255,252,24,0,192,0,0,0,36,16,84,0,0,0,0,0,0,36,16,84,0,0,0,0,0,0,36,16,84,0,0,0,0,0,0,63,255,214,0,0,0,0,0,0,63,255,214,0,0,0,0,0,0,127,255,214,0,0,0
1029 REM **TRAIN ENGINE**
1030 DATA 0,0,0,12,3,128,7,131,192,0,0,0,12,195,128,3,1,128,0,0,0,31,255,128,0,0,0,0,0,0,31,255,240,0,0,0,0,0,0,31,255,240,0,0,0,0,3,240,31,255,240,0,0,0,0,2,128,15,255,240,0,0,0,12,2,128,31,255,240,0,0,0
1039 REM **TREE**
1040 DATA 3,15,0,127,255,252,0,60,0,3,255,192,63,255,188,0,60,0,15,255,240,55,247,248,0,60,0,15,255,248,31,63,248,0,60,0,126,255,126,31,60,240,0,60,0,127,255,254,24,60,48,0,60,0,255,255,252,0,60,0,0,60,0,127,223,252,0,60,0,0,60,0
1049 REM *CAR*
1050 DATA 0,0,0,0,127,0,12,0,24,0,0,0,0,132,128,0,0,0,0,0,0,1,4,64,0,0,0,0,0,0,2,4,32,0,0,0,0,0,0,127,255,255,0,0,0,0,0,0,63,255,255,0,0,0,0,0,0,127,255,254,0,0,0,0,0,0,30,0,60,0,0,0
1059 REM **TELEVISION SET**
1060 DATA 0,0,0,48,0,104,48,0,104,0,0,0,48,0,120,63,255,248,0,0,0,48,0,104,63,255,248,0,0,0,48,0,120,0,0,0,0,0,0,48,0,104,0,0,0,0,0,0,48,0,120,0,0,0,63,255,248,48,0,104,0,0,0,63,255,248,48,0,120,0,0,0
1079 REM **FLYING GOOSE**
1080 DATA 0,0,0,0,120,0,1,0,0,0,0,0,0,124,0,3,0,0,0,0,0,0,124,240,0,0,0,0,0,0,96,125,216,0,0,0,1,224,0,127,255,252,0,0,0,0,248,0,127,255,255,0,0,0,0,248,0,15,255,128,0,0,0,0,248,0,7,255,0,0,0,0
1089 REM **TANK**
1090 DATA 0,0,0,64,3,0,51,51,48,0,0,0,64,255,0,63,255,240,0,0,0,65,255,128,12,204,192,0,0,0,67,0,255,0,0,0,64,0,0,79,255,192,0,0,0,64,0,0,255,255,240,0,0,0,64,0,0,255,255,248,0,0,0,64,0,0,127,255,252,0,0,0
1099 REM **CLOCK**
2000 DATA 0,0,0,9,64,144,8,66,16,0,0,0,8,32,16,8,24,16,0,0,0,8,16,16,8,0,16,15,255,240,10,15,80,15,255,240,8,0,16,8,0,16,0,0,0,8,24,16,8,0,16,0,0,0,8,66,16,9,0,144,0,0,0,8,0,16,8,0,16,0,0,0
2009 REM **APPLE**
2010 DATA 0,0,0,1,255,128,3,255,192,0,0,0,3,255,192,1,255,128,0,192,0,3,159,192,0,255,0,0,32,0,7,255,224,0,60,0,0,16,0,7,255,224,0,0,0,0,16,0,7,255,224,0,0,0,0,60,0,7,255,224,0,0,0,0,255,0,3,255,192,0,0,0
2019 REM **SCREEN PRINT AT X,Y**
2020 DATA 2,2,2,2,2,7,7,7,7,7,12,12,12,12,12,17,17,17,17,17
2030 DATA 3,8,13,18,23,3,8,13,18,23,3,8,13,18,23,3,8,13,18,23
2039 REM **ATTRIBUTE PRINT AT**
2040 DATA 0,5,10,15,20,160,165,170,175,180,320,325,330,335,340,480,485,490,495,500
2050 DATA 0,1,2,32,33,34,64,65,66
8999 STOP
9000 CLEAR : SAVE "PAIRS" LINE 1
9010 PRINT AT 10,6;"REWIND TO VERIFY";AT 12,8,"PRESS PLAY"
9020 VERIFY "PAIRS"
9021 CLS
9022 PRINT AT 10,8;" VERIFY O.K."
9030 FOR X=1 TO 5: BEEP .2,6: BEEP .5,4: NEXT X
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

