Category Archives: Retro Computing

2013 Chicago CoCoFEST! Report

BREAKING NEWS! The Glenside Color Computer Club has posted dates for the 2014 CoCoFEST! The 23rd Annual “Last” Chicago CoCoFEST! will be held April 26 & 27, 2014 in Lombard, Illinois at the same location used by the Vintage Computer Festival. See www.glensideccc.com for more details.

NOTE: There are still some things missing in this report. Check back and I will update it. The latest revision is marked at the top, and changes will be in that color (red). I will be updating this report with photos and video clips as I have time.

http://www.youtube.com/watch?v=sshcYHuBnvc

2013 Chicago CoCoFEST!

(April 26-27, Lombard, IL)
by Allen Huffman of Sub-Etha Software
(4/29/2013, 5/7/2013)

NOTE: Any mistakes in this report are most likely unintentional, except for the intentional ones. This report is being created on an Apple iPad (then later edited in WordPress), so be warned. There may be auto-correct ahead…

BACKGROUND

Since many reading this will not know who I am, I decided to add this new section. My name is Allen Huffman, and although I live in Iowa today, I am originally from Texas. I became a CoCo owner in 1982, after starting out with a Commodore VIC-20. In 1990, I started a CoCo software company, Sub-Etha Software, with my friend, Terry Todd. We made our first public appearance at the 1990 Atlanta CoCoFest, just before our ads began appearing in The Rainbow magazine. In 1991, I wrote my first CoCoFest report (after realizing no one seemed to be doing them any more) and I have continued doing them for most of the events I have attended. In 1995, I took a job with Microware, creator of OS-9, and moved to Iowa. At that time, Sub-Etha was shut down, though I still attended as many CoCoFests as I could.

Although I am no longer as active within the community as I once was, I still plan to get my CoCo stuff out of storage and set it back up some day, and I still try to contribute where I can — even if it is just “yet another” CoCoFest report, or home movie DVD from the event.

I have DVDs available from Chicago 2009, 2010 and (soon) 2013, and copies of my “CoCoFest Chronicles” book covering 1990-1997 are also still available.

And now… My trip report.

TIMELINE

It has been just two weeks since the Boston marathon explosion and the fertilizer explosion in West, Texas. Local gas price is about $3.30/gallon. We have recently learned a new word: Sequester. It apparently means “flight delays.”

THE DRIVE

My last trip was to the 2010 event, and for that one I didn’t arrive until after the event closed on Saturday. This year, I had planned to try to head out early on Friday and be there for the Friday evening socilaizing. Unfortunately, I was broker than broke, and the only way I could justify the trip was by having something to sell to generate income. This year, it would be a new DVD of footage from the 2010 CoCoFEST! I got started a bit too late on them, and had to spend all Friday evening finishing up and burning discs instead of driving to Illinois.

At 4am Saturday morning, my alarm went off and I prepared to head out… I did this hitting snooze a few times. Eventually, just before 5 a.m., I was in the car and programming the GPS to take me to the Fairfield Inn and Suites in Lombard, Illinois. This was a new location for the event, so I was trusting that the GPS could get me there. (Anyone remember when we did this with paper maps?) Initially, the route looked a bit longer than I remembered. It seemed both Apple Maps and Google Maps had suggested a different route than what my car suggested. I soon realized I had disabled “Allow Toll Roads” and thus my car was going to take me 27 miles further to avoid them. (See past CoCoFest reports for details on my loathing of toll roads.)

Since I wanted to try to get there by opening, and preferred using the route I was more familiar with, I enabled toll roads and recalculated. The new route looked a bit easier. I would be departing from Des Moines, Iowa, and driving on I-80 until Davenport, then heading up I-88 for awhile and then making a short drive up I-355 to the hotel exit. This route took me through several toll booths, and cost me about $11. I suppose I would have to sell a few extra DVDs to cover that.

My trip there was uneventful. I only stopped once for gas and some McFood, so I have no fun stories to tell about “sewage lagoons” or visiting “The World’s Largest Truck Stop”. (See past CoCoFest reports for details on those.) This time, the most exciting thing I noticed was passing a large factory labeled “NESTLE.” It made me hungry for chocolate, which seemed appropriate heading to a “CoCo” fest. (Apparently I have typed that enough that my iPad is no longer trying to correct the spelling.)

THE ARRIVAL

Five and a half hours later, I was taking the exit next to the Fairfield Inn and Suites. It was about 10:15, so I knew the Fest had already opened. I drove past the hotel’s main entrance so I could drive around it and try to find where everyone was parking near the conference room. I found myself driving to far and ended up beside a separate multi-story building behind the hotel. I realized my mistake and circled back. After not seeing anything that looked like CoCoFEST! activity outside the hotel, I parked near the main entrance and went in.

I walked inside, camcorder running (to record “virtual CoCoFEST!” footage), and saw a breakfast area in front of me, and two hallways leading to guest rooms. There didn’t seem to be any signage or indication of a CoCoFEST! or even a conference room. The front desk clerk was on the phone, so I walked over to the corridors to see if I could find any signs pointing the way. Nothing. Had I misread the location?

The clerk finished his phone call then came out to find me, asking if I needed help finding something. I asked where the conference room was, and he said they were in the next building — the one I initially accidentally drove around. I went back to my car and drove over to park by that building. There was no obvious sign of CoCoFEST! activity, unlike pulling in to previous event locations where you could always identify many out of state license plates and often seen familiar faces roaming about.

Sign in the lobby.
Sign in the lobby.

Camcorder in hand, I went in to the building. It was a large, multi story office building with a revolving door. The entrance area was large and tall. I noticed immediately a standing sign that read: CoCoFEST! I felt confident it was indeed somewhere in this building.

A note on the sign said “All conference rooms in the lower level” so I headed to the elevators and took it down to “LL”.

Sign in the lower level. Notice the QR codes!
Sign in the lower level. Notice the QR codes!

When the doors opened, I saw clear glass walls in front of me with tables and a cafe type area behind them, and a nice CoCoFEST! sign on an easel. The sign even had a QR code on it, which was the first time I’d see one used in conjunction with the CoCo.

I stepped out and looked around. Down to the left was a doorway, and through it I saw Bob “Gator” Swoger sitting at a table behind a laptop.

First look at the CoCoFEST! room. It's tiny!
First look at the CoCoFEST! room. It’s tiny!

I walked over and peered in to the room. It was tiny. There were only a dozen or so tables along the walls, plus a few rows of tables in the center loaded with random stuff that I assumed was intended for the No Minimum Bid Auction. Very few people where there. I was glad to be back after three years, but had a bad feeling about the tiny size of this event. Had things really dwindled this much in the past two years?

Bob was having problems with the computer, and wasn’t ready to take money and issue name badges yet. I walked around the room and said hello to various folks, and saw where John Strong’s StrongWare table was. Next to it was another table with a banner that said “Sub-Etha Softwear”. It had the original “Software” lettering, with letters “ear” stuck on top of the original “are.” This was from the years after I joined Microware in 1995 and had to stop vending OS-9 products to avoid a conflict of interest and instead sold buttons and T-shirts. I contemplated pulling off the “ear” letters, but in usual fashion, I got distracted and never got around to it.

I was a bit surprised to see a table with my former company name on it. I wasn’t even sure until Friday night if I would even be able to attend, since I knew if I didn’t get the DVDs made to sell I would have no way to pay for the trip. John Strong had offered to let me share his table, but since Glenside was only charging $10 for a booth, they set aside a table for me just in case. I love those guys! I found out later that I was originally going to be one table down, but John had Karl Sefcik swap a space so I could be in the middle.

As I continued to look around, I was still shocked at how sad and small the event looked compared to when I saw it last in 2010. I was also puzzled that I didn’t see certain familiar faces that I knew were expected to be there, such as Cloud-9 and HawkSoft. Brother Jeremy wasn’t there, either, but I later found out he was now living in England. At the time, though, it just seemed like it truly was going to be the “last” CoCoFEST!

While I was exploring, I heard Brian Shubring (the “Music Man”) fire up his MIDI player to play the anthems that normally indicate the official opening of the Fest. Perhaps Bob Swoger wasn’t the only one having delays? It looked like I hadn’t actually missed the official opening after all! When the music finished, I then heard club president Tony Podraza making a loud announcement. For some reason, he wasn’t making them in the CoCoFEST! room. That’s weird. I wondered where he was and who he was speaking to.

A look in to the second (larger) CoCoFEST! room.
A look in to the second (larger) CoCoFEST! room.

The small room I was in had a door on each end of one wall. I had entered through one door, nearest the elevators, but there was another open door at the other end. I had noticed some CoCo folks coming in through it but just thought they were using it because it was closer to their booth. When I walked over to locate Tony, I saw a small group standing out in the hallway at another door leading in to a SECOND room. Over their shoulders I could see more vendor booths… It seems there were actually two rooms being used for this year’s CoCoFEST! They sat at a 90 degree angle at the corner of the lower level of this conference center. Whew! It wasn’t such a small fest after all!

I walked in to this other room, and suddenly felt much better. I noticed tables for Cloud-9 and HawkSoft. I saw John Mark Mobley’s large backdrop display. I saw plenty of monitors displaying real (or possibly virtual) green CoCo screens. And, most importantly, I saw folks there I didn’t recognize. New faces at a CoCoFEST!? Each year, it always seems there are a few, so I was relieved to see this pattern continue.

I took a few photos and shot a bit of video, then went back out to the car to start bringing in items. In addition to my Fest DVDs, I brought some leftover CoCo buttons I had made over a decade ago and my scrolling LED sign. Five minutes later, I was back in the room, setting things up at the table. John Strong was not here yet, but others confirmed they had seen him.

Sub-Etha Software table.
Sub-Etha Software table.

I began the task of trying to remember how to program my old BETA-BRITE LED sign. I had not really used it since around 2000 at the last PennFest, and I hoped the remote control batteries would still work. It was quite a struggle to remember how to enter in the color and scroll sequences, but I managed to at least get a few simple messages going. While I was fussing with it, John Strong made his appearance. It was good to see him again.

StrongWare had two new CDs to sell, each with various John Strong games on them including a brand new one. I worked on putting in descriptions and prices in the LED sign while he unloaded. He left to get more, and soon returned with a huge ink jet printer which he would be using to print out the CoCoFEST! group photos he started doing back in 2009.

Although our tables were pretty bare, I at least had a whole set of old CoCo buttons on the table, and a small stack of DVDs. I used my old MacBook to start playing the DVD, and then decided it was time to walk around a bit and see what this year’s event had to offer. And maybe find a restroom.

THE SHOW AREA

2013 Chicago:

The festival occupied all three conference rooms (A, B and C) of the Heron Point Conference Center and made use of the cafe area for seminars. The smaller entrance room was Heron Point C (1760 square feet), and the larger room was made up of the A and B rooms without the dividing wall between them (2160 square feet). The claimed maximum capacity (when configured in a theater layout with just seating) is 190 people, so there should be plenty of room to grow in this location.

You can visit http://www.marriott.com/hotels/event-planning/floor-plans/chifs-fairfield-inn-and-suites-chicago-lombard to see the hotel’s actual floor plan of the rooms that were used.

No show guide was provided, but I was able to obtain the hand drawn layout map from Linda Podraza. This may very well be the most accurate fest map I have ever included. Except for the items that were changed after the map was drawn.

First Room (C):

+--------------------------------------------------------------------+
| Glenside     David    Bargeman  Sub-Etha  StrongWare               |
| IDE          Keil     Research  Software                           |
|                                                           Dan      |
|                                                           Keller   |
|                                                                    |
| G                                                                  |
| C                                                         O'Keefe  |
| C            Mike     John-Mark LogiCall  Music Man                |
| C            Rowen    Mobley                                       |
+--------|   |-----------------------------------------|   |---------+

Second Room (A & B, combined):

+--------------------------------------------------------------------+
| CoCoBoot   Pitre    Cloud-9  Drivewire Crislip   HAWKSoft GriffArt |
|            Tech              CC Coding                             |
|                                                           Nosko’s  |
| Chasteen                                                  Laser    |
|                                                                    |
| Frank                                                     John     |
| Pittel                                                    Linville |
|                   Lost                Mark                         |
|                   Wizard    Weller    Martin                       |
+--------|   |-----------------------------------------|   |---------+

Andrew Weiler – information needed.

Bargeman Research Labs – Karl Sefcik was once again there with a booth to help support the festival. His table always has a bizarre grouping of seemingly random items. He donated a T1 phone system of some kind to the auction this year, for example. One of the key things Karl did during the show was monitor the CoCo IRC chat room and let us know when folks were having issues with the live video stream. Towards the end of the event, he would also help the club sell a few more souvenir writing pens by taking orders and getting the pens from Glenside. Very useful, Karl!

Cloud-9 booth.
Cloud-9 booth.

Cloud-9 – (video interview) Mark Marlette was showing off their new TRIAD 512K memory upgrade for the CoCo 3. It was a tiny blue circuit board with only three small chips on it. It used SRAM and they claim it has a 90% heat reduction over old style memory upgrades. It also uses only 1/20th of the power, which means a CoCo 3 using it should run cooler and use less power than even a stock 128K model. Mark referred to it as “the CoCo finally goes green.” At $50, it seemed quite reasonable and I expect to upgrade my CoCo 3 as soon as I get it set up again. Little things like this might just let us get a few more years out of the old hardware.

Mark also had various development boards on the table that were variations of things like Arduinos. He showed ones that was being used to develop a new SD card interface, and one that had USB support. All of these projects are for components of the long-awaited SuperBoard upgrade. For those who weren’t following this over a decade ago when the idea first came up, the SuperBoard is an “everything” upgrade that would plug in to the CoCo 3 where the memory upgrade and 6809 go. The 6809 would have to be replaced with a socket to do this, but beyond that it would be a plug-and-go upgrade.

SuperBoard has evolved considerably since the original design, and when it is released, it should bring us hard drive replacements, USB support, networking and other much desired options. Various products that Cloud-9 has sold over the past years were created to test concepts destined for the SuperBoard. As one who place a down payment on the SuperBoard years ago, I look forward to it finally shipping.

Other items of interest on the table included an updated PC keyboard adapter, and some miscellaneous used items for sale including a Telepak II. I remember seeing this RS232 pak replacement being advertised in The Rainbow magazine, but I do not know if I ever saw one in person until this event.

CoCoBoot Project – information needed.

CoCo Coding booth. Furious Felines on the left, and Pitman on the right.
CoCo Coding booth. Furious Felines on the left, and Pitman on the right.

The 2013 CoCo Coding Contest – Aaron Wolfe (“the DriveWire guy”) was hosting the 2013 Color Computer Coding Contest. At his table, you could see various entries running and try them out. I checked out Furious Felines for the CoCo 3, and some semigraphics game called Pitman. They really brought back some memories to the early days of 8-bit computing. Strangely enough, I found the semigraphics game to be very compelling in spite of the limited display. It seems the past few years of “casual gaming” have brought out much interest in simple games (wrapped in fancy high tech graphics) that they may actually have more appeal now than they would have thirty years ago. Certainly, Pitman was every bit as enjoyable as any modern “casual” game. I look forward to exploring the www.cococoding.com website to see what the other entries were. (Check out that site, and follow the links. One entrant, Jim Gerrie, has created dozens of recreations of classic games for the CoCo and MC-10. Ever see Joust on a VGD screen? Neat!) Full details on the winning entries can be found in the Seminars section of this report.

CoCo LogiCall V7.0 Operating System – For years, I have been aware of a product called LogiCall that Bob Swoger created for a Timex Sinclair computer. Recently, he brought out a Color Computer 3 version of the system. Even though it has been around at least since 2010 (I forget which festival it was announced), this was the first time I actually learned what all it did. See the Seminars section of this report for a longer description.

Dan Keller – (video interview) Dan is a fan of vintage computer (he was wearing a Commodore 64 shirt and an Atari 2600 hat) as well as a professional cartoonist. For the show, he created an original piece of artwork which was a Tandy/Radio Shack themed “My Little Ponies: Friendship is Magic” character. (See Auction for more details.) He had a variety of different prints and original pieces of art, including a really nice TRON Legacy inspired piece which would have come home with me had I any money to spend at this event.

To learn more about his artwork, visit http://www.projectdestinystudios.com.

Dave Keil’s CoCo Emulators – David Keil has a well-known TRS-80 Computer Emulator page on the Internet. He has created emulators for the TRS-80 Model 1, Model III/4, and CoCo 1/2/3. His CoCo emulator was one of the first ones available, though it hasn’t had much development since 2001. David returned to the CoCoFEST! this year to give a seminar about how you can use the free VirtualBox software to run old DOS style apps, like his emulator, on modern Windows systems. He also had a variety of used CoCo items to sell, including a CoCo 3, Orchestra-90 paks, Speech paks and CoCo service manuals. At his table, he had both the original green CoCo 1 service manual, and an updated version that was in the white book binding like the later CoCo 2 and 3 manuals were. I had never seen one like that before and had no idea Radio Shack updated the format. I took a number of items off his hands, via proxy sales to folks unable to attend the show in person.

Frank Pittel – information needed.

Glenside's IDE project booth.
Glenside’s IDE project booth.

Glenside MARK II IDE Interface – (video interview) Brian Goers was the man in charge of arranging the seminars this year, as well as staffing the IDE project booth. Through the years, Glenside has created a few hardware projects, including a short run of MIDI interfaces. This IDE project was an early effort to bring IDE hard drives to the CoCo. They still have about 70 of the boards left, and were selling them off for less than $10 each since today IDE is pretty much obsolete (but you can still use the IDE interface and go to memory cards).

The IDE board is huge. It was designed with a few unique features that go beyond just being a hard drive interface. Brian showed me where they had two places on the board to solder in CoCo edge connectors and use it as a Y-cable. You could plug in the interface, as well as a floppy controller and RS232 pak, for example. There were also a few spots where you could add extra components if you wanted to wire up a realtime clock or other hardware. Someone at the Fest suggested I take a look at them, since it was a cheap way to get a CoCo prototype board to experiment with, even if there was no need or desire to use it as an IDE hard drive interface.

These boards have quite a bit of history behind them. In 2009 I interviewed Jim Hathaway who showed one of the first prototypes. Over the years, various folks were involved with revising the hardware and software. It would be nice someday to read a full write up of the history of the project and learn who all did what. Today it seems we just see it as “that booth in the corner at the CoCoFEST!” Perhaps someone will read about it in this report and find a new use for those boards. I am sure they’d love to sell you a few.

Glenside Color Computer Club – The host club for the event, Glenside, always has a table at the entrance for selling fest admissions and issuing name badges. This year, they ran out of the plastic badge inserts and only had some leftover smaller ones. We all took different approaches as trying to affix our larger badges to them, such as folding or trimming them down.

Griff-Art – Linda Podraza, Tony’s wife, often sets up a table with various craft/art projects she has been working on. She did not set up her table this year, though.

HAWKSoft – Chris and Nancy Hawks have been vending since the days of Rainbofests. Chris was still there with his doctor’s robe full of past fest name badges and, like usual, he had something new to demonstrate. In past years, he has shown off things like a CoCo 3 SVHS adapter, that allowed you to hook a CoCo to a modern TV and get much higher quality than using the composite outputs.

Chris Hawks of HAWKSoft and his virtual CoCo 3 running on Raspberry Pi.
Chris Hawks of HAWKSoft and his virtual CoCo 3 running on Raspberry Pi.

This year, he was showing off one of the most unique PC keyboards ever made. Chris has taken a CoCo 3 case, and removed the internals. Inside was a small microcontroller (a Teensy 2.0 from prjc.com). It was connected to the keyboard connector, and he wrote code to translate the keyboard matrix in to USB keyboard commands. This keyboard could then be plugged in to a PC, Mac or Linux machine for those who wanted to have a “real” CoCo keyboard when using an emulator. At the show, Chris had it plugged up to a tiny Raspberry Pi computer ($25-$35) and he demonstrated booting up to a Linux shell prompt, then typing (on the CoCo keyboard) commands to load and run the M.E.S.S. emulator. In a moment, he had a full virtual CoCo 3 running The Sands of Egypt. If you had just walked by, you would have no idea that the CoCo 3 he was typing on wasn’t really a CoCo.

Amusingly, to me, where the cartridge port for the CoCo was, Chris has mounted a small USB hub. Just like many modern USB keyboards, this USB CoCo keyboard also had a built in hub. Chris said his next goal was to mount the tiny Raspberry Pi inside the case so you could have a self-contained Virtual CoCo. It was really neat. Years ago, there was a CoCo 4 project that intended to do something like this, but at the time, the costs would have been much higher. The Raspberry Pi really does change things.

Chris has plans to make his work freely available for those who wish to recreate it, but it didn’t sound like he was going to make it a product.

His wife, Nancy, also had a pretty display set up with some of her creations. I did not get to speak with her much this year, so I will describe what she had in an uneducated way: something that looked like it was patterns sewn on cloth that probably took a ton of time to do.

He can stop buying floppy drives any time he wants.
He can stop buying floppy drives any time he wants.

Jim O’Keefe (aka, “Jose Jiminez”) – (video interview) Jim claims to have an addiction to floppy drives and controllers, so he rented a table to hold his collection. He was seen with a stack of about eight floppy drives piled on his table. He had brought various bits of his gear to test the equipment out. He originally got in to the CoCo around 1982, and had attended the first three or four Chicago Rainbowfests. By the time the CoCo 3 came out, he had moved on to the PC but, a few years ago, some Internet searches made him nostalgic and he started getting back in to the CoCo.

He shared an amusing story about how he crashed the vendor party after the original Rainbowfest, and then mentioned how he and a friend (Chuck Yeager, but not the test pilot one) had a CoCo software called Zytech Limited. They wrote an adventure game called Plateau of the Past, which was a graphical adventure that did not require a bunch of typing. Jim had a great sense of humor and was great fun to chat with. I hope to see him at a festival again.

