Porting Adventure

Adventures with adventure: porting Crowther and Woods Collossal Cave Adventure to the ICL 1900 under George 3.

In the days before every machine was some variant of a 32 or 64 bit word with 8 bit bytes running programs written in C under some version of Unix porting programs could be quite tricky.

In order to give on example of what kind of problems could be encountered I decided to port Will Crowther and Don Woods' "Adventure" to the ICL 1900 running George 3.

The version chosen is Woods' "350 point" variant. (Copy here). This version of Adventure was written in Fortran IV for the DEC PDP 10, a 36 bit word machine with 6, 7, 8 or 9 bit characters.

Initial thoughts

Language

The Fortran compilers for the ICL 1900 implement various versions of ICL Extended Fortran, a dialect of Fortran 66. It is largely the same language as the PDP Fortran IV dialect with some simple differences.

(Much of this information, worked out with great loss of hair, turns out to be available from the Altas Computer Lab: Atlas Computing Division: Engineering Reports: FORTRAN Compilers and Loaders, R E Thomas, 29.04.76 ACD: Engineering Paper No 42).

The PDP source code format uses a TAB to seperate the label number field from the instruction field, rather than the traditional fixed 6 character field. Continuation lines are indicated by a numeric chacter as the first character of the instruction field, rather than the tradinional nonspace/nonzero in the 7th character position.

It is possible to store text containing TAB characters in George 3 NORMAL or ALLCHAR files, but the operating system and most available programming languages are rather poor at manipulating such files.

Eventualy I decided to write an Algol 60 program to convert from the PDP-10 source format to something readable by the ICL compilers.

The Fortran IV computed GOTO is written as:

       GOTO (1, 2, 3) EXPRESSION
while the ICL Fortran computed GOTO looks like:
       GOTO (1, 2, 3), VARIABLE
We will have to use temporary variables in the places where the expression is too complicated for ICL Fortran.

Fortran IV has some extra verbs for doing I/O to the console, TYPE and ACCEPT. These can be trivialy replaced by READ and WRITE for ICL Extended Fortran.

