Corran Webster

Hamurabi

2023-01-22

In the early days of the personal computer revolution, in the late 70s and early 80s, there was no common automated way for the new personal computers to communicate to one another. Floppy drives were expensive and hard drives even more so, modems were rare and slow, and while you could save data out to cassette tape on most of these early computers reading it back was unreliable. For many of these computers the way you got a program from one computer to another was, at least for hobbyists, to print out the code and manually type it in to another one. There was a small industry of publications–both magazines and books–that printed program listings for games, often in written in BASIC, that you could type in and then play.

An example of this sort of game, found in many different variants (due to the different implementations of BASIC on different machines) is a game called “Hamurabi” (note the spelling: filenames were often limited to 8 characters). This is an early simulation game and ancestor in some sense of games like SimCity and Civilization, although obviously far simpler.

You can play a MS-DOS (and presumably MS BASIC) version of Hamurabi at the Internet Archive and find the code for a version from the book BASIC Computer Games by David H. Ahl.

If you have never written in an older BASIC, then it's interesting to look at the structure of these programs. Most early basics lacked things like functions and namespaces: all state was global and flow control was via simple IF ... THEN ... statements, FOR loops, and jumps using GOTO and GOSUB (now considered harmful). It leads to a different style of programming which is foreign to most coders today.

I've transcribed the code from BASIC Computer Games; let’s take a look at what’s going on.

Setup

The first few lines are the introduction, printing out a title and basic orientation:

10 PRINT TAB(32);"HAMURABI"
20 PRINT TAB(15);"CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY"
30 PRINT:PRINT:PRINT
80 PRINT "TRY YOUR HAND AT GOVERNING ANCIENT SUMERIA"
90 PRINT "FOR A TEN-YEAR TERM OF OFFICE.":PRINT

This is followed by code to set up the initial state:

95 D1=0: P1=0
100 Z=0: P=95: S=2800: H=3000: E=H-S
110 Y=3: A=H/Y: I=5: Q=1
210 D=0

BASICs of the era allowed variable names of one or two characters (often a letter followed by an optional number) which can make deciphering things difficult. Some of these variables become clearer in the next section:

Some variables are used for multiple things. For example Q initially indicates whether there was a plague or not, but we will see that in other sections of code it has different meanings. Similarly Y here is the productivity of the land in bushels per acre, but in other places is the cost of land in bushels per acre.

All of these variables hold numerical values; string variables were also allowed, usually indicated by a $ suffix for the name (eg. A$), but this game does not use them.

The Mainloop

The next section is the start of the game's mainloop. Most BASICs of the time did have some sort of basic looping constructs, usually either FOR or WHILE loops, it was common to directly jump around in the code using GOTO commands. In this case the end of the loop is line 555, and looping is potentially terminated on lines 270, 552, and a number of places if it detects bad input.

The initial section of the mainloop prints out the current state of the city. It has computational code mixed in with the output, here incrementing the year and adding immigrants to the population in the middle of output code, rather than before, for example.

215 PRINT:PRINT:PRINT "HAMURABI:  I BEG TO REPORT TO YOU,": Z=Z+1
217 PRINT "IN YEAR";Z;",";D;"PEOPLE STARVED,";I;"CAME TO THE CITY,"
218 P=P+I

Similarly here the code branches based on whether or not a plague happened. The halving of the population due to plague is performed as part of the same branch as the output, because that saves a line or two of code. And on some memory-constrained systems, a line or two of code might matter! The source for this program is a little under 4k bytes, and systems like the VIC-20 (the predecessor of the Commodore 64) only had about 3.5k bytes available for BASIC programs.

227 IF Q>0 THEN 230
228 P=INT(P/2)
229 PRINT "A HORRIBLE PLAGUE STRUCK!  HALF THE PEOPLE DIED."
230 PRINT "POPULATION IS NOW";P

The code then prints out the remaining information about the city's status in a fairly straightforward way.

232 PRINT "THE CITY NOW OWNS ";A;"ACRES."
235 PRINT "YOU HARVESTED";Y;"BUSHELS PER ACRE."
250 PRINT "THE RATS ATE";E;"BUSHELS."
260 PRINT "YOU NOW HAVE ";S;"BUSHELS IN STORE.": PRINT

This then brings us to the first main branch point: if this is the start of the 11th year, the game is over, so jump to the end.

270 IF Z=11 THEN 860

Getting Input from the Player

After this we get into the section where the player gets to plan for the year ahead. The first option they have is to buy or sell land. The first line determines the current cost of land in bushels of grain per acre, which one of 17 through to 26, inclusive (and uniformly distributed).

