-->

Saturday, January 18, 2014

Arm Yourself!

I am building a robotic arm. Ostensibly, when I am done with it, I will attach it to my Whitebox Robotics robot, Tesla, so that Tesla can manipulate its world.

The parts I am using are mainly from a company called ServoCity. This is the first time I have used any parts from them, and so far, I can highly recommend what I have seen. Sparkfun also approves of their products.

Anyway, the arm consists of two 15 inch channels  from ServoCity, and three 1/4 scale winch servos, geared down to have more torque. When it is all built, I expect the arm to be able to lift three to five pounds, fully extended. I've done some tests so far, and it seems to be meeting this expectation. The entire thing is controlled by an Arduino Mega, running FINF (a Forth implementation), which allows me to experiment with the arm really quickly.

Here it is, running through a test sequence:




Sunday, December 8, 2013

CoCo, There's Still Time for You


Ahh, misspent youth. When I was a young man, I caught the bug. Computers. The Radio Shack in the mall put a TRS-80 Model III out in front of their store, and I was intrigued. Intrigue turned to passion, and passion turned into nerdly obsession, and I spent every hour I could standing there, playing with it, learning about it, writing programs in BASIC which I couldn’t save, and which would be gone the next time I could convince my parents to take me to the mall.

READY
>_

From there, I was hooked, and my obsession eventually became a career. My first computer was almost a Timex/Sinclair ZX-81, which I saved for all summer, mowing lawns in order to have the $99 that this wonderful little machine cost. At the end of the summer, my parents intervened, and, since it had the Good Housekeeping seal of approval (and the ZX-81 did not), I ended up with a Radio Shack TRS-80 Color Computer, instead - grey case, with 16K of memory on what was almost a reference implementation of the 6809E chipset.

I was thirteen.

Between the TRS-80s and the Burroughs mainframe running the CANDE OS (pronounced like "candy") with the VDTs and DecWriter printer terminals at the college where my dad was taking night classes, this was my introduction to computer science. I read every book I could in the college library – even the ones I didn’t understand. I learned about Pascal and LISP, BASIC and FORTRAN, Algol 68 and SNOBOL. I learned about the TRS-80 Color Computer. I memorized the memory map. I knew the PEEKs and POKEs. I became an expert on the hardware – and then I started hacking it. Primitive relay driven robotics projects, toy tanks with wires dangling out of them, wired to the PIA chip in the CoCo. Sensors and bad, poorly connected soldering. Memory upgrades. A floppy drive! No more finicky, unreliable tape backups. And in the end, many years later, a hard drive. 40 Megabytes worth of RLL driven storage, way more reliable than the floppies, thanks to a WD1002A-27X ISA hard drive controller, and an interface kit from Burke and Burke. More space than I would ever use.

I spend a lot of my time
doing things like this.
At the end of it all, I had quite the system. Running Microware OS-9, a multitasking operating system, I had a CGA monitor, a detached keyboard, a hard drive, floppy drives, a printer, and a DecWriter II (later a video terminal) running a secondary shell and login. Through the modem, I had access to the nascent internet through Delphi and CompuServe, and I could send emails to people around the world through the RiBBS BBS system.

Radio Shack, in the heyday of the TRS-80 line, controlled a large part of the home computer and business computer markets, but, eventually, the the IBM PC won. And, like many other computers and video games, my Color Computer ended up in boxes in the attic, full of hardware, cables,  slowly mouldering floppy drives -- and one 40 megabyte RLL hard drive, containing a file system not compatible with any modern system, accessible through hardware increasingly incompatible with modern PCs - or any other hardware. A hard drive containing the only copies some of the first programs I ever wrote, slowly deteriorating on largely inaccessible hardware.

The hard drive in question, with the Color Computer interface. The top board is a Western Digital ISA hard drive board, model WD1002A-27X. The small board underneath it is a Burke & Burke COCO XT board, which is an interface between ISA hard drive boards and the Color Computer expansion bus. Optionally, these came with a real time clock. Mine doesn't have that.


Over the years and decades, I had made some attempts to recover this, to move these files to modern hardware, before the inevitable happened, and the contents of that drive were lost forever.  Many challenges lay in that path, including no clear interface between the CoCo and a PC, no way to directly access the hard drive from modern equipment, and the fragility of the old equipment. In the end, the fragility became a major issue, and rescuing the data from the hard drive became a race for time, as simply accessing data on the disk would tend to corrupt it. There was one program in particular, an adventure game written in Pascal that I wrote, which I very much wanted a copy of. If I could get that file, I would consider the recovery effort to be a success.


A previous attempt at resurrecting the color computer. Here, I put the computer in a modern case with a multi-pak (expansion port). What ultimately doomed this attempt was signal degradation between the computer board and the multi-pak. The combination of multi-pak and computer board is too long to fit in pretty much any computer case, which prompted many attempts at rewiring. Some were more successful than others, but none proved to be 100% reliable.
The case for re-homing the
CoCo parts. Here, it is being
inspected for quality.
The CoCo board and a multi-pak board, re-homed in a modern PC case.
Another view. The nylon bolt is holding a daughterboard in place.
As long as there is no smoke or loud pops, everything is OK.
It works! ..sort of. The hard drive is in the second bay. While I could boot to the hard drive and read it, corruption of the directory structure happened rapidly in this configuration.

Recently, I attempted one more time to salvage these old floppies and the hard drive, and, this time, I succeeded. Here's how I did it, with notes on what worked for me, and what didn't.

 

Access to the hardware

 

Solving the monitor problem

Some of the first problems to solve were simply access to the hardware. The Color Computer uses RF output in an analog format (NTSC, PAL in other parts of the world), which is not compatible with modern televisions, for the most part. Fortunately, I do have an old analog television, just for problems like this, but, it's small, and, the CoCo has never had truly great output for televisions -- wavy lines and other interference are common. There are a couple of possibilities here, and I tried them both. First, I can convert the analog output to VGA using a converter. These converters proved to be a little difficult to find, but I managed to find a decent one on Amazon, which I used for a while. In the end the resolution wasn't there, and the output was a bit fuzzy -- good enough to play old video games, but not good enough for clear text.