The program uses the IMPLICIT INTEGER statement to implicitly declare all variables as integer. This is not implemented by the smaller ICL compilers (#XFAT) so the larger compilers (e.g. #XFIV) will need to be used.

PDP Fortran IV allows character constants to be used anywhere integer constants are allowed, for example in code like:

      IF (WD1 .EQ. 'PLUGH') GOTO 2100
In ICL Fortran 66 character constants can only be used in DATA statements and subroutine and function calls, so this code will have to be rewritten as:
       IF (EQUAL (WD1, 'PLUGH')) GOTO 2100
(Assuming a suitable LOGICAL FUNCTION EQUAL).

PDP Fortran IV allows logical operations (.AND., .OR., .XOR. and .SHIFT.) on integer variables. Functions will have to be written in PLAN to replace them. (See here).

PDP-10 Fortran uses the format "G" for free-format input. This is similar to "G0" or "I0" in ICL Fortran with one unfortunate difference - the PDP-10 version stops reading at EOL. This means that code like this:

	READ (1, 1000) LOC, NEWLOC, (TK (I), I = 1, 20)
1000    FORMAT (99G)
will read only as many integers as are on the line, setting subsequent values to zero. The naïve translation into ICL Fortran would read exactly 22 integers, skipping any line boundaries. In order to work around this we will write a routine to emulate the PDP-10 behaviour by reading the line as text then doing in-core reads to convert the numbers. (See here).

PDP-10 Fortran allows octal constants, prefixed by a double quote:

	DATA MASKS/"4000000000,"20000000,"100000,"400,"2,0/
These will have to be replaced by decimal constants.

Hardware differences

The PDP-10 was interesting in that bytes could be of any size required, a byte address specified the word, the offset of the byte and the length of the byte.

Practicaly this meant that programs could be written to store text in six, seven or nine bit bytes, stored six, five or four bytes to the word.

Adventure was written to use seven bit bytes, five to the word, manipulated using Fortran A5 format. (Five characters, left justified in the word, padded with spaces on the right).

The routine GETIN reads from the console, and uses bit manipulation instructions to split the input into two five character words (with optional five character extensions used only for output). For example the user input:

	GO DOWNSTREAM
will be parsed into "GO" and "DOWNS" with an extension of "TREAM". The available commands and items have been carefully named to be unique in the first five characters, for example "DOWN" is distinguished from "DOWNS" in one important place.

To reduce the problems of porting, and avoid changing the gameplay we will keep the basic five-character names for comamnds and items.

However the ICL 1900 can only store 4 (six bit) characters per word, so we will need two machine words per five character "word" of user input. Luckily for us the ICL 1900 Fortran compiler normally allocates two words per INTEGER variable (to make sure that INTEGER is the same size as REAL, for compatability with IBM Fortran). We will have to be careful when copying user input as a simple assignment will only copy the first machine word (four characters). Similarly we will use the LOCATION compilation option to make sure subroutine arguments are passed by location rather than value.

(Later on we might like to reduce the memory usage of the program by compiling with the COMPRESS INTEGER AND LOGICAL option which instructs the compiler to only use one machine word per integer, and modifying the code to use two word arrays to hold the five characters of user input.)

We will write our own version of GETIN, using the Fortran library function COPY for byte copying.

The subroutine A5TOA1 converts some words input by the user in A5 format to A1 format for output, also by bit twiddling. We will write our own version of A5TOA1 using the COPY subroutine.

The function RAN generates random numbers, using:

	R=MOD(R*1021,1048576)
	RAN=(RANGE*R)/1048576
which will overflow with 24 bit integers. From sheer laziness we will use the routine FPMCRV from the SUBGROUPFSCE library.

A similar algorithm is used by the function WIZARD to produce a random challenge, which the user has to manipulate in some undocumented way. This will also require changing.

The array TRAVEL is initialised from the database to hold "NEWLOC * 1000 + KEYWORD", Unfortunatly "NEWLOC" can be up to 700,000 and 700,000,000 is too large to fit into an ICL 1900 integer (the largest positive integer is 8,388,607). The simple solution is to break the TRAVEL table into two arrays, one holding NEWLOC and the other KEYWORD.

To reduce the chance of the user examining the core image the list of commands is obscured by XORing them with a secret constant. As we have no easy access to both machine words of the 5 character "words" this may be difficult.

Environment

The PDP version was written to run under the TOPS-10 OS. We will be running under George 3. (I suppose it would be possible to run under Executive, entering commands from a card reader and reading the replies off the line printer).

The TOPS-10 version of Adventure was known as ADVENT - TOPS-10 had 6 character filenames (6 x 6bit characters in one 36 bit word). We'll call ours ADVENTURE as we have the luxury of 12 character filenames, although we'll also stick to good old 1960's style shouting.

The TOPS-10 version read its database from unit 1, which was attached to the data file by the IFILE subroutine. We will attach unit 1 to *TR0 using a standard INPUT statement in the program description and the George 3 ASSIGN command. We use a tape reader as the database contains TAB characters.

On TOPS-10 Adventure reads from the console with the ACCEPT verb and writes to the console with TYPE. We will use standard READ and WRITE from *CR0 and *LP0.

After initialising Adventure performs a PAUSE and the initialised core image is saved. This will work unchanged on George 3.

If the user wants to save the game Adventure will PAUSE and the core image can be saved. This will produce rather large savegames, maybe later on we should write code to save just the variable part.

The code needs to know the current date and time. On TOPS-10 this was quite difficult, it had to extract the information from text strings. On the ICL 1900 we can just use the library ITIME and IDATE subroutines (which give the time in seconds past midnight and the date in days since 31 DEC 1899).

Initial results

Currently the program runs fairly well. The core usage is rather large, around 44K words. Some of the "are you a wizard" code is unimplimented.

Compilation and initialisation:

:JOHN.ADVENT(3/FORT) IS ALREADY ONLINE
FILES ALREADY ONLINE: :MACROS.CONSOLIDATE(2/) :LIB.SUBGROUPSRF4(1/V9P)
:JOHN.ADVENT(1/PROG) IS ALREADY ONLINE
:JOHN.BITOPS(1/SEMI) IS ALREADY ONLINE
:LIB.SUBGROUPFSCE(1/V3) IS ALREADY ONLINE
09.58.04 FREE *DA14,0 TRANSFERS
09.58.04 0.34 DELETED,CLOCKED 0.02
09.58.04 0.34 CORE GIVEN 32576

FORTRAN COMPILATION BY #XFIV MK 3B    DATE  26/06/10  TIME  09/58/04

 0000
 0001                 ERRORLIST
0.34 :HALTED : BP
0.35 :HALTED : BP
09.58.08 FREE *DA3 ,108 TRANSFERS
09.58.08 FREE *CR0 ,3117 TRANSFERS
09.58.08 FREE *DA1 ,61 TRANSFERS
09.58.08 FREE *DA4 ,143 TRANSFERS
0.41 :DELETED : FI
09.58.08 FREE *LP0 ,7 TRANSFERS
09.58.08 FREE *DA0 ,3713 TRANSFERS
09.58.08 0.41 DELETED,CLOCKED 0.03
:JOHN.BITOPS(1/SEMI) IS ALREADY ONLINE
:LIB.SUBGROUPFSCE(1/V3) IS ALREADY ONLINE
:LIB.SUBGROUPSRF4(1/V9P) IS ALREADY ONLINE
:JOHN.ADVENT(1/PROG) IS ALREADY ONLINE
09.58.08 0.43 CORE GIVEN 11392

CONSOLIDATED BY XPCK 12K    DATE    26/06/10   TIME 09/58/08

09.58.09 0.43 CORE GIVEN 43904

09.58.09 FREE *LP0 ,8 TRANSFERS
09.58.09 FREE *DA1 ,66 TRANSFERS
09.58.09 FREE *DA2 ,7 TRANSFERS
09.58.09 FREE *DA3 ,98 TRANSFERS
09.58.09 FREE *DA4 ,87 TRANSFERS
09.58.09 FREE *DA13,9 TRANSFERS
09.58.09 0.44 CORE GIVEN 43840
0.44 :HALTED : LD
END OF MACRO
PARAMETER NUMBER 8 UNACCESSED
END OF MACRO
INITIALISING...
TABLE SPACE USED:
  9626 OF   9650 WORDS OF MESSAGES
   741 OF    750 TRAVEL OPTIONS
   297 OF    300 VOCABULARY WORDS
   140 OF    150 LOCATIONS
    53 OF    100 OBJECTS
    31 OF     35 ACTION VERBS
   201 OF    205 RTEXT MESSAGES
    10 OF     12 CLASS MESSAGES
     9 OF     20 HINTS
    32 OF     35 MAGIC MESSAGES

09.58.11 FREE *TR0 ,1809 TRANSFERS
0.47 :HALTED : INIT DONE
*DA14 NOT SAVED
*LP0  NOT SAVED
*CR0  NOT SAVED
Initial run:
10.08.13_ en

WELCOME TO ADVENTURE!!  WOULD YOU LIKE INSTRUCTIONS?

_ y

SOMEWHERE NEARBY IS COLOSSAL CAVE, WHERE OTHERS HAVE FOUND FORTUNES IN
TREASURE AND GOLD, THOUGH IT IS RUMORED THAT SOME WHO ENTER ARE NEVER
SEEN AGAIN.  MAGIC IS SAID TO WORK IN THE CAVE.  I WILL BE YOUR EYES
AND HANDS.  DIRECT ME WITH COMMANDS OF 1 OR 2 WORDS.  I SHOULD WARN
YOU THAT I LOOK AT ONLY THE FIRST FIVE LETTERS OF EACH WORD, SO YOU'LL
HAVE TO ENTER "NORTHEAST" AS "NE" TO DISTINGUISH IT FROM "NORTH".
(SHOULD YOU GET STUCK, TYPE "HELP" FOR SOME GENERAL HINTS.  FOR INFOR-
MATION ON HOW TO END YOUR ADVENTURE, ETC., TYPE "INFO".)
         - - -
THIS PROGRAM WAS ORIGINALLY DEVELOPED BY WILLIE CROWTHER.  MOST OF THE
FEATURES OF THE CURRENT PROGRAM WERE ADDED BY DON WOODS (DON @ SU-AI).
CONTACT DON IF YOU HAVE ANY QUESTIONS, COMMENTS, ETC.

YOU ARE STANDING AT THE END OF A ROAD BEFORE A SMALL BRICK BUILDING.
AROUND YOU IS A FOREST.  A SMALL STREAM FLOWS OUT OF THE BUILDING AND
DOWN A GULLY.

_ enter

YOU ARE INSIDE A BUILDING, A WELL HOUSE FOR A LARGE SPRING.

THERE ARE SOME KEYS ON THE GROUND HERE.

THERE IS A SHINY BRASS LAMP NEARBY.

THERE IS FOOD HERE.

THERE IS A BOTTLE OF WATER HERE.

_ get 

GET WHAT?

_ keys

OK

_ lamp 

WHAT DO YOU WANT TO DO WITH THE LAMP?

_ get

OK

_ bottle

WHAT DO YOU WANT TO DO WITH THE BOTTLE?

_ get

OK

_ get food

OK

_ look

SORRY, BUT I AM NOT ALLOWED TO GIVE MORE DETAIL.  I WILL REPEAT THE
LONG DESCRIPTION OF YOUR LOCATION.

YOU ARE INSIDE A BUILDING, A WELL HOUSE FOR A LARGE SPRING.

_ out

YOU'RE AT END OF ROAD AGAIN.

_ quit

DO YOU REALLY WANT TO QUIT NOW?

_ y

OK



YOU SCORED  27 OUT OF A POSSIBLE 350, USING   11 TURNS.

YOU ARE OBVIOUSLY A RANK AMATEUR.  BETTER LUCK NEXT TIME.

TO ACHIEVE THE NEXT HIGHER RATING, YOU NEED  9 MORE POINTS.

10.09.44 FREE *CR0 ,26 TRANSFERS

10.09.44 FREE *LP0 ,130 TRANSFERS
1.07 :DELETED : 00
10.09.44 1.07 DELETED,CLOCKED 0.02
10.09.44_ 

Now what

The following features are not yet implemented:
  1. Reduction of the memory used for the in-core text - currently it's stored as five chars per pair of 4 char words. This can be fairly simply changed.
  2. Reduction of the space allocated per integer variable - This will need changes to the types of variable used to store user input.
  3. Obfuscation of the available verbs in the core image.
  4. The full "magic mode" dialogue - more effort will be needed to understand what the PDP-10 version is actualy doing.

Progress report #1

The in-core text (LINES array) has been changed to store text in A8 instead of A5 format, reducing its size. The core image now takes 37632 words, down from 43840 words.
10.45.02 2.08 CORE GIVEN 37632
2.08 :HALTED : LD
END OF MACRO
PARAMETER NUMBER 8 UNACCESSED
END OF MACRO
INITIALISING...
TABLE SPACE USED:
  6523 OF   6550 WORDS OF MESSAGES
   741 OF    750 TRAVEL OPTIONS
   297 OF    300 VOCABULARY WORDS
   140 OF    150 LOCATIONS
    53 OF    100 OBJECTS
    31 OF     35 ACTION VERBS
   201 OF    205 RTEXT MESSAGES
    10 OF     12 CLASS MESSAGES
     9 OF     20 HINTS
    32 OF     35 MAGIC MESSAGES

10.45.04 FREE *TR0 ,1809 TRANSFERS
2.10 :HALTED : INIT DONE
The changes required were minor:
Index: adven.f4
===================================================================
RCS file: /usr/local/lib/cvs-repo/adventure/adv350-pdp10/adven.f4,v
retrieving revision 1.21
diff -u -r1.21 adven.f4
--- adven.f4	27 May 2010 20:52:13 -0000	1.21
+++ adven.f4	26 Jun 2010 08:52:13 -0000
@@ -16,7 +16,7 @@
 C  ADVENTURES
 
 C  CURRENT LIMITS:
-C      9650 WORDS OF MESSAGE TEXT (LINES, LINSIZ).
+C      6550 WORDS OF MESSAGE TEXT (LINES, LINSIZ).
 C	750 TRAVEL OPTIONS (TRAVEL, TRVSIZ).
 C	300 VOCABULARY WORDS (KTAB, ATAB, TABSIZ).
 C	150 LOCATIONS (LTEXT, STEXT, KEY, COND, ABB, ATLOC, LOCSIZ).
@@ -47,7 +47,7 @@
 	COMMON /WIZCOM/ WKDAY,WKEND,HOLID,HBEGIN,HEND,HNAME,
 	1	SHORT,MAGIC,MAGNM,LATNCY,SAVED,SAVET,SETUP
 
-	DIMENSION LINES(9650)
+	DIMENSION LINES(6550)
 	DIMENSION TRAVEL(750)
 	DIMENSION TRAVL2(750)
 	DIMENSION KTAB(300),ATAB(300)
@@ -62,7 +62,7 @@
 	DIMENSION MTEXT(35)
 	DIMENSION TK(20),DSEEN(6),DLOC(6),ODLOC(6),HNAME(4)
 
-	DATA LINSIZ/9650/,TRVSIZ/750/,LOCSIZ/150/,
+	DATA LINSIZ/6550/,TRVSIZ/750/,LOCSIZ/150/,
 	1  VRBSIZ/35/,RTXSIZ/205/,CLSMAX/12/,HNTSIZ/20/,MAGSIZ/35/
 
 C  STATEMENT FUNCTIONS
 C
@@ -253,13 +253,13 @@
 
 C  SECTIONS 1, 2, 5, 6, 10, 12.  READ MESSAGES AND SET UP POINTERS.
 
-1004	READ(1,1005)LOC,(LINES(J),J=LINUSE+1,LINUSE+14),KK
-1005	FORMAT(I0,15A5)
+1004	READ(1,1005)LOC,(LINES(J),J=LINUSE+1,LINUSE+9),KK
+1005	FORMAT(I0,9A8,A1)
 	CALL OVERFL (J)
 	IF(.NOT. EQUAL (KK, ' '))CALL BUG(0)
 	IF(LOC.EQ.-1)GOTO 1002
-	DO 1006 K=1,14
-	KK=LINUSE+15-K
+	DO 1006 K=1,9
+	KK=LINUSE+10-K
 	IF(.NOT. EQUAL (LINES(KK), ' '))GOTO 1007
 1006	CONTINUE
 	IF(LOC.EQ.0)GOTO 1004
@@ -298,7 +298,7 @@
 1020	LINUSE=KK+1
 	LINES(LINUSE)=-1
 	OLDLOC=LOC
-	IF(LINUSE+14.GT.LINSIZ)CALL BUG(2)
+	IF(LINUSE+9.GT.LINSIZ)CALL BUG(2)
 	GOTO 1004
 
 C  THE STUFF FOR SECTION 3 IS ENCODED HERE.  EACH "FROM-LOCATION" GETS A
Index: advn2.f4
===================================================================
RCS file: /usr/local/lib/cvs-repo/adventure/adv350-pdp10/advn2.f4,v
retrieving revision 1.13
diff -u -r1.13 advn2.f4
--- advn2.f4	26 Jun 2010 08:23:33 -0000	1.13
+++ advn2.f4	26 Jun 2010 08:52:14 -0000
@@ -50,7 +50,7 @@
 	LOGICAL BLKLIN
 	COMMON /TXTCOM/ RTEXT,LINES
 	COMMON /BLKCOM/ BLKLIN
-	DIMENSION RTEXT(205),LINES(9650)
+	DIMENSION RTEXT(205),LINES(6550)
 
 	IF(N.EQ.0)RETURN
 	IF(EQUAL(LINES(N+1),'>$<'))RETURN
@@ -59,7 +59,7 @@
 1	L=IABS(LINES(K))-1
 	K=K+1
 	WRITE (2,2) (LINES(I),I=K,L)
-2	FORMAT(' ',14A5)
+2	FORMAT(' ',9A8)
 	K=L+1
 	IF(LINES(K).GE.0)GOTO 1
 	RETURN
@@ -75,7 +75,7 @@
 	IMPLICIT INTEGER(A-Z)
 	COMMON /TXTCOM/ RTEXT,LINES
 	COMMON /PTXCOM/ PTEXT
-	DIMENSION RTEXT(205),LINES(9650),PTEXT(100)
+	DIMENSION RTEXT(205),LINES(6550),PTEXT(100)
 
 	M=PTEXT(MSG)
 	IF(SKIP.LT.0)GOTO 9
Note that the original "FORMAT(I0,15A5)" has been replaced by "FORMAT(I0,9A8,A1)" rather than the obvious translation "FORMAT(I0,10A8)" as the Fortran runtime crashes if we even hint that we are going to read a record longer that 80 characters. Herman Holerith rules from beyond the grave.
See also: Log of build of ADVENT on TOPS-10

All questions to john@AtlanTech.com