Flexi-Board is a bulletin board system (BBS) program written for Sinclair-family computers with modem connectivity, allowing remote callers to read and write messages, chat with the sysop, and manage a message base. The program makes heavy use of machine code routines loaded separately as “code” at address 63360, called via USR to handle serial communications, with key parameters like the receive-next pointer stored at address 63549. Initialization in lines 9000–9006 sets up symbolic constants using mathematical expressions (such as NOT PI for 0, SGN PI for 1, INT PI for 3) to avoid storing literal numbers, and uses DELETE to strip the setup lines from memory after running. The message base is stored as CODE at address 32768, saved and reloaded with SAVE/LOAD CODE, and the program supports options like read-only, read-write, and write-only access modes along with scrolling control. A quirky data entry in line 9001 uses “September 31, 1986” as the default date, which is a non-existent calendar date.
Program Analysis
Program Structure
Flexi-Board is organized into functional blocks accessed via computed GO TO and GO SUB targets, most of which are derived from CODE "x" where x is a character whose ASCII value corresponds to a line number. This makes the control flow extremely compact but opaque. The main sections are:
- Lines 1–9: Core BBS loop — login, input dispatch, serial send/receive
- Lines 10–13: Initialization / login sequence
- Lines 15–80: Menu command handlers (Enter bulletin, Write message, Chat, Load, Save, Print, View, Create, Options)
- Lines 84–125: Utility subroutines (send, beep, menu display, numeric validation, message numbering)
- Lines 166–175: LOAD/SAVE CODE for the message base
- Lines 198–235: Remote user (caller-side) menu and message handling
- Lines 1000–3003: DATA blocks for welcome screen, main menu, and message-type menu
- Lines 9000–9007: One-time initialization, then self-deletes via
DELETE - Line 9999: SAVE statements for both the BASIC program and the machine code block
Initialization and Self-Modifying Setup
Lines 9000–9006 perform all startup work. Constants are created using mathematical expressions rather than numeric literals — a well-known memory-saving idiom on Sinclair machines:
| Variable | Expression | Value |
|---|---|---|
o | NOT PI | 0 |
i | SGN PI | 1 |
b | i+i | 2 |
t | INT PI | 3 |
f | b+b | 4 |
v | b+t | 5 |
s | v+b | 7 |
These constants serve double duty: they reduce token storage and make many computed line targets self-documenting once decoded. Line 9006 uses DELETE to remove the initialization lines (9008 onward, then 9000–9006 itself) from memory after execution, leaving only the operational program. Line 9007 then jumps to line b (line 2) to start the BBS.
Machine Code Integration
A separate machine code block is loaded at address 63360 (length 2176 bytes, reaching 65535) and called via USR bbs where bbs = 63360. The machine code handles serial I/O — sending and receiving characters over the modem. Key memory locations used as communication channels between BASIC and the machine code are:
rn = 63549: A “register/next” pointer or mode byte POKEd before each USR call to control behaviorbf = 63280: Base of the receive buffer; characters are read back by PEEKing frombfonward in line 8bs = 32768: Start of the message base CODE block in RAMbl = bf - bs: Length of the message base block for LOAD/SAVE CODE
The BASIC-to-MC interface at lines 5–9 reads a count from a DATA statement, then iterates calling USR bbs to send strings (s$), checking the return value st for error/disconnect conditions. The receive loop in line 8 polls PEEK y against CODE r$ (carriage return, CHR$ 13) to accumulate a line of input into i$.
Computed GO TO / GO SUB Targets
Nearly all control flow uses CODE "x" to generate line numbers, exploiting the ASCII values of printable characters:
CODE "i"= 105 → subroutine: input with prompt (line 105)CODE "b"= 98 → BEEP subroutine (line 98)CODE "h"= 104 → parse h$ for message number range (line 104)CODE "s"= 115 → format record number as string (line 115)CODE "g"= 103 → RESTORE and seek to message record (line 103)CODE "m"= 109 → compose and send a message (line 109)CODE "T"= 84 → send string with trailing CR (line 84)CODE "j"= 106 → convert i$ to uppercase (line 106, via 107)CODE "^"= 94 → validate numeric string in i$ (line 94)CODE "z"= 122 → increment message counter h with wraparound (line 122)CODE ">="= 62 → maps to line 200 (remote user menu)CODE "<="= 60 → maps to line 60 (GO TO b, main loop)CODE "("= 40 → line 40 (options prompt)CODE "/"= 47 → line 47CODE "L"= 76 → line 76 (prompt: bulletin or message?)CODE "M"= 77 → line 77 (default start/end for scan)CODE "c"= 99 → line 99 (display menu, get choice)CODE "d"= 100 → line 100 (validate menu choice)CODE "f"= 102 → line 102 (prompt with POKE rn,f)CODE "t"= 116 → line 116 (pad string to 4 digits)
Message Base Management
Messages are stored as a CODE block at address 32768, loaded and saved with LOAD i$CODE bs,bl and SAVE i$CODE bs,bl (lines 166, 175). Message records are accessed by POKEing rn with a record number and calling USR bbs for sequential reads. The program maintains low (l) and high (h) message number bounds, with wraparound at 9999 (line 122). Message numbers are zero-padded to four digits by the subroutine at lines 115–125.
Access Control and Options
The option string o$ is a 7-character DIM array used as a status register:
o$(b TO t)= positions 2–3: access mode, one of “RW”, “RO”, or “WO”o$(f)= position 4: scroll flag, “Y” or “N”o$(s)= position 7: current message scan status (“S”, “Q”, ” “)o$(i)= position 1: currently loaded message base filename
These option bytes are also mirrored to the machine code area via POKE bs+x, CODE o$(x+i) in line 43 so the ML routines can read them directly.
Remote vs. Sysop Menus
The program maintains two separate menu flows. Lines 2–13 represent the sysop-side login and command loop, using the local keyboard (c$="HEDWFLOPSTUV*!" as valid command characters). Lines 200–235 represent the remote caller’s menu (dispatching on x$="MRWCTAG"), reached via GO TO CODE ">=" (line 200). The caller menu at line 200 uses the same serial I/O subroutines, building the full BBS interaction over the modem link.
Notable Techniques
VAL$ "m$"in line 42 evaluates the string variablem$at runtime — an indirect variable dereference idiom.RESTORE VAL "1e3"andRESTORE VAL "2e3"use scientific notation to jump to DATA lines 1000 and 2000 respectively, saving bytes over storing “1000”.- Line 220 polls
INKEY$inside aFORloop as a timeout for sysop availability, jumping to line 30 (Chat) if a keypress is detected. - String slicing
m$( TO f+v)(i.e.,m$(1 TO 9)= ” Message “) produces the shorter labell$from a longer constant, avoiding a separate string assignment. - The default date “September 31, 1986” (line 9001) is an impossible calendar date — September has only 30 days.
Bugs and Anomalies
- September 31, 1986: The default value of
d$is set to a non-existent date; this is a sysop-configurable field (line 55) so it causes no runtime error, but it would display incorrectly until changed. - Line 3 computed target:
GO TO x*v+vusesv=5, so for a matching character at positionxinc$, the target line is5x+5. This mapsc$="HEDWFLOPSTUV*!"positions 1–14 to lines 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75 — a clean and intentional design. - Line 67
FOR x=x TO y: The loop variable and start value share the namex; this works becausexwas set by the precedingGO SUB CODE "M"call, but it is fragile if the flow changes.
Content
Source Code
1 REM Flexi-Board \* 1986 by Kurt A. Casby
2 BORDER i: PAPER i: CLS : PRINT "Today's Date: ";d$''TAB v;"!";q$;p$''"*";b$''"Options: ";o$(b);"/";o$(t),"Scroll=";o$(f)''"Load";m$''"Save";m$''"Print";m$''"View";m$''"Enter";m$;k$''"Write a";l$''" Force Chat Mode Hang up"
3 LET z$=a$: GO SUB CODE "i": FOR x=i TO LEN c$: IF i$=c$(x) THEN GO TO x*v+v
4 NEXT x: GO SUB CODE "b": GO TO t
5 POKE rn,t: READ s$: FOR y=i TO VAL s$: READ s$: LET st=USR bbs: IF st THEN RETURN
6 NEXT y: LET s$=t$: RANDOMIZE USR bbs: READ s$: POKE rn,f
7 LET st=USR bbs: LET y=bf: LET i$=""
8 IF PEEK y<>CODE r$ AND LEN i$<v*v THEN LET i$=i$+CHR$ PEEK y: LET y=y+i: GO TO f+f
9 RETURN
10 BORDER o: PAPER o: CLS : LET x=o: POKE rn,i: LET st=USR bbs: IF st THEN GO TO st
11 BORDER i: PAPER i: CLS : RESTORE VAL "1e3": GO SUB v: LET x=x+i: IF st THEN GO TO st
12 IF i$<>p$ THEN GO TO s+f-(x>=t)
13 LET s$=t$+"Name?": GO SUB s: LET n$=i$: GO TO CODE ">="
15 CLS : PRINT "Enter";m$;k$'': POKE rn,v+v: GO SUB s: GO SUB CODE "h": GO TO b
20 GO TO b
25 CLS : PRINT "Write";l$'': INPUT "To: "; LINE z$: IF NOT LEN z$ THEN GO TO b
26 INPUT "Re: "; LINE i$: LET n$=y$: POKE rn,t*f: GO SUB CODE "m": GO TO b
30 CLS : LET s$=t$+"Chat Mode!": GO SUB CODE "T": PRINT "With: ";n$'': POKE rn,CODE r$: GO SUB s: GO TO CODE "<="
35 CLS : PRINT " LOAD ";m$: GO SUB CODE "c": IF LEN i$ THEN GO SUB CODE "INKEY$"
36 GO TO b
40 LET z$="RW, RO, WO? ": GO SUB CODE "i": IF i$<>"RW" AND i$<>"RO" AND i$<>"WO" THEN GO SUB CODE "b": GO TO CODE "("
41 LET o$(b TO t)=i$
42 LET z$=VAL$ "m$"+"Scroll? (Y/N) ": GO SUB CODE "i": IF i$<>"Y" AND i$<>"N" THEN GO SUB CODE "b": GO TO CODE "*"
43 LET o$(f)=i$: FOR x=o TO t: POKE bs+x,CODE o$(x+i): NEXT x: LET a=i: GO TO b
45 POKE rn-i,CODE " ": GO SUB CODE "L": IF i$="B" THEN POKE rn,s+f: GO SUB s: GO TO CODE "/"
46 POKE rn,t*t: FOR x=l TO h: GO SUB CODE "s": NEXT x
47 POKE rn-i,CODE " ": GO TO b
50 GO SUB CODE "CODE ": GO TO b
55 INPUT "Date? "; LINE d$: GO TO b
60 GO TO b
65 GO SUB CODE "L": IF i$="B" THEN CLS : POKE rn,v+t: GO SUB s: PAUSE o: GO TO b
66 LET o$(s)=" ": LET z$=f$: GO SUB CODE "i": LET s$=i$: LET z$=g$+STR$ l+"-"+STR$ h+"?": GO SUB CODE "i": LET z$=s$: GO SUB CODE "M": IF NOT LEN i$ THEN GO TO b
67 CLS : FOR x=x TO y STEP z: POKE rn,s: GO SUB CODE "s": IF st<>i AND o$(s)<>"S" THEN LET z$=e$: GO SUB CODE "i": IF i$="Q" OR i$="S" THEN LET o$(s)=i$: IF i$="Q" THEN GO TO b
68 IF st<>i THEN IF i$="P" OR i$="D" THEN LET z$=i$: POKE rn-i,CODE " ": POKE rn,t*t-t*(i$="D"): GO SUB CODE "s": POKE rn-i,CODE " ": IF z$="D" THEN GO SUB CODE "h": PRINT l$;"Deleted!"''
69 NEXT x: LET z$="Finished!": GO SUB CODE "i": GO TO b
70 CLS : PRINT "Create";m$: GO SUB CODE "c": IF LEN i$ THEN LET o$(i)=i$: GO SUB CODE "g": POKE rn,b: GO SUB s: POKE bs+f,t: LET l=o: LET h=o: GO TO CODE "("
71 GO TO b
75 INPUT VAL$ "q$"; LINE p$: GO TO b
76 LET z$=k$+" or"+l$+"?": GO SUB CODE "i": RETURN
77 IF NOT LEN i$ THEN LET i$=STR$ (l*(z$<>"R")+h*(z$="R"))
78 GO SUB CODE "^": IF NOT LEN i$ THEN RETURN
79 IF x<l OR x>h THEN LET i$="": RETURN
80 LET y=h*(z$<>"R")+l*(z$="R"): LET z=i-b*(z$="R"): RETURN
84 POKE rn,t: RANDOMIZE USR bbs: RETURN
94 FOR x=i TO LEN i$: IF i$(x)<"0" OR i$(x)>"9" THEN LET i$="": RETURN
95 NEXT x: LET x=VAL i$: RETURN
98 BEEP VAL ".01",v*t: RETURN
99 RESTORE VAL "3e3": READ i$: LET y=VAL i$: FOR x=i TO y: READ i$: PRINT 'i$: LET s$=i$(i): NEXT x
100 LET z$=a$: GO SUB CODE "i": IF LEN i$ AND (i$<"A" OR i$>s$) THEN GO SUB CODE "b": GO TO CODE "d"
101 RETURN
102 POKE rn,f: GO SUB s: GO SUB CODE "j": RETURN
103 RESTORE VAL "3e3": FOR x=CODE "?" TO CODE i$: READ b$: NEXT x: RETURN
104 LET h$=i$: LET l=VAL h$(i TO f): LET h=VAL h$(v TO v+t): RETURN
105 INPUT VAL$ "z$"; LINE i$
106 FOR y=i TO LEN i$: IF i$(y)>"Z" THEN LET i$(y)=CHR$ (CODE i$(y)-CODE " ")
107 NEXT y: RETURN
109 GO SUB CODE "z": LET s$=s$+r$+d$+r$+"To: "+z$+r$: LET s$=s$+"From: "+n$+r$+"Re: "+i$+r$+r$: GO SUB s: IF st THEN RETURN
110 GO SUB CODE "h": LET a=i: RETURN
115 LET s$=STR$ x
116 IF LEN s$=f THEN GO SUB s: RETURN
117 LET s$="0"+s$: GO TO CODE "t"
122 LET y=h+i: IF y>=VAL "1e4" THEN LET y=i
123 LET s$=STR$ y
124 IF LEN s$=f THEN RETURN
125 LET s$="0"+s$: GO TO CODE " STICK "
166 LOAD i$CODE bs,bl: GO SUB CODE "g": LET a=o: FOR x=o TO t: LET o$(x+i)=CHR$ PEEK (bs+x): NEXT x: POKE rn,s+s: GO SUB s: GO SUB CODE "h": RETURN
175 LET i$=o$(i): SAVE i$CODE bs,bl: LET a=o: RETURN
198 LET s$=t$+"That option is not available!": GO SUB CODE "T"
199 LET s$=t$+"Press <ENTER> to continue!": GO SUB CODE "f"
200 RESTORE VAL "2e3": GO SUB v: IF st THEN GO TO st
201 GO SUB CODE "j": FOR x=i TO LEN x$: IF i$=x$(x) THEN GO TO x*v+CODE ">="
202 NEXT x: GO TO CODE ">="
205 LET s$=t$+m$+k$+r$+b$+t$: GO SUB CODE "T": POKE rn,v+t: GO SUB s: GO TO CODE "<="
210 IF o$(b)<>"R" THEN GO TO CODE " AND "
211 LET s$=f$: GO SUB CODE "f": LET z$=i$: LET s$=g$+STR$ l+"-"+STR$ h+"?": GO SUB CODE "f": GO SUB CODE "M": IF NOT LEN i$ THEN GO TO CODE " AND "
212 LET o$(s)=" ": FOR x=x TO y STEP z: POKE rn,s: GO SUB CODE "s": IF st THEN GO TO st*(st<>i)+CODE " VERIFY "*(st=i)
213 IF o$(s)<>"S" THEN LET s$=t$+e$: GO SUB CODE "f": IF i$="Q" OR i$="S" THEN LET o$(s)=i$: IF i$="Q" THEN GO TO CODE ">="
214 NEXT x: GO TO CODE "<="
215 IF o$(b TO t)="RO" OR (PEEK (rn-t-t)<>o AND o$(f)="N") THEN GO TO CODE " AND "
216 POKE rn,f: LET s$=t$+"To:": GO SUB s: IF NOT LEN i$ THEN GO TO CODE ">="
217 LET z$=i$: LET s$=t$+"Re:": GO SUB s: LET s$=t$+v$+r$+"your message. Let the text wrap": GO SUB CODE "T": LET s$="around, press <ENTER> only at"+r$+"the end of each paragraph!"+r$+w$+t$: GO SUB CODE "T": POKE rn,v: GO SUB CODE "m": GO TO CODE ">="
220 LET s$="One Moment Please!": GO SUB CODE "T": FOR x=i TO v^t: GO SUB CODE "b": IF INKEY$<>"" THEN GO TO VAL "30"
221 NEXT x: LET s$="Sysop not Available!": GO SUB CODE "T": GO TO CODE ">="
225 LET x=NOT PEEK (rn-v): POKE rn-v,x: GO TO CODE ">="
230 LET s$="Line Length (32-255)? ": GO SUB CODE "f": IF NOT LEN i$ THEN GO TO CODE ">="
231 GO SUB CODE "^": IF LEN i$ THEN IF x>=CODE " " AND x<=CODE " COPY " THEN POKE rn-i,x
232 GO TO CODE ">="
235 LET s$="Life is not a bowl of cherries!"+r$+"bye now!": GO SUB CODE "T": GO TO v+v
1000 DATA "4",t$+" Welcome to the"
1001 DATA " Flexi-Board BBS system!"
1002 DATA r$+"Dedicated to all Timex/Sinclair"
1003 DATA " Users in America!"
1004 DATA q$
2000 DATA "8",t$+b$+t$+"Main Menu:"+r$
2001 DATA "<M>"+m$(t TO )+k$
2002 DATA "<R>ead a"+l$
2003 DATA "<W>rite a"+l$
2004 DATA "<C>hat with Sysop"
2005 DATA "<T>oggle Line Feeds"
2006 DATA "<A>lter Line Length"
2007 DATA "<G>oodbye (Logoff)"
2008 DATA a$
3000 DATA "3",t$+m$+"Menu:"+r$
3001 DATA "A-Monday - Friday!"
3002 DATA "B-Weekend Special!"
3003 DATA a$
9000 CLEAR VAL "32767": LOAD "code"CODE : LET o=NOT PI: LET i=SGN PI: LET b=i+i: LET t=INT PI: LET f=b+b: LET v=b+t: LET s=v+b: INK s
9001 LET n$="": LET b$="": LET q$="Password=": LET d$="September 31, 1986": LET c$="HEDWFLOPSTUV*!": LET m$=" Message Base ": LET l$=m$( TO f+v): LET k$="Bulletin": LET r$=CHR$ VAL "13": LET t$=r$+r$: DIM o$(s): DIM h$(f+f)
9002 LET bs=VAL "32768": LET bbs=VAL "63360": LET bf=VAL "63280": LET bl=bf-bs: LET rn=VAL "63549": LET x=INT ((bs+f)/VAL "256"): POKE VAL "63528",(bs+f)-VAL "256"*x: POKE VAL "63529",x
9005 LET p$="password": LET y$="The Tailgunner": LET o$=" NN": LET a$="Your Choice?": LET f$="<F>orward or <R>everse order?": LET g$="Start at ": LET e$="<S>croll, <N>ext, or <Q>uit?": LET v$="Wait for '>' prompt, then send": LET w$="--Control C to end!": LET x$="MRWCTAG"
9006 DELETE VAL "9008",: DELETE VAL "9e3",VAL "9006"
9007 GO TO b
9998 STOP
9999 SAVE "bbs" LINE VAL "9e3": SAVE "code"CODE VAL "63360",VAL "2176"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

