Eliad Wannum’s BBS

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: BBS

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) and xmt=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 computed GO TO and GO SUB statements.
  • 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 a GO SUB target that falls through to line 50 and the index of the date row.
  • The time of day is kept in the FRAMES system variable (23672–23674), which the sysop sets by POKEing (hr*3600+min*60)*60.
  • SGN PI, NOT PI and VAL "n" are used for small constants to save memory.
  • ON ERR GO TO traps 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, dy and yr, 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 while x already holds a positive value.

Related Content

Image Gallery

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.