310 C=INT(10*RND(1)): Y=C+17
312 PRINT "LAND IS TRADING AT";Y;"BUSHELS PER ACRE."

User input needs to be validated, of course. The game needs to check that the player entered a valid amount: the player needs to have enough grain in storage to pay for the land they want to buy, but also the amount needs to be a non-negative number. In the case of invalid inputs, the program jumps either to an end-game (at line 850, if the player enters a negative) or an error message (at line 710). Additionally, if the player didn't buy any land, then they may instead sell land, and so there is a branch to cover that case, with additional error-checking. At the end of it all, the amount of land and grain is adjusted appropriately.

320 PRINT "HOW MANY ACRES DO YOU WISH TO BUY";
321 INPUT Q: IF Q<0 THEN 850
322 IF Y*Q<=S THEN 330
323 GOSUB 710
324 GOTO 320
330 IF Q=0 THEN 340
331 A=A+Q: S=S-Y*Q: C=0
334 GOTO 400
340 PRINT "HOW MANY ACRES DO YOU WISH TO SELL";
341 INPUT Q: IF Q<0 THEN 850
342 IF Q<A THEN 350
343 GOSUB 720
344 GOTO 340
350 A=A-Q: S=S+Y*Q: C=0
400 PRINT

The next part of the planning stage is to determine how much to feed people. Food has to come out of storage. Again, this value needs to be error-checked, both for negative values, and for trying to use more grain than is available.

410 PRINT "HOW MANY BUSHELS DO YOU WISH TO FEED YOUR PEOPLE";
411 INPUT Q
412 IF Q<0 THEN 850
418 REM *** TRYING TO USE MORE GRAIN THAN IS IN SILOS?
420 IF Q<=S THEN 430
421 GOSUB 710
422 GOTO 410
430 S=S-Q: C=1: PRINT

The final decision that the player needs to make each turn is how much land to plant with seed. This choice is constrained in three ways: the acres of land available, the bushels of seed available, and how many people are available to tend the crops. The player can sow 2 acres per bushel and tend 10 acres per person.

440 PRINT "HOW MANY ACRES DO YOU WISH TO PLANT WITH SEED";
441 INPUT D: IF D=0 THEN 511
442 IF D<0 THEN 850
444 REM *** TRYING TO PLANT MORE ACRES THAN YOU OWN?
445 IF D<=A THEN 450
446 GOSUB 720
447 GOTO 440
449 REM *** ENOUGH GRAIN FOR SEED?
450 IF INT(D/2)<=S THEN 455
452 GOSUB 710
453 GOTO 440
454 REM *** ENOUGH PEOPLE TO TEND THE CROPS?
455 IF D<10*P THEN 510
460 PRINT "BUT YOU HAVE ONLY";P;"PEOPLE TO TEND THE FIELDS!  NOW THEN,"
470 GOTO 440
510 S=S-INT(D/2)

Determining the Results

The game now evaluates the outcome of the player's choices. A subroutine at line 800 generates a random integer between 1 and 5, which is the number of bushels of grain that are grown per acre, and this can be used to determine the harvest.

511 GOSUB 800
512 REM *** A BOUNTIFUL HARVEST!
515 Y=C: H=D*Y: E=0

Another random integer between 1 and 5 is used to determine whether rats get into the crops. This happens if the generated number is odd, so 60% of the time. With this information, the number of bushels in storage can be updated.

521 GOSUB 800
522 IF INT(C/2)<>C/2 THEN 530
523 REM *** RATS ARE RUNNING WILD!!
525 E=INT(S/C)
530 S=S-E+H