John Linville – (video interview) John has been contributing some interesting projects lately. This year, he had four different projects lined up on his table.The first was the CoCo 3 video player which he originally demonstrated in 2010. The player displays 2, 4, 16 or 256 colors, and works in any CoCo 3 resolution. He had a video loop demonstrating these difference modes. For some reason, the 4-color mode really reminded me of Commodore 64 graphics. The 256-color mode works only on a composite monitor and uses various tricks to simulate that many colors. He would convert standard videos over to a specialized format, then they would be loaded on a CoCo format compact flash card in the Cloud-9 SuperIDE interface. His player would boot up via HDB-DOS, then play as much video and sound as could fit on the card. He previously showed off complete movies being played on the CoCo. The quality, of course, is very low compared to modern technology, but to me it reminded me of what all web video looked like back around 1995. A very impressive feat for an 8-bit processor designed in the 1970s running on a computer from 1986!

Some of John Linville's demos.
Some of John Linville’s demos.

His next demo was Follow Me, a Simon style game. I believe he said he wrote it as a way to use a special paddle type controller he built. He referred to it as a rotary controller, so I assume it was a spinner that rotated 360 degrees instead of a traditional paddle that would stop at each end. It looked like the standard black CoCo joystick base (with the single red button), but there was a spinner controller where the joystick would have been. The game was entered in the RetroChallenge game this past winter, and it was shown playing on a Tano Dragon CoCo clone.

Third was his game Fahrfall (alpha test 3). This was an experiment in using the colorful semi-graphics modes on the CoCo 1/2 machines (64×96 with 9 colors). The game would not run on a CoCo 3 due to it lacking those modes. This game involved a human player that could be moved left and right, and the goal was to run off the end of platforms that slowly moved up the screen, and to land on a platform below. If you missed, the player fell off the screen. If you didn’t jump soon enough, the player would be crushed by the top of the screen. It played really well and the animation looked great considering the low resolution graphics it used. He had it playing on a custom joystick that had a nice arcade button, and a good feeling digital arcade joystick. This game was winner of the RetroChallenge Winter Warmup 2012.

His fourth demonstration was a new game called Sluzzle. It was a sliding picture puzzle game where a graphic was divided up in to blocks and you moved them around to put the picture back together. The unique aspect of this was that it used some tricks on a CoCo 1/2 to simulate 44 different colors. By changing video modes and flickering between screens, he could get very colorful photos that looked pretty good from a few feet away. It was easily the most impressive photo displayer I have ever seen on a CoCo 1/2, and being able to use this technique in a simple game was a nice bonus.

When asked what his next project was, he mentioned doing more work on the 44-color viewer to see if he could get even more colors out of it, including pure black and pure white which his current version does not do. He also mentioned that he acquired Steve Noskowitz’s laser show equipment (see Seminars), so he may be working on that for the next CoCoFEST! To learn more about his projects, visit http://vdgtricks.blogspot.com.

John & Marge Chasteen – John supports the club by having a booth and setting up his CoCo gear. It’s a great way to get help on anything CoCo-related.

John Mark Mobley and George Schneeweiss (Glenside Treasurer) check out the Raspberry Pi.
John Mark Mobley and George Schneeweiss (Glenside Treasurer) check out the Raspberry Pi.

John Mark Mobley – This year, John Mark Mobley was showing off a Raspberry Pi. His colorful tabletop display still had some CoCo screen shots and information, but some of the sections were now changed out to describe the Raspberry Pi educational computer. This $25-$35 computer was a full ARM processor Linux machine, capable of running as a “headless” Unix style system (that you could telnet in to over a network), or you could hook it up to a TV set using composite video or HDMI. It had a USB port that could be used to hook up a mouse and keyboard, and then you could run a full GUI and use it like a desktop computer.

Although still not quite an off-the-shelf home computer like the CoCo was, it took very little extra hardware (cables, case, power supply) to turn it in to something that is much closer to a cheap home computer you could hook up to a TV and start using. Instead of disk drives, it use an SD memory card. And, not only does it run tons of Linux software, it can run a CoCo emulator as well, or act as a Drivewire 4 server to a real CoCo. It’s truly an amazing achievement for such a low price.

And though I am normally puzzled by the appearance of non-CoCo hardware at a CoCoFEST!, the ability to have a Pi run a CoCo emulator does seem to make it qualify as something on topic.

Lost Wizard Enterprises – (video interview) William Astle (from Canada, eh?) had an FPGA board running Gary Becker’s virtual CoCo 3. William was showing off a Freecell game he wrote to enter in to the CoCo Coding Contest (spoiler alert: it won in the <16K category). He explained that the FPGA could run at speeds equivalent to a 25mhz 6809, but it wasn’t 100% timing accurate so certain things, like SockMaster demos, did not run properly. For all other items that didn’t require such cycle accurate timing, it ran just fine. It was hooked up to a VGA monitor and PC keyboard, and I believe it was emulating a standard Tandy floppy controller, and using an SD memory card for storage. Very neat.

The hardware was a reference platform from the company that sold the FPGA chip. This was going to be the basis of the CoCo-X project Gary Becker had recently tried to get funded via Kickstarter, but it feel short of the $50,000 goal he wanted. It seems we had a chance to get a “CoCo 4” but it wasn’t the product or price we were willing (or able) to support financially. Perhaps another attempt will be made at some point as this form of emulation was quite impressive.

Also found running at his booth was a copy of Dungeons of Daggorath. I forgot to ask him why, but perhaps this was a reference to his “Lost Wizard” name…?

Mark Martin – Mark has an Arduino project here was working on at the Fest. He used the time to do some soldering, and to make contact with other CoCoists to exchange ideas. Mark is working on one of several “hook an Arduino to a CoCo” projects right now, but his is a bit different. He seems to be shooting for a retro Cloud interface, which would let the CoCo (then, other retro computers) use cloud storage as a virtual file system. This is an interesting spin, and similar to some of the network mounting options that Roger Taylors SD pak product had and some of the capabilities in DriveWire. It really is nice to see so many potential options coming out, rather than just having one that we have to “take it or leave it”.

On e-Bay, this might be hundreds of dollars worth of gear. At the Fest, cheap!
On e-Bay, this might be hundreds of dollars worth of gear. At the Fest, cheap!

Mike Rowen – (video interview) Mike started out on a TRS-80 Model I, then moved on to a Color Computer 2 and 3 later. He was a Unix professional, so he worked with OS-9 and UUCP. Around the mid-90s, he left behind his CoCo hobby, but returned about three years ago after buying a replacement on e-Bay. At this show, he had some neat “Long live the CoCo” coffee mugs which had a screen shot of NitrOS-9 on one side and Disk Extended Color BASIC on the other, as well as a CoCo 1, CoCo 2 and CoCo 3. He promoted them as “CoCo 1, 2 and 3 compatible.” He also had a table of $5 ROM Paks, including Dino Wars. He also had some free stickers to give out, including one that said “TANDY Color Computer 8-bit POWER”.

The Music Man – (video interview) Brian Shubring once again was on hand to provide some background music and, if needed, amplification. Brian’s CoCo MIDI setup was booting via Drivewire, then running off of a small 2.5″ laptop IDE hard drive. It was quite the downsize from a huge PC tower case repack he used to use.

Nosko’s Laser Show – Along the back wall of the room was a dancing laser beam projected on the wall. What an unusual thing to see at a CoCoFEST! Steven Nosko is now a retired Motorolla employee who, at one point, built a laser show display that was controlled by an original grey case Color Computer 1. He actually visited a CoCoFEST! once before to present his show and discuss it, and it was good to meet him again. See the Seminar section for a full description.

Pitre Tech. Look for his CoCo history book coming out later this year!
Pitre Tech. Look for his CoCo history book coming out later this year!

Pitre Technology – (video interview) Boisy Pitre had his own table next to Cloud-9 where he was showing off some of his experiments. Before mentioning them, it should be noted that he is also working on what will be the definitive book on the Color Computer. It will be called “CoCo: The Colorful History of Tandy’s Underdog Computer” and is written by Boisy G. Pitre and Bill Loguidice. They expect it to be released before the end of 2013.

As far as projects, Boisy had initially attempted to use an Arduino as a micro Drivewire disk server, but he found that the timing of the Arduino was not stable enough at the high speed baud rates needed, so he looked for a different solution. He was showing off an Arduino interfaced to the CoCo cartridge port. Years ago, Boisy had purchased some old parallel port interface boards from Dennis Kistz. (Dennis is the gracious host of the MaltedMedia CoCo mailing list, and former operator of Green Mountain Micro back in the early days of the CoCo.) Boisy decided to populate these bare boards with the chips necessary to allow interfacing with the CoCo memory map. He then soldered on a header connector which could plug in to a row of I/O pins on the Arduino.

He created special software to run on the Arduino, and then modified HDB-DOS on the CoCo to talk to it. His results were very high speed Drivewire transfers to a microSD card on the Arduino. There have already been ways to use a memory card as a hard drive, such as the Cloud-9 IDE board and the SD Pak sold by Roger Taylor, but these approaches require the card to be formatted for the CoCo or OS-9. Boisy’s approach used a standard FAT formatted memory card, and interfaced between disk image files on that card. This meant you could take the microSD card out of his interface and read it in a computer or copy over new disk images. Anyone who has ever tried to mount an OS-9 formatted Compact Flash card under Windows will know how well that doesn’t work.

It was also quite fast. He was able to achieve “megaread” speeds much closer to that of hard drives, while the bitbanger serial port approach creates speeds similar to floppy drives. By using memory mapped I/O, he was able to read and write a byte of data instead of having to send out a stream of bits one at a time. This was easily one of the most exciting projects I have seen at a CoCoFEST! in years because of the potential to support other things.

Boisy pointed out that the Ethernet port on the Arduino he was using could be used as well, and code could be written to allow this interface to act as a hard drive, internet gateway, virtual serial port or just about anything else that could be plugged up. By making it work over the Drivewire protocol, only one base device driver would be needed. Instead of requesting a disk sector, it could read or write serial data to a com port or internet socket. Current Drivewire already offer this, but it requires having a full computer booted and physically connected to the CoCo. A micro server like Boisy was working on could be designed to live in a ROM pak case, and free the CoCo from any dependencies on another machine. This was an exciting idea. Even with small Linux servers like the Raspberry Pi being available, they still have the complexity of a full operating system to maintain and update and are certainly not “plug and go” like a custom designed interface could be.

If you want to learn more about this, Boisy has documented his experiments online. See http://arduinococo.blogspot.com for more details.

Darren Atkinson's CoCo SDC prototype, and Boisy's CoCo Arduino experiment.
Darren Atkinson’s CoCo SDC prototype, and Boisy’s CoCo Arduino experiment.

One other item Boisy was showing was a “CoCo SDC”, a special FD502 disk drive controller pak that contained a custom circuit board which emulated the original Western Digital FD1773 chip. This project was by Darren Atkinson, and his goal was to create something that looked just like a standard floppy controller to the CoCo, but used SD cards for storage instead of a physical floppy drive.

The circuit board inside looked very professional, and enough work had been completed to allow it to be demonstrated. Boisy had a printout of instructions, and although the CoCo powered up with the standard “DISK EXTENDED COLOR BASIC” copyright notice, he quickly showed us that it was not the same DISK BASIC. There was a minor extension that had been added that let the user specify which disk image on the SD card could be loaded as a virtual floppy. Boisy typed something like:

DRIVE 0,”DK.DSK”

This told the virtual drive controller to map in an emulator disk image called “DK.DSK” as drive 0. Then, typing “DIR” (or “DIR 0”) would show the contents of that disk image as if it were reading sectors from a physical floppy disk. Except much faster! Since there was no waiting on floppy disks to spin up and seek, access time was noticeably faster than a real floppy. The CoCo ROM disk code was completely unaware that it was not talking to a real floppy drive. This would, by theory, allow programs that used their own custom disk routines to work, while they cannot work in an RGB-DOS or Drivewire environment.

Some examples of this include games like Marty’s Nightmare and Z’89 by Steve Bjork. One of these games used 512-byte sectors, and the other 1024-byte sectors. These nonstandard sector sizes, as well as missing track 0 and other tricks, acted as copy protection and prevented them from running from hard drive systems. Darren’s interface supported a .DMK format which could replicate these nonstandard disk images, allowing copy protected software to run from SD. (In an e-mail, Darren explained that Carl England’s Defeater program was used to clone the protected disks in to disk images that contained the same track and sector format.)

Another problem usually encountered when moving RS-DOS software to a hard drive is that many will only run from Drive 0 (or Drive 0-3, without being patched). This virtual drive controller gets around all of these issues, allowing virtual disks to be swapped out on demand and appear as DRIVE 0 for full compatibility.

It’s certainly a fascinating project, and I hope it comes out as something I can buy. With all these options – SuperIDE memory cards, Drivewire to a PC or micro server, emulated floppy controllers – we have some wonderful choices.

Richard & Brenda Crislip – information needed.

Roy R Justus – Roy was scheduled to attend, but did not make it. I was disappointed in this, since in the past, I have seen recreated “program by switch” computers, walking hexapod robots and various other creations of his. He’s also the guy who created and sold a CoCo 3 to VGA adapter, so his contributions to the show were missed.

John Strong shows off his new game Gems 2 running in a CoCo emulator.
John Strong shows off his new game Gems 2 running in a CoCo emulator.

StrongWare – (video interview) John Strong was offering two CDs of his game products. One disc was the StrongWare Collection, and it featured his Tetris clone, Soviet Bloc, as well as his Columns clone, Gems. There was also a Simon Says type game on it. The disc was sold for $20, I think, and it contained emulator-ready files. Soviet Bloc and Gems were possibly the only CoCo games ever made that supported stereo sound via an Orchestra-90 pak.

His second CD contained his new game, Gems 2. Gems 2 was a different type of puzzle game, where the screen was full of colored gems and you had to click on a certain color to make all the connected gems of the same color disappear. (Bubble Bobble?) John explained that the game allowed a replay of the level using the same pattern so you could try again to beat it. It also had an undo and redo feature, so you could take back moves in case you got stuck.

John also showed off some of his Windows-based development tools he has been creating. He had a tile editor, for games, as well as a sprite editor. He also had a disk utility that could do the typical things like move files in and out of an emulator disk image, but also view and edit some of those files. He could directly view VEF graphics files as well as some of the RS-DOS formats. His work-in-progress program even allowed editing the disk images with a hex editor, or manipulating graphics files (like a Paint program). He also secretly showed a huge level map for an unannounced game project he has been working on. If he can sell some copies of Gems 2, he says he will continue development on the new product. If there is no interest or support, he may be done developing CoCo items.

Sub-Etha Software – At my table, I was selling two CoCoFEST! DVDs. One was a compilation of interviews and footage from the 2009 Chicago CoCoFEST!. This disc included interviews with various vendors and some attendees, including the late Howard Luckey, as well as footage from the No Minimum Bid auction and Saturday Night Music Jam. The disc served as a companion to my 2009 trip report, which was included on the disc along with all the digital photos I took that year. Since I had previously sold this disc at the 2010 show, I had only brought four copies to sell, and I sold them all.

I also had a brand new 2010 DVD which had much more footage on it, but no room for any bonus content like photos. This disc featured 19 interviews, and a CoCo demo seminar by John Mark Mobley. It also had Tony Podraza speaking about the passing of Howard Luckey. (The 2010 event was dedicated to his memory, and his photo had appeared on the name badges that year.) Sadly, that was not the only tribute on this DVD. Malcolm Cleveland had also passed away, so I included an interview with him as well as a short extra clip “In memory of Malcolm Cleveland.” He was always a pleasure to talk to, and will be missed. I really hope my 2013 DVD doesn’t have another tribute to another CoCo family member we lose.

The last item I had was a new printing of my CoCoFEST Chronicles book, which contains versions of my trip reports from 1990-1997. The book was made available in 1998 in a spiral bound format, and I was showing off a new paperback edition that had the same type of binding that a normal book had. Technology has made it possible to print something like this at a lower cost, so if there is interest, I will create a new edition to offer.

THE SEMINARS

Early festivals I attended had separate seminar rooms for the presentations. As attendance numbers dropped, during a seminar the festival room would empty out leaving the merchants all alone and lonely. Glenside decided to save some money on the seminar room rental (which also helped compensate for the drop in income from fewer tickets sold) and have the seminars in the actual fest room. Over the years, various approaches were taken, such as having an area near the center of the room be set aside with seating, or just having folks move chairs from one place to the other when a merchant gave a talk.

This year, things reverted back to how they used to be. The conference center not only had the two rooms being used by the fest, but a central cafe area with chairs and tables (and water and coffee). The cafe area was being used for seminars this year, but since it was just a few steps away from the doors of the fest rooms, and since it had glass walls, I suppose the merchants didn’t feel quite as lonely during the seminars.

Saturday:

12:15 – “CoCo Laser Show” – Steven Noskowitz

Steve Noskowitz explains how he built his CoCo 1 based laser show. This was one of my all-time favorite presentations.
Steve Noskowitz explains how he built his CoCo 1 based laser show. This was one of my all-time favorite presentations.

In 2002, Steve showed up at a CoCoFEST! and gave a talk on his CoCo 1 based laser show. He was back again and demonstrated the original show, as well as some other things he had created over the years. He showed us star constellations (done for a Boy Scout troop, I think he said?), some space shuttle landing animations, and a spinning Motorola logo, among other things. He spent some time demonstrating how the laser worked to create the shapes we saw on the screen. He would change speed settings to show how some dots could appear as a line, or a square, or, if going too fast, a square with rounded corners. It was fascinating to see how new this “old” equipment worked. It still seemed quite impressive, decades after he designed and built it.

One change I noticed from his original presentation was that he was now playing audio from a PC laptop instead of a cassette tape drive.

1:15 – “LogiCall” – Bob Swoger

Bob Swoger demonstrates LogiCall.
Bob Swoger demonstrates LogiCall.

Bob gave a walkthru demonstration of his LogiCall disk operating system environment for the CoCo 3. This program originally started out for a Sinclair computer, but has been ported over in recent years. Read more about it in the CoCo Coding Contest section.

2:00 – “No Minimum Bid Auction” – Tony Podraza

The annual tradition of “selling all kinds of good stuff and junk to help Glenside pay for the Fest” was held once again. This year, there seemed to be much more “good stuff” than “junk” with things like CoCo 3s and 2 meg memory boards coming up for auction. Tony was also in great form this year and zipping around enough to make me feel tired watching him.

5:00 – “Group Photo” – John Strong

John Strong's CoCo Family Portrait.
John Strong’s CoCo Family Portrait.

While not a seminar, this was a loosely scheduled event so I will mention it here. John Strong gathered everyone together outside in front of the conference center for a group photo. The outdoor location was chosen, I assume, to avoid having to set up all the umbrella lights this year. Using a timer, John was able to be in the picture too. I was in the one he did in 2009, but I showed up too late in 2010 to be in that one. I do not know if he has taken similar group photos at the shows I missed (2011 and 2012).

If you were at the festival and would like to get a print of this photo, contact John Strong. A huge print of this photo was then offered on Sunday in the No Minimum Bid Auction. John tried to get as many of us as possible to autograph it around our picture. Sadly, I think this huge print only went for $10. I was really surprised it wasn’t bid up higher to help the club out.

Sunday:

11:00 – “VirtualBox” – David Keil

I was not present during this seminar, but it was meant to be a discussion of using the free open-source VirtualBox emulator to run older programs on a modern Windows computer. David Keil wrote one of the early Color Computer emulators, but I gather it does not run properly on a modern 64-bit Windows system. VirtualBox can be used to run older applications inside a virtual machine on a modern PC.

12:30 – “CoCo Coding Contest” – Aaron Wolfe

Aaron Wolfe's CoCo Coding Contest winner announcement.
Aaron Wolfe’s CoCo Coding Contest winner announcement.

Earlier this year, a Color Computer programming contest was announced. A website, www.cococoding.com, kept track of entries. There were 18 entries in various categories, and you could see them run at Aaron’s booth. Aaron set up two flat screen monitors displaying a slide presentation as he described each winning entry. I would have liked to have seen a demo of each program so I could understand more about it, but I suppose that is what his booth was for with the demos running.

The winners were announced (and live streamed on the internet) during this seminar:

Best Puzzle Game – Pipe Frenzy by Jim and Charlie Gerrie. This game required the player to lay down pipe pieces so the water could flow without spilling. My understanding is that this is a father and son team. They have cranked our dozens of games (including a Doctor Who one!) with many being compatible with the MC-10. The games make use of the original CoCo (and MC-10) semi-graphics displays but, while primitive to look at, they have some excellent game play.

Audio Category Winner – Sound Chaser by Bill Piece. This OS-9 program is designed to be the “one player to rule them all” for the CoCo.

Most Talked About Program – LogiCall by Bob Swoger, Chris Hawks and John Mark Mobley. For more details on LogiCall, see the seminar presentation.

Best BASIC Game – Scrabble Helper by Jim and Charlie Gerrie. This program includes a dictionary and helps find places to put those letter tiles and make points with them.

Best BASIC Utility – SideKick by Luis Antonoisi. This was a menu driven interface to all the virtual floppy dries on an RGB-DOS/HDB-DOS hard drive system.

CoCoFEST! Fan Favorite / Nick Marentez Choice Award – Furious Felines by Diego Barizo. This CoCo 3 graphical game involved stacking cats to a certain height, then choosing a top on that would jump off and land on a spring board which would launch it across the screen, hopefully to catch a mouse. The wind needed to be taken in to consideration, and more and more obstacles popped up as the game progressed. I see it as similar to the old cannon firing games from the original home computers, but it’s obviously meant to inspire thoughts of Angry Birds (which, when I first saw it, I thought was similar to the old cannon firing games from the original home computers)…

Best Arcade Game – Unatron by Kevin Dowd. My understanding is that this was a game engine/iibrary that was originally being written back around 1982.

Most Creative Game – Tunnel Jumper by Jim and Charlie Gerrie

<16K Efficiency Award – FreeCell by William Astle. The game made famous by Windows, now on the CoCo.

Best New OS-9 Utility – Minted by Luis Antoniosi. A simple full screen text editor for OS-9.

Perfect Attendance Award (or Good Job Award, or…) – Sluzzle by John Linville. It was some CoCo 1/2 thing where you made big squares move or something.

