Pairs

This file is part of and SINCUS Exchange Tape 103 - Potpourri. Download the collection to get this file.
Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Game

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.

  1. Lines 5–40: Variable and array declarations, initial constants.
  2. Lines 50–230: Screen setup, UDG loading, grid drawing, and countdown display.
  3. Lines 240–410: Main input loop — prompt, validate, match-check, reveal/hide.
  4. Lines 420–470: End-of-game routine, lowest-score tracking, restart.
  5. Lines 520–570: Subroutines — cell reveal (520), cell fill (530), row/column decode (540).
  6. Lines 600–800: Initialization — data load, randomization of picture/position assignments.
  7. Lines 1000–2050: DATA blocks for 11 sprite bitmaps, screen AT coordinates, and attribute table offsets.
  8. 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 Y busy-wait delay loop for each second.
  • POKE 23609,110 at line 15 sets the keyboard repeat delay (REPDEL system variable), likely to prevent accidental key repetition during INPUT.
  • The PAPER 6: INK 6 at 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)ContentSize
1000Ship sprite (UDG bytes)72 bytes
1010Helicopter sprite72 bytes
1020Bus sprite72 bytes
1030Train engine sprite72 bytes
1040Tree sprite72 bytes
1050Car sprite72 bytes
1060Television set sprite72 bytes
1080Flying goose sprite72 bytes
1090Tank sprite72 bytes
2000Clock sprite72 bytes
2010Apple sprite72 bytes
2020Screen row AT values for 20 positions20 values
2030Screen column AT values for 20 positions20 values
2040Attribute row offsets T(20)20 values
2050Attribute 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 by STOP at 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

Related Products

Related Articles

Related Content

Image Gallery

Pairs

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.

People

No people associated with this content.

Scroll to Top