hammurabi-p9/hammurabi.c

681 lines
21 KiB
C

/*
* hammurabi - ancient Sumerian city-state resource management game
*
* Copyright 2020 David Meyer <papa@sdf.org> +JMJ
*
*/
#include <u.h>
#include <libc.h>
#include <stdio.h>
/* Constants */
#define MAXYEARS 10
#define INITPOP 100
#define INITGRAIN 2800
#define INITLAND 1000
/* Types */
typedef enum {FALSE, TRUE} Boolean;
typedef enum {BUY, SELL, FEED, PLANT, RESET, EXEC, CALC, QUIT} Plancmd;
/* Message texts */
char MSGDANCE[] =
"|vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv|\n\
|(}_ _ _ _ _ _ _ _ _ _ _ _ _ _{)|\n\
|\\/\\/ \\(}/ (} \\(}/ (} \\()/ {) \\{)/ {) \\{)/ \\/\\/|\n\
||\\ |_ _/|\\_ |_ _/|\\_ \\/ _/|\\_ _| _/|\\_ _| /||\n\
||_\\_ | \\ / \\ | \\ / \\ /\\ / \\ / | / \\ / | _/_||\n\
|vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv|\n\
";
char MSGHELP[] =
"\n\
You are the ruler of an ancient Sumerian city-state. I, your Grand\n\
Vizier, will assist you during your ten-year reign to govern your _ _{)\n\
realm. \\/\\/\n\
/|\n\
At the beginning of each year, you will plan the employment of your /|\n\
city's resources to buy or sell land, allocate land and grain to plant _/_|\n\
new crops, and allocate grain to feed your people. Some guidelines:\n\
\n\
* Land price varies between 17 and 26 bushels of grain per acre.\n\
* Planting crops requires one bushel of grain to seed two acres.\n\
* One person can farm ten acres of land.\n\
* One person needs to eat 20 bushels of grain per year to stay alive.\n\
\n\
On the planning menu, press one of the bracketed characters to [F]eed\n\
your people, [P]lant crops, or [B]uy or [S]ell land. When you are\n\
satisfied with the plan, press [E] to execute it. You may also\n\
[R]eset the current plan and start over, or [Q]uit the game.\n\
\n\
When prompted for a numeric value (to buy, sell, or plant land, or\n\
allocate grain for food), you may enter an integer value, or an\n\
arithmetic expression composed of integers and the operators \"+\", \"-\",\n\
\"*\", or \"/\" for addition, subtraction, multiplication, and division.\n\
The expression will be evaluated with integer arithmetic in the order\n\
the operators appear from left to right, and the value of the\n\
expression will be used for your input.\n\
\n\
After the year's plan has been executed, I will report to you the\n\
year's events, harvest, births and immigration, and deaths from\n\
starvation and plague, and the results.\n\
\n\
At the end of your reign, history will judge you based on the average\n\
portion of the population you starved, and your city's growth in land\n\
per person. If in any year you starve more than 45 percent of the\n\
population, the survivors will rise up and depose you immediately.\n\
\n\
Hammurabi is based on the game HAMURABI by David Ahl.\n\
\n\
";
char MSGMENU[]=
"\n\
[F]eed your people with grain | [E]xecute your current plan\n\
[P]lant crops on your land | [R]eset your current plan and start over\n\
[B]uy more land | [Q]uit the game\n\
[S]ell land for grain\n\
";
char MSGPLTOT[] =
" ------------------------------- | -------------------------------\n";
char MSGSPLASH[] =
" ___ ___ ___. .__ \n\
/ | \\_____ _____ _____ __ ______________ \\_ |__ |__|\n\
/ ~ \\__ \\ / \\ / \\| | \\_ __ \\__ \\ | __ \\| |\n\
\\ Y // __ \\| Y Y \\ Y Y \\ | /| | \\// __ \\| \\_\\ \\ |\n\
\\___|_ /(____ /__|_| /__|_| /____/ |__| (____ /___ /__|\n\
\\/ \\/ \\/ \\/ \\/ \\/ \n\
The ancient game of ,.__., resource management\n\
/,',.`.\\\n\
Version 1.0 ____|:|db|:|____ by David Meyer\n\
for Plan 9 ___//\"\"\"|:|88|:|\"\"\"\\\\___ <papa@sdf.org>\n\
_____//\":=_=_=_=|--|=_=_=_=:\"\\\\_____\n\
__/.-._//__|L_L_L_L|--|L_L_L_L|__\\\\_.-.\\__\n\
//\"||m|:.-. .-. .-.,.__.,.-. .-. .-.:|m||\"\\\\\n\
// ||8||: : L_L_L_/.',.`.\\_L_L_L : :||8|| \\\\\n\
/'=======\"L.='.::'`:|:|db|:|:'`::.`=.L\"=======`\\\n\
| .---. .--. |_:==' |:|88|:| `==:_| .--. .---. |\n\
| | : |_.='/=======|:--:|=======\\`=._| : | |\n\
| | _,=' ||==.=.==|:--:|==.=.==|| `=._ | |\n\
|_:='_______|| || || |:--:| || || ||_______`=:_|\n\
|| || || |:--:| || || ||\n\
''-------|:--:|-------''\n\
':==:'\n\
";
/* Honorifics */
#define MAXHONOR 9
char *HONORIFIC[] = {"Great", "Mighty", "Wise", "Benevolent", "Merciful", "Just", "Sublime",
"Glorious", "Serene", "Majestic"};
/* Function prototypes */
void completedterm(void);
int evtbrthimm(void);
int evtlandpr(void);
int evtplague(void);
int evtratseat(void);
int evtstarve(void);
int evtyield(void);
int grainafterplan(void);
int grainafterlandsp(void);
int grainharvest(void);
int graintoplant(void);
int inputbuy(void);
int inputfood(void);
int inputiexpr(void);
int inputplant(void);
int inputsell(void);
int landafterplan(void);
void microcalc(void);
int peoplefed(void);
Plancmd planmenu(void);
Boolean planyear(void);
int popnet(void);
void printdeposed(void);
void printgreeting(void);
void printinitrept(void);
void printyearrept(void);
int salegrain(void);
Boolean scanyes(char *prompt);
/* Constant variables */
int CONSCTL;
/* Game state */
int Year = 0;
int Pop = INITPOP;
int Grain = INITGRAIN;
int Land = INITLAND;
/* Total statistics */
int TotStarved = 0;
int TotStarvePct = 0;
/* Annual plan */
int PlanFood, PlanSale, PlanPlant;
/* Annual events */
int EvtYield, EvtLandPr, EvtBrthImm, EvtPlague, EvtRatsEat, EvtStarve;
/* Beginning of year stats */
int PrevPop, PrevGrain, PrevLand;
/* Derived values */
int GrainAfterPlan, LandAfterPlan, GrainHarvest;
void
main(void)
{
int popfed, starvepct;
Boolean gameover = FALSE;
CONSCTL = open("/dev/consctl", OWRITE);
if(CONSCTL == -1) exits("can't open CONSCTL: %r\n");
srand(time(0));
print(MSGSPLASH);
if(scanyes("Would you like instructions?") == TRUE) print(MSGHELP);
print(MSGDANCE);
EvtLandPr = evtlandpr();
printgreeting();
printinitrept();
do
{
++ Year;
if(planyear() == FALSE) gameover = TRUE;
else
{
PrevPop = Pop;
PrevGrain = Grain; Grain = GrainAfterPlan;
PrevLand = Land; Land = LandAfterPlan;
EvtYield = evtyield();
EvtRatsEat = evtratseat();
GrainHarvest = grainharvest();
Grain = Grain + GrainHarvest - EvtRatsEat;
EvtBrthImm = evtbrthimm();
popfed = peoplefed();
EvtStarve = evtstarve();
TotStarved = TotStarved + EvtStarve;
starvepct = 100 * EvtStarve / Pop;
if(starvepct > 45)
{
printgreeting();
print("You have starved %d of your subjects in one year!\n", EvtStarve);
printdeposed();
gameover = TRUE;
}
else
{
TotStarvePct = TotStarvePct + starvepct;
Pop = Pop + EvtBrthImm - EvtStarve;
EvtPlague = evtplague();
Pop = Pop - EvtPlague;
printgreeting();
printyearrept();
if(Year == MAXYEARS) completedterm();
else
{
EvtLandPr = evtlandpr();
}
}
}
} while(Year < MAXYEARS && gameover == FALSE);
print("\n");
print(MSGDANCE);
exits(0);
}
void
completedterm(void)
{
int avgstarvepct = TotStarvePct / MAXYEARS;
int landperperson = Land / Pop;
int likeassassination = Pop * 0.8 * frand();
printgreeting();
print("In your %d-year reign, %d people died of starvation, an average of %d people\nper year.\n", MAXYEARS, TotStarved, avgstarvepct);
print("You started with %d acres per person and ended with %d acres per person.\n", INITLAND / INITPOP, landperperson);
if(avgstarvepct > 33 || landperperson < 7) printdeposed();
else if(avgstarvepct > 10 || landperperson < 9) print("Your heavy-handed performance smacks of Nero and Ivan IV. Your (surviving)\npeople find you an unpleasant ruler, and, frankly, hate your guts!\n");
else if(avgstarvepct > 3 || landperperson < 10)
{
print("Your performance could have been better, but wasn't too bad. ");
if(likeassassination > 0) print("%d %s would like to see you assassinated, but we all have our trivial\nproblems.\n", likeassassination, (likeassassination == 1 ? "person" : "people"));
}
else print("A fantastic performance! Charlemagne, Disraeli, and Jefferson combined could not\nhave done better!\n");
}
int
evtbrthimm(void)
{
return ((nrand(5) + 1) * (20 * Land + Grain) / Pop / 100 + 1);
}
int
evtlandpr(void)
{
return (nrand(10) + 17);
}
int
evtplague(void)
{
return ((nrand(100) < 15) ? (Pop / 2) : 0);
}
int
evtratseat(void)
{
int eat;
switch(nrand(5)+1)
{
case 1:
eat = Grain;
break;
case 3:
eat = Grain / 3;
break;
case 5:
eat = Grain / 5;
break;
default:
eat = 0;
}
return eat;
}
int
evtstarve(void)
{
int popfed = peoplefed();
return ((popfed < Pop) ? (Pop - popfed) : 0);
}
int
evtyield(void)
{
return (nrand(5) + 1);
}
int
grainafterplan(void)
{
return (grainafterlandsp() - graintoplant() - PlanFood);
}
int
grainafterlandsp(void)
{
return (Grain + (PlanSale * EvtLandPr));
}
int
grainharvest(void)
{
return (PlanPlant * EvtYield);
}
int
graintoplant(void)
{
return (PlanPlant / 2);
}
int
inputbuy(void)
{
int buy, grainbal, price;
Boolean valid = FALSE;
grainbal = Grain - PlanFood - (PlanPlant / 2);
do
{
print("\nThe price of land is %d bushels per acre.\n", EvtLandPr);
print("How many acres do you wish to buy? ");
buy = inputiexpr();
price = buy * EvtLandPr;
if(buy < 0)
print("Sire, it is impossible to buy a negative amount of land.\nIf you wish to sell land, enter \"0\", then select (S)ell from the menu.\n");
else if(price > grainbal)
print("Sire, %d acres of land costs %d bushels of grain, and you have only %d bushels.\n",
buy, price, grainbal);
else valid = TRUE;
} while(valid == FALSE);
return buy;
}
int
inputfood(void)
{
int food, grainbal;
Boolean valid = FALSE;
grainbal = Grain + (PlanSale * EvtLandPr) - (PlanPlant / 2);
do
{
print("\nHow much grain do you wish to give your people for food? ");
food = inputiexpr();
if(food < 0)
print("Sire, it is impossible to give the people a negative amount of grain.\nEnter \"0\" to return to the menu.\n");
else if(food > grainbal)
print("Sire, you only have %d bushels of grain in the granaries.\n", grainbal);
else valid = TRUE;
} while(valid == FALSE);
return food;
}
int
inputiexpr(void)
{
char buf[80];
int buflen = 80;
int result = 0;
int ch, rix, wix, p, o, n[40];
char op[39];
if(fgets(buf, buflen, stdin) != NULL)
{
if(buf[strlen(buf) - 1] != '\n') while((ch = getchar()) != '\n' && ch != EOF) ;
rix = wix = 0;
while(buf[rix] != '\0')
{
if(strchr("0123456789+-*/", buf[rix]) != 0)
{
if(rix > wix) buf[wix] = buf[rix];
++wix;
}
++ rix;
}
buf[wix] = '\0';
p = sscanf(buf, "%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d",
&n[0],&op[0],&n[1],&op[1],&n[2],&op[2],&n[3],&op[3],&n[4],&op[4],&n[5],&op[5],&n[6],&op[6],&n[7],&op[7],&n[8],&op[8],&n[9],&op[9],
&n[10],&op[10],&n[11],&op[11],&n[12],&op[12],&n[13],&op[13],&n[14],&op[14],&n[15],&op[15],&n[16],&op[16],&n[17],&op[17],&n[18],&op[18],&n[19],&op[19],
&n[20],&op[20],&n[21],&op[21],&n[22],&op[22],&n[23],&op[23],&n[24],&op[24],&n[25],&op[25],&n[26],&op[26],&n[27],&op[27],&n[28],&op[28],&n[29],&op[29],
&n[30],&op[30],&n[31],&op[31],&n[32],&op[32],&n[33],&op[33],&n[34],&op[34],&n[35],&op[35],&n[36],&op[36],&n[37],&op[37],&n[38],&op[38],&n[39]);
if(p > 0)
{
result = n[0];
for(o = 0; o < (int) (p / 2); ++ o)
{
switch (op[o])
{
case '+':
result = result + n[o+1];
break;
case '-':
result = result - n[o+1];
break;
case '*':
result = result * n[o+1];
break;
case '/':
result = result / n[o+1];
break;
}
}
}
}
return result;
}
int
inputplant(void)
{
int grainbal, landbal, plant;
Boolean valid = FALSE;
grainbal = Grain - PlanFood + (PlanSale * EvtLandPr);
landbal = Land - PlanSale;
do
{
print("\nHow many acres do you wish to plant? ");
plant = inputiexpr();
if(plant < 0)
print("Sire, it is impossible to plant a negative amount of land.\nEnter \"0\" to return to the menu.\n");
else if(plant > landbal) print("Sire, you have only %d acres of lands.\n", landbal);
else if((plant / 2) > grainbal)
print("Sire, you have only %d bushels of grain in the granaries.\n", grainbal);
else if(plant > (10 * Pop)) print("Sire, you have only enough people to plant %d acres.\n", 10 * Pop);
else valid = TRUE;
} while(valid == FALSE);
return plant;
}
int
inputsell(void)
{
int landbal, sell, price;
Boolean valid = FALSE;
landbal = Land - PlanPlant;
do
{
print("\nThe price of land is %d bushels per acre.\n", EvtLandPr);
print("How many acres do you wish to sell? ");
sell = inputiexpr();
if(sell < 0)
print("Sire, it is impossible to sell a negative amount of land.\nIf you wish to buy land, enter \"0\", then select (B)uy from the menu.\n");
else if(sell > landbal) print("Sire, you have only %d acres of land.\n", landbal);
else valid = TRUE;
} while(valid == FALSE);
return sell;
}
int
landafterplan(void)
{
return (Land - PlanSale);
}
void
microcalc(void)
{
}
int
peoplefed(void)
{
return (PlanFood / 20);
}
Plancmd
planmenu(void)
{
int cmdch = 0;
Plancmd cmd;
print("\n Plan for Year %d\n", Year);
print(" Grain in storage %7d | Land (price %2d) %7d\n", Grain, EvtLandPr, Land);
if(PlanSale > 0)
print(" From land sale %7d | Sell %7d\n", salegrain(), PlanSale);
if(PlanSale < 0)
print(" For land purchase%7d | Purchase %7d\n", salegrain() * -1, PlanSale * -1);
if(PlanSale != 0)
print("%s Net %7d | Net %7d\n", MSGPLTOT, grainafterlandsp(), LandAfterPlan);
if(PlanFood > 0) print(" Provide for food %7d |\n", PlanFood);
if(PlanPlant > 0)
print(" Provide for seed %7d | Plant %7d\n", graintoplant(), PlanPlant);
if(PlanFood > 0 || PlanPlant > 0)
print("%s Balance %7d | Fallow acres %7d\n", MSGPLTOT, GrainAfterPlan,
Land - PlanSale - PlanPlant);
print("\n Population: %d (need food: %d; can farm: %d)\n", Pop, Pop * 20, Pop * 10);
print(MSGMENU);
if(write(CONSCTL, "rawon", 5) != 5) exits("\ncan't turn off echo\n");
while(cmdch == 0)
{
print(" What is your choice? [fpbserq] ");
cmdch = getchar();
if(cmdch == EOF) exits("\nerror reading terminal input\n");
print("%c\n", cmdch);
switch(cmdch)
{
case 'b':
cmd = BUY;
break;
case 'e':
cmd = EXEC;
break;
case 'f':
cmd = FEED;
break;
case 'p':
cmd = PLANT;
break;
case 'q':
cmd = QUIT;
break;
case 'r':
cmd = RESET;
break;
case 's':
cmd = SELL;
break;
default:
cmdch = 0;
}
}
if(write(CONSCTL, "rawoff", 6) != 6) exits("\ncan't turn on echo\n");
return cmd;
}
Boolean
planyear(void)
{
Boolean play = TRUE;
Plancmd cmd;
PlanFood = PlanSale = PlanPlant = 0;
do
{
cmd = planmenu();
switch(cmd)
{
case BUY:
PlanSale = -1 * inputbuy();
break;
case SELL:
PlanSale = inputsell();
break;
case FEED:
PlanFood = inputfood();
break;
case PLANT:
PlanPlant = inputplant();
break;
case RESET:
PlanFood = PlanSale = PlanPlant = 0;
break;
case EXEC:
print("\nSo it shall be written, so it shall be done.\n");
break;
case CALC:
// microcalc();
break;
case QUIT:
play = FALSE;
break;
}
if(cmd != QUIT)
{
GrainAfterPlan = grainafterplan();
LandAfterPlan = landafterplan();
}
} while(cmd != EXEC && cmd != QUIT);
return play;
}
int
popnet(void)
{
return (Pop + EvtBrthImm - EvtStarve);
}
void
printdeposed(void)
{
print("Due to this extreme mismanagement you have not only been deposed and executed, but you have also been declared National Fink!\n");
}
void
printgreeting(void)
{
print("\nO %s One, I beg to report,\n", HONORIFIC[nrand(MAXHONOR + 1)]);
}
void
printEvtLandPr(void)
{
print("The price of land is %d bushels of grain per acre.\n", EvtLandPr);
}
void
printinitrept(void)
{
print(" ______________________________________________________________________\n");
print(" ( Your Reign Begins @\n");
print(" (}_ _ | Land %6d acres |\n", Land);
print(" \\/\\/ | Grain %6d bushels |\n", Grain);
print(" |\\ | Population %6d |\n", Pop);
print(" |\\ | |\n");
print(" |_\\_ | Land price %6d bushels per acre |\n", EvtLandPr);
print(" |_____________________________________________________________________|\n");
print(" (______________________________________________________________________@\n");
}
void
printyearrept(void)
{
if(EvtPlague > 0) print("A horrible plague struck! Half the people died!\n");
print(" ______________________________________________________________________\n");
print(" ( YEAR %2d @\n", Year);
print(" | Harvest yield %1d bushels per acre | Population last year %6d |\n", EvtYield, PrevPop);
print(" (}_ _ | Grain reserve %6d | Births and immigrants %6d |\n", GrainAfterPlan, EvtBrthImm);
print(" \\/\\/ | Harvest %6d | ", GrainHarvest);
if(EvtStarve > 0) print("Starvation deaths %6d |\n", EvtStarve);
else print(" |\n");
print(" |\\ | ");
if(EvtRatsEat > 0) print("Eaten by rats %6d | ", EvtRatsEat);
else print(" | ");
if(EvtPlague > 0) print("%-20s%6d |\n", "Plague deaths", EvtPlague);
else print(" |\n");
print(" |\\ | -------------------------------- | -------------------------------- |\n");
print(" |_\\_ | Balance %6d | Current population %6d |\n", Grain, Pop);
print(" | |\n");
print(" | Land %6d |\n", Land);
print(" |_____________________________________________________________________|\n");
print(" (______________________________________________________________________@\n");
}
int
salegrain(void)
{
return (PlanSale * EvtLandPr);
}
Boolean
scanyes(char *prompt)
{
int in = 0;
if(write(CONSCTL, "rawon", 5) != 5) exits("\ncan't turn off echo\n");
while(in != 'y' && in != 'n')
{
print("%s [yn] ", prompt);
in = getchar();
if(in == EOF) exits("\nerror reading terminal input\n");
print("%c\n", in);
}
if(write(CONSCTL, "rawoff", 6) != 6) exits("\ncan't turn on echo\n");
print("\n");
return (in == 'y' ? TRUE : FALSE);
}