Post Reply 
Retro computing: Hammurabi
09-28-2024, 12:02 PM (This post was last modified: 09-28-2024 12:23 PM by CheshireChris.)
Post: #1
Retro computing: Hammurabi
I was inspired by Mike's recent posting of a Prime version of the 1970s mainframe "Trek" to have a go myself at porting something to the Prime. This is my first attempt at Prime programming in PPL and is a version of the classic resource management game "Hammurabi", in which the player must run ancient Sumeria for a ten-year term of office. Each year you must decide how much land to buy or sell, how much grain to feed your people, and how many acres of land to plant with seed. If you don't feed your people enough, some will starve and there is a risk of plague. If you starve too many people you will be impeached and thrown out of office before the end of the game! If you survive your ten-your term, you'll be presented with an assessment of your performance.

The program was converted from a BASIC listing in David Ahl's public domain book, "BASIC Computer Games, Microcomputer Edition", which can be downloaded from the Internet Archive, and I found the process of converting it from BASIC to PPL relatively straightforward. It took about eight hours' work from start to finish, and I understand a lot now about PPL programming than I did when I started the task! I've added a lot of comments to the source code so if you want to look at the source you should find it straightforward. I would encourage you NOT to look at the source code before playing, because it gives away the winning strategy. The program is 354 lines long and 21KB in size.

To install the program, simply copy the "Hammurabi.hpprgm" file to the "Programs" list on your Prime and run it either from the Program Catalog (Shift+1) or just by typing "Hammurabi" from the Home screen. All output is to the Prime's terminal. A line appears at the bottom of the terminal when input is required: enter a number and press the ENTER key. To exit at any time, press the ESC key.

As I say, this is my first attempt at Prime programming. All constructive feedback is very welcome!

Code:

// Hammurabi
// Converted from a BASIC program in David Ahl's
// "BASIC Computer Games, Microcomputer Edition"
// by Chris Marriott.
//
// Revision history:
// 28 Sep 24: Initial release.

// Constants controlling model behaviour
CONST gameYears:=10;    // number of years to run the model
CONST bushelsPerPerson:=20;  // bushels to feed one person for a year
CONST acresPerPerson:=10;  // acres a person can sow
CONST impeachPercent:=45;  // percentage of deaths triggering impeachment
CONST plaguePercent:=15;    // % probability of plague
CONST STATUS_OK:=0;    // OK to continue
CONST STATUS_QUIT:=1;    // end the game

// Declare variables
population;        // current population
incomers;          // people coming to the city
bushels;          // bushels in the treasury
acres;              // acres owned
acresPlanted;        // acres planted
bushelsFed;          // bushels fed to the people
eaten;              // bushels eaten by rats
harvest;          // bushels harvested
harvestYield;        // bushels per acre harvested
deaths;          // deaths this year
totalDeaths;          // total number of deaths
deathPercent;        // average % deaths
year;              // game year
plague;          // probability of plague
status;          // game status

// Main program

EXPORT Hammurabi()
BEGIN

// Initialise data and display the introductory text
InitData();
Intro();

// Continue until we either reach the time limit, or
// we are impeached.
WHILE year<gameYears AND status==STATUS_OK DO 

  // Display the start of year status
  AnnualReport();

  // Buy or sell land
  BuySellLand();

  // Feed the people
  FeedPeople();

  // Plant crops
  PlantCrops();

  // Harvest time
  HarvestCrops();

  // End of year processing
  YearEnd();

END;

// On a normal exit, display the final year's stats
// and a performance report. If we exited due to being
// impeached, display the impeachment report.
IF status==STATUS_OK THEN
  AnnualReport();
  FinalStatus();
ELSE
  PRINT("\nPress any key to continue...");
  PRINT();
  PRINT("You starved "+deaths+" people in one year!");
  Impeach();
END;

PRINT("\nPress any key to exit.");
RETURN 0;

END;

// Display the introductory screen

Intro()
BEGIN
PRINT();
PRINT("HAMMURABI\n\n");
PRINT("Try your hand at governing ancient Sumeria for");
PRINT("a "+gameYears+"-year term of office. Each year, you must");
PRINT("decide how much land to buy or sell, how much");
PRINT("grain to feed your people, and how much to");
PRINT("plant in the fields. The consequences of failure");
PRINT("are severe!");
PRINT("\nConverted from David Ahl's \"BASIC Computer");
PRINT("Games\" by Chris Marriott.");
PRINT("\nPress any key to begin...");
WAIT();
END;

// Initialise game data