Best of Show – DW4MAN by Bill Pierce. A front end for managing Drivewire under OS-9, which allowed it to control the remote DW server rather than having to do everything from the host PC/Mac/Linux box running it.

I only saw a few of these running, so I really don’t know what all they did. I look forward to exploring them.

It should be noted that some sort of inside joke popped up during this seminar, with John Linville’s 44-color CoCo 1/2 game basically being shrugged off. This seemed to escalate for the rest of the show, so I hope John has had some time to recover.

NEW STUFF

In 2009, I added a section to my fest report called “New Stuff” and mention that, earlier on, such a section would have been pointless since CoCoFests were all about new stuff. Today, though, new development is much rarer and thus I want to call special attention to the new things observed at this event. Some of these items may just be “new to me” since I missed the last two shows.

John Linville was showing off the demos he had created since 2010, such as the 44 color picture viewer on a CoCo 2.

Cloud-9 stuff (TRIAD memory upgrade, lower left).
Cloud-9 stuff (TRIAD memory upgrade, lower left).

Cloud-9 had the new TRIAD 512K memory board, which uses 1/20th of the power and runs 90% cooler.

Aaron Wolfe was showing off the CoCo Coding Contest entries, including some very challenging games written in BASIC. Furious Felines was a take on Angry Birds.

Boisy Pitre had his Arduino/CoCo experiment board, including HDB-DOS support, as well as Darren Atkinson’s virtual CoCo floppy drive controller prototype.

HAWKSoft had a Raspberry Pi running a CoCo 3 emulator, and a real CoCo case/keyboard hooked up to it for a genuine CoCo experience (other than taking much longer to start up compared to a real CoCo).

OTHER STUFF

The No Minimum Bid Auctions have become a staple at these events, and are often the only thing that makes Glenside enouhg money to allow doing another festival.

The auction this year was a bit different. Tony Podraza hosted it, without need of a microphone (it was in the smaller room), and there was quite a bit of “good” hardware in the mix, and far less of the odd junky tidbits. But of course, there was plenty of that — auctioning off a plastic storage bin, for example. There were also tubs of floppy disks, cables and various other tidbits. During the auction, Tony would quiz us and the first responder would receive a Glenside CoCoFEST! pen. Very nice. (We also got one just for buying a ticket… And they are the nice kind, too. The clicky type.)

A number of CoCos came up for auction, with Cloud-9’s Mark Marlette taking a look at each of them, and buying many. What surprised me was the number of CoCo 2s that got bit on to some really nice prices. Boisy Pitre, specifically, seemed to be looking for a particular model. He bid and one a rather unusual CoCo 2 which had an original chicklet keyboard on it — from the CoCo 1. I assume someone must have swapped out keyboards at some point — perhaps putting the better (?) CoCo 2 keyboard in an old CoCo 1? I didn’t even realize they would fit across models.

There were many Orchestra-90 paks, still in the box, and various RS232 paks and even some MIDI paks that came up. In fact, this year there seemed to be much more “CoCo” stuff in the mix than usual. The large table full of items was rather sparse by the end of the Sunday auction. I hope it raised some good money for Glenside.

Dan Keller's TRS-80 CoCo themed "My Little Ponies" tribute art.
Dan Keller’s TRS-80 CoCo themed “My Little Ponies” tribute art.

There were also a few donations to the auction. An artist, Dan Keller, created a custom piece for the club — a “My Little Ponies” drawing themed to the Tandy/Radio Shack Color Computer (featuring the red/green/blue stripes found on the computer label). For those unaware, My Little Ponies was originally an 80s TV series designed to sell toys to young girls. It was brought back at some point in recent years and updated to be a show that adults could enjoy as well. It has become something of a phenomenon on the internet with adults, including men (certainly not the original “young girls” target audience of the original). I, myself, know several guys that admit to loving the show. I am going to have to check it out.

John Strong also donated a large color group photo that had been signed by many of the attendees. Sadly, it only went for $10. I expect it cost John more than that in ink.

Overall, this was a very interesting auction.

THE AUCTION BY PROXY EXPERIMENT

Some of the stuff purchased by proxy during the auction.
Some of the stuff purchased by proxy during the auction.

At the request of those watching the live stream from home, Aaron brought his webcam laptop in from the other room and I put it on a tripod and tried to point it around the best I could so folks at home could see what was coming up. Someone on the chat room asked about something, and I offered to bid for them if they could PayPal me the money. This ended up with me taking home a tub of items — RS232 paks, Orchestra-90 paks, a Speech/Sound pak, a CoCo 3, two MultiPaks, some MIDI stuff, a copy of A-DOS 3, and even a Modem Pak. I would spend the next few evenings at home packing and shipping the items out — certainly alot of work, but if generated some extra money for the club and I was happy to do it. Some of the items I shipped were purchased from vendors directly, and it seems to have contributed about $250-$300 to the show.

If we try something like this again next year, perhaps we can figure out how to make it work better. The delay on the live stream made it difficult for those watching at home to keep up with the price. I had to ask Tony to pause a number of times while I awaited a counter bid from the chatroom. Fortunately, he tolerated this experiment quite well.

A solution to this was offered at the Fest, but I cannot remember who offered it. He said we should simply do it like e-Bay does, where the remote bidders simply specify their highest price they are willing to go, then the proxy bidders will bid up as necessary until it hits that price and they stop. In some ways, this is better than e-Bay because there would be no last minute sniping EXCEPT by those who are there in person. What this means is that, while someone remotely might be able to bid up the price, someone actually at the festival could ALWAYS win it just by bidding a dime more. It may not be fair to the remote bidders, but there always will be advantages to being there in person.

I am interested in running a “CoCoFEST! by Proxy” type service at future events, but I am thinking it needs some ground rules. Rather than someone (like me) just taking orders and buying stuff to make a few bucks for a “service fee,” I prefer to do it to help out the Glenside CoCo Club. I will want everyone interested to actually join the club and pay their membership dues, or at the very least, pay for a ticket. After all, none of us could get in and bid on things if we didn’t buy a ticket. I would even argue that a virtual ticket might go for a higher price than being there in person since it is a “special service.” Unfortunately, none of this is official and if anyone attempts it, they might just have to do it as business opportunity and keep all the money themselves. I, for one, will only volunteer if it drives membership and ticket sales to the club.

Glenside has nothing to do with the live video stream or the chatroom. If it wasn’t for someone there (Aaron Wolfe) bringing along a laptop and web cam and putting it online, there would be no live streaming at all. Ultimately, whoever does something like this needs to be prepared to offer all of those services.

THE EVENINGS

CoCoist fill up the back room at Casey's restaurant (all tables full by the time we were done).
CoCoist fill up the back room at Casey’s restaurant (all tables full by the time we were done).

On Saturday night, the plan was for everyone to drive down the road (maybe a mile or two?) to a place called Casey’s for a group dinner. The restaurant had set aside a back room just for us, and we easily filled it.

The restaurant’s (somewhat pricey) menu had a good mixture of everything from burgers to steak, though I found my $10 burger to be less than wonderful. However, there was one appetizer item that seemed to be a hit. The servers kept bringing out something that they would set on fire. It was some kind of cheese item, and several in our large group ordered them.

Service was also a bit slow initially as our group waited far too long to get menus and drinks, but we were warned about that ahead of time. Judging by the large number of people waiting to be seated when we left, it seems prices and service speed are not a deterrent. This place does seem popular.

For someone on a “no budget” like myself, I would have preferred something a bit cheaper. Sodas were $2.60 and the menu stated they only came with “one free refill.” I drank water (and they didn’t have either Dr. Pepper or Mr. Pibb.)

Mark Marlette celebrates being older than the CoCo.
Mark Marlette celebrates being older than the CoCo.

After dinner, a cake was brought in that read “Happy CoCo Birthday” and it was presented to Mark Marlette in honor of him turning 50. He shared his cake with us, and I promptly forgot about how long it took to get my order taken. Happy birthday, Mark!

After this, folks headed back home (if they were local) or to the hotel.

Saturday night social hour. The heart of the Fest is community.
Saturday night social hour. The heart of the Fest is community.

Without Brother Jeremy attending, there was no Saturday Night Music Jam. Instead, folks mingled in the fest rooms until (or past!) Midnight, getting caught up and playing around with CoCo stuff. I dropped in for a bit and had a chat with my former coworker, Boisy Pitre. We realize how much older we are now when we look back at our time at Microware Systems, with Boisy starting there over 20 years ago, and myself a few years later. We reminisced about those early days when various CoCoFEST friends also worked with us, including Scott McGee, James Jones, Joel Hegberg, Eric Crichlow and Chet Simpson.

So, while it wasn’t the same as having a late night jam session, there did seem to be plenty of folks taking advantage of that extra social time together. I was glad to see the tradition continue, even if I was too exhausted to take advantage of it.

SUMMARY

There was something different about this year’s event. Perhaps it was just the change in venue. This was the first Fest I have been to since the original 1992 non-Rainbow CoCoFEST that was not held inside a hotel, though the hotel was just a short walk across the parking lot. By not having it be just a room in a hotel, it had a different feeling. And the facility was very nice and felt like a step up from the previous locations.

Of the many comments about the location, the one reoccurring one was that we would like to see all the vendors compressed in to one room. Since socializing and seeing our friends is a major reason we attend, not being able to do so without abandoning our table was a detriment. I would suggest that all the vendors with things to sell could go in to the main room, and the smaller room could be used for the No Minimum Bid Auction and for a “CoCo Corner” area where folks could buy a table and set up their CoCo gear just to play around with. In past years, someone suggested doing a big annual tune-up get-together where we would haul out all our CoCo gear, and get everything cleaned and updated to the latest and greatest NitrOS-9 and such. From the count of how many non-vendors who had tables just to have a “home base” during the show, it seems this idea is already happening on it’s own.

I would also really, really like to see the return of a show guide, even if it’s just a half sheet of paper listing the schedule, and a brief description of each vendor and what they are offering. I missed out on talking to a few folks because I didn’t realize who they were or what they were doing. So in “put up or shut up” fashion, I volunteer to create such a handout, as long as someone from Glenside is able to run copies off before Saturday opening. I create stage schedules and handouts for a number of regional Renaissance festivals each year, and spent several years putting out a near-quarterly magazine on the same topic, so spinning off a CoCoFEST! show guide would be very easy to me. I suppose I will have to crash one of the Glenside meetings and suggest that.

Speaking of Glenside meetings… If you are reading this, and you are not a member of the Glenside Color Computer Club, please join. Their website is www.glensideccc.com and I hope they soon offer some downloadable application form or at least a way to PayPal in membership fees and sign up online.

RUMORS

This was a rather rumorless festival, since most speculative items were being talked about publicly and thus don’t qualify as a rumor.

There were a few hints dropped by Boisy Pitre dealing with his upcoming book on the history of the CoCo. It seems him and his coauthor have managed to interview and, in some cases, meet with many people that has a substantial impact on the Color Computer. It sounds like we might finally get to hear some of the stories behind Radio Shack creating the machine, and perhaps we’ll get some insight on “what could have been” with some projects that never made it off the drawing board. I can’t wait (and, he says they will have an eBook edition as well, though those will be hard to get autographed at a book signing).

Boisy Pitre and Mark Marlette take their turn babysitting the original CoCo 3 prototype circuit boards.
Boisy Pitre and Mark Marlette take their turn babysitting the original CoCo 3 prototype circuit boards.

Another item that is noteworthy is that I let Mark Marlette and Boisy Pitre take my Color Computer 3 prototypes back to their home base for evaluation. When Mark gets the time, he is going to go over the two prototype boards and try to learn more about them. There was talk of a 256 color mode in early CoCo 3 specification documents (which we have seen), but if it exists in the production CoCo 3s, no one has ever figure out how to use it. Photos I took of the prototypes have been analyzed and it does seem there are hints of a 256-color mode on the circuit board, so maybe Mark can confirm that.

For the newer readers, these two prototype boards were used by Microware when they did the Extended Color Basic extensions for Radio Shack in 1985. Microware ceased to exist in 2001 when it was purchased by a company called RadiSys, and a few years later they finally downsized so much they no longer needed to be in the three story custom-built Microware building. (By that time, the building was already leased out to many other tenants.) I was working for RadiSys during this time, and as we were cleaning things out, one of the long time employees made sure I grabbed the box containing the Color Computer stuff. Inside were two full CoCo 3 prototypes (with onboard 512K and built in disk controller), and a few networking cards (today believed to be Arc Net cards). There were a few other items too, so perhaps by the next Fest, we’ll have better descriptions for all of that.

But maybe this is just a rumor. Either way, between Boisy’s upcoming book and Mark going over the prototypes, we may be learning quite a bit about the CoCo’s past in the future…

THE RETURN

Always a sad sight...
Always a sad sight…

During early Chicago CoCoFESTs, there was a tradition to go to a Mongolian BBQ place with Scott Griepentrog and his crew. That was the first time I had ever been exposed to that food, though today, chains like Hu Hot have made these places common. I do not think we have had a Sunday evening food tradition since Scott stopped attending, and in a way I miss it.

So instead of that, I got things packed up, said my goodbyes, then met with John and Shirley Strong at a nearby White Castle (we don’t have those in Iowa) for a quick bite to eat and a chance to actually talk. (It is always amazing to me how fast time flies at the CoCoFest, and how little you get to talk to the person at the table right next to you.) After discussing our plans for world domination, I said my final goodbyes to Lombard, Illinois and headed home.

But this time, I would do it using an alternate route that avoided all those pesky toll booths. It added about 25 miles to my trip, but saved me about $12!

There was nothing eventful on the drive home, but I did spend much time thinking about all those long 12-20 hour drives we used to take from Texas to Chicago or Atlanta or Iowa for CoCoFests. What an amazing trip it’s been. So far.

DISCLAIMER

Yes, I left out things. Important things. Major things. Things I don’t even know about yet, but I am sure I will once someone reads this report and let’s me know about them. As I learn more, I will try to revise this report and fix mistakes and add information.

There really is no excuse for someone going to an event as small as this and not being able to say hello to everyone there, yet I will try to come up with one because that is exactly what I did. I have been in touch via e-mail to a few folks who were there, but I never managed to find them and say hi and ask what they were up to. For my excuse, I will just say that I tried, but every time I had time, they weren’t at their booth. I will try harder next time.

I also apologize for all those I didn’t get to do a video interview with. I had intended to try to give everyone there (at least the vendors) a chance to introduce themselves and plug their projects. Maybe some day when my son is older, I can bring him along and he can intern for me so we can get more accomplished.

THANKS

You know how someone will say “I couldn’t have done it without…” when they are giving special thanks? I have written more than my fair share of statements like that over the years. In the early days of Sub-Etha Software going to the Atlanta and Chicago CoCoFests, we were working minimum wage retail jobs and such an adventure was quite expensive. We would coast in to the hotel parking lot on fumes, and hope we would sell enough to afford gas back home. We would have to run down during the day and pay our hotel bill once we collected enough money to do so. I don’t think we ever lost money going to a CoCoFest in those early years, but in some of the later ones, that was only due to the massive support of CoCo friends like Carl Boll who was known to buy copies of items he already owned just to help vendors out. What a guy.

This year, I find myself in a similar financial situation, and were it not for the support of some of my friends within Glenside, there would have simply been no way I could have attended this event. A huge thank you goes out to Bob Swoger for working with me on this, and to all those behind the scenes that helped out. I also owe some thanks to John and Shirley Strong for offering to help me out if I needed some assistance getting back home. So this year, I can specifically look to these two entities and say, I really couldn’t have done it without you.

Of course, big thanks goes to those who purchased my 2009 and 2010 CoCoFEST! DVDs. I will offer them for sale via mail order for $5 (plus a buck shipping, if you don’t mind), and will be working on an edition covering the 2013 CoCoFest (I shot almost four hours of footage, so it might end up costing more as it is likely to fill two DVDs). Your support could mean me having to thank you in this spot next time :)

And Glenside… the last group standing… What can I say? If you folks hadn’t decided to do “one more” after CoCoPro took over after RainbowFest ended, this all would have ended much, much sooner. The Atlanta CoCoFests ended in 1995, and Ron Bull held a trio of events in Pennsylvania to get us to the year 2000 with our beloved 8-bit “toy” computer, but Glenside has remained. And here we are, 21 years since I attended my first Chicago CoCoFest in 1992… My Chicago CoCoFest experience is now old enough to drink. It’s a good thing I’m still young, or that would make me feel really old.

And, as always, thank YOU for taking time out of your life to read through this overly long trip report.

Allen C. Huffman, formerly of Sub-Etha Software
alsplace@pobox.com on the Internet (“since 1995!”)
http://www.subethasoftware.com (new blog!)
http://www.cocopedia.com (The CoCo Wiki)
P.O. Box 22031 / Des Moines, IA 50325-9401
(Sorry. No phone. I had to shut them all off in 2011 to save money… Fun times, eh?)

CoCoFEST!

This past weekend, the Glenside Color Computer Club in Illinois hosted the 22nd annual “Last” Chicago CoCoFEST! It was held at a new location which I think was the same place the Vintage Computer Festival had used recently.

For those who couldn’t attend, I have some DVDs available from the 2009 and 2010 event. They primarily contain interviews with various vendors and attendees, and some footage of the festival — clips from the No Minimum Bid Auction and such. There is often shocking footage of our Saturday Night Music Jam, where tuning is optional and no one knows the words.

The 2009 DVD also contains my fest trip report and digital photos as bonus DVD-ROM content. The brand-new 2010 DVD contains more video (about 96 minutes) but there was no room for the bonus content.

If you want a copy, $5 each is all it takes, and if you don’t mind, throw in $1 for shipping. You can PayPal me at alsplace@pobox.com

I am being sent some video from the past years I missed, and hope to bring out some more discs in the near future. Some clips will be posted to YouTube, but I am hoping to raise money from selling DVDs so I can maintain my Glenside club membership and such.

Thanks…

The EthaWin user interface.

Updates:

  • 12/26/2017 – Fixed some typos, adding some section headers.

Years ago, I created the EthaWin user interface for the Radio Shack Color Computer 3 running OS-9. This is its story…

A GUI for the Rest of Us

At the time, the main GUI for CoCO OS-9 apps was something called MultiView. MultiView was sold separately from OS-9 and no runtime version was available, so anyone who wanted to sell a MultiView app could only sell it to users who also owned MultiView. This certainly limited the potential market for MultiView apps, and probably explains why there were never as many GUI-based OS-9 apps as there could have been.

Another issue was performance. When running OS-9 on a graphics screen, it was pushing around far more data than an 80×24 hardware text screen required, and thus most of us never really used the graphical displays unless we really needed proportional fonts and other features. (Yes, back in the 1980s, there were proportional screen fonts for an operating system running on an 8-bit home computer. I guess we just took it for granted at the time, but it seems really forward thinking in retrospect.)

It also goes without saying that running a 640×225 graphic screen chewed up more of the limited system RAM than a text screen, as well, making things quite a tight fit on a 128K CoCo 3 if you had multiple windows and programs open.

One of the last obstacles MultiView faced was the learning curve. Setting it up and using it was not really any different (or any more difficult) than any other OS-9 program, but programming it was another issue. Really smart programmers didn’t seem to have any problem creating MultiView apps in BASIC09 or C or 6809 assembly. I, unfortunately, was not one of those. Reading the documentation in the MultiView developers manual made my head spin. I am not sure I even knew C programming yet, so many of the concepts were completely lost on me.

So, with all this in mind, I decided to create “the user interface for the rest of us.” I wanted something that gave the advantages of a GUI — a menu bar with pull down menus, pop-up overlay windows, and mouse control. I also wanted it te completely and easily usable from the keyboard (something MultiView, and many modern operating systems, do not provide). It also had to be fast, and use very little memory. And most importantly, it had to be super simple to program for so anyone could create programs to use it.

A GUI without Graphics

I realized that most of the GUI applications I had really didn’t need to be on graphical screens. They were just programs with mouse support. Apps like this could just as easily run on a text screen if there was some way to give them a menu and mouse system.

My solution was to build EthaWin, which would operate on a high-speed, low memory text screen. I would make use of OS-9’s powerful terminal capabilities which included things like overlay windows and locking the working area so you could scroll just some parts of the display without effecting the rest.

I would create a menu bar at the top, and the user would be able to use a mouse (moving a cursor block around) to pull down any menu and make a selection. Or, they would be able to use the ALT-key to select a menu, and arrow keys to move between options (or between different menus).

I wrote EthaWin using the original OS-9 K&R C compiler, and designed it so all the developer had to do was set some variables and create some arrays which represented the menus and their options. It was super simple C code, and it worked great.

The first (and probably only) EthaWin app that was created was a disk utility I wrote called “Towel.” It quickly became one of the pieces of software I used more than anything else, since it greatly simplified the process of selectively copying files or doing other common operations. It sold very well, and I hope others found it as useful.

MM/1 K-Windows EthaWin

EthaWin was later ported to OS-9/68000 on the MM/1 computer, which always operated on a graphical screen (like a Macintosh or Atari ST or Amiga did). The code worked the same way, but graphics commands were used to decorate the menus and make things look more like a traditional GUI. The MM/1 also had an on-screen mouse at all times, so it used that instead of the cursor moving trick I did on the CoCo’s hardware text screen.

In 1995, I took a full-time job with Microware Systems, the creators of OS-9. Sub-Etha Software shut down to avoid a conflict of interest, but that didn’t stop me from trying to do more with EthaWin.

Termcap EthaWin

At Microware, we all had SUN Workstations, and used green screen terminals to hook to various OS-9 boxes via serial ports. After seeing some of the rather fancy curses Unix apps that did all kinds of windowing type things via a terminal session, I set out to port EthaWin to generic OS-9/68000, using nothing but the Termcap library. Termcap (terminal capabilities) was a standard way of presenting different types of screen control codes, such as VT100 or ANSI. A Termcap program would read an environment variable to learn what kind of terminal the user was using, then it would load the specific codes for that terminal from a definition text file. A Termcap program could clear the screen or move the cursor, without needing to know what escape sequence did that for the user’s terminal.

