Runs a dial-up bulletin board on an unexpanded TS2068 with a 2050 modem and a 2040 printer. Callers see a logon screen and bulletin, give their name, and choose from a menu: Chat, Goodbye, Leave a message, Quick scan, Read a message, Time on, and Users list. The message base holds 40 messages of up to 535 characters each, and it can be saved to and loaded from tape as a DATA array. Calls, messages read and left, and chat requests are logged on the printer. Pressing NOT on the keyboard gives the sysop an editor for checking and setting the clock, deleting, reading and leaving messages, hanging up and saving the message base.
The bulletin credits “Sysop: ELIAD P. WANNUM” and “Mad Programmer: Paul Holmgren,” and the idle screen shows “The E.P.M. BBS” with “© PEH SOFTWARE: 9-27-86”. The program is a customized copy of ISTUG-BBS, reworded and branded for this board.
Program Analysis
Program Structure
- Line 0: a REM holding machine code for the serial routines, called at
26715(status),rcv=26724(receive a character) andxmt=26759(wait until ready to transmit) - Lines 20–120: send newline, send string, beep, and the sysop and carrier check
- Lines 130–250: idle screen, wait for a call, answer, and ask the caller about line feeds
- Lines 260–360: main menu
- Lines 390–870: message header formatting, read, leave and quick scan
- Lines 880–1000: chat mode
- Lines 1010–1250: logon, bulletin, caller name, users list and hang-up
- Lines 1260–1330: clock and time-online formatting
- Lines 1340–1510: screen text in
DATA - Lines 1520–1710: sysop editor
- Lines 1820–1940: initialization, optional message-base load, and setting the clock
Hardware I/O
Data goes to the modem on port 115 with OUT 115, and port 119 (mc) is used for control and status. Before each character is sent the program calls the transmit-ready routine, and every incoming character is read with CHR$ USR rcv. Line 90 treats IN mc<128 as carrier loss and hangs up. The logon screen gives the settings as full duplex, 7-bit words, even parity and one stop bit.
Message Storage
Messages are stored in m$(41,600). Columns 1–32 hold the TO: field, 33–64 hold From: and the caller’s name, and the body starts at column 65. Row 41 holds the 14-character date and time stamp for each message (d$(y*14-13 TO y*14)), so the stamps are saved with the array. While a message is being entered, Backspace (code 8) is honored and Ctrl-C (code 3) ends it.
Notable Techniques
- Line numbers are kept in variables (
menu,read,chat,quit) and used in computedGO TOandGO SUBstatements. - The variable
beep(60) is both the line of the beep subroutine and the constant 60 in the hour and minute arithmetic. Similarly,sm(41) is both aGO SUBtarget that falls through to line 50 and the index of the date row. - The time of day is kept in the
FRAMESsystem variable (23672–23674), which the sysop sets by POKEing(hr*3600+min*60)*60. SGN PI,NOT PIandVAL "n"are used for small constants to save memory.ON ERR GO TOtraps errors during a call, sends “A bug has Bit you” and returns to the menu.
Potential Issues
- The time display at line 1320 uses
mo,dyandyr, which are set only by the editor’s E (establish time) option. The sysop therefore has to set the time before going online, or the routine fails as soon as the idle loop at line 170 calls it. - The sysop delete option (line 1580) does not check that the message number is between 1 and 40.
- Lines 900 and 1060 start loops with
FOR x=SGN x, which works only whilexalready holds a positive value.
Source Code
0 REM CODE FLASH w NEW O<>CODE FLASH w NEW >=CODE FLASH w NEW ( POKE FLASH sO FLASH w NEW >=CODE FLASH w NEW ( POKE y OPEN #s<>CODE FLASH w NEW >=CODE FLASH w NEW ( POKE <>
10 GO TO VAL "1820"
20 REM send a nl= NEW LINE
30 POKE 23692,255: PRINT : RANDOMIZE USR xmt: OUT 115,nlv: 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 "1250"
100 IF INKEY$="c" OR INKEY$="C" THEN GO TO chat
110 IF INKEY$="NOT " THEN GO TO edit
120 GO TO ret
130 REM CLOSE # modem
140 GO SUB VAL "1770"
150 OUT mc,34: OUT mc,ed
160 REM test FOR IN call
170 GO SUB VAL "1290": PRINT AT 17,27;T$(2): LET ret=VAL "180": GO TO VAL "110"
180 IF IN mc=5 THEN GO TO 170
190 OUT mc,2: OUT mc,34: PAUSE ret
200 REM start/set on LINE time
210 ON ERR GO TO 1250: OUT mc,64: OUT mc,123: OUT mc,55: GO SUB beep: LET temp=hr*beep+min
220 LET nlv=CODE l$: LET p$="Hello"+l$+"Hello, Is this 2 lines Y/N >": GO SUB sm
230 LET c$=CHR$ USR rcv: IF c$="y" OR c$="Y" THEN GO TO VAL "1020"
240 IF c$="n" OR c$="N" THEN LET nlv=10: GO TO VAL "1020"
250 GO TO 230
260 REM menu
270 RESTORE VAL "1350": ON ERR GO TO VAL "1720": GO SUB cls: GO SUB 1160
280 LET p$="(C,G,L,Q,R,T,U)? ": GO SUB sm: LET c$=CHR$ USR rcv: PRINT c$: GO SUB nl
290 IF c$="c" OR c$="C" THEN GO TO chat
300 IF c$="g" OR c$="G" THEN GO TO quit
310 IF c$="l" OR c$="L" THEN GO TO leave
320 IF c$="q" OR c$="Q" THEN GO TO VAL "840"
330 IF c$="r" OR c$="R" THEN GO SUB nl: GO TO read
340 IF c$="t" OR c$="T" THEN GO SUB VAL "1290": GO SUB sm: GO TO VAL "280"
350 IF c$="u" OR c$="U" THEN GO TO VAL "1190"
360 GO TO VAL "280"
370 REM CLS SCREEN$ ,12=FF
380 CLS : OUT 115,12: RETURN
390 REM FORMAT & READ header
400 LET p1=32: LET p2=64: LET c$=m$(y, TO p2): FOR x=5 TO p2
410 IF c$(x)=l$ AND x<=32 THEN LET p1=x: LET x=39
420 IF c$(x)=l$ AND (x>32) AND (x<=64) THEN LET p2=x: LET X=64
430 NEXT x: LET p$="Message :"+STR$ y+" "+d$(y*14-13 TO y*14)+l$+c$( TO p1)+c$(33 TO p2): RETURN
440 REM READ mess
450 LET p$="Message 1 to 40, Menu=0 >? ": GO SUB sm: LET c$=" ": ON ERR GO TO menu
460 FOR x=SGN x TO PI: LET c$(x)=CHR$ USR rcv: IF c$(2)=l$ THEN LET c$=c$(SGN PI): GO TO VAL "500"
470 IF c$(PI)=l$ THEN LET c$=c$( TO 2): GO TO VAL "500"
480 IF (c$(x)<"0") OR (c$(x)>"9") THEN GO TO VAL "450"
490 NEXT x: IF LEN c$>2 THEN GO TO read
500 GO SUB nl: LET y=VAL c$: IF NOT y THEN GO TO menu
510 IF m$(y,1)=" " THEN GO TO VAL "1760"
520 GO SUB 390: LET p$=p$+m$(y,65 TO )
530 GO SUB cls: PRINT ''"Working": FOR x=SGN PI TO LEN p$: LET c=CODE p$(x): IF c=3 THEN GO TO VAL "560"
540 RANDOMIZE USR xmt: OUT 115,c
550 NEXT x: LET x=x-SGN PI
560 GO SUB nl: PRINT p$( TO x): LPRINT "Read mess. "+STR$ y: GO TO VAL "450"
570 REM con= CONTINUE
580 GO SUB nl: LET p$="Press <ENTER> when ready": GO SUB sm
590 IF CHR$ USR rcv=l$ THEN GO TO ret
600 GO TO VAL "590"
610 REM leave message
620 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 "1800"
630 GO SUB nl: FOR y=SGN PI TO VAL "40"
640 IF m$(y,1)=" " THEN GO TO VAL "660"
650 NEXT y: GO TO VAL "1740"
660 IF ed THEN GO TO VAL "1670"
670 LPRINT "Left mess. #";y
680 LET p$="Who Gets Mess. "+STR$ y+"?": GO SUB sm: GO SUB nl: LET m$(y)="TO: ": FOR x=5 TO 31
690 LET m$(y,x)=CHR$ USR rcv: IF m$(y,x)=CHR$ 8 THEN LET x=x-SGN PI: GO TO 690
700 IF m$(y,x)=l$ THEN GO TO VAL "720"
710 PRINT m$(y,x);: NEXT x: LET m$(y,x)=l$
720 LET m$(y,33 TO )="From: "+n$: LET p$="Input Message (535 chars max).. <CTRL C> to save...": GO SUB sm: GO SUB nl
730 LET ret=VAL "780": FOR x=CODE "A" TO VAL "600"
740 LET c=USR rcv: IF c=8 THEN LET x=x-SGN x: GO TO VAL "740"
750 IF x<65 THEN GO TO VAL "720"
760 LET m$(y,x)=CHR$ c: PRINT m$(y,x);: IF c=INT PI THEN GO TO VAL "790"
770 GO TO 90
780 NEXT x: LET x=VAL "600"
790 IF ed THEN LET ed=x
800 LET p$="Message"+(" full and" AND x>=600)+" saved.": GO SUB sm: GO SUB 830: IF ed THEN GO TO VAL "1810"
810 GO TO VAL "870"
820 REM FORMAT date/time
830 GO SUB 1290: LET d$(y*14-13 TO y*14)=p$: RETURN
840 REM Quick scan
850 LET ret=VAL "860": FOR y=SGN PI TO 40: IF m$(y,1)<>" " THEN GO SUB 390: GO SUB sm: IF NOT ed THEN GO TO 90
860 NEXT Y: IF ed THEN LET ed=NOT PI: GO TO VAL "1710"
870 LET ret=menu: GO TO con
880 REM chat
890 LET p$="Paging sysop....": GO SUB sm
900 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 con: GO TO menu
910 GO SUB cls: LET p$=N$+"entering chat mode": GO SUB sm: GO SUB nl: POKE 23658,ed: PRINT '"SYMBL SHIFT/NOT TO LEAVE CHAT"
920 IF INKEY$="NOT " THEN GO TO edit
930 LET rcv1=ed: LET xmt1=ed
940 LET x=USR 26715: LET Xmt1=x AND (x=1 OR x=3): LET Rcv1=x AND (x=2 OR x=3)
950 IF Rcv1 THEN LET c=USR rcv: PRINT CHR$ c;: IF c=CODE l$ THEN PRINT '">";
960 IF Xmt1 AND INKEY$<>"" THEN GO TO 980
970 GO TO 920
980 IF Xmt1 THEN LET c$=INKEY$: PRINT c$;: OUT 115,CODE c$: FOR x=NOT PI TO PI: NEXT x: IF c$=l$ THEN RANDOMIZE USR xmt: OUT 115,62
990 IF INKEY$<>"" THEN GO TO 990
1000 GO TO 920
1010 REM send logon SCREEN$
1020 RESTORE VAL "1380": GO SUB cls: GO SUB VAL "1160": LET ret=VAL "1040": GO TO con
1030 REM send bulletin
1040 RESTORE VAL "1410": GO SUB cls: GO SUB VAL "1160"
1050 REM INPUT callers name
1060 LET p$="Who's calling?"+l$: GO SUB sm: LET N$=" ": RANDOMIZE USR xmt: OUT 115,5: GO SUB beep: FOR x=SGN x TO LEN n$
1070 LET n$(x)=CHR$ USR rcv: IF n$(x)=CHR$ 8 THEN LET x=x-SGN PI: GO TO 1070
1080 IF x<SGN PI THEN GO TO VAL "1060"
1090 IF n$(x)=l$ THEN GO TO 1110
1100 PRINT n$(x);: NEXT x: LET x=x-SGN PI: LET n$=n$+l$
1110 LET n$=n$( TO x): IF LEN n$<5 THEN GO TO VAL "1060"
1120 LET p$=N$+"Correct (Y/N) >": GO SUB sm: LET c$=CHR$ USR rcv
1130 IF c$="n" OR c$="N" THEN GO TO 1060
1140 GO SUB nl: GO SUB 1290: LPRINT N$'p$( TO 14): GO TO menu
1150 REM send SCREEN$ DATA
1160 READ y: LET ret=VAL "1170": FOR y=SGN PI TO y: READ p$: GO SUB sm: GO TO 90
1170 NEXT y: RETURN
1180 REM user LIST
1190 RESTORE VAL "1440": GO SUB cls: GO SUB VAL "1160": LET ret=menu: GO TO con
1200 REM quit bbs
1210 GO SUB cls
1220 RESTORE VAL "1500": GO SUB VAL "1160"
1230 LET C$=CHR$ USR rcv: IF c$="n" OR c$="N" THEN GO TO menu
1240 READ p$: GO SUB sm: GO SUB VAL "1290": GO SUB sm: LPRINT p$'': OUT mc,64: OUT mc,0
1250 OUT mc,0: GO TO VAL "140"
1260 REM timer
1270 LET time=VAL "INT ((PEEK 23672+256*PEEK 23673+65536*PEEK 23674)/3600)": LET hr=INT (time/beep): LET min=INT (time-hr*beep): IF hr>=24 THEN LET hr=NOT PI: LET dy=dy+SGN PI: GO SUB VAL "1910"
1280 RETURN
1290 GO SUB 1270: LET time=INT ((hr*beep+min)-temp)
1300 LET p1=INT (time/beep): LET p2=INT (time-(p1*beep)): IF p1<NOT x THEN LET p1=p1+24
1310 LET c=SGN PI: GO SUB 1330: LET c=2: LET p1=hr: LET p2=min: GO SUB 1330
1320 LET p$=STR$ mo+"-"+STR$ dy+"-"+STR$ yr+";"+t$(2)+" Online "+t$(1)+l$: RETURN
1330 LET t$(c)=("0" AND LEN STR$ p1=SGN PI)+STR$ p1+":"+("0" AND LEN STR$ p2=SGN PI)+STR$ p2: RETURN
1340 REM menu DATA
1350 DATA 10," ELIAD P. WANNUM's TIMEX-BOARD","","<C>hat","<G>oodby","<L>eave a Message"
1360 DATA "<Q>uick Scan messages","<R>ead message","<T>ime on","<U>sers list",""
1370 REM logon DATA
1380 DATA 15," WELCOME TO ELIAD WANNUM's BBS","-------------------------------- ","Settings:","DUPLEX: FULL WORD SIZE: 7","PARITY: EVEN STOP BITS: 1",""
1390 DATA "","We are operating on an unex-","panded Timex 2068, a 2050 modem","and a 2040 printer.","","Tell us what you think about","this BBS","","Open OCCASIONALLY"
1400 REM Bull DATA
1410 DATA 8,"Sysop: ELIAD P. WANNUM","Mad Programmer: Paul Holmgren","","**** >>> -Bullentin- <<< **** "
1420 DATA "","Keep your Modem's warm and stay","tuned to the E.P.M. BBs",""
1430 REM USR DATA
1440 DATA 8,""
1450 DATA ""
1460 DATA ""
1470 DATA ""
1480 DATA "","If we missed you please Leave","Sysop a mess. and you will be","added to our list."
1490 REM quit DATA
1500 DATA 8,"","The ELIAD P. WANNUM","","Users Group hopes you enjoyed"
1510 DATA "","our little Bulletin Board.","","Are you ready to leave Y/N >","Goodby. Hanging up now"
1520 REM edit
1530 ON ERR RESET
1540 LET p$="Commander Sysop taking Control, Please Stand By": GO SUB sm
1550 CLS : PRINT "SYSOP EDITOR"''"C heck Time"'"D elete a mess."'"E stablish correct time"'"H ang up"'"L eave a mess."'"M ain Menu"'"Q uick Scan Mess."'"R ead Mess."'"S ave messages"''"(C,D,E,H,L,M,Q,R,S)"
1560 POKE VAL "23658",VAL "8": PAUSE ed: LET c$=INKEY$
1570 IF c$="C" THEN GO SUB VAL "1290": PRINT p$( TO 14): GO TO VAL "1710"
1580 IF c$="D" THEN INPUT "DELETE Mess. #?: ";x: PRINT "Mess. # ";x;" deleted.": LET m$(x)="": GO TO VAL "1710"
1590 IF c$="E" THEN GO SUB VAL "1900"
1600 IF c$="H" THEN GO TO quit
1610 IF c$="L" THEN LET ed=nl: GO TO VAL "630"
1620 IF c$="M" THEN GO TO menu
1630 IF c$="Q" THEN LET ed=nl: GO TO VAL "840"
1640 IF c$="R" THEN INPUT " READ Mess. #?: ";y: CLS : GO SUB VAL "390": PRINT p$'m$(y,CODE "A" TO ): GO TO VAL "1710"
1650 IF c$="S" THEN LET m$(sm)=d$: SAVE "TSBBSmbase" DATA m$()
1660 GO TO VAL "1550"
1670 CLS : LET ed=NOT PI: INPUT " SYMBL SHIFT/NOT TO STOP :"'"Mess. TO ? ";p$: LET m$(y)="To :"+p$+l$: LET m$(y,VAL "33" TO )="From : Sysop ELIAD"+l$: GO SUB VAL "830": GO SUB VAL "390": PRINT P$: LET p$=""
1680 INPUT LINE c$: IF c$="NOT " THEN LET p$=p$+CHR$ PI: GO TO VAL "1700"
1690 LET p$=p$+c$+l$: PRINT c$: IF LEN p$<=VAL "535" THEN GO TO VAL "1680"
1700 LET m$(y,CODE "A" TO )=p$: PRINT
1710 PRINT " NEXT ?": PAUSE ed: GO TO VAL "1550"
1720 REM error trapped messages
1730 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
1740 IF ed THEN PRINT "I'm full, delete something first": LET ED=NOT X: GO TO VAL "1710"
1750 LET p$="Message base full right now. Please try later": GO SUB sm: GO TO con
1760 LET ret=read: LET p$="Sorry no message "+STR$ y+". Try again": GO SUB sm: GO SUB nl: GO TO con
1770 REM idle SCREEN$
1780 CLS : PRINT AT VAL "7",VAL "7";"The E.P.M. BBS";AT VAL "10",VAL "6";"Scanning Phone Line."'''''''"* PEH SOFTWARE: 9-27-86": RETURN
1790 REM LPRINT sysop mess
1800 GO SUB cls: LET y=sm: LET m$(y)="": LET ed=y: GO TO VAL "720"
1810 LPRINT n$;m$(sm,65 TO ed): LET ed=NOT PI: GO TO menu
1820 REM OPEN # 40 mess. base
1830 PAPER NOT PI: BORDER NOT PI: INK VAL "9": CLS
1840 PRINT '" NOT from most modes will GO TO SYSOP edit menu."
1850 LET rcv=VAL "26724": LET xmt=VAL "26759": DIM T$(2,5): LET temp=PI: LET nlv=VAL "13": LET mc=VAL "119"
1860 LET nl=VAL "30": LET sm=VAL "41": LET beep=VAL "60": LET menu=VAL "270": LET cls=VAL "380": LET read=VAL "450": LET con=VAL "580": LET leave=VAL "620": LET chat=VAL "890": LET edit=VAL "1530": LET quit=VAL "1210": LET l$=CHR$ 13: DIM m$(VAL "41",VAL "600"): LET ed=NOT PI
1870 INPUT "Add a Message base? ";p$: IF p$="y" OR p$="Y" THEN PRINT "Start MESSAGE BASE tape.": LOAD "" DATA m$()
1880 LET d$=m$(sm): GO TO edit
1890 REM FORMAT & POKE clock
1900 INPUT "Month 1-12 ";mo,"Day 1-31 ";dy,"Hour 00-23 ";hr,"Minute ";min,"Year 86 ";yr
1910 LET x=VAL "(hr*3600+min*60)*60": LET y=INT (x/65536): POKE 23674,y
1920 LET p1=x-y*65536: LET x=INT (p1/256): POKE 23673,x: POKE 23672,p1-x*256: RETURN
1930 REM SAVE
1940 CLEAR : SAVE "BBS" LINE VAL "1820": GO TO VAL "1820"Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