InitData()
BEGIN
status:=STATUS_OK;
year:=0;
population:=95;
deaths:=0;
totalDeaths:=0;
deathPercent:=0;
bushels:=2800;
harvest:=3000;
eaten:=harvest-bushels;
harvestYield:=3;
acres:=harvest/harvestYield;
incomers:=5;
plague:=100; // no plague the first year!
END;

// Display the report for the current year

AnnualReport()
BEGIN

// Increment the year
year:=year+1;

PRINT();
PRINT("Hammurabi, I beg to report to you,");
PRINT("In year "+year+", "+deaths+" people starved, "+incomers+" came to the city.");

population:=population+incomers;

// If the plague has struck, halve the population
IF plague<plaguePercent THEN
  population:=FLOOR(population/2);
  PRINT("A horrible plague struck! Half the people died.");
END;

PRINT("Population is now "+population);
PRINT("The city now owns "+acres+" acres.");
PRINT("You harvested "+harvestYield+" bushels per acre.");
PRINT("Rats ate "+eaten+" bushels.");
PRINT("You now have "+bushels+" bushels in store.");
PRINT("\n");
END;

// Buy or sell land

BuySellLand()
BEGIN
LOCAL landPrice, trade, totalCost;  

// Generate the price of land this year
landPrice:= RANDINT(17,26);

// Do we want to buy land?
REPEAT
  PRINT("Land is trading at "+landPrice+" bushels per acre.");
  PRINT("How many acres do you wish to buy?");
  trade:=ReadNum();
  totalCost:=landPrice*trade;
  IF totalCost>bushels THEN
    TooExpensive();
  END;
UNTIL totalCost<=bushels;

IF trade<>0 THEN
  acres:=acres+trade;
  bushels:=bushels-totalCost;

// If we haven't bought land, do we want to sell?
ELSE
  REPEAT
    PRINT("How many acres do you wish to sell?");
    trade:=ReadNum();
    IF trade>acres THEN
      TooMuchLand();
    END;
  UNTIL trade<=acres;
  acres:=acres-trade;
  bushels:=bushels+landPrice*trade;
END;
END;

// Decide how much grain to feed the people
FeedPeople()
BEGIN
REPEAT
  PRINT("How many bushels do you wish to feed your people?");  
  bushelsFed:=ReadNum();
  IF bushelsFed>bushels THEN
    TooExpensive();
  END
UNTIL bushelsFed<=bushels;
bushels:=bushels-bushelsFed;
END;

// Decide how many acres to plant

PlantCrops()
BEGIN
LOCAL OK;

REPEAT
  OK:=1;
  PRINT("How many acres do you wish to plant with seed?");
  acresPlanted:=ReadNum();

  // More acres that you own?
  IF acresPlanted>acres THEN
    TooMuchLand();
    OK:=0;
  END;

  // Enough grain for seed?
  IF OK==1 AND FLOOR(acresPlanted/2)>bushels THEN
    TooExpensive();
    OK:=0;
  END;

  // Enough people to tend the crops?
  IF OK==1 AND acresPlanted>acresPerPerson*population THEN
    PRINT("But you only have "+population+" people to tend the fields! Now then,");
    OK:=0;
  END;

UNTIL OK==1;

// Remove the planted grain from the treasury
bushels:=bushels-FLOOR(acresPlanted/2);

END;

// Harvest this year's crops

HarvestCrops()
BEGIN
LOCAL rnd;

// Random harvest yield
harvestYield:=RANDINT(1,5);
harvest:=acresPlanted*harvestYield;

// Rats?
rnd:=RANDINT(1,5);
IF FLOOR(rnd/2)==rnd/2 THEN
  eaten:=FLOOR(bushels/rnd);
ELSE
  eaten:=0;  
END;

bushels:=bushels-eaten+harvest;

END;

// End of year processing

YearEnd()
BEGIN
LOCAL peopleFed, rnd;

// How many people have come to the city?
rnd:=RANDINT(1,5);
incomers:=FLOOR(rnd*(20*acres+bushels)/population/100+1);

// How many people had full tummies?
peopleFed:=FLOOR(bushelsFed/bushelsPerPerson);

// Calculate the probability of plague.
plague:=RANDOM(0,100);

// If the population is greater than the number of people
// fed, people will starve
IF population>peopleFed THEN
  deaths:=population-peopleFed;
  IF deaths>impeachPercent/100*population THEN
    status:=STATUS_QUIT;
  END;
  deathPercent:=((year-1)*deathPercent+deaths*100/population)/year;
  population:=peopleFed;
  totalDeaths:=totalDeaths+deaths;
