BMS Doug
Posts: 4423
Joined: Thu Mar 27, 2014 2:42 pm
Location: London, UK

Re: A Birthday Present for Fido

Thu Nov 26, 2020 7:21 pm

ejolson wrote:
Thu Nov 26, 2020 6:58 pm
Does anyone know what DB means in the third column?
DogBarks?
Doug.
Building Management Systems Engineer.

User avatar
jahboater
Posts: 7056
Joined: Wed Feb 04, 2015 6:38 pm
Location: Wonderful West Dorset

Re: A Birthday Present for Fido

Thu Nov 26, 2020 7:44 pm

The ARM CPU cores in the M1 have a node size of 5nm, which is impressive.

ejolson
Posts: 7241
Joined: Tue Mar 18, 2014 11:47 am

Re: A Birthday Present for Fido

Thu Nov 26, 2020 9:55 pm

BMS Doug wrote:
Thu Nov 26, 2020 7:21 pm
ejolson wrote:
Thu Nov 26, 2020 6:58 pm
Does anyone know what DB means in the third column?
DogBarks?
That's a relief. I was worried it might stand for dog bytes.

ejolson
Posts: 7241
Joined: Tue Mar 18, 2014 11:47 am

Re: A Birthday Present for Fido

Thu Nov 26, 2020 10:07 pm

jahboater wrote:
Thu Nov 26, 2020 7:44 pm
The ARM CPU cores in the M1 have a node size of 5nm, which is impressive.
If it weren't for all the barking around here and the price, I'd seriously consider getting an M1. I wonder if it could boot the 64-bit version of Raspberry Pi OS.

ejolson
Posts: 7241
Joined: Tue Mar 18, 2014 11:47 am

Re: A Birthday Present for Fido

Sun Nov 29, 2020 1:02 am

ejolson wrote:
Sat Oct 24, 2020 4:31 am
Does anybody have thoughts how to do this?
It turns out not necessary to use tcpser or a telnetd login session to get the hostcm file server working with the SuperPET emulator. After some simple modifications to the hostcm program, the exec feature of ncat works just fine.

First download Rob Ferguson's hostcm Linux server from

http://seefigure1.com/projects/hostcm/index.html

The modifications to hostcm involve disabling the error checking on the TTY ioctls, which don't work when the program is running under ncat. To do this apply the following patch:

Code: Select all

diff -c hostcm-orig/Makefile hostcm/Makefile
*** hostcm-orig/Makefile 2015-01-22 17:49:11.000000000 -0800
--- hostcm/Makefile 2020-10-20 19:35:35.664510325 -0700
***************
*** 5,11 ****
  # -O4 is clang specific; UNIX_LIKE indicates support for tty ioctls and non-POSIX termios extensions.
  
  #CFLAGS = -g -Wall -DDEBUG -DUNIX_LIKE
! CFLAGS = -O -Wall -DUNIX_LIKE
  
  all: hostcm
  
--- 5,12 ----
  # -O4 is clang specific; UNIX_LIKE indicates support for tty ioctls and non-POSIX termios extensions.
  
  #CFLAGS = -g -Wall -DDEBUG -DUNIX_LIKE
! #CFLAGS = -O -Wall -DUNIX_LIKE
! CFLAGS = -O -Wall
  
  all: hostcm
  
diff -c hostcm-orig/main.c hostcm/main.c
*** hostcm-orig/main.c 2013-11-23 08:43:48.000000000 -0800
--- hostcm/main.c 2020-11-28 15:14:05.677197376 -0800
***************
*** 157,175 ****
  
   if (tcgetattr(fdin, &tsave) == -1) {
    error("Can't save input termios");
!   exit(-1);
   }
  
   if (setterm(fdin, &opt) == -1) {
!   exit(-1);
   }
  
   server(fdin, fdout);
  
   if (tcsetattr(fdin, TCSANOW, &tsave) == -1) {
    error("Can't restore input termios");
!   exit(-1);
   }
- 
   return(0);
  }
--- 157,174 ----
  
   if (tcgetattr(fdin, &tsave) == -1) {
    error("Can't save input termios");
! //  exit(-1);
   }
  
   if (setterm(fdin, &opt) == -1) {
! //  exit(-1);
   }
  
   server(fdin, fdout);
  
   if (tcsetattr(fdin, TCSANOW, &tsave) == -1) {
    error("Can't restore input termios");
! //  exit(-1);
   }
   return(0);
  }
