ISTUG-BBS is a bulletin board system written entirely in BASIC for the unexpanded TS2068, allowing remote callers to read and leave messages, chat with the sysop, and scan bulletins via a modem connection. The program uses machine code routines at fixed addresses (26724 for receive, 26766 for transmit, and 26715 for status polling) to handle serial communication through port 115, with modem control via port 119 (CODE “w”). Message storage uses a DIM m$(41,600) string array holding up to 40 messages of 600 characters each, with a parallel DIM d$(560) array for 14-character date/time stamps. The sysop editor, entered by pressing SYMBOL SHIFT, supports reading, deleting, writing, and saving the message base to tape using SAVE “BBSmsgbase” DATA m$(). The system clock is read from the three-byte frame counter at addresses 23672–23674 and converted to hours/minutes for time-on-line tracking.
Program Analysis
Program Structure
The program is organized as a collection of named subroutines, with line numbers stored in BASIC variables rather than hard-coded into GO TO and GO SUB statements. The initialization block at line 1760–1820 assigns all major entry points to variables:
nl= 30 (send newline)sm= 41 (send message, also doubles as the message count limit)beep= 70 (audible beep)menu= 250 (main menu)cls= 360 (clear screen)con= 540 (continue/press enter prompt)ret(dynamic return address, set before branching)
The main execution flow starts at line 10 with GO TO VAL "1760", runs initialization, then enters the sysop editor at 1490. Normal callers enter at line 170 after an incoming call is detected. The program uses ON ERR GO TO extensively for both error recovery and as a flow-control mechanism (e.g., redirecting to the menu on input errors).
Machine Code Interface
Serial communication is handled entirely through three machine code routines at fixed RAM addresses, initialized at line 1790:
| Variable | Address | Purpose |
|---|---|---|
rcv | 26724 | Receive one character; returns ASCII code via USR |
xmt | 26766 | Transmit one character; called before each OUT 115,c |
| 26715 | (literal) | Poll modem status; returns 0, 1, 2, or 3 for TX/RX ready flags |
Characters are received with CHR$ USR rcv and transmitted by first calling RANDOMIZE USR xmt then OUT 115,CODE c$. The modem control register is accessed via port mc (CODE “w” = 119). A form-feed character (ASCII 12) is sent over the modem via OUT 115,12 at line 360 to clear the caller’s terminal screen.
Message Storage
The message base uses a two-dimensional string array DIM m$(41,600) declared at line 1800, where slot 41 (sm) is reserved as a scratch/metadata record. Each message record is structured as:
- Columns 1–31: “TO: ” header with recipient name
- Columns 33–64: “From: ” header with sender name
- Column 65 onward: message body text (up to 534 characters)
- A
CHR$ 3(ETX) byte marks the end of the message body
Date/time stamps are stored in a separate flat string d$ with 14-character slots per message (indexed as d$(y*14-13 TO y*14)). The entire message base is saved to tape with SAVE "BBSmsgbase" DATA m$() from the sysop editor.
Clock and Timing
The system clock is read from the frame counter at addresses 23672–23674 (the standard three-byte ticker) at line 1180, converted to hours and minutes, and compared against the session start time stored in temp. The clock can be set by the sysop via the “E” editor command, which POKEs new values back into 23672–23674 at lines 1855–1860. Midnight rollover is handled at line 1180 with a day counter increment.
Sysop Editor
The sysop editor is entered by pressing SYMBOL SHIFT from most program states (detected as INKEY$=" STEP " — the token representation of that key combination). It is protected by ON ERR RESET at line 1470, which resets error handling to prevent callers from accidentally or intentionally entering the editor via error conditions. The editor flag ed is set to NOT PI (i.e., 0, false) for normal operation and to a non-zero value when the sysop is active, allowing the same code paths (message writing, quick scan) to behave differently for the sysop.
Data Transmission Loop
Sending a string stored in p$ is handled by the subroutine at line 50, which iterates character by character, calling the transmit machine code routine and then OUT 115 for each. A special case exists in the message display loop at line 490: when a CHR$ 3 (ETX) is encountered in the message body, transmission stops early, preventing garbage after the message terminator from being sent to the caller.
Input Handling and Backspace
Several input loops implement a software backspace by detecting CHR$ 8 from the caller and decrementing the loop index x by 2 (one for the current position, one for the NEXT x increment). This pattern appears in name input (line 980), message addressing (line 620), and message body input (line 670).
Notable Techniques and Idioms
VAL "number"is used extensively inGO TOandGO SUBas a memory optimization, avoiding storage of integer line-number literals.SGN PIis used as a constant 1 throughout (sincePI > 0,SGN PI = 1), saving token space over the literal1.NOT PIevaluates to 0 (false), used to initialize boolean-style flags likeed.- The
retvariable acts as a computed return address: it is set to a target line number before calling a subroutine, and the subroutine ends withGO TO ret, enabling reuse of single routines from multiple call sites without nestingGO SUB. - The bulletin, menu text, logon screen, and user list are all stored as
DATAstatements and sent via a genericREAD/GO SUB smloop at line1070, with the firstDATAvalue being the item count. - Line
600usesLET m$(y,LEN d$)=CHR$ PI—LEN d$is 560 (14×40), andCHR$ PIisCHR$ 3sinceINT PI = 3, pre-placing an ETX terminator.
Bugs and Anomalies
- At line
430, the loopFOR x=SGN x TO PIusesSGN xas the start value. Sincexis carried over from prior usage, this is not always 1; ifxhappened to be 0 the loop would start at 0, potentially writing outside the intended 1–3 character input bufferc$=" ". - At line
550,GO TO VAL "550"creates an infinite tight loop with no timeout or escape path for the caller; only an error or disconnect can break out of the “press ENTER” prompt if the caller does not send a carriage return. - The variable
smis used both as the subroutine entry point for “send message” (line 41) and as the message array size limit (41), creating a naming collision that is intentional but potentially confusing. - Line
1550doesLET ed=nlbefore jumping to the leave-message routine. Sincenlis the line number 30, this setsedto 30 rather than a true/false flag, which the message-writing code at line600interprets as truthy, redirecting to the sysop message path — this appears intentional for sysop-mode message leaving.
Content
Image Gallery
Source Code
0 REM CODE FLASH w NEW O<>CODE FLASH w NEW \ >=CODE FLASH w NEW ( POKE FLASH sO AND CONTINUE AND 8 POKE FLASH w NEW \ >=CODE FLASH w NEW ( POKE y OPEN #s<>CODE FLASH w NEW \ >=CODE FLASH w NEW ( POKE STR$ !$!,T]USR ] OR *K\>) GO SUB LEN LPRINT >PEEK PLOT #>XPEEK LIST #>PEEK REM STR$ z GO SUB LEN LPRINT +s( RANDOMIZE <>> OPEN #w>" OPEN #w>@ OPEN #w>{ OPEN #w>7 OPEN #w<> \* PEH SOFTWARE 1986
10 GO TO VAL "1760"
20 REM send a nl= NEW LINE
30 POKE 23692,255: PRINT : RANDOMIZE USR xmt: OUT 115,CODE l$: RETURN
40 REM PRINT TO caller
50 GO SUB nl: PRINT p$;: FOR x=SGN PI TO LEN p$: RANDOMIZE USR xmt: OUT 115,CODE p$(x): NEXT x: RETURN
60 REM BEEP
70 FOR x=9 TO 2 STEP -SGN PI: BEEP .2,15: NEXT x: RETURN
80 REM test FOR sysop access
90 IF IN mc<128 THEN GO TO VAL "1160"
100 IF INKEY$="c" OR INKEY$="C" THEN GO TO VAL "810"
110 IF INKEY$=" STEP " THEN GO TO VAL "1470"
120 GO TO ret
130 REM CLOSE # modem
140 GO SUB VAL "1710": IF ed THEN LET ed=NOT PI
150 OUT mc,34: OUT mc,ed
160 REM test FOR IN call
170 GO SUB VAL "1200": PRINT AT 17,27;t$(2): LET ret=VAL "180": GO TO VAL "110"
180 IF IN mc=5 THEN GO TO 170
190 REM start/set on LINE time
200 RANDOMIZE USR 26834: PAUSE ret: ON ERR GO TO VAL "1160": GO SUB beep: LET temp=hr*60+min: LET l$=CHR$ 13: RANDOMIZE USR 26788: LET p$="Hello"+l$+"Hello is this 2 lines Y/N": GO SUB sm
210 LET c$=CHR$ USR rcv: GO SUB nl: IF c$="y" OR c$="Y" THEN GO TO VAL "930"
220 IF c$="n" OR c$="N" THEN LET l$=CHR$ 10: RANDOMIZE USR 26782: GO TO VAL "930"
230 GO TO 210
240 REM menu
250 RESTORE VAL "1260": ON ERR GO TO VAL "1670": GO SUB cls: GO SUB VAL "1070"
260 LET p$="(C,G,L,Q,R,T,U)? ": GO SUB sm: LET c$=CHR$ USR rcv: PRINT c$: GO SUB nl
270 IF c$="c" OR c$="C" THEN GO TO VAL "810"
280 IF c$="g" OR c$="G" THEN GO TO VAL "1120"
290 IF c$="l" OR c$="L" THEN GO TO VAL "580"
300 IF c$="q" OR c$="Q" THEN LET p$="use CONTROL C to Abort Scan.": GO SUB sm: GO TO VAL "760"
310 IF c$="r" OR c$="R" THEN GO SUB nl: GO TO VAL "420"
320 IF c$="t" OR c$="T" THEN GO SUB VAL "1200": GO SUB sm: GO TO VAL "260"
330 IF c$="u" OR c$="U" THEN GO TO VAL "1100"
340 GO TO VAL "260"
350 REM CLS SCREEN$ ,12=FF
360 CLS : OUT 115,12: RETURN
370 REM FORMAT & READ header
380 LET p1=32: LET p2=64: FOR x=5 TO p2: IF m$(y,x)=l$ AND x<=32 THEN LET p1=x: LET x=39
390 IF m$(y,x)=l$ AND x<=64 THEN LET p2=x: LET x=64
400 NEXT x: LET p$="Message :"+STR$ y+" "+d$(y*14-13 TO y*14)+l$+m$(y, TO p1)+m$(y,33 TO p2): RETURN
410 REM READ msg
420 LET p$="Message 1 to 40, Menu=0 >? ": GO SUB sm: LET c$=" ": ON ERR GO TO menu
430 FOR x=SGN x TO PI: LET c$(x)=CHR$ USR rcv: IF c$(2)=l$ OR c$(PI)=l$ THEN GO TO VAL "460"
440 IF (c$(x)<"0") OR (c$(x)>"9") THEN GO TO VAL "420"
450 NEXT x: IF LEN c$>2 THEN GO TO VAL "420"
460 GO SUB nl: LET y=VAL c$: IF NOT y THEN GO TO menu
470 IF y>=sm OR m$(y,1)=" " THEN GO TO VAL "1700"
480 GO SUB 370: LET p$=p$+m$(y,65 TO )
490 GO SUB cls: PRINT ''"Working": FOR x=SGN PI TO LEN p$: LET c=CODE p$(x): IF c=3 THEN GO TO VAL "520"
500 RANDOMIZE USR xmt: OUT 115,c
510 NEXT x: LET x=x-SGN PI
520 GO SUB nl: PRINT p$( TO x): LPRINT " READ msg. "+STR$ y: GO TO VAL "420"
530 REM con= CONTINUE
540 GO SUB nl: LET p$="Press <ENTER> when ready": GO SUB sm
550 IF CHR$ USR rcv=l$ THEN GO TO ret
560 GO TO VAL "550"
570 REM leave message
580 GO SUB cls: LET p$="Is this message for Sysop Y/N>": GO SUB sm: LET c$=CHR$ USR rcv: IF c$="y" OR c$="Y" THEN GO TO VAL "1740"
590 GO SUB nl: FOR y=SGN PI TO VAL "40": IF m$(y,1)<>" " THEN NEXT y: GO TO VAL "1680"
600 LET m$(y,LEN d$)=CHR$ PI: IF ed THEN GO TO VAL "1610"
610 LPRINT "Left msg. #";y: LET p$="Who gets Mess. "+STR$ y+"?": GO SUB sm: GO SUB nl: LET m$(y)="TO: ": FOR x=5 TO 31
620 LET m$(y,x)=CHR$ USR rcv: IF m$(y,x)=CHR$ 8 AND x>=5 THEN LET x=x-2: NEXT x
630 IF m$(y,x)=l$ THEN GO TO VAL "650"
640 PRINT m$(y,x);: NEXT x: LET m$(y,x)=l$
650 LET m$(y,33 TO )="From: "+n$: LET p$="Input Message (534 chars max).. <CONTROL C> to save.": GO SUB sm: GO SUB nl
660 LET ret=VAL "700": FOR x=CODE "A" TO VAL "599"
670 LET c=USR rcv: IF c=8 AND (x>=65) THEN LET x=x-2: NEXT x
680 LET m$(y,x)=CHR$ c: PRINT CHR$ c;: IF c=3 THEN GO TO VAL "710"
690 GO TO 90
700 NEXT x
710 LET p3=x: LET p$="Message"+(" full and" AND x>=VAL "599")+" saved.": GO SUB sm: GO SUB VAL "740": IF ed THEN LET ed=p3: GO TO VAL "1750"
720 GO TO VAL "790"
730 REM FORMAT date/time
740 GO SUB VAL "1200": LET d$(y*14-13 TO y*14)=p$: RETURN
750 REM Quick scan
760 LET ret=VAL "770": FOR y=SGN PI TO 40: IF m$(y,1)<>" " THEN GO SUB 370: GO SUB sm: IF NOT ed THEN GO TO 90
770 IF IN 115=3 THEN GO TO VAL "260"
780 NEXT y: IF ed THEN GO TO VAL "1650"
790 LET ret=menu: GO TO con
800 REM chat
810 LET p$="Paging sysop....": GO SUB sm: FOR x=SGN x TO VAL "144": IF INKEY$="" THEN PRINT ".";: RANDOMIZE USR xmt: OUT 115,46: BEEP .1,15: NEXT x: LET p$="Sorry, all we found were cobwebs": GO SUB sm: LPRINT "Tried chat": PAUSE CODE "c": GO TO menu
820 GO SUB cls: LET p$=n$+"entering chat mode": GO SUB sm: GO SUB nl: POKE VAL "23658",ed: PRINT "SYMBL SHIFT/ STEP TO LEAVE CHAT."
830 IF INKEY$=" STEP " THEN GO TO VAL "1470"
840 LET p1=ed: LET p2=ed
850 LET x=USR 26715: LET p2=x AND (x=1 OR x=3): LET p1=x AND (x=2 OR x=3)
860 IF p1 THEN LET c=USR rcv: PRINT CHR$ c;: IF c=CODE l$ THEN PRINT '">";
870 IF p2 AND INKEY$<>"" THEN GO TO 890
880 GO TO 830
890 IF p2 THEN LET c$=INKEY$: PRINT c$;: OUT 115,CODE c$: FOR x=NOT PI TO PI: NEXT x: IF c$=CHR$ 13 THEN RANDOMIZE USR xmt: OUT 115,62
900 IF INKEY$<>"" THEN GO TO 900
910 GO TO 830
920 REM send logon SCREEN$
930 RESTORE VAL "1290": GO SUB cls: GO SUB VAL "950": LET ret=VAL "1050": GO TO con
940 REM send bulletin
950 RESTORE VAL "1330": GO SUB cls: GO SUB VAL "1070"
960 REM INPUT callers name
970 GO SUB beep: LET p$="Who's calling?"+l$: GO SUB sm: LET n$=" ": RANDOMIZE USR xmt: OUT 115,5: FOR x=SGN x TO LEN n$
980 LET n$(x)=CHR$ USR rcv: IF n$(x)=CHR$ 8 AND (x>=2) THEN LET x=x-2: NEXT x
990 IF x<SGN PI THEN GO TO VAL "970"
1000 IF n$(x)=l$ THEN GO TO VAL "1020"
1010 PRINT n$(x);: NEXT x
1020 LET n$=n$( TO x-SGN PI)+l$: IF LEN n$<5 THEN GO TO VAL "970"
1030 LET p$=n$+"Correct (Y/N) >": GO SUB sm: LET c$=CHR$ USR rcv
1040 IF c$="n" OR c$="N" THEN GO TO VAL "970"
1050 GO SUB nl: GO SUB 1200: LPRINT n$'p$( TO 14): GO TO menu
1060 REM send SCREEN$ DATA
1070 READ y: LET ret=VAL "1080": FOR y=SGN PI TO y: READ p$: GO SUB sm: GO TO 90
1080 NEXT y: RETURN
1090 REM user LIST
1100 RESTORE VAL "1370": GO SUB cls: GO SUB VAL "1070": LET ret=menu: GO TO con
1110 REM quit BBS
1120 GO SUB cls
1130 RESTORE VAL "1430": GO SUB VAL "1070"
1140 LET c$=CHR$ USR rcv: IF c$="n" OR c$="N" THEN GO TO menu
1150 READ p$: GO SUB sm: GO SUB VAL "1200": GO SUB sm: LPRINT p$'': OUT mc,64: OUT mc,0
1160 OUT mc,0: GO TO VAL "140"
1170 REM timer
1180 LET time=VAL "((PEEK 23672+256*PEEK 23673+65536*PEEK 23674)/3600)": LET hr=INT (time/60): LET min=INT (time-hr*60): IF hr>=24 THEN LET hr=NOT PI: LET dy=dy+SGN PI: GO SUB VAL "1850"
1190 RETURN
1200 GO SUB 1180: LET time=INT ((hr*60+min)-temp)
1210 LET p1=INT (time/60): LET p2=INT (time-(p1*60)): IF p1<0 THEN LET p1=p1+24
1220 LET c=SGN PI: GO SUB 1240: LET c=2: LET p1=hr: LET p2=min: GO SUB 1240
1230 LET p$=STR$ mo+"-"+STR$ dy+"-"+STR$ yr+";"+t$(2)+" Online "+t$(1)+l$: RETURN
1240 LET t$(c)=("0" AND LEN STR$ p1=SGN PI)+STR$ p1+":"+("0" AND LEN STR$ p2=SGN PI)+STR$ p2: RETURN
1250 REM menu DATA
1260 DATA 10," I.S.T.U.G.'s TIMEX-BOARD","","<C>hat","<G>oodby","<L>eave a Message"
1270 DATA "<Q>uick Scan messages","<R>ead message","<T>ime on","<U>sers list",""
1280 REM logon DATA
1290 DATA 11," WELCOME TO I.S.T.U.G.'s BBS","-------------------------------","using an unexpanded Timex 2068","","Settings:","Duplex: Full Word Size: 7","Parity: Even Stop Bits: 1",""
1300 DATA "","Open 24hrs a day, 7 days a week",""
1310 DATA ""
1320 REM bull DATA
1330 DATA 8,"Mad Programmer: Paul Holmgren","Igor Sysop: Willie Jones","","**** >>> -Bullentin- <<< **** "
1340 DATA "","Keep your Modem's warm and stay","tuned to the I.S.T.U.G. BBs",""
1350 DATA ""
1360 REM user DATA
1370 DATA 8,""
1380 DATA ""
1390 DATA ""
1400 DATA ""
1410 DATA "","If we missed you please leave","Sysop a mess. and you will be","added to our list"
1420 REM quit DATA
1430 DATA 8,"","The Indiana Sinclair Timex","","Users Group hopes you enjoyed"
1440 DATA "","our little Bulletin Board.","","Are you ready to leave Y/N >","Goodby. Hanging up now"
1450 DATA ""
1460 REM edit
1470 ON ERR RESET
1480 LET p$="Commander Sysop taking control, Please Stand By": GO SUB sm
1490 CLS : PRINT "SYSOP EDITOR"''"C heck Time"'"D elete a msg."'"E stablish correct time"'"H ang up"'"L eave a msg."'"M ain Menu"'"Q uick Scan Msg. base"'"R ead Msg."'"S ave messages"''"(C,D,E,H,L,M,Q,R,S)"
1500 POKE VAL "23658",VAL "8": PAUSE ed: LET c$=INKEY$
1510 IF c$="C" THEN GO SUB VAL "1200": PRINT p$( TO 14): GO TO VAL "1650"
1520 IF c$="D" THEN INPUT "DELETE msg. #?: ";x: PRINT "Msg. # ";x;" deleted": LET m$(x)="": LET p$="": GO SUB VAL "740": GO TO VAL "1650"
1530 IF c$="E" THEN GO SUB VAL "1840"
1540 IF c$="H" THEN GO TO VAL "1120"
1550 IF c$="L" THEN LET ed=nl: GO TO VAL "590"
1560 IF c$="M" THEN GO TO menu
1570 IF c$="Q" THEN LET ed=nl: GO TO VAL "750"
1580 IF c$="R" THEN INPUT " READ Msg. #? ";y: CLS : GO SUB VAL "370": PRINT p$'m$(y,CODE "A" TO ): GO TO VAL "1650"
1590 IF c$="S" THEN LET m$(sm)=d$: SAVE "BBSmsgbase" DATA m$()
1600 GO TO VAL "1490"
1610 CLS : LET ed=NOT PI: INPUT " SYMBL SHIFT/ STEP TO STOP :"'"Msg. TO ? ";p$: LET m$(y)="To :"+p$+l$: LET m$(y,CODE "!" TO )="From : Sysop Willie"+l$: GO SUB VAL "740": GO SUB VAL "370": PRINT p$: LET p$=""
1620 INPUT LINE c$: IF c$=" STEP " THEN GO TO VAL "1640"
1630 LET p$=p$+c$+l$: PRINT c$: IF LEN p$<=VAL "535" THEN GO TO VAL "1620"
1640 LET m$(y,CODE "A" TO )=p$+CHR$ PI: PRINT
1650 LET ed=NOT PI: PRINT " NEXT ?": PAUSE ed: GO TO VAL "1490"
1660 REM error trapped messages
1670 ON ERR RESET : LET p$="Sorry "+n$+" A bug has Bit you.": GO SUB sm: GO SUB beep: GO TO menu: REM ?=control g=Beep
1680 IF ed THEN PRINT "I,m full, delete something first": GO TO VAL "1650"
1690 LET p$="Message base full right now."+l$+"Please try later": LPRINT '" I'm full. Do something. SOS"'': GO SUB sm: GO TO con
1700 LET ret=VAL "420": LET p$="Sorry no message "+STR$ y+". Try again": GO SUB sm: GO TO con
1710 REM idle SCREEN$
1720 CLS : PRINT AT VAL "7",VAL "7";"The I.S.T.U.G. BBS";AT VAL "10",PI+PI;"Scanning Phone LINE "'''''''" PEH SOFTWARE: 10-11-86": RETURN
1730 REM LPRINT sysop msg
1740 GO SUB cls: LET y=sm: LET m$(y)="": LET ed=y: GO TO VAL "650"
1750 LPRINT n$;m$(sm,CODE "A" TO ed): LET ed=NOT PI: GO TO menu
1760 REM init, 40 msg. base
1770 PAPER NOT PI: BORDER NOT PI: INK VAL "9": CLS
1780 PRINT '" STEP from most modes will GO TO SYSOP edit menu"
1790 LET rcv=VAL "26724": LET xmt=VAL "26766": DIM t$(2,5): LET temp=PI: LET mc=CODE "w"
1800 LET nl=VAL "30": LET sm=VAL "41": LET beep=VAL "70": LET menu=VAL "250": LET cls=VAL "360": LET con=VAL "540": LET l$=CHR$ 13: DIM m$(sm,VAL "600"): LET ed=NOT PI
1810 INPUT "Add a message base? ";p$: IF p$="y" OR p$="Y" THEN PRINT "Start Message Base Tape.": LOAD "" DATA m$()
1820 LET d$=m$(sm): GO TO VAL "1490"
1830 REM FORMAT & POKE clock
1840 INPUT "Month 1-12 ";mo,"Day 1-31 ";dy,"Hour 00-23 ";hr,"Minute ";min,"Year 86 ";yr
1850 LET x=VAL "(hr*3600+min*60)*60": LET y=INT (x/65536): POKE 23674,y
1860 LET p1=x-y*65536: LET x=INT (p1/256): POKE 23673,x: POKE 23672,p1-x*256: RETURN
1870 REM SAVE
1880 CLEAR : SAVE "bbs" LINE PI: GO TO VAL "1760"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.