--- title: "Flexi-Board" id: 50508 type: "computer_media" slug: "casboard-2068" url: "http://localhost/computer_media/casboard-2068/" markdown_url: "http://localhost/computer_media/casboard-2068.md" published_at: "2023-06-18T15:23:03+00:00" modified_at: "2026-03-30T21:42:44+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A full-featured modem BBS program with machine code serial I/O, message base management, sysop chat, and a clever initialization trick that erases its own setup lines after booting." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "Kurt A. Casby" slug: "kurt-a-casby" taxonomy: "post_tag" url: "http://localhost/tag/kurt-a-casby/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Kurt A. Casby" slug: "kurt-a-casby" taxonomy: "indiv" url: "http://localhost/indiv/kurt-a-casby/" genre: - name: "BBS" slug: "bbs" taxonomy: "genre" url: "http://localhost/type/bbs/" media_type: "Program" programmers: - name: "Kurt A. Casby" slug: "kurt-a-casby" taxonomy: "indiv" url: "http://localhost/indiv/kurt-a-casby/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Casboard%202068%20%281986%29%28Kurt%20A.%20Casby%29%28TS2068%29%28US%29%28Program%29.zip" producer_company: - id: 11029 title: "Kurt A. Casby" type: "company" url: "http://localhost/company/kurt-a-casby/" images: - url: "http://localhost/wp-content/uploads/2023/06/SCR-20260330-jldq.png" - url: "http://localhost/wp-content/uploads/2023/06/Casboard-2068.jpg" related_products: - id: 26949 title: "Casboard 2068" type: "product" url: "http://localhost/product/casboard-2068/" related_content: - id: 26949 title: "Casboard 2068" type: "product" url: "http://localhost/product/casboard-2068/" media_type_tags: "BBS" --- 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 behavior - `bf = 63280`: Base of the receive buffer; characters are read back by PEEKing from `bf` onward in line 8 - `bs = 32768`: Start of the message base CODE block in RAM - `bl = 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 47 - `CODE "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 variable `m$` at runtime — an indirect variable dereference idiom. - `RESTORE VAL "1e3"` and `RESTORE VAL "2e3"` use scientific notation to jump to DATA lines 1000 and 2000 respectively, saving bytes over storing “1000”. - Line 220 polls `INKEY$` inside a `FOR` loop 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 label `l$` 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+v` uses `v=5`, so for a matching character at position `x` in `c$`, the target line is `5x+5`. This maps `c$="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 name `x`; this works because `x` was set by the preceding `GO SUB CODE "M"` call, but it is fragile if the flow changes. ## 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$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 xh 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 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 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$(t TO )+k$ 2002 DATA "ead a"+l$ 2003 DATA "rite a"+l$ 2004 DATA "hat with Sysop" 2005 DATA "oggle Line Feeds" 2006 DATA "lter Line Length" 2007 DATA "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$="orward or everse order?": LET g$="Start at ": LET e$="croll, ext, or 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" ```