diff -c hostcm-orig/server.c hostcm/server.c
*** hostcm-orig/server.c 2013-11-23 08:43:48.000000000 -0800
--- hostcm/server.c 2020-10-01 21:48:37.455150418 -0700
***************
*** 16,22 ****
  {
   char outbuf[BUFSIZ];
   char inbuf[BUFSIZ];
!  int count;
   int n;
  
   while (1) {
--- 16,22 ----
  {
   char outbuf[BUFSIZ];
   char inbuf[BUFSIZ];
!  int count = 0;
   int n;
  
   while (1) {
diff -c hostcm-orig/term.c hostcm/term.c
*** hostcm-orig/term.c 2014-05-11 13:46:58.000000000 -0700
--- hostcm/term.c 2020-10-20 19:36:13.927348388 -0700
***************
*** 67,73 ****
    return(-1);
   }
  
!  printf("got here\n");
  
  #if defined(DEBUG) && defined(UNIX_LIKE)
   if (ioctl(fd, TIOCMGET, &mbits) == -1) {
--- 67,73 ----
    return(-1);
   }
  
! // printf("got here\n");
  
  #if defined(DEBUG) && defined(UNIX_LIKE)
   if (ioctl(fd, TIOCMGET, &mbits) == -1) {
In particular, download, patch, compile and then copy the hostcm program into the bin directory using the commands

Code: Select all

$ wget http://seefigure1.com/projects/hostcm/hostcm.zip
$ mkdir hostcm
$ cd hostcm
$ unzip ../hostcm.zip
$ patch -p1 <../hostcm.patch 
patching file Makefile
patching file main.c
patching file server.c
patching file term.c
$ make
$ cp hostcm ~/bin/hostcm
Create a directory to hold the files served by hostcm.

Code: Select all

$ mkdir ~/hostcm
Now modify the .xsession script to start the ncat program and add the options to VICE to connect to it. It should look like

Code: Select all

#!/bin/bash
sleep 1
xsetroot -solid black
echo "keysym Control_R = Control_L" | xmodmap -
screen=`xrandr | grep '*' | awk '{ print $1 }'`
width=${screen%%x*}
height=${screen##*x}
let "nh=width*532/704"
let "nw=height*704/532"
let "height=nh<height?nh:height"
let "width=nw<width?nw:width"
cat >$HOME/.config/vice/sdl-vicerc <<EOF
[PET]
MenuKey=282
SDLWindowWidth=$width
SDLWindowHeight=$height
SaveResourcesOnExit=1
ConfirmOnExit=0
FullscreenEnable=0
CrtcFilter=0
RefreshRate=5
EOF
cd ~/hostcm
ncat -l -k -p 25232 -e ~/bin/hostcm & ncat_pid=$!
sleep 2
/usr/local/vice-sdl/bin/xpet +sound -keymap 2 \
    +rsdev1ip232 -rsdev1 127.0.0.1:25232 \
    -symkeymap /home/ejolson/.config/vice/sdl-sym-PET.vkm \
    -truedrive -superpet -cpu6809 -model SuperPET \
    -drive8type 4040 \
    -8 ~/images/home.d64 \
    -drive9type 4040 \
    -9 ~/images/Waterloo2-Language-No6702-1.d64 \
    -drive10type 4040 \
    -10 ~/images/Waterloo2-Language-No6702-2.d64
kill $ncat_pid
At this point it should be possible to transparently load and save files to the hostcm file server.

For example, one could type

Code: Select all

g WUMPUS.PAS
to load Fido's birthday present into the editor buffer from the emulated floppy disk and then

Code: Select all

p host.wumpus.pas
to write it out over the serial link.

After this, one could delete all lines in the current buffer with

Code: Select all

*d
and then reload the same program back from the file server with

Code: Select all

g host.wumpus.pas
to check that everything is working.

While 2400 baud or 2.4 Kbps is about 41666 times slower than the 100 Mbps network speed of the original Raspberry Pi, hostcm appears just as convenient when setting up a school computer lab for a bunch of super pets as Pi Server is when doing the same today.
    hostcm.png
    hostcm.png (64.03 KiB) Viewed 1771 times

    ejolson
    Posts: 7241
    Joined: Tue Mar 18, 2014 11:47 am

    Re: A Birthday Present for Fido

    Mon Nov 30, 2020 7:02 am

    Here is a test of the SuperPET in the context of a simple recursive numerical quadrature routine based on the 4 and 5 point Gaussian formulas. The output
      gaussquad.png
      gaussquad.png (63.38 KiB) Viewed 1700 times
        indicates a syntax error; however, what really happened was a stack overflow.

        The good news is that
        • The SuperPET didn't crash.
        The bad news is that
        • The error message is misleading.
          • The recursion depth was not enough to obtain more than 1 significant digit of accuracy.
          For reference the output using FreePascal on a Pi is

          Code: Select all

          $ ./gaussquad 
          Adaptive Gaussian Quadrature
             i                       eps                      quad
             1       5.000000000000E-001       5.498310997927E-002
             2       2.500000000000E-001      -5.669562344359E-002
             3       1.250000000000E-001      -5.669562344359E-002
             4       6.250000000000E-002      -4.192952352932E-002
             5       3.125000000000E-002      -7.887512415585E-002
             6       1.562500000000E-002      -7.242143014464E-002
             7       7.812500000000E-003      -7.241097472859E-002
             8       3.906250000000E-003      -7.242804178022E-002
             9       1.953125000000E-003      -7.246075629128E-002
            10       9.765625000000E-004      -7.195134302359E-002
          
          and the source code is

          Code: Select all

          (*  adaptive gaussian quadrature
              written november 2020 by eric olson *)
          
          program main(input,output);
          const vmax=8;
          type values=array[1..vmax] of real;
              quadrature=record
                  n:integer;
                  x,w:values
              end;
          var q1,q2:quadrature;
          
          function g(x:real):real;
          begin
              g:=sin(1/x)
          end;
          
          procedure doinit;
          begin
              q1.n:=4;
              q1.x[1]:=-0.86113631159405257523;
              q1.x[2]:=-0.33998104358485626480;
              q1.x[3]:=0.33998104358485626480;
              q1.x[4]:=0.86113631159405257523;
              q1.w[1]:=0.34785484513745385736;
              q1.w[2]:=0.65214515486254614264;
              q1.w[3]:=0.65214515486254614264;
              q1.w[4]:=0.34785484513745385736;
              q2.n:=5;
              q2.x[1]:=-0.90617984593866399279;
              q2.x[2]:=-0.53846931010568309104;
              q2.x[3]:=0.0;
              q2.x[4]:=0.53846931010568309104;
              q2.x[5]:=0.90617984593866399279;
              q2.w[1]:=0.23692688505618908753;
              q2.w[2]:=0.47862867049936646802;
              q2.w[3]:=0.56888888888888888891;
              q2.w[4]:=0.47862867049936646802;
              q2.w[5]:=0.23692688505618908753
          end;
          
          function quad(var q:quadrature; a,b:real):real;
          var s,d2:real;
              k:integer;
          begin
              d2:=(b-a)/2;
              s:=0.0;
              for k:=1 to q.n do begin
                  s:=s+q.w[k]*g((q.x[k]+1)*d2+a)
              end;
              quad:=s*d2
          end;
          
          function aquad(a,b,eps:real):real;
          var y1,y2,c:real;
          begin
              y1:=quad(q1,a,b);
              y2:=quad(q2,a,b);
              if(abs(y1-y2)<eps) then begin
                  aquad:=y2
              end else begin
                  c:=(a+b)/2;
                  y1:=aquad(a,c,eps/2);
                  y2:=aquad(c,b,eps/2);
                  aquad:=y1+y2
              end
          end;
          
          procedure dorun(a,b:real);
          var i:integer;
              y,eps:real;
          begin
              eps:=1;
              writeln('i':4,'eps':26,'quad':26);
              for i:=1 to 10 do begin
                  eps:=eps/2;
                  y:=aquad(a,b,eps);
                  writeln(i:4,' ':6,eps:20,' ':6,y:20)
              end
          end;
          
          begin
              writeln('Adaptive Gaussian Quadrature');
              doinit;
              dorun(0.00001,0.3)
          end.
          
          The limited recursion depth places a significant restriction on the kinds of programs and algorithms that can be taught using Waterloo microPascal. In contrast, the fact that the operating system and programming languages available on the Raspberry Pi are exactly the same as those used worldwide by software professionals is arguably an important benefit open-source has brought to modern computer-science educational efforts that wasn't possible before.

          Given the ease of use and quality of the free software available, why do some people recommend purpose-built educational software with limited capabilities that don't wear well as a student gains experience? Do music teachers ever suggest their students start with a toy piano before moving on to a real one? What's the point of teaching students how to use a tool that is of no use once they master what they were supposed to be learning?

          BMS Doug
          Posts: 4423
          Joined: Thu Mar 27, 2014 2:42 pm
          Location: London, UK

          Re: A Birthday Present for Fido

          Mon Nov 30, 2020 7:31 am

          ejolson wrote:
          Sun Oct 11, 2020 4:26 pm

          The tradition of interpreted languages for teaching beginners seems to have continued with Scratch and Python.
          <snip>

          The distinction between a real computer and a toy to teach children programming continues to this day: I find it interesting the original concept of the Raspberry Pi was a teaching device limited to running only MicroPython.

          After age 5 children get tired of pretending with lumps of plastic and want something that really works. Thesame is likely true of hobbyists, makers and even most software professionals.

          Although Python and Scratch continue in the tradition of interactive programming languages with slow performance that were designed for teaching, the vendor supported software included with the Pi, while only 32-bit, is a full distribution of Linux with all the same compilers and development tools used from embedded to the cloud. The difference is significant though the similarities are many.
          ejolson wrote: Given the ease of use and quality of the free software available, why do some people recommend purpose-built educational software with limited capabilities that don't wear well as a student gains experience? Do music teachers ever suggest their students start with a toy piano before moving on to a real one? What's the point of teaching students how to use a tool that is of no use once they master what they were supposed to be learning?

          I admire your project ejolson, although I can't agree with your base argument.

          With Scratch young children can easily achieve programming goals, such as to move a sprite around a screen or even interact with the physical environment.
          Young children would quickly get bored if trying to achieve this with more complex languages. So Scratch serves a very good purpose of encouraging an interest in computing among young children.

          I wouldn't attempt to start teaching a child english from a dictionary, nor literature from war and peace.

          Young flautists and saxophonists undoubtedly started with a recorder first.
          Doug.
          Building Management Systems Engineer.

          ejolson
          Posts: 7241
          Joined: Tue Mar 18, 2014 11:47 am

          Re: A Birthday Present for Fido

          Tue Jan 26, 2021 4:05 am

          BMS Doug wrote:
          Mon Nov 30, 2020 7:31 am
          I admire your project ejolson, although I can't agree with your base argument.

          With Scratch young children can easily achieve programming goals, such as to move a sprite around a screen or even interact with the physical environment.
          Young children would quickly get bored if trying to achieve this with more complex languages. So Scratch serves a very good purpose of encouraging an interest in computing among young children.

          I wouldn't attempt to start teaching a child english from a dictionary, nor literature from war and peace.

          Young flautists and saxophonists undoubtedly started with a recorder first.
          Your point has been well taken. I find it interesting how similar is the fingering between the notes on a flute, saxophone and recorder. Note also that all three are real musical instruments.

          While no longer young, I just spent a couple weeks solving Advent of Code puzzles

          viewtopic.php?p=1784564#p1784564

          using three different languages.
          • Waterloo microPascal
          • NuScratch/Scratch3
          • Julia
          The first seven puzzles were completed in microPascal, the next seven in Scratch and the seven after that in Julia. As there were 25 puzzles total, that left four undone, but never mind that.

          From an artistic point of view, I feel the programs I wrote using microPascal were the most beautiful. Although the ones in Julia were easiest to write, I'm the least satisfied with them, as they include a number of short cuts that resulted algorithms with asymptotic run times much worse than they should have been. For example, Day 20 employed an O(n^3) algorithm to solve what I think was an O(n logn) puzzle.

          What seemed to allow this sloppiness was the ease with which one could write inefficient code using high-level operations in Julia coupled to the fact that the just-in-time compiler on the Pi 4B led to a computation that still finished after a short amount of time.

          Since the problems solved with computers have become larger as computers have become faster, writing code that scales in an optimal way as problem size increases becomes more and more important as the speed of the computer increases. Therefore, the relatively small size of the Advent of Code problems appear to encourage poor programming practices if solved using a computer system as fast as Julia running on a Raspberry Pi.

          As Fido's birthday is right around the corner, I'll end with a few comments on Scratch before returning to Hunt the Wumpus.

          NuScratch was super-slow and lacked the ability to divide an algorithm up into separate procedures and function calls; however, Scratch3 allowed creation of custom blocks in a way that allows breaking a problem into smaller sub-problems. In my opinion, the idea of taking a big problem and dividing it up into smaller ones is so central to solving any complex problem that it should be emphasized right from the beginning.

          I suspect the primary reason Euclidean geometry was taught and preserved throughout the centuries is that it provides a playground similar to solving Advent of Code puzzles that requires one to break down a complex argument into easy to understand lemmas and theorems. For this reason it appears to me that NuScratch is of very limited value for anything other than a one-hour workshop to promote interest in computers and technology.

          In many ways Scratch3 was a surprise. The off-line version ran quickly on the Pi 4B. Moreover, even though all variables were globally defined, it turned out possible to write recursive algorithms by passing any value that needed to be preserved across function calls as arguments. This works because arguments to a custom block have local scope.

          Although it was possible to write rather complicated code in Scratch3, it took a very long time to do so because of the drag and drop interface. After about 20 lines of code or 50 blocks, the mouse interface became tedious to the point of making my fingers hurt. It was further difficult to refactor or change code because of the way the blocks stick together.

          While NuScratch lacks essential functionality needed to break a complex task into smaller ones, the problem with Scratch3 is that it is too powerful.

          In particular, it would appear possible to become fairly accomplished at writing programs using Scratch3. The reason this is a problem is because a student could become too proficient in Scratch and subsequently find it discouraging to relearn it all over again in order to transition to a more convenient language.

          As indicated by the many accounting packages and other applications that were written in BASIC during the golden age of personal computing, it is possible for a programmer to get stuck with the first language they learn. This becomes a problem only if that first language was designed as a simple teaching language and yet contains enough functionality to appear useful in practical contexts.

          As a young person, I needed a break from computers for about 4 years (age 16 through 19) to forget enough BASIC in order to be ready to learn Pascal.

          Now, as if to help get this thread back on topic, it would appear Aaron Reed has just written a serendipitous essay on the history of Hunt the Wumpus.

          https://if50.substack.com/p/1973-hunt-the-wumpus

          ejolson
          Posts: 7241
          Joined: Tue Mar 18, 2014 11:47 am

          Re: A Birthday Present for Fido

          Thu Feb 25, 2021 3:46 am

          ejolson wrote:
          Wed Oct 28, 2020 7:40 am
          Heater wrote:
          Tue Oct 27, 2020 5:49 pm
          The mut variables are there for our canine friends. I here they like them.
          Since searching for random seeds is intrinsically parallel, I decided to convert the dodecahedron detector to CUDA. My strategy was to take the random graph generating code from wump.c, put it in a C++ object, and then create thousands of those objects one for each of the CUDA cores.

          Running the code on a GeForce GT 630 graphics card yielded

          Code: Select all

          $ ./gpufind -a 142000000 -b 142100000
          CUDA Device GeForce GT 630:
              Compute Capability Version 2.1
              Multiprocessors 2 at 1620 MHz
              Memory 4123520 KB at 500 MHz
          
          CUDA grid and block: <<<4,512>>>>
          
          Checking seeds 142000000 up to but not including 142100000...
          
          Dodecahedrons:
              142020269
          
          Seeds per second:  68477.5
          
          The single-threaded CPU code on a Raspberry Pi 4B ran as

          Code: Select all

          $ ./dfind -a 142000000 -b 142100000
          Checking seeds 142000000 up to but not including 142100000...
          
          Dodecahedrons:
              142020269
          
          Seeds per second:  19232.6
          
          As it's difficult to let sleeping dogs lie, I finally got the open source hipcc compiler for ROCm installed and decided to check how quickly an AMD GPU could find dodecahedrons compared to a Raspberry Pi 4B. Astonishingly, the only changes to the CUDA code needed to run on AMD were to replace all occurrences of the word cuda with hip.

          To get reliable test results, I decided to check a wider range of values and obtained the output

          Code: Select all

          $ ./hipfind.550 -a 140000000 -b 150000000
          GPU Device Baffin [Radeon RX 550 640SP / RX 560/560X]:
                  Compute Capability Version 8.0
                  Multiprocessors 16 at 1300 MHz
                  Memory 4194304 KB at 1750 MHz
          
          GPU grid and block: <<<32,256>>>>
          
          Checking seeds 140000000 up to but not including 150000000...
          
          Dodecahedrons:
                  142020269
          
          Seeds per second:  452160
          
          and

          Code: Select all

          $ ./hipfind.vii -a 140000000 -b 150000000
          GPU Device Vega 20 [Radeon VII]:
              Compute Capability Version 9.0
              Multiprocessors 60 at 1801 MHz
              Memory 16760832 KB at 1000 MHz
          
          GPU grid and block: <<<120,256>>>>
          
          Checking seeds 140000000 up to but not including 150000000...
          
          Dodecahedrons:
              142020269
          
          Seeds per second:  3.20599e+06
          
          For comparison I multiplied the speed of the single-threaded code running on the Pi 4B by 4 to obtain

          4*19232.6 = 76930.4 seeds per second

          I also reran the CUDA code on the GT 630 with the larger interval and the reduced block size that's apparently needed for reliable operation on some CUDA devices such as the Jetson.

          Code: Select all

          $ ./gpufind -a 140000000 -b 150000000
          CUDA Device GeForce GT 630:
              Compute Capability Version 2.1
              Multiprocessors 2 at 1620 MHz
              Memory 4123520 KB at 500 MHz
          
          CUDA grid and block: <<<4,256>>>>
          
          Checking seeds 140000000 up to but not including 150000000...
          
          Dodecahedrons:
              142020269
          
          Seeds per second:  70003
          
          In summary the results are

          Code: Select all

                     seeds/sec  ratio  price  $/ratio
          Pi 4B         76930      1     35      35
          GT 630        70003    0.9     79      88
          Radeon 550   452160    5.9     79      13
          Radeon VII  3205990   41.7    699      17
          
          While the price-performance ratio neglects to take into account the fact that the GPUs need to be plugged into a PC (as well as the fact that the GT 630 is about 8 years old), it definitely highlights how amazingly cheap computing power has become.

          For reference the hip version of the dodecahedron finder is

          Code: Select all

          #include <hip/hip_runtime.h>
          #include <stdio.h>
          #include <stdlib.h>
          #include <unistd.h>
          #include <sys/time.h>
          
          #define NROOM   20
          #define NTUNN   3
          #define NDODE   1024
          
          #ifdef CLOCK_MONOTONIC_RAW
          static struct timespec tic_start;
          void tic() {
              clock_gettime(CLOCK_MONOTONIC_RAW,&tic_start);
          }
          double toc() {
              struct timespec tic_stop;
              clock_gettime(CLOCK_MONOTONIC_RAW,&tic_stop);
              double sec=tic_stop.tv_sec-tic_start.tv_sec;
              return sec+(tic_stop.tv_nsec-tic_start.tv_nsec)*1.0e-9;
          }
          #else
          static struct timeval tic_start;
          void tic() {
              gettimeofday(&tic_start,0);
          }
          double toc() {
              struct timeval tic_stop;
              gettimeofday(&tic_stop,0);
              double sec=tic_stop.tv_sec-tic_start.tv_sec;
              return sec+(tic_stop.tv_usec-tic_start.tv_usec)*1.0e-6;
          }
          #endif
          
          typedef unsigned int myui;
          
          void docheck(const char *s){
              hipError_t r;
              if((r=hipGetLastError())!=hipSuccess){
                  fprintf(stderr,"GPU error %d: %s!\n%s\n",
                      r,s,hipGetErrorString(r));
                  exit(1);
              }
          }
          
          __device__ myui dolist[NDODE];
          __device__ int doind=0;
          
          class caveworker {
          public:
          int maybedo_r;
          struct room {
              int tunn[NTUNN];
          } room[NROOM];
          int randx;
          __device__ int r7rand(){
              return(((randx=randx*1103515245+12345)>>16)&077777);
          }
          __device__ int rnum(int n){
              return((r7rand()/32768.0)*n);
          }
          __device__ int tunnel(int i){
              struct room *p;
              int n,j,c;
              c=20;
          loop:
              n=rnum(NROOM);
              if(n==i) if(--c>0) goto loop;
              p=&room[n];
              for(j=0;j<NTUNN;j++) if(p->tunn[j]==-1){
                  p->tunn[j]=i;
                  return n;
              }
              goto loop;
          }
          __device__ void generate(myui x){
              int i,j,k;
              struct room *p;
              randx=x;
          init:
              p=&room[0];
              for(i=0;i<NROOM;i++){
                  for(j=0;j<NTUNN;j++) p->tunn[j]=-1;
                  p++;
              }
              k=0;
              for(i=1;i<NROOM;){
                  j=rnum(NROOM);
                  p=&room[j];
                  if(j==k||p->tunn[0]>=0||p->tunn[1]>=0) continue;
                  p->tunn[1]=k;
                  room[k].tunn[0]=j;
                  k=j;
                  i++;
              }
              p=&room[0];
              for(i=0;i<NROOM;i++){
                  for(j=0;j<NTUNN;j++){
                      if(p->tunn[j]<0) p->tunn[j]=tunnel(i);
                      if(p->tunn[j]==i) goto init;
                      for(k=0;k<j;k++) if(p->tunn[j]==p->tunn[k]) goto init;
                  }
                  p++;
              }
          }
          __device__ void imatmul(int A[NROOM][NROOM],
              int B[NROOM][NROOM],int C[NROOM][NROOM]){
              int i,j,k;
              for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) A[i][j]=0;
              for(i=0;i<NROOM;i++){
                  for(k=0;k<NROOM;k++){
                      for(j=0;j<NROOM;j++){
                          A[i][j]+=B[i][k]*C[k][j];
                      }
                  }
              }
          }
          __device__ int maybedo(myui x){
              int i,j,v,d;
              int A[NROOM][NROOM];
              int T[NROOM][NROOM],TT[NROOM][NROOM];
              for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) A[i][j]=0;
              for(v=0;v<NROOM;v++) for(d=0;d<NTUNN;d++){
                  j=v;
                  i=room[v].tunn[d];
                  A[i][j]=1;
              }
              for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) T[i][j]=A[i][j];
              for(v=1;v<5;v++){
                  imatmul(TT,A,T);
                  for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) A[i][j]=TT[i][j];
              }
              for(v=0;v<NROOM;v++){
                  if(A[v][v]!=6) return 0;
              }
              return 1;
          }
          };
          
          __device__ caveworker *cwgpu;
          caveworker *cwcpu;
          
          __global__ void gpusetup(caveworker *cw){
              cwgpu=cw;
          }
          
          __global__ void genmaygpu(myui a,myui b){
              unsigned long long n=blockIdx.x*blockDim.x+threadIdx.x;
              myui an=a+n*(b-a)/(gridDim.x*blockDim.x);
              myui bn=a+(n+1)*(b-a)/(gridDim.x*blockDim.x);
              for(myui x=an;x<bn;x++){
                  cwgpu[n].generate(x);
                  if(cwgpu[n].maybedo(x)){
                      int i=atomicAdd(&doind,1);
                      dolist[i]=x;
                  }
              }
          }
          void getmaybedo(myui r[NDODE]){
              hipMemcpyFromSymbol(r,dolist,
                  sizeof(myui)*NDODE,0,hipMemcpyDeviceToHost);
              docheck("getmaybedo");
          }
          
          myui streams,mygrid=64,myblock=256;
          int main(int argc,char *argv[]){
              int opt;
              myui a=1000,b=2000;
              while((opt=getopt(argc,argv,"a:b:"))!=-1){
                  switch(opt){
          case 'a':
                      a=atoi(optarg);
                      break;
          case 'b':
                      b=atoi(optarg);
                      break;
          default:
                      fprintf(stderr,"Usage: %s [-a first] [-b last]\n",argv[0]);
                      exit(1);
                  }
              }
              hipDeviceProp_t dp;
              hipGetDeviceProperties(&dp,0);
              printf("GPU Device %s:\n"
                  "\tCompute Capability Version %d.%d\n"
                  "\tMultiprocessors %d at %d MHz\n"
                  "\tMemory %ld KB at %d MHz\n",
                  dp.name,dp.major,dp.minor,
                  dp.multiProcessorCount,dp.clockRate/1000,
                  dp.totalGlobalMem/1024,dp.memoryClockRate/1000);
              if(dp.multiProcessorCount) mygrid=2*dp.multiProcessorCount;
              streams=myblock*mygrid;
              printf("\nGPU grid and block: <<<%d,%d>>>>\n",mygrid,myblock);
              hipMalloc(&cwcpu,sizeof(class caveworker)*streams);
              docheck("Failed to allocate memory for cwgpu");
              gpusetup<<<1,1>>>(cwcpu);
              docheck("gpusetup");
          
              printf("\nChecking seeds %d up to but not including %d...\n",a,b);
              fflush(stdout);
              tic();
              genmaygpu<<<mygrid,myblock>>>(a,b);
              docheck("Failed to call genmaygpu");
              hipDeviceSynchronize();
              docheck("Failed to synchronize at exit");
              double t=toc();
              myui r[NDODE];
              printf("\nDodecahedrons:\n");
              getmaybedo(r);
              for(myui *p=r;*p;p++) printf("\t%u\n",*p);
              printf("\nSeeds per second:  %g\n",(b-a)/t);
          }
          
          As I haven't tried to tune it, it's possible the grid and block sizes need to be optimized differently for the AMD hardware. Is it possible to run this code on an AMD APU? What about a Pico?

          ejolson
          Posts: 7241
          Joined: Tue Mar 18, 2014 11:47 am

          Re: A Birthday Present for Fido

          Fri Feb 26, 2021 3:47 am

          ejolson wrote:
          Thu Feb 25, 2021 3:46 am
          As I haven't tried to tune it, it's possible the grid and block sizes need to be optimized differently for the AMD hardware. Is it possible to run this code on an AMD APU? What about a Pico?
          Changing the grid and block size on ROCm didn't lead to obvious improvements in speed for the Radeon GPUs.

          Since there are no Pi Pico computers here, I decided to search for dodecahedrons using the Pi Zero to obtain another entry in the price performance table. The run was

          Code: Select all

          $ ./dfind -a 142000000 -b 142100000 ; # Raspberry Pi Zero
          Checking seeds 142000000 up to but not including 142100000...
          
          Dodecahedrons:
              142020269
          
          Seeds per second:  3455.89
          
          I also looked up the current market price of the GT 630 on ebay and obtained about $21 plus ridiculous shipping. I'll leave the original prices in the chart, since the only interest one might have in a GT 630 at this point is historical.

          Finally, since it just happened to be available, I ran the same program on an Pentium 4 2.4GHz. As this is 20-year-old hardware, current prices are a bit difficult to ascertain. It looks like the processor itself is less than $5 on ebay while complete systems were all surplussed long ago.

          At any rate, the run looks like

          Code: Select all

          $ ./dfind -a 142000000 -b 142100000 ; # Pentium 4 2.4 GHz
          Checking seeds 142000000 up to but not including 142100000...
          
          Dodecahedrons:
              142020269
          
          Seeds per second:  11492.4
          
          The updated performance table looks like

          Code: Select all

                     seeds/sec  ratio  price  $/ratio
          Pi Zero        3456    0.045    5     111
          P4 2.4GHz     11312    0.147  450    3061
          GT 630        70003    0.9     79      88
          Pi 4B         76930    1.0     35      35
          Radeon 550   452160    5.9     79      13
          Radeon VII  3205990   41.7    699      17
          
          Aside from the Pico, the main thing missing the from the chart is the PET. Without the pet how is it possible to celebrate Fido's birthday?

          ejolson
          Posts: 7241
          Joined: Tue Mar 18, 2014 11:47 am

          Re: A Birthday Present for Fido

          Sun Feb 28, 2021 3:21 am

          ejolson wrote:
          Fri Feb 26, 2021 3:47 am
          Aside from the Pico, the main thing missing the from the chart is the PET. Without the pet how is it possible to celebrate Fido's birthday?
          I modified my dodecahedral wumpus cave finder so it would compile with cc65 for the Commodore PET. The resulting program

          Code: Select all

          #include <stdio.h>
          #include <stdlib.h>
          #include <unistd.h>
          #include <time.h>
          
          #define NROOM   20
          #define NTUNN   3
          
          unsigned long tstart;
          void tic(){
              tstart=clock();
          }
          unsigned long toc(){
              unsigned long tstop=clock();
              unsigned long tdiff=tstop-tstart;
              return tdiff;
          }
          
          struct room {
              int tunn[NTUNN];
          } room[NROOM]={
              { 1, 4, 7},{ 0, 2, 9},{ 1, 3,11},{ 2, 4,13},{ 0, 3, 5},
              { 4, 6,14},{ 5, 7,16},{ 0, 6, 8},{ 7, 9,17},{ 1, 8,10},
              { 9,11,18},{ 2,10,12},{11,13,19},{ 3,12,14},{ 5,13,15},
              {14,16,19},{ 6,15,17},{ 8,16,18},{10,17,19},{12,15,18}};
          
          static long randx=1;
          static unsigned r7rand(){
              return(((randx=randx*1103515245+12345)>>16)&077777);
          }
          static int rnum(int n){
              return((unsigned long)n*r7rand()/32768ul);
          }
          int tunnel(int i){
              struct room *p;
              int n,j,c;
              c=20;
          loop:
              n=rnum(NROOM);
              if(n==i) if(--c>0) goto loop;
              p=&room[n];
              for(j=0;j<NTUNN;j++) if(p->tunn[j]==-1){
                  p->tunn[j]=i;
                  return n;
              }
              goto loop;
          }
          
          static int icomp(const void *p1,const void *p2){
              return(*(int *)p1-*(int *)p2);
          }
          static void generate(unsigned long x){
              int i,j,k;
              struct room *p;
              randx=x;
          init:
              p=&room[0];
              for(i=0;i<NROOM;i++){
                  for(j=0;j<NTUNN;j++) p->tunn[j]=-1;
                  p++;
              }
              k=0;
              for(i=1;i<NROOM;){
                  j=rnum(NROOM);
                  p=&room[j];
                  if(j==k||p->tunn[0]>=0||p->tunn[1]>=0) continue;
                  p->tunn[1]=k;
                  room[k].tunn[0]=j;
                  k=j;
                  i++;
              }
              p=&room[0];
              for(i=0;i<NROOM;i++){
                  for(j=0;j<NTUNN;j++){
                      if(p->tunn[j]<0) p->tunn[j]=tunnel(i);
                      if(p->tunn[j]==i) goto init;
                      for(k=0;k<j;k++) if(p->tunn[j]==p->tunn[k]) goto init;
                  }
                  qsort(&p->tunn[0],NTUNN,sizeof(int),icomp);
                  p++;
              }
          }
          static void imatmul(unsigned long A[NROOM][NROOM],
              unsigned long B[NROOM][NROOM],unsigned long C[NROOM][NROOM]){
              int i,j,k;
              for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) A[i][j]=0;
              for(i=0;i<NROOM;i++){
                  for(k=0;k<NROOM;k++){
                      for(j=0;j<NROOM;j++){
                          A[i][j]+=B[i][k]*C[k][j];
                      }
                  }
              }
          }
          unsigned long A[NROOM][NROOM];
          unsigned long T[NROOM][NROOM],TT[NROOM][NROOM];
          static int maybedo(){
              int i,j,v,d;
              for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) A[i][j]=0;
              for(v=0;v<NROOM;v++) for(d=0;d<NTUNN;d++){
                  j=v;
                  i=room[v].tunn[d];
                  A[i][j]=1;
              }
              for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) T[i][j]=A[i][j];
              for(v=1;v<5;v++){
                  imatmul(TT,A,T);
                  for(i=0;i<NROOM;i++) for(j=0;j<NROOM;j++) A[i][j]=TT[i][j];
              }
              for(v=0;v<NROOM;v++){
                  if(A[v][v]!=6) return 0;
              }
              return 1;
          }
          
          int main(){
              unsigned long tdiff,tint,tdec;
              unsigned long a=142020268ul,b=142020271ul,x;
              printf("Checking seeds %lu up to but not including %lu...\n",a,b);
              fflush(stdout);
              if(!maybedo()) printf("Something is wrong!\n");
              printf("\nDodecahedrons:\n");
              tic();
              for(x=a;x<b;x++){
                  generate(x);
                  if(maybedo()){
                      printf("    %lu\n",x); fflush(stdout);
                  }
              }
              tdiff=toc();
              tint=((b-a)*CLOCKS_PER_SEC*10000+tdiff/2)/tdiff;
              tdec=tint%10000;
              tint/=10000;
              printf("\nSeeds per second:  %lu.%04lu\n",tint,tdec);
              return 0;
          }
          
          produced the output
            petfind.png
            petfind.png (145.35 KiB) Viewed 1387 times
              The updated summary table is

              Code: Select all

                         seeds/sec  ratio  price  $/ratio
              PET          0.0075  9.75e-8  795  8.15e9
              Pi Zero        3456    0.045    5     111
              P4 2.4GHz     11312    0.147  450    3061
              GT 630        70003    0.9     79      88
              Pi 4B         76930    1.0     35      35
              Radeon 550   452160    5.9     79      13
              Radeon VII  3205990   41.7    699      17
              
              I always suspected owning a pet was orders of magnitude more costly, even when it came to getting something useless done. This also explains why Fido is always borrowing that time machine when working on any type of computational project.

              I wonder how the Pico would fare?

              ejolson
              Posts: 7241
              Joined: Tue Mar 18, 2014 11:47 am

              Re: A Birthday Present for Fido

              Wed Apr 21, 2021 3:42 pm

              ejolson wrote:
              Sun Feb 28, 2021 3:21 am
              I wonder how the Pico would fare?
              Still no Pico, but I was just thinking how it might make a better super-pet emulator than VICE running as a remote desktop. At any rate, nothing makes a worse super pet than the time Fido jumped off the roof of the doghouse wearing a colourful cape.

              While a Pico can function independently directly driving a display with a keyboard, I wonder if there is an easy way to view graphical output created by the Pico over USB and interact with it.

              ejolson
              Posts: 7241
              Joined: Tue Mar 18, 2014 11:47 am

              Re: A Birthday Present for Fido

              Tue May 04, 2021 5:07 pm

              ejolson wrote:
              Wed Apr 21, 2021 3:42 pm
              ejolson wrote:
              Sun Feb 28, 2021 3:21 am
              I wonder how the Pico would fare?
              Still no Pico, but I was just thinking how it might make a better super-pet emulator than VICE running as a remote desktop. At any rate, nothing makes a worse super pet than the time Fido jumped off the roof of the doghouse wearing a colourful cape.
              I was looking for something else and found

              https://askhistorians.libsyn.com/askhis ... ith-jbdyer

              which begins by talking about Bob Albrecht and how it was time-sharing that allowed people with no research, academic or military affiliations to use a computer first-hand for the first time.

              Slightly before the People's Computer Company, the Dartmouth computer literacy project made BASIC available across the campus. In both cases it quickly became clear computer games not only provided a way to familiarize people with the new technology, but were actually fun.

              What I find interesting about these early computer games is how their simplicity inspired beginners to create their own improved versions. While there is now an indie game industry, the fact that the early games were coded using general-purpose programming languages, in my opinion, led to transferable skills that empowered people to later do much more with what they learned while making the game.

              lurk101
              Posts: 586
              Joined: Mon Jan 27, 2020 2:35 pm
              Location: Cumming, GA (US)

              Re: A Birthday Present for Fido

              Wed May 12, 2021 5:53 pm

              Dealing with unfinished business while waiting for semi-official FreeRTOS for Pico…

              A version of ‘Hunt the Wumpus’ in C for Raspberry Pico. With a simplified cave generator, bonus dodecahedron detector, and a few 'cheat' commands when built for debug.

              Code: Select all

              /*
               *  wumpus (ported by lurk101)
               *  stolen from PCC Vol 2 No 1
               *
               *  This version has been updated to compile for Pico using
               *  gcc on the Raspberry Pi.
               *  The cave generator from the original has been replaced.
               */
              
              // Compiled for Pico on Pi4 with GCC. NOTE: unsigned and int are assumed 32 bits.
              
              #if !defined(WUMPUS_CHEAT)
              #define WUMPUS_CHEAT 0
              #endif
              
              #include "pico/stdlib.h"
              #include <stdbool.h>
              #include <stdio.h>
              #include <stdlib.h>
              
              // boundaries
              #define N_BATS 3
              #define N_ROOMS 20  // must be even
              #define N_TUNNELS 3 // must be 3
              #define N_PITS 3
              #define N_ARROWS 5
              
              // room flags
              #define HAS_BAT ((char)(1 << 0))
              #define HAS_PIT ((char)(1 << 1))
              #define HAS_WUMPUS ((char)(1 << 2))
              #define HAS_VISIT ((char)(1 << 3))
              
              // tunnel flag
              #define UN_MAPPED ((char)-1)
              
              static unsigned cave, empty_cave;     // bitmaps
              static unsigned arrow, loc, wloc;     // arrow count, player and wumpus locations
              static char cycle[N_ROOMS];           // array of next room in cycle
              static char* const flags = cycle;     // reuse cycle array but avoid usage confusion!
              static char room[N_ROOMS][N_TUNNELS]; // tunnel map
              
              static const char* intro = // clang-format off
                  "\n"
                  "The Wumpus lives in a cave of %d rooms.\n"
                  "Each room has %d tunnels leading to other rooms.\n\n"
                  "Hazards:\n\n"
                  "Bottomless Pits - %d rooms have Bottomless Pits in them.\n"
                  " If you go there, you fall into the pit and lose!\n"
                  "Super Bats - %d other rooms have super bats.\n"
                  " If you go there, a bat will grab you and take you to\n"
                  " somewhere else in the cave where you could\n"
                  " fall into a pit or run into the . . .\n\n"
                  "Wumpus:\n\n"
                  "The Wumpus is not bothered by the hazards since\n"
                  "he has sucker feet and is too big for a bat to lift.\n\n"
                  "Usually he is asleep. Two things wake him up:\n"
                  " your entering his room\n"
                  " your shooting an arrow anywhere in the cave.\n"
                  "If the wumpus wakes, he either decides to move one room or\n"
                  "stay where he was. But if he ends up where you are,\n"
                  "he eats you up and you lose!\n\n"
                  "You:\n\n"
                  "Each turn you may either move or shoot a crooked arrow.\n\n"
                  "Moving - You can move to one of the adjoining rooms;\n"
                  " that is, to one that has a tunnel connecting it with\n"
                  " the room you are in.\n\n"
                  "Shooting - You have %d arrows. You lose when you run out.\n"
                  " Each arrow can go from 1 to %d rooms.\n"
                  " You aim by telling the computer\n"
                  " The arrow's path is a list of room numbers\n"
                  " telling the arrow which room to go to next.\n"
                  " The first room in the path must be connected to the\n"
                  " room you are in. Each succeeding room must be\n"
                  " connected to the previous room.\n"
                  " If there is no tunnel between two of the rooms\n"
                  " in the arrow's path, the arrow chooses one of the\n"
                  " three tunnels from the room it's in and goes its\n"
                  " own way.\n\n"
                  " If the arrow hits the wumpus, you win!\n"
                  " If the arrow hits you, you lose!\n\n"
                  "Warnings:\n\n"
                  "When you are one or two rooms away from the wumpus,\n"
                  "the computer says:\n"
                  "   'I smell a Wumpus'\n"
                  "When you are one room away from some other hazard, it says:\n"
                  "   Bat    - 'Bats nearby'\n"
                  "   Pit    - 'I feel a draft'\n";
              // clang-format on
              
              // Random numbers
              
              // Uniform distribution 0..n-1
              static unsigned random_number(unsigned n) {
                  return (unsigned)(((rand() & RAND_MAX) / 2147483648.0) * n);
              }
              
              // Bitmap functions
              static inline void occupy_room(unsigned b) { cave &= ~(1 << b); }
              static inline bool room_is_empty(unsigned b) { return (cave & (1 << b)) != 0; }
              static inline unsigned empty_room_count(void) { return __builtin_popcount(cave); }
              
              // Console output
              static void put_str(char* s) {
                  fputs(s, stdout);
                  fflush(stdout);
              }
              
              static void put_char(char c) {
                  putc(c, stdout);
                  fflush(stdout);
              }
              
              static void put_nl(void) { put_char('\n'); }
              
              // Console input
              static unsigned argc;
              static char* argv[N_ARROWS + 1];
              static char cmd_buffer[64];
              static char cchr;
              
              static void get_chr(void) {
                  put_char(cchr = fgetc(stdin));
                  if (cchr == '\r')
                      put_nl();
              }
              
              static void get_and_parse_cmd(void) {
                  // read line into buffer
                  char* cp = cmd_buffer;
                  do {
                      get_chr();
                      if (cchr != '\b')
                          *cp++ = cchr;
                      else if (cp != cmd_buffer) {
                          cp--;
                          put_str(" \b");
                      }
                  } while (cchr != '\r');
                  // parse buffer
                  cp = cmd_buffer;
                  bool not_last = true;
                  for (argc = 0; not_last && (argc <= N_ARROWS); argc++) {
                      while ((*cp == ' ') || (*cp == ','))
                          cp++; // skip blanks
                      if (*cp == '\r')
                          break;
                      argv[argc] = cp; // start of string
                      while ((*cp != ' ') && (*cp != ',') && (*cp != '\r'))
                          cp++; // skip non blank
                      if (*cp == '\r')
                          not_last = false;
                      *cp++ = 0; // terminate string
                  }
                  if (argc)
                      *argv[0] |= ' '; // to lower lowercase
              }
              
              // Cave generator helpers
              
              #if N_ROOMS == 20 // dodecahedron must have 20 rooms
              
              static unsigned char A[N_ROOMS][N_ROOMS];
              static unsigned char T[N_ROOMS][N_ROOMS];
              static unsigned char TT[N_ROOMS][N_ROOMS];
              
              #if !defined(NDEBUG)
              
              // Known dodecahedron for debug sanity check
              static const char dodecahedron[N_ROOMS][N_TUNNELS] = {
                  {1, 4, 7},   {0, 2, 9},    {1, 3, 11},  {2, 4, 13},  {0, 3, 5},    {4, 6, 14},   {5, 7, 16},
                  {0, 6, 8},   {7, 9, 17},   {1, 8, 10},  {9, 11, 18}, {2, 10, 12},  {11, 13, 19}, {3, 12, 14},
                  {5, 13, 15}, {14, 16, 19}, {6, 15, 17}, {8, 16, 18}, {10, 17, 19}, {12, 15, 18}};
              
              #endif // !defined(NDEBUG)
              
              // The dodecahedron detector
              static void matrix_mult(void) {
                  unsigned i, j, k;
                  for (i = 0; i < N_ROOMS; i++)
                      for (j = 0; j < N_ROOMS; j++)
                          TT[i][j] = 0;
                  for (i = 0; i < N_ROOMS; i++)
                      for (k = 0; k < N_ROOMS; k++)
                          for (j = 0; j < N_ROOMS; j++)
                              TT[i][j] = TT[i][j] + A[i][k] * T[k][j];
                  for (i = 0; i < N_ROOMS; i++)
                      for (j = 0; j < N_ROOMS; j++)
                          A[i][j] = TT[i][j];
              }
              
              static bool is_dodecahedron(void) {
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      for (unsigned j = 0; j < N_ROOMS; j++)
                          A[i][j] = 0;
                  for (unsigned v = 0; v < N_ROOMS; v++)
                      for (unsigned d = 0; d < N_TUNNELS; d++)
                          A[v][(unsigned char)room[v][d]] = 1;
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      for (unsigned j = 0; j < N_ROOMS; j++)
                          T[i][j] = A[i][j];
                  for (unsigned p = 2; p < 6; p++)
                      matrix_mult();
                  for (unsigned v = 0; v < N_ROOMS; v++)
                      if (A[v][v] != 6)
                          return false;
                  return true;
              }
              
              #endif // N_ROOMS == 20
              
              // Pick a random vacant room
              static unsigned pick_empty_room(void) {
                  unsigned r, n = random_number(empty_room_count());
                  for (r = 0; r < N_ROOMS; r++)
                      if (room_is_empty(r)) {
                          if (n == 0)
                              break;
                          n--;
                      }
                  occupy_room(r);
                  return r;
              }
              
              // Add tunnel from room to room
              static void add_direct_tunnel(unsigned f, unsigned t) {
                  for (unsigned i = 0; i < N_TUNNELS; i++)
                      if (room[f][i] == UN_MAPPED) {
                          room[f][i] = t;
                          break;
                      }
              }
              
              // Add tunnels connecting to and from
              static void add_tunnel(unsigned f, unsigned t) {
                  add_direct_tunnel(f, t);
                  add_direct_tunnel(t, f);
              }
              
              // exchange adjacent bytes if 1st byte is greater than 2nd
              static inline void exchange(char* t) {
                  if (*t > *(t + 1)) {
                      *t = *t ^ *(t + 1);
                      *(t + 1) = *t ^ *(t + 1);
                      *t = *t ^ *(t + 1);
                  }
              }
              
              // Generate a new cave
              static bool directed_graph(void) {
              
                  srand(time_us_32());
              
              #if !defined(NDEBUG) && (N_ROOMS == 20)
              
                  // test the dodecahedron detector
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      for (unsigned t = 0; t < N_TUNNELS; t++)
                          room[r][t] = dodecahedron[r][t];
                  assert(is_dodecahedron());
              
              #endif // !defined(NDEBUG) && (N_ROOMS == 20)
              
                  // Clear the tunnel map
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      for (unsigned t = 0; t < N_TUNNELS; t++)
                          room[r][t] = UN_MAPPED;
              
                  // Step 1 - Generate a random 20 room cycle.
                  unsigned r = 0, rs = 0;
                  cave = empty_cave;
                  occupy_room(r);
                  do {
                      unsigned e = pick_empty_room();
                      add_tunnel(r, e);
                      cycle[r] = e;
                      r = e;
                  } while (cave);
                  cycle[r] = rs;
                  add_tunnel(r, rs);
              
                  // step 2 - add the third tunnels... if possible.
                  cave = empty_cave;
                  for (unsigned n = 0; n < N_ROOMS / 2; n++) {
                      assert(cave); // Can't happen
                      r = pick_empty_room();
                      unsigned save_cave = cave;
                      // disqualify neighbors
                      for (unsigned t = 0; t < N_TUNNELS; t++)
                          occupy_room(room[r][t]);
                      if (!cave) // Oops, can't complete this one!
                          return false;
                      unsigned e = pick_empty_room();
                      cave = save_cave;
                      occupy_room(e);
                      add_tunnel(r, e);
                  }
              
                  // step 3 - sort the tunnels
                  for (unsigned i = 0; i < N_ROOMS; i++) {
                      exchange(room[i]);
                      exchange(room[i] + 1);
                      exchange(room[i]);
                  }
              
              #if !defined(NDEBUG)
              
                  // map sanity check
                  unsigned count[N_ROOMS];
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      count[i] = 0;
                  for (unsigned i = 0; i < N_ROOMS; i++) {
                      // 3 unique tunnels
                      assert((room[i][0] != room[i][1]) && (room[i][0] != room[i][2]) &&
                             (room[i][1] != room[i][2]));
                      for (unsigned j = 0; j < N_TUNNELS; j++) {
                          // tunnel doesn't go to itself
                          assert(room[i][j] != i);
                          count[(unsigned char)room[i][j]]++;
                      }
                  }
                  // each room has 3 tunnels
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      assert(count[i] == 3);
              
              #endif // !defined(NDEBUG)
              
                  return true;
              }
              
              // recursive depth 1st neighbor search
              static bool near(unsigned r, char has, unsigned depth) {
                  for (unsigned t = 0; t < N_TUNNELS; t++) {
                      if (flags[(unsigned char)room[r][t]] & has)
                          return true;
                      if ((depth > 1) && near(room[r][t], has, depth - 1))
                          return true;
                  }
                  return false;
              }
              
              // try to find player
              static bool search_path(unsigned r, unsigned depth) {
                  flags[r] |= HAS_VISIT;
                  for (unsigned t = 0; t < N_TUNNELS; t++) {
                      unsigned e = room[r][t];
                      if (e == loc)
                          return true;
                      if (depth)
                          if (!(flags[e] & HAS_VISIT) && search_path(e, depth - 1)) {
                              printf("%d ", room[r][t] + 1);
                              return true;
                          }
                  }
                  return false;
              }
              
              // state functions
              typedef void* (*func_ptr)(void);
              static func_ptr start_handler(void);
              static func_ptr instruction_handler(void);
              static func_ptr init_cave_handler(void);
              static func_ptr setup_handler(void);
              static func_ptr loop_handler(void);
              static func_ptr done_handler(void);
              static func_ptr again_handler(void);
              static func_ptr move_player_handler(void);
              static func_ptr shoot_handler(void);
              static func_ptr move_wumpus_handler(void);
              static func_ptr leave_handler(void);
              
              static bool valid_room_number(int n) {
                  bool b = (n >= 0) && (n < N_ROOMS);
                  if (!b)
                      printf("\n%d is not a room number\n", n + 1);
                  return b;
              }
              
              // begin
              static func_ptr start_handler(void) {
                  empty_cave = (unsigned)-1 >> (32 - N_ROOMS);
                  put_str("\n\n"
                          "Welcome to HUNT THE WUMPUS.\n\n"
                          "Instructions (y/N) ? ");
                  get_and_parse_cmd();
                  if ((argc == 0) || (*argv[0] == 'n'))
                      return (func_ptr)init_cave_handler;
                  return (func_ptr)instruction_handler;
              }
              
              // exit. Nowhere to go...
              static func_ptr leave_handler(void) {
                  put_str("\nBye!\n\n");
                  exit(0);
                  return (func_ptr)NULL; // satisfy the compiler
              }
              
              // show instructions
              static func_ptr instruction_handler(void) {
                  printf(intro, N_ROOMS, N_TUNNELS, N_PITS, N_BATS, N_ARROWS, N_ARROWS);
                  return (func_ptr)init_cave_handler;
              }
              
              // create a fresh cave
              static func_ptr init_cave_handler(void) {
                  put_str("\nCreating new cave map.");
                  while (!directed_graph())
                      ;
              #if N_ROOMS == 20
                  if (is_dodecahedron())
                      put_str(" Ooh! You're entering the rarest of caves, a dodecahedron.");
              #endif // N_ROOMS == 20
                  put_nl();
                  return (func_ptr)setup_handler;
              }
              
              // setup a new game in the current cave
              static func_ptr setup_handler(void) {
                  // put in player, wumpus, pits and bats
                  unsigned i, j;
                  arrow = N_ARROWS;
                  for (i = 0; i < N_ROOMS; i++)
                      flags[i] = 0;
                  for (i = 0; i < N_PITS;) {
                      j = random_number(N_ROOMS);
                      if ((flags[j] & HAS_PIT) == 0) {
                          flags[j] |= HAS_PIT;
                          i++;
                      }
                  }
                  for (i = 0; i < N_BATS;) {
                      j = random_number(N_ROOMS);
                      if ((flags[j] & (HAS_PIT | HAS_BAT)) == 0) {
                          flags[j] |= HAS_BAT;
                          i++;
                      }
                  }
                  wloc = random_number(N_ROOMS);
                  flags[wloc] |= HAS_WUMPUS;
                  for (;;)
                  {
                      i = random_number(N_ROOMS);
                      if ((flags[i] & (HAS_PIT | HAS_BAT | HAS_WUMPUS)) == 0) {
                          loc = i;
                          break;
                      }
                  }
                  return (func_ptr)loop_handler;
              }
              
              // just landed in new room
              static func_ptr loop_handler(void) {
                  //  main loop of the game
                  printf("\nYou are in room %d", loc + 1);
                  if (flags[loc] & HAS_PIT) {
                      put_str(". You fell into a pit. You lose.\n");
                      return (func_ptr)done_handler;
                  }
                  if (flags[loc] & HAS_WUMPUS) {
                      put_str(". You were eaten by the wumpus. You lose.\n");
                      return (func_ptr)done_handler;
                  }
                  if (flags[loc] & HAS_BAT) {
                      put_str(". Theres a bat in your room. Carying you away.\n");
                      loc = random_number(N_ROOMS);
                      return (func_ptr)loop_handler;
                  }
                  if (near(loc, HAS_WUMPUS, 2))
                      put_str(". I smell a wumpus");
                  if (near(loc, HAS_BAT, 1))
                      put_str(". Bats nearby");
                  if (near(loc, HAS_PIT, 1))
                      put_str(". I feel a draft");
                  printf(". There are tunnels to rooms %d, %d and %d.\n", room[loc][0] + 1, room[loc][1] + 1,
                         room[loc][2] + 1);
                  return (func_ptr)again_handler;
              }
              
              #if !defined(NDEBUG) || WUMPUS_CHEAT
              
              static func_ptr dump_cave_handler(void) {
                  for (unsigned r = 0; r < N_ROOMS; r++) {
                      if ((r & 3) == 0)
                          put_nl();
                      printf("%02d:%02d %02d %02d  ", r + 1, room[r][0] + 1, room[r][1] + 1, room[r][2] + 1);
                  }
                  printf("\n\nPlayer:%02d  Wumpus:%02d  Pits:", loc + 1, wloc + 1);
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      if (flags[r] & HAS_PIT)
                          printf("%02d ", r + 1);
                  put_str(" Bats:");
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      if (flags[r] & HAS_BAT)
                          printf("%02d ", r + 1);
                  put_nl();
                  return (func_ptr)again_handler;
              }
              
              static func_ptr best_shot_handler(void) {
                  printf("\nBest shot: ");
                  unsigned i;
                  for (i = 0; i <= N_ARROWS; i++) {
                      for (unsigned j = 0; j < N_ROOMS; j++)
                          flags[j] &= ~HAS_VISIT;
                      if (search_path(wloc, i)) {
                          printf("%d", wloc + 1);
                          break;
                      }
                  }
                  if (i == N_ARROWS)
                      put_str("none");
                  put_nl();
                  return (func_ptr)again_handler;
              }
              
              #endif // NDEBUG
              
              // what are you going to do here?
              static func_ptr again_handler(void) {
                  put_str("\nMove or shoot (m/s) ? ");
                  get_and_parse_cmd();
                  if (argc == 0)
                      return (func_ptr)again_handler;
                  switch (*argv[0]) {
                  case 'm':
                      return (func_ptr)move_player_handler;
                  case 's':
                      return (func_ptr)shoot_handler;
              #if !defined(NDEBUG) || WUMPUS_CHEAT
                  case 'd': // dump cave map
                      return (func_ptr)dump_cave_handler;
                  case 'b': // find best shot
                      return (func_ptr)best_shot_handler;
              #endif // NDEBUG
                  }
                  put_str("\nWhat ?\n");
                  return (func_ptr)again_handler;
              }
              
              // move on to next room
              static func_ptr move_player_handler(void) {
                  if (argc < 2) {
                      put_str("\nwhich room ?\n");
                      return (func_ptr)again_handler;
                  }
                  int r, t;
                  r = atoi(argv[1]) - 1;
                  if (!valid_room_number(r))
                      return (func_ptr)again_handler;
                  if (r < N_ROOMS)
                      for (t = 0; t < N_TUNNELS; t++)
                          if (r == room[loc][t]) {
                              loc = r;
                              if (flags[r] & HAS_WUMPUS)
                                  return (func_ptr)move_wumpus_handler;
                              return (func_ptr)loop_handler;
                          }
                  put_str("You hit the wall!\n");
                  return (func_ptr)again_handler;
              }
              
              // shoot an arrow
              static func_ptr shoot_handler(void) {
                  if (argc < 2) {
                      put_str("Which tunnel(s) ?\n");
                      return (func_ptr)again_handler;
                  }
                  for (unsigned i = 1; i < argc; i++)
                      if (!valid_room_number(atoi(argv[i]) - 1))
                          return (func_ptr)again_handler;
                  unsigned t, r = atoi(argv[1]) - 1;
                  for (t = 0; t < N_TUNNELS; t++)
                      if (room[loc][t] == r)
                          break;
                  if (t == N_TUNNELS) {
                      put_str("\nNo tunnel to that room!\n");
                      return (func_ptr)again_handler;
                  }
                  put_nl();
                  int l = loc;
                  for (unsigned i = 0; i < 5; i++) {
                      if (i > argc - 2)
                          break;
                      r = atoi(argv[i + 1]) - 1;
                      for (t = 0; t < N_TUNNELS; t++)
                          if (r == room[l][t])
                              break;
                      if (t == N_TUNNELS)
                          t = random_number(N_TUNNELS);
                      r = room[l][t];
                      put_str("~>");
                      sleep_ms(500);
                      printf("%d", r + 1);
                      if (r == loc) {
                          put_str("\n\nYou shot yourself! You lose.\n");
                          return (func_ptr)done_handler;
                      }
                      if (flags[r] & HAS_WUMPUS) {
                          printf("\n\nYou slew the wumpus in room %d. You win!\n", r + 1);
                          return (func_ptr)done_handler;
                      }
                      sleep_ms(500);
                      l = r;
                  }
                  put_str("\n\nYou missed!");
                  if (--arrow == 0) {
                      put_str(" That was your last shot! You lose.\n");
                      return (func_ptr)done_handler;
                  }
                  put_nl();
                  return (func_ptr)move_wumpus_handler;
              }
              
              // wumpus disturbed, time to move it
              static func_ptr move_wumpus_handler(void) {
                  int i;
                  flags[wloc] &= ~HAS_WUMPUS;
                  i = random_number(N_TUNNELS + 1);
                  if (i != N_TUNNELS)
                      wloc = room[wloc][i];
                  if (wloc == loc) {
                      printf("\nThe wumpus %sate you. You lose.\n", ((i == N_TUNNELS) ? "" : "moved and "));
                      return (func_ptr)done_handler;
                  }
                  flags[wloc] |= HAS_WUMPUS;
                  return (func_ptr)loop_handler;
              }
              
              // game over. Play again?
              static func_ptr done_handler(void) {
                  put_str("\nAnother game (Y/n) ? ");
                  get_and_parse_cmd();
                  if ((argc == 0) || (*argv[0] == 'y')) {
                      put_str("\nSame room setup (Y/n) ? ");
                      get_and_parse_cmd();
                      if ((argc == 0) || (*argv[0] == 'y'))
                          return (func_ptr)setup_handler;
                      else
                          return (func_ptr)init_cave_handler;
                  }
                  return (func_ptr)leave_handler;
              }
              
              // forever loop
              int main(void) {
                  stdio_init_all();
                  getchar_timeout_us(1000); // swallow the spurious EOF character???
                  put_str("\033[H\033[J");  // try to clear the screen
                  func_ptr state = (func_ptr)start_handler;
                  for (;; state = state())
                      ;
              }
              CMakeLists.txt

              Code: Select all

              cmake_minimum_required(VERSION 3.13)
              
              set(CMAKE_C_STANDARD 11)
              set(CMAKE_CXX_STANDARD 17)
              
              set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")
              
              include(pico_sdk_import.cmake)
              
              project(wumpus C CXX ASM)
              
              option(WUMPUS_CHEAT "Include cheat code" OFF)
              
              pico_sdk_init()
              
              add_executable(wumpus wumpus.c )
              
              pico_set_program_name(wumpus "wumpus")
              pico_set_program_version(wumpus "0.1")
              
              pico_enable_stdio_uart(wumpus 1)
              pico_enable_stdio_usb(wumpus 0)
              
              target_link_libraries(wumpus pico_stdlib)
              
              pico_add_extra_outputs(wumpus)
              
              if (NOT ("${CMAKE_BUILD_TYPE}" MATCHES "Debug"))
                  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -Wl,–gc-sections -O3")
              endif()
              if (WUMPUS_CHEAT)
                  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWUMPUS_CHEAT=1")
              endif()
              set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
              
              message(STATUS "Build type ${CMAKE_BUILD_TYPE}")
              message(STATUS "Cheat ${WUMPUS_CHEAT}")
              message(STATUS "CFlags ${CMAKE_C_FLAGS}")
              
              Last edited by lurk101 on Fri May 14, 2021 6:12 pm, edited 6 times in total.
              The old semiconductor paradigms are rapidly becoming a thing of the past.
              Today, it's about the best transistors, architectures, and accelerators for the job.

              kilograham
              Raspberry Pi Engineer & Forum Moderator
              Raspberry Pi Engineer & Forum Moderator
              Posts: 627
              Joined: Fri Apr 12, 2019 11:00 am
              Location: austin tx

              Re: A Birthday Present for Fido

              Thu May 13, 2021 12:32 pm

              lurk101 wrote:
              Wed May 12, 2021 5:53 pm
              Dealing with unfinished business while waiting for semi-official FreeRTOS for Pico…
              If interested, you can play with

              https://github.com/kilograham/FreeRTOS- ... GCC/RP2040 or https://github.com/kilograham/FreeRTOS- ... GCC/RP2040

              and

              https://github.com/kilograham/FreeRTOS/ ... %2B_RP2040

              Thee may change b4 they are upstreamed

              lurk101
              Posts: 586
              Joined: Mon Jan 27, 2020 2:35 pm
              Location: Cumming, GA (US)

              Re: A Birthday Present for Fido

              Thu May 13, 2021 4:54 pm

              kilograham wrote:
              Thu May 13, 2021 12:32 pm
              lurk101 wrote:
              Wed May 12, 2021 5:53 pm
              Dealing with unfinished business while waiting for semi-official FreeRTOS for Pico…
              If interested, you can play with

              https://github.com/kilograham/FreeRTOS- ... GCC/RP2040 or https://github.com/kilograham/FreeRTOS- ... GCC/RP2040

              and

              https://github.com/kilograham/FreeRTOS/ ... %2B_RP2040

              Thee may change b4 they are upstreamed
              Ooh! An SMP branch! Yes, I'm interested.
              The old semiconductor paradigms are rapidly becoming a thing of the past.
              Today, it's about the best transistors, architectures, and accelerators for the job.

              ejolson
              Posts: 7241
              Joined: Tue Mar 18, 2014 11:47 am

              Re: A Birthday Present for Fido

              Fri May 14, 2021 6:42 pm

              lurk101 wrote:
              Wed May 12, 2021 5:53 pm
              Dealing with unfinished business while waiting for semi-official FreeRTOS for Pico…

              A version of ‘Hunt the Wumpus’ in C for Raspberry Pico. With a simplified cave generator, bonus dodecahedron detector, and a few 'cheat' commands when built for debug.
              Thanks for posting. It looks great! There is a misspelling of the word "moved" in the move_wumpus_handler and what looks like a buffer overflow in get_and_parse_cmd.

              Edit: It appears the spelling is fixed.

              ejolson
              Posts: 7241
              Joined: Tue Mar 18, 2014 11:47 am

              Re: A Birthday Present for Fido

              Sat May 15, 2021 4:11 am

              ejolson wrote:
              Fri May 14, 2021 6:42 pm
              Edit: It appears the spelling is fixed.
              Here is a back port of the the pico version of Hunt the Wumpus to the Rasbperry Pi. I think I've also fixed the buffer overflow.

              Code: Select all

              /*
               *  wumpus (ported by lurk101)
               *  stolen from PCC Vol 2 No 1
               *
               *  This version has been updated to compile for Pico using
               *  gcc on the Raspberry Pi.
               *  The cave generator from the original has been replaced.
               */
              
              // Compiled for Pico on Pi4 with GCC. NOTE: unsigned and int are assumed 32 bits.
              
              #if !defined(WUMPUS_CHEAT)
              #define WUMPUS_CHEAT 0
              #endif
              
              #ifndef __linux__
              #include "pico/stdlib.h"
              #include <stdbool.h>
              #include <stdio.h>
              #include <stdlib.h>
              #else
              #include <stdbool.h>
              #include <stdio.h>
              #include <stdlib.h>
              #include <assert.h>
              #include <unistd.h>
              #include <sys/time.h>
              #include <termios.h>
              #include <signal.h>
              
              void sleep_ms(unsigned int ms){
                  usleep(ms*1000);
              }
              unsigned int time_us_32(){
                  struct timeval tv;
                  gettimeofday(&tv,0);
                  return tv.tv_usec;
              }
              
              struct termios tiorig;
              void onecho(){
                  tcsetattr(0,TCSANOW,&tiorig);
              }
              void onexit(int s){
                  onecho();
                  printf("Exiting on signal %d...\n",s);
                  exit(1);
              }
              void stdio_init_all(){
                  struct termios t;
                  tcgetattr(0,&t);
                  tiorig=t;
                  signal(SIGHUP,onexit);
                  signal(SIGINT,onexit);
                  signal(SIGQUIT,onexit);
                  atexit(onecho);
                  t.c_lflag &= ~(ECHO|ICANON);
                  t.c_iflag &= ~(INLCR|ICRNL);
                  tcsetattr(0,TCSANOW,&t);
              }
              #define getchar_timeout_us(X)
              #endif
              
              // boundaries
              #define N_BATS 3
              #define N_ROOMS 20  // must be even
              #define N_TUNNELS 3 // must be 3
              #define N_PITS 3
              #define N_ARROWS 5
              
              // room flags
              #define HAS_BAT ((char)(1 << 0))
              #define HAS_PIT ((char)(1 << 1))
              #define HAS_WUMPUS ((char)(1 << 2))
              #define HAS_VISIT ((char)(1 << 3))
              
              // tunnel flag
              #define UN_MAPPED ((char)-1)
              
              static unsigned cave, empty_cave;     // bitmaps
              static unsigned arrow, loc, wloc;     // arrow count, player and wumpus locations
              static char cycle[N_ROOMS];           // array of next room in cycle
              static char* const flags = cycle;     // reuse cycle array but avoid usage confusion!
              static char room[N_ROOMS][N_TUNNELS]; // tunnel map
              
              static const char* intro = // clang-format off
                  "\n"
                  "The Wumpus lives in a cave of %d rooms.\n"
                  "Each room has %d tunnels leading to other rooms.\n\n"
                  "Hazards:\n\n"
                  "Bottomless Pits - %d rooms have Bottomless Pits in them.\n"
                  " If you go there, you fall into the pit and lose!\n"
                  "Super Bats - %d other rooms have super bats.\n"
                  " If you go there, a bat will grab you and take you to\n"
                  " somewhere else in the cave where you could\n"
                  " fall into a pit or run into the . . .\n\n"
                  "Wumpus:\n\n"
                  "The Wumpus is not bothered by the hazards since\n"
                  "he has sucker feet and is too big for a bat to lift.\n\n"
                  "Usually he is asleep. Two things wake him up:\n"
                  " your entering his room\n"
                  " your shooting an arrow anywhere in the cave.\n"
                  "If the wumpus wakes, he either decides to move one room or\n"
                  "stay where he was. But if he ends up where you are,\n"
                  "he eats you up and you lose!\n\n"
                  "You:\n\n"
                  "Each turn you may either move or shoot a crooked arrow.\n\n"
                  "Moving - You can move to one of the adjoining rooms;\n"
                  " that is, to one that has a tunnel connecting it with\n"
                  " the room you are in.\n\n"
                  "Shooting - You have %d arrows. You lose when you run out.\n"
                  " Each arrow can go from 1 to %d rooms.\n"
                  " You aim by telling the computer\n"
                  " The arrow's path is a list of room numbers\n"
                  " telling the arrow which room to go to next.\n"
                  " The first room in the path must be connected to the\n"
                  " room you are in. Each succeeding room must be\n"
                  " connected to the previous room.\n"
                  " If there is no tunnel between two of the rooms\n"
                  " in the arrow's path, the arrow chooses one of the\n"
                  " three tunnels from the room it's in and goes its\n"
                  " own way.\n\n"
                  " If the arrow hits the wumpus, you win!\n"
                  " If the arrow hits you, you lose!\n\n"
                  "Warnings:\n\n"
                  "When you are one or two rooms away from the wumpus,\n"
                  "the computer says:\n"
                  "   'I smell a Wumpus'\n"
                  "When you are one room away from some other hazard, it says:\n"
                  "   Bat    - 'Bats nearby'\n"
                  "   Pit    - 'I feel a draft'\n";
              // clang-format on
              
              // Random numbers
              
              // Uniform distribution 0..n-1
              static unsigned random_number(unsigned n) {
                  return (unsigned)(((rand() & RAND_MAX) / 2147483648.0) * n);
              }
              
              // Bitmap functions
              static inline void occupy_room(unsigned b) { cave &= ~(1 << b); }
              static inline bool room_is_empty(unsigned b) { return (cave & (1 << b)) != 0; }
              static inline unsigned empty_room_count(void) { return __builtin_popcount(cave); }
              
              // Console output
              static void put_str(char* s) {
                  fputs(s, stdout);
                  fflush(stdout);
              }
              
              static void put_char(char c) {
                  putc(c, stdout);
                  fflush(stdout);
              }
              
              static void put_nl(void) { put_char('\n'); }
              
              // Console input
              static unsigned argc;
              static char* argv[N_ARROWS + 1];
              static char cmd_buffer[64];
              static char cchr;
              
              static void get_chr(void) {
                  put_char(cchr = fgetc(stdin));
                  if (cchr == '\r')
                      put_nl();
              }
              
              static void get_and_parse_cmd(void) {
                  // read line into buffer
                  char* cp = cmd_buffer;
                  do {
                      get_chr();
                      if (cchr != '\b') {
                          if(cp-cmd_buffer+1>=sizeof(cmd_buffer))
                              cchr = '\r';
                          *cp++ = cchr;
                      } else if (cp != cmd_buffer) {
                          cp--;
                          put_str(" \b");
                      }
                  } while (cchr != '\r');
                  // parse buffer
                  cp = cmd_buffer;
                  bool not_last = true;
                  for (argc = 0; not_last && (argc <= N_ARROWS); argc++) {
                      while ((*cp == ' ') || (*cp == ','))
                          cp++; // skip blanks
                      if (*cp == '\r')
                          break;
                      argv[argc] = cp; // start of string
                      while ((*cp != ' ') && (*cp != ',') && (*cp != '\r'))
                          cp++; // skip non blank
                      if (*cp == '\r')
                          not_last = false;
                      *cp++ = 0; // terminate string
                  }
                  if (argc)
                      *argv[0] |= ' '; // to lower lowercase
              }
              
              // Cave generator helpers
              
              #if N_ROOMS == 20 // dodecahedron must have 20 rooms
              
              static unsigned char A[N_ROOMS][N_ROOMS];
              static unsigned char T[N_ROOMS][N_ROOMS];
              static unsigned char TT[N_ROOMS][N_ROOMS];
              
              #if !defined(NDEBUG)
              
              // Known dodecahedron for debug sanity check
              static const char dodecahedron[N_ROOMS][N_TUNNELS] = {
                  {1, 4, 7},   {0, 2, 9},    {1, 3, 11},  {2, 4, 13},  {0, 3, 5},    {4, 6, 14},   {5, 7, 16},
                  {0, 6, 8},   {7, 9, 17},   {1, 8, 10},  {9, 11, 18}, {2, 10, 12},  {11, 13, 19}, {3, 12, 14},
                  {5, 13, 15}, {14, 16, 19}, {6, 15, 17}, {8, 16, 18}, {10, 17, 19}, {12, 15, 18}};
              
              #endif // !defined(NDEBUG)
              
              // The dodecahedron detector
              static void matrix_mult(void) {
                  unsigned i, j, k;
                  for (i = 0; i < N_ROOMS; i++)
                      for (j = 0; j < N_ROOMS; j++)
                          TT[i][j] = 0;
                  for (i = 0; i < N_ROOMS; i++)
                      for (k = 0; k < N_ROOMS; k++)
                          for (j = 0; j < N_ROOMS; j++)
                              TT[i][j] = TT[i][j] + A[i][k] * T[k][j];
                  for (i = 0; i < N_ROOMS; i++)
                      for (j = 0; j < N_ROOMS; j++)
                          A[i][j] = TT[i][j];
              }
              
              static bool is_dodecahedron(void) {
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      for (unsigned j = 0; j < N_ROOMS; j++)
                          A[i][j] = 0;
                  for (unsigned v = 0; v < N_ROOMS; v++)
                      for (unsigned d = 0; d < N_TUNNELS; d++)
                          A[v][(unsigned char)room[v][d]] = 1;
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      for (unsigned j = 0; j < N_ROOMS; j++)
                          T[i][j] = A[i][j];
                  for (unsigned p = 2; p < 6; p++)
                      matrix_mult();
                  for (unsigned v = 0; v < N_ROOMS; v++)
                      if (A[v][v] != 6)
                          return false;
                  return true;
              }
              
              #endif // N_ROOMS == 20
              
              // Pick a random vacant room
              static unsigned pick_empty_room(void) {
                  unsigned r, n = random_number(empty_room_count());
                  for (r = 0; r < N_ROOMS; r++)
                      if (room_is_empty(r)) {
                          if (n == 0)
                              break;
                          n--;
                      }
                  occupy_room(r);
                  return r;
              }
              
              // Add tunnel from room to room
              static void add_direct_tunnel(unsigned f, unsigned t) {
                  for (unsigned i = 0; i < N_TUNNELS; i++)
                      if (room[f][i] == UN_MAPPED) {
                          room[f][i] = t;
                          break;
                      }
              }
              
              // Add tunnels connecting to and from
              static void add_tunnel(unsigned f, unsigned t) {
                  add_direct_tunnel(f, t);
                  add_direct_tunnel(t, f);
              }
              
              // exchange adjacent bytes if 1st byte is greater than 2nd
              static inline void exchange(char* t) {
                  if (*t > *(t + 1)) {
                      *t = *t ^ *(t + 1);
                      *(t + 1) = *t ^ *(t + 1);
                      *t = *t ^ *(t + 1);
                  }
              }
              
              // Generate a new cave
              static bool directed_graph(void) {
              
                  srand(time_us_32());
              
              #if !defined(NDEBUG) && (N_ROOMS == 20)
              
                  // test the dodecahedron detector
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      for (unsigned t = 0; t < N_TUNNELS; t++)
                          room[r][t] = dodecahedron[r][t];
                  assert(is_dodecahedron());
              
              #endif // !defined(NDEBUG) && (N_ROOMS == 20)
              
                  // Clear the tunnel map
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      for (unsigned t = 0; t < N_TUNNELS; t++)
                          room[r][t] = UN_MAPPED;
              
                  // Step 1 - Generate a random 20 room cycle.
                  unsigned r = 0, rs = 0;
                  cave = empty_cave;
                  occupy_room(r);
                  do {
                      unsigned e = pick_empty_room();
                      add_tunnel(r, e);
                      cycle[r] = e;
                      r = e;
                  } while (cave);
                  cycle[r] = rs;
                  add_tunnel(r, rs);
              
                  // step 2 - add the third tunnels... if possible.
                  cave = empty_cave;
                  for (unsigned n = 0; n < N_ROOMS / 2; n++) {
                      assert(cave); // Can't happen
                      r = pick_empty_room();
                      unsigned save_cave = cave;
                      // disqualify neighbors
                      for (unsigned t = 0; t < N_TUNNELS; t++)
                          occupy_room(room[r][t]);
                      if (!cave) // Oops, can't complete this one!
                          return false;
                      unsigned e = pick_empty_room();
                      cave = save_cave;
                      occupy_room(e);
                      add_tunnel(r, e);
                  }
              
                  // step 3 - sort the tunnels
                  for (unsigned i = 0; i < N_ROOMS; i++) {
                      exchange(room[i]);
                      exchange(room[i] + 1);
                      exchange(room[i]);
                  }
              
              #if !defined(NDEBUG)
              
                  // map sanity check
                  unsigned count[N_ROOMS];
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      count[i] = 0;
                  for (unsigned i = 0; i < N_ROOMS; i++) {
                      // 3 unique tunnels
                      assert((room[i][0] != room[i][1]) && (room[i][0] != room[i][2]) &&
                             (room[i][1] != room[i][2]));
                      for (unsigned j = 0; j < N_TUNNELS; j++) {
                          // tunnel doesn't go to itself
                          assert(room[i][j] != i);
                          count[(unsigned char)room[i][j]]++;
                      }
                  }
                  // each room has 3 tunnels
                  for (unsigned i = 0; i < N_ROOMS; i++)
                      assert(count[i] == 3);
              
              #endif // !defined(NDEBUG)
              
                  return true;
              }
              
              // recursive depth 1st neighbor search
              static bool near(unsigned r, char has, unsigned depth) {
                  for (unsigned t = 0; t < N_TUNNELS; t++) {
                      if (flags[(unsigned char)room[r][t]] & has)
                          return true;
                      if ((depth > 1) && near(room[r][t], has, depth - 1))
                          return true;
                  }
                  return false;
              }
              
              // try to find player
              static bool search_path(unsigned r, unsigned depth) {
                  flags[r] |= HAS_VISIT;
                  for (unsigned t = 0; t < N_TUNNELS; t++) {
                      unsigned e = room[r][t];
                      if (e == loc)
                          return true;
                      if (depth)
                          if (!(flags[e] & HAS_VISIT) && search_path(e, depth - 1)) {
                              printf("%d ", room[r][t] + 1);
                              return true;
                          }
                  }
                  return false;
              }
              
              // state functions
              typedef void* (*func_ptr)(void);
              static func_ptr start_handler(void);
              static func_ptr instruction_handler(void);
              static func_ptr init_cave_handler(void);
              static func_ptr setup_handler(void);
              static func_ptr loop_handler(void);
              static func_ptr done_handler(void);
              static func_ptr again_handler(void);
              static func_ptr move_player_handler(void);
              static func_ptr shoot_handler(void);
              static func_ptr move_wumpus_handler(void);
              static func_ptr leave_handler(void);
              
              static bool valid_room_number(int n) {
                  bool b = (n >= 0) && (n < N_ROOMS);
                  if (!b)
                      printf("\n%d is not a room number\n", n + 1);
                  return b;
              }
              
              // begin
              static func_ptr start_handler(void) {
                  empty_cave = (unsigned)-1 >> (32 - N_ROOMS);
                  put_str("\n\n"
                          "Welcome to HUNT THE WUMPUS.\n\n"
                          "Instructions (y/N) ? ");
                  get_and_parse_cmd();
                  if ((argc == 0) || (*argv[0] == 'n'))
                      return (func_ptr)init_cave_handler;
                  return (func_ptr)instruction_handler;
              }
              
              // exit. Nowhere to go...
              static func_ptr leave_handler(void) {
                  put_str("\nBye!\n\n");
                  exit(0);
                  return (func_ptr)NULL; // satisfy the compiler
              }
              
              // show instructions
              static func_ptr instruction_handler(void) {
                  printf(intro, N_ROOMS, N_TUNNELS, N_PITS, N_BATS, N_ARROWS, N_ARROWS);
                  return (func_ptr)init_cave_handler;
              }
              
              // create a fresh cave
              static func_ptr init_cave_handler(void) {
                  put_str("\nCreating new cave map.");
                  while (!directed_graph())
                      ;
              #if N_ROOMS == 20
                  if (is_dodecahedron())
                      put_str(" Ooh! You're entering the rarest of caves, a dodecahedron.");
              #endif // N_ROOMS == 20
                  put_nl();
                  return (func_ptr)setup_handler;
              }
              
              // setup a new game in the current cave
              static func_ptr setup_handler(void) {
                  // put in player, wumpus, pits and bats
                  unsigned i, j;
                  arrow = N_ARROWS;
                  for (i = 0; i < N_ROOMS; i++)
                      flags[i] = 0;
                  for (i = 0; i < N_PITS;) {
                      j = random_number(N_ROOMS);
                      if ((flags[j] & HAS_PIT) == 0) {
                          flags[j] |= HAS_PIT;
                          i++;
                      }
                  }
                  for (i = 0; i < N_BATS;) {
                      j = random_number(N_ROOMS);
                      if ((flags[j] & (HAS_PIT | HAS_BAT)) == 0) {
                          flags[j] |= HAS_BAT;
                          i++;
                      }
                  }
                  wloc = random_number(N_ROOMS);
                  flags[wloc] |= HAS_WUMPUS;
                  for (;;)
                  {
                      i = random_number(N_ROOMS);
                      if ((flags[i] & (HAS_PIT | HAS_BAT | HAS_WUMPUS)) == 0) {
                          loc = i;
                          break;
                      }
                  }
                  return (func_ptr)loop_handler;
              }
              
              // just landed in new room
              static func_ptr loop_handler(void) {
                  //  main loop of the game
                  printf("\nYou are in room %d", loc + 1);
                  if (flags[loc] & HAS_PIT) {
                      put_str(". You fell into a pit. You lose.\n");
                      return (func_ptr)done_handler;
                  }
                  if (flags[loc] & HAS_WUMPUS) {
                      put_str(". You were eaten by the wumpus. You lose.\n");
                      return (func_ptr)done_handler;
                  }
                  if (flags[loc] & HAS_BAT) {
                      put_str(". Theres a bat in your room. Carying you away.\n");
                      loc = random_number(N_ROOMS);
                      return (func_ptr)loop_handler;
                  }
                  if (near(loc, HAS_WUMPUS, 2))
                      put_str(". I smell a wumpus");
                  if (near(loc, HAS_BAT, 1))
                      put_str(". Bats nearby");
                  if (near(loc, HAS_PIT, 1))
                      put_str(". I feel a draft");
                  printf(". There are tunnels to rooms %d, %d and %d.\n", room[loc][0] + 1, room[loc][1] + 1,
                         room[loc][2] + 1);
                  return (func_ptr)again_handler;
              }
              
              #if !defined(NDEBUG) || WUMPUS_CHEAT
              
              static func_ptr dump_cave_handler(void) {
                  for (unsigned r = 0; r < N_ROOMS; r++) {
                      if ((r & 3) == 0)
                          put_nl();
                      printf("%02d:%02d %02d %02d  ", r + 1, room[r][0] + 1, room[r][1] + 1, room[r][2] + 1);
                  }
                  printf("\n\nPlayer:%02d  Wumpus:%02d  Pits:", loc + 1, wloc + 1);
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      if (flags[r] & HAS_PIT)
                          printf("%02d ", r + 1);
                  put_str(" Bats:");
                  for (unsigned r = 0; r < N_ROOMS; r++)
                      if (flags[r] & HAS_BAT)
                          printf("%02d ", r + 1);
                  put_nl();
                  return (func_ptr)again_handler;
              }
              
              static func_ptr best_shot_handler(void) {
                  printf("\nBest shot: ");
                  unsigned i;
                  for (i = 0; i <= N_ARROWS; i++) {
                      for (unsigned j = 0; j < N_ROOMS; j++)
                          flags[j] &= ~HAS_VISIT;
                      if (search_path(wloc, i)) {
                          printf("%d", wloc + 1);
                          break;
                      }
                  }
                  if (i == N_ARROWS)
                      put_str("none");
                  put_nl();
                  return (func_ptr)again_handler;
              }
              
              #endif // NDEBUG
              
              // what are you going to do here?
              static func_ptr again_handler(void) {
                  put_str("\nMove or shoot (m/s) ? ");
                  get_and_parse_cmd();
                  if (argc == 0)
                      return (func_ptr)again_handler;
                  switch (*argv[0]) {
                  case 'm':
                      return (func_ptr)move_player_handler;
                  case 's':
                      return (func_ptr)shoot_handler;
              #if !defined(NDEBUG) || WUMPUS_CHEAT
                  case 'd': // dump cave map
                      return (func_ptr)dump_cave_handler;
                  case 'b': // find best shot
                      return (func_ptr)best_shot_handler;
              #endif // NDEBUG
                  }
                  put_str("\nWhat ?\n");
                  return (func_ptr)again_handler;
              }
              
              // move on to next room
              static func_ptr move_player_handler(void) {
                  if (argc < 2) {
                      put_str("\nwhich room ?\n");
                      return (func_ptr)again_handler;
                  }
                  int r, t;
                  r = atoi(argv[1]) - 1;
                  if (!valid_room_number(r))
                      return (func_ptr)again_handler;
                  if (r < N_ROOMS)
                      for (t = 0; t < N_TUNNELS; t++)
                          if (r == room[loc][t]) {
                              loc = r;
                              if (flags[r] & HAS_WUMPUS)
                                  return (func_ptr)move_wumpus_handler;
                              return (func_ptr)loop_handler;
                          }
                  put_str("You hit the wall!\n");
                  return (func_ptr)again_handler;
              }
              
              // shoot an arrow
              static func_ptr shoot_handler(void) {
                  if (argc < 2) {
                      put_str("Which tunnel(s) ?\n");
                      return (func_ptr)again_handler;
                  }
                  for (unsigned i = 1; i < argc; i++)
                      if (!valid_room_number(atoi(argv[i]) - 1))
                          return (func_ptr)again_handler;
                  unsigned t, r = atoi(argv[1]) - 1;
                  for (t = 0; t < N_TUNNELS; t++)
                      if (room[loc][t] == r)
                          break;
                  if (t == N_TUNNELS) {
                      put_str("\nNo tunnel to that room!\n");
                      return (func_ptr)again_handler;
                  }
                  put_nl();
                  int l = loc;
                  for (unsigned i = 0; i < 5; i++) {
                      if (i > argc - 2)
                          break;
                      r = atoi(argv[i + 1]) - 1;
                      for (t = 0; t < N_TUNNELS; t++)
                          if (r == room[l][t])
                              break;
                      if (t == N_TUNNELS)
                          t = random_number(N_TUNNELS);
                      r = room[l][t];
                      put_str("~>");
                      sleep_ms(500);
                      printf("%d", r + 1);
                      if (r == loc) {
                          put_str("\n\nYou shot yourself! You lose.\n");
                          return (func_ptr)done_handler;
                      }
                      if (flags[r] & HAS_WUMPUS) {
                          printf("\n\nYou slew the wumpus in room %d. You win!\n", r + 1);
                          return (func_ptr)done_handler;
                      }
                      sleep_ms(500);
                      l = r;
                  }
                  put_str("\n\nYou missed!");
                  if (--arrow == 0) {
                      put_str(" That was your last shot! You lose.\n");
                      return (func_ptr)done_handler;
                  }
                  put_nl();
                  return (func_ptr)move_wumpus_handler;
              }
              
              // wumpus disturbed, time to move it
              static func_ptr move_wumpus_handler(void) {
                  int i;
                  flags[wloc] &= ~HAS_WUMPUS;
                  i = random_number(N_TUNNELS + 1);
                  if (i != N_TUNNELS)
                      wloc = room[wloc][i];
                  if (wloc == loc) {
                      printf("\nThe wumpus %sate you. You lose.\n", ((i == N_TUNNELS) ? "" : "moved and "));
                      return (func_ptr)done_handler;
                  }
                  flags[wloc] |= HAS_WUMPUS;
                  return (func_ptr)loop_handler;
              }
              
              // game over. Play again?
              static func_ptr done_handler(void) {
                  put_str("\nAnother game (Y/n) ? ");
                  get_and_parse_cmd();
                  if ((argc == 0) || (*argv[0] == 'y')) {
                      put_str("\nSame room setup (Y/n) ? ");
                      get_and_parse_cmd();
                      if ((argc == 0) || (*argv[0] == 'y'))
                          return (func_ptr)setup_handler;
                      else
                          return (func_ptr)init_cave_handler;
                  }
                  return (func_ptr)leave_handler;
              }
              
              // forever loop
              int main(void) {
                  stdio_init_all();
                  getchar_timeout_us(1000); // swallow the spurious EOF character???
                  put_str("\033[H\033[J");  // try to clear the screen
                  func_ptr state = (func_ptr)start_handler;
                  for (;; state = state())
                      ;
              }
              
              I confined all changes to the headers and terminal setup at the beginning of the file. Hopefully it still builds on the Pico.

              ejolson
              Posts: 7241
              Joined: Tue Mar 18, 2014 11:47 am

              Re: A Birthday Present for Fido

              Sat May 15, 2021 2:55 pm

              ejolson wrote:
              Sat May 15, 2021 4:11 am
              ejolson wrote:
              Fri May 14, 2021 6:42 pm
              Edit: It appears the spelling is fixed.
              Here is a back port of the the pico version of Hunt the Wumpus to the Rasbperry Pi.
              I've been testing this version and it seems to play fine. I noticed you swapped out the random number generator from the 7th edition of Unix and replaced it with one from in the Pico C library. Do you know if there are any seeds for this generator that result in a dodecahedron?

              Alternatively, one could skip the random cave generation a certain percentage of the time to guarantee a dodecahedron can appear. Given people's risk assessment during the epidemic it would appear 10 percent is considered extremely rare.

              In the SuperPET code I allowed input of the random seed so known dodecahedra and previous game configurations could be played by choice.

              ejolson
              Posts: 7241
              Joined: Tue Mar 18, 2014 11:47 am

              Re: A Birthday Present for Fido

              Sun May 16, 2021 6:06 pm

              ejolson wrote:
              Tue May 04, 2021 5:07 pm
              What I find interesting about these early computer games is how their simplicity inspired beginners to create their own improved versions. While there is now an indie game industry, the fact that the early games were coded using general-purpose programming languages, in my opinion, led to transferable skills that empowered people to later do much more with what they learned while making the game.
              Yesterday the cassette came unwound that contained the PET version of Dog Star Adventure.

              https://en.wikipedia.org/wiki/Dog_Star_Adventure

              Even after the deck was cleaned and the sticky capstan replaced, the recently crinkled tape was unreadable. This led to an emergency in the doghouse that I tried to solve with the source code published in
              • Softside: Your BASIC Software Magazine, May 1979.
                • Computer and Video Games, No.8, June 1982.
                While the May 1979 magazine proudly proclaimed Dog Star Adventure on the cover, it was useful to examine both magazines to detect errors in the optical character recognition. Unfortunately, the original was written in TRS-80 Basic and not fully compatible with the PET.

                Interestingly, the June 1982 magazine also included a short review of Wumpus.

                Image

                I reflected on two things after reading the review.
                • In the past it was standard practice to translate publicly available source code so it ran on a different computer system and sell the loadable images. For example, Microsoft translated the Adventure game of Crowther and Woods so it ran on popular 8-bit home computers and sold that as a commercial product.
                  • As the Wumpus can be smelled from two rooms away, the Sharpsoft version appears to derive from the Unix code rather than the Basic program written by Gregory Yob. I wonder if it had the same random cave generator and whether it ever created a dodecahedron.
                  Had the extent to which copyright applies to computer software been fully realized back then, it is possible these commercial products could not have been made. Alternatively, something like the GNU license might have been created sooner, in which case the source code for Microsoft's translation of Adventure as well as the Sharpsoft version of Wumpus might be easily available.
                  Last edited by ejolson on Sun May 16, 2021 11:20 pm, edited 1 time in total.

                  ejolson
                  Posts: 7241
                  Joined: Tue Mar 18, 2014 11:47 am

                  Re: A Birthday Present for Fido

                  Sun May 16, 2021 8:51 pm

                  ejolson wrote:
                  Sun May 16, 2021 6:06 pm
                  as well as the Sharpsoft version of Wumpus might be easily available.
                  I did a little more searching and found

                  Image

                  on page 41 of
                  • Computer and Video Games, No. 6, April 1982
                  published on cassette tape by Silversoft. Given the super designation, this big 16K program might have many enhancements compared to the game that has been focused on in this thread.

                  From a cultural point of view it would be really interesting to compare the different features implemented by various people at different times and correlate those features to trends in literature and art. Further information including sample runs or even the source code for many different wumpus programs would make a great birthday present for Fido even better!

                  Speaking of variations, for at least ten years Microsoft has sponsored an annual Hunt-the-Wumpus game-design competition. For example, in 2019, nine teams created new versions with innovative features and themes.

                  Image
                  https://www.lwsd.org/programs-and-servi ... ompetition

                  Again, the details of each variation and the source code seem difficult to locate. Could all these games be lost after only a few years pass? Did any have dodecahedron detectors? Do wumpi eat source code?

                  ejolson
                  Posts: 7241
                  Joined: Tue Mar 18, 2014 11:47 am

                  Re: A Birthday Present for Fido

                  Mon May 17, 2021 9:38 pm

                  ejolson wrote:
                  Sun May 16, 2021 8:51 pm
                  ejolson wrote:
                  Sun May 16, 2021 6:06 pm
                  as well as the Sharpsoft version of Wumpus might be easily available.
                  I did a little more searching and found

                  Image

                  on page 41 of
                  • Computer and Video Games, No. 6, April 1982
                  published on cassette tape by Silversoft. Given the super designation, this big 16K program might have many enhancements compared to the game that has been focused on in this thread.
                  It seems that Super Wumpus might refer to the version modified by Howard Franklin called Wumpus 3 mentioned in

                  Image
                  https://www.atariarchives.org/bcc2/show ... p?page=244

                  According to Fido, the tape referred to in the article was made of paper.
                  Jason Dyer wrote: people have known about it (Wumpus 3) but it has long been considered a “lost game”. Fortunately, I found it in a special “Games” booklet that PCC put out in 1974.
                  https://bluerenga.blog/2019/04/05/befor ... s-2-and-3/

                  What excited me about this discovery was that the listing was labeled

                  Image

                  Could this be the same Super Wumpus sold by Silversoft? The only problem with that theory is that the length is given as 2529 words, which implies it could fit in 8 KB or 4 KB and not need a full 16 KB to run. Does anyone still have the tape?

                  While looking for other versions, I found an online book that teaches Python where the first programming example is to code Hunt the Wumpus. Unless you actually want to learn Python--reportedly what the Pi was designed for--the best part of the book are 22 cartoons which describe various aspects of writing wumpus programs. For example

                  Image
                  https://livebook.manning.com/book/hello ... chapter-2/
                  Last edited by ejolson on Tue May 18, 2021 4:17 pm, edited 2 times in total.

                  ejolson
                  Posts: 7241
                  Joined: Tue Mar 18, 2014 11:47 am

                  Re: A Birthday Present for Fido

                  Tue May 18, 2021 8:13 am

                  ejolson wrote:
                  Mon May 17, 2021 9:38 pm
                  Could this be the same Super Wumpus sold by Silversoft? The only problem with that theory is that the length is given as 2529 words, which implies it could fit in 8 KB or 4 KB and not need a full 16 KB to run. Does anyone still have the tape?
                  Woohoo! I found the tape!
                    SuperWumpus.TapeA.jpg
                    SuperWumpus.TapeA.jpg (47.36 KiB) Viewed 428 times
                    http://www.zx81stuff.org.uk/zx81/tape/SuperWumpus

                    It seems Super Wumpus as sold by Silversoft is quite different than Howard's game. Instead of termite swarms that eat arrows it has food, floods, a time limit and a graphical depiction of the caves. Rather than moving by room number one must choose L, R or B for left, right or back. All commands are given by one of the single letters A, D, E, F, G, M, S, R and Q. Full words are not recognized.

                    Here is the splash screen

                    Image

                    and the graphical depiction of rooms looks like

                    Image

                    I might need a PETSCII translation for the super pet.

                    After figuring out how to enable the keyboard on the RetroArch Sinclair EightyOne emulator and turn off the hot keys, it played reasonably well. Annoyingly, the description of whether you smell a wumpus, a draft and other things is only momentarily displayed on the screen and then overwritten by the menu of commands. It's difficult to reconstruct the game from the Basic listing because it is a save image with initialized variables that govern how the game behaves.

                    Although, I haven't won a single game yet, it's amazing how many different 8-bit computers fit inside a Raspberry Pi.

                    ejolson
                    Posts: 7241
                    Joined: Tue Mar 18, 2014 11:47 am

                    Re: A Birthday Present for Fido

                    Thu May 20, 2021 4:53 am

                    ejolson wrote:
                    Tue May 18, 2021 8:13 am
                    It's difficult to reconstruct the game from the Basic listing because it is a save image with initialized variables that govern how the game behaves.
                    In many ways the data-ownership model of Rust is the modern day equivalent of the revolutionary changes Pascal brought in it's time with structured programming and strong typing. As Fido likes gophers (for chasing in the park) better, I decided to rewrite Super Wumpus in the Go language for the Raspberry Pi.

                    The comparison between writing
                    • a Wumpus game on the SuperPET
                      • a Super Wumpus game on the Pi
                      examines two similar tasks to shed light on how programming a computer designed for teaching has changed since the golden age of personal computing until today.

                      A preliminary run of the Go code looks like

                      Code: Select all

                      $ ./gohunt 
                      gohunt--Go Implementation of Super Wumpus
                      Written 2021 by Eric Olson
                      
                      You are playing
                      
                          ┏━┓╻ ╻┏━┓┏━╸┏━┓   ╻ ╻╻ ╻┏┳┓┏━┓╻ ╻┏━┓
                          ┗━┓┃ ┃┣━┛┣╸ ┣┳┛   ┃╻┃┃ ┃┃┃┃┣━┛┃ ┃┗━┓
                          ┗━┛┗━┛╹  ┗━╸╹┗╸   ┗┻┛┗━┛╹ ╹╹  ┗━┛┗━┛
                      
                      An original recreation of the fantastic Silversoft game.
                      Enter ? at any prompt for help.
                      
                      Select level of play (1-3): 3
                      Select cave pattern (0-18446744073709551615): 3
                      Select number of arrows (1-10): 10
                      Select amount of food in Kg (30-40): 40
                      Select the game (0-255): 213
                      
                      You are in room number 2.        ┏━━━━━┓     ┏━━━━━┓     ┏━━━━━┓
                                                       ┃  6  ┣━━━━━┫  2  ┣━━━━━┫  14 ┃
                                                       ┗━━━━━┛     ┗━━┳━━┛     ┗━━━━━┛
                                                        (l)eft        ┃        (r)ight
                                                                   ┏━━┻━━┓
                                                                   ┃  7  ┃
                                                                   ┗━━━━━┛
                                                                   (b)ack
                      Do something: l
                      
                      You are in room number 6.        ┏━━━━━┓     ┏━━━━━┓     ┏━━━━━┓
                      I smell a Wumpus!                ┃  5  ┣━━━━━┫  6  ┣━━━━━┫  11 ┃
                      I see the point.                 ┗━━━━━┛     ┗━━┳━━┛     ┗━━━━━┛
                                                        (l)eft        ┃        (r)ight
                                                                   ┏━━┻━━┓
                                                                   ┃  2  ┃
                                                                   ┗━━━━━┛
                                                                   (b)ack
                      Do something: l
                      
                      You are in room number 5.        ┏━━━━━┓     ┏━━━━━┓     ┏━━━━━┓
                      I smell a Wumpus!                ┃  12 ┣━━━━━┫  5  ┣━━━━━┫  15 ┃
                      It's brighter this way.          ┗━━━━━┛     ┗━━┳━━┛     ┗━━━━━┛
                      In the corner are arrows!         (l)eft        ┃        (r)ight
                                                                   ┏━━┻━━┓
                                                                   ┃  6  ┃
                                                                   ┗━━━━━┛
                                                                   (b)ack
                      Do something: sl
                      
                      OK, the arrow has been fired.  Now entering room numbers
                          12, 18, 17, 3, 9.
                      
                      The smell is getting stronger.  Look out!  It's the Wumpus!
                      It has not seen you.  It seems to be leaving.
                      
                      Do something: sr
                      
                      OK, the arrow has been fired.  Now entering room numbers
                          15, 10, 18, 12, 5.
                      Well done.  You got the Wumpus!
                      Now you must get out.
                      
                      Do something: l
                      
                      You are in room number 12.       ┏━━━━━┓     ┏━━━━━┓     ┏━━━━━┓
                      I can hear squeaking.            ┃  16 ┣━━━━━┫  12 ┣━━━━━┫  18 ┃
                      This ground is loose.            ┗━━━━━┛     ┗━━┳━━┛     ┗━━━━━┛
                      I see the point.                  (l)eft        ┃        (r)ight
                                                                   ┏━━┻━━┓
                                                                   ┃  5  ┃
                                                                   ┗━━━━━┛
                                                                   (b)ack
                      Do something: b
                      
                      You are in room number 5.        ┏━━━━━┓     ┏━━━━━┓     ┏━━━━━┓
                      It's brighter this way.          ┃  12 ┣━━━━━┫  5  ┣━━━━━┫  15 ┃
                      In the corner are arrows!        ┗━━━━━┛     ┗━━┳━━┛     ┗━━━━━┛
                                                        (l)eft        ┃        (r)ight
                                                                   ┏━━┻━━┓
                                                                   ┃  6  ┃
                                                                   ┗━━━━━┛
                                                                   (b)ack
                      Do something: r
                      
                      You are in room number 15.       ┏━━━━━┓     ┏━━━━━┓     ┏━━━━━┓
                      Look, there's light!             ┃  10 ┣━━━━━┫  15 ┣━━━━━┫  11 ┃
                      I see the point.                 ┗━━━━━┛     ┗━━┳━━┛     ┗━━━━━┛
                                                        (l)eft        ┃        (r)ight
                                                                   ┏━━┻━━┓
                                                                   ┃  5  ┃
                                                                   ┗━━━━━┛
                                                                   (b)ack
                      Warning:  The cave will soon be
                      filling with water.  Hurry up!
                      
                      Do something: escape
                      
                      Didn't you do well!
                      You got the Wumpus and escaped.
                      
                      Do you want to try again (y,n)? n
                      
                      Bye bye!
                      
                      I'm quite impressed with how much more natural left, right and back are for navigating. Note that, in line with modern user interfaces, I implemented back in a multilevel fashion just like a web browser.

                      Somehow the Go source bloated to 1077 lines compared to the 531 lines of the original ZX-81 Basic. This suggests there will be twice as many bugs in my new version; therefore, I'll spend a little time polishing the source before posting it.

                      Edit: In fact I noticed a bug just now in the output. The Wumpus enters the room you are in and then leaves, but the program forgot to call the move routine. That's why the Wumpus is killed by the arrow in room 5 rather than one of the adjoining rooms. This bug is now fixed bringing the total number of lines up to 1078. As there must be other bugs, I better play the game a few more times to find them.

                      ejolson
                      Posts: 7241
                      Joined: Tue Mar 18, 2014 11:47 am

                      Re: A Birthday Present for Fido

                      Thu May 20, 2021 5:13 pm

                      ejolson wrote:
                      Thu May 20, 2021 4:53 am
                      As there must be other bugs, I better play the game a few more times to find them.
                      I just fixed some misplaced parenthesis that affected how time and energy evolve during the game. I think the dynamics are pretty close to the original with one major change. The original code would get stuck in a loop if you requested to drop or eat something that you didn't have.

                      The repeated question

                      How many (1-9)?

                      and the refusal to accept 0 would lead to an infinite conversation consisting of the phrase you do not have that many and a repeat of the question.

                      Upon playing the game a bit more, I think getting stuck in this way was by design. That's why the game warns you and asks for confirmation before allowing you eat or drop the last bit of what you're carrying.

                      From an educational perspective, learning to not commit to something you're unable to do is an important life lesson. It could also help with the use of modern office productivity software. Nevertheless, I put an addition check into the code to remove this learning opportunity.

                      As for other bugs, the font on my phone doesn't render the Unicode text graphics accurately. It's not monospaced.

                      I took the text banner from

                      $ toilet -f future Super Wumpus

                      Then cut and pasted the needed graphics characters to make the text boxes for the map. Does anyone know if the observed rendering bug is caused by
                      • using the wrong unicode characters.
                        • the forum software for choosing the wrong font.
                          • the font metrics on my phone.
                          While it looks okay in a terminal window and on my desktop browser, it's not clear where the bug really is and the work around. Just like committing to dropping an arrow one doesn't have, how could anyone commit to using a monospaced Unicode font without the ability to render one?

                          Do the Unicode graphics in the previous code block look fine on anyone's phone?

                          Return to “Other projects”