Hamurabi
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:
D1
: The total death count due to starvation.P1
: The average annual percentage of the population that dies of starvation.Z
: The year count.P
: The current population.S
: The number of bushels of grain in storage.H
: The number of bushels of grain harvested.E
: The number of bushels of grain eaten by rats.A
: The number of acres of land controlled by the city.I
: Number of immigrants.D
: Deaths due to starvation.
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.