The approach I took to port EthaWin to Termcap was to write a new library, which I called TCWin (Termcap Windows). I created Termcap versions of all the standard CoCo OS-9 library calls that did things to the screen, such as turning bold on, or underline off. I figured once I had all of those, none of the rest of my source code would need to be touched. Much.

On the CoCo (and under K-Windows on the MM/1), overlay windows were managed by the system, so if you drew one, used it, then made it go away, whatever was under it would be restored. While there were some terminal protocols that offered things like this, I could not rely on that. I decided to create a buffer to represent the virtual screen, with bytes for the character and its attributes (blink, bold, underline, etc.). My routines would draw to this buffer and then the buffer would be updated to the terminal screen, and if I created an overlay window, I would create a new buffer for that, and be able to restore what was under it since the original screen buffer still existed.

I thought it was a very clever solution, and other than having to create some new wrapper functions for printing to the screen and disabling mouse functions, it worked. All functions of the normal EthaWin could be used, including scrolling. The TCWin library, when used by itself, could even do some things that the CoCo version could not do, such as having independent control of any window on the screen. (On the CoCo implementation, once an overlay window was created, all I/O went just to that window and you couldn’t use any background windows until you closed the overlay.)

DOS EthaWin

Just for fun, while on a work trip to Rochester, New York, I sat around with an old CoCo friend of mine one night (who had long since moved to PCs) and ported EthaWIn yet again, this time to run on a DOS screen. Sadly, the code for this port was lost with a Toshiba hard drive crash soon afterwards. That was when I became aware of the concept of backing up hard drives…

But I digress…

Arduino EthaWin?

I could (and maybe will) write a whole series of articles on how I created this environment, but instead, I wonder… Could I port it to an Arduino? I have plans to dig out the old code and rewrite/update it for the CoCo, to use as part of my CoCo Ethernet experiment, so why not also update it for other uses?

I could not find an implementation of Termcap for the Arduino, but that’s not really important. The only capability I really need is the character sequences for certain functions (clear, cursor move, etc.) for whatever terminal types I plan to support. Initially, I might say I will only support VT100 and ANSI (very common for Unix and PCs). Then, someone could telnet in to an Ethernet Arduino, or connect using a good terminal program (not the crappy serial monitor that’s part of the Arduino IDE), and run apps more in the style of Unix — with nice text screens and menus and such.

There are some technical challenges in doing this with just 2K of RAM. Since I was previously caching a full copy of the window (and attributes) as well as overlay windows, I might have to give up some of that support. But I think some of the basics could be supported.

We shall see.

If I do decide to work on this, I will create a new category for EthaWin posts so those interested can follow my progress (or lack thereof).

Until then…

CoCo appears on TWiT

Curtis Boyle, a long time TRS-80 Color Computer developer, donated some Radio Shack CoCo stuff to Leo Laporte’s TWiT TV studio museum. (If you don’t know what that is, don’t worry. I would have no idea myself if it weren’t for working at a place that sold satellite TV systems back around 2002, where I watched him on a show at the defunct cable channel Tech TV.) Anyway, Curtis wrote a demo program that drew the TWiT logo and flashed colors, and it was shown on the live stream between recording some of their shows.

Screen shots follow. Good job, Curtis!

TRS-80 Color Computer items on Leo's desk.
TRS-80 Color Computer items on Leo’s desk.
Curtis Boyle's demo program, shown on TWiT between shows
Curtis Boyle’s demo program, shown on TWiT between shows

4/19/2013 Update: Curtis shared his source code on the CoCo mailing list today.


10 PALETTE 8,63:PALETTE 0,0:WIDTH40:CLS1
20 ON BRK GOTO 410
30 'TWIT logo CC3 program by L. Curtis Boyle
40 POKE65497,0:INPUT "<R>GB OR <C>OMPOSITE MONITOR (TV):";MN$
50 PALETTE 0,0:PALETTE 3,63
60 IF MN$<>"R" AND MN$<>"r" THEN 80
70 PALETTE 1,36:PALETTE 2,25:BC=0:GOTO 90
75 'Depending on your TV/Composite monitor settings, you may need to adjust these
80 PALETTE 1,8:PALETTE 2,31:BC=0
90 PALETTE 4,BC:HSCREEN2:HCLS4:POKE 65434,BC
100 HCIRCLE(70,96),70,2
110 HPAINT(70,96),2,2
130 HCIRCLE(70,96),56,0,1,.5,1
140 HCIRCLE(70,96),44,0,1,.51,.99
150 HCOLOR0:HLINE(15,92)-(125,104),PSET,BF
160 HPAINT(18,87),0,0
170 HLINE(50,70)-(62,134),PSET,BF
180 HLINE(78,96)-(90,134),PSET,BF
190 HCIRCLE(84,76),7,1:HPAINT(84,76),1,1
200 HCOLOR3:HPRINT(18,19),"Netcasts you love from"
210 HPRINT(21,20),"people you trust"
215 'Start of "TWiT"
220 HCOLOR3:HLINE(149,65)-(191,75),PSET,BF
230 HLINE(165,75)-(175,117),PSET,BF
240 HLINE(191,75)-(200,117),PSET:HLINE-(210,117),PSET
250 HLINE-(217,91),PSET:HLINE-(224,117),PSET:HLINE-(234,117),PSET
260 HLINE-(246,65),PSET:HLINE-(236,65),PSET:HLINE-(229,91),PSET:HLINE-(222,65),PSET
270 HLINE-(212,65),PSET:HLINE-(205,91),PSET:HLINE-(198,65),PSET:HLINE-(191,65),PSET
280 HPAINT(194,66),3,3
290 HLINE(254,75)-(264,117),PSET,BF:HCIRCLE(259,65),7,1:HPAINT(259,65),1,1
300 HLINE(272,65)-(314,75),PSET,BF:HLINE(288,75)-(298,117),PSET,BF
310 FORT=1 TO 4000:NEXT T
320 ST=1:EN=15
330 FOR TC=ST TO EN
340 IF INKEY$="" THEN 380
350 IF ST<>1 THEN 370
360 ST=BC:EN=BC:GOTO 330
370 ST=1:EN=15:GOTO330
380 PALETTE4,TC:POKE65434,TC:FORT=1TO100:NEXTT
390 NEXTTC:GOTO330
400 GOTO 400
410 POKE65496,0:END

1983 *ALL RAM* BBS ported to Arduino from BASIC

See also: Part 1Part 2, Part 3, and Part 4.

Updates:

  • 2/17/2017 – Removed link to my BBS since it hasn’t operated for years.
  • 6/24/2013 – Thanks to some comments, I fixed an issue around line 1010 where it was changing.
    aStr[ c]

    to aStr1. I am not sure why, but bracket-c-bracket must mean something to the code formatter I am using, which is unfortunate. Or maybe it’s just a bug. Which is also unfortunate. There was one other place in the code that had a bracket-c so I fixed it by adding a space after the bracket. Hopefully it works. Anyone know how to attach downloads to a WordPress posting?

  • 4/19/2013 – Sometimes, in the evening (or on weekends), I have my Arduino online. You can telnet to it: (no longer running)
  • 4/16/2013 – The WordPress editor just loves to mangle source code. I think I have finally gotten it all cleaned up. The version posted below is the latest version, with some bug fixes and other stuff cleaned up. Unlike the original posting, this one does not have Ethernet support. I have been working on a Telnet Server, and have been using the BBS to test it. You will see stuff #ifdef’d out below that makes use of the new Telnet functions. As soon as I have it polished, I will post it, too (or send it to you if you want to see the work-in-progress).
  • 7/1/2022 – Cleanup on aisle three…

Following up with part 1, part 2, part 3 and part 4, here is the source code. Remember, this is awful C. It is a line-by-line translation of BASIC to Arduino.

As I finish this experiment, I will continue to report the source code here to keep it in sync.

Source code last updated: 2/14/2015

See latest on my GitHub: GitHub – allenhuffman/ALLRAMBBS: 1983 cassette BBS ported from BASIC ;-)

// BOF preprocessor bug prevent - insert me on top of your arduino-code
#if 1
__asm volatile ("nop");
#endif
/*-----------------------------------------------------------------------------
 
 *ALL RAM* BBS for Arduino
 by Allen C. Huffman (alsplace@pobox.com / www.appleause.com)
 
 This is an experiment to see how easy it is to translate Microsoft BASIC to
 Arduino C. The translated C code is incredibly ugly and will be disturbing
 to most modern programmers. It was done just to see if it could be done, and
 what better thing to translate than a program that was also originally done
 just to see if it could be done -- a cassette based BBS package from 1983!
 
 About the *ALL RAM* BBS:
 
 The *ALL RAM* BBS System was writtin in 1983 for the Radio Shack TRS-80
 Color Computer ("CoCo"). At this time, existing BBS packages for the CoCo
 required "2-4 disk drives" to operate, so *ALL RAM* was created to prove
 a BBS could run from a cassette-based computer. Instead of using floppy
 disks to store the userlog and message base, they were contained entirely
 in RAM. The in-memory databases could be saved to cassette tape and
 reloaded later.
 
 The original BASIC source code is included in comments, followed by a very
 literal line-by-line translation to Arduino C. Remember, it's not a rewrite
 in C -- it's BASIC code done in C.
 
 Be warned. There be "gotos" ahead!
 
 References about memory saving:
 
 http://www.controllerprojects.com/2011/05/23/saving-ram-space-on-arduino-when-you-do-a-serial-printstring-in-quotes/
 http://www.adafruit.com/blog/2008/04/17/free-up-some-arduino-sram/
 
 2013-04-02 0.0 allenh - Initial version with working userlog.
 2013-04-03 1.0 allenh - Message base working. Core system fully functional.
 Preliminary support for Arduino Ethernet added.
 2013-04-04 1.1 allenh - SD card support for loading/saving.
 2013-04-05 1.2 allenh - Ethernet (telnet) support.
 2013-04-06 1.3 allenh - Cleanup for posting to www.appleause.com
 2013-04-09 1.4 allenh - Fixed a bug with INPUT_SIZE and input.
 2013-04-12 1.5 allenh - Integration with new Telnet server code, misc fixes.
 2015-02-14 1.6 allenh - Adding some "const" to make it build with 1.6.0.
 -----------------------------------------------------------------------------*/
#include <avr/pgmspace.h>

#define VERSION "1.6"

// To enable SD card support, define the PIN used by SD.begin(x)
//#define SD_PIN        4

// To enable Ethernet support, defined the PORT used by EthernetServer(PORT)
//#define ENET_PORT     23

// NOTE: On smaller Arduinos (UNO), there is not enough Flash or RAM to have
// both SD and Ethernet support at the same time.

#if defined(SD_PIN)
#include <SD.h>
#endif

#if defined(ENET_PORT)
#include <SPI.h>
#include <Ethernet.h>

extern EthernetClient client;

// Prototypes...
byte telnetRead(EthernetClient client);
byte telnetInput(EthernetClient client, char *cmdLine, byte len);
#endif

// And so it begins.
void setup()
{
  Serial.begin(9600);

  while(!Serial);

  showHeader();

#if defined(ENET_PORT)
  telnetInit();
#endif

  showConfig();

  // Scroll off any leftover console output.
  //for(int i=0; i<24; i++) print();
}

// We don't really have anything loop worthy for this program, so we'll
// just have some fun and pretend like we are starting up the CoCo each
// time through.
void loop()
{
  showCoCoHeader(); // Just for fun...

  allram();

  print();
  print(F("BREAK IN 520"));
  print(F("OK"));

  delay(5000);
}

// Show program header.
void showHeader()
{
  // Emit some startup stuff to the serial port.
  print(F("\n"
    "*ALL RAM* BBS for Arduino "VERSION" - 30 year anniversary edition!\n"
    "Copyright (C) 1983 by Allen C. Huffman\n"
    "Ported from TRS-80 Color Computer Extended Color BASIC.\n"
    "Build Date: "__DATE__" "__TIME__"\n"));
}

/*---------------------------------------------------------------------------*/
// In BASIC, strings are dynamic. For C, we have to pre-allocate buffers for
// the strings.
#define INPUT_SIZE  32  // 64. For aStr, etc.

#define MAX_USERS   3   // NM$(200) Userlog size. (0-200, 0 is Sysop)
#define NAME_SIZE   12   // 20. Username size (nmStr)
#define PSWD_SIZE   8   // 8. Password size (psStr & pwStr) 
#define ULOG_SIZE   (NAME_SIZE+1+PSWD_SIZE+1+1)

// To avoid hard coding some values, we define these here, too. Each message
// is made up of lines, and the first line will contain the From, To, and
// Subject separated by a character. So, while the original BASIC version
// hard coded this, we will calculate it, letting the subject be as large
// as whatever is left over (plus room for separaters and NULL at the end).
#define FR_SIZE     NAME_SIZE                      // From
#define TO_SIZE     NAME_SIZE                      // To
#define SB_SIZE     (INPUT_SIZE-FR_SIZE-1-TO_SIZE) // "From\To\Subj"

// The original BASIC version was hard-coded to hold 20 messages of 11 lines
// each (the first line was used for From/To/Subject). The Arduino has far
// less RAM, so these have been made #defines so they can be changed.
#define MAX_MSGS    3   // 19  (0-19, 20 messages)
#define MAX_LINE    2   // 10  (0-10, 11 lines)

// Rough estimate of how many bytes these items will take up.
#define ULOG_MEM    ((MAX_USERS+1)*(ULOG_SIZE))
#define MBASE_MEM   ((MAX_MSGS+1)*MAX_LINE*INPUT_SIZE)

// Validate the settings before compiling.
#if (FR_SIZE+1+TO_SIZE+SB_SIZE > INPUT_SIZE)
#error INPUT_SIZE too small to hold "From\To\Sub".
#endif

/*---------------------------------------------------------------------------*/

//0 REM *ALL RAM* BBS System 1.0
//1 REM   Shareware / (C) 1983
//2 REM     By Allen Huffman
//3 REM  110 Champions Dr, #811
//4 REM     Lufkin, TX 75901
//5 CLS:FORA=0TO8:READA$:POKE1024+A,VAL("&H"+A$):NEXTA:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3
//10 CLEAR21000:DIMNM$(200),MS$(19,10),A$,F$,S$,T$,BR$,CL$,NM$,PS$,PW$,A,B,C,CL,LN,LV,MS,NM,KY,UC

// All variables in BASIC are global, so we are declaring them outside the
// functions to make them global in C as well. Arrays in BASIC are "0 to X",
// and in C they are "0 to X-1", so we add one to them in C to get the same
// number of elements.
char nmArray[MAX_USERS+1][ULOG_SIZE];             // NM$(200)
char msArray[MAX_MSGS+1][MAX_LINE+1][INPUT_SIZE+1];// MS$(19,10) 1.4
char aStr[INPUT_SIZE+1];                          // A$ 1.4
char fStr[FR_SIZE];                               // F$ - From
char sStr[SB_SIZE];                               // S$ - Subj
char tStr[TO_SIZE];                               // T$ - To
char nmStr[NAME_SIZE];                            // NM$ - Name
char psStr[PSWD_SIZE];                            // PS$ - Pswd
char pwStr[PSWD_SIZE];                            // PW$ - Pswd

// To save RAM, these two strings will exist in Flash memory. It will
// require a bit of work later to use them (__FlashStringHelper*).
const char PROGMEM brStr[] PROGMEM = "*==============*==============*"; // BR$ - border
const char PROGMEM clStr[] PROGMEM = "\x0c\x0e";                        // CL$ - clear

int a, b, c, cl, ln, lv, ms, nm, ky, uc;
// A, B, C - misc.
// CL - Calls
// LN - Line Number
// LV - Level
// MS - Messages
// NM - Names (users)
// KY - Keys (commands entered)
// UC - Uppercase input (1=Yes, 0=No)

