Zebra Light Pen is a two-part ZX81/TS1000 program package consisting of a light pen driver and a Concentration-style card-matching game. The first listing (the game) uses a 6×7 grid stored in arrays P and C, assigning pairs of characters from the ZX81 character set (CHR$ values starting at 166) to random board positions, then using a light pen scanning routine to detect which cell the player selects. The second listing is a standalone test/demonstration routine that also POKEs 17 bytes of Z80 machine code starting at address 16514; this machine code reads the EAR port (IN A,(254)), tests bit 3, and returns a non-zero value if the light pen is detected, which BASIC then calls via USR 16514. Sound effects are produced by POKEing values to address 9000, which is also used as the BLIP variable. The game awards 10 points per match and deducts 5 per mismatch, running until all 21 pairs are found.
Program Analysis
Overview and Structure
The package comprises two separate BASIC programs. The first is the Concentration game (LPEN), and the second is a light pen test routine (LPTEST). Both share the same Z80 machine code driver POKEd into memory starting at address 16514. The game program embeds the machine code implicitly inside its REM line 1, while the test program re-POKEs it explicitly at lines 80–96.
Machine Code Light Pen Driver
Lines 80–96 of the test program POKE 17 bytes of Z80 machine code into addresses 16514–16530. These bytes implement the light pen polling routine called via USR PEN (where PEN=16514). The disassembly is approximately:
XOR A— clear accumulatorLD C,A— zero C registerLD B,A— zero B register (BC = return value candidate)LD A,(0FEh)/IN A,(254)— read EAR/MIC port- Test bit 3 of the result; if clear (light detected), jump forward
- Load a count and loop, returning 1 if light pen pulse found, 0 otherwise
RET— return to BASIC
The routine reads port 254 (the keyboard/EAR port), isolating bit 3 which corresponds to the EAR input — the standard method for interfacing a light pen on this hardware. USR PEN returns a non-zero value when the pen is detected over a bright screen area.
Game Board Initialization
The board is a 6×7 grid (42 cells) managed by two arrays: C(6,7) stores character codes for each cell’s hidden value, and P(6,7) tracks whether a cell has been matched (1) or not (0). Fourteen pairs are placed (covering 28 of 42 cells), with character codes starting at 167 (Y+166 for Y=1..14). Pairs are placed by random selection with collision checking at line 2093.
An unusual loop controls how many times each pair value is placed:
FOR Z=1 TO 2+(Y/2=INT(Y/2))*2
This iterates 2 times for odd Y and 4 times for even Y, meaning even-numbered card types get placed four times (two pairs) while odd ones get two. This gives 7×2 + 7×4 = 42 placements — exactly filling the board. However, the win condition checks MATCHED<21, implying 21 pairs, which is inconsistent with only 14 distinct card values across 42 cells.
Light Pen Scanning Routine
The scan is a two-phase process. First, a row scan (lines 2130–2200) iterates Y from 1 to 6, printing a solid bar (O$, 29 block characters) across each row, sampling USR PEN before and after, then restoring the row. A hit is detected by Q AND NOT R — the pen fired while the bar was displayed but not after it was removed. The column scan (lines 2300–2370) then similarly sweeps across the 7 columns of the identified row, printing a 2-character block at each column position.
The variables DELAY and DELAY1 are set to line numbers 3010 and 3030 respectively and used as GOSUB DELAY and GOSUB DELAY1. Line 3010 contains only a REM and a RETURN, making it a near-zero delay. Line 3030 runs a FOR J=1 TO D: NEXT J loop for a variable delay. This is a clever use of numeric variables as GOSUB targets.
Sound Effects
Audio feedback is generated by POKEing values to address 9000, stored in variable BLIP. This directly manipulates the cassette/speaker output bit. Three distinct sound patterns are used:
- Match sound (subroutine 3100): ascending POKE sequence 1..16
- Select beep (subroutine 3060): brief POKE 7, short loop, POKE 0
- Mismatch buzz (subroutine 3300): POKE 15, short delay, POKE 0
- Setup chime (subroutine 3200): POKE 3, loop, then random POKE
Subroutine 3250 (lines 3250–3280) implements a toggling sound effect using variable W as a flag, but this routine is never called from the main game flow — it appears to be an unused utility.
Screen Layout
The board occupies even rows 2–14 (for the 6 card rows, addressed as 2*Y+1). Each column occupies 4 character positions (4*ZC-3). The string O$ (29 solid block characters) is used both as a “curtain” during scanning and to blank status lines. X$ (built as 7 repetitions of " ██") forms the hidden-card display pattern.
Test Routine Structure
The second program provides a standalone light pen verification tool. It displays a fixed trivia question about New York state capitals and scans four answer rows. The scan subroutine at line 300 flashes each cell on and off twice, accumulating a score in N: +10 if pen detected when cell is blank, +1 if detected when cell is lit. A score of exactly 20 (N=20) indicates a confident pen detection at that row. Only answer W=1 (“ALBANY”) triggers the “CORRECT” response.
Bugs and Anomalies
- Line 2590 prints “CONGRATIONS” — a typo for “CONGRATULATIONS.”
- Line 2120 prints “MAKE Y0UR FIRST CHOICE” with a zero instead of the letter O, but line 2490 corrects this with “MAKE YOUR FIRST CHOICE.”
- The win condition
MATCHED<21at line 2600 requires 21 matches, but only 14 distinct values are placed (yielding at most 14 or 7 pairs depending on interpretation) — the game may never reach the win state as written. - Subroutines at lines 3250–3280 are defined but never called from any reachable code path.
- Line 2341 contains
POKE 9000,0using a literal address rather than theBLIPvariable, breaking the abstraction used elsewhere. - The score variable
SCis initialized at line 2024 butMATCHEDis initialized at line 105 in a different section; aRUN 2000or mid-program restart would not resetMATCHED.
Key Variables Summary
| Variable | Purpose |
|---|---|
PEN | Address of machine code USR routine (16514) |
DELAY | Line number used as GOSUB target for near-zero delay (3010) |
DELAY1 | Line number used as GOSUB target for variable delay (3030) |
BLIP | Address for speaker POKE sound output (9000) |
C(6,7) | Board cell character codes |
P(6,7) | Board cell matched flags |
ZC, ZD | Column and row of pen-selected cell |
XC, XD | Column and row of first chosen cell |
SC | Current score |
MATCHED | Count of matched pairs found |
Content
Source Code
1 REM [J]###<= RETURN▛ABS [:]RND:▘+TAB ▚RNDTAN 21
88 CLS
89 PRINT "[L][I][G][H][T]█[C][O][N][C][E][N][T][R][A][T][I][O][N]"
90 PRINT "[C][O][P][Y][R][I][G][H][T]█[1][9][8][3]"
91 PRINT "[Z][E][B][R][A]█[S][Y][S][T][E][M][S][,][I][N][C][.]"
92 REM [A][L][L]█[R][I][G][H][T][S]█[R][E][S][E][R][V][E][D]
100 LET PEN=16514
101 LET DELAY=3010
102 DIM P(6,7)
103 LET O$="█████████████████████████████"
104 DIM C(6,7)
105 LET MATCHED=0
106 LET DELAY1=3030
107 LET BLIP=9000
2000 REM [C][O][N][C][E][N][T][R][A][T][I][O][N]
2024 LET SC=100
2030 LET W$=" ██"
2031 LET X$=""
2040 FOR Y=1 TO 7
2042 LET X$=X$+W$
2044 NEXT Y
2069 SLOW
2070 PRINT "I AM SETTING UP THE BOARD"
2071 PRINT "PLEASE WAIT 20 SECONDS"
2075 GOSUB 3100
2076 FAST
2079 LET H=42
2080 FOR Y=1 TO 14
2082 FOR Z=1 TO 2+(Y/2=INT (Y/2))*2
2090 LET S=INT (RND*6)+1
2091 LET T=INT (RND*7)+1
2093 IF C(S,T)<>0 THEN GOTO 2090
2100 LET C(S,T)=Y+166
2101 GOSUB 3200
2102 NEXT Z
2104 NEXT Y
2105 GOSUB 3100
2109 SLOW
2110 CLS
2111 GOSUB 3000
2115 PRINT AT 1,1;"THE SCORE IS ";SC;"██";
2120 PRINT AT 16,4;"MAKE Y0UR FIRST CHOICE";
2122 GOTO 2490
2125 REM [R][O][W]█[S][C][A][N]
2130 FOR Y=1 TO 6
2131 LET Q=USR PEN
2135 PRINT AT 2*Y+1,0;O$;
2136 GOSUB DELAY
2137 LET R=USR PEN
2160 PRINT AT 2*Y+1,1;X$;
2165 IF Q AND NOT R THEN GOTO 2300
2180 NEXT Y
2200 GOTO 2125
2300 REM [C][O][L][U][M][N]█[S][C][A][N]
2305 LET X=Y
2310 FOR Y=1 TO 7
2315 LET Q=USR PEN
2320 PRINT AT 2*X+1,Y*4-3;"██";
2322 GOSUB DELAY
2330 LET R=USR PEN
2340 PRINT AT 2*X+1,Y*4-3;" ";
2341 POKE 9000,0
2350 IF Q AND NOT R THEN GOTO 2375
2360 NEXT Y
2370 GOTO 2125
2375 LET ZC=Y
2376 LET ZD=X
2400 RETURN
2401 REM [E][N][D]█[P][E][N]█[I][N][P][U][T]█[R][O][U][T][I][N][E]
2490 PRINT AT 16,4;"MAKE YOUR FIRST CHOICE██";
2495 GOSUB 2125
2496 LET XD=ZD
2497 LET XC=ZC
2498 GOSUB 3060
2500 IF P(ZD,ZC)=0 THEN GOTO 2530
2510 PRINT AT 15,4;"TRY AGAIN";
2520 GOTO 2495
2530 PRINT AT 2*ZD,4*ZC-3;CHR$ C(ZD,ZC);
2540 PRINT AT 15,0;O$;
2550 PRINT AT 16,4;"MAKE YOUR SECOND CHOICE";
2555 GOSUB 2125
2559 REM [S][A][M][E]█[O][R]█[U][S][E][D]
2560 IF P(ZD,ZC)=0 AND NOT (ZC=XC AND ZD=XD) THEN GOTO 2577
2570 PRINT AT 15,4;"TRY AGAIN ";
2571 GOSUB 3060
2575 GOTO 2555
2577 REM [M][A][T][C][H]
2578 PRINT AT 15,0;O$;
2579 PRINT AT 2*ZD,4*ZC-3;CHR$ C(ZD,ZC);
2580 IF C(ZD,ZC)<>C(XD,XC) THEN GOTO 2650
2582 LET MATCHED=MATCHED+1
2590 PRINT AT 16,4;"MATCH FOUND. CONGRATIONS█";
2591 GOSUB 3100
2592 LET P(ZD,ZC)=1
2593 LET P(XD,XC)=1
2594 LET SC=SC+10
2595 PRINT AT 1,1;"THE SCORE IS ";SC;"██";
2596 LET D=20
2597 GOSUB DELAY1
2600 IF MATCHED<21 THEN GOTO 2490
2610 PRINT AT 15,4;"GAME OVER";
2620 PRINT AT 16,4;"PRESS ENTER TO BEGIN AGAIN";
2630 INPUT K$
2640 RUN
2650 REM [M][I][S][M][A][T][C][H]
2652 PRINT AT 15,0;O$;
2653 GOSUB 3300
2655 PRINT AT 16,4;"NO MATCH FOUND█████████";
2657 LET SC=SC-5
2658 PRINT AT 1,1;"THE SCORE IS ";SC;"██";
2660 LET D=20
2665 GOSUB DELAY1
2670 PRINT AT 2*ZD,4*ZC-3;"█";
2675 PRINT AT 2*XD,4*XC-3;"█";
2680 GOTO 2490
2998 REM [S][U][B][R][O][U][T][I][N][E][S]
2999 REM [B][L][A][C][K]█[S][C][R][E][E][N]
3000 FOR T=1 TO 20
3002 PRINT O$
3004 NEXT T
3005 PRINT "██[L][I][G][H][T][W][A][R][E]█[B][Y]█[Z][E][B][R][A]█[S][Y][S][T][E][M][S][.]";
3006 RETURN
3010 REM [D][E][L][A][Y]
3020 RETURN
3030 REM [D][E][L][A][Y][1]
3031 FOR J=1 TO D
3032 NEXT J
3033 RETURN
3050 REM [S][O][U][N][D]█[R][O][U][T][I][N][E][S]
3060 POKE BLIP,7
3065 FOR W=1 TO 4
3066 NEXT W
3070 POKE BLIP,0
3080 RETURN
3100 FOR W=1 TO 16
3110 POKE BLIP,W
3120 NEXT W
3130 RETURN
3200 POKE BLIP,3
3210 FOR W=1 TO 10
3211 NEXT W
3220 POKE BLIP,INT (16*RND)
3230 RETURN
3250 IF W THEN GOTO 3270
3255 LET W=1
3260 POKE BLIP,7
3265 RETURN
3270 LET W=0
3275 POKE BLIP,6
3280 RETURN
3300 POKE BLIP,15
3310 LET D=5
3320 GOSUB DELAY1
3325 POKE BLIP,0
3330 RETURN
9000 SAVE "LPEN[1]"
9010 RUN
1 REM [J]###<= RETURN▛ABS [:]RND:▘+TAB ▚RNDTAN
79 REM [M][A][C][H][I][N][E]█[C][O][D][E]█[P][O][K][E][D]█[L][I][N][E][1]
80 POKE 16514,175
81 POKE 16515,79
82 POKE 16516,71
83 POKE 16517,87
84 POKE 16518,219
85 POKE 16519,254
86 POKE 16520,7
87 POKE 16521,210
88 POKE 16522,142
89 POKE 16523,64
90 POKE 16524,14
91 POKE 16525,1
92 POKE 16526,21
93 POKE 16527,194
94 POKE 16528,134
95 POKE 16529,64
96 POKE 16530,201
100 LET A=16514
101 LET Y=5
200 LET X=10
205 CLS
206 PRINT "LIGHT PEN TEST ROUTINE"
210 GOSUB 300
220 PRINT AT 6,5;"█ N=";N;" ";
230 GOTO 210
300 REM [S][C][A][N]█[B][L][O][C][K]█[A][T]█[X][,][Y]
310 LET N=0
330 FOR I=1 TO 2
340 PRINT AT X,Y;" ";
350 FOR F=1 TO 2
360 NEXT F
380 IF USR A THEN LET N=N+10
390 PRINT AT X,Y;"█";
400 FOR F=1 TO 2
420 NEXT F
440 IF USR A THEN LET N=N+1
470 NEXT I
475 RETURN
480 REM [P][R][I][N][T]█[Q][U][E][S][T][I][O][N]
490 PRINT AT 3,0;"WHAT CITY IS THE CAPITOL OF","NEW YORK STATE?";
1000 PRINT AT 10,7;"ALBANY";
1010 PRINT AT 12,7;"NEW YORK";
1020 PRINT AT 14,7;"MANHATTAN";
1030 PRINT AT 16,7;"ROCHESTER";
1099 REM [F][I][N][D]█[L][I][G][H]█[P][E][N]
1100 FOR W=1 TO 4
1110 LET X=2*W+8
1120 GOSUB 300
1124 REM [P][R][I][N][T]█[R][E][S][U][L][T]
1130 IF N=20 THEN GOSUB 1500
1140 IF N<>20 THEN PRINT AT 6,5;" ";
1199 NEXT W
1300 GOTO 1100
1500 IF W=1 THEN GOTO 1550
1505 IF W<>1 THEN PRINT AT 6,5;"WRONG ";
1510 RETURN
1550 PRINT AT 6,5;"CORRECT";
1560 RETURN
2000 SAVE "LPTES[T]"
2010 RUN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.