The second possibility, at least on the CoCo III, is to use composite output. I have an old Apple ][ monitor, so, I tried this for a while, but, the output was never very crisp, especially at higher resolutions (such as when running OS-9 in 80 column mode), so in the end, eye fatigue did this solution in.

The third possibility, at least on more late model CoCos, is that there is a mostly-CGA-compatible port on the underneath of the machine, which can plug to the (mostly) proprietary CM8 monitor which was sold as an accessory to the CoCo. These monitors are hard to find, having not been made in several decades, but there are other, truly CGA compatible monitors that can be used (if you can find one), usually with a bit of level converting or other soldering involved. In the end, I did -- very luckily! -- score a CM8 monitor on eBay, and, in the end, this was the solution I went with for video output.

The Keyboard Problem

The second problem to conquer, hardware-wise, is the fact that there is not a detachable keyboard for the CoCo. In the past, what I have done to solve this is to find an older keyboard, and re-wire it so that the keys on the keyboard map to the IO matrix that the CoCo expects. This usually involves a lot of soldering, a rather thick cable between the keyboard and the computer, and an irrevocably  altered case. None of these were desirable, so, after a week or so of crouching on the ground typing on the old machine, I broke down and got a PS/2 keyboard interface from Cloud9. This allows me to plug a readily available, modern keyboard into the Color Computer. I had a mini keyboard in the style of the original IBM PC which I used. It's got real springs in the keys and makes some very satisfying, if somewhat loud, clacky sounds while typing. Between that and the coiled cable, it is very retro-awesome.

Rescuing the Files

In order to copy the files off of the floppies and on to something a little more reliable, I used another product from Cloud9, their SuperIDE interface. There are essentially two interfaces like this, one created by Cloud9, and the other put together by the Glenside Color Computer club -- who, incidentally, have been annually hosting the "last" CocoFest for over twenty years at this point. Because the Cloud9 interface supports NitrOS-9, and because NitrOS-9 and DriveWire integration is pretty tight (I used DriveWire later on in the process), I went with the SuperIDE interface. This interface allows me to hook up IDE devices to the CoCo, and access them like regular hard drives. In my case, I used a ZIP drive to do initial backups, and a compact flash card to do the initial booting into NitROS-9. I also used the compact flash to back up RSDOS (ie, native color computer) disks. HDB-DOS, an extension to Disk Extended Color BASIC (or, "DECB" for short), allows a CF card to look like 255 individually addressable floppies. For the first four floppy drives, a command allows you to switch back and forth between real floppy drives and the virtual ones, which allows you to copy between the physical drives and the CF card. After some long nights of babysitting the machine while changing floppies, accompanied by a database to track which slot had which virtual floppy, the easy part of copying the RS-DOS files was done.

This setup also allows for partitioning of a CF card (or other IDE device) to have two partitions -- one, RSDOS "floppies" as described above, and the other, an OS-9 partition. One of the emulated floppies serves as the boot floppy for the OS-9 system, and can be set to execute when the system powers on. This setup requires you to load a custom version of HDB-DOS into one of the ROM banks in the SuperIDE interface (The custom ROM image points to the correct offsets for the partitions), and creating an OS-9 boot floppy image suitable to your system, loaded into the correct "floppy" slot. Make backups, and make backups often! Use a scratch CF card, instead of one you might mind losing data on. Setting this up can be a little tricky, but is well worth the effort. Cloud9 can also set you up with a preformatted CF card, to save you the effort. Many thanks to Mark Marlette and Boisy Pitre over at Cloud9, who answered all my dumb questions and got me un-stuck on more than one occasion!


Access to OS-9

OS-9 proved to be slightly more difficult, especially as I was not to be satisfied by simply backing up the files into a safer medium than that which a dying hard drive provided. I wanted to get these files off of that hard drive, and accessible to a PC. Cloud9 offers a service for this, but I wanted to give it a go first, before admitting defeat. After many false starts, This is what I did:

1. Create a NitrOS-9 boot disk, and alter it to allow access to the old hard drive. RLL and MFM hard drives are not plug and play. Even on the PC, the user of these devices had to know things like the number of cylinders, sectors, the sector size, and the desired interleave factor to get them to work right. In the CoCo, same thing. For the Burke & Burke interface, and using OS-9, you create a driver description file (which uses a core driver module, provided here by B&B ages ago), which contains this information. These are hard to set up. In older versions of OS-9, the order of modules in the OS9Boot file matters, and they must be in contiguous sectors on the disk. Size matters, as well, and the entire process of creating a viable boot disk with a working boot file is very, very finicky. Fortunately, when I was young, I saw the danger of losing this information, once I got it right, and made many backups of the boot floppy. Like, a lot of backups. Ten, maybe twenty. The paranoia, as it turns out, was justified -- these 30 year old floppies started failing pretty regularly once I started accessing them again.

Fragile. So very fragile.

I fortunately had some floppies which were "new" -- never opened, never used (but still decades old). These also proved to be a little unreliable, but less so, and, mostly good enough. From here, with the same (arduous) process described above, I created a NitrOS-9 boot disk, and added in the old drivers. NitrOS-9 is a more modern implementation of OS-9, and is actively maintained (go here for the code base, and here for more information), so, the process is a bit easier these days, but it can still be touch and go. After a few tries, I had access to the old hard drive under NitrOS-9.

From here, make many, many backups of the boot floppy, and continue to the next step.

 
The boards plugged into the final system. From left to right: Floppy drive controller, SuperIDE interface, RS232 Pak, Bluetooth RS232 Pak emulator.

The ZIP drive and floppy drives. The ZIP drive is on the top.

2. Alter the boot disk so that it can talk to the Color Computer and some sort of modern hardware at the same time. Ideally, this would mean that the boot disk would be able to talk to a modern hard drive (a PATA/IDE drive, in my case, since I am using the SuperIDE interface. Interfaces for SCSI exist, as well), and the old RLL/COCO XT drive at the same time. Another very desirable feature would be to talk to a modern PC using something like DriveWire. Having this all at the same time would be ideal.

Unfortunately, it did not work out. I could get the combinations of (DriveWire + COCO XT) and (DriveWire + SuperIDE) to work, but not  (DriveWire + COCO XT + SuperIDE). The problem appears to be the undelying SuperIDE drivers and the COCO XT drivers interfering with each other. Most attempts with all three wouldn't even boot, and things like loading the other drivers (say, COCO XT) after successful boot would at best not be able to access their respective drives, and, at worst, immediately crash the system on loading the driver and device descriptors. Since my goal was not truly to have a system with all these at the same time, but rather to rescue old files, I resolved to use the two combinations, (DriveWire + COCO XT) and (DriveWire + SuperIDE), to do separate pieces of the work. Since I could have (DriveWire + SuperIDE), I used that to copy any OS-9 specific floppies I had over to my IDE drive (an IOMega ZIP drive, which worked out very well for this purpose), and, to rescue the files off the hard drive, I used the combination of (DriveWire + COCO XT). To copy the files using either configuration, I used the built-in OS-9 command, DSAVE, which basically issues a bunch of directory change and COPY commands, to copy each individual file. Depending on memory configuration, you may need to direct the DSAVE output to a file first, and then execute it. In OS-9, you can preload modules to make execution faster (for instance, you may preload and hold resident in memory the DIR, CHD and LIST commands). I found that I had to remove a lot of the modules I had preloaded in order to get DSAVE to not error out because of too little memory, even on a machine with a 512K expansion board (this is a LOT of memory for a Color Computer, considering the first iterations of the machine came with memory sizes such as 4K and 16K). There is another program available for the CoCo, named "ARC", which, for a lot of things, does a better job of copying directories and subdirectories, but, because of the state of the drive it was copying from, it didn't always work out. I found that the slow, plodding method that DSAVE takes to be more reliable in this situation. ARC, however, is a great tool. It, and a bunch of other really useful tools can be downloaded here.

Note- using the "ex" builtin, if you have Shell+ installed (standard on NitrOS-9), can reduce the memory problem I describe here. 

From here, the procedure was:
  • Start DriveWire on the PC
  • Ensure connectivity from the CoCo, usually by just doing a DIR on the remotely emulated drive
  • Start DSAVE on the CoCo, and monitor it. I would recommend getting a book and sitting there babysitting it, because, while reliable, there will from time to time be hiccups, and you'll need to restart it occasionally.

DriveWire

DriveWire is an application that runs on a PC, which allows access to the Color Computer. It can emulate hard drives through special drivers under OS-9, and even do things like allow the Color Computer to host web sites. Communication from the PC to the Color Computer is through the bitbanger serial port, which is the built in serial port on the back of the CoCo. It can also communicate with Color Computer emulators, such as the VCC Emulator created by Steve Bjork, via TCP/IP.

DriveWire 4, showing an emulated hard drive and two floppy drives.
 

After quite a few hours, the copy process was done. There were read errors on the old hard drive, as is to be expected, but I'd guess that about 90% of what was on there was successfully copied. At some future date, I may try to repair the hard drive sector by sector, but I don't actually have high hopes for that, since every read tends to corrupt it just a little bit more.

I did, however, find many cool things on that drive!

What I found on the hard drive

It's amazing, looking back in time to where I was about two decades ago. The last that this machine was heavily used was somewhere around 1992 to 1995.

Languages, Languages, Languages

I have always loved playing around with different computer languages. Aside from the standbys of C and Pascal (of which I have two versions each, from Microware and Frank Hogg Laboratories/DynaSoft), I also have compilers for languages such as LISP (xlisp in this case), LOGO (fun! this language can do a lot more than just turtle graphics!), and PILOT (a programming language for teachers, allowing them to create online lessons), and Basic09 (a cleaned up, Pascal-like version of BASIC made specifically for the OS-9 operating system).

Snapshot of life

There were also grocery lists, to do lists, old, low resolution GIFs, lists of phone numbers, and letters to an old girlfriend (Did she really used to call me that? How embarrassing.) There were also emails and posts from the Delphi and CompuServe systems (but not the missing archives, sorry), as well as emails sent across the RiBBs BBS system, and various online manuals. There were also copies of Tandy's productivity software, such as DeskMate and MultiVue, which is a windowing system for OS-9. MultiVue is still under active development. Latest versions, source code repository, and news can be found here.

An older version of MultiVue running on my CoCo, output to my TV. The drives are listed along the left side, with details in the middle. This is showing the top level directory for floppy drive 0, which is a bootable drive (note the os9boot file). MultiVue is capable of launching programs, managing windows, and so forth.


In the end, I did find a copy of the program that, for me, had become symbolic of this rescue effort, and the measure of success as to whether my rescue attempts, in the end, came through.

I was successful. I rescued it from the old, dying system, and now have many backups of it, and many other programs like it. Named ADVGAME.PAS, it is a youthful attempt at creating a text-based adventure game.

Here's the code:

PROGRAM SWAMP (INPUT,OUTPUT);
 (* THE SWAMP:
     AN ADVENTURE GAME
     FEATURING AN ENTIRE WORLD;
     YOU DO NOT HAVE TO DO THE 
     ADVENTURE, BUT IT IS THERE WHEN
     YOU ARE READY TO GO FIND IT. *)
 CONST XCOORD=0;           (* THESE ARE FOR THE *)
       YCOORD=1;           (* COORDINATES KEPT IN ARRAYS *)
       ZCOORD=2;
       SWALL=40;           (* SOUTHMOST SQUARE ON THE BOARD *)
       WWALL=40;           (* WESTMOST SQUARE*)
       BS=8;               (* BACKSPACE CHARACTER *)
       EOF=27;             (* END OF FILE CHARACTER *) 
       CR=13;              (* CARRIAGE RETURN CHARACTER *)
       MAXLOOSE=200;      (* MAX AMOUNT OF LOOSE OBJECTS *)
       MAXHELD=10;        (* MAX AMOUNT OF OWNED OBJECTS *)
       HIDDEN=0;          (* THESE ARE FOR THE ARRAY *)
       CLEAR=1;           (* NAMED 'VISIBILITY' *)
       BURIED=3;          (* SEE BELOW,IN THE 'VAR' SECTION *)

  TYPE LINE=ARRAY [0..255] OF CHAR;
       COMMANDS=(NOP,NORTH,SOUTH,EAST,WEST,
                 CHOP,BURY,DIG,
                 LOOK,DRINK,TAKE,MACHETE,
                 HIDE,JUNGLE,WEEDS,HANDS,
                 WOOD,STICKS,ROCK,GESTURE,
                 ALL,TREE,JUNK,
                 DECIPHER,EXAM,WATER,
                 EAT,BUGS,
                 DROP,INVENT,GUN,BOMB);
       (* ADDITIONS TO THE LIST OF COMMANDS WILL 
          CAUSE NEED FOR ALTERATION IN THE FOLLOWING 
          PROCEDURES/FUNCTIONS:
          PARSE,ACTION,   
          NAMEOFIT       *)

   VAR YOU:ARRAY[XCOORD..ZCOORD] OF INTEGER;       (*COORDINATES OF THE PLAYER ON THE FIELD *)
       UNDERSTOOD,         (* FLAG FOR WHETHER OR NOT THE LAST LINE *)
                           (* WAS UNDERSTOOD *)
       MOVEMENT,           (* WAS THERE MOVEMENT THIS TURN? *)
       HIDING,             (* WHETHER OR NOT PERSON IS HIDING *)
       GAMING:BOOLEAN;     (* FLAG FOR WHETHER OR NOT THE GAME WILL CONTINUE *)
       INLINE:LINE;        (* THE INPUT LINE *)
       HPOS:COMMANDS;      (* THE POSITION OF THE HANDS *)
       INPARSED:ARRAY[1..100] OF COMMANDS; (* THE PARSED INPUT LINE *)
       BACKSPACE:CHAR;     (* THE BACKSPACE CHAR *)
       WORD:INTEGER;       (* THE NUMBER OF WORDS *)
                           (* PARSED *)
       LOOP:INTEGER;       (*GENERAL PURPOSE LOOP VARIABLE *)
       HUNGER,             (* HUNGER OF PLAYER *)
       THIRST:INTEGER;     (* THIRST OF INDIVIDUAL *)
       FIELD:ARRAY[0..1600] OF CHAR;
                           (* THE PLAYING FIELD *)
                           (* ARRAYTOP FOR FIELD MUST EQUAL 
                             SWALL*WWALL *)
       SPECIAL:ARRAY [1..100] OF INTEGER;
                           (* SEE THE PROCEDURE 'DESCSP' *)
                           (* IN 'DESCRIBE' *)
       LOOSESTUFF:ARRAY[1..MAXLOOSE] OF COMMANDS; (* STUFF LYING AROUND *)
       VISIBILITY,         (* VISIBILITY OF THE LOOSE STUFF *)
       LOCATION:ARRAY [1..MAXLOOSE] OF INTEGER; (* LOCATIONS OF LOOSESTUFF *)
       STUFF:ARRAY[1..MAXHELD] OF COMMANDS; (* STUFF HELD BY PLAYER *)



  FUNCTION UNCOORD(X,Y:INTEGER):INTEGER;
   (* THIS FUNCTION CONVERTS X,Y COORDINATES TO ONE

      DIMENSIONAL COORDINATES ON A LINE WHICH
      IS THE LENGTH OF SWALL TIMES WWALL.
      IN OTHER WORDS,THE FIELD. *)
   BEGIN (* UNCOORD *)
    UNCOORD:=(Y*WWALL)+X;
   END;  (* UNCOORD *)



 (* THE FUNCTIONS N,S,E, AND W
    ARE FOR MOVEMENT OF THE PLAYER ACROSS THE BOARD
    N:NORTH
    S:SOUTH
    E:EAST
    W:WEST       *)

 FUNCTION N(I:INTEGER):INTEGER;
  BEGIN
   I:=I-1;
   IF I<0 data-blogger-escaped-begin="" data-blogger-escaped-end="" data-blogger-escaped-function="" data-blogger-escaped-i:="I+1;" data-blogger-escaped-i="" data-blogger-escaped-if="" data-blogger-escaped-movement:="TRUE;" data-blogger-escaped-n:="I;" data-blogger-escaped-s="" data-blogger-escaped-then="">SWALL THEN I:=SWALL;
   S:=I;
   MOVEMENT:=TRUE;
  END;



 FUNCTION E(I:INTEGER):INTEGER;
  BEGIN
   E:=N(I);
   MOVEMENT:=TRUE;
  END;



 FUNCTION W(I:INTEGER):INTEGER;
  BEGIN
   I:=I+1;
   IF I>WWALL THEN I:=WWALL;
   W:=I;
   MOVEMENT:=TRUE;
  END;

 (* THE FOLLOWING COMMANDS ARE FOR 
     PROCESSING CERTIAN COMMANDS,SUCH AS
      INVENTORY,TAKE,DROP,ET CETERA.
     SOME OF THESE COMMANDS WILL NOT BE USED VERY
     MANY TIMES IN THE PROGRAM,AND,IN FACT,
     SOME OF THEM WILL BE USED ONLY AT ONE
     SPOT. NEVERTHELESS,THEY ARE PRESENTED AS FUNCTIONS
     TO INCREASE THE PROGRAM'S MODULARITY AND TO INCREASE READABILITY. *)

 PROCEDURE NAMEOFIT(WHICH:COMMANDS);
  (* WRITES OUT THE NAME OF A COMMAND *)
   BEGIN (* NAMEOFIT *)
    CASE WHICH OF 
     GUN:WRITE(' GUN ');
     MACHETE:WRITE(' MACHETE ');
     DRINK:WRITE(' DRINK ');
     NORTH:WRITE(' NORTH ');
     STICKS:WRITE(' BUNCH OF STICKS ');
     HANDS:WRITE(' HANDS ');
     BUGS:WRITE(' BUGS ');
     ROCK:WRITE(' ROCK ');
     WOOD:WRITE(' PIECE OF WOOD');
     SOUTH:WRITE(' SOUTH ');
     EAST:WRITE(' EAST ');
     WEST:WRITE(' WEST ');
     DROP:WRITE(' DROP ');
     TAKE:WRITE(' TAKE ');
     BOMB:WRITE(' BOMB ')
    END; (* CASE OF WHICH *)
   END; (* NAMEOFIT *)


 FUNCTION HAVE(WHAT:COMMANDS):BOOLEAN;
  (* DOES THE PLAYER HAVE A CETIAN ITEM? *)
  VAR I:INTEGER;
  BEGIN (* HAVE *)
   HAVE:=FALSE;
   FOR I:=1 TO MAXHELD DO
    IF STUFF[I]=WHAT THEN HAVE:=TRUE;
  END; (* HAVE *)



 FUNCTION HERE(WHAT:COMMANDS):BOOLEAN;
  (* IS A CERTIAN ITEM LYING ABOUT?
     ALSO:
      IT MUST BE IN PLAIN SIGHT *)
  VAR I:INTEGER;
      PLACE:CHAR;
  BEGIN (* HERE *)
   HERE:=FALSE;
   FOR I:=1 TO MAXLOOSE DO
    IF (LOCATION[I]=UNCOORD(YOU[XCOORD],YOU[YCOORD])) AND
       (VISIBILITY[I]=CLEAR) AND
       (LOOSESTUFF[I]=WHAT) THEN 
        HERE:=TRUE;
   IF HAVE(WHAT) THEN HERE:=TRUE;
   PLACE:=FIELD[UNCOORD(YOU[XCOORD],YOU[YCOORD])];
   CASE WHAT OF
    JUNGLE:IF PLACE='J' THEN HERE:=TRUE;
    WEEDS:IF PLACE='U' THEN HERE:=TRUE;
    TREE:IF PLACE='T' THEN HERE:=TRUE;
    WATER:IF PLACE='W' THEN HERE:=TRUE
   END; (* CASE OF WHAT *)
  END; (* HERE *)


 PROCEDURE MAKEOBJECT(WHAT:COMMAND;XY:INTEGER);
  VAR I:INTEGER;
      MADE:BOOLEAN;        (* WAS AN OBJECT MADE? *)
   BEGIN (* MAKEOBJECT *)
    I:=0;
    MADE:=FALSE;
    REPEAT
     I:=I+1;
     IF LOOSESTUFF[I]=NOP THEN
      BEGIN
       LOOSESTUFF[I]:=WHAT;
       LOCATION[I]:=XY;
       MADE:=TRUE;
      END;
     UNTIL (MADE) OR (I>MAXLOSE);
    END; (* MAKEOBJECT *)



 PROCEDURE OWN(WHAT:COMMANDS);
  VAR I,J:INTEGER;         (* LOOP VARIABLE *)
      OWNERSHIP:BOOLEAN;       (* SUCCESSFUL OR NOT ? *)
  BEGIN (* OWN *)
   OWNERSHIP:=FALSE;
   J:=1;
   REPEAT
    IF (LOCATION[J]=UNCOORD(YOU[XCOORD],YOU[YCOORD])) AND
       (LOOSESTUFF[J]=WHAT) AND
       (VISIBILITY[J]<>BURIED) THEN
     BEGIN
      I:=1;
      REPEAT 
       IF STUFF[I]=NOP THEN
        BEGIN
         OWNERSHIP:=TRUE;
         STUFF[I]:=WHAT;
         LOCATION[J]:=(-1);
         LOOSESTUFF[J]:=NOP;
         VISIBILITY[J]:=CLEAR;
        END;
       I:=I+1;
      UNTIL (OWNERSHIP) OR (I>=MAXHELD);
     END;
    J:=J+1;
   UNTIL (J>=MAXLOOSE) OR (OWNERSHIP);
   IF (HERE(JUNGLE)) AND (WHAT=BUGS) THEN 
    BEGIN
     WRITELN('THEY CRAWL OUT OF YOUR HANDS BEFORE YOU CAN REALLY GRAB THEM.');
     OWNERSHIP:=TRUE;
    END;
   IF NOT OWNERSHIP THEN
    IF NOT HAVE(NOP) THEN 
        WRITELN('YOU ARE CARRYING TOO MUCH STUFF')
     ELSE 
      BEGIN
       WRITE('WHAT ');
       NAMEOFIT(WHAT);
       WRITELN('?');
      END
    ELSE WRITELN('GOT IT.');
  END; (* OWN *)


 PROCEDURE DISOWN(WHICH:COMMAND);
  VAR I:INTEGER;
      DROPPED:BOOLEAN;
  BEGIN (* DISOWN *)
   IF WHICH=ALL THEN
    BEGIN
     WRITELN('YOU DROP EVERYTHING.');
     FOR I:=1 TO MAXHELD DO
      IF STUFF[I]<>NOP THEN DISOWN(STUFF[I]);
    END
    ELSE
   IF WHICH<>NOP THEN
    BEGIN (* IF *)
     I:=0;
     DROPPED:=FALSE;
     REPEAT
      I:=I+1;
      IF STUFF[I]=WHICH THEN
       BEGIN
        STUFF[I]:=NOP;
        MAKEOBJECT(WHICH,UNCOORD(YOU[XCOORD],YOU[YCOORD]));
        DROPPED:=TRUE;
       END;
     UNTIL (DROPPED) OR (I>MAXHELD);
     IF NOT DROPPED THEN WRITELN('YOU DONT HAVE ONE.');
    END (* IF *)
   ELSE WRITELN('YOU HAVE TO TELL ME WHAT TO DROP');
  END; (* DISOWN *)


 PROCEDURE CONCEAL(WHAT:COMMANDS;HOW:INTEGER);
  (* PLEASE NOTE:
      IF AN OBJECT IS NOT HIDEABLE IT 
      WILL BECOME VISIBLE IN THE NEXT TURN *)
  VAR I:INTEGER;           (* LOOP VARIABLE *)
      OK:BOOLEAN;
  BEGIN (* CONCEAL *)
   IF WHAT=ALL THEN 
    BEGIN
     FOR I:=1 TO MAXHELD DO
      IF STUFF[I]<>NOP THEN CONCEAL(STUFF[I],HOW);
    END;
   OK:=FALSE;
   IF (WHAT=NOP) AND (HOW<>BURIED) THEN
    CASE FIELD[UNCOORD(YOU[XCOORD],YOU[YCOORD])] OF
     'J','U':BEGIN
              HIDING:=TRUE;
              OK:=TRUE;
              WRITELN('YOU ARE HIDDEN.');
             END
     OTHERWISE WRITELN('YOU CANT HIDE HERE.')
    END (* CASE OF PLAYER'S LOCATION *)
   ELSE IF HAVE(WHAT) THEN 
    BEGIN (* IF *)
     DISOWN(WHAT);
     I:=0;
     REPEAT
      IF (LOCATION[I]=UNCOORD(YOU[XCOORD],YOU[YCOORD])) AND
       (LOOSESTUFF[I]=WHAT) THEN
       BEGIN
        VISIBILITY[I]:=HOW;
        OK:=TRUE;
       END;
      I:=I+1;
     UNTIL (OK) OR (I>MAXLOOSE);
     IF OK THEN
      BEGIN
       WRITE('IT IS ');
       CASE HOW OF
        BURIED:WRITELN('BURIED');
        HIDDEN:WRITELN('HIDDEN')
       END;
      END
      ELSE WRITELN('YOU DONT HAVE ONE.');
    END; (* IF *)
  END; (* CONCEAL *)


 PROCEDURE UNEARTH;
  VAR I:INTEGER;           (* LOOP VAR *)
  BEGIN (* UNEARTH *)
   FOR I:=1 TO MAXLOOSE DO
    IF LOCATION[I]=UNCOORD(YOU[XCOORD],YOU[YCOORD]) THEN
     BEGIN (* IF *)
      CASE VISIBILITY[I] OF
       HIDDEN:BEGIN
               WRITE('IN YOUR SCUFFLING ABOUT YOU FOUND A');
               NAMEOFIT(LOOSESTUFF[I]);
               WRITELN('.');
              END;
       BURIED:BEGIN
               WRITE('YOU DUG UP A');
               NAMEOFIT(LOOSESTUFF[I]);
               WRITELN('.');
              END
      END; (* CASE OF VISIBILITY *);
      VISIBILITY[I]:=CLEAR;
     END (* IF *);
    END; (* UNEARTH *)

 PROCEDURE MOW(X,Y:INTEGER;TOOL:COMMANDS);
  (* X AND Y ARE THE LOCATION ON THE GRID CHANGE IS
     TO TAKE PLACE;
     TOOL IS THE OBJECT TO BE USED *)
  VAR PLACE:CHAR;          (* PLAYER'S LOCATION IN CHAR FORM *)
     CHANGE:CHAR;         (* WHATEVER IT IS TO BE CHANGED TO *)
  BEGIN
   PLACE:=FIELD[UNCOORD(X,Y)];
   CASE PLACE OF 
    'J':BEGIN
         IF TOOL=MACHETE THEN CHANGE:='U';
         IF TOOL=BOMB THEN CHANGE:='B';
        END;
    'U':BEGIN
         IF TOOL=MACHETE THEN CHANGE:='C';
         IF TOOL=BOMB THEN CHANGE:='B';
         MAKEOBJECT(STICKS,UNCOORD(X,Y));
        END;
    'C':IF TOOL=BOMB THEN CHANGE:='R';
    'R':IF TOOL=BOMB THEN 
         BEGIN
          CHANGE:='R';
          MAKEOBJECT(ROCK,UNCOORD(X,Y));
         END;
    'T':BEGIN
         IF TOOL=BOMB THEN CHANGE:='B';
         IF TOOL=MACHETE THEN CHANGE:='C';
         MAKEOBJECT(WOOD,UNCOORD(X,Y));
        END
    OTHERWISE CHANGE:=PLACE
   END; (* CASE OF PLACE *)
   IF PLACE=CHANGE THEN WRITELN('YOU CANT DO THAT');
   FIELD[UNCOORD(X,Y)]:=CHANGE;
  END; (* CHANGE *)


  PROCEDURE ANNIHILATE(WHAT:COMMANDS);
   (* ANNIHILATES SOMETHING OF THE PLAYER'S POSSESSION *)
   VAR I:INTEGER;
       ZAPPED:BOOLEAN;
   BEGIN (* ANNIHILATE *)
    IF HAVE(WHAT) THEN 
     I:=0;
     ZAPPED:=FALSE;
     REPEAT
      I:=I+1;
      IF STUFF[I]=WHAT THEN
       BEGIN
        ZAPPED:=TRUE;
        STUFF[I]:=NOP;
       END;
     UNTIL (ZAPPED) OR (I>MAXHELD);
   END;


 PROCEDURE SCRAWLINGS(WHAT,MODE:COMMANDS);
  (* IF ANY MARKINGS ARE ON THE OBJECT 'WHAT',
     THIS PROCEDURE WILL TELL OF THEM *)
  BEGIN (* SCRAWLINGS *)
   IF HERE(WHAT) THEN
    CASE MODE OF
     DECIPHER:CASE WHAT OF
           MACHETE:WRITELN('"SEARS,ROEBUCK, & CO."');
           WOOD:BEGIN
                 WRITELN('"DO NOT REMOVE THIS TAG UNDER PENALTY OF LAW"');
                 WRITELN('(ASK A STUPID QUESTION...)');
                END;
           JUNGLE:BEGIN
                   WRITELN('"INGREDIENTS:');
                   WRITELN(' ENRICHED WHEAT FLOUR,');
                   WRITELN(' ANIMAL AND VEGETABLE SHORTENING');
                   WRITELN('  ALL NATURAL FLAVORS"');
                  END;
           BUGS:WRITELN('"PATENT PENDING"');
           BOMB:WRITELN('"!!BOMB EXPLODES ON IMPACT!!"')
          OTHERWISE WRITELN('NOTHING WRITTEN ON IT.')
          END; (* CASE OF WHAT *)
      EXAM:CASE WHAT OF
            WOOD:WRITELN('LOOKS LIKE ORDINARY WOOD TO ME.');
            STICKS:WRITELN('LOOKS LIKE THEY WERE CHOPPED FROM SOME UNDERBRUSH.');
            ROCK:WRITELN('LOOKS LIKE YOU COULD BELT SOMEONE GOOD WITH IT.');
            MACHETE:WRITELN('IT HAS SOMETHING WRITTEN ON IT.');
            JUNGLE:WRITELN('BUGS EVERYWHERE,MAN!');
            BOMB:WRITELN('LOOKS LIKE A BOMB')
           OTHERWISE WRITELN('NOTHING OUT OF THE ORDINARY.')
           END (* CASE OF WHAT *)
     END (* CASE OF MODE *)
   ELSE
    BEGIN
     WRITE('I DONT SEE ANY');
     NAMEOFIT(WHAT);
     WRITELN('.');
    END;
  END; (* SCRAWLINGS *)


 (* THIS IS THE END OF THE SPECIAL COMMAND SECTION *)


 PROCEDURE MAP(NUM:INTEGER);
  VAR I,J:INTEGER;         (* LOOP VARIABLE *)
  BEGIN (* MAP *)
   CASE NUM OF 
    1:BEGIN
       FOR J:=1 TO SWALL*WWALL DO
         FIELD[J]:='J';
       FIELD[UNCOORD(15,20)]:='T';
       FIELD[UNCOORD(10,10)]:='W';
       FIELD[UNCOORD(40,10)]:='W';
       FIELD[UNCOORD(40,20)]:='W';
       FIELD[UNCOORD(15,20)]:='W';
       FIELD[UNCOORD(15,15)]:='W';
       FOR I:=10 TO 15 DO
        FOR J:=10 TO 15 DO
         FIELD[UNCOORD(I,J)]:='W';
       FIELD[UNCOORD(14,14)]:='T';
       SPECIAL[2]:=UNCOORD(14,14);
       FIELD[UNCOORD(10,10)]:='U';
       FIELD[UNCOORD(15,15)]:='U';
       FOR J:=10 TO 20 DO
        FIELD[UNCOORD(12,J)]:='W';
      VISIBILITY[1]:=HIDDEN;
      LOOSESTUFF[1]:=MACHETE;
      LOCATION[1]:=UNCOORD(WWALL DIV 2,SWALL DIV 2);
      SPECIAL[1]:=UNCOORD(WWALL DIV 2,SWALL DIV 2);
      END;
   10:BEGIN (* NO OPERATION *) END
   END;
  END; (* MAP *)



 FUNCTION COM(WHICH:COMMANDS;WDS:INTEGER):INTEGER;
  BEGIN (* COM *)
   INPARSED[WDS]:=WHICH;
   IF WDS<98 data-blogger-escaped-100:begin="" data-blogger-escaped-100="" data-blogger-escaped-2:case="" data-blogger-escaped-2:if="" data-blogger-escaped-2="" data-blogger-escaped-3:case="" data-blogger-escaped-4:case="" data-blogger-escaped-4="" data-blogger-escaped-5="" data-blogger-escaped-a="" data-blogger-escaped-about="" data-blogger-escaped-actual="" data-blogger-escaped-all="" data-blogger-escaped-amount="" data-blogger-escaped-and="" data-blogger-escaped-anything="" data-blogger-escaped-are="" data-blogger-escaped-area="" data-blogger-escaped-arraytop="" data-blogger-escaped-be="" data-blogger-escaped-begin="" data-blogger-escaped-being="" data-blogger-escaped-blanks="" data-blogger-escaped-by="" data-blogger-escaped-carving="" data-blogger-escaped-case="" data-blogger-escaped-char="" data-blogger-escaped-chars="" data-blogger-escaped-checks="" data-blogger-escaped-clearing="" data-blogger-escaped-com:="WDS+1;" data-blogger-escaped-com="" data-blogger-escaped-comes="" data-blogger-escaped-compiled="" data-blogger-escaped-compute="" data-blogger-escaped-coordinates="" data-blogger-escaped-currently="" data-blogger-escaped-describe="" data-blogger-escaped-described="" data-blogger-escaped-describes="" data-blogger-escaped-descsp="" data-blogger-escaped-do="" data-blogger-escaped-east="" data-blogger-escaped-end="" data-blogger-escaped-eol="" data-blogger-escaped-eplace:="FIELD[UNCOORD(X,E(Y))];" data-blogger-escaped-eplace="WHAT" data-blogger-escaped-field="" data-blogger-escaped-for="" data-blogger-escaped-form="" data-blogger-escaped-function="" data-blogger-escaped-goes="" data-blogger-escaped-growth="" data-blogger-escaped-how="" data-blogger-escaped-i:="1" data-blogger-escaped-i:integer="" data-blogger-escaped-i="" data-blogger-escaped-if="" data-blogger-escaped-in="" data-blogger-escaped-inline="" data-blogger-escaped-inparsed="" data-blogger-escaped-is="" data-blogger-escaped-it="" data-blogger-escaped-j:="1" data-blogger-escaped-j:integer="" data-blogger-escaped-j="" data-blogger-escaped-jungle="" data-blogger-escaped-junkhere="" data-blogger-escaped-l:="1" data-blogger-escaped-l="" data-blogger-escaped-len:="2" data-blogger-escaped-len="" data-blogger-escaped-length="" data-blogger-escaped-lenloop="" data-blogger-escaped-less="" data-blogger-escaped-livingston="" data-blogger-escaped-location="" data-blogger-escaped-long="" data-blogger-escaped-loop="" data-blogger-escaped-maxloose="" data-blogger-escaped-needed="" data-blogger-escaped-neplace:="FIELD[UNCOORD(N(X),E(Y))];" data-blogger-escaped-neplace="WHAT" data-blogger-escaped-no="" data-blogger-escaped-north="" data-blogger-escaped-northeast="" data-blogger-escaped-northwest="" data-blogger-escaped-note="" data-blogger-escaped-nplace:="FIELD[UNCOORD(N(X),Y)];" data-blogger-escaped-nplace="WHAT" data-blogger-escaped-number="" data-blogger-escaped-nwplace:="FIELD[UNCOORD(N(X),W(Y))];" data-blogger-escaped-nwplace:char="" data-blogger-escaped-nwplace="WHAT" data-blogger-escaped-of="" data-blogger-escaped-on="" data-blogger-escaped-one="" data-blogger-escaped-op="" data-blogger-escaped-operation="" data-blogger-escaped-or="" data-blogger-escaped-out="" data-blogger-escaped-parse:="WDS;" data-blogger-escaped-parse:integer="" data-blogger-escaped-parse="" data-blogger-escaped-parsed="" data-blogger-escaped-path="" data-blogger-escaped-place:char="" data-blogger-escaped-place="" data-blogger-escaped-please="" data-blogger-escaped-pointer="" data-blogger-escaped-prints="" data-blogger-escaped-procedure="" data-blogger-escaped-reads="" data-blogger-escaped-remains="" data-blogger-escaped-represented="" data-blogger-escaped-returns="" data-blogger-escaped-rocky="" data-blogger-escaped-scanned="" data-blogger-escaped-see="" data-blogger-escaped-seplace:="FIELD[UNCOORD(S(X),E(Y))];" data-blogger-escaped-seplace="WHAT" data-blogger-escaped-smouldering="" data-blogger-escaped-so="" data-blogger-escaped-some="" data-blogger-escaped-south="" data-blogger-escaped-southeast="" data-blogger-escaped-southwest="" data-blogger-escaped-special="" data-blogger-escaped-specialx="" data-blogger-escaped-splace:="FIELD[UNCOORD(S(X),Y)];" data-blogger-escaped-splace="WHAT" data-blogger-escaped-statement="" data-blogger-escaped-surround="" data-blogger-escaped-swplace:="FIELD[UNCOORD(S(X),W(Y))];" data-blogger-escaped-swplace="WHAT" data-blogger-escaped-tests="" data-blogger-escaped-than="" data-blogger-escaped-that="" data-blogger-escaped-the="" data-blogger-escaped-then="" data-blogger-escaped-to="" data-blogger-escaped-tree="" data-blogger-escaped-value="" data-blogger-escaped-var="" data-blogger-escaped-variable="" data-blogger-escaped-variables="" data-blogger-escaped-way="" data-blogger-escaped-wds:="COM(WATER,WDS);" data-blogger-escaped-wds="" data-blogger-escaped-west="" data-blogger-escaped-what="" data-blogger-escaped-where="" data-blogger-escaped-word="" data-blogger-escaped-words="" data-blogger-escaped-wordval:="WORDVAL+INLINE[J];" data-blogger-escaped-wordval="" data-blogger-escaped-wplace:="FIELD[UNCOORD(X,W(Y))];" data-blogger-escaped-wplace="WHAT" data-blogger-escaped-write="" data-blogger-escaped-writeln="" data-blogger-escaped-x="" data-blogger-escaped-y="" data-blogger-escaped-your:="">NOP) AND
        (VISIBILITY[I]=CLEAR) THEN
      BEGIN
       WRITE('I SEE A');
       NAMEOFIT(LOOSESTUFF[I]);
       WRITELN('.');
      END;
    END; (* JUNKHERE *)


  BEGIN (* DESCRIBE *)
   PLACE:=FIELD[UNCOORD(X,Y)];
   CASE PLACE OF
     'J':WRITELN('YOU ARE IN A THICK JUNGLE');
     'U':WRITELN('YOU ARE IN SOME THICK UNDERGROWTH');
     'C':WRITELN('YOU ARE IN A CLEARING');
     'T':WRITELN('YOU ARE UNDER A TREE');
     'R':WRITELN('YOU ARE IN A VERY ROCKY AREA');
     'W':WRITELN('YOU ARE ON WATER');
     'B':BEGIN
          WRITELN('YOU ARE IN THE SMOULDERING RUINS OF');
          WRITELN('SOME JUNGLE GROWTH');
         END
   END; (* CASE OF PLACE *)
   IF PLACE='J' THEN
    WRITELN('YOU CANT SEE TOO FAR IN ANY DIRECTION')
   ELSE SURROUND;
   DESCSP(X,Y);
   JUNKHERE;
  END; (* DESCRIBE *)



 PROCEDURE ACTION(WDS:INTEGER);
     VAR I,J,K:INTEGER;    (* LOOP VARIABLES *)
      OWNERSHIP:BOOLEAN;   (* USED BY SOME OF THE ROUTINES *)
  BEGIN (* ACTION *)
   FOR I:=1 TO WDS DO
    CASE INPARSED[I] OF 
     NORTH:YOU[XCOORD]:=N(YOU[XCOORD]);
     SOUTH:YOU[XCOORD]:=S(YOU[XCOORD]);
     EAST:YOU[YCOORD]:=E(YOU[YCOORD]);
     WEST:YOU[YCOORD]:=W(YOU[YCOORD]);
     LOOK:MOVEMENT:=TRUE;
     GESTURE:CASE INPARSED[I+1] OF
              NORTH,
              SOUTH,
              EAST,
              WEST:BEGIN
                     HPOS:=INPARSED[I+1];
                     WRITE('YOUR HANDS INDICATE THE');
                     NAMEOFIT(INPARSED[I+1]);
                     I:=I+1;
                   END
             OTHERWISE 
              BEGIN
               HPOS:=INPARSED[I+1];
               WRITELN('YOUR HANDS INDICATE THE');
               NAMEOFIT(INPARSED[I+1]);
              END
             END; (* CASE IF INPARSED[I+1] *)
     HIDE:CONCEAL(INPARSED[I+1],HIDDEN);
     BURY:CONCEAL(INPARSED[I+1],BURIED);
     DIG:UNEARTH;
     DECIPHER:SCRAWLINGS(INPARSED[I+1],DECIPHER);
     EXAM:SCRAWLINGS(INPARSED[I+1],EXAM);
     EAT:BEGIN
         CASE INPARSED[I+1] OF
          BOMB:WRITELN('YEAH. RIGHT.');
          BUGS:IF HERE(JUNGLE) THEN
                BEGIN
                 WRITELN('');
                 HUNGER:=HUNGER+11;
                END;
          MACHETE:WRITELN('I ABSOLUTELY REFUSE TO EAT A MACHETE.');
          ROCK:WRITELN('YOU HAVE WIERD CULINARY TASTES.');
          NOP:WRITELN('EAT WHAT?')
          OTHERWISE WRITELN('THAT ISNT EDIBLE')
         END; (* CASE OF INPARSED *)
        END; (* EAT *)
     CHOP:BEGIN (* CHOP *)
           IF HAVE(MACHETE) THEN 
            CASE INPARSED[I+1] OF
             NOP,          (* IF UNSPECIFIED *)
             JUNGLE,
             WEEDS,
             TREE:MOW(YOU[XCOORD],YOU[YCOORD],MACHETE);
             STICKS:IF HAVE(STICKS) THEN WRITELN('NOW THEY ARE SHORTER.');
             WOOD:IF HAVE(WOOD) THEN
                   BEGIN
                    ANNIHILATE(WOOD);
                    MAKEOBJECT(STICKS,UNCOORD(YOU[XCOORD],YOU[YCOORD]));
                   END;
             GUN:IF HAVE(GUN) THEN
                  BEGIN
                   ANNIHILATE(GUN);
                   MAKEOBJECT(JUNK,UNCOORD(YOU[XCOORD],YOU[YCOORD]));
                  END
             OTHERWISE WRITELN('YOU CANT CUT THAT.');
            END (* CASE OF INPARSED[I+1] *)
          ELSE WRITELN('YOU DONT HAVE THE CORRECT TOOL.');
         END; (* CHOP *)
     TAKE:OWN(INPARSED[I+1]);
     DROP:DISOWN(INPARSED[I+1]);
     INVENT:BEGIN
                OWNERSHIP:=FALSE;
                WRITELN('YOU HAVE:');
                FOR J:=1 TO MAXHELD DO
                 IF STUFF[J]<>NOP THEN
                  BEGIN (* IF *)
                   OWNERSHIP:=TRUE;
                   NAMEOFIT(STUFF[J]);
                  END; (* IF *)
                WRITELN;
                IF NOT OWNERSHIP THEN WRITELN('NOTHING.');
          END;

     DRINK:IF HERE(WATER) THEN
            BEGIN
             THIRST:=THIRST+4;
             CASE THIRST OF
              1,2,3,4,5:WRITELN('AH! AH... REFRSHING! AHHH...');
              6,7,8,9,10:WRITELN('STILL A LITTLE THIRSTY,THOUGH.');
              11,12,13,14,15,16:WRITELN('MY! THAT WAS GOOD.');
              16,17,18,19,20:WRITELN('NOT BAD.')
              OTHERWISE WRITELN('YOU FEEL A BIT WATERLOGGED.')
             END; (* CASE OF THIRST *)
            END
           ELSE WRITELN('DRINK WHAT?');
     NOP: BEGIN (* NO OPERATION *) END
    END; (* CASE OF INPARSED[I] *)
   IF WDS>1 THEN UNDERSTOOD:=TRUE;
  END; (* ACTION *)




 BEGIN (* SWAMP *)
  BACKSPACE:=BS; (* SO IT'S A PRINTABLE CHAR *)
  FOR LOOP:=1 TO MAXHELD DO
   STUFF[LOOP]:=NOP;
  FOR LOOP:=1 TO MAXLOOSE DO
   BEGIN
    LOOSESTUFF[LOOP]:=NOP;
    LOCATION[LOOP]:=(-1);
   END;
  MAP(1);
  HUNGER:=40;
  THIRST:=40;
  GAMING:=TRUE;
  HIDING:=FALSE;
  YOU[XCOORD]:=WWALL DIV 2;
  YOU[YCOORD]:=SWALL DIV 2;
  YOU[ZCOORD]:=0;
  DESCRIBE(YOU[XCOORD],YOU[YCOORD]);
  REPEAT
   FOR LOOP:=1 TO 100 DO  (* CLEAR OUT INLINE *)
    INLINE[LOOP]:=' ';
   (* BEGIN READ INLINE *)
    LOOP:=1;
    WRITE('#'); (* PROMPT *)
    REPEAT
     LOOP:=LOOP+1;
     READ(INLINE[LOOP]);
     IF LOOP<1 data-blogger-escaped-anyhow="" data-blogger-escaped-be="" data-blogger-escaped-because="" data-blogger-escaped-boundary="" data-blogger-escaped-check="" data-blogger-escaped-going="" data-blogger-escaped-if="" data-blogger-escaped-incremented="" data-blogger-escaped-inline="" data-blogger-escaped-is="" data-blogger-escaped-it="" data-blogger-escaped-loop:="LOOP-2;" data-blogger-escaped-then="" data-blogger-escaped-to="" data-blogger-escaped-two="" data-blogger-escaped-until="">99) OR (INLINE[LOOP]=CR);
   IF LOOP<98 data-blogger-escaped--100:writeln="" data-blogger-escaped--110:writeln="" data-blogger-escaped--20:writeln="" data-blogger-escaped--40="" data-blogger-escaped-0:writeln="" data-blogger-escaped-10:writeln="" data-blogger-escaped-1:writeln="" data-blogger-escaped-5:writeln="" data-blogger-escaped-a="" data-blogger-escaped-action="" data-blogger-escaped-almost="" data-blogger-escaped-and="" data-blogger-escaped-are="" data-blogger-escaped-begin="" data-blogger-escaped-case="" data-blogger-escaped-constricting="" data-blogger-escaped-death="" data-blogger-escaped-describe="" data-blogger-escaped-died="" data-blogger-escaped-double="" data-blogger-escaped-end.="" data-blogger-escaped-end="" data-blogger-escaped-food="" data-blogger-escaped-from="" data-blogger-escaped-gaming:="FALSE;" data-blogger-escaped-gaming="" data-blogger-escaped-getting="" data-blogger-escaped-have="" data-blogger-escaped-having="" data-blogger-escaped-hidden.="" data-blogger-escaped-hiding:="FALSE;" data-blogger-escaped-hiding="" data-blogger-escaped-hunger:="HUNGER-1;" data-blogger-escaped-hunger="" data-blogger-escaped-hungry.="" data-blogger-escaped-if="" data-blogger-escaped-inline="" data-blogger-escaped-intestines="" data-blogger-escaped-lack="" data-blogger-escaped-longer="" data-blogger-escaped-loop:="LOOP+1;" data-blogger-escaped-movement:="FALSE;" data-blogger-escaped-movement="" data-blogger-escaped-near="" data-blogger-escaped-no="" data-blogger-escaped-not="" data-blogger-escaped-nourishment.="" data-blogger-escaped-of="" data-blogger-escaped-pang="" data-blogger-escaped-pangs.="" data-blogger-escaped-pre="" data-blogger-escaped-read="" data-blogger-escaped-severe="" data-blogger-escaped-swamp="" data-blogger-escaped-then="" data-blogger-escaped-thirst.="" data-blogger-escaped-thirst:="THIRST-1;" data-blogger-escaped-thirst="" data-blogger-escaped-thirsty="" data-blogger-escaped-unconcious="" data-blogger-escaped-understood:="FALSE;" data-blogger-escaped-understood="" data-blogger-escaped-until="" data-blogger-escaped-up="" data-blogger-escaped-weak="" data-blogger-escaped-word:="PARSE;" data-blogger-escaped-writeln="">
Note: DynaSoft Pascal does not have a string data type.

And there it is  -- rescued from my dimming earlier years, an attempt at a world, an exposition of self taught, budding skills. Time has passed, the hardware no longer relevant, the software relevant to nobody but me. But I had to reach back, through the unrelenting waters of time, and grasp for one distant yet limpid memory, and rekindle that link to my own personal past.

I won. I got it. I made that link. And thirteen? Distancing and ever fading thirteen..  Thirteen, I'm all right with you.



(disclosure: although I mentioned Cloud-9 a bit in this post, I am not an employee, shareholder, etc. I just use their stuff :-) )

Saturday, July 20, 2013

Just a lazy Saturday..

Just a lazy Saturday, poking around with a Whitebox Robotics 914 that I got off of eBay. All in all, a very versatile and well constructed platform. I would highly recommend picking one up. One of the cool things, as you can see a little bit in the video below, is that it is highly modular, and has plenty of room for expandability.




Also, a quick note. If anyone has wondered where the prototype for the Heathkit version of this has wandered off to, her name is "Rosie" (I didn't name her, she is running Windows XP, and that was the name of the onboard computer when I got her), and she is safe and sound. And not for sale.

Here's a brief writeup on Make Magazine, with a picture of Rosie.


Saturday, March 30, 2013

We Didn't Start the Fire

...but a Digispark can sure light things up! A Digispark is a small, cheap Arduino board -- I bought a couple for $8.95 each -- which allows you to leave them in a project without feeling guilty about it or worrying too much about the cost. They are not as capable as a full-blown Arduino, but for smaller (and some medium sized) projects, they are powerful enough, and won't leave you with unused pins on the board, or have you worrying about underutilization of your hardware. Digispark was originally a Kickstarter project that I learned about through my friend 'Dillo, who has a great blog over at Roadknight Labs. I missed out on the Kickstarter, but managed to pick some up after the fact. You can order them directly here.

Incidentally, there are many other really cool Kickstarter projects out there, such as the Parallela, which is a massively parallel single board computer system (can't wait to get my hands on one!), and the 3Doodler, which allows you to draw 3D objects using the same plastics and technology as 3D printers such as the MakerBot and RepRaps. Ones I also have my eye on are the APOC Mini Radiation Detector, and the PowerPot, which allows you to produce electricity while camping by cooking food or boiling water.

What I decided to build with a Digispark was.. this glowy thing.





The basic components are a translucent light cover and a a shadow box I got at a thrift store...



...a Digispark Arduino board (which is about the size of a quarter), along with the Digispark RGB LED shield (which are shown here stacked together)...



...and a couple of switches.



The switch on the left is a numeric up/down switch, which goes from 0 to 6. The wiring on it is pretty simple -- there's a common terminal, and then three others, which indicate the current selection in binary. I couldn't find a datasheet for the switch, but a  little experimentation determined which was the common terminal.

The numeric switch with the leads attached.

From there, I wired up the Digispark directly to a USB cable. When it is being programmed, the device will be plugged directly into a computer, and under normal operation, it will be plugged into a 5v USB power supply.

The Digispark with a USB cable wired directly to it.
I found information for the wiring layout for USB here on Wikipedia. Wikipedia rocks! 

Here is a video of the digispark, wired up to the USB cable, running the demo RGB Arduino program.



From here, I soldered up the numeric switch to the Digispark board, using pins not used by the RGB shield. I used knots to indicate which line was which. The one with no knots is the common line (note -- in the picture, it is soldered to the wrong pin. I had it soldered to +5v, but it should have been connected to ground)



From here, the power switch got soldered to the board...

Adding the wires to the switch. Shrink tubing leaves everything so much better looking when you are done!


...and since the Digispark throws off so much of its power as waste heat, I decided to wire a 1K resistor in serial to the board. It works -- the board powers up with it and is still programmable, so I kept it. Here's hoping it will be a little more efficient!

I am really horrible at remembering how to decode resistor values, so, here's a nifty online calculator!

Putting it all together was fairly straightforward. One thing that needed to be considered, however, was that the Digispark board produces quite a lot of heat, and I needed to isolate it from the wood box I was mounting it on. For this, I just used a bolt and some zip ties. Holes were also cut for the two switches (and a power light), and hot glued from the back to keep them firmly in place.



From here, I decided I would rather have the base black than wood, so I used some chalkboard paint to spray paint it.



In order to not have wires dangling everywhere, I decided to close the box. For this, I repurposed a couple of coke cans...


...sealed up the seam with hot glue, and then added some rubber feet that I got at Vetco Electronics in Bellevue, WA to finish things off. (Vetco is an awesome store, by the way. Somewhat akin to Weirdstuff Warehouse in the San Francisco bay area -- both great places to go look through surplus electronics and find parts!)




And here's what we have so far, shown without the light cover:



Digisparks use a special version of the Arduino 1.x software, with addins for the board, such as the device programmer and libraries. You can download it from the wiki here, as well as find other good information about the board. One quirk I found that was, when reprogramming the board, you have to unplug the board and then plug it back in, at which point the Arduino plugin recognizes it and starts the programming process. Not a big deal, but not immediately obvious, either, and worth pointing out to anyone just getting started.

The code I wrote for it was a variation on the RGB Demo program, with other variations possible depending on the setting of the numeric switch. For instance, setting "0" is all white and as bright as it goes, "1" flickers between blue and green very rapidly, "2" is purple, and "3" is the default demo program, and so on. And the cool thing is, if I get tired of those options, it's very easy to reprogram it.

Here's the program I am currently running on it. Note that it's easily expandable using the switch() statement.




#include <DigisparkRGB.h>
#include <DigiUSB.h>

#define PIN1 5
#define PIN2 4
#define PIN3 3

byte RED = 0;
byte BLUE = 2;
byte GREEN = 1;
byte COLORS[] = {
  RED, 
  BLUE, 
  GREEN
};

// the setup routine runs once when you press reset:
void setup()  
{ 

  pinMode(PIN1, INPUT); 
  pinMode(PIN2, INPUT); 
  pinMode(PIN3, INPUT);

  digitalWrite(PIN1, HIGH);
  digitalWrite(PIN2, HIGH);
  digitalWrite(PIN3, HIGH);

  DigisparkRGBBegin();
  //DigiUSB.begin();
} 


void loop ()
{
  static bool flicker = false;
  byte val = 0;

  int pin1 = digitalRead(PIN1);
  int pin2 = digitalRead(PIN2);
  int pin3 = digitalRead(PIN3);

  flicker ^= true;

  //val = ((pin1==HIGH) ? 1 : 0) | ((pin2==HIGH) ? 2 : 0) | ((pin3==HIGH) ? 4 : 0);
  val = (pin1==HIGH ? 1 : 0)| ((pin2==HIGH) ? 2 : 0);

  //DigiUSB.println(val);
  //return;

  switch(val)
  {
  case 0:
    {
      DigisparkRGB(RED, 255);
      DigisparkRGB(GREEN, 255);
      DigisparkRGB(BLUE, 255);

      break;
    }

  case 1:
    {
      if(flicker) {
        DigisparkRGB(RED, 0);
        DigisparkRGB(GREEN, 255);
        DigisparkRGB(BLUE, 0);
      } 
      else 
      {
        DigisparkRGB(RED, 0);
        DigisparkRGB(GREEN, 255);
        DigisparkRGB(BLUE, 255);
      }

      break;
    }


  case 2:
    {
      DigisparkRGB(RED, 255);
      DigisparkRGB(GREEN, 0);
      DigisparkRGB(BLUE, 255);

      break;
    }
    
  case 3:
    {
      //DigisparkRGB(RED, 0);
      //DigisparkRGB(GREEN, 0);
      //DigisparkRGB(BLUE, 0);
      //DigisparkRGB(RED, 255);
      fade();

      break;
    }


  case 4:
    {
      if(flicker) 
      {
        DigisparkRGB(RED, 255);
        DigisparkRGB(GREEN, 255);
        DigisparkRGB(BLUE, 255);
      }
      else
      {
        DigisparkRGB(RED, 0);
        DigisparkRGB(GREEN, 0);
        DigisparkRGB(BLUE, 0);
      }
      break;
    }

  default: 
    //fade();
    break;
  }

}

void fade() 
{
  //direction: up = true, down = false
  static boolean dir = true;
  static int i = 0;

  //while(1)
  {
    fade(COLORS[i%3], dir);
    i++;
    dir = !dir;
  }
}

void fade(byte Led, boolean dir)
{
  int i;

  //if fading up
  if (dir)
  {
    for (i = 0; i < 256; i++) 
    {
      DigisparkRGB(Led, i);
      DigisparkRGBDelay(5);//1);
    }
  }
  else
  {
    for (i = 255; i >= 0; i--) 
    {
      DigisparkRGB(Led, i);
      DigisparkRGBDelay(5);//1);
    }
  }
}






And here it is, all put together.

Chalk heart drawing courtesy of my wonderful and artistic girlfriend, Charlotte.


So the veredict? Digisparks are awesome! I recommend picking up a few and playing around with them at your earliest convenience.

Here's a video of the glowy thing, running it through a couple of settings:


And another of it with the lights off (neither of the videos really do it complete justice)



And speaking of pretty lights  and glowing at night, here's a song by a group called "Pretty Lights", called "Gold Coast Hustle". It's good music to watch the glowy thing by.


Wednesday, February 20, 2013

Nature Abhors a Vacuum, and So Do My Cats

Today, I learned how to vacuum form plastic. It's pretty easy to get the general idea, but I need to refine my process. The plastic I used was a cut up milk carton, and the box is a cigar box I got at a thrift store. Here's how I did it.

1. Cut some holes in a box.


This is the original cigar box.







30 holes in the back will provide the suction to the sheet of plastic.

The large hole is where the vacuum cleaner will hook up






The holes I cut were a the corners of one inch squares. I managed to get 30 holes on the back of the cigar box. I used a 1/16" drill bit to do this. The hole on the side is for the vacuum cleaner, which will be used to suck the plastic down to the cigar box.


2. Put some junk on that box.


My girlfriend wouldn't allow me to use
 her ceramic Buddha salt shakers,
so I had to use a pair of pliers, instead.
















3. Attach a sheet of plastic to a frame






This is a cut open milk bottle, attached to a wooden frame the size of the cigar box with some thumbtacks. The thumbtacks melted a little bit on the next step.


4. Heat the plastic in the oven.


This part not shown, because it required quick back and forth from the oven before the plastic re-solidified. Basically what I did was to put the oven on broil for about five minutes, reduce to 350, and then put the frame with the plastic in. About two to three minutes later, the plastic was completely clear.


5. Suck the air out that box.


It's pretty opaque at this point,
 but when it was in the oven,
the plastic was perfectly see-through.















From the oven, I quickly moved it to the top of the cigar box, turned on the vacuum, and waited until it was again opaque.




6. Wait for it to cool, and you're done!























What I need to do from here is to figure out how to seal the plastic to the frame better, so that the suction from the vacuum doesn't have leakage.