--- title: "Zebra Light Pen" id: 56755 type: "computer_media" slug: "zebra-light-pen" url: "http://localhost/computer_media/zebra-light-pen/" markdown_url: "http://localhost/computer_media/zebra-light-pen.md" published_at: "2024-09-22T23:31:59+00:00" modified_at: "2026-04-04T20:17:48+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/LPCon16K.png" excerpt: "A light pen driver with Z80 machine code paired with a full Concentration card-matching game — discover how hardware input, USR calls, and grid logic combine." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Zebra Systems" slug: "zebra-systems" taxonomy: "post_tag" url: "http://localhost/tag/zebra-systems/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Art" slug: "art" taxonomy: "genre" url: "http://localhost/type/art/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Zebra%20Light%20Pen%20%28198x%29%28Zebra%20Systems%29%28TS1000%29%28US%29%28Cassette%29.zip" mediadate: "198x" producer_company: - id: 11469 title: "Zebra Systems, Inc." type: "company" url: "http://localhost/company/zebra-systems/" images: - url: "http://localhost/wp-content/uploads/2024/09/LPCon16K.png" - url: "http://localhost/wp-content/uploads/2024/09/LPTest2K.png" - url: "http://localhost/wp-content/uploads/2024/09/ZLP2.jpg" related_products: - id: 13098 title: "Light Pen" type: "product" url: "http://localhost/product/light-pen/" media_type_tags: "Art" --- 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: 1. `XOR A` — clear accumulator 2. `LD C,A` — zero C register 3. `LD B,A` — zero B register (BC = return value candidate) 4. `LD A,(0FEh)` / `IN A,(254)` — read EAR/MIC port 5. Test bit 3 of the result; if clear (light detected), jump forward 6. Load a count and loop, returning 1 if light pen pulse found, 0 otherwise 7. `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<21` at 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,0` using a literal address rather than the `BLIP` variable, breaking the abstraction used elsewhere. - The score variable `SC` is initialized at line 2024 but `MATCHED` is initialized at line 105 in a different section; a `RUN 2000` or mid-program restart would not reset `MATCHED`. ### 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 | ## 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 ```