void allram()
{
  // HACK - create adefault Sysop account.
  nm = 0;
  strncpy_P(nmArray[0], PSTR("SYSOP\\TEST9"), ULOG_SIZE);

  cls(); // From line 5  

  //15 CL$=CHR$(12)+CHR$(14):BR$="*==============*==============*":GOSUB555
  //char cl[] = "\0xC\0xE";
  //char br[] = "*==============*==============*";
  gosub555();

//20 CLS:PRINTTAB(6)"*ALL RAM* BBS SYSTEM":PRINT"USERS:"NM,"CALLS:"CL:PRINTTAB(5)"SYSTEM AWAITING CALLER";:go:SOUND200,10
line20:
nmStr[0] = 0; // reset user.
  cls();
  printTab(6);
  print(F("*ALL RAM* BBS SYSTEM"));
  printSemi(F("USERS:"));
  printSemi(nm);
  printComma();
  printSemi(F("CALLS:"));
  print(cl);
  printTab(5);
  printSemi(F("SYSTEM AWAITING CALLER"));
  //gosub1005();
  if (gosub1005()==255) goto line20;
  sound(200,10);

  //25 A$="Welcome To *ALL RAM* BBS!":GOSUB1055:KY=0:CL=CL+1
  strncpy_P(aStr, PSTR("Welcome To *ALL RAM* BBS!"), INPUT_SIZE);
  gosub1055();
  ky = 0;
  cl = cl + 1;

  showLoginMessage();

  //30 PRINT:PRINT"Password or 'NEW' :";:UC=1:GOSUB1005:PS$=A$:IFA$=""ORA$="NEW"THEN55ELSEPRINT"Checking: ";:A=0
line30:
  print();
  printSemi(F("Password or 'NEW' :"));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  strncpy(psStr, aStr, PSWD_SIZE);
  if (aStr[0]=='\0' || strcmp(aStr, "NEW")==0)
  {
    goto line55;
  }
  else
  {
    printSemi(F("Checking: "));
    a = 0;
  }

line35:
  //35 A$=NM$(A):B=INSTR(A$,"\"):NM$=LEFT$(A$,B-1):PW$=MID$(A$,B+1,LEN(A$)-B-1):LV=VAL(RIGHT$(A$,1)):IFPW$=PS$THEN45ELSEA=A+1:IFA<=NM THEN35
  strncpy(aStr, nmArray[a], ULOG_SIZE);
  b = instr(aStr, "\\");
  strncpy(nmStr, aStr, b-1);
  nmStr[b-1] = '\0';  
  strncpy(pwStr, &aStr[b], strlen(aStr)-b-1);
  pwStr[strlen(aStr)-b-1] = '\0';
  lv = atoi(&aStr[strlen(aStr)-1]);
  if (strncmp(pwStr, psStr, PSWD_SIZE)==0)
  {
    goto line45;
  }
  else
  {
    a = a + 1;
    if (a<=nm) goto line35;
  }

line40: // for empty userlog bug
  //40 PRINT"*INVALID*":KY=KY+1:IFKY<3THEN30ELSE215
  print(F("*INVALID*"));
  ky = ky + 1;
  if (ky<3) goto line30;
  goto line215;

line45:
  //45 PRINT"*ACCEPTED*":PRINTBR$:PRINT"On-Line: "NM$:PRINT"Access :"LV:PRINT"Caller :"CL:KY=0:GOTO115
  print(F("*ACCEPTED*"));
  print((__FlashStringHelper*)brStr);
  printSemi(F("On-Line: "));
  print(nmStr);
  printSemi(F("Access :"));
  print(lv);
  printSemi(F("Caller :"));
  print(cl);
  ky = 0;
  goto line115;

  //50 'New User
line55:
  //55 A$="Password Application Form":GOSUB1055
  strncpy_P(aStr, PSTR("Password Application Form"), INPUT_SIZE);
  gosub1055();

  //60 IFNM=200THENPRINT"Sorry, the userlog is full now.":GOTO215ELSEPRINT"Name=20 chars, Password=8 chars"
  if (nm==MAX_USERS)
  {
    print(F("Sorry, the userlog is full now."));
    goto line215;
  }
  else
  {
    printSemi(F("Name="));
    printNumSemi(NAME_SIZE);
    printSemi(F(" chars, Password="));
    printNumSemi(PSWD_SIZE); 
    printSemi(F(" chars"));
  }

line65:
  //65 PRINT:PRINT"Full Name :";:UC=1:GOSUB1005:NM$=A$:IFA$=""ORLEN(A$)>20THEN30
  print();
  printSemi(F("Full Name :"));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  strncpy(nmStr, aStr, NAME_SIZE);
  if (aStr[0]=='\0' || strlen(aStr)>20) goto line30;

  //70 PRINT"Password  :";:UC=1:GOSUB1005:PW$=A$:IFA$=""ORLEN(A$)>8THEN30
  printSemi(F("Password  :"));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  strncpy(pwStr, aStr, PSWD_SIZE);
  if (aStr[0]=='\0' || strlen(aStr)>8) goto line30;

  //75 PRINT:PRINT"Name :"NM$:PRINT"Pswd :"PW$:PRINT"Is this correct? ";:UC=1:GOSUB1005:IFLEFT$(A$,1)="Y"THEN80ELSE65
  print();
  printSemi(F("Name :"));
  print(nmStr);
  printSemi(F("Pswd :"));
  print(pwStr);
  printSemi(F("Is this correct? "));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  if (aStr[0]=='Y')
  {
    goto line80;
  }
  else
  {
    goto line65;
  }

line80:
  //80 NM=NM+1:NM$(NM)=NM$+"\"+PW$+"0":LV=0:KY=0
  nm = nm + 1;
  strncpy(nmArray[nm], nmStr, NAME_SIZE);
  strcat_P(nmArray[nm], PSTR("\\"));
  strncat(nmArray[nm], pwStr, PSWD_SIZE);
  //strcat_P(nmArray[nm], PSTR("0"));
  strcat_P(nmArray[nm], PSTR("1")); // AUTO VALIDATED
  //lv = 0;
  lv = 1;
  ky = 0;
  //85 PRINT"Your password will be validated as soon as time permits.  Press":PRINT"[ENTER] to continue :";:GOSUB1005
  print(F("Your password will be validated as soon as time permits.  Press"));
  printSemi(F("[ENTER] to continue :"));
  //gosub1005();
  if (gosub1005()==255) goto line20;

  //100 'Main Menu
line105:
  //105 A$="*ALL RAM* BBS Master Menu":GOSUB1055
  strncpy_P(aStr, PSTR("*ALL RAM* BBS Master Menu"), INPUT_SIZE);
  gosub1055();

  //110 PRINT"C-all Sysop","P-ost Msg":PRINT"G-oodbye","R-ead Msg":PRINT"U-serlog","S-can Titles"
  printSemi(F("C-all Sysop"));
  printComma();
  print(F("P-ost Msg"));
  printSemi(F("G-oodbye"));
  printComma();
  print(F("R-ead Msg"));
  printSemi(F("U-serlog"));
  printComma();
  print(F("S-can Titles"));

line115:
  //115 PRINTBR$
  print((__FlashStringHelper*)brStr);

line120:
  //120 KY=KY+1:IFKY>200THENPRINT"Sorry, your time on-line is up.":GOTO210ELSEIFKY>180THENPRINT"Please complete your call soon."
  ky = ky + 1;
  if (ky>200)
  {
    print(F("Sorry, your time on-line is up."));
    goto line210;
  }
  else if (ky>180)
  {
    print(F("Please complete your call soon."));
  }

line125:
  showFreeRam();
  //125 PRINTTAB(7)"?=Menu/Command :";:UC=1:GOSUB1005:A$=LEFT$(A$,1)
  printTab(7);
  printSemi(F("?=Menu/Command :"));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[1] = '\0';

line130:
  //130 LN=INSTR("?CGRSPU%",A$):IFLN=0THENPRINT"*Invalid Command*":GOTO120
  ln = instr("?CGRSPU%", aStr);
  if (ln==0)
  {
    print(F("*Invalid Command*"));
    goto line120;
  }

  //135 IFLV<1ANDLN>5THENPRINT" Sorry, you are not validated.":GOTO125
  if (lv<1 && ln>5)
  {
    print(F(" Sorry, you are not validated."));
    goto line125;
  }

  //140 ONLN GOTO105,155,205,405,455,305,255,505
  if (ln==1) goto line105;
  if (ln==2) goto line155;
  if (ln==3) goto line205;
  if (ln==4) goto line405;
  if (ln==5) goto line455;
  if (ln==6) goto line305;
  if (ln==7) goto line255;
  if (ln==8) goto line505;

  //150 'Call Sysop
line155:
  //155 A$="Calling the Sysop":GOSUB1055:A=0
  strncpy_P(aStr, PSTR("Calling the Sysop"), INPUT_SIZE);
  gosub1055();
  a = 0;

  //165 PRINT" BEEP!";:SOUND150,5:IFINKEY$=CHR$(12)THEN175ELSEprintING$(5,8);:A=A+1:IFA<25THEN165
line165:
  printSemi(F(" BEEP!"));
  sound(150, 5);
  if (inkey()==12)
  {
    goto line175;
  }
  else
  {
    string(5, 8);
    a = a + 1;
    if (a<25) goto line165;
  }

  //170 PRINT:PRINT" The Sysop is unavaliable now.":GOTO115
  print(F(""));
  print(F(" The Sysop is unavaliable now."));
  goto line115;

line175:
  //175 PRINT:PRINTTAB(6)"*Chat mode engaged*"
  print();
  printTab(6);
  print(F("*Chat mode engaged*"));

line180:
  //180 GOSUB1005:IFLEFT$(A$,3)="BYE"THEN185ELSE180
  //gosub1005();
  if (gosub1005()==255) goto line20;
  if (strncmp(aStr, "BYE", 3)==0)
  {
    goto line185;
  }
  else
  {
    goto line180;
  }

line185:
  //185 PRINTTAB(5)"*Chat mode terminated*":GOTO115
  goto line115;

  //200 'Goodbye
line205:
  //205 A$="Thank you for calling":GOSUB1055
  strncpy_P(aStr, PSTR("Thank you for calling"), INPUT_SIZE);
  gosub1055();

line210:
  //210 PRINT:PRINT"Goodbye, "NM$"!":PRINT:PRINT"Please call again."
  print(F(""));
  printSemi(F("Goodbye, "));
  printSemi(nmStr);
  print(F("!"));
  print();
  print(F("Please call again."));

line215:
  //215 PRINT:PRINT:PRINT"*ALL RAM* BBS disconnecting..."
  print();
  print();
  print(F("*ALL RAM* BBS disconnecting..."));

  //220 FORA=1TO1000:NEXTA
  delay(1000);

#if defined(ENET_PORT)
  telnetDisconnect();
#endif

  //225 GOTO20
  goto line20;

  //250 'Userlog
line255:
  //255 A$="List of Users":GOSUB1055:PRINT"Users on system:"NM:IFNM=0THEN115ELSEA=1
  strncpy_P(aStr, PSTR("List of Users"), INPUT_SIZE);
  gosub1055();
  printSemi(F("Users on system:"));
  print(nm);
  if (nm==0)
  {
    goto line115;
  }
  else
  {
    a = 1;
  }
line260:
  //260 A$=NM$(A):PRINTLEFT$(A$,INSTR(A$,"\")-1)TAB(29)RIGHT$(A$,1)
  strncpy(aStr, nmArray[a], INPUT_SIZE);
  {
    char tempStr[NAME_SIZE+1];         // Add room for NULL.
    strncpy(tempStr, aStr, NAME_SIZE+1);
    tempStr[instr(tempStr, "\\")-1] = '\0';
    printSemi(tempStr);
  }
  printTab(29);
  print(right(aStr,1));

  //265 IF(A/10)=INT(A/10)THENPRINT"C-ontinue or S-top :";:UC=1:GOSUB1005:IFLEFT$(A$,1)="S"THEN275
  if (a % 10 == 9)
  {
    printSemi(F("C-ontinue or S-top :"));
    uc = 1;
    //gosub1005();
    if (gosub1005()==255) goto line20;
    if (aStr[0]=='S') goto line275;
  }

  //270 A=A+1:IFA<=NM THEN260
  a = a + 1;
  if (a<=nm) goto line260;

line275:
  //275 PRINT"*End of Userlog*":GOTO115
  print(F("*End of Userlog*"));
  goto line115;

  //300 'Post Msg
line305:
  //305 IFMS=20THENPRINT"One moment, making room...":FORA=0TO18:FORB=0TO10:MS$(A,B)=MS$(A+1,B):NEXTB:NEXTA:MS=19
  if (ms==MAX_MSGS+1)
  {
    print(F("One moment, making room..."));
    for(a=0; a<=MAX_MSGS-1; a++)
    {
      for(b=0; b<=MAX_LINE; b++)
      {
        strncpy(msArray[a][b], msArray[a+1][b], INPUT_SIZE);
      }
    }
    ms = MAX_MSGS;
  }
  //310 CLS:PRINTCL$"This will be message #"MS+1:FORA=0TO10:MS$(MS,A)="":NEXTA:F$=NM$
  cls();
  print((__FlashStringHelper*)clStr);
  printSemi(F("This will be message #"));
  print(ms+1);
  for (a=0; a<=MAX_LINE; a++)
  {
    msArray[ms][a][0] = '\0';
  }
  strncpy(fStr, nmStr, NAME_SIZE);

  //315 PRINT"From :"F$:PRINT"To   :";:UC=1:GOSUB1005:A$=LEFT$(A$,20):T$=A$:IFA$=""THEN115
  printSemi(F("From :"));
  print(fStr);
  printSemi(F("To   :"));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[NAME_SIZE] = '\0';
  strncpy(tStr, aStr, TO_SIZE);
  if (aStr[0]=='\0') goto line115;

  //320 PRINT"Is this message private? ";:UC=1:GOSUB1005:IFLEFT$(A$,1)="Y"THENS$="*E-Mail*":GOTO330
  printSemi(F("Is this message private? "));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  if (aStr[0]=='Y')
  {
    strncpy_P(sStr, PSTR("*E-Mail*"), SB_SIZE);
    goto line330;
  }

  //325 PRINT"Subj :";:UC=1:GOSUB1005:A$=LEFT$(A$,18):S$=A$:IFA$=""THEN115
  printSemi(F("Subj :"));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[SB_SIZE-1] = '\0';
  strncpy(sStr, aStr, SB_SIZE);
  if (aStr[0]=='\0') goto line115;

line330:
  //330 PRINT"Enter up to 10 lines, 64 chars. [ENTER] on a blank line to end.":A=0
  printSemi(F("Enter up to"));
  printSemi(MAX_LINE);
  printSemi(F("lines,"));
  printSemi(INPUT_SIZE);
  print(F("chars. [ENTER] on a blank line to end."));
  a = 0;

line335:
  //335 A=A+1:PRINTUSING"##>";A;:GOSUB1005:MS$(MS,A)=A$:IFA$=""THENA=A-1:GOTO345ELSEIFA<10THEN335
  a = a + 1;
  printUsingSemi("##>", a);
  //gosub1005();
  if (gosub1005()==255) goto line20;
  strncpy(msArray[ms][a], aStr, INPUT_SIZE);
  if (aStr[0]=='\0')
  {
    a = a - 1;
    goto line345;
  }
  else if (a<MAX_LINE) goto line335;

line340:
  //340 PRINT"*Message Buffer Full*"
  print(F("*Message Buffer Full*"));

line345:
  //345 PRINT"A-bort, C-ont, E-dit, L-ist, or S-ave Message? ";:UC=1:GOSUB1005:A$=LEFT$(A$,1):IFA$=""THEN345
  printSemi(F("A-bort, C-ont, E-dit, L-ist, or S-ave Message? "));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[1] = '\0';
  if (aStr[0]=='\0') goto line345;

  //350 LN=INSTR("ACELS",A$):IFLN=0THEN345ELSEONLN GOTO385,355,360,375,380
  ln = instr("ACELS", aStr);
  if (ln==0) goto line345;
  if (ln==1) goto line385;
  if (ln==2) goto line355;
  if (ln==3) goto line360;
  if (ln==4) goto line375;
  if (ln==5) goto line380;

line355:
  //355 IFA<10THENPRINT"Continue your message:":GOTO335ELSE340
  if (a<MAX_LINE)
  {
    printSemi(F("Continue your message:"));
    goto line335;
  } 
  else goto line340;

line360:  
  //360 PRINT"Edit line 1 -"A":";:GOSUB1005:LN=VAL(LEFT$(A$,2)):IFLN<1ORLN>A THEN345
  printSemi(F("Edit line 1 -"));
  printSemi(a);
  printSemi(F(":"));
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[2] = '\0';
  ln = atoi(aStr);
  if (ln<1 || ln>a) goto line345;

  //365 PRINT"Line currently reads:":PRINTMS$(MS,LN):PRINT"Enter new line:":GOSUB1005:A$=LEFT$(A$,64):IFA$=""THENPRINT"*Unchanged*"ELSEMS$(MS,LN)=A$:PRINT"*Corrected*"
  print(F("Line currently reads:"));
  print(msArray[ms][ln]);
  print(F("Enter new line:"));
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[INPUT_SIZE] = '\0'; // 1.4
  if (aStr[0]=='\0')
  {
    print(F("*Unchanged*"));
  }
  else
  {
    strncpy(msArray[ms][ln], aStr, INPUT_SIZE);
    print(F("*Corrected*"));
  }

  //370 GOTO360
  goto line360;

line375:
  //375 CLS:PRINTCL$"Message Reads:":FORB=1TOA:PRINTUSING"##>";B;:PRINTMS$(MS,B):NEXTB:GOTO345
  cls();
  print((__FlashStringHelper*)clStr);
  print(F("Message Reads:"));
  for (b=1; b<=a; b++)
  {
    printUsingSemi("##>", b);
    print(msArray[ms][b]);
  }
  goto line345;

line380:
  //380 MS$(MS,0)=T$+"\"+F$+"\"+S$:MS=MS+1:PRINT"*Message"MS"stored*":GOTO115
  strcpy(msArray[ms][0], tStr);
  strcat_P(msArray[ms][0], PSTR("\\"));
  strcat(msArray[ms][0], fStr);
  strcat_P(msArray[ms][0], PSTR("\\"));
  strcat(msArray[ms][0], sStr);
  ms = ms + 1;
  printSemi(F("*Message"));
  printSemi(ms);
  print(F("stored*"));
  goto line115;

line385:
  //385 PRINT"*Message Aborted*":GOTO115
  print(F("*Message Aborted*"));
  goto line115;

  //400 'Read Msg
line405:
  //405 IFMS=0THENPRINT"The message base is empty.":GOTO115
  if (ms==0)
  {
    print(F("The message base is empty."));
    goto line115;
  }

  //410 CLS:PRINTCL$
  cls();
  print((__FlashStringHelper*)clStr);

line415:
  //415 PRINT"Read Message 1 -"MS":";:GOSUB1005:A=VAL(LEFT$(A$,2)):IFA<1ORA>MS THEN115
  printSemi(F("Read Message 1 -"));
  printSemi(ms);
  printSemi(F(":"));
  //gosub1005();
  if (gosub1005()==255) goto line20;
  aStr[2] = '\0';
  a = atoi(aStr);
  if (a<1 || a>ms) goto line115;

  //420 A$=MS$(A-1,0):B=INSTR(A$,"\"):C=INSTR(B+1,A$,"\"):T$=LEFT$(A$,B-1):F$=MID$(A$,B+1,C-B-1):S$=RIGHT$(A$,LEN(A$)-C)
  strncpy(aStr, msArray[a-1][0], INPUT_SIZE);
  b = instr(aStr, "\\");
  c = instr(b+1, aStr, "\\");
  strncpy(tStr, aStr, b-1);
  tStr[b-1] = '\0';
  strncpy(fStr, (aStr-1)+b+1, c-b-1);
  fStr[c-b-1] = '\0'; // FIXTHIS - max copy sizes here?
  strncpy(sStr, right(aStr, strlen(aStr)-c), SB_SIZE);

  //425 IFS$="*E-Mail*"ANDLV<8THENIFNM$<>T$ANDNM$<>F$THENPRINT"That message is private.":GOTO415
  if (strcmp(sStr, "*E-Mail*")==0 && lv<8)
  {
    if (strcmp(nmStr, tStr)!=0 && strcmp(nmStr, fStr)!=0)
    {
      print(F("That message is private."));
      goto line415;
    }
  }

  //430 CLS:PRINTCL$"Message #"A:PRINT"From :"F$:PRINT"To   :"T$:PRINT"Subj :"S$:PRINT:B=0
  cls();
  print((__FlashStringHelper*)clStr);
  printSemi(F("Message #"));
  print(a);
  printSemi(F("From :"));
  print(fStr);
  printSemi(F("To   :"));
  print(tStr);
  printSemi(F("Subj :"));
  print(sStr);
  print();
  b = 0;

line435:
  //435 B=B+1:PRINTMS$(A-1,B):IFMS$(A-1,B)=""THEN440ELSEIFB<10THEN435
  b = b + 1;
  print(msArray[a-1][b]);
  if (msArray[a-1][b][0]=='\0')
  {
    goto line440;
  }
  else if (b<MAX_LINE) goto line435;

line440:
  //440 PRINT"*End of Message*":GOTO415
  print(F("*End of Message*"));
  goto line415;

  //450 'Scan Titles
line455:
  //455 IFMS=0THENPRINT"The message base is empty.":GOTO115
  if (ms==0)
  {
    print(F("The message base is empty."));
    goto line115;
  }

  //460 CLS:PRINTCL$"Message Titles:":A=0
  cls();
  print((__FlashStringHelper*)clStr);
  print(F("Message Titles:"));
  a = 0;

line465:
  //465 A$=MS$(A,0):PRINTUSING"[##] SB: ";A+1;:B=INSTR(A$,"\"):C=INSTR(B+1,A$,"\"):PRINTRIGHT$(A$,LEN(A$)-C):PRINTTAB(5)"TO: "LEFT$(A$,B-1):A=A+1:IFA<MS THEN465
  strncpy(aStr, msArray[a][0], INPUT_SIZE);
  printUsingSemi("[##] SB: ", a+1);
  b = instr(aStr, "\\");
  c = instr(b+1, aStr, "\\");
  print(right(aStr, strlen(aStr)-c));
  printTab(5);
  printSemi(F("TO: "));
  {
    char tempStr[TO_SIZE];
    strncpy(tempStr, aStr, b-1);
    tempStr[b-1] = '\0';
    print(tempStr);
  }
  a = a + 1;
  if (a<ms) goto line465;

  //470 PRINT"*End of Messages*":GOTO115
  print(F("*End of Messages*"));
  goto line115;

  //500 '%SYSOP MENU%
line505:
  //505 IFLV<9THENA$="Z":GOTO130
  if (lv<9)
  {
    strncpy_P(aStr, PSTR("Z"), INPUT_SIZE);
    goto line130;
  }

  //510 PRINT"PASSWORD?";:GOSUB1005:IFA$<>"?DROWSSAP"THENPRINT"Thank You!":GOTO115
  printSemi(F("PASSWORD?"));
  //gosub1005();
  if (gosub1005()==255) goto line20;
  if (strcmp(aStr, "?DROWSSAP")!=0)
  {
    print(F("Thank You!"));
    goto line115;
  }

  //515 PRINT"Abort BBS? YES or NO? ";:UC=1:GOSUB1005:IFA$<>"YES"THEN115
  printSemi(F("Abort BBS? YES or NO? "));
  uc = 1;
  //gosub1005();
  if (gosub1005()==255) goto line20;
  if (strcmp(aStr, "YES")!=0) goto line115;

  //520 GOSUB605:STOP
  gosub605();
  return;
} // end of allram()

/*---------------------------------------------------------------------------*/
// Subroutines (formerly GOSUBs)
//
//550 '%LOAD%
void gosub555()
{
  //555 PRINT"%LOAD% [ENTER] WHEN READY";:GOSUB1005
  printSemi(F("%LOAD% [ENTER] WHEN READY"));
  //gosub1005();
  print();
  if (aStr[0]=='!') return;

  //560 OPEN"I",#-1,"USERLOG":INPUT#-1,CL,NM:FORA=0TONM:INPUT#-1,NM$(A):NEXTA:CLOSE
  loadUserlog();

  //565 OPEN"I",#-1,"MSG BASE":INPUT#-1,MS:FORA=0TOMS-1:FORB=0TO10:INPUT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN
  loadMsgBase();
}

//600 '%SAVE%
void gosub605()
{
  //605 PRINT"%SAVE% [ENTER] WHEN READY";:GOSUB1005:MOTORON:FORA=0TO999:NEXTA
  printSemi(F("%SAVE% [ENTER] WHEN READY"));
  gosub1005();
  
  //610 OPEN"O",#-1,"USERLOG":PRINT#-1,CL,NM:FORA=0TONM:PRINT#-1,NM$(A):NEXTA:CLOSE
  saveUserlog();

  //615 OPEN"O",#-1,"MSG BASE":PRINT#-1,MS:FORA=0TOMS-1:FORB=0TO10:PRINT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN
  saveMsgBase();
}

//1000 'User Input
#define CR         13
#define INBUF_SIZE 64
byte gosub1005()
{
  byte ch; // Used only here, so we can make it local.
  byte count;

  //1005 LINEINPUTA$:A$=LEFT$(A$,64):IFUC=0ORA$=""THENRETURN
  count = lineinput(aStr, INPUT_SIZE);
  aStr[INPUT_SIZE] = '\0';
  if ((uc==0) || (aStr[0]=='\0')) return count;

  //1010 FORC=1TOLEN(A$):CH=ASC(MID$(A$,C,1)):IFCH>96THENMID$(A$,C,1)=CHR$(CH-32)
  for (c=0; c<strlen(aStr); c++)
  {
    ch = aStr[c];
    if (ch>96) aStr[c] = ch-32;    
    //1015 IFCH=92THENMID$(A$,C,1)="/"
    if (ch==92) aStr[c] = '/';
    //1020 NEXTC:UC=0:RETURN
  }
  uc = 0;
  
  return count;
}

//1050 'Function Border
void gosub1055()
{
  //1055 CLS:PRINTCL$BR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$:RETURN
  cls();
  print((__FlashStringHelper*)clStr);
  print((__FlashStringHelper*)brStr);
  printTab((32-strlen(aStr))/2);
  print(aStr);
  print((__FlashStringHelper*)brStr);
}

/*---------------------------------------------------------------------------*/
// The following functions mimic some of the Extended Color BASIC commands.

// CLS
// Clear the screen.
void cls()
{
  print();
  print(F("--------------CLS--------------"));
}

// SOUND tone, duration
// On the CoCo, tone (1-255), duration (1-255; 15=1 second).
void sound(byte tone, byte duration)
{
  Serial.write(0x07);   // BEL
  delay(duration*66.6); // Estimated delay.
}

/*---------------------------------------------------------------------------*/
// String functions.

// STRING$(length, charcode)
// Generate a string of length charcode chracters.
void string(byte length, byte charcode)
{
  int i;

  for (i=0; i<length; i++) printCharSemi(charcode);
}

// RIGHT$(str, length)
// Note: Modifies the passed in string, which is okay for our purpose but
// would not be suitable as a generic replacement for RIGHT$.
char *right(char *str, byte length)
{
  return &str[strlen(str)-length];
}

// INSTR(first, str, substr)
// Starting at pos, return position of substr in str, or 0 if not found.
int instr(byte pos, char *str, char *substr)
{
  if (pos<1) return 0;
  return instr(aStr+pos, substr) + pos;
}
int instr(char *str, char *substr)
{
  char *ptr;

  ptr = strstr(str, substr);
  if (ptr==NULL) return 0; // No match?
  if (ptr==&str[strlen(str)]) return 0; // Matched the \0 at end of line?
  return ptr-str+1;
}

/*---------------------------------------------------------------------------*/
// Input functions.