Once the supply of grain is known, the population can be adjusted. Another random number from 1 to 5 is generated and used to determine the number of immigrants (notice how the comments don't agree with the rest of the code!) The basic factor is 20 times the land owned by the city plus the grain in storage, all divided by 100 times the population; this is multiplied by the random factor and 1 is added to it before truncating. This number is stashed away and is only added to the population at the start of the next turn, on line 218.

531 GOSUB 800
532 REM *** LET'S HAVE SOME BABIES
533 I=INT(C*(20*A+S)/P/100+1)

Each person eats 20 bushels of grain, so the game determines the population which can be fed based on the decisions made earlier in the planning stages. There is also a 15% chance of plague, although the formula is somewhat obfuscated. If everyone is fed, the game goes back to the start of the main loop. The population changes are applied at the start of the loop.

539 REM *** HOW MANY PEOPLE HAD FULL TUMMIES?
540 C=INT(Q/20)
541 REM *** HORROS, A 15% CHANCE OF PLAGUE
542 Q=INT(10*(2*RND(1)-.3))
550 IF P<C THEN 210

If there were people who starved to death, then the game determines if it is a game-over: a 45% death rate is enough to trigger this event. Otherwise the running total of deaths and running average death rate are updated, and the code returns to the start of the main loop.

551 REM *** STARVE ENOUGH FOR IMPEACHMENT?
552 D=P-C: IF D>.45*P THEN 560
553 P1=((Z-1)*P1+D*100/P)/Z
555 P=C: D1=D1+D: GOTO 215

Utility Routines

At this point the main loop is done, and we have various subroutines and utility code. The first of these is the game-over losing text. This has two entry points, one for a single turn game-over from too many deaths from starvation on line 560, and the other for the bad-ending from either too many total deaths or too little land on line 565. Due to memory constraints, avoiding repetition by jumping into different points in the code was common for basic code of the era.

560 PRINT: PRINT "YOU STARVED";D;"PEOPLE IN ONE YEAR!!!"
565 PRINT "DUE TO THIS EXTREME MISMANAGEMENT YOU HAVE NOT ONLY"
566 PRINT "BEEN IMPEACHED AND THROWN OUT OF OFFICE BUT YOU HAVE"
567 PRINT "ALSO BEEN DECLARED NATIONAL FINK!!!!": GOTO 990

The next few lines of code are a subroutine that handles error conditions for trying to use too many bushels of grain.

710 PRINT "HAMURABI:  THINK AGAIN.  YOU HAVE ONLY"
711 PRINT S;"BUSHELS OF GRAIN.  NOW THEN,"
712 RETURN

And this is the code for a similar subroutine that handles trying to use too much land.

720 PRINT "HAMURABI:  THINK AGAIN.  YOU OWN ONLY";A;"ACRES.  NOW THEN,"
730 RETURN

The next couple of lines are a subroutine that generates a random integer from 1 to 5.

800 C=INT(RND(1)*5)+1
801 RETURN

And this section of code is called when the user enters a negative input, which turns out to result in a game-over.

850 PRINT: PRINT "HAMURABI:  I CANNOT DO WHAT YOU WISH."
855 PRINT "GET YOURSELF ANOTHER STEWARD!!!!!"
857 GOTO 990

The Endgame

Finally we come to the end-game results and reporting. The key statistics for outcomes are the average percentage deaths form starvation, and the final ratio of land per person. Bad endings occur when you average over 33% deaths per year or end up will less than 7 acres per person. The good ending requires less than 3% starvation on average and at least 10 acres per person. In the middle are two partial wins, a bad one one which occurs with more than 10% average starvation rate or less than 9 acres per person, and the other better ending taking the remaining cases.

860 PRINT "IN YOUR 10-YEAR TERM OF OFFICE,";P1;"PERCENT OF THE"
862 PRINT "POPULATION STARVED PER YEAR ON THE AVERAGE, I.E. A TOTAL OF"
865 PRINT D1;"PEOPLE DIED!!": L=A/P
870 PRINT "YOU STARTED WITH 10 ACRES PER PERSON AND ENDED WITH"
875 PRINT L;"ACRES PER PERSON.": PRINT
880 IF P1>33 THEN 565
885 IF L<7 THEN 565
890 IF P1>10 THEN 940
892 IF L<9 THEN 940
895 IF P1>3 THEN 960
896 IF L<10 THEN 960
900 PRINT "A FANTASTIC PERFORMANCE!!!  CHARLEMANGE, DISRAELI, AND"
905 PRINT "JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!":GOTO 990
940 PRINT "YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV."
945 PRINT "THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND,"
950 PRINT "FRANKLY, HATE YOUR GUTS!!":GOTO 990
960 PRINT "YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT"
965 PRINT "REALLY WASN'T TOO BAD AT ALL. ";INT(P*.8*RND(1));"PEOPLE"
970 PRINT "WOULD DEARLY LIKE TO SEE YOU ASSASSINATED BUT WE ALL HAVE OUR"
975 PRINT "TRIVIAL PROBLEMS."

The final section of code is the end, where all code eventually flows or jumps to, which prints the ASCII bell character 10 times and prints a farewell message.

990 PRINT: FOR N=1 TO 10: PRINT CHR$(7);: NEXT N
995 PRINT "SO LONG FOR NOW.": PRINT
999 END

Reading through this code, you can see the way that programmers needed to be disciplined about the way that they wrote in languages like BASIC. And even with that discipline, unpacking the code to see what each section does, and how it connects to all the other code is typically unclear and needs close study. On the other hand, it was easy to write simple scripts that just did a thing and stopped.

Many of the use cases of BASIC have been taken over by languages like Python (the "newbie friendly/learnering langauge" role) and JavaScript (the "ubiquitously available langauge" role), but there isn't really the equivalent of these small, simple games and programs in the modern world.