ELSE
  // We've fed everyone
  deaths:=0;
END;

END;

// Final status report

FinalStatus()
BEGIN
LOCAL acrespp, summary;
PRINT("\nPress any key to continue...");
WAIT();
PRINT();
PRINT("In your term of office, "+FLOOR(deathPercent)+"% of the population starved on average.");
PRINT("A total of "+totalDeaths+" people died!");
acrespp:=acres/population;
PRINT("You started with 10 acres per person and ended with "+FLOOR(acrespp)+" acres per person.");

summary:=0;
IF deathPercent>33 OR acrespp<7 THEN
  Impeach();
  summary:=1;
END;
IF summary==0 AND (deathPercent>10 OR acrespp<9) THEN
  PRINT("Your heavy-handed performance smacks of Nero and Ivan IV. The people (remaining) find you an unpleasant ruler and, frankly, hate your guts!");
  summary:=1; 
END;
IF summary==0 AND acrespp<10 THEN
  PRINT("Your performance could have been somewhat better, but really wasn't too bad at all. "+FLOOR(population*0.8*RANDOM())+" people would dearly like to see you assassinated, but we all have our trivial problems.");
  summary:=1;
END;
IF summary==0 THEN
  PRINT("A fantastic performance! Charlemagne, Disraeli and Jefferson combined could not have done better!");
END;
 
END;

// You've been impeached

Impeach()
BEGIN
PRINT("Due to this extreme mismanagement you have not only been impeached and thrown out of office, but you have also been declared a national disgrace!");
END;

// Not enough grain to do this

TooExpensive()
BEGIN
PRINT("Hammurabi, think again! You have only "+bushels+" bushels. Now then,");
END;

// Not enough land to do this

TooMuchLand()
BEGIN
PRINT("Hammurabi, think again! You own only "+acres+" acres. Now then,");
END;

// Read a line from the terminal, echo the input, and return a numeric value

ReadNum()
BEGIN
LOCAL input;
input:=READLINE();
PRINT(input);
RETURN EXPR(input);
END;


Attached File(s) Thumbnail(s)
   

.hpprgm  Hammurabi.hpprgm (Size: 20.8 KB / Downloads: 7)

Chris
Find all posts by this user
Quote this message in a reply
09-28-2024, 12:13 PM
Post: #2
RE: Retro computing: Hammurabi
Sorry - posting the source code has made the message very wide. I didn't know that would happen. Hope you can read it OK.

Chris
Find all posts by this user
Quote this message in a reply
09-28-2024, 06:33 PM
Post: #3
RE: Retro computing: Hammurabi
Hi Chris,

Very nice, but I see one small problem. The program crashes when an invalid value is entered. It happened to me by accident, and a Syntax Error occurred, causing the program to terminate without the possibility of continuing. When you get data using READLINE, you should validate its correctness. In your case, you always expect a positive integer, so it's easy to check whether the given value is a valid number.

Instead of writing:
Code:
ReadNum()
BEGIN
  LOCAL input;
  input := READLINE();
  PRINT(input);
  RETURN EXPR(input);
END;

you should check if the entered value is numeric and re-prompt with READLINE if the value is incorrect:

Code:
Conv_positive_num(t)
BEGIN
 t:=SUPPRESS(t," ");
 local i,s=SIZE(t);
 FOR i FROM 1 TO s DO
  IF t[i]<48 OR t[i]>57 THEN
    RETURN -1; // invalid value
  END;
 END;
 RETURN EXPR(t);
END;

ReadNum()
BEGIN
 LOCAL inp,num;
 REPEAT
  inp:=READLINE();
  num:=Conv_positive_num(inp);
  IF num == -1 THEN
   PRINT("Invalid value (" + inp + "). Try again.");
  END;
 UNTIL num <> -1;
 PRINT(num);
 RETURN num;
END;

The code is longer, but now the program doesn't crash when an incorrect value is entered by accident. Additionally, EXPR treats spaces as multiplication, for example, 2 [space] 240 (if you wanted to write "2 240", it would be treated as 2*240, which results in 480). However, using the conversion as shown above, spaces are removed, so "2 240" will be 2240 as expected.

Piotr Kowalewski
Find all posts by this user
Quote this message in a reply
09-28-2024, 06:35 PM
Post: #4
RE: Retro computing: Hammurabi
Thanks, Piotr!

Chris
Find all posts by this user
Quote this message in a reply
Post Reply 




User(s) browsing this thread: 1 Guest(s)