#ifdef ENET_PORT
byte lineinput(char *cmdLine, byte len)
{
  return telnetInput(client, cmdLine, len);
}
#else
// LINE INPUT str
// Read string up to len bytes. This code comes from my Hayes AT Command
// parser, so the variables are named differently.
#define CR           13
#define BEL          7
#define BS           8
#define CAN          24
byte lineinput(char *cmdLine, byte len)
{
  int     ch;
  byte    cmdLen = 0;
  boolean done;

  done = false;
  while(!done)
  {
    //ledBlink();

    ch = -1; // -1 is no data available

    if (Serial.available()>0)
    {
      ch = Serial.read();
    }
    else
    {
      continue; // No data. Go back to the while()...
    }
    switch(ch)
    {
    case -1: // No data available.
      break;

    case CR:
      print();
      cmdLine[cmdLen] = '\0';
      done = true;
      break;

      /*case CAN:
       print(F("[CAN]"));
       cmdLen = 0;
       break;*/

    case BS:
      if (cmdLen>0)
      {
        printCharSemi(BS);
        printSemi(F(" "));
        printCharSemi(BS);
        cmdLen--;
      }
      break;

    default:
      // If there is room, store any printable characters in the cmdline.
      if (cmdLen<len)
      {
        if ((ch>31) && (ch<127)) // isprint(ch) does not work.
        {
          printCharSemi(ch);
          cmdLine[cmdLen] = ch; //toupper(ch);
          cmdLen++;
        }
      }
      else
      {
        printCharSemi(BEL); // Overflow. Ring 'dat bell.
      }
      break;
    } // end of switch(ch)
  } // end of while(!done)

  return cmdLen;
}
#endif

// INKEY$
// Return character waiting (if any) from standard input (not ethernet).
char inkey()
{
  if (Serial.available()==0) return 0;
  return Serial.read();
}

/*---------------------------------------------------------------------------*/

// File I/O
// Ideally, I would have created wrappers for the OPEN, READ, CLOSE commands,
// but I was in a hurry, so...

//SD Card routines
#if defined(SD_PIN)
#define TEMP_SIZE 4
#define FNAME_MAX (8+1+3+1)
boolean initSD()
{
  static bool sdInit = false;

  if (sdInit==true) return true;

  printSemi(F("Initializing SD card..."));
  pinMode(SD_PIN, OUTPUT);
  if (!SD.begin(SD_PIN))
  {
    print(F("initialization failed."));
    return false;
  }
  print(F("initialization done."));
  sdInit = true;

  return true;
}
#endif

//560 OPEN"I",#-1,"USERLOG":INPUT#-1,CL,NM:FORA=0TONM:INPUT#-1,NM$(A):NEXTA:CLOSE
void loadUserlog()
{
#if defined(SD_PIN)
  File myFile;
  char tempStr[TEMP_SIZE];
  char filename[FNAME_MAX];

  if (!initSD()) return;

  strncpy_P(filename, PSTR("USERLOG"), FNAME_MAX);

  myFile = SD.open(filename, FILE_READ);

  if (myFile)
  {
    printSemi(filename);
    print(F(" opened."));
    fileReadln(myFile, tempStr, TEMP_SIZE);
    cl = atoi(tempStr);
    Serial.print(F("cl = "));
    Serial.println(cl);
    fileReadln(myFile, tempStr, TEMP_SIZE);
    nm = atoi(tempStr);
    Serial.print(F("nm = "));
    Serial.println(nm);
    for (a=0; a<=nm; a++)
    {
      fileReadln(myFile, nmArray[a], ULOG_SIZE);
      Serial.print(a);
      Serial.print(F(". "));
      Serial.println(nmArray[a]);
    }  
    myFile.close();
  }
  else
  {
    printSemi(F("Error opening "));
    print(filename);
  }
#else  
  print(F("(USERLOG would be loaded from tape here.)"));
#endif
}

//565 OPEN"I",#-1,"MSG BASE":INPUT#-1,MS:FORA=0TOMS-1:FORB=0TO10:INPUT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN
void loadMsgBase()
{
#if defined(SD_PIN)
  File myFile;
  char tempStr[TEMP_SIZE];
  char filename[FNAME_MAX];

  if (!initSD()) return;

  strncpy_P(filename, PSTR("MSGBASE"), FNAME_MAX);

  myFile = SD.open(filename, FILE_READ);
  if (myFile)
  {
    printSemi(filename);
    print(F(" opened."));
    fileReadln(myFile, tempStr, TEMP_SIZE);
    ms = atoi(tempStr);
    Serial.print("ms = ");
    Serial.println(ms);
    for (a=0; a<=ms-1; a++)
    {
      for (b=0; b<=MAX_LINE; b++)
      {
        fileReadln(myFile, msArray[a][b], INPUT_SIZE);
        Serial.print(F("msArray["));
        Serial.print(a);
        Serial.print(F("]["));
        Serial.print(b);
        Serial.print(F("] = "));
        Serial.println(msArray[a][b]);
      }
    }  
    myFile.close();
  }
  else
  {
    printSemi(F("Error opening "));
    print(filename);
  }
#else
  print(F("(MSGBASE would be loaded from tape here.)"));
#endif
}

#if defined(SD_PIN)
//byte fileReadln(File myFile, char *buffer, byte count)
{
  char ch;
  int  pos;

  pos = 0;
  while(myFile.available() && pos<count)
  {
    ch = myFile.read();
    if (ch==CR)
    {
      buffer[pos] = '\0';
      break;
    }
    if (ch>=32)
    {
      //Serial.print(ch);
      buffer[pos] = ch;
      pos++;
    }
  }
  if (pos>=count) buffer[pos] = '\0';
  //Serial.println();
  return pos;
}
#endif

//610 OPEN"O",#-1,"USERLOG":PRINT#-1,CL,NM:FORA=0TONM:PRINT#-1,NM$(A):NEXTA:CLOSE
void saveUserlog()
{
#if defined(SD_PIN)
  File myFile;
  char filename[FNAME_MAX];

  if (!initSD()) return;

  strncpy_P(filename, PSTR("USERLOG"), FNAME_MAX);

  if (SD.exists(filename)==true) SD.remove(filename);

  myFile = SD.open(filename, FILE_WRITE);
  if (myFile)
  {
    printSemi(filename);
    print(F(" created."));
    myFile.println(cl);
    Serial.print(F("cl = "));
    Serial.println(cl);
    myFile.println(nm);
    Serial.print(F("nm = "));
    Serial.println(nm);
    for (a=0; a<=nm; a++)
    {
      myFile.println(nmArray[a]);
      Serial.print(a);
      Serial.print(F(". "));
      Serial.println(nmArray[a]);
    }
    myFile.close();
  }
  else
  {
    print(F("Error creating file."));
  }
#else
  print(F("save USERLOG"));
#endif
}

//615 OPEN"O",#-1,"MSG BASE":PRINT#-1,MS:FORA=0TOMS-1:FORB=0TO10:PRINT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN
void saveMsgBase()
{
#if defined(SD_PIN)
  File myFile;
  char filename[FNAME_MAX];

  if (!initSD()) return;

  strncpy_P(filename, PSTR("MSGBASE"), FNAME_MAX);

  if (SD.exists(filename)==true) SD.remove(filename);

  myFile = SD.open(filename, FILE_WRITE);
  if (myFile)
  {
    printSemi(filename);
    print(F(" created."));
    myFile.println(ms);
    for (a=0; a<=ms-1; a++)
    {
      for (b=0; b<=MAX_LINE; b++)
      {
        myFile.println(msArray[a][b]);
        Serial.print(F("msArray["));
        Serial.print(a);
        Serial.print(F("]["));
        Serial.print(b);
        Serial.print(F("] = "));
        Serial.println(msArray[a][b]);
      }
    }
    myFile.close();
  }
  else
  {
    print(F("Error creating file."));
  }
#else
  print(F("save MSGBASE"));
#endif
}

/*---------------------------------------------------------------------------*/
// Print (output) routines.

// For TAB to work, we need to track where we think we are on the line.
byte tabPos = 0;

// We want to simulate the following:
// PRINT "HELLO"  -- string, with carraige return at end of line
// PRINT A        -- number (space before and after), with carraige return
// PRINT "HELLO"; -- no carraige return
// PRINT TAB(5);  -- tab to position 5
// PRINT "A","B"  -- tab to column 16 (on 32-column screen)

// Due to various types of strings on Arduino (in memory or Flash), we will
// have to duplicate some functions to have versions that take the other
// types of strings.

// printTypeSemi() routines will not put a carraige return at the end.

// PRINT TAB(column);
void printTab(byte column)
{
  while(tabPos<column)
  {
    printSemi(F(" ")); // Print, and increment tab position.
  }
}

// PRINT,
// NOTE: DECB doesn't add a carraige return after a comma.
void printComma()
{
  printTab(tabPos + (16-(tabPos % 16)));
}

// PRINT "STRING";
// For normal strings in RAM.
void printSemi(const char *string)
{
  tabPos = tabPos + Serial.print(string);
#if defined(ENET_PORT)
  client.print(string);
#endif
}
// For strings in Flash.
void printSemi(const __FlashStringHelper *string)
{
  tabPos = tabPos + Serial.print(string);
#if defined(ENET_PORT)
  client.print(string);
#endif
}
// For printing a byte as a number (0-255).
void printSemi(byte num)
{
  Serial.print(F(" "));
  tabPos = tabPos + Serial.print(num);
  Serial.print(F(" "));
  tabPos = tabPos + 2;
#if defined(ENET_PORT)
  client.print(F(" "));
  client.print(num);
  client.print(F(" "));
#endif
}
// For printing a single character.
void printCharSemi(char ch)
{
  tabPos = tabPos + Serial.print(ch);
#if defined(ENET_PORT)
  client.print(ch);
#endif
}
// For printing a byte as a number with no spaces.
void printNumSemi(byte num)
{
  tabPos = tabPos + Serial.print(num);
#if defined(ENET_PORT)
  client.print(num);
#endif
}

// PRINT
void print(void)
{
  Serial.println();
#if defined(ENET_PORT)
  client.println();
#endif
}

// PRINT "STRING"
void print(const char *string)
{
  Serial.println(string);
  tabPos = 0;
#if defined(ENET_PORT)
  client.println(string);
#endif
}
void print(const __FlashStringHelper *string)
{
  Serial.println(string);
  tabPos = 0;
#if defined(ENET_PORT)
  client.println(string);
#endif
}

// PRINT I
void print(int num)
{
  Serial.print(F(" "));
  Serial.println(num);
  tabPos = 0;
#if defined(ENET_PORT)
  client.print(F(" "));
  client.println(num);
#endif
}

// PRINT USING(format, number);
// NOTE: This only emulates printing positive integers, which is the
// only way it is used by this program.
void printUsingSemi(char *format, byte num)
{
  byte i;
  byte fmtDigits;
  byte numDigits;
  byte tempNum;

  i = 0;
  while(format[i]!='\0')
  {
    if (format[i]!='#') {
      printCharSemi(format[i]);
      i++;
      continue;
    }
    else
    {
      // Start counting the run of #s.
      // Find end of #'s to know how many to use.
      fmtDigits = 0;
      while(format[i]=='#' && format[i]!='\0')
      {
        fmtDigits++;
        i++;
      }
      // Now we know how many # (digits).
      tempNum = num;
      numDigits = 1;
      while(tempNum>10)
      {
        tempNum = tempNum/10;
        numDigits++;
      }
      while(numDigits<fmtDigits)
      {
        printSemi(F(" "));
        fmtDigits--;
      }
      printNumSemi(num);
    }
  }
}

/*---------------------------------------------------------------------------*/
// Show some stuff functions.

// Emit some configuration information.
void showConfig()
{
  print((__FlashStringHelper*)brStr);
  print(F("*ALL RAM* Configuration:"));
  printSemi(F("Userlog size :"));
  print(MAX_USERS+1);
  printSemi(F("Input size   :"));
  print(INPUT_SIZE);
  printSemi(F("Username size:"));
  print(NAME_SIZE);
  printSemi(F("Password size:"));
  print(PSWD_SIZE);
  printSemi(F("Msg base size:"));
  print(MAX_MSGS+1);
  printSemi(F("Message lines:"));
  print(MAX_LINE+1);
#if defined(SD_PIN)
  print(F("SD card      : Enabled"));
#endif
#if defined(ENET_PORT)
  print(F("Ethernet     : Enabled"));
#endif
  printSemi(F("ESTIMATED MEM:"));
  printSemi(ULOG_MEM + MBASE_MEM);
  print(F("bytes."));
  printSemi(F("Free RAM     :"));
  print(freeRam());
  print((__FlashStringHelper*)brStr);
}

void showLoginMessage()
{
  print(F("\nYou are connected to an Arduino UNO R3, a small computer thing you can buy\n"
    "from Radio Shack for $29.99 (or around $22 online). It has 2K of RAM and\n"
    "32K of Flash for program storage. It has a SainSmart Ethernet Shield ($17.99)\n"
    "attached to it. The software running here is a line-by-line port of my\n"
    "*ALL RAM* BBS program written in 1983. The original Extended Color BASIC\n"
    "code was converted as literally as possible to Arduino C. It's a travesty.\n"
    "\n"
    "This BBS program was designed to run on a cassette based TRS-80 Color\n"
    "Computer with 32K of RAM. All messages and users were stored in memory.\n"
    "Obviously, with only 2K of RAM, this is not possible, so this version has\n"
    "been configured to allow only a few users and a teensy tiny message base\n"
    "(smaller than Twitter posts!). Enjoy the experiment!\n"
    "\n"
    "(If userlog is full, use the Sysop password 'TEST'.)"));
}

void showCoCoHeader()
{
  cls();
  print(F("EXTENDED COLOR BASIC 1.1\n"
    "COPYRIGHT (C) 1982 BY TANDY\n"
    "UNDER LICENSE FROM MICROSOFT\n"
    "\n"
    "OK\n"
    "CLOAD\"ALLRAM\"\n"
    "RUN"));
}

/*---------------------------------------------------------------------------*/

// Debug and utility functions.
//

