Memory Mat is a concentration-style card-matching game in which the player must find all 26 pairs among 52 face-down playing cards laid out in a 10×6 grid. The program defines four custom UDG characters for the card suit symbols (Hearts, Clubs, Diamonds, Spades) using POKE USR, then shuffles a 52-element array using a Fisher-Yates-style random swap loop. Cards are displayed as inverse-video numbered tiles; selecting a pair reveals the card value and suit, with red ink used for Hearts and Diamonds and black for Clubs and Spades. The game tracks both the number of choices made and pairs found, displaying them on screen throughout play, and uses BEEP tones to signal correct matches, mismatches, and game completion.
Program Analysis
Program Structure
The program is organized into a main flow (lines 10–380), two display subroutines (lines 500–540 and 800–820), a grid-position calculator (lines 600–630), and standard housekeeping at lines 9997–9998. Initialization occupies lines 10–90, the screen layout is drawn at lines 120–150, and the game loop runs from lines 200–380.
| Line range | Purpose |
|---|---|
| 10 | Set screen attributes |
| 20–30 | Define UDGs for H, C, D, S suit symbols |
| 40–90 | Data setup, shuffle, and suit/value extraction |
| 120–150 | Draw card-back grid using block graphics |
| 160–380 | Main game loop: input, validation, reveal, scoring |
| 500–540 | Subroutine: reveal a card face |
| 600–630 | Subroutine: calculate screen row/column from card number |
| 800–820 | Subroutine: redraw a card face-down |
UDG Suit Symbols
Lines 20–30 use a READ/POKE USR loop to load 8-byte bitmap data for four UDG characters. The suit letter ("H", "C", "D", "S") is read as a string, and USR x$ gives the address of its UDG bitmap in RAM. Line 60 converts these letters to their display character codes by adding 79 (CODE s$(i)+79), mapping the letters A–S range to UDG character codes 144–162, and stores them in the s() array for later use in card display.
Deck Shuffle
Lines 70–80 implement a shuffle: array c() is first filled sequentially 1–52, then each element is swapped with a randomly chosen element using INT(52*RND)+1. This is a variant of the Knuth (Fisher-Yates) shuffle, though it swaps with any position 1–52 rather than the canonical 1–i range; the result is still a reasonable randomization for gameplay purposes.
Suit and Value Extraction
Line 90 unpacks each shuffled card number into a suit index t(i) and a value index v(i) using integer division. t(i) = INT((c(i)-1)/13)+1 gives suits 1–4, and v(i) = c(i) - 13*(t(i)-1) gives values 1–13. The value string v$="A23456789TJQK" maps index positions to standard card face characters, with T representing the Ten.
Grid Layout and Position Calculation
The 52 cards are arranged in a 10-column by 6-row (with a partial last row) grid. The subroutine at lines 600–630 computes screen coordinates from a card number n: the tens digit determines the row (l1 = 1 + 3*x10) and the units digit determines the column (c1 = 3*x1 - 2). A special case at line 605 handles multiples of 10 by adjusting x10 and x1. Each card cell is 3 characters wide and 3 rows tall, giving a tightly packed but readable layout.
Card Face Display
Subroutine 500 reveals a card at position (l1, c1). Ink color is set conditionally: INK 2-(sn=2*INT(sn/2))*2 evaluates to INK 2 (red) when sn is even (suits 2 and 4, i.e. Clubs and Spades in this encoding) and INK 0 (black) otherwise, effectively coloring Hearts and Diamonds red. The value character is printed from v$(vn) and the suit symbol from CHR$ s(sn). A PLOT/DRAW sequence at line 530 draws a border rectangle around the revealed card cell in pixel coordinates.
Input Validation
The game validates each entered number with a chain of checks: non-integer or out-of-range values (lines 210, 250), already-paired cards where v(p)=0 (lines 220, 260), and identical pair entries (line 270). Each invalid case re-issues an appropriate INPUT prompt in a tight local loop rather than branching to a central error handler.
Card Concealment
Subroutine 800 redraws a card face-down by printing the card number in INVERSE 1 mode. Line 800 includes a conditional extra print of the block graphic \:: (a solid block) for single-digit numbers, ensuring the display is visually consistent regardless of whether the number occupies one or two character positions. Line 810 fills the second row of the card cell with a solid block graphic.
Matching Logic and Scoring
After both cards are revealed, line 300 compares v(p1) and v(p2). A match sets both to 0 (marking them as paired), increments pr, and plays a two-note ascending beep. A mismatch plays a low beep, pauses 150 frames (~6 seconds at 25 fps), then calls subroutine 800 twice to re-conceal both cards. The game ends when pr=26 (all 26 pairs found), reporting the total number of choices.
Notable Techniques
- Using
CODE s$(i)+79to convert suit letter codes to UDG display codes avoids a lookup table. - The PLOT/DRAW card border in subroutine 500 uses the formula
c1*8-1and178-8*l1to convert PRINT AT coordinates to pixel coordinates. - Setting
v(i)=0to mark matched pairs reuses the value array as both a game-state flag and a value store, saving a separate matched/unmatched array. - The screen background is drawn using repeated
\::\::block graphic strings (solid blue squares on PAPER 4) to simulate face-down card backs before individual inverse numbers are overlaid.
Potential Issues
- The color assignment logic in line 500 (
INK 2-(sn=2*INT(sn/2))*2) colors even-numbered suits red and odd-numbered suits black. Whether this correctly assigns red to Hearts and Diamonds depends on the suit ordering established by the string"HCDS"(H=1 odd=black, C=2 even=red, D=3 odd=black, S=4 even=red), which reverses the conventional red/black mapping. Hearts and Spades would appear black; Clubs and Diamonds red. - The shuffle at line 80 iterates
ifrom 1 to 52 but swaps with any random index 1–52, meaning positionican be revisited and un-shuffled in a later pass. This is a known slight bias but does not affect playability. - Lines 120–130 use
PAPER 4for the blue card-back grid, but line 140 switches toPAPER 7only for the border drawing and does not restore PAPER 4, so subsequent attribute changes depend on the current state at that point.
Content
Source Code
10 PAPER 7: INK 0: FLASH 0: BORDER 7
20 FOR i=1 TO 4: READ x$: FOR j=0 TO 7: READ x: POKE USR x$+j,x: NEXT j: NEXT i
30 DATA "H",34,119,127,127,62,62,28,8,"C",28,28,8,107,127,107,8,8,"D",8,28,62,127,62,28,8,0,"S",8,28,62,127,127,127,107,8
40 LET s$="HCDS": LET v$="A23456789TJQK"
50 DIM c(52): DIM t(52): DIM v(52): DIM s(4)
60 FOR i=1 TO 4: LET s(i)=CODE s$(i)+79: NEXT i
70 FOR i=1 TO 52: LET c(i)=i: NEXT i
80 FOR i=1 TO 52: LET r=INT (52*RND)+1: LET z=c(i): LET c(i)=c(r): LET c(r)=z: NEXT i
90 FOR i=1 TO 52: LET t(i)=INT ((c(i)-1)/13)+1: LET v(i)=c(i)-13*(t(i)-1): NEXT i
120 PAPER 4: PRINT TAB 31: FOR i=1 TO 5: FOR j=1 TO 2: PRINT " \::\:: \::\:: \::\:: \::\:: \::\:: \::\:: \::\:: \::\:: \::\:: \::\:: ": NEXT j: PRINT TAB 31: NEXT i
130 FOR j=1 TO 2: PRINT " \::\:: \::\:: ": NEXT j: PRINT TAB 7
140 PAPER 7: PLOT 0,175: DRAW 0,-151: DRAW 55,0: DRAW 0,24: DRAW 192,0: DRAW 0,127: DRAW -247,0
150 FOR n=1 TO 52: GO SUB 600: PRINT AT l1,c1; INVERSE 1;n: NEXT n
160 LET ch=0: LET pr=0: PRINT AT 16,8;"CHOICES: 0 PAIRS: 0"
200 PRINT AT 18,8;" ": INPUT "Enter pair: (1)=";p1
210 IF p1<>INT p1 OR p1>52 OR p1<1 THEN INPUT "Invalid - reenter (1)=";p1: GO TO 210
220 IF v(p1)=0 THEN INPUT "Already paired - reenter (1)=";p1: GO TO 210
230 LET n=p1: GO SUB 600: GO SUB 500
240 INPUT " (2)=";p2
250 IF p2<>INT p2 OR p2>52 OR p2<1 THEN INPUT "Invalid - reenter (2)=";p2: GO TO 250
260 IF v(p2)=0 THEN INPUT "Already paired - reenter (2)=";p2: GO TO 250
270 IF p1=p2 THEN INPUT "Pair must be separate - reenter (2)";p2: GO TO 250
280 LET n=p2: GO SUB 600: GO SUB 500
290 LET ch=ch+1: PRINT AT 16,17;ch
300 IF v(p1)<>v(p2) THEN GO TO 340
310 PRINT AT 18,8; INK 7;"Correct choice!": LET v(p1)=0: LET v(p2)=0: LET pr=pr+1: PRINT AT 16,28;pr: BEEP .5,25: BEEP .5,15
320 IF pr=26 THEN PRINT AT 20,0;"You have finished after ";ch;" turns": BEEP .5,20: BEEP .5,25: BEEP .5,20: GO TO 360
330 GO TO 350
340 PRINT AT 18,8;"Do not match ": BEEP 1,-15: PAUSE 150: GO SUB 800: LET n=p1: GO SUB 600: GO SUB 800
350 INPUT "Press ENTER for next go"; LINE z$: GO TO 200
360 PAUSE 150: INPUT "Press 1 to repeat or 0 to stop"; LINE z$
370 IF z$="1" THEN RUN
380 STOP
500 PAPER 7: LET vn=v(n): LET sn=t(n): INK 2-(sn=2*INT (sn/2))*2
510 PRINT AT l1,c1;v$(vn);" ";AT l1+1,c1;" ";CHR$ s(sn)
520 INK 0
530 PLOT c1*8-1,178-8*l1: DRAW 17,0: DRAW 0,-17: DRAW -17,0: DRAW 0,17
540 BEEP 1,5: RETURN
600 LET x10=INT (n/10): LET x1=n-10*x10
605 IF x1=0 THEN LET x10=x10-1: LET x1=10
610 LET l1=1+3*x10
620 LET c1=3*x1-2
630 RETURN
800 PRINT AT l1,c1; INVERSE 1;n: IF n<10 THEN PRINT "\::"
810 PRINT AT l1+1,c1;"\::\::"
820 RETURN
9997 STOP
9998 SAVE "MEMORY MAT" LINE 1
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