unsigned int freeRam() {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

void showFreeRam()
{
  printSemi(F("Free RAM:"));
  print(freeRam());
}

/*---------------------------------------------------------------------------*/

void showUserlog()
{
  int i;
  printSemi(F("Users: "));
  print(nm);
  for (i=0; i<=nm; i++) {
    printSemi(i);
    printSemi(F(". "));
    print(nmArray[i]);
  }
}

void showMessageBase()
{
  int i,j;

  for (int i=0; i<ms; i++)
  {
    for (int j=0; j<=MAX_LINE; j++)
    {
      printSemi(F("msArray["));
      printSemi(i);
      printSemi(F(","));
      printSemi(j);
      printSemi(F("] = "));
      print(msArray[i][j]);
    }
  }
}


/*---------------------------------------------------------------------------*/

//0 REM *ALL RAM* BBS Editor 1.0
//1 REM   Shareware / (C) 1983
//2 REM     By Allen Huffman
//3 REM  110 Champions Dr, #811
//4 REM     Lufkin, TX 75901
//5 FORA=0TO8:READA$:POKE1024+A,VAL("&H"+A$):NEXTA:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3
//10 CLEAR21000:DIMNM$(200),MS$(19,10),A$,F$,S$,T$,NM$,PW$,A,B,C,LN,LV,MS,NM,PR:PR=80
//15 CLS:PRINTTAB(3)"*ALL RAM* EDITOR COMMANDS:":printING$(32,45)
//20 PRINTTAB(4)"1. CREATE USERLOG",TAB(4)"2. LOAD USERLOG/MSG BASE",TAB(4)"3. SAVE USERLOG/MSG BASE",TAB(4)"4. PRINT USERLOG",TAB(4)"5. PRINT MESSAGES",TAB(4)"6. EDIT USERS",TAB(4)"7. KILL MESSAGES",TAB(4)"8. QUIT"
//25 PRINT@392,"ENTER FUNCTION :"
//30 A$=INKEY$:IFA$=""THEN30ELSEPRINT@408,A$:LN=VAL(A$):IFLN<1ORLN>8THENSOUND50,1:GOTO25
//35 SOUND200,1:ONLN GOTO55,105,155,205,255,305,405,40
//40 STOP
//50 'Create Userlog
//55 CLS:PRINTTAB(7)"SYSOP INFORMATION:":printING$(32,45)
//60 PRINT@128,"SYSOP'S NAME:    (20 CHARACTERS)>";:LINEINPUTA$:IFA$=""ORLEN(A$)>20THENSOUND50,1:GOTO15ELSENM$=A$
//65 PRINT@192,"PASSWORD    :     (8 CHARACTERS)>";:LINEINPUTA$:IFA$=""ORLEN(A$)>8THENSOUND50,1:GOTO15ELSEPW$=A$
//70 PRINT@297,"*VERIFY ENTRY*":PRINT:PRINT"NAME :"NM$:PRINT"PSWD :"PW$:PRINT@456,"IS THIS CORRECT?";
//75 LINEINPUTA$:IFLEFT$(A$,1)<>"Y"THENSOUND50,1:GOTO55
//80 NM$(0)=NM$+"\"+PW$+"9":GOTO15
//100 'Load Userlog/Msg Base
//105 CLS:PRINTTAB(5)"LOAD USERLOG/MSG BASE:":printING$(32,45)
//110 LINEINPUT" READY TAPE, THEN PRESS ENTER:";A$:PRINT@168,"...ONE MOMENT..."
//115 OPEN"I",#-1,"USERLOG":PRINT@232,"LOADING  USERLOG":INPUT#-1,CL,NM:FORA=0TONM:INPUT#-1,NM$(A):NEXTA:CLOSE
//120 OPEN"I",#-1,"MSG BASE":PRINT@240,"MSG BASE":INPUT#-1,MS:FORA=0TOMS-1:FORB=0TO10:INPUT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:GOTO15
//150 'Save Userlog/Msg Base
//155 CLS:PRINTTAB(5)"SAVE USERLOG/MSG BASE:":printING$(32,45)
//160 LINEINPUT" READY TAPE, THEN PRESS ENTER:";A$:PRINT@168,"...ONE MOMENT...":MOTORON:FORA=1TO1000:NEXTA
//165 PRINT@232,"SAVING   USERLOG":OPEN"O",#-1,"USERLOG":PRINT#-1,CL,NM:FORA=0TONM:PRINT#-1,NM$(A):NEXTA:CLOSE
//170 PRINT@240,"MSG BASE":OPEN"O",#-1,"MSG BASE":PRINT#-1,MS:FORA=0TOMS-1:FORB=0TO10:PRINT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:GOTO15
//200 'Print Userlog
//205 IFNM$(0)=""THENPRINT@454,"*USERLOG NOT LOADED*":SOUND50,1:GOTO25
//210 CLS:PRINTTAB(9)"PRINT USERLOG:":printING$(32,45)
//215 LINEINPUT"    PRESS ENTER WHEN READY:";A$:PRINT@169,"...PRINTING..."
//220 PRINT#-2,TAB((PR-30)/2)"[*ALL RAM* BBS System Userlog]":PRINT#-2,"":PRINT#-2,TAB((PR-46)/2)"[###]  [        NAME        ]  [PASSWORD]  [L]"
//225 FORA=0TONM:A$=NM$(A):B=INSTR(A$,"\"):NM$=LEFT$(A$,B-1):PW$=MID$(A$,B+1,LEN(A$)-B-1):LV=VAL(RIGHT$(A$,1))
//230 A$="000........................................0":B=LEN(STR$(A))-1:MID$(A$,4-B,B)=RIGHT$(STR$(A),B):MID$(A$,8,LEN(NM$))=NM$:MID$(A$,32,LEN(PW$))=PW$:MID$(A$,44,1)=RIGHT$(STR$(LV),1)
//235 PRINT@238,A:PRINT#-2,TAB((PR-44)/2)A$:NEXTA:GOTO15
//250 'Print Messages
//255 IFMS$(0,0)=""THENPRINT@454,"*MSG BASE NOT LOADED*":SOUND50,1:GOTO25
//260 CLS:PRINTTAB(8)"PRINT  MESSAGES:":printING$(32,45)
//265 LINEINPUT"    PRESS ENTER WHEN READY:";A$:PRINT@169,"...PRINTING..."
//270 PRINT#-2,TAB((PR-30)/2)"[*ALL RAM* BBS System Messages]":PRINT#-2,""
//275 FORA=0TOMS-1:A$=MS$(A,0):B=INSTR(A$,"\"):C=INSTR(B+1,A$,"\"):T$=LEFT$(A$,B-1):F$=MID$(A$,B+1,C-B-1):S$=RIGHT$(A$,LEN(A$)-C)
//280 PRINT@238,A+1:B=(PR-64)/2:PRINT#-2,TAB(B)"Message #"A:PRINT#-2,TAB(B)"TO :"T$:PRINT#-2,TAB(B)"FR :"F$:PRINT#-2,TAB(B)"SB :"S$:PRINT#-2,STRING$(64,45):C=0
//285 C=C+1:PRINT#-2,TAB(B)MS$(A,C):IFMS$(A,C)=""THEN290ELSEIFC<10THEN285
//290 PRINT#-2,"":NEXTA:GOTO15
//300 'Edit Users
//305 IFNM$(0)=""THENPRINT@454,"*USERLOG NOT LOADED*":SOUND50,1:GOTO25
//310 CLS:PRINTTAB(10)"EDIT  USERS:":printING$(32,45):A=0
//315 PRINT@70,"USERS ON SYSTEM:"NM
//320 A$=NM$(A):B=INSTR(A$,"\"):NM$=LEFT$(A$,B-1):PW$=MID$(A$,B+1,LEN(A$)-B-1):LV=VAL(RIGHT$(A$,1))
//325 PRINT@128,"USER #"A:PRINT:PRINT"NAME: "NM$:PRINT"PSWD: "PW$:PRINT"LVL :"LV
//330 PRINT@320,STRING$(32,45)TAB(4)"D-LET   UP-BACK   J-UMP",TAB(4)"E-DIT   DN-NEXT   M-ENU"
//335 PRINT@456,"ENTER FUNCTION :"
//340 A$=INKEY$:IFA$=""THEN340ELSEPRINT@472,A$;:LN=INSTR("DEJM"+CHR$(94)+CHR$(10),A$):IFLN=0THENSOUND50,1:GOTO335
//345 SOUND200,1:ONLN GOTO350,365,385,15,390,395
//350 IFA=0THENSOUND1,5:GOTO335
//355 IFA=NM THENNM=NM-1:A=A-1:GOTO315
//360 FORB=A TONM:NM$(B)=NM$(B+1):NEXTB:NM=NM-1:GOTO315
//365 PRINT@198,;:LINEINPUTA$:IFA$=""ORLEN(A$)>20THENPRINT@198,NM$ELSENM$=A$
//370 PRINT@230,;:LINEINPUTA$:IFA$=""ORLEN(A$)>8THENPRINT@230,PW$ELSEPW$=A$
//375 PRINT@262,;:LINEINPUTA$:B=VAL(A$):IFB<1ORB>9THENPRINT@261,LV ELSELV=B
//380 NM$(A)=NM$+"\"+PW$+RIGHT$(STR$(LV),1):GOTO335
//385 PRINT@456," JUMP TO USER # ";:LINEINPUTA$:B=VAL(A$):IFB<0ORB>NM THENSOUND1,5:GOTO335ELSEA=B:GOTO320
//390 A=A-1:IFA<0THENA=NM
//391 GOTO315
//395 A=A+1:IFA>NM THENA=0
//396 GOTO315
//400 'Kill Messages
//405 IFMS$(0,0)=""THENPRINT@454,"*MSG BASE NOT LOADED*":SOUND50,1:GOTO25
//410 CLS:PRINTTAB(9)"KILL MESSAGES:":ING$(32,45)
//415 PRINT@96,"DELETE MESSAGE # 1 -"MS":";:LINEINPUTA$:A=VAL(A$):IFA<1ORA>MS THEN15
//420 A$=MS$(A-1,0):B=INSTR(A$,"\"):C=INSTR(B+1,A$,"\"):T$=LEFT$(A$,B-1):F$=MID$(A$,B+1,C-B-1):S$=RIGHT$(A$,LEN(A$)-C)
//425 PRINT:PRINT"TO: "T$:PRINT"FR: "F$:PRINT"SB: "S$
//430 PRINT:LINEINPUT"DELETE THIS?";A$:IFLEFT$(A$,1)<>"Y"THEN410
//435 IFA=MS THENMS=MS-1:GOTO410
//440 FORB=A-1 TOMS-2:FORC=0TO10:MS$(B,C)=MS$(B+1,C):NEXTC:NEXTB:MS=MS-1:GOTO410

// End of file.

Extended Color BASIC to Arduino Sketch, part 4

See also: Part 1Part 2, Part 3, Part 4 and full source.

Put on your protective eyewear, folks. You are about to see something very, very disturbing. So disturbing, in fact, that I am hesitant to share it with you. But I will, anyway.

A few nights ago, I began the process of converting my 1983 BBS program line-by-line from Microsoft Extended Color Basic over to Arduino C. This isn’t a port or a rewrite. It’s a conversion as close as possible to the original BASIC source. And it’s something that should never be done.

So let’s get started, and do it.

I will be including the original BASIC source code as comments, followed by the lines of C that replicate the commands. Anything that was a GOSUB will be turned in to a function, but everything else will just be lines of C code, with liberal use of labels and the C “goto” command. (And if anyone ever asks you if it is possible to use goto in C, tell them no. You do not believe it is. We can stop this insanity before it spreads any further.)

Variables

In BASIC, all variables are global and accessible anywhere, so for this conversion, all (almost) of the variables will be made global as well.

In BASIC, there are two types of variables – strings, and numeric. A string is represented by the name of the variable followed by a dollar sign. For instance, A$ or NM$. A numeric variable has no dollar sign, like X or Y.

While BASIC would let you have long variable names, only the first two characters were processed, so a variable called “NAME$” would really be “NA$”. A clever programmer could make sure that the first two letters were unique, and use long variable names like “NAME” and “USERNUMBER” to make the code easier to read. But since I needed every last byte of memory I could get, I did not waste the space on longer variable names.

In BASIC, you could have a string variable called NM$ and a numeric variable called NM. In C, variable names must be unique. You couldn’t have both “int x;” and “char x;”. Because of this, I chose to rename all the basic string variables and add “Str” to the end of the name. I also converted variable names to lowercase, which wasn’t really necessary, but I was already breaking enough rules without having uppercase variable names. NM$ in BASIC becomes nmStr in C. Numeric variables remained the same, just in lowercase.

In BASIC, strings are dynamic. You can have A$=”HELLO” and B$=”GOODBYE” and create a new string like C$=A$+B$. Standard C does not work this way, so all string variables had to have their maximum size predefined. The ALL RAM BBS was written with certain maximum sizes in mind for user names and message lines, so I chose to use those values for the C string lengths.

Memory

On the original TRS-80 Color Computer, I had 32K of memory to play with. On the Arduino UNO R3, I had less than 2K. In BASIC, and with most C systems, all code and initialized variables (like strings) were stored in the same place. But, the Arduino uses something called Harvard Architecture, where the contents of Flash are separate from the RAM. A normal C program that has any string constants (char *msg=”Hello…”) will have those strings loaded in to RAM, taking up some of the precious 2K. For tiny small programs, this is no big deal. but for this project, every last byte is needed.

There are methods to use the Flash for string storage on the Arduino and I had to learn about them and make extensive use of them. This may be unfamiliar to most C programmers, but we can discuss the techniques I used to save memory in a future article. For now, just know there were some things I had to do for this that would not have been needed if there was more RAM to play with.

Due to the very limited amount of memory, I had to greatly downsize the in-memory storage. Instead of holding 200 users, I might only be able to support three or four. Instead of twenty messages of ten lines each, I might only be able to support three tiny messages of, say, two lines each. And instead of each line being up to 64 characters (twice the screen width of the Color Computer’s screen), I changed that to just 32. This makes Twitter posts look huge by comparison.

And, since I needed a quick way to test the limits, I decided to replace all hard coded values in the BASIC source with #defines in the C version. This let me easily change the maximum number of users or the message size to see how far I could get.

Printing

In BASIC, the PRINT command either prints with a carriage return at the end, or not, depending on the use of a semicolon. PRINT”HELLO” would have a carraige return, but PRINT”HELLO”; would not. Originally, I put in the appropriate Arduino “Serial.println()” or “Serial.print()”, but once it came time to deal with TAB, I had to rethink that. In order to track tabs, I needed my own print routine that could keep track of how many characters had been displayed since the last carriage return.

I decided to create print() and printSemi() to act like PRINT and PRINT;. I also created printTab(), printComma() and even a scaled down version of PRINT USING called printUsing(). They do not quite look the same but they should get the same results

But, that was not enough. When BASIC prints a numeric variable, it puts a space before and after the number. So I created printNum() and printNumSemi(). And due to how Arduino handles printing out different variable types, I had to create a special routine for when I wanted to print a single character (else my print routine would print it as a number).

I also had to deal with the different types of strings the Arduino has — whether variables in RAM, or strings from Flash. It was quite a learning experience, and I will document this, as well, in a future article.

GOTO

Yes, C has a goto, but don’t tell anyone you hear it from me. A label is made (“label:”) and then you simply “goto label;”. There are much better ways to do this in C, but since this is a literal translation, I used goto. So there.

BASIC Commands

BASIC has many functions that do not exist in C, so I created some workalike C functions. They are not full implementations of the ones found in Microsoft BASIC — they were just enough to replicate the functionality needed for the BBS.

I even created versions of the SOUND and CLS commands, even though this Arduino version has no sounds or video screen. Line by line port, remember?

Source Code

And now… the source code. This version is trimmed down a bit, with extra things (like support for Ethernet and SD card storage), removed so we can just focus on the conversion. I will post the full source later.

#include

void setup()
{
    Serial.begin(9600);
    while (!Serial)
        ;
    allram();
}

void loop()
{
}

/---------------------------------------------------------------------------/
// In BASIC, strings are dynamic. For C, we have to pre-allocate buffers for
// the strings.
#define INPUT_SIZE  32  // 64. For aStr, etc.

#define MAX_USERS   3   // NM$(200) Userlog size. (0-200, 0 is Sysop)
#define NAME_SIZE   10  // 20. Username size (nmStr)
#define PSWD_SIZE   8   // 8. Password size (psStr & pwStr)
#define ULOG_SIZE   (NAME_SIZE+1+PSWD_SIZE+1+1)

// To avoid hard coding some values, we define these here, too. Each message
// is made up of lines, and the first line will contain the From, To, and
// Subject separated by a character. So, while the original BASIC version
// hard coded this, we will calculate it, letting the subject be as large
// as whatever is left over (plus room for separaters and NULL at the end).
#define FR_SIZE     NAME_SIZE                      // From
#define TO_SIZE     NAME_SIZE                      // To
#define SB_SIZE     (INPUT_SIZE-FR_SIZE-1-TO_SIZE) // "FromToSubj"

// The original BASIC version was hard-coded to hold 20 messages of 11 lines
// each (the first line was used for From/To/Subject). The Arduino has far
// less RAM, so these have been made #defines so they can be changed.
#define MAX_MSGS    4   // 19  (0-19, 20 messages)
#define MAX_LINE    2   // 10  (0-10, 11 lines)

// Rough estimate of how many bytes these items will take up.
#define ULOG_MEM    ((MAX_USERS+1)(ULOG_SIZE))
#define MBASE_MEM   ((MAX_MSGS+1)MAX_LINE*INPUT_SIZE)

// Validate the settings before compiling.
#if (FR_SIZE+1+TO_SIZE+SB+SIZE > INPUT_SIZE)
#error INPUT_SIZE too small to hold "FromToSub".
#endif

/---------------------------------------------------------------------------/

/// And now... The BASIC code begins.

//0 REM ALL RAM BBS System 1.0
//1 REM   Shareware / (C) 1983
//2 REM     By Allen Huffman
//3 REM  110 Champions Dr, #811
//4 REM     Lufkin, TX 75901
//5 CLS:FORA=0TO8:READA$:POKE1024+A,VAL("&H"+A$):NEXTA:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3

*** We will do a CLS later. This FOR/NEXT loop was to load a bit of assembly language in to memory and execute it. This routine would clear memory and give the maximum amount to BASIC. And no, I haven’t lived there since 1995.

*** Next, we have the variables. You didn’t have to pre-define them in BASIC, but it would speed things up later when they were first used since memory would be set aside ahead of time. In the original, sizes were hard coded (see the 200, 19, and 10, below) but in the C code we will use the #define values defined earlier.

//10 CLEAR21000:DIMNM$(200),MS$(19,10),A$,F$,S$,T$,BR$,CL$,NM$,PS$,PW$,A,B,C,CL,LN,LV,MS,NM,KY,UC

// All variables in BASIC are global, so we are declaring them outside the
// functions to make them global in C as well. Arrays in BASIC are "0 to X",
// and in C they are "0 to X-1", so we add one to them in C to get the same
// number of elements.
char nmArray[MAX_USERS+1][ULOG_SIZE];             // NM$(200)
char msArray[MAX_MSGS+1][MAX_LINE+1][INPUT_SIZE]; // MS$(19,10)
char aStr[INPUT_SIZE];                            // A$
char fStr[FR_SIZE];                               // F$ - From
char sStr[SB_SIZE];                               // S$ - Subj
char tStr[TO_SIZE];                               // T$ - To
char nmStr[NAME_SIZE];                            // NM$ - Name
char psStr[PSWD_SIZE];                            // PS$ - Pswd
char pwStr[PSWD_SIZE];                            // PW$ - Pswd

// To save RAM, these two strings will exist in Flash memory. It will
// require a bit of work later to use them (__FlashStringHelper).
prog_char brStr[] PROGMEM = "============================"; // BR$ - border
prog_char clStr[] PROGMEM = "x0cx0e";                        // CL$ - clear

*** Above, PROGMEM causes these strings to be stored in Flash. You will see later that it also requires special code to access them.

int a, b, c, cl, ln, lv, ms, nm, ky, uc;
// A, B, C - misc.
// CL - Calls
// LN - Line Number
// LV - Level
// MS - Messages
// NM - Names (users)
// KY - Keys (commands entered)
// UC - Uppercase input (1=Yes, 0=No)

*** Above, I chose int as the variable type, but byte would be better since none of my numeric variables ever go negative. I should change this, and save an extra 10 bytes (trust me, it all mattered in this project). The only exception would be the CL number of calls variable, since a byte could only count to 255. But, this isn’t a real useful BBS, so beyond a few test calls, maybe that would be fine.

*** The next function is the main BBS code. The only thing not inside of it are routines that were GOSUBS, and any additional helper functions to replicate certain BASIC commands.

void allram()
{
    // HACK - create adefault Sysop account.
    nm = 0;
    strncpy_P(nmArray[0], PSTR("SYSOP\TEST9"), ULOG_SIZE);

*** The original BASIC version required there to be a User 0 for the system operator account. It could never run without it, since that user could not be deleted. But, since we are not using the editor to create users, I hard coded a SysOp account.

cls(); // From line 5

//15 CL$=CHR$(12)+CHR$(14):BR$="============================":GOSUB555
//char cl[] = "\0xC\0xE";
//char br[] = "============================";
gosub555();

line20:
//20 CLS:PRINTTAB(6)"ALL RAM BBS SYSTEM":PRINT"USERS:"NM,"CALLS:"CL:PRINTTAB(5)"SYSTEM AWAITING CALLER";:GOSUB1005:SOUND200,10
cls();
printTab(6);
print(F("ALL RAM BBS SYSTEM"));
printSemi(F("USERS:"));
printSemi(nm);
printComma();
printSemi(F("CALLS:"));
print(cl);
printTab(5);
printSemi(F("SYSTEM AWAITING CALLER"));
gosub1005();
sound(200,10);

*** As you can see, the print() routines look a bit different, but you should be able to follow them exactly as the original BASIC code. The sound() call doesn’t do anything, but I did try to make it delay the same amount of time the CoCo’s sound would have played.

//25 A$="Welcome To ALL RAM BBS!":GOSUB1055:KY=0:CL=CL+1
strncpy_P(aStr, PSTR("Welcome To ALL RAM BBS!"), INPUT_SIZE);
gosub1055();
ky = 0;
cl = cl + 1;

*** In BASIC, you could assign a string just by setting it to a variable. I figured the closest version of this in C would be the string copy function. So, A$=”whatever” becomes a call to copy “whatever” in to the aStr string. The PSTR() macro causes the string to be stored in Flash, and the special version of strcpy_P() is used to copy from Flash to RAM. The end result is the same.

//30 PRINT:PRINT"Password or 'NEW' :";:UC=1:GOSUB1005:PS$=A$:IFA$=""ORA$="NEW"THEN55ELSEPRINT"Checking: ";:A=0
line30:
print();
printSemi(F("Password or 'NEW' :"));
uc = 1;
gosub1005();
strncpy(psStr, aStr, PSWD_SIZE);
if (aStr[0]=='\0' || strcmp(aStr, "NEW")==0)
{
    goto line55;
}
else
{
    printSemi(F("Checking: "));
    a = 0;
}

*** In my original BASIC version, the function starting at line 1005 was an input routine. It would read a line from the user in to A$. It would use the variable UC to know if it should return the string in ALL UPPERCASE. Even at such a young age, I had already figured out parameter passing… I just didn’t have a programming language that supported it.

line35:
//35 A$=NM$(A):B=INSTR(A$,""):NM$=LEFT$(A$,B-1):PW$=MID$(A$,B+1,LEN(A$)-B-1):LV=VAL(RIGHT$(A$,1)):IFPW$=PS$THEN45ELSEA=A+1:IFA< =NM THEN35
strncpy(aStr, nmArray[a], ULOG_SIZE);
b = instr(aStr, "\");
strncpy(nmStr, aStr, b-1);
nmStr[b-1] = '\0';
strncpy(pwStr, &aStr[b], strlen(aStr)-b-1);
pwStr[strlen(aStr)-b-1] = '\0';
lv = atoi(&aStr[strlen(aStr)-1]);
if (strncmp(pwStr, psStr, PSWD_SIZE)==0)
{
    goto line45;
}
else
{
    a = a + 1;
    if (a<=nm) goto line35;
}

*** I wrote my own INSTR() routines to replicate the BASIC command. Instead of comparing a string with IF A$=B$, the C string compare strcmp() was used.

line40: // for empty userlog bug
//40 PRINT"INVALID":KY=KY+1:IFKY20THEN30
print();
printSemi(F("Full Name :"));
uc = 1;
gosub1005();
strncpy(nmStr, aStr, NAME_SIZE);
if (aStr[0]=='\0' || strlen(aStr)>20) goto line30;

//70 PRINT"Password  :";:UC=1:GOSUB1005:PW$=A$:IFA$=""ORLEN(A$)>8THEN30
printSemi(F("Password  :"));
uc = 1;
gosub1005();
strncpy(pwStr, aStr, PSWD_SIZE);
if (aStr[0]=='\0' || strlen(aStr)>8) goto line30;

//75 PRINT:PRINT"Name :"NM$:PRINT"Pswd :"PW$:PRINT"Is this correct? ";:UC=1:GOSUB1005:IFLEFT$(A$,1)="Y"THEN80ELSE65
print();
printSemi(F("Name :"));
print(nmStr);
printSemi(F("Pswd :"));
print(pwStr);
printSemi(F("Is this correct? "));
uc = 1;
gosub1005();
if (aStr[0]=='Y')
{
    goto line80;
}
else
{
    goto line65;
}

*** I could have written something to handle LEFT$, but since I only use it to look at the first character of a string, I decided to just do it the C way and look at the first character of the string.

line80:
//80 NM=NM+1:NM$(NM)=NM$+""+PW$+"0":LV=0:KY=0
nm = nm + 1;
strncpy(nmArray[nm], nmStr, NAME_SIZE);
strcat_P(nmArray[nm], PSTR("\"));
strncat(nmArray[nm], pwStr, PSWD_SIZE);
//strcat_P(nmArray[nm], PSTR("0"));
strcat_P(nmArray[nm], PSTR("1")); // AUTO VALIDATED
//lv = 0;
lv = 1;
ky = 0;
//85 PRINT"Your password will be validated as soon as time permits.  Press":PRINT"[ENTER] to continue :";:GOSUB1005
print(F("Your password will be validated as soon as time permits.  Press"));
printSemi(F("[ENTER] to continue :"));
gosub1005();

*** BASIC allows building strings just by adding them up. I used the C string concatanate strcat() routine to do the same. I have some lines commented out, because the original made all new users unvalidated, and for testing, I wanted someone to be able to register and actually use the system.

//100 'Main Menu
line105:
//105 A$="ALL RAM BBS Master Menu":GOSUB1055
strncpy_P(aStr, PSTR("ALL RAM BBS Master Menu"), INPUT_SIZE);
gosub1055();

//110 PRINT"C-all Sysop","P-ost Msg":PRINT"G-oodbye","R-ead Msg":PRINT"U-serlog","S-can Titles"
printSemi(F("C-all Sysop"));
printComma();
print(F("P-ost Msg"));
printSemi(F("G-oodbye"));
printComma();
print(F("R-ead Msg"));
printSemi(F("U-serlog"));
printComma();
print(F("S-can Titles"));

line115:
//115 PRINTBR$
print((__FlashStringHelper*)brStr);

*** The weird casting to __FlashStringHelper* is something needed to let the print() routine handle string variables that are stored in Flash.

line120:
//120 KY=KY+1:IFKY>200THENPRINT"Sorry, your time on-line is up.":GOTO210ELSEIFKY>180THENPRINT"Please complete your call soon."
ky = ky + 1;
if (ky>200)
{
    print(F("Sorry, your time on-line is up."));
    goto line210;
}
else if (ky>180)
{
    print(F("Please complete your call soon."));
}

line125:
//125 PRINTTAB(7)"?=Menu/Command :";:UC=1:GOSUB1005:A$=LEFT$(A$,1)
printTab(7);
printSemi(F("?=Menu/Command :"));
uc = 1;
gosub1005();
aStr[1] = '\0';

line130:
//130 LN=INSTR("?CGRSPU%",A$):IFLN=0THENPRINT"Invalid Command":GOTO120
ln = instr("?CGRSPU%", aStr);
if (ln==0)
{
    print(F("Invalid Command"));
    goto line120;
}

//135 IFLV5THENPRINT" Sorry, you are not validated.":GOTO125
if (lv5)
{
    print(F(" Sorry, you are not validated."));
    goto line125;
}

//140 ONLN GOTO105,155,205,405,455,305,255,505
if (ln==1) goto line105;
if (ln==2) goto line155;
if (ln==3) goto line205;
if (ln==4) goto line405;
if (ln==5) goto line455;
if (ln==6) goto line305;
if (ln==7) goto line255;
if (ln==8) goto line505;

*** A C switch()/case might have been closer to ON GOTO, but I just chose to do it the brute force way.

//150 'Call Sysop
line155:
//155 A$="Calling the Sysop":GOSUB1055:A=0
strncpy_P(aStr, PSTR("Calling the Sysop"), INPUT_SIZE);
gosub1055();
a = 0;

*** Annoying. I should have known it was “SysOp” and not “Sysop” back then. It stands for System Operator.


//165 PRINT" BEEP!";:SOUND150,5:IFINKEY$=CHR$(12)THEN175ELSEprintING$(5,8);:A=A+1:IFA";A;:GOSUB1005:MS$(MS,A)=A$:IFA$=""THENA=A-1:GOTO345ELSEIFA", a);
gosub1005();
strncpy(msArray[ms][a], aStr, INPUT_SIZE);
if (aStr[0]=='\0')
{
    a = a - 1;
    goto line345;
}
else if (a

*** Since I did not create a LEFT$ function, I cheat and just hack the original aStr to be 2 characters long, by placing a NULL terminator character in position three. C strings are base-0, so it is [0][1][2].

//365 PRINT"Line currently reads:":PRINTMS$(MS,LN):PRINT"Enter new line:":GOSUB1005:A$=LEFT$(A$,64):IFA$=""THENPRINT"Unchanged"ELSEMS$(MS,LN)=A$:PRINT"Corrected"
print(F("Line currently reads:"));
print(msArray[ms][ln]);
print(F("Enter new line:"));
gosub1005();
aStr[INPUT_SIZE-1] = '\0';
if (aStr[0]=='\0')
{
    print(F("Unchanged"));
}
else
{
    strncpy(msArray[ms][ln], aStr, INPUT_SIZE);
    print(F("Corrected"));
}

*** You will see that wherever I need to shorten a string using LEFT$, I just do the trick of NULL terminating it at that position. The -1 is because the C string is base-0, but BASIC counts the string characters starting at 1. I think.

//370 GOTO360
goto line360;

line375:
//375 CLS:PRINTCL$"Message Reads:":FORB=1TOA:PRINTUSING"##>";B;:PRINTMS$(MS,B):NEXTB:GOTO345
cls();
print((__FlashStringHelper*)clStr);
print(F("Message Reads:"));
for (b=1; b< =a; b++) {
    printUsingSemi("##>", b);
    print(msArray[ms][b]);
}
goto line345;

*** PRINT USING in BASIC could do a bunch of things, but I was only using it to print a number without spaces before and after it, which is what happens in BASIC if you just print a numeric variable.

line380:
//380 MS$(MS,0)=T$+""+F$+""+S$:MS=MS+1:PRINT"Message"MS"stored":GOTO115
strcpy(msArray[ms][0], tStr);
strcat_P(msArray[ms][0], PSTR("\"));
strcat(msArray[ms][0], fStr);
strcat_P(msArray[ms][0], PSTR("\"));
strcat(msArray[ms][0], sStr);
ms = ms + 1;
printSemi(F("Message"));
printSemi(ms);
print(F("stored"));
goto line115;

line385:
//385 PRINT"Message Aborted":GOTO115
print(F("Message Aborted"));
goto line115;

//400 'Read Msg
line405:
//405 IFMS=0THENPRINT"The message base is empty.":GOTO115
if (ms==0)
{
    print(F("The message base is empty."));
    goto line115;
}

//410 CLS:PRINTCL$
cls();
print((__FlashStringHelper*)clStr);

line415:
//415 PRINT"Read Message 1 -"MS":";:GOSUB1005:A=VAL(LEFT$(A$,2)):IFAMS THEN115
printSemi(F("Read Message 1 -"));
printSemi(ms);
printSemi(F(":"));
gosub1005();
aStr[2] = '\0';
a = atoi(aStr);
if (ams) goto line115;

//420 A$=MS$(A-1,0):B=INSTR(A$,""):C=INSTR(B+1,A$,""):T$=LEFT$(A$,B-1):F$=MID$(A$,B+1,C-B-1):S$=RIGHT$(A$,LEN(A$)-C)
strncpy(aStr, msArray[a-1][0], INPUT_SIZE);
b = instr(aStr, "\");
c = instr(b+1, aStr, "\");
strncpy(tStr, aStr, b-1);
tStr[b-1] = '\0';
strncpy(fStr, (aStr-1)+b+1, c-b-1);
fStr[c-b-1] = '\0'; // FIXTHIS - max copy sizes here?
strncpy(sStr, right(aStr, strlen(aStr)-c), SB_SIZE);

//425 IFS$="E-Mail"ANDLV<8THENIFNM$<>T$ANDNM$<>F$THENPRINT"That message is private.":GOTO415
if (strcmp(sStr, "E-Mail")==0 && lv<8)
{
     if (strcmp(nmStr, tStr)!=0 && strcmp(nmStr, fStr)!=0)
    {
        print(F("That message is private."));
        goto line415;
    }
}

//430 CLS:PRINTCL$"Message #"A:PRINT"From :"F$:PRINT"To   :"T$:PRINT"Subj :"S$:PRINT:B=0
cls();
print((__FlashStringHelper*)clStr);
printSemi(F("Message #"));
print(a);
printSemi(F("From :"));
print(fStr);
printSemi(F("To   :"));
print(tStr);
printSemi(F("Subj :"));
print(sStr);
print();
b = 0;

line435:
//435 B=B+1:PRINTMS$(A-1,B):IFMS$(A-1,B)=""THEN440ELSEIFB"?DROWSSAP"THENPRINT"Thank You!":GOTO115
printSemi(F("PASSWORD?"));
gosub1005();
if (strcmp(aStr, "?DROWSSAP")!=0)
{
    print(F("Thank You!"));
    goto line115;
}

*** And now you know the super secret shutdown password.

//515 PRINT"Abort BBS? YES or NO? ";:UC=1:GOSUB1005:IFA$<>"YES"THEN115
printSemi(F("Abort BBS? YES or NO? "));
uc = 1;
gosub1005();
if (strcmp(aStr, "YES")!=0) goto line115;

//520 GOSUB605:STOP
gosub605();
return;
} // end of allram()

*** Next we have the subroutines, which were originally GOSUBs. Since I was not able to hook up a cassette recorder to my Arduino, I chose to just fake the load and save routines.

/---------------------------------------------------------------------------/
    // Subroutines (formerly GOSUBs)
    //
    // 550 '%LOAD%
    void gosub555()
{
    // 555 PRINT"%LOAD% [ENTER] WHEN READY";:GOSUB1005
    printSemi(F("%LOAD% [ENTER] WHEN READY"));
    //  gosub1005();
    print();
    if (aStr[0] == '!')
        return;

    // 560 OPEN"I",#-1,"USERLOG":INPUT#-1,CL,NM:FORA=0TONM:INPUT#-1,NM$(A):NEXTA:CLOSE
    loadUserlog();

    // 565 OPEN"I",#-1,"MSG BASE":INPUT#-1,MS:FORA=0TOMS-1:FORB=0TO10:INPUT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN
    loadMsgBase();
}

// 600 '%SAVE%
void gosub605()
{
    // 605 PRINT"%SAVE% [ENTER] WHEN READY";:GOSUB1005:MOTORON:FORA=0TO999:NEXTA
    printSemi(F("%SAVE% [ENTER] WHEN READY"));
    gosub1005();

    // 610 OPEN"O",#-1,"USERLOG":PRINT#-1,CL,NM:FORA=0TONM:PRINT#-1,NM$(A):NEXTA:CLOSE
    saveUserlog();

    // 615 OPEN"O",#-1,"MSG BASE":PRINT#-1,MS:FORA=0TOMS-1:FORB=0TO10:PRINT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN
    saveMsgBase();
}

// 1000 'User Input
#define CR 13
#define INBUF_SIZE 64
void gosub1005()
{
    byte ch; // Used only here, so we can make it local.

    // 1005 LINEINPUTA$:A$=LEFT$(A$,64):IFUC=0ORA$=""THENRETURN
    lineinput(aStr, INPUT_SIZE);
    if ((uc == 0) || (aStr[0] == '\0'))
        return;

    // 1010 FORC=1TOLEN(A$):CH=ASC(MID$(A$,C,1)):IFCH>96THENMID$(A$,C,1)=CHR$(CH-32)
    for (c = 0; c96)
        aStr[c] = ch - 32;
    // 1015 IFCH=92THENMID$(A$,C,1)="/"
    if (ch == 92)
        aStr[c] = '/';
    // 1020 NEXTC:UC=0:RETURN
}
uc = 0;
}

// 1050 'Function Border
void gosub1055()
{
    // 1055 CLS:PRINTCL$BR$:PRINTTAB((32-LEN(A$))/2)A$:PRINTBR$:RETURN
    cls();
    print((__FlashStringHelper)clStr);
    print((__FlashStringHelper)brStr);
    printTab((32 - strlen(aStr)) / 2);
    print(aStr);
    print((__FlashStringHelper *)brStr);
}

*** Now we try to recreate some of the BASIC commands I used. Or at least just enough of them to get the desired results.

/---------------------------------------------------------------------------/
// The following functions mimic some of the Extended Color BASIC commands.

// CLS
// Clear the screen.
void cls()
{
    print(F("n--------------------------------n"));
}

// SOUND tone, duration
// On the CoCo, tone (1-255), duration (1-255; 15=1 second).
void sound(byte tone, byte duration)
{
    Serial.write(0x07);   // BEL
    delay(duration*66.6); // Estimated delay.
}

/---------------------------------------------------------------------------/
// String functions.

// STRING$(length, charcode)
// Generate a string of length charcode chracters.
void string(byte length, byte charcode)
{
int i;

for (i=0; i0)
{
ch = Serial.read();
}
else
{
continue; // No data. Go back to the while()...
}
switch(ch)
{
case -1: // No data available.
break;
case CR:
  print();
  cmdLine[cmdLen] = '&#92;&#48;';
  done = true;
  break;

case BS:
  if (cmdLen&gt;0)
  {
    printCharSemi(BS);
    printSemi(F(&quot; &quot;));
    printCharSemi(BS);
    cmdLen--;
  }
  break;

default:
  // If there is room, store any printable characters in the cmdline.
  if (cmdLen31) &amp;&amp; (ch&lt;127)) // isprint(ch) does not work.
    {
      printCharSemi(ch);
      cmdLine[cmdLen] = ch; //toupper(ch);
      cmdLen++;
    }
  }
  else
  {
    printCharSemi(BEL); // Overflow. Ring 'dat bell.
  }
  break;
} // end of switch(ch)
} // end of while(!done) return cmdLen; } // INKEY$ // Return character waiting (if any) from standard input (not ethernet). char inkey() { if (Serial.available()==0) return 0; return Serial.read(); } /---------------------------------------------------------------------------/ // File I/O // Ideally, I would have created wrappers for the OPEN, READ, CLOSE commands, // but I was in a hurry, so... //560 OPEN"I",#-1,"USERLOG":INPUT#-1,CL,NM:FORA=0TONM:INPUT#-1,NM$(A):NEXTA:CLOSE void loadUserlog() { print(F("(USERLOG would be loaded from tape here.)")); } //565 OPEN"I",#-1,"MSG BASE":INPUT#-1,MS:FORA=0TOMS-1:FORB=0TO10:INPUT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN void loadMsgBase() { print(F("(MSGBASE would be loaded from tape here.)")); } //610 OPEN"O",#-1,"USERLOG":PRINT#-1,CL,NM:FORA=0TONM:PRINT#-1,NM$(A):NEXTA:CLOSE void saveUserlog() { print(F("save USERLOG")); } //615 OPEN"O",#-1,"MSG BASE":PRINT#-1,MS:FORA=0TOMS-1:FORB=0TO10:PRINT#-1,MS$(A,B):NEXTB:NEXTA:CLOSE:RETURN void saveMsgBase() { print(F("save MSGBASE")); }

*** Next, the PRINT routines. Due to the way things work with all these Flash based strings, I had to duplicate some of these routines with the only difference being what type of parameter was passed in (RAM string versus Flash string). This was the first time I really made use of any C++ functionality.

/---------------------------------------------------------------------------/
// Print (output) routines.

// For TAB to work, we need to track where we think we are on the line.
byte tabPos = 0;

// We want to simulate the following:
// PRINT "HELLO"  -- string, with carraige return at end of line
// PRINT A        -- number (space before and after), with carraige return
// PRINT "HELLO"; -- no carraige return
// PRINT TAB(5);  -- tab to position 5
// PRINT "A","B"  -- tab to column 16 (on 32-column screen)

// Due to various types of strings on Arduino (in memory or Flash), we will
// have to duplicate some functions to have versions that take the other
// types of strings.

// printTypeSemi() routines will not put a carraige return at the end.

// PRINT TAB(column);
// PRINT TAB(column);
void printTab(byte column)
{
while(tabPos10)
{
tempNum = tempNum/10;
numDigits++;
}
while(numDigits

And there you have it, for better or for worse. I will follow up with a full posting of all the source, including the Ethernet and SD card routines (but there’s not enough memory to use them both at the same time).

More to come…

Extended Color BASIC to Arduino Sketch, part 3

See also: Part 1Part 2, Part 3, Part 4 and full source.

Just because something can be done, doesn’t mean it should.

“Why are you converting thirty year old BASIC programs to C?” you might ask… Well, mostly just to see how easily it could be done, but I admit, nostalgia has a big part in this. I am not quite sure what led me to looking in to my 1983 BBS program, but once I saw it running on the XRoar CoCo emulator, it brought back all kinds of fond memories of sitting in front of a TV set typing in lines of BASIC code.

I believe I got in to computers at the very best time. A few years earlier, and it was punch cards and flip switches. A decade or so later, and computers were becoming appliances. There was a bit of time in between when owning a computer primarily meant programming a computer. I got in to it late enough that I didn’t have to be a hardware guy to build my own computer, and early enough that I was still compelled to figure out what to do at the blinking cursor after the “OK” prompt. There just wasn’t nearly as much off-the-shelf software to buy back then, you see. Especially on my allowance.

I realized tonight that there were many like myself who once wrote their own programs and experimented. As time moved on, most of us ended up with a PC or Mac or even Linux machine, and do little more than run software written by others. Thirty years ago, if we wanted to print up a sheet of return address mailing labels, we would type in a few lines of BASIC and be done.

10 FOR I=1 TO 25
20 PRINT #-2, "Allen C. Huffman"
30 PRINT #-2, "PO Box 22031"
40 PRINT #-2, "Clive IA 50325-94014"
50 PRINT #-2
60 NEXT I

Today, if we don’t have a template for MS-Word, we might end up Googling to find some freeware or shareware offering. (I suppose we have also passed up the pre-Internet times when folks had to go to a computer store like Babbages or CompUSA to look for something on the shelf to buy.)

But what about all those BASIC coders that perhaps haven’t ever written even the simplest line of code for a Windows PC? Did they all just decide they no longer liked it? Or did the environment change enough that programming just wasn’t as accessible?

32K CoCo BASIC memory.

Certainly, modern computers no longer power up instantly and present you with a place to type and run a program…

My recent exposure to the Arduino has made me want to program for fun again. Just like my TRS-80 Color Computer, I purchased my Arduino UNO R3 at a nearby Radio Shack. But unlike my CoCo, the Arduino required a host PC/Mac/Linux machine to make it do anything, and then you had to know C/C++. Fortunately, I do… I learned it on my CoCo under OS-9 back in the late 1980s! But what about all those non-OS-9 CoCo owners who stuck with Disk Extended Color BASIC? For them, an Arduino would present quite a learning curve.

But could someone who knows BASIC learn enough to write (very poor) C and actually use an Arduino? I think they could.

void program()
{
    for (i = 0; i < = 25; i++)
    {
        Serial.println("Allen C. Huffman");
        Serial.println("P.O. Box 22031");
        Serial.println("Clive IA 50325-9401");
        Serial.println();
    }
}

Tonight, my *ALL RAM* BBS system is fully functional on my Arduino. The code I originally wrote thirty years ago in BASIC was well structured, with subroutines for items like input and output which translated easily to C functions. Program logic was a mess of if statements and gotos, but since C supports both, it was fairly easy to make the jump. But, it wasn’t pretty. Instead of line numbers, I created C lables in the format of “lineXXX:” where XXX was the desired line number:

line120 :
    // 120 KY=KY+1:IFKY>200THENPRINT"Sorry, your time on-line is up.":GOTO210ELSEIFKY>180THENPRINT"Please complete your call soon."
    ky = ky + 1;
if (ky > 200)
{
    Serial.println("Sorry, your time on-line is up.");
    goto line210;
}
else if (ky > 180)
{
    Serial.println("Please complete your call soon.");
}

And, to make things flow a bit better, I created C functions to replicate some of the BASIC keywords:

line130 :
    // 130 LN=INSTR("?CGRSPU%",A$):IFLN=0THENPRINT"*Invalid Command*":GOTO120
    ln = instr("?CGRSPU%", aStr);
if (ln == 0)
{
    print("*Invalid Command*");
    goto line120;
}

Some things did not have a direct equivalent in C, but could be done in a simple, brute-force manner:

//140 ONLN GOTO105,155,205,405,455,305,255,505
if (ln==1) goto line105;
if (ln==2) goto line155;
if (ln==3) goto line205;
if (ln==4) goto line405;
if (ln==5) goto line455;
if (ln==6) goto line305;
if (ln==7) goto line255;
if (ln==8) goto line505;

I was surprised at how well one could program C as if it were BASIC.

As of tonight, *ALL RAM* BBS runs on my Arduino, complete with userlog and message base. Due to limited memory, there is only room for a few users and a few TINY (Twitter-sized) messages, but it all works. I had to do some Arduino tricks to get strings and such moved in to Flash to free up precious RAM, and once I did that, I even had room to use some SD memory card routines for replacing the original cassette tape load/save routines. (Yes, I realize this is rather silly. There’s no reason to run a RAM-based BBS if you have access to disk storage, but I never said this was a practical experiment.)

I even managed to use my Arduino Ethernet shield and telnet in to the micro BBS. The quick modifications I made to allow this weren’t fully baked — once the connection was broken I had to reset the Arduino — but I believe I will work on that next. (Though probably not with SD card support. I just don’t think there’s enough RAM for both.)

So, in coming days or weeks, I will try to find time to share the code, or at least examples of how I created BASIC-like functions in C.

Until then…

*ALL RAM* BBS Logoff Screen

P.S. – For the younger ones of you out there, yes, there was a time when lowercase was not available. The original TRS-80 Color Computer presented inverse letters to represent lowercase. It wasn’t until the later model “Tandy” Color Computer 2s that the machine got true lowercase. (My CoCo 1 had an aftermarket lowercase board installed in it…) I feel so old.

Extended Color BASIC to Arduino Sketch, part 2

See also: Part 1Part 2, Part 3, Part 4 and full source.

Yesterday, I shared a silly little project I was undertaking where I was going to port a BASIC program to the Arduino, line-by-line. I am glad to say I have successfully done just that, but the practicality of such accomplishment is very questionable.

For programs that were not created to use 21K of RAM for a memory-resident userlog and message base (like my *ALL RAM* BBS program does), it seems fairly easy to port over Microsoft BASIC code and get it running on the Arduino with surprisingly easy changes. I am tempted to find some classic BASIC programs (like ELIZA or Hunt the Wumpus) and try to convert them, as well, just to see if 2K of RAM is enough for hopefully less memory-hungry code.

So let’s talk a bit about memory. The Radio Shack TRS-80 Color Computer came out in 1980 with a 4K model (later generations would support as much as 512K, or megabytes through third party suppliers). In a 4K system, all of the BASIC code took memory, as well as any variables the program used. I expect working in an Arduino is similar to the 1980 experience, except for more program space.

On a 4K computer, if you had a BASIC program that took up 3K, you only had 1K left for variables. On the Arduino UNO, there is 2K of RAM, and 32K of Flash storage to hold the program. So, that same 3K program that only had 1K of RAM for variables on a 4K CoCo would do much better on an Arduino since it could store the 3K program in Flash, then have the full 2K for variables.

32K CoCo BASIC memory.

Unfortunately, my BBS program was designed to work on a 32K CoCo. These early 8-bit computers, even with 64K upgrades, still had a BASIC that only recognized 32K (without loading some extra patch program). And, on power up, some of that memory was reserved to hold four graphics pages. Thus, a 32K CoCo would show 24874 bytes available in BASIC.

You could adjust the amount of memory reserved for graphics screens higher or lower with the “PCLEAR” command. A “PCLEAR 8” reduced memory down to 18727 bytes. 6K (6144 bytes, or 1.5K per graphics page) was consumed. For programs not making use of these graphics, we wanted to get rid of that reserve and use it ourselves. Unfortunately, the PCLEAR command only allowed going down to “PCLEAR 1”, which gave 29479 bytes free for BASIC. That’s a nice bit of extra, but someone clever figured out how to achieve a PCLEAR 0 and get the most memory we could use without patching BASIC: 31015 bytes free.

Note that, once you plugged in a Radio Shack Disk Controller and added Disk Extended Color BASIC, the memory available went down a bit due to overhead of supporting the disk system. For this article, we are looking strictly at a circa 1980 style CoCo with a cassette recorder only.

And what does this have to do with the Arduino? I wanted to make sure you understood the limits I was facing when trying to port a cassette based BBS program to a machine with only 2K of RAM (and not all of that is usable by the user program).

My end result was a fully functional clone of the *ALL RAM* BBS, but with some rather impractical limits. For my demo system, I only allocated enough space to support about four users. Even I have more friends than that. And for messages, the limit was about four messages, each having just two or three lines. I would make a comment about how useless this would be, but Twitter seems to have proven people will use something that even gives less space than my 2K BBS.

So how did I do it? One word: gotos. Lots and lots of gotos. My translation was literal. Even though there are much better, more efficient ways to write this program (which I plan to undertake), I thought it would be fun to see just how easily BASIC could be coded in C.

I started out by taking an ASCII listing of the BASIC program, and adding “//” comment markers to the start of each line. I pasted that in to a fresh Arduino sketch, so it looked like this:

//0 REM *ALL RAM* BBS System 1.0
//1 REM Shareware / (C) 1983
//2 REM By Allen Huffman
//3 REM 110 Champions Dr, #811
//4 REM Lufkin, TX 75901
//5 CLS:FORA=0TO8:READA$:POKE1024+A,VAL("&H"+A$):NEXTA:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3
//10 CLEAR21000:DIMNM$(200),MS$(19,10),A$,F$,S$,T$,BR$,CL$,NM$,PS$,PW$,A,B,C,CL,LN,LV,MS,NM,KY,UC
//15 CL$=CHR$(12)+CHR$(14):BR$="*==============*==============*":GOSUB555
//20 CLS:PRINTTAB(6)"*ALL RAM* BBS SYSTEM":PRINT"USERS:"NM,"CALLS:"CL:PRINTTAB(5)"SYSTEM AWAITING //25 A$="Welcome To *ALL RAM* BBS!":GOSUB1055:KY=0:CL=CL+1
//30 PRINT:PRINT"Password or 'NEW' :";:UC=1:GOSUB1005:PS$=A$:IFA$=""ORA$="NEW"THEN55ELSEPRINT"Checking: ";:A=0

And thus it began. I would go line-by-line and try to write C code for each statement. All C variables would be made global, just like they were in BASIC, and blocks of code called by GOSUBs would become standard C functions (since they return just like BASIC). But, for GOTOs, I decided to just use the C goto, and added labels in the format of “line125:” so I could later “goto line125;” in C.

It looked like it just might work… (And, since I started this story in reverse, you already know that it did, but I will share some of the ways I did it in a future posting.)

Until then… Enjoy having more than 2K of RAM!