Hammurabi

Descrition del contenete del págine

Conversion de Hammurabi a pluri lingues de programation.

Etiquettes:

Ti-ci programa esset convertet a 10 lingues de programation.

Originale

Origin of hammurabi.bas:

Original program:
    Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
    Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
    Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
    - http://web.archive.org/web/20150215091831/http://www.dunnington.u-net.com/public/basicgames/
    - http://web.archive.org/web/20150120012304/http://www.dunnington.u-net.com/public/basicgames/HMRABI

Modernized versions:
    - https://archive.org/details/hamurabi.qb64

More details:
    - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
    - https://www.mobygames.com/game/22232/hamurabi/

5 CLS: COLOR 12
10 PRINT TAB(35); "Hammurabi"
20 PRINT TAB(31); "Creative Computing"
25 PRINT TAB(29); "Morristown, New Jersey"
30 PRINT: PRINT: PRINT: RANDOMIZE TIMER
80 COLOR 14
85 PRINT "Try your hand at governing ancient Sumeria for a ten-year term of office."
90 PRINT: COLOR 15
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
215 PRINT: PRINT: COLOR 13: PRINT "Hammurabi: I beg to report to you,": Z = Z + 1: COLOR 15: PRINT
217 PRINT "In year"; STR$(Z); ","; D; "people starved and"; I; "came to the city."
218 P = P + I
227 IF Q > 0 THEN 230
228 P = INT(P / 2)
229 COLOR 12: PRINT "A horrible plague struck!  Half the people died.": COLOR 15
230 PRINT "The population is now"; STR$(P); ".": PRINT
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
270 IF Z = 11 THEN 860
310 C = INT(10 * RND(1)): Y = C + 17
312 PRINT "Land is trading at"; Y; "bushels per acre.": PRINT
320 COLOR 10: PRINT "How many acres do you wish to buy";
321 INPUT Q: COLOR 15: 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 COLOR 10: PRINT "How many acres do you wish to sell";
341 INPUT Q: COLOR 15: 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 REM
410 COLOR 10: PRINT "How many bushels do you wish to feed your people";
411 INPUT Q: COLOR 15
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
440 COLOR 10: PRINT "How many acres do you wish to plant with seed";
441 INPUT D: COLOR 15: 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...": PRINT
470 GOTO 440
510 S = S - INT(D / 2)
511 GOSUB 800
512 REM *** A BOUNTIFUL HARVEST!
515 Y = C: H = D * Y: E = 0
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
531 GOSUB 800
532 REM *** LET'S HAVE SOME BABIES
533 I = INT(C * (20 * A + S) / P / 100 + 1)
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
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
560 COLOR 12: PRINT: PRINT "You starved"; D; "people in one year!!!": PRINT
565 PRINT "Due to this extreme mismanagement you have not only been impeached and thrown"
566 PRINT "out of office but you have also been declared national fink!!!": GOTO 990
710 PRINT "Hammurabi, think again.  You have only"; S; "bushels of grain.  Now then...": PRINT
712 RETURN
720 PRINT "Hammurabi, think aain.  You own only"; A; "acres.  Now then...": PRINT
730 RETURN
800 C = INT(RND(1) * 5) + 1
801 RETURN
850 PRINT: PRINT "Hammurabi, I cannot do what you wish."
855 PRINT "Get yourself another steward!"
857 GOTO 990
860 PRINT "In your 10-year term of office,"; P1; "percent of the"
862 PRINT "population starved per on the average, i.e., a total of"; D1
865 PRINT "people died!": PRINT: L = A / P
870 PRINT "You started with 10 acres per person and ended with"; L
876 PRINT "acres per person."
875 PRINT: COLOR 11
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!  Charlemagne, Disraeli, and Jefferson combined could"
905 PRINT "not have done better!": GOTO 990
940 PRINT "Your heavy-handed performance smacks of Nero and Ivan IV.  The people"
945 PRINT "(remaining) find you an unpleasant ruler and, frankly, hate your guts!"
950 GOTO 990
960 PRINT "Your performance could have been somewat better, but really wasn't too bad at"
965 ZZ = INT(P * .8 * RND(1))
970 PRINT "all. "; ZZ; "people would dearly like to see you assassinated, but we all have our"
975 PRINT "trivial problems."
990 PRINT: FOR N = 1 TO 10: PRINT CHR$(7);: NEXT N
995 COLOR 15: PRINT "So long for now.": PRINT
999 END


In C#

// Hammurabi

// Description:
//  A simple text-based simulation game set in the ancient kingdom of Sumeria.

// Original program:
//  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
//
// BASIC port:
//  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
//  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
//  - http://web.archive.org/web/20150215091831/http://www.dunnington.u-net.com/public/basicgames/
//  - http://web.archive.org/web/20150120012304/http://www.dunnington.u-net.com/public/basicgames/HMRABI
//
// Modernized versions:
//  - https://archive.org/details/hamurabi.qb64
//
// More details:
//  - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
//  - https://www.mobygames.com/game/22232/hamurabi/

// This improved remake in C#:
//  Copyright (c) 2025, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-01-01.
//
// Last modified: 20251205T1535+0100.
//
// Acknowledgment:
//  The following Python port was used as a reference of the original
//  variables: <https://github.com/jquast/hamurabi.py>.

using System;

class hammurabi
{
    const int ACRES_A_BUSHEL_CAN_SEED = 2; // yearly
    const int ACRES_A_PERSON_CAN_SEED = 10; // yearly
    const int ACRES_PER_PERSON = 10; // to calculate the initial acres of the city
    const int BUSHELS_TO_FEED_A_PERSON = 20; // yearly
    const int IRRITATION_LEVELS = 5; // after the switch in `showIrritation`
    const int IRRITATION_STEP = MAX_IRRITATION / IRRITATION_LEVELS;
    const int MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1;
    const int MIN_HARVESTED_BUSHELS_PER_ACRE = 17;
    const int MAX_IRRITATION = 16;
    const double PLAGUE_CHANCE = 0.15; // 15% yearly
    const int RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10;
    const int YEARS = 10; // goverment period

    const ConsoleColor DEFAULT_INK = ConsoleColor.White;
    const ConsoleColor INPUT_INK = ConsoleColor.Green;
    const ConsoleColor INSTRUCTIONS_INK = ConsoleColor.DarkYellow;
    const ConsoleColor RESULT_INK = ConsoleColor.Cyan;
    const ConsoleColor SPEECH_INK = ConsoleColor.Magenta;
    const ConsoleColor TITLE_INK = ConsoleColor.White;
    const ConsoleColor WARNING_INK = ConsoleColor.Red;

    enum Result
    {
        VeryGood,
        NotTooBad,
        Bad,
        VeryBad,
    }

    static int acres;
    static int bushelsEatenByRats;
    static int bushelsHarvested;
    static int bushelsHarvestedPerAcre;
    static int bushelsInStore;
    static int bushelsToFeedWith;
    static int dead;
    static int infants;
    static int irritation; // counter from 0 to 99 (inclusive)
    static int population;
    static int starvedPeoplePercentage;
    static int totalDead;

    static Random rand = new Random();

    // Return a random number from 1 to 5 (inclusive).
    //
    static int Random1To5()
    {
        return rand.Next(5) + 1;
    }

    // Return the proper wording for `n` persons, using the given or default words
    // for singular and plural forms.
    //
    static string persons
    (
        int n,
        string singular = "person",
        string plural = "people"
    )
    {
        switch (n)
        {
            case 0:
                return "nobody";
            case 1:
                return $"one {singular}";
            default:
                return $"{n} {plural}";
        }
    }

    static void PrintInstructions()
    {
        Console.ForegroundColor = INSTRUCTIONS_INK;

        Console.WriteLine("Hammurabi is a simulation game in which you, as the ruler of the ancient");
        Console.WriteLine("kingdom of Sumeria, Hammurabi, manage the resources.");

        Console.WriteLine("\nYou may buy and sell land with your neighboring city-states for bushels of");
        Console.WriteLine($"grain ― the price will vary between {MIN_HARVESTED_BUSHELS_PER_ACRE} and {MAX_HARVESTED_BUSHELS_PER_ACRE} bushels per acre.  You also must");
        Console.WriteLine("use grain to feed your people and as seed to plant the next year's crop");

        Console.WriteLine("\nYou will quickly find that a certain number of people can only tend a certain");
        Console.WriteLine("amount of land and that people starve if they are not fed enough.  You also");
        Console.WriteLine("have the unexpected to contend with such as a plague, rats destroying stored");
        Console.WriteLine("grain, and variable harvests.");

        Console.WriteLine("\nYou will also find that managing just the few resources in this game is not a");
        Console.WriteLine("trivial job.  The crisis of population density rears its head very rapidly.");

        Console.WriteLine($"\nTry your hand at governing ancient Sumeria for a {YEARS}-year term of office.");

        Console.ForegroundColor = DEFAULT_INK;
    }

    static void PressEnter(string prompt = "> ")
    {
        InputString(prompt);
    }

    // Print the given prompt in the correspondent color, wait until the user
    // enters a string and return.
    //
    static string InputString(string prompt = "")
    {
        Console.ForegroundColor = INPUT_INK;
        Console.Write(prompt);
        string result = Console.ReadLine();
        Console.ForegroundColor = DEFAULT_INK;
        return result;
    }

    // Print the given prompt in the correspondent color, wait until the user
    // enters a valid integer and return.
    //
    static int InputInt(string prompt = "")
    {
        Console.ForegroundColor = INPUT_INK;

        int number;

        while (true)
        {
            Console.Write(prompt);
            try
            {
                number = (int) Int64.Parse(Console.ReadLine());
                break;
            }
            catch
            {
                Console.WriteLine("Integer expected.");
            }
        }
        Console.ForegroundColor = DEFAULT_INK;
        return number;
    }

    static void PrintCredits()
    {
        Console.ForegroundColor = TITLE_INK;
        Console.WriteLine("Hammurabi");
        Console.WriteLine("\nOriginal program:");
        Console.WriteLine("  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.");
        Console.WriteLine("\nBASIC port:");
        Console.WriteLine("  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.");
        Console.WriteLine("  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.");
        Console.WriteLine("\nThis improved remake in C#:");
        Console.WriteLine("  Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("  SPDX-License-Identifier: Fair");
        Console.ForegroundColor = DEFAULT_INK;
    }

    static string OrdinalSuffix(int n)
    {
        switch (n)
        {
            case 1:
                return "st";
            case 2:
                return "nd";
            case 3:
                return "rd";
            default:
                return "th";
        }
    }

    // Return the description of the given year as the previous one.
    //
    static string Previous(int year)
    {
        if (year == 0)
        {
            return "the previous year";
        }
        else
        {
            return $"your {year}{OrdinalSuffix(year)} year";
        }
    }

    static void PrintAnnualReport(int year)
    {
        Console.Clear();
        Console.ForegroundColor = SPEECH_INK;
        Console.WriteLine("Hammurabi, I beg to report to you.");
        Console.ForegroundColor = DEFAULT_INK;

        Console.WriteLine
        (
            "\nIn {0}, {1} starved and {2} {3} born.",
            Previous(year),
            persons(dead),
            persons(infants, "infant", "infants"),
            infants > 1 ? "were" : "was"
        );

        population += infants;

        double plagueChance = rand.NextDouble();
        if (year > 0 ? plagueChance <= PLAGUE_CHANCE : false)
        {
            population = (int) population / 2;
            Console.ForegroundColor = WARNING_INK;
            Console.WriteLine("A horrible plague struck!  Half the people died.");
            Console.ForegroundColor = DEFAULT_INK;
        }

        Console.WriteLine($"The population is {population}.");
        Console.WriteLine($"The city owns {acres} acres.");
        Console.WriteLine
        (
            "You harvested {0} bushels ({1} per acre).",
            bushelsHarvested,
            bushelsHarvestedPerAcre
        );
        if (bushelsEatenByRats > 0)
        {
            Console.WriteLine($"The rats ate {bushelsEatenByRats} bushels.");
        }
        Console.WriteLine($"You have {bushelsInStore} bushels in store.");
        bushelsHarvestedPerAcre =
            (int) (RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * rand.NextDouble()) +
            MIN_HARVESTED_BUSHELS_PER_ACRE;
        Console.WriteLine($"Land is trading at {bushelsHarvestedPerAcre} bushels per acre.\n");
    }

    static void SayBye()
    {
        Console.ForegroundColor = DEFAULT_INK;
        Console.WriteLine("\nSo long for now.\n");
    }

    static void QuitGame()
    {
        SayBye();
        Environment.Exit(0);
    }

    static void Relinquish()
    {
        Console.ForegroundColor = SPEECH_INK;
        Console.WriteLine("\nHammurabi, I am deeply irritated and cannot serve you anymore.");
        Console.WriteLine("Please, get yourself another steward!");
        Console.ForegroundColor = DEFAULT_INK;
        QuitGame();
    }

    static void IncreaseIrritation()
    {
        irritation += 1 + rand.Next(IRRITATION_STEP);
        if (irritation >= MAX_IRRITATION)
        {
            Relinquish();
        } // this never returns
    }

    static void PrintIrritated(string adverb)
    {
        Console.WriteLine($"The steward seems {adverb} irritated.");
    }

    static void ShowIrritation()
    {
        if (irritation < IRRITATION_STEP || irritation < IRRITATION_STEP * 2)
        {
            PrintIrritated("slightly");
        }
        else if (irritation < IRRITATION_STEP * 3)
        {
            PrintIrritated("quite");
        }
        else if (irritation < IRRITATION_STEP * 4)
        {
            PrintIrritated("very");
        }
        else
        {
            PrintIrritated("profoundly");
        }
    }

    // Print a message begging to repeat an ununderstandable input.
    //
    static void BegRepeat()
    {
        IncreaseIrritation(); // this may never return
        Console.ForegroundColor = SPEECH_INK;
        Console.WriteLine("I beg your pardon?  I did not understand your order.");
        Console.ForegroundColor = DEFAULT_INK;
        ShowIrritation();
    }

    // Print a message begging to repeat a wrong input, because there's only `n`
    // items of `name`.
    //
    static void BegThinkAgain(int n, string name)
    {
        IncreaseIrritation(); // this may never return
        Console.ForegroundColor = SPEECH_INK;
        Console.WriteLine($"I beg your pardon?  You have only {n} {name}.  Now then…");
        Console.ForegroundColor = DEFAULT_INK;
        ShowIrritation();
    }

    // Buy or sell land.
    //
    static void Trade()
    {
        int acresToBuy;
        int acresToSell;

        while (true)
        {
            acresToBuy = InputInt("How many acres do you wish to buy? (0 to sell): ");
            if (acresToBuy < 0)
            {
                BegRepeat(); // this may never return
                continue;
            }
            if (bushelsHarvestedPerAcre * acresToBuy <= bushelsInStore)
            {
                break;
            }
            BegThinkAgain(bushelsInStore, "bushels of grain");
        }

        if (acresToBuy != 0)
        {
            Console.WriteLine($"You buy {acresToBuy} acres.");
            acres += acresToBuy;
            bushelsInStore -= bushelsHarvestedPerAcre * acresToBuy;
            Console.WriteLine($"You now have {acres} acres and {bushelsInStore} bushels.");
        }
        else
        {
            while (true)
            {
                acresToSell = InputInt("How many acres do you wish to sell?: ");
                if (acresToSell < 0)
                {
                    BegRepeat(); // this may never return
                    continue;
                }
                if (acresToSell < acres)
                {
                    break;
                }
                BegThinkAgain(acres, "acres");
            }

            if (acresToSell > 0)
            {
                Console.WriteLine($"You sell {acresToSell} acres.");
                acres -= acresToSell;
                bushelsInStore += bushelsHarvestedPerAcre * acresToSell;
                Console.WriteLine($"You now have {acres} acres and {bushelsInStore} bushels.");
            }
        }
    }

    // Feed the people.
    //
    static void Feed()
    {
        while (true)
        {
            bushelsToFeedWith = InputInt("How many bushels do you wish to feed your people with?: ");
            if (bushelsToFeedWith < 0)
            {
                BegRepeat(); // this may never return
                continue;
            }
            // Trying to use more grain than is in silos?
            if (bushelsToFeedWith <= bushelsInStore)
            {
                break;
            }
            BegThinkAgain(bushelsInStore, "bushels of grain");
        }

        Console.WriteLine($"You feed your people with {bushelsToFeedWith} bushels.");
        bushelsInStore -= bushelsToFeedWith;
        Console.WriteLine($"You now have {bushelsInStore} bushels");
    }

    // Seed the land.
    //
    static void Seed()
    {
        int acresToSeed;

        while (true)
        {
            acresToSeed = InputInt("How many acres do you wish to seed?: ");
            if (acresToSeed < 0)
            {
                BegRepeat(); // this may never return
                continue;
            }
            if (acresToSeed == 0)
            {
                break;
            }

            // Trying to seed more acres than you own?
            if (acresToSeed > acres)
            {
                BegThinkAgain(acres, "acres");
                continue;
            }

            // Enough grain for seed?
            if ((int) acresToSeed / ACRES_A_BUSHEL_CAN_SEED > bushelsInStore)
            {
                BegThinkAgain
                (
                    bushelsInStore,
                    $"bushels of grain,\nand one bushel can seed {ACRES_A_BUSHEL_CAN_SEED} acres"
                );
                continue;
            }

            // Enough people to tend the crops?
            if (acresToSeed <= ACRES_A_PERSON_CAN_SEED * population)
            {
                break;
            }

            BegThinkAgain
            (
                population,
                $"people to tend the fields,\nand one person can seed {ACRES_A_PERSON_CAN_SEED} acres"
            );
        }

        int bushelsUsedForSeeding = (int) acresToSeed / ACRES_A_BUSHEL_CAN_SEED;
        Console.WriteLine($"You seed {acresToSeed} acres using {bushelsUsedForSeeding} bushels.");
        bushelsInStore -= bushelsUsedForSeeding;
        Console.WriteLine($"You now have {bushelsInStore} bushels.");

        // A bountiful harvest!
        bushelsHarvestedPerAcre = Random1To5();
        bushelsHarvested = acresToSeed * bushelsHarvestedPerAcre;
        bushelsInStore += bushelsHarvested;
    }

    static bool IsEven(int n)
    {
        return n % 2 == 0;
    }

    static void CheckRats()
    {
        int ratChance = Random1To5();
        bushelsEatenByRats = IsEven(ratChance) ? (int) bushelsInStore / ratChance : 0;
        bushelsInStore -= bushelsEatenByRats;
    }

    // Set the variables to their values in the first year.
    //
    static void Init()
    {
        dead = 0;
        totalDead = 0;
        starvedPeoplePercentage = 0;
        population = 95;
        infants = 5;
        acres = ACRES_PER_PERSON * (population + infants);
        bushelsHarvestedPerAcre = 3;
        bushelsHarvested = acres * bushelsHarvestedPerAcre;
        bushelsEatenByRats = 200;
        bushelsInStore = bushelsHarvested - bushelsEatenByRats;
        irritation = 0;
    }

    static void PrintResult(Result result)
    {
        Console.ForegroundColor = RESULT_INK;

        switch (result)
        {
            case Result.VeryGood :
                Console.WriteLine("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could");
                Console.WriteLine("not have done better!");
                break;
            case Result.NotTooBad :
                Console.WriteLine("Your performance could have been somewat better, but really wasn't too bad at");
                Console.WriteLine
                (
                    "all. {0} people would dearly like to see you assassinated, but we all have our",
                    (int) ((double) population * .8 * rand.NextDouble())
                );
                Console.WriteLine("trivial problems.");
                break;
            case Result.Bad :
                Console.WriteLine("Your heavy-handed performance smacks of Nero and Ivan IV.  The people");
                Console.WriteLine("(remaining) find you an unpleasant ruler and, frankly, hate your guts!");
                break;
            case Result.VeryBad :
                Console.WriteLine("Due to this extreme mismanagement you have not only been impeached and thrown");
                Console.WriteLine("out of office but you have also been declared national fink!!!");
                break;
        }

        Console.ForegroundColor = DEFAULT_INK;
    }

    static void PrintFinalReport()
    {
        Console.Clear();

        if (starvedPeoplePercentage > 0)
        {
            Console.WriteLine
            (
                "In your {0}-year term of office, {1} percent of the",
                YEARS,
                starvedPeoplePercentage
            );
            Console.WriteLine
            (
                "population starved per year on the average, i.e., a total of {0} people died!\n",
                totalDead
            );
        }

        int acresPerPerson = acres / population;
        Console.WriteLine
        (
            "You started with {0} acres per person and ended with {1}.\n",
            ACRES_PER_PERSON,
            acresPerPerson
        );

        if (starvedPeoplePercentage > 33 || acresPerPerson < 07)
        {
            PrintResult(Result.VeryBad);
        }
        else if (starvedPeoplePercentage > 10 || acresPerPerson < 09)
        {
            PrintResult(Result.Bad);
        }
        else if (starvedPeoplePercentage > 03 || acresPerPerson < 10)
        {
            PrintResult(Result.NotTooBad);
        }
        else
        {
            PrintResult(Result.VeryGood);
        }
    }

    static void CheckStarvation(int year)
    {
            // How many people has been fed?
            int fedPeople = (int) bushelsToFeedWith / BUSHELS_TO_FEED_A_PERSON;

            if (population > fedPeople)
            {
                dead = population - fedPeople;
                starvedPeoplePercentage = ((year - 1) * starvedPeoplePercentage + dead * 100 / population) / year;
                population -= dead;
                totalDead += dead;

                // Starve enough for impeachment?
                if (dead > (int) (.45 * (double) population))
                {
                    Console.ForegroundColor = WARNING_INK;
                    Console.WriteLine($"\nYou starved {dead} people in one year!!!\n");
                    Console.ForegroundColor = DEFAULT_INK;
                    PrintResult(Result.VeryBad);
                    QuitGame();
                }
            }
    }

    static void Govern()
    {
        Init();

        PrintAnnualReport(0);

        for (int year = 1; year <= YEARS; year++)
        {
            Trade();
            Feed();
            Seed();
            CheckRats();

            // Let's have some babies
            infants = (int) (Random1To5() * (20 * acres + bushelsInStore) / population / 100 + 1);

            CheckStarvation(year);

            PressEnter("\nPress the Enter key to read the annual report. ");
            PrintAnnualReport(year);
        }
    }

    static void Main()
    {
        Console.Clear();
        PrintCredits();

        PressEnter("\nPress the Enter key to read the instructions. ");
        Console.Clear();
        PrintInstructions();

        PressEnter("\nPress the Enter key to start. ");
        Govern();

        PressEnter("Press the Enter key to read the final report. ");
        PrintFinalReport();
        SayBye();
    }
}

In Crystal

# Hammurabi
#
# Description:
#   A simple text-based simulation game set in the ancient kingdom of Sumeria.
#
# Original program:
#   Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
#
# BASIC port:
#   Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
#   Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
#
# More details:
#   - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
#   - https://www.mobygames.com/game/22232/hamurabi/
#
# This improved remake in Crystal:
#   Copyright (c) 2025, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2025-04-21.
#
# Last modified: 20250421T2026+0200.
#
# Acknowledgment:
#   The following Python port was used as a reference of the original
#   variables: <https://github.com/jquast/hamurabi.py>.
#

# Terminal {{{1
# ==============================================================================

# Screen colors
BLACK   = 0
RED     = 1
GREEN   = 2
YELLOW  = 3
BLUE    = 4
MAGENTA = 5
CYAN    = 6
WHITE   = 7
DEFAULT = 9

# Screen attributes
NORMAL = 0

# Screen color offsets
FOREGROUND = +30
BRIGHT     = +60

# Clears the screen and moves the cursor to the home position.
def clear_screen
  print("\e[2J\e[H")
end

def set_color(color : Int32)
  print("\e[#{color}m")
end

# Globals {{{1
# ==============================================================================

ACRES_A_BUSHEL_CAN_SEED             =  2 # yearly
ACRES_A_PERSON_CAN_SEED             = 10 # yearly
ACRES_PER_PERSON                    = 10 # to calculate the initial acres of the city
BUSHELS_TO_FEED_A_PERSON            = 20 # yearly
IRRITATION_LEVELS                   =  5 # after the switch in `show_irritation`
IRRITATION_STEP                     = (MAX_IRRITATION / IRRITATION_LEVELS).to_i
MAX_HARVESTED_BUSHELS_PER_ACRE      = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1
MIN_HARVESTED_BUSHELS_PER_ACRE      =   17
MAX_IRRITATION                      =   16
PLAGUE_CHANCE                       = 0.15 # 15% yearly
RANGE_OF_HARVESTED_BUSHELS_PER_ACRE =   10
YEARS                               =   10 # goverment period

DEFAULT_INK      = WHITE + FOREGROUND
INPUT_INK        = BRIGHT + GREEN + FOREGROUND
INSTRUCTIONS_INK = YELLOW + FOREGROUND
RESULT_INK       = BRIGHT + CYAN + FOREGROUND
SPEECH_INK       = BRIGHT + MAGENTA + FOREGROUND
TITLE_INK        = BRIGHT + WHITE + FOREGROUND
WARNING_INK      = BRIGHT + RED + FOREGROUND

enum Result
  Very_Good
  Not_Too_Bad
  Bad
  Very_Bad
end

class Global
  class_property acres : Int32 = 0
  class_property bushels_eaten_by_rats : Int32 = 0
  class_property bushels_harvested : Int32 = 0
  class_property bushels_harvested_per_acre : Int32 = 0
  class_property bushels_in_store : Int32 = 0
  class_property bushels_to_feed_with : Int32 = 0
  class_property dead : Int32 = 0
  class_property infants : Int32 = 0
  class_property irritation : Int32 = 0 # counter (0 .. 99)
  class_property population : Int32 = 0
  class_property starved_people_percentage : Int32 = 0
  class_property total_dead : Int32 = 0
end

# User input {{{1
# ==============================================================================

# Prints the given prompt and waits until the user enters a string.
#
def input_string(prompt = "") : String
  s = nil
  set_color(INPUT_INK)
  while s.is_a?(Nil)
    print(prompt)
    s = gets
  end
  set_color(DEFAULT_INK)
  return s
end

# Print the given prompt in the correspondent color, wait until the user
# enters a string and return it parsed as an `Int32`; return also an ok flag
# which is `false` if no appropriate value could be found, or if the input
# string contained more than just the number. Restore the default color.
#
def input_int(prompt : String) : Tuple(Int32, Bool)
  begin
    n = input_string(prompt).to_i
  rescue
    return 0, false
  end
  return n, true
end

def pause(prompt : String = "> ")
  input_string(prompt)
end

# Credits and instructions {{{1
# ==============================================================================

CREDITS =
  "Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in Crystal:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair"

def print_credits
  set_color(TITLE_INK)
  print("#{CREDITS}\n")
  set_color(DEFAULT_INK)
end

def instructions : String
  return sprintf(
    "Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between %d and %d bushels per acre.  You also must
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough.  You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a %d-year term of office.",

    MIN_HARVESTED_BUSHELS_PER_ACRE, MAX_HARVESTED_BUSHELS_PER_ACRE, YEARS)
end

def print_instructions
  set_color(INSTRUCTIONS_INK)
  print("#{instructions}\n")
  set_color(DEFAULT_INK)
end

# Game {{{1
# ==============================================================================

# Return a random number from 1 to 5 (inclusive).
#
def random_1_to_5 : Int32
  return Random.rand(5) + 1
end

# Return the proper wording for `n` persons, using the given or default words
# for singular and plural forms.
#
def persons(
  n : Int32,
  singular = "person",
  plural = "people",
) : String
  case n
  when 0; return "nobody"
  when 1; return "one " + singular
  else    return n.to_s + plural
  end
end

def ordinal_suffix(n : Int32) : String
  case n
  when 1; return "st"
  when 2; return "nd"
  when 3; return "rd"
  else    return "th"
  end
end

# Return the description of the given year as the previous one.
#
def previous(year : Int32) : String
  if year == 0
    return "the previous year"
  else
    return sprintf("your %v%v year", year, ordinal_suffix(year))
  end
end

def print_annual_report(year : Int32)
  clear_screen
  set_color(SPEECH_INK)
  print("Hammurabi, I beg to report to you.\n")
  set_color(DEFAULT_INK)

  print("\nIn #{previous(year)}, #{persons(Global.dead)} starved and #{persons(Global.infants, "infant", "infants")} #{Global.infants > 1 ? "were" : "was"} born.\n")

  Global.population += Global.infants

  if year > 0 && Random.rand <= PLAGUE_CHANCE
    Global.population = (Global.population / 2).to_i
    set_color(WARNING_INK)
    print("A horrible plague struck!  Half the people died.\n")
    set_color(DEFAULT_INK)
  end

  print("The population is #{Global.population}.\n")
  print("The city owns #{Global.acres} acres.\n")
  print("You harvested #{Global.bushels_harvested} bushels (#{Global.bushels_harvested_per_acre} per acre).\n")
  if Global.bushels_eaten_by_rats > 0
    print("The rats ate #{Global.bushels_eaten_by_rats} bushels.\n")
  end
  print("You have #{Global.bushels_in_store} bushels in store.\n")
  Global.bushels_harvested_per_acre =
    (RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * Random.rand).to_i +
      MIN_HARVESTED_BUSHELS_PER_ACRE
  print("Land is trading at #{Global.bushels_harvested_per_acre} bushels per acre.\n\n")
end

def say_bye
  set_color(DEFAULT_INK)
  print("\nSo long for now.\n\n")
end

def quit_game
  say_bye
  exit(0)
end

def relinquish
  set_color(SPEECH_INK)
  print("\nHammurabi, I am deeply irritated and cannot serve you anymore.\n")
  print("Please, get yourself another steward!\n")
  set_color(DEFAULT_INK)
  quit_game
end

def increase_irritation
  Global.irritation += 1 + Random.rand(IRRITATION_STEP)
  if Global.irritation >= MAX_IRRITATION
    relinquish # this never returns
  end
end

def print_irritated(adverb : String)
  print("The steward seems #{adverb} irritated.\n")
end

def show_irritation
  case
  when Global.irritation < IRRITATION_STEP
  when Global.irritation < IRRITATION_STEP * 2; print_irritated("slightly")
  when Global.irritation < IRRITATION_STEP * 3; print_irritated("quite")
  when Global.irritation < IRRITATION_STEP * 4; print_irritated("very")
  else                                          print_irritated("profoundly")
  end
end

# Print a message begging to repeat an ununderstandable input.
#
def beg_repeat
  increase_irritation # this may never return
  set_color(SPEECH_INK)
  print("I beg your pardon?  I did not understand your order.\n")
  set_color(DEFAULT_INK)
  show_irritation
end

# Print a message begging to repeat a wrong input, because there's only `n`
# items of `name`.
#
def beg_think_again(n : Int32, name : String)
  increase_irritation # this may never return
  set_color(SPEECH_INK)
  print("I beg your pardon?  You have only #{n} #{name}.  Now then…\n")
  set_color(DEFAULT_INK)
  show_irritation
end

# Buy or sell land.
#
def trade
  acres_to_buy : Int32
  acres_to_sell : Int32
  ok : Bool

  while true
    acres_to_buy, ok = input_int("How many acres do you wish to buy? (0 to sell): ")
    if !ok || acres_to_buy < 0
      beg_repeat # this may never return
      next
    end
    if Global.bushels_harvested_per_acre * acres_to_buy <= Global.bushels_in_store
      break
    end
    beg_think_again(Global.bushels_in_store, "bushels of grain")
  end

  if acres_to_buy != 0
    print("You buy #{acres_to_buy} acres.\n")
    Global.acres += acres_to_buy
    Global.bushels_in_store -= Global.bushels_harvested_per_acre * acres_to_buy
    print("You now have #{Global.acres} acres and #{Global.bushels_in_store} bushels.\n")
  else
    while true
      acres_to_sell, ok = input_int("How many acres do you wish to sell?: ")
      if !ok || acres_to_sell < 0
        beg_repeat # this may never return
        next
      end
      if acres_to_sell < Global.acres
        break
      end
      beg_think_again(Global.acres, "acres")
    end

    if acres_to_sell > 0
      print("You sell #{acres_to_sell} acres.\n")
      Global.acres -= acres_to_sell
      Global.bushels_in_store += Global.bushels_harvested_per_acre * acres_to_sell
      print("You now have #{Global.acres} acres and #{Global.bushels_in_store} bushels.\n")
    end
  end
end

# Feed the people.
#
def feed
  ok : Bool

  while true
    Global.bushels_to_feed_with, ok = input_int("How many bushels do you wish to feed your people with?: ")
    if !ok || Global.bushels_to_feed_with < 0
      beg_repeat # this may never return
      next
    end
    # Trying to use more grain than is in silos?
    if Global.bushels_to_feed_with <= Global.bushels_in_store
      break
    end
    beg_think_again(Global.bushels_in_store, "bushels of grain")
  end

  print("You feed your people with #{Global.bushels_to_feed_with} bushels.\n")
  Global.bushels_in_store -= Global.bushels_to_feed_with
  print("You now have #{Global.bushels_in_store} bushels.\n")
end

# Seed the land.
#
def seed
  acres_to_seed : Int32
  ok : Bool

  while true
    acres_to_seed, ok = input_int("How many acres do you wish to seed?: ")
    if !ok || acres_to_seed < 0
      beg_repeat # this may never return
      next
    end
    if acres_to_seed == 0
      break
    end

    # Trying to seed more acres than you own?
    if acres_to_seed > Global.acres
      beg_think_again(Global.acres, "acres")
      next
    end

    # Enough grain for seed?
    if (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED).to_i > Global.bushels_in_store
      beg_think_again(
        Global.bushels_in_store,
        sprintf(
          "bushels of grain,\nand one bushel can seed %v acres",
          ACRES_A_BUSHEL_CAN_SEED
        )
      )
      next
    end

    # Enough people to tend the crops?
    if acres_to_seed <= ACRES_A_PERSON_CAN_SEED * Global.population
      break
    end

    beg_think_again(
      Global.population,
      sprintf(
        "people to tend the fields,\nand one person can seed %v acres",
        ACRES_A_PERSON_CAN_SEED
      )
    )
  end

  bushels_used_for_seeding = (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED).to_i
  print("You seed #{acres_to_seed} acres using #{bushels_used_for_seeding} bushels.\n")
  Global.bushels_in_store -= bushels_used_for_seeding
  print("You now have #{Global.bushels_in_store} bushels.\n")

  # A bountiful harvest!
  Global.bushels_harvested_per_acre = random_1_to_5
  Global.bushels_harvested = acres_to_seed * Global.bushels_harvested_per_acre
  Global.bushels_in_store += Global.bushels_harvested
end

def is_even(n : Int32) : Bool
  return n % 2 == 0
end

def check_rats
  rat_chance = random_1_to_5
  Global.bushels_eaten_by_rats = is_even(rat_chance) ? (Global.bushels_in_store / rat_chance).to_i : 0
  Global.bushels_in_store -= Global.bushels_eaten_by_rats
end

# Set the variables to their values in the first year.
#
def init
  Global.dead = 0
  Global.total_dead = 0
  Global.starved_people_percentage = 0
  Global.population = 95
  Global.infants = 5
  Global.acres = ACRES_PER_PERSON * (Global.population + Global.infants)
  Global.bushels_harvested_per_acre = 3
  Global.bushels_harvested = Global.acres * Global.bushels_harvested_per_acre
  Global.bushels_eaten_by_rats = 200
  Global.bushels_in_store = Global.bushels_harvested - Global.bushels_eaten_by_rats
  Global.irritation = 0
end

def print_result(result : Result)
  set_color(RESULT_INK)

  case result
  when Result::Very_Good
    print("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could\n")
    print("not have done better!\n")
  when Result::Not_Too_Bad
    print("Your performance could have been somewat better, but really wasn't too bad at\n")
    print("all. #{((Global.population).to_f * 0.8 * Random.rand).to_i} people would dearly like to see you assassinated, but we all have our\n")
    print("trivial problems.\n")
  when Result::Bad
    print("Your heavy-handed performance smacks of Nero and Ivan IV.  The people\n")
    print("(remaining) find you an unpleasant ruler and, frankly, hate your guts!\n")
  when Result::Very_Bad
    print("Due to this extreme mismanagement you have not only been impeached and thrown\n")
    print("out of office but you have also been declared national fink!!!\n")
  end

  set_color(DEFAULT_INK)
end

def print_final_report
  clear_screen

  if Global.starved_people_percentage > 0
    print("In your #{YEARS}-year term of office, #{Global.starved_people_percentage} percent of the\n")
    print("population starved per year on the average, i.e., a total of #{Global.total_dead} people died!\n\n")
  end

  acres_per_person = Global.acres / Global.population
  print("You started with #{ACRES_PER_PERSON} acres per person and ended with #{acres_per_person}.\n\n")

  case
  when Global.starved_people_percentage > 33, acres_per_person < 7; print_result(Result::Very_Bad)
  when Global.starved_people_percentage > 10, acres_per_person < 9; print_result(Result::Bad)
  when Global.starved_people_percentage > 3, acres_per_person < 10; print_result(Result::Not_Too_Bad)
  else                                                              print_result(Result::Very_Good)
  end
end

def check_starvation(year : Int32)
  # How many people has been fed?
  fed_people = (Global.bushels_to_feed_with / BUSHELS_TO_FEED_A_PERSON).to_i

  if Global.population > fed_people
    Global.dead = Global.population - fed_people
    Global.starved_people_percentage = (((year - 1) * Global.starved_people_percentage + Global.dead * 100 / Global.population) / year).to_i
    Global.population -= Global.dead
    Global.total_dead += Global.dead

    # Starve enough for impeachment?
    if Global.dead > (0.45 * (Global.population).to_f).to_i
      set_color(WARNING_INK)
      print("\nYou starved #{Global.dead} people in one year!!!\n\n")
      set_color(DEFAULT_INK)
      print_result(Result::Very_Bad)
      quit_game
    end
  end
end

def govern
  init

  print_annual_report(0)

  (1..YEARS).each do |year|
    trade
    feed
    seed
    check_rats

    # Let's have some babies
    Global.infants = (random_1_to_5 * (20 * Global.acres + Global.bushels_in_store) / Global.population / 100 + 1).to_i

    check_starvation(year)

    pause("\nPress the Enter key to read the annual report. ")
    print_annual_report(year)
  end
end

# Main {{{1
# ==============================================================================

clear_screen
print_credits

pause("\nPress the Enter key to read the instructions. ")
clear_screen
print_instructions

pause("\nPress the Enter key to start. ")
govern

pause("Press the Enter key to read the final report. ")
print_final_report
say_bye

In D

// Hammurabi

// Description:
//     A simple text-based simulation game set in the ancient kingdom of Sumeria.

// Original program:
//     Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
//
// BASIC port:
//     Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
//     Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
//
// More details:
//     - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
//     - https://www.mobygames.com/game/22232/hamurabi/

// This improved remake in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-23.
//
// Last modified: 20251219T2142+0100.
//
// Acknowledgment:
//     The following Python port was used as a reference of the original
//     variables: <https://github.com/jquast/hamurabi.py>.
//

module hammurabi;

// Modules {{{1
// =============================================================

    import std.random : uniform01;
    import std.stdio : readln;
    import std.stdio : write;
    import std.stdio : writef;
    import std.stdio : writefln;
    import std.stdio : writeln;
    import std.string : strip;

// Terminal {{{1
// =============================================================================

enum BLACK = 0;
enum RED = 1;
enum GREEN = 2;
enum YELLOW = 3;
enum BLUE = 4;
enum MAGENTA = 5;
enum CYAN = 6;
enum WHITE = 7;
enum DEFAULT = 9;

enum STYLE_OFF = 20;
enum FOREGROUND = 30;
enum BACKGROUND = 40;
enum BRIGHT = 60;

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    write("\x1B[H");
}

void setStyle(int style)
{
    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    write("\x1B[2J");
}

void clearScreen()
{
    eraseScreen();
    resetAttributes();
    moveCursorHome();
}

// Data {{{1
// =============================================================

enum ACRES_A_BUSHEL_CAN_SEED = 2; // yearly
enum ACRES_A_PERSON_CAN_SEED = 10; // yearly
enum ACRES_PER_PERSON = 10; // to calculate the initial acres of the city
enum BUSHELS_TO_FEED_A_PERSON = 20; // yearly
enum IRRITATION_LEVELS = 5; // after the switch in `showIrritation`
enum IRRITATION_STEP = MAX_IRRITATION / IRRITATION_LEVELS;
enum MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1;
enum MIN_HARVESTED_BUSHELS_PER_ACRE = 17;
enum MAX_IRRITATION = 16;
enum PLAGUE_CHANCE = 0.15; // 15% yearly
enum RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10;
enum YEARS = 10; // goverment period

enum DEFAULT_INK = FOREGROUND + WHITE;
enum INPUT_INK = FOREGROUND + BRIGHT + GREEN;
enum INSTRUCTIONS_INK = FOREGROUND + YELLOW;
enum RESULT_INK = FOREGROUND + BRIGHT + CYAN;
enum SPEECH_INK = FOREGROUND + BRIGHT + MAGENTA;
enum TITLE_INK = FOREGROUND + BRIGHT + WHITE;
enum WARNING_INK = FOREGROUND + BRIGHT + RED;

enum Result { VERY_GOOD, NOT_TOO_BAD, BAD, VERY_BAD }

int acres = 0;
int bushelsEatenByRats = 0;
int bushelsHarvested = 0;
int bushelsHarvestedPerAcre = 0;
int bushelsInStore = 0;
int bushelsToFeedWith = 0;
int dead = 0;
int infants = 0;
int irritation = 0; // counter (0 ..= 99)
int population = 0;
int starvedPeoplePercentage = 0;
int totalDead = 0;

// Credits and instructions {{{1
// =============================================================

enum CREDITS =
"Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in D:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair";

void printCredits()
{
    setStyle(TITLE_INK);
    writef("%s\t", CREDITS);
    setStyle(DEFAULT_INK);
}

enum INSTRUCTIONS =
"Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between %d and %d bushels per acre.  You also must
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough.  You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a %d-year term of office.";

void printInstructions()
{
    setStyle(INSTRUCTIONS_INK);
    writef(
        INSTRUCTIONS,
        MIN_HARVESTED_BUSHELS_PER_ACRE,
        MAX_HARVESTED_BUSHELS_PER_ACRE,
        YEARS
        );
    setStyle(DEFAULT_INK);
}

// User input {{{1
// =============================================================

string acceptString(string prompt)
{
    write(prompt);
    return strip(readln());
}

void pause(string prompt = "> ")
{
    setStyle(INPUT_INK);
    acceptString(prompt);
    setStyle(DEFAULT_INK);
}

/// Print the given prompt, accept a string from the user. If the typed string
/// is a valid integer return it; otherwise return -1.

int getInteger(string prompt)
{
    import std.conv : to;

    int result;
    string s = acceptString(prompt);
    try
    {
        result = to!int(s);
    }
    catch (Exception exc)
    {
        result = -1;
    }
    return result;
}


// Random numbers {{{1
// =============================================================

/// Return a random number from 1 to 5 (inclusive).

int numberFrom1To5()
{
    import std.random : uniform;

    return uniform(1, 6);
}

// Strings {{{1
// =============================================================

/// Return a string with the proper wording for `n` persons, using the given or
/// default words for singular and plural forms.

string persons(int n, string singular = "person", string plural = "people")
{
    import std.format : format;

    switch (n)
    {
        case 0: return "nobody";
        case 1: return format("one %s", singular);
        default: return format("%d %s", n, plural);
    }
}

string ordinalSuffix(int n)
{
    switch (n)
    {
    case 1: return "st";
    case 2: return "nd";
    case 3: return "rd";
    default: return "th";
    }
}

// Game {{{1
// =============================================================================

/// Return a string with the description of the given year as the previous one.

string previous(int year)
{
    import std.format : format;

    if (year == 0)
    {
        return "the previous year";
    }
    else
    {
        return format("your %d%s year", year, ordinalSuffix(year));
    }
}

void printAnnualReport(int year)
{
    clearScreen();
    setStyle(SPEECH_INK);
    writeln("Hammurabi, I beg to report to you.");
    setStyle(DEFAULT_INK);

    string yearText = previous(year);
    string personsText = persons(dead);
    string infantsText = persons(infants);

    writefln(
        "\nIn %s, %s starved and %s %s born.",
        yearText,
        personsText,
        infantsText,
        (infants > 1) ? "were" : "was"
    );

    population += infants;

    if (year > 0 && uniform01() <= PLAGUE_CHANCE)
    {
        population = cast(int)(population / 2);
        setStyle(WARNING_INK);
        writeln("A horrible plague struck!  Half the people died.");
        setStyle(DEFAULT_INK);
    }

    writefln("The population is %d.", population);
    writefln("The city owns %d acres.", acres);
    writefln(
        "You harvested %d bushels (%d per acre).",
        bushelsHarvested,
        bushelsHarvestedPerAcre
    );
    if (bushelsEatenByRats > 0)
    {
        writefln("The rats ate %d bushels.", bushelsEatenByRats);
    }
    writefln("You have %d bushels in store.", bushelsInStore);
    bushelsHarvestedPerAcre =
        cast(int)(cast(float)RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * uniform01()) +
        MIN_HARVESTED_BUSHELS_PER_ACRE;
    writefln("Land is trading at %d bushels per acre.\n", bushelsHarvestedPerAcre);
}

void sayBye()
{
    setStyle(DEFAULT_INK);
    writeln("\nSo long for now.");
}

void quitGame()
{
    import core.stdc.stdlib : exit;

    sayBye();
    exit(0);
}

void relinquish()
{
    setStyle(SPEECH_INK);
    writeln("\nHammurabi, I am deeply irritated and cannot serve you anymore.");
    writeln("Please, get yourself another steward!");
    setStyle(DEFAULT_INK);
    quitGame();
}

void increaseIrritation()
{
    import std.random : uniform;

    irritation += uniform(1, IRRITATION_STEP + 1);
    if (irritation >= MAX_IRRITATION)
    {
        relinquish(); // this never returns
    }
}

void printIrritated(string adverb)
{
    writefln("The steward seems %s irritated.", adverb);
}

void showIrritation()
{
    if (irritation < IRRITATION_STEP * 2)
    {
        printIrritated("slightly");
    }
    else if (irritation < IRRITATION_STEP * 3)
    {
        printIrritated("quite");
    }
    else if (irritation < IRRITATION_STEP * 4)
    {
        printIrritated("very");
    }
    else
    {
        printIrritated("profoundly");
    }
}

/// Print a message begging to repeat an ununderstandable input.

void begRepeat()
{
    increaseIrritation(); // this may never return
    setStyle(SPEECH_INK);
    writeln("I beg your pardon?  I did not understand your order.");
    setStyle(DEFAULT_INK);
    showIrritation();
}

/// Print a message begging to repeat a wrong input, because there's only `n`
/// items of `name`.

void begThinkAgain(int n, string name)
{
    increaseIrritation(); // this may never return
    setStyle(SPEECH_INK);
    writefln("I beg your pardon?  You have only %d %s.  Now then…", n, name);
    setStyle(DEFAULT_INK);
    showIrritation();
}

/// Buy or sell land.

void trade()
{

    int acresToBuy = 0;
    int acresToSell = 0;

    while (true)
    {
        acresToBuy = getInteger("How many acres do you wish to buy? (0 to sell): ");
        if (acresToBuy < 0)
        {
            begRepeat(); // this may never return
        }
        else
        {
            if (bushelsHarvestedPerAcre * acresToBuy <= bushelsInStore)
            {
                break;
            }
            else
            {
                begThinkAgain(bushelsInStore, "bushels of grain");
            }
        }
    }

    if (acresToBuy != 0)
    {
        writefln("You buy %d acres.", acresToBuy);
        acres += acresToBuy;
        bushelsInStore -= bushelsHarvestedPerAcre * acresToBuy;
        writefln("You now have %d acres and %d bushels.", acres, bushelsInStore);
    }
    else
    {
        while (true)
        {
            acresToSell = getInteger("How many acres do you wish to sell?: ");
            if (acresToSell < 0)
            {
                begRepeat(); // this may never return
            }
            else
            {
                if (acresToSell < acres)
                {
                    break;
                }
                else
                {
                    begThinkAgain(acres, "acres");
                }
            }
        }

        if (acresToSell > 0)
        {
            writefln("You sell %d acres.", acresToSell);
            acres -= acresToSell;
            bushelsInStore += bushelsHarvestedPerAcre * acresToSell;
            writefln("You now have %d acres and %d bushels.", acres, bushelsInStore);
        }
    }
}

/// Feed the people.

void feed()
{
    while (true)
    {
        bushelsToFeedWith = getInteger("How many bushels do you wish to feed your people with?: ");
        if (bushelsToFeedWith < 0)
        {
            begRepeat(); // this may never return
        }
        else
        {
            // Trying to use more grain than is in silos?
            if (bushelsToFeedWith <= bushelsInStore)
            {
                break;
            }
            else
            {
                begThinkAgain(bushelsInStore, "bushels of grain");
            }
        }
    }

    writefln("You feed your people with %d bushels.", bushelsToFeedWith);
    bushelsInStore -= bushelsToFeedWith;
    writefln("You now have %d bushels.", bushelsInStore);
}

/// Seed the land.

void seed()
{
    import std.format : format;

    int acresToSeed = 0;

    while (true)
    {
        acresToSeed = getInteger("How many acres do you wish to seed?: ");
        if (acresToSeed < 0)
        {
            begRepeat(); // this may never return
            continue;
        }
        if (acresToSeed == 0)
        {
            break;
        }

        // Trying to seed more acres than you own?
        if (acresToSeed > acres)
        {
            begThinkAgain(acres, "acres");
            continue;
        }

        string message = format(
            "bushels of grain,\nand one bushel can seed %d acres",
            ACRES_A_BUSHEL_CAN_SEED
        );

        // Enough grain for seed?
        if (cast(int)(acresToSeed / ACRES_A_BUSHEL_CAN_SEED) > bushelsInStore)
        {
            begThinkAgain(bushelsInStore, message);
            continue;
        }

        // Enough people to tend the crops?
        if (acresToSeed <= ACRES_A_PERSON_CAN_SEED * population)
        {
            break;
        }

        message = format(
            "people to tend the fields,\nand one person can seed %d acres",
            ACRES_A_PERSON_CAN_SEED
        );

        begThinkAgain(population, message);
    }

    int bushelsUsedForSeeding = (acresToSeed / ACRES_A_BUSHEL_CAN_SEED);
    writefln("You seed %d acres using %d bushels.", acresToSeed, bushelsUsedForSeeding);
    bushelsInStore -= bushelsUsedForSeeding;
    writefln("You now have %d bushels.", bushelsInStore);

    // A bountiful harvest!
    bushelsHarvestedPerAcre = numberFrom1To5();
    bushelsHarvested = acresToSeed * bushelsHarvestedPerAcre;
    bushelsInStore += bushelsHarvested;
}

bool isEven(int n)
{
    return n % 2 == 0;
}

void checkRats()
{
    int ratChance = numberFrom1To5();
    bushelsEatenByRats = isEven(ratChance) ? cast(int)(bushelsInStore / ratChance) : 0;
    bushelsInStore -= bushelsEatenByRats;
}

/// Set the variables to their values in the first year.

void init()
{
    dead = 0;
    totalDead = 0;
    starvedPeoplePercentage = 0;
    population = 95;
    infants = 5;
    acres = ACRES_PER_PERSON * (population + infants);
    bushelsHarvestedPerAcre = 3;
    bushelsHarvested = acres * bushelsHarvestedPerAcre;
    bushelsEatenByRats = 200;
    bushelsInStore = bushelsHarvested - bushelsEatenByRats;
    irritation = 0;
}

void printResult(Result r)
{
    setStyle(RESULT_INK);

    final switch (r)
    {
        case Result.VERY_GOOD:
            writeln("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could");
            writeln("not have done better!");
            break;
        case Result.NOT_TOO_BAD:
            writeln("Your performance could have been somewat better, but really wasn't too bad at");
            writefln(
                "all. %d people would dearly like to see you assassinated, but we all have our",
                cast(int)(population * 0.8 * uniform01())
            );
            writeln("trivial problems.");
            break;
        case Result.BAD:
            writeln("Your heavy-handed performance smacks of Nero and Ivan IV.  The people");
            writeln("(remaining) find you an unpleasant ruler and, frankly, hate your guts!");
            break;
        case Result.VERY_BAD:
            writeln("Due to this extreme mismanagement you have not only been impeached and thrown");
            writeln("out of office but you have also been declared national fink!!!");
    }

    setStyle(DEFAULT_INK);
}

void printFinalReport()
{
    clearScreen();

    if (starvedPeoplePercentage > 0)
    {
        writefln(
            "In your %d-year term of office, %d percent of the",
            YEARS,
            starvedPeoplePercentage
        );
        writefln(
            "population starved per year on the average, i.e., a total of %d people died!\n",
            totalDead
        );
    }

    int acresPerPerson = acres / population;
    writefln(
        "You started with %d acres per person and ended with %d.\n",
        ACRES_PER_PERSON,
        acresPerPerson
    );

    if (starvedPeoplePercentage > 33 || acresPerPerson < 7)
    {
        printResult(Result.VERY_BAD);
    }
    else if (starvedPeoplePercentage > 10 || acresPerPerson < 9)
    {
        printResult(Result.BAD);
    }
    else if (starvedPeoplePercentage > 3 || acresPerPerson < 10)
    {
        printResult(Result.NOT_TOO_BAD);
    }
    else
    {
        printResult(Result.VERY_GOOD);
    }
}

void checkStarvation(int year)
{
    // How many people has been fed?
    int fedPeople = (bushelsToFeedWith / BUSHELS_TO_FEED_A_PERSON);

    if (population > fedPeople)
    {
        dead = population - fedPeople;
        starvedPeoplePercentage = ((year - 1) * starvedPeoplePercentage + dead * 100 / population) / year;
        population -= dead;
        totalDead += dead;

        // Starve enough for impeachment?
        if (dead > cast(int)(0.45 * population))
        {
            setStyle(WARNING_INK);
            writefln("\nYou starved %d people in one year!!!\n", dead);
            setStyle(DEFAULT_INK);
            printResult(Result.VERY_BAD);
            quitGame();
        }
    }
}

void govern()
{
    init();

    printAnnualReport(0);

    foreach (int year; 1 .. YEARS + 1)
    {
        trade();
        feed();
        seed();
        checkRats();

        // Let's have some babies
        infants = cast(int)(numberFrom1To5() * (20 * acres + bushelsInStore) / population / 100 + 1);

        checkStarvation(year);

        pause("\nPress the Enter key to read the annual report. ");
        printAnnualReport(year);
    }
}

// Main {{{1
// =============================================================

void main()
{
    clearScreen();
    printCredits();
    pause("\n\nPress the Enter key to read the instructions. ");
    clearScreen();
    printInstructions();
    pause("\n\nPress the Enter key to start. ");
    govern();
    pause("\nPress the Enter key to read the final report. ");
    printFinalReport();
    sayBye();
}

In Hare

// Hammurabi
//
// Description:
//      A simple text-based simulation game set in the ancient kingdom of Sumeria.
//
// Original program:
//      Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
//
// BASIC port:
//      Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
//      Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
//
// More details:
//      - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
//      - https://www.mobygames.com/game/22232/hamurabi/
//
// This improved remake in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-19.
//
// Last modified: 20260213T1645+0100.
//
// Acknowledgment:
//      The following Python port was used as a reference of the original
//      variables: <https://github.com/jquast/hamurabi.py>.
//

// Modules {{{1
// =============================================================

use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

// Terminal {{{1
// =============================================================================

def BLACK = 0;
def RED = 1;
def GREEN = 2;
def YELLOW = 3;
def BLUE = 4;
def MAGENTA = 5;
def CYAN = 6;
def WHITE = 7;
def DEFAULT = 9;

def STYLE_OFF = 20;
def FOREGROUND = 30;
def BACKGROUND = 40;
def BRIGHT = 60;

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

// Data {{{1
// =============================================================

def ACRES_A_BUSHEL_CAN_SEED = 2; // yearly
def ACRES_A_PERSON_CAN_SEED = 10; // yearly
def ACRES_PER_PERSON = 10; // to calculate the initial acres of the city
def BUSHELS_TO_FEED_A_PERSON = 20; // yearly
def IRRITATION_LEVELS = 5; // after the switch in `show_irritation`
def IRRITATION_STEP = MAX_IRRITATION / IRRITATION_LEVELS;
def MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1;
def MIN_HARVESTED_BUSHELS_PER_ACRE = 17;
def MAX_IRRITATION = 16;
def PLAGUE_CHANCE = 0.15; // 15% yearly
def RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10;
def YEARS = 10; // goverment period

def DEFAULT_INK = FOREGROUND + WHITE;
def INPUT_INK = FOREGROUND + BRIGHT + GREEN;
def INSTRUCTIONS_INK = FOREGROUND + YELLOW;
def RESULT_INK = FOREGROUND + BRIGHT + CYAN;
def SPEECH_INK = FOREGROUND + BRIGHT + MAGENTA;
def TITLE_INK = FOREGROUND + BRIGHT + WHITE;
def WARNING_INK = FOREGROUND + BRIGHT + RED;

type result = enum {
        VERY_GOOD,
        NOT_TOO_BAD,
        BAD,
        VERY_BAD,
};

let acres: int = 0;
let bushels_eaten_by_rats: int = 0;
let bushels_harvested: int = 0;
let bushels_harvested_per_acre: int = 0;
let bushels_in_store: int = 0;
let bushels_to_feed_with: int = 0;
let dead: int = 0;
let infants: int = 0;
let irritation: int = 0; // counter (0 ..= 99)
let population: int = 0;
let starved_people_percentage: int = 0;
let total_dead: int = 0;

// Credits and instructions {{{1
// =============================================================

def CREDITS: str =
`Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in Hare:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair`;

fn print_credits() void = {
        set_style(TITLE_INK);
        fmt::println(CREDITS)!;
        set_style(DEFAULT_INK);
};

def INSTRUCTIONS =
`Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between {} and {} bushels per acre.  You also must
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough.  You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a {}-year term of office.`;

fn print_instructions() void = {
        set_style(INSTRUCTIONS_INK);
        fmt::printfln(
                INSTRUCTIONS,
                MIN_HARVESTED_BUSHELS_PER_ACRE,
                MAX_HARVESTED_BUSHELS_PER_ACRE,
                YEARS
                )!;
        set_style(DEFAULT_INK);
};

// User input {{{1
// =============================================================

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn pause(prompt: str = "> ") void = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        free(accept_string(prompt));
};

fn accept_integer(prompt: str = "") (int | ...strconv::error) = {
        const s = accept_string(prompt);
        defer free(s);
        return strconv::stoi(s);
};

// Accept an integer from the user with the given prompt; if the input is an
// invalid integer, return -1.
//
fn get_integer(prompt: str) int = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return match (accept_integer(prompt)) {
                case let number: int => yield number;
                case => yield -1;
                };
};

// Random numbers {{{1
// =============================================================

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

// Return a random number from 1 to 5 (inclusive).
//
fn random_1_to_5() int = {
        return random::u64n(&rand, 5): int + 1;
};

// Strings {{{1
// =============================================================

// Return a string with the proper wording for `n` persons, using the given or
// default words for singular and plural forms, and a flag indicating whether
// the string must be freed by the caller or not.
//
fn persons(n: int, singular: str = "person", plural: str = "people") (str, bool) = {
        switch (n) {
        case 0 => return ("nobody", false);
        case 1 => return (fmt::asprintf("one {}", singular)!, true);
        case => return (fmt::asprintf("{} {}", n, plural)!, true);
        };
};

fn ordinal_suffix(n: int) str = {
        switch (n) {
        case 1 => return "st";
        case 2 => return "nd";
        case 3 => return "rd";
        case => return "th";
        };
};

// Game {{{1
// =============================================================================

// Return a string with the description of the given year as the previous one;
// return also a flag indicating whether the string wether must be freed by the
// caller or not.
//
fn previous(year: int) (str, bool) = {
        if (year == 0) {
                return ("the previous year", false);
        } else {
                return (
                        fmt::asprintf(
                                "your {}{} year",
                                year,
                                ordinal_suffix(year)
                        )!,
                        true
                );
        };
};

fn print_annual_report(year: int) void = {
        clear_screen();
        set_style(SPEECH_INK);
        fmt::println("Hammurabi, I beg to report to you.")!;
        set_style(DEFAULT_INK);

        const (year_text, year_text_must_be_freed) = previous(year);
        const (persons_text, persons_text_must_be_freed) = persons(dead);
        const (infants_text, infants_text_must_be_freed) = persons(infants);

        fmt::printf(
                "\nIn {}, {} starved and {} {} born.\n",
                year_text,
                persons_text,
                infants_text,
                if (infants > 1) "were" else "was"
        )!;
        if (year_text_must_be_freed) {
                free(year_text);
        };
        if (persons_text_must_be_freed) {
                free(persons_text);
        };
        if (infants_text_must_be_freed) {
                free(infants_text);
        };

        population += infants;

        if (year > 0 && random::f64rand(&rand) <= PLAGUE_CHANCE) {
                population = (population / 2): int;
                set_style(WARNING_INK);
                fmt::println("A horrible plague struck!  Half the people died.")!;
                set_style(DEFAULT_INK);
        };

        fmt::printfln("The population is {}.", population)!;
        fmt::println("The city owns", acres, "acres.")!;
        fmt::printf(
                "You harvested {} bushels ({} per acre).\n",
                bushels_harvested,
                bushels_harvested_per_acre
        )!;
        if (bushels_eaten_by_rats > 0) {
                fmt::println("The rats ate", bushels_eaten_by_rats, "bushels.")!;
        };
        fmt::println("You have", bushels_in_store, "bushels in store.")!;
        bushels_harvested_per_acre =
                (RANGE_OF_HARVESTED_BUSHELS_PER_ACRE: f64 * random::f64rand(&rand)): int +
                MIN_HARVESTED_BUSHELS_PER_ACRE;
        fmt::println("Land is trading at", bushels_harvested_per_acre, "bushels per acre.\n")!;
};

fn say_bye() void = {
        set_style(DEFAULT_INK);
        fmt::println("\nSo long for now.\n")!;
};

fn quit_game() void = {
        say_bye();
        os::exit(0);
};

fn relinquish() void = {
        set_style(SPEECH_INK);
        fmt::println("\nHammurabi, I am deeply irritated and cannot serve you anymore.")!;
        fmt::println("Please, get yourself another steward!")!;
        set_style(DEFAULT_INK);
        quit_game();
};

fn increase_irritation() void = {
        irritation += 1 + random::u64n(&rand, IRRITATION_STEP): int;
        if (irritation >= MAX_IRRITATION) {
                relinquish(); // this never returns
        };
};

fn print_irritated(adverb: str) void = {
        fmt::printfln("The steward seems {} irritated.", adverb)!;
};

fn show_irritation() void = {
        if (irritation < IRRITATION_STEP * 2) {
                print_irritated("slightly");
        } else if (irritation < IRRITATION_STEP * 3) {
                print_irritated("quite");
        } else if (irritation < IRRITATION_STEP * 4) {
                print_irritated("very");
        } else {
                print_irritated("profoundly");
        };
};

// Print a message begging to repeat an ununderstandable input.
//
fn beg_repeat() void = {
        increase_irritation(); // this may never return
        set_style(SPEECH_INK);
        fmt::println("I beg your pardon?  I did not understand your order.")!;
        set_style(DEFAULT_INK);
        show_irritation();
};

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
fn beg_think_again(n: int, name: str) void = {
        increase_irritation(); // this may never return
        set_style(SPEECH_INK);
        fmt::printfln("I beg your pardon?  You have only {} {}.  Now then…", n, name)!;
        set_style(DEFAULT_INK);
        show_irritation();
};

// Buy or sell land.
//
fn trade() void = {
        let acres_to_buy: int = 0;
        let acres_to_sell: int = 0;

        for (true) {
                acres_to_buy = get_integer("How many acres do you wish to buy? (0 to sell): ");
                if (acres_to_buy < 0) {
                        beg_repeat(); // this may never return
                        continue;
                };
                if (bushels_harvested_per_acre * acres_to_buy <= bushels_in_store) {
                        break;
                };
                beg_think_again(bushels_in_store, "bushels of grain");
        };

        if (acres_to_buy != 0) {

                fmt::printfln("You buy {} acres.", acres_to_buy)!;
                acres += acres_to_buy;
                bushels_in_store -= bushels_harvested_per_acre * acres_to_buy;
                fmt::printfln("You now have {} acres and {} bushels.", acres, bushels_in_store)!;

        } else {

                for (true) {
                        acres_to_sell = get_integer("How many acres do you wish to sell?: ");
                        if (acres_to_sell < 0) {
                                beg_repeat(); // this may never return
                                continue;
                        };
                        if (acres_to_sell < acres) {
                                break;
                        };
                        beg_think_again(acres, "acres");
                };

                if (acres_to_sell > 0) {
                        fmt::printfln("You sell {} acres.", acres_to_sell)!;
                        acres -= acres_to_sell;
                        bushels_in_store += bushels_harvested_per_acre * acres_to_sell;
                        fmt::printfln("You now have {} acres and {} bushels.", acres, bushels_in_store)!;
                };

        };
};

// Feed the people.
//
fn feed() void = {
        for (true) {
                bushels_to_feed_with = get_integer("How many bushels do you wish to feed your people with?: ");
                if (bushels_to_feed_with < 0) {
                        beg_repeat(); // this may never return
                        continue;
                };
                // Trying to use more grain than is in silos?
                if (bushels_to_feed_with <= bushels_in_store) {
                        break;
                };
                beg_think_again(bushels_in_store, "bushels of grain");
        };

        fmt::printfln("You feed your people with {} bushels.", bushels_to_feed_with)!;
        bushels_in_store -= bushels_to_feed_with;
        fmt::printfln("You now have {} bushels.", bushels_in_store)!;
};

// Seed the land.
//
fn seed() void = {
        let acres_to_seed:int = 0;

        for (true) {

                acres_to_seed = get_integer("How many acres do you wish to seed?: ");
                if (acres_to_seed < 0) {
                        beg_repeat(); // this may never return
                        continue;
                };
                if (acres_to_seed == 0) {
                        break;
                };

                // Trying to seed more acres than you own?
                if (acres_to_seed > acres) {
                        beg_think_again(acres, "acres");
                        continue;
                };

                const message = fmt::asprintf(
                        "bushels of grain,\nand one bushel can seed {} acres",
                        ACRES_A_BUSHEL_CAN_SEED
                )!;
                defer free(message);

                // Enough grain for seed?
                if ((acres_to_seed / ACRES_A_BUSHEL_CAN_SEED): int > bushels_in_store) {
                        beg_think_again(bushels_in_store, message);
                        continue;
                };

                // Enough people to tend the crops?
                if (acres_to_seed <= ACRES_A_PERSON_CAN_SEED * population) {
                        break;
                };

                const message = fmt::asprintf(
                        "people to tend the fields,\nand one person can seed {} acres",
                        ACRES_A_PERSON_CAN_SEED
                )!;
                defer free(message);

                beg_think_again(population, message);

        };

        let bushels_used_for_seeding = (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED): int;
        fmt::printfln("You seed {} acres using {} bushels.", acres_to_seed, bushels_used_for_seeding)!;
        bushels_in_store -= bushels_used_for_seeding;
        fmt::printfln("You now have {} bushels.", bushels_in_store)!;

        // A bountiful harvest!
        bushels_harvested_per_acre = random_1_to_5();
        bushels_harvested = acres_to_seed * bushels_harvested_per_acre;
        bushels_in_store += bushels_harvested;
};

fn is_even(n: int) bool = {
        return n % 2 == 0;
};

fn check_rats() void = {
        let rat_chance = random_1_to_5();
        bushels_eaten_by_rats = if (is_even(rat_chance)) (bushels_in_store / rat_chance): int else 0;
        bushels_in_store -= bushels_eaten_by_rats;
};

// Set the variables to their values in the first year.
//
fn init() void = {
        dead = 0;
        total_dead = 0;
        starved_people_percentage = 0;
        population = 95;
        infants = 5;
        acres = ACRES_PER_PERSON * (population + infants);
        bushels_harvested_per_acre = 3;
        bushels_harvested = acres * bushels_harvested_per_acre;
        bushels_eaten_by_rats = 200;
        bushels_in_store = bushels_harvested - bushels_eaten_by_rats;
        irritation = 0;
};

fn print_result(r: result) void = {
        set_style(RESULT_INK);

        switch (r) {
        case result::VERY_GOOD =>
                fmt::println("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could")!;
                fmt::println("not have done better!")!;
        case result::NOT_TOO_BAD =>
                fmt::println("Your performance could have been somewat better, but really wasn't too bad at")!;
                fmt::printf(
                        "all. {} people would dearly like to see you assassinated, but we all have our\n",
                        ((population): f64 * 0.8 * random::f64rand(&rand)): int
                )!;
                fmt::println("trivial problems.")!;
        case result::BAD =>
                fmt::println("Your heavy-handed performance smacks of Nero and Ivan IV.  The people")!;
                fmt::println("(remaining) find you an unpleasant ruler and, frankly, hate your guts!")!;
        case result::VERY_BAD =>
                fmt::println("Due to this extreme mismanagement you have not only been impeached and thrown")!;
                fmt::println("out of office but you have also been declared national fink!!!")!;
        };

        set_style(DEFAULT_INK);
};

fn print_final_report() void = {
        clear_screen();

        if (starved_people_percentage > 0) {
                fmt::printf(
                        "In your {}-year term of office, {} percent of the\n",
                        YEARS,
                        starved_people_percentage
                )!;
                fmt::printf(
                        "population starved per year on the average, i.e., a total of {} people died!\n\n",
                        total_dead
                )!;
        };

        let acres_per_person = acres / population;
        fmt::printf(
                "You started with {} acres per person and ended with {}.\n\n",
                ACRES_PER_PERSON,
                acres_per_person
        )!;

        if (starved_people_percentage > 33 || acres_per_person < 7) {
                print_result(result::VERY_BAD);
        } else if (starved_people_percentage > 10 || acres_per_person < 9) {
                print_result(result::BAD);
        } else if (starved_people_percentage > 3 || acres_per_person < 10) {
                print_result(result::NOT_TOO_BAD);
        } else {
                print_result(result::VERY_GOOD);
        };
};

fn check_starvation(year: int) void = {
                // How many people has been fed?
                let fed_people = (bushels_to_feed_with / BUSHELS_TO_FEED_A_PERSON): int;

                if (population > fed_people) {

                        dead = population - fed_people;
                        starved_people_percentage = ((year - 1) * starved_people_percentage + dead * 100 / population) / year;
                        population -= dead;
                        total_dead += dead;

                        // Starve enough for impeachment?
                        if (dead > (0.45 * (population): f64): int) {

                                set_style(WARNING_INK);
                                fmt::println("\nYou starved", dead, "people in one year!!!\n")!;
                                set_style(DEFAULT_INK);
                                print_result(result::VERY_BAD);
                                quit_game();

                        };

                };
};

fn govern() void = {
        init();

        print_annual_report(0);

        for (let year = 1; year <= YEARS; year += 1) {

                trade();
                feed();
                seed();
                check_rats();

                // Let's have some babies
                infants = (random_1_to_5() * (20 * acres + bushels_in_store) / population / 100 + 1): int;

                check_starvation(year);

                pause("\nPress the Enter key to read the annual report. ");
                print_annual_report(year);

        };
};

// Main {{{1
// =============================================================

export fn main() void = {
        randomize();

        clear_screen();
        print_credits();

        pause("\nPress the Enter key to read the instructions. ");
        clear_screen();
        print_instructions();

        pause("\nPress the Enter key to start. ");
        govern();

        pause("Press the Enter key to read the final report. ");
        print_final_report();
        say_bye();
};

In Kotlin

/*
Hammurabi

Description:
    A simple text-based simulation game set in the ancient kingdom of Sumeria.

Original program:
    Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
    Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
    Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

More details:
    - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
    - https://www.mobygames.com/game/22232/hamurabi/

Improved remake in Odin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair
    Written in 2023-09, 2023-10.

This conversion from Odin to Kotlin:
    Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair
    Written in 2023-11-01/02, 2025-05-18.
    Last modified: 20260205T1346+0100.

*/

import kotlin.random.Random
import kotlin.system.exitProcess

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BRIGHT = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Global variables and constants {{{1
// =============================================================

const val ACRES_A_BUSHEL_CAN_SEED = 2 // yearly
const val ACRES_A_PERSON_CAN_SEED = 10 // yearly
const val ACRES_PER_PERSON = 10 // to calculate the initial acres of the city
const val BUSHELS_TO_FEED_A_PERSON = 20 // yearly
const val MAX_IRRITATION = 16
const val IRRITATION_LEVELS = 5 // after the switch in `showIrritation`
const val IRRITATION_STEP: Int = MAX_IRRITATION / IRRITATION_LEVELS
const val RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10
const val MIN_HARVESTED_BUSHELS_PER_ACRE = 17
const val MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1
const val PLAGUE_CHANCE = 0.15 // 15% yearly
const val YEARS = 10 // goverment period

const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val INSTRUCTIONS_INK = BRIGHT + YELLOW + FOREGROUND
const val RESULT_INK = BRIGHT + CYAN + FOREGROUND
const val SPEECH_INK = BRIGHT + MAGENTA + FOREGROUND
const val TITLE_INK = BRIGHT + WHITE + FOREGROUND
const val WARNING_INK = BRIGHT + RED + FOREGROUND

enum class Result {VeryGood, NotTooBad, Bad, VeryBad}

var acres: Int = 0
var bushelsEatenByRats: Int = 0
var bushelsHarvested: Int = 0
var bushelsHarvestedPerAcre: Int = 0
var bushelsInStore: Int = 0
var bushelsToFeedWith: Int = 0
var dead: Int = 0
var infants: Int = 0
var irritation: Int = 0 // counter (0..99)
var population: Int = 0
var starvedPeoplePercentage: Int = 0
var totalDead: Int = 0

// Instructions {{{1
// =============================================================

fun instructions(): String {

    return """Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between $MIN_HARVESTED_BUSHELS_PER_ACRE and $MAX_HARVESTED_BUSHELS_PER_ACRE bushels per acre.  You also must
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough.  You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a $YEARS-year term of office."""

}

fun printInstructions() {
    setColor(INSTRUCTIONS_INK)
    println(instructions())
    setColor(DEFAULT_INK)
}

// User input {{{1
// =============================================================

fun pause(prompt: String = "> ") {
    setColor(INPUT_INK)
    print(prompt)
    readln()
    setColor(DEFAULT_INK)
}

// Print the given prompt in the correspondent color, wait until the user
// enters a string and return it parsed as an `int`; return also an ok flag
// which is `false` if no appropriate value could be found, or if the input
// string contained more than just the number. Restore the default color.
fun input(prompt: String): Pair<Int, Boolean> {
    setColor(INPUT_INK)
    print(prompt)
    val input = readln()
    var number : Int = 0
    var ok : Boolean
    try {
        number = input.toInt()
        ok = true
    }
    catch (e: NumberFormatException) {
        ok = false
    }
    setColor(DEFAULT_INK)
    return Pair(number, ok)
}

// Credits {{{1
// =============================================================

const val credits =

"""Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in Kotlin:
  Copyright (c) 2023, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair"""

fun printCredits() {
    setColor(TITLE_INK)
    println(credits)
    setColor(DEFAULT_INK)
}

// Main (2) {{{1
// =============================================================

fun ordinalSuffix(n: Int): String {
    when (n) {
        1 -> return "st"
        2 -> return "nd"
        3 -> return "rd"
        else -> return "th"
    }
}

// Return the description of the given year as the previous one.
fun previous(year: Int): String {
    if (year == 0) {
        return "the previous year"
    } else {
        return "your $year${ordinalSuffix(year)} year"
    }
}

// Return the proper wording for `n` persons, using the given or default words
// for singular and plural forms.
fun persons(n: Int, singular: String = "person", plural: String = "people"): String {
    when (n) {
        0 -> return "nobody"
        1 -> return "one $singular"
        else -> return "$n $plural"
    }
}

fun printAnnualReport(year: Int) {

    clearScreen()
    setColor(SPEECH_INK)
    println("Hammurabi, I beg to report to you.")
    setColor(DEFAULT_INK)

    println("\nIn ${previous(year)}, ${persons(dead)} starved and ${persons(infants, "infant", "infants")}, ${if (infants > 1) "were" else "was"} born.")

    population += infants

    var plagueChance = Random.nextDouble()
    if (if (year > 0) plagueChance <= PLAGUE_CHANCE else false) {
        population = (population / 2)
        setColor(WARNING_INK)
        println("A horrible plague struck!  Half the people died.")
        setColor(DEFAULT_INK)
    }

    println("The population is $population.")
    println("The city owns $acres acres.")
    println("You harvested $bushelsHarvested bushels ($bushelsHarvestedPerAcre per acre).")
    if (bushelsEatenByRats > 0) {
        println("The rats ate $bushelsEatenByRats bushels.")
    }
    println("You have $bushelsInStore bushels in store.")
    bushelsHarvestedPerAcre =
        (RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * Random.nextDouble()).toInt() +
        MIN_HARVESTED_BUSHELS_PER_ACRE
    println("Land is trading at $bushelsHarvestedPerAcre bushels per acre.\n")

}

fun sayBye() {
    setColor(DEFAULT_INK)
    println("\nSo long for now.\n")
}

fun quitGame() {
    sayBye()
    exitProcess(0)
}

fun relinquish() {
    setColor(SPEECH_INK)
    println("\nHammurabi, I am deeply irritated and cannot serve you anymore.")
    println("Please, get yourself another steward!")
    setColor(DEFAULT_INK)
    quitGame()
}

fun increaseIrritation() {
    irritation += (1..IRRITATION_STEP).random()
    if (irritation >= MAX_IRRITATION) relinquish() // this never returns
}

fun printIrritated(adverb: String) {
    println("The steward seems $adverb irritated.")
}

fun showIrritation() {
    when {
        irritation < IRRITATION_STEP -> {}
        irritation < IRRITATION_STEP * 2 -> printIrritated("slightly")
        irritation < IRRITATION_STEP * 3 -> printIrritated("quite")
        irritation < IRRITATION_STEP * 4 -> printIrritated("very")
        else -> printIrritated("profoundly")
    }
}

// Print a message begging to repeat an ununderstandable input.
fun begRepeat() {
    increaseIrritation() // this may never return
    setColor(SPEECH_INK)
    println("I beg your pardon?  I did not understand your order.")
    setColor(DEFAULT_INK)
    showIrritation()
}

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
fun begThinkAgain(n: Int, name: String) {
    increaseIrritation() // this may never return
    setColor(SPEECH_INK)
    println("I beg your pardon?  You have only $n $name.  Now then…")
    setColor(DEFAULT_INK)
    showIrritation()
}

// Buy or sell land.
fun trade() {

    var acresToBuy: Int
    var acresToSell: Int

    while (true) {
        var (number, ok) = input("How many acres do you wish to buy? (0 to sell): ")
        if (! ok || number < 0) {
            begRepeat() // this may never return
            continue
        }
        acresToBuy = number
        if (bushelsHarvestedPerAcre * acresToBuy <= bushelsInStore) break
        begThinkAgain(bushelsInStore, "bushels of grain")
    }

    if (acresToBuy != 0) {

        println("You buy $acresToBuy acres.")
        acres += acresToBuy
        bushelsInStore -= bushelsHarvestedPerAcre * acresToBuy
        println("You now have $acres acres and $bushelsInStore bushels.")

    } else {

        while (true) {
            var (number, ok) = input("How many acres do you wish to sell?: ")
            if (! ok || number < 0) {
                begRepeat() // this may never return
                continue
            }
            acresToSell = number
            if (acresToSell < acres) break
            begThinkAgain(acres, "acres")
        }

        if (acresToSell > 0) {
            println("You sell $acresToSell acres.")
            acres -= acresToSell
            bushelsInStore += bushelsHarvestedPerAcre * acresToSell
            println("You now have $acres acres and $bushelsInStore bushels.")
        }

    }

}

// Feed the people.
fun feed() {

    while (true) {
        var (number, ok) = input("How many bushels do you wish to feed your people with?: ")
        if (! ok || number < 0) {
            begRepeat() // this may never return
            continue
        }
        bushelsToFeedWith = number
        // Trying to use more grain than is in silos?
        if (bushelsToFeedWith <= bushelsInStore) break
        begThinkAgain(bushelsInStore, "bushels of grain")
    }

    println("You feed your people with $bushelsToFeedWith bushels.")
    bushelsInStore -= bushelsToFeedWith
    println("You now have $bushelsInStore bushels.")

}

// Seed the land.
fun seed() {

    var acresToSeed: Int

    while (true) {

        var (number, ok) = input("How many acres do you wish to seed?: ")
        if (! ok || number < 0) {
            begRepeat() // this may never return
            continue
        }
        acresToSeed = number
        if (acresToSeed == 0) break

        // Trying to seed more acres than you own?
        if (acresToSeed > acres) {
            begThinkAgain(acres, "acres")
            continue
        }

        // Enough grain for seed?
        if ((acresToSeed / ACRES_A_BUSHEL_CAN_SEED) > bushelsInStore) {
            begThinkAgain(
                bushelsInStore,
                "bushels of grain,\nand one bushel can seed $ACRES_A_BUSHEL_CAN_SEED acres"
            )
            continue
        }

        // Enough people to tend the crops?
        if (acresToSeed <= ACRES_A_PERSON_CAN_SEED * population) break

        begThinkAgain(
            population,
            "people to tend the fields,\nand one person can seed $ACRES_A_PERSON_CAN_SEED acres"
        )

    }

    var bushelsUsedForSeeding = (acresToSeed / ACRES_A_BUSHEL_CAN_SEED)
    println("You seed $acresToSeed acres using $bushelsUsedForSeeding bushels.")
    bushelsInStore -= bushelsUsedForSeeding
    println("You now have $bushelsInStore bushels.")

    // A bountiful harvest!
    bushelsHarvestedPerAcre = (1..5).random()
    bushelsHarvested = acresToSeed * bushelsHarvestedPerAcre
    bushelsInStore += bushelsHarvested

}

fun isEven(n: Int): Boolean {
    return n % 2 == 0
}

fun checkRats() {

    var ratChance = (1..5).random()
    bushelsEatenByRats = if (isEven(ratChance)) (bushelsInStore / ratChance) else 0
    bushelsInStore -= bushelsEatenByRats

}

// Set the variables to their values in the first year.
fun init() {

    dead = 0
    totalDead = 0
    starvedPeoplePercentage = 0
    population = 95
    infants = 5
    acres = ACRES_PER_PERSON * (population + infants)
    bushelsHarvestedPerAcre = 3
    bushelsHarvested = acres * bushelsHarvestedPerAcre
    bushelsEatenByRats = 200
    bushelsInStore = bushelsHarvested - bushelsEatenByRats
    irritation = 0

}

// Result {{{1
// =============================================================

fun printResult(result: Result) {

    setColor(RESULT_INK)

    when (result) {
        Result.VeryGood -> {
            println("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could")
            println("not have done better!")
        }
        Result.NotTooBad -> {
            println("Your performance could have been somewat better, but really wasn't too bad at")
            println("all. ${(population.toDouble() * .8 * Random.nextDouble()).toInt()} people would dearly like to see you assassinated, but we all have our")
            println("trivial problems.")
        }
        Result.Bad -> {
            println("Your heavy-handed performance smacks of Nero and Ivan IV.  The people")
            println("(remaining) find you an unpleasant ruler and, frankly, hate your guts!")
        }
        Result.VeryBad -> {
            println("Due to this extreme mismanagement you have not only been impeached and thrown")
            println("out of office but you have also been declared national fink!!!")
        }
    }

    setColor(DEFAULT_INK)

}

fun printFinalReport() {

    clearScreen()

    if (starvedPeoplePercentage > 0) {
        println("In your $YEARS-year term of office, $starvedPeoplePercentage percent of the")
        println("population starved per year on the average, i.e., a total of $totalDead people died!\n")
    }

    var acresPerPerson = acres / population
    println("You started with $ACRES_PER_PERSON acres per person and ended with $acresPerPerson.\n")

    when {
        starvedPeoplePercentage > 33 || acresPerPerson <  7 -> printResult(Result.VeryBad)
        starvedPeoplePercentage > 10 || acresPerPerson <  9 -> printResult(Result.Bad)
        starvedPeoplePercentage >  3 || acresPerPerson < 10 -> printResult(Result.NotTooBad)
        else -> printResult(Result.VeryGood)
    }

}

// Main (1) {{{1
// =============================================================

fun checkStarvation(year: Int) {

        // How many people has been fed?
        var fedPeople = (bushelsToFeedWith / BUSHELS_TO_FEED_A_PERSON)

        if (population > fedPeople) {

            dead = population - fedPeople
            starvedPeoplePercentage = ((year - 1) * starvedPeoplePercentage + dead * 100 / population) / year
            population -= dead
            totalDead += dead

            // Starve enough for impeachment?
            if (dead > (.45 * population.toDouble()).toInt()) {

                setColor(WARNING_INK)
                println("\nYou starved $dead people in one year!!!\n")
                setColor(DEFAULT_INK)
                printResult(Result.VeryBad)
                quitGame()

            }

        }

}

fun govern() {

    init()

    printAnnualReport(0)

    for (year in 1 .. YEARS) {

        trade()
        feed()
        seed()
        checkRats()

        // Let's have some babies
        infants = ((1..5).random() * (20 * acres + bushelsInStore) / population / 100 + 1)

        checkStarvation(year)

        pause("\nPress the Enter key to read the annual report. ")
        printAnnualReport(year)

    }

}

fun main() {

    clearScreen()
    printCredits()

    pause("\nPress the Enter key to read the instructions. ")
    clearScreen()
    printInstructions()

    pause("\nPress the Enter key to start. ")
    govern()

    pause("Press the Enter key to read the final report. ")
    printFinalReport()
    sayBye()

}

In Nim

#[
Hammurabi

Description:
  A simple text-based simulation game set in the ancient kingdom of Sumeria.

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

More details:
  - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
  - https://www.mobygames.com/game/22232/hamurabi/

Improved remake in Odin:
  Copyright (c) 2023, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair

This improved remake in Odin:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair

Written on 2025-01-22.

Last modified: 20251119T0013+0100.

Acknowledgment:
  The following Python port was used as a reference of the original
  variables: <https://github.com/jquast/hamurabi.py>.

]#

import std/math
import std/random
import std/strformat
import std/strutils
import std/terminal
import system

# Terminal {{{1
# =============================================================

proc cursorHome() =
  setCursorPos(0, 0)

proc clearScreen() =
  eraseScreen()
  cursorHome()

proc eraseLineRight() =
  write(stdout, "\e[K")

proc eraseDown() =
  # Erases the screen from the current line down to the bottom of the screen.
  #
  # This is defined in std/terminal but deactivated, because it does not have
  # equivalent in Windows.
  write(stdout, "\e[J")

const black = 0
const red = 1
const green = 2
const yellow = 3
const blue = 4
const magenta = 5
const cyan = 6
const white = 7
const default = 9

const foreground = +30 # color offset
const background = +40 # color offset
const bright = +60 # foreground and background offset

const normal = 0
const bold = 1 # bold text (or bright on terminals not supporting)
const dim = 2 # dim text
const italic = 3 # italic (or reverse on terminals not supporting)
const underlined = 4
const blinking = 5 # blinking/bold text
const rapidBlinking = 6 # rapid blinking/bold text (not widely supported)
const reversed = 7
const hidden = 8
const crossedout = 9 # strikethrough

const styleOff = +20 # style offset to turn it off

const notBold = 21 # or double underlined on some terminals
const notDim = 22
const notItalic = 23
const notUnderlined = 24
const notBlinking = 25
const notReversed = 27
const notHidden = 28
const notCrossedout = 29

proc ansiStyleCode(fg: int = default, bg: int = default, style: int = normal): string =
  result = &"\e[0;{style};{fg + foreground};{bg + background}m"

proc setStyle(style: string) =
  write(stdout, style)

proc setStyles(fg: int = default, bg: int = default, style: int = normal) =
  setStyle(ansiStyleCode(fg, bg, style))

# Globals {{{1
# =============================================================

const acresABushelCanSeed = 2 # yearly
const acresAPersonCanSeed = 10 # yearly
const initialAcresPerPerson = 10 # to calculate the initial acres of the city
const bushelsToFeedAPerson = 20 # yearly
const irritationLevels = 5 # after the switch in `showIrritation`
const maxIrritation = 16
const irritationStep = int(maxIrritation / irritationLevels)
const minHarvestedBushelsPerAcre = 17
const rangeOfHarvestedBushelsPerAcre = 10
const maxHarvestedBushelsPerAcre = minHarvestedBushelsPerAcre + rangeOfHarvestedBushelsPerAcre - 1
const plagueChance = 0.15 # 15% yearly
const years = 10 # goverment period

type Result = enum
  veryGood
  notTooBad
  bad
  veryBad

var acres: int
var bushelsEatenByRats: int
var bushelsHarvested: int
var bushelsHarvestedPerAcre: int
var bushelsInStore: int
var bushelsToFeedWith: int
var dead: int
var infants: int
var irritation: int # counter (0 .. 99)
var population: int
var starvedPeoplePercentage: int
var totalDead: int

const defaultStyle = ansiStyleCode(white)
const inputStyle = ansiStyleCode(bright + green)
const instructionsStyle = ansiStyleCode(bright + yellow)
const resultStyle = ansiStyleCode(bright + cyan)
const speechStyle = ansiStyleCode(bright + magenta)
const titleStyle = ansiStyleCode(bright + white)
const warningStyle = ansiStyleCode(bright + red)

# User input {{{1
# =============================================================

# Print the given prompt and return the integer entered by the
# user; if the user input is not a valid integer, ask again.
#
proc input(prompt: string): int =
  setStyle(inputStyle)
  defer: setStyle(defaultStyle)
  while true:
    try:
      write(stdout, prompt)
      result = parseInt(readLine(stdin))
      break
    except ValueError:
      continue

proc pause(prompt: string = "> ") =
  setStyle(inputStyle)
  write(stdout, prompt)
  discard readLine(stdin)
  setStyle(defaultStyle)

# Instructions and credits {{{1
# =============================================================

proc instructions(): string =
  result = fmt"""
Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between {minHarvestedBushelsPerAcre} and {maxHarvestedBushelsPerAcre} bushels per acre.  You also must
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough.  You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a {years}-year term of office."""

proc printInstructions() =
  setStyle(instructionsStyle)
  writeLine(stdout, instructions())
  setStyle(defaultStyle)

const credits = """Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in Nim:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair"""

proc printCredits() =
  setStyle(titleStyle)
  writeLine(stdout, credits)
  setStyle(defaultStyle)

# Game {{{1
# =============================================================

proc ordinalSuffix(n: int): string =
  case n:
  of 1: return "st"
  of 2: return "nd"
  of 3: return "rd"
  else: return "th"

# Return the description of the given year as the previous one.
#
proc previous(year: int): string =
  if year == 0:
    return "the previous year"
  else:
    return fmt"your {year}{ordinalSuffix(year)} year"

# Return the proper wording for `n` persons, using the given or default words
# for singular and plural forms.
#
proc persons(n: int, singular = "person", plural = "people"): string =
  case n:
  of 0: return "nobody"
  of 1: return fmt"one {singular}"
  else: return fmt"{n} {plural}"

proc printAnnualReport(year: int) =
  clearScreen()
  setStyle(speechStyle)
  writeLine(stdout, "Hammurabi, I beg to report to you.")
  setStyle(defaultStyle)

  let children = persons(infants, "infant", "infants")
  let were = if infants > 1: "were" else: "was"
  write(stdout, &"\nIn {previous(year)}, {persons(dead)} starved and {children} {were} born.\n")

  population += infants

  if year > 0 and rand(1.0) <= plagueChance:
    population = int(population / 2)
    setStyle(warningStyle)
    writeLine(stdout, "A horrible plague struck!  Half the people died.")
    setStyle(defaultStyle)

  writeLine(stdout, fmt"The population is {population}.")
  writeLine(stdout, fmt"The city owns {acres} acres.")
  write(stdout, &"You harvested {bushelsHarvested} bushels ({bushelsHarvestedPerAcre} per acre).\n")
  if bushelsEatenByRats > 0:
    writeLine(stdout, fmt"The rats ate {bushelsEatenByRats} bushels.")
  writeLine(stdout, fmt"You have {bushelsInStore} bushels in store.")
  bushelsHarvestedPerAcre =
    int(rangeOfHarvestedBushelsPerAcre * rand(1.0)) +
    minHarvestedBushelsPerAcre
  writeLine(stdout, &"Land is trading at {bushelsHarvestedPerAcre} bushels per acre.\n")

proc sayBye() =
  setStyle(defaultStyle)
  writeLine(stdout, "\nSo long for now.\n")

proc quitGame() =
  sayBye()
  quit(0)

proc relinquish() =
  setStyle(speechStyle)
  writeLine(stdout, "\nHammurabi, I am deeply irritated and cannot serve you anymore.")
  writeLine(stdout, "Please, get yourself another steward!")
  setStyle(defaultStyle)
  quitGame()

proc increaseIrritation() =
  irritation += 1 + rand(irritationStep - 1)
  if irritation >= maxIrritation:
    relinquish() # this never returns

proc printIrritated(adverb: string) =
  writeLine(stdout, fmt"The steward seems {adverb} irritated.")

proc showIrritation() =
  if irritation < irritationStep:
    discard
  elif irritation < irritationStep * 2:
    printIrritated("slightly")
  elif irritation < irritationStep * 3:
    printIrritated("quite")
  elif irritation < irritationStep * 4:
    printIrritated("very")
  else:
    printIrritated("profoundly")

# Print a message begging to repeat an ununderstandable input.
#
proc begRepeat() =
  increaseIrritation() # this may never return
  setStyle(speechStyle)
  writeLine(stdout, "I beg your pardon?  I did not understand your order.")
  setStyle(defaultStyle)
  showIrritation()

# Print a message begging to repeat a wrong input, because there's only `n`
# items of `name`.
#
proc begThinkAgain(n: int, name: string) =
  increaseIrritation() # this may never return
  setStyle(speechStyle)
  writeLine(stdout, fmt"I beg your pardon?  You have only {n} {name}.  Now then…")
  setStyle(defaultStyle)
  showIrritation()

# Buy or sell land.
#
proc trade() =
  var acresToBuy: int
  var acresToSell: int
  var ok: bool

  while true:
    acresToBuy = input("How many acres do you wish to buy? (0 to sell): ")
    if acresToBuy < 0:
      begRepeat() # this may never return
      continue
    if bushelsHarvestedPerAcre * acresToBuy <= bushelsInStore:
      break
    begThinkAgain(bushelsInStore, "bushels of grain")

  if acresToBuy != 0:

    writeLine(stdout, fmt"You buy {acresToBuy} acres.")
    acres += acresToBuy
    bushelsInStore -= bushelsHarvestedPerAcre * acresToBuy
    writeLine(stdout, fmt"You now have {acres} acres and {bushelsInStore} bushels.")

  else:

    while true:
      acresToSell = input("How many acres do you wish to sell?: ")
      if acresToSell < 0:
        begRepeat() # this may never return
        continue
      if acresToSell < acres:
        break
      begThinkAgain(acres, "acres")

    if acresToSell > 0:
      writeLine(stdout, fmt"You sell {acresToSell} acres.")
      acres -= acresToSell
      bushelsInStore += bushelsHarvestedPerAcre * acresToSell
      writeLine(stdout, fmt"You now have {acres} acres and {bushelsInStore} bushels.")

# Feed the people.
#
proc feed() =
  var ok: bool

  while true:
    bushelsToFeedWith = input("How many bushels do you wish to feed your people with?: ")
    if bushelsToFeedWith < 0:
      begRepeat() # this may never return
      continue
    # Trying to use more grain than is in silos?
    if bushelsToFeedWith <= bushelsInStore:
      break
    begThinkAgain(bushelsInStore, "bushels of grain")

  writeLine(stdout, fmt"You feed your people with {bushelsToFeedWith} bushels.")
  bushelsInStore -= bushelsToFeedWith
  writeLine(stdout, fmt"You now have {bushelsInStore} bushels.")

# Seed the land.
#
proc seed() =
  var acresToSeed: int
  var ok: bool

  while true:

    acresToSeed = input("How many acres do you wish to seed?: ")
    if acresToSeed < 0:
      begRepeat() # this may never return
      continue
    if acresToSeed == 0:
      break

    # Trying to seed more acres than you own?
    if acresToSeed > acres:
      begThinkAgain(acres, "acres")
      continue

    # Enough grain for seed?
    if int(acresToSeed / acresABushelCanSeed) > bushelsInStore:
      begThinkAgain(
        bushelsInStore,
        fmt"bushels of grain,\nand one bushel can seed {acresABushelCanSeed} acres")
      continue

    # Enough people to tend the crops?
    if acresToSeed <= acresAPersonCanSeed * population:
      break

    begThinkAgain(
      population,
      fmt"people to tend the fields,\nand one person can seed {acresAPersonCanSeed} acres")

  var bushelsUsedForSeeding = int(acresToSeed / acresABushelCanSeed)
  writeLine(stdout, fmt"You seed {acresToSeed} acres using {bushelsUsedForSeeding} bushels.")
  bushelsInStore -= bushelsUsedForSeeding
  writeLine(stdout, fmt"You now have {bushelsInStore} bushels.")

 # A bountiful harvest!
  bushelsHarvestedPerAcre = rand(1 .. 5)
  bushelsHarvested = acresToSeed * bushelsHarvestedPerAcre
  bushelsInStore += bushelsHarvested

proc isEven(n: int): bool =
  result = n %% 2 == 0

proc checkRats() =
  var ratChance = rand(1 .. 5)
  bushelsEatenByRats = if isEven(ratChance): int(bushelsInStore / ratChance) else: 0
  bushelsInStore -= bushelsEatenByRats

# Set the variables to their values in the first year.
#
proc init() =
  dead = 0
  totalDead = 0
  starvedPeoplePercentage = 0
  population = 95
  infants = 5
  acres = initialAcresPerPerson * (population + infants)
  bushelsHarvestedPerAcre = 3
  bushelsHarvested = acres * bushelsHarvestedPerAcre
  bushelsEatenByRats = 200
  bushelsInStore = bushelsHarvested - bushelsEatenByRats
  irritation = 0

proc printResult(result: Result) =
  setStyle(resultStyle)

  case result:
  of veryGood:
      writeLine(stdout, "A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could")
      writeLine(stdout, "not have done better!")
  of notTooBad:
      writeLine(stdout, "Your performance could have been somewat better, but really wasn't too bad at")
      write(stdout, &"all. {int(float(population) * 0.8 * rand(1.0))} people would dearly like to see you assassinated, but we all have our\n")
      writeLine(stdout, "trivial problems.")
  of bad:
      writeLine(stdout, "Your heavy-handed performance smacks of Nero and Ivan IV.  The people")
      writeLine(stdout, "(remaining) find you an unpleasant ruler and, frankly, hate your guts!")
  of veryBad:
      writeLine(stdout, "Due to this extreme mismanagement you have not only been impeached and thrown")
      writeLine(stdout, "out of office but you have also been declared national fink!!!")

  setStyle(defaultStyle)

proc printFinalReport() =
  clearScreen()

  if starvedPeoplePercentage > 0:
    write(stdout, &"In your {years}-year term of office, {starvedPeoplePercentage} percent of the\n")
    write(stdout, &"population starved per year on the average, i.e., a total of {totalDead} people died!\n\n")

  var acresPerPerson = acres / population
  write(stdout, &"You started with {initialAcresPerPerson} acres per person and ended with {acresPerPerson}.\n\n")

  if starvedPeoplePercentage > 33 or acresPerPerson < 7:
    printResult(veryBad)
  elif starvedPeoplePercentage > 10 or acresPerPerson < 9:
    printResult(bad)
  elif starvedPeoplePercentage > 3 or acresPerPerson < 10:
    printResult(notTooBad)
  else:
    printResult(veryGood)

proc checkStarvation(year: int) =
    # How many people has been fed?
    var fedPeople = int(bushelsToFeedWith / bushelsToFeedAPerson)

    if population > fedPeople:

      dead = population - fedPeople
      starvedPeoplePercentage = int((float64((year - 1) * starvedPeoplePercentage) + float64(dead) * 100.0 / float64(population)) / float64(year))
      population -= dead
      totalDead += dead

      # Starve enough for impeachment?
      if dead > int(0.45 * float(population)):
        setStyle(warningStyle)
        writeLine(stdout, &"\nYou starved {dead} people in one year!!!\n")
        setStyle(defaultStyle)
        printResult(veryBad)
        quitGame()

proc govern() =
  init()

  printAnnualReport(0)

  for year in 1 .. years:

    trade()
    feed()
    seed()
    checkRats()

    # Let's have some babies
    infants = int(rand(1 .. 5) * (20 * acres + bushelsInStore) / population / 100 + 1)

    checkStarvation(year)

    pause("\nPress the Enter key to read the annual report. ")
    printAnnualReport(year)

# Main {{{1
# =============================================================

randomize()

clearScreen()
printCredits()

pause("\nPress the Enter key to read the instructions. ")
clearScreen()
printInstructions()

pause("\nPress the Enter key to start. ")
govern()

pause("Press the Enter key to read the final report. ")
printFinalReport()
sayBye()

In Odin

/*
Hammurabi

Description:
    A simple text-based simulation game set in the ancient kingdom of Sumeria.

Original program:
    Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
    Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
    Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

More details:
    - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
    - https://www.mobygames.com/game/22232/hamurabi/

This improved remake in Odin:
    Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-09, 2023-10, 2025-02.

Last modified: 20250227T1855+0100.

Acknowledgment:
    The following Python port was used as a reference of the original
    variables: <https://github.com/jquast/hamurabi.py>.

*/

package hammurabi

import "../lib/anodino/src/basic"
import "../lib/anodino/src/read"
import "../lib/anodino/src/term"
import "core:fmt"
import "core:math/rand"
import "core:os"

ACRES_A_BUSHEL_CAN_SEED             ::  2 // yearly
ACRES_A_PERSON_CAN_SEED             :: 10 // yearly
ACRES_PER_PERSON                    :: 10 // to calculate the initial acres of the city
BUSHELS_TO_FEED_A_PERSON            :: 20 // yearly
IRRITATION_LEVELS                   ::  5 // after the switch in `show_irritation`
IRRITATION_STEP                     : int : MAX_IRRITATION / IRRITATION_LEVELS
MAX_HARVESTED_BUSHELS_PER_ACRE      :: MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1
MIN_HARVESTED_BUSHELS_PER_ACRE      :: 17
MAX_IRRITATION                      :: 16
PLAGUE_CHANCE                       :: 0.15 // 15% yearly
RANGE_OF_HARVESTED_BUSHELS_PER_ACRE :: 10
YEARS                               :: 10 // goverment period

DEFAULT_INK      :: basic.WHITE
INPUT_INK        :: basic.BRIGHT_GREEN
INSTRUCTIONS_INK :: basic.YELLOW
RESULT_INK       :: basic.BRIGHT_CYAN
SPEECH_INK       :: basic.PINK
TITLE_INK        :: basic.BRIGHT_WHITE
WARNING_INK      :: basic.BRIGHT_RED

Result :: enum {
    Very_Good,
    Not_Too_Bad,
    Bad,
    Very_Bad,

}

acres                      : int
bushels_eaten_by_rats      : int
bushels_harvested          : int
bushels_harvested_per_acre : int
bushels_in_store           : int
bushels_to_feed_with       : int
dead                       : int
infants                    : int
irritation                 : int // counter (0 ..= 99)
population                 : int
starved_people_percentage  : int
total_dead                 : int

// Return a random number from 1 to 5 (inclusive).
//
random_1_to_5 :: proc() -> int {

    return rand.int_max(5) + 1

}

// Return the proper wording for `n` persons, using the given or default words
// for singular and plural forms.
//
persons :: proc(
    n: int,
    singular := "person",
    plural := "people"
    ) -> string {

    switch n {
        case 0 : return "nobody"
        case 1 : return fmt.tprint("one", singular)
        case   : return fmt.tprint(n, plural)
    }

}

instructions :: proc() -> string {

    return fmt.tprintf(

`Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between %v and %v bushels per acre.  You also must
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough.  You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a %v-year term of office.`,

    MIN_HARVESTED_BUSHELS_PER_ACRE, MAX_HARVESTED_BUSHELS_PER_ACRE, YEARS)

}

print_instructions :: proc() {

    basic.color(INSTRUCTIONS_INK)
    fmt.println(instructions())
    basic.color(DEFAULT_INK)

}

pause :: proc(prompt : string = "> ") {

    basic.color(INPUT_INK)
    fmt.print(prompt)
    s, _ := read.a_string()
    delete(s)
    basic.color(DEFAULT_INK)

}

// Print the given prompt in the correspondent color, wait until the user
// enters a string and return it parsed as an `int`; return also an ok flag
// which is `false` if no appropriate value could be found, or if the input
// string contained more than just the number. Restore the default color.
//
input :: proc(prompt : string) -> (int, bool) {

    basic.color(INPUT_INK)
    defer basic.color(DEFAULT_INK)
    fmt.print(prompt)
    return read.an_int()

}

credits :=

`Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in Odin:
  Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair`

print_credits :: proc() {

    basic.color(TITLE_INK)
    fmt.println(credits)
    basic.color(DEFAULT_INK)

}

ordinal_suffix :: proc(n : int) -> string {

    switch n {
        case 1 : return "st"
        case 2 : return "nd"
        case 3 : return "rd"
        case   : return "th"
    }

}

// Return the description of the given year as the previous one.
//
previous :: proc(year : int) -> string {

    if year == 0 {
        return "the previous year"
    } else {
        return fmt.tprintf("your %v%v year", year, ordinal_suffix(year))
    }

}

print_annual_report :: proc(year : int) {

    term.clear_screen()
    basic.color(SPEECH_INK)
    fmt.println("Hammurabi, I beg to report to you.")
    basic.color(DEFAULT_INK)

    fmt.printf(
        "\nIn %v, %v starved and %v %v born.\n",
        previous(year),
        persons(dead),
        persons(infants, "infant", "infants"),
        infants > 1 ? "were" : "was"
    )

    population += infants

    if year > 0 && rand.float64() <= PLAGUE_CHANCE {
        population = int(population / 2)
        basic.color(WARNING_INK)
        fmt.println("A horrible plague struck!  Half the people died.")
        basic.color(DEFAULT_INK)
    }

    fmt.printfln("The population is %v.", population)
    fmt.println("The city owns", acres, "acres.")
    fmt.printf(
        "You harvested %v bushels (%v per acre).\n",
        bushels_harvested,
        bushels_harvested_per_acre
    )
    if bushels_eaten_by_rats > 0 {
        fmt.println("The rats ate", bushels_eaten_by_rats, "bushels.")
    }
    fmt.println("You have", bushels_in_store, "bushels in store.")
    bushels_harvested_per_acre =
        int(RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * rand.float64()) +
        MIN_HARVESTED_BUSHELS_PER_ACRE
    fmt.println("Land is trading at", bushels_harvested_per_acre, "bushels per acre.\n")

}

say_bye :: proc() {

    basic.color(DEFAULT_INK)
    fmt.println("\nSo long for now.\n")

}

quit_game :: proc() {

    say_bye()
    os.exit(0)

}

relinquish :: proc() {

    basic.color(SPEECH_INK)
    fmt.println("\nHammurabi, I am deeply irritated and cannot serve you anymore.")
    fmt.println("Please, get yourself another steward!")
    basic.color(DEFAULT_INK)
    quit_game()

}

increase_irritation :: proc() {

    irritation += 1 + rand.int_max(IRRITATION_STEP)
    if irritation >= MAX_IRRITATION do relinquish() // this never returns

}

print_irritated :: proc(adverb : string) {

    fmt.printfln("The steward seems %v irritated.", adverb)

}

show_irritation :: proc() {

    switch {
        case irritation < IRRITATION_STEP     :
        case irritation < IRRITATION_STEP * 2 : print_irritated("slightly")
        case irritation < IRRITATION_STEP * 3 : print_irritated("quite")
        case irritation < IRRITATION_STEP * 4 : print_irritated("very")
        case                                  : print_irritated("profoundly")
    }

}

// Print a message begging to repeat an ununderstandable input.
//
beg_repeat :: proc() {

    increase_irritation() // this may never return
    basic.color(SPEECH_INK)
    fmt.println("I beg your pardon?  I did not understand your order.")
    basic.color(DEFAULT_INK)
    show_irritation()

}

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
beg_think_again :: proc(n : int, name : string) {

    increase_irritation() // this may never return
    basic.color(SPEECH_INK)
    fmt.printfln("I beg your pardon?  You have only %v %s.  Now then…", n, name)
    basic.color(DEFAULT_INK)
    show_irritation()

}

// Buy or sell land.
//
trade :: proc() {

    acres_to_buy  : int
    acres_to_sell : int
    ok            : bool

    for {
        acres_to_buy, ok = input("How many acres do you wish to buy? (0 to sell): ")
        if ! ok || acres_to_buy < 0 {
            beg_repeat() // this may never return
            continue
        }
        if bushels_harvested_per_acre * acres_to_buy <= bushels_in_store do break
        beg_think_again(bushels_in_store, "bushels of grain")
    }

    if acres_to_buy != 0 {

        fmt.printfln("You buy %v acres.", acres_to_buy)
        acres += acres_to_buy
        bushels_in_store -= bushels_harvested_per_acre * acres_to_buy
        fmt.printfln("You now have %v acres and %v bushels.", acres, bushels_in_store)

    } else {

        for {
            acres_to_sell, ok = input("How many acres do you wish to sell?: ")
            if ! ok || acres_to_sell < 0 {
                beg_repeat() // this may never return
                continue
            }
            if acres_to_sell < acres do break
            beg_think_again(acres, "acres")
        }

        if acres_to_sell > 0 {
            fmt.printfln("You sell %v acres.", acres_to_sell)
            acres -= acres_to_sell
            bushels_in_store += bushels_harvested_per_acre * acres_to_sell
            fmt.printfln("You now have %v acres and %v bushels.", acres, bushels_in_store)
        }

    }

}

// Feed the people.
//
feed :: proc() {

    ok : bool

    for {
        bushels_to_feed_with, ok = input("How many bushels do you wish to feed your people with?: ")
        if ! ok || bushels_to_feed_with < 0 {
            beg_repeat() // this may never return
            continue
        }
        // Trying to use more grain than is in silos?
        if bushels_to_feed_with <= bushels_in_store do break
        beg_think_again(bushels_in_store, "bushels of grain")
    }

    fmt.printfln("You feed your people with %v bushels.", bushels_to_feed_with)
    bushels_in_store -= bushels_to_feed_with
    fmt.printfln("You now have %v bushels.", bushels_in_store)

}

// Seed the land.
//
seed :: proc() {

    acres_to_seed : int
    ok            : bool

    for {

        acres_to_seed, ok = input("How many acres do you wish to seed?: ")
        if ! ok || acres_to_seed < 0 {
            beg_repeat() // this may never return
            continue
        }
        if acres_to_seed == 0 do break

        // Trying to seed more acres than you own?
        if acres_to_seed > acres {
            beg_think_again(acres, "acres")
            continue
        }

        // Enough grain for seed?
        if int(acres_to_seed / ACRES_A_BUSHEL_CAN_SEED) > bushels_in_store {
            beg_think_again(
                bushels_in_store,
                fmt.tprintf(
                    "bushels of grain,\nand one bushel can seed %v acres",
                    ACRES_A_BUSHEL_CAN_SEED
                )
            )
            continue
        }

        // Enough people to tend the crops?
        if acres_to_seed <= ACRES_A_PERSON_CAN_SEED * population do break

        beg_think_again(
            population,
            fmt.tprintf(
                "people to tend the fields,\nand one person can seed %v acres",
                ACRES_A_PERSON_CAN_SEED
            )
        )

    }

    bushels_used_for_seeding := int(acres_to_seed / ACRES_A_BUSHEL_CAN_SEED)
    fmt.printfln("You seed %v acres using %v bushels.", acres_to_seed, bushels_used_for_seeding)
    bushels_in_store -= bushels_used_for_seeding
    fmt.printfln("You now have %v bushels.", bushels_in_store)

    // A bountiful harvest!
    bushels_harvested_per_acre = random_1_to_5()
    bushels_harvested = acres_to_seed * bushels_harvested_per_acre
    bushels_in_store += bushels_harvested

}

is_even :: proc(n : int) -> bool {

    return n %% 2 == 0

}

check_rats :: proc() {

    rat_chance := random_1_to_5()
    bushels_eaten_by_rats = is_even(rat_chance) ? int(bushels_in_store / rat_chance) : 0
    bushels_in_store -= bushels_eaten_by_rats

}

// Set the variables to their values in the first year.
//
init :: proc() {

    dead = 0
    total_dead = 0
    starved_people_percentage = 0
    population = 95
    infants = 5
    acres = ACRES_PER_PERSON * (population + infants)
    bushels_harvested_per_acre = 3
    bushels_harvested = acres * bushels_harvested_per_acre
    bushels_eaten_by_rats = 200
    bushels_in_store = bushels_harvested - bushels_eaten_by_rats
    irritation = 0

}

print_result :: proc(result : Result) {

    basic.color(RESULT_INK)

    switch result {
        case .Very_Good :
            fmt.println("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could")
            fmt.println("not have done better!")
        case .Not_Too_Bad :
            fmt.println("Your performance could have been somewat better, but really wasn't too bad at")
            fmt.printf(
                "all. %v people would dearly like to see you assassinated, but we all have our\n",
                int(f64(population) * .8 * rand.float64()))
            fmt.println("trivial problems.")
        case .Bad :
            fmt.println("Your heavy-handed performance smacks of Nero and Ivan IV.  The people")
            fmt.println("(remaining) find you an unpleasant ruler and, frankly, hate your guts!")
        case .Very_Bad :
            fmt.println("Due to this extreme mismanagement you have not only been impeached and thrown")
            fmt.println("out of office but you have also been declared national fink!!!")
    }

    basic.color(DEFAULT_INK)

}

print_final_report :: proc() {

    term.clear_screen()

    if starved_people_percentage > 0 {
        fmt.printf(
            "In your %v-year term of office, %v percent of the\n",
            YEARS,
            starved_people_percentage
        )
        fmt.printf(
            "population starved per year on the average, i.e., a total of %v people died!\n\n",
            total_dead
        )
    }

    acres_per_person := acres / population
    fmt.printf(
        "You started with %v acres per person and ended with %v.\n\n",
        ACRES_PER_PERSON,
        acres_per_person
    )

    switch {
        case starved_people_percentage > 33, acres_per_person < 07 : print_result(.Very_Bad)
        case starved_people_percentage > 10, acres_per_person < 09 : print_result(.Bad)
        case starved_people_percentage > 03, acres_per_person < 10 : print_result(.Not_Too_Bad)
        case                                                       : print_result(.Very_Good)
    }

}

check_starvation :: proc(year : int) {

        // How many people has been fed?
        fed_people := int(bushels_to_feed_with / BUSHELS_TO_FEED_A_PERSON)

        if population > fed_people {

            dead = population - fed_people
            starved_people_percentage = ((year - 1) * starved_people_percentage + dead * 100 / population) / year
            population -= dead
            total_dead += dead

            // Starve enough for impeachment?
            if dead > int(.45 * f64(population)) {

                basic.color(WARNING_INK)
                fmt.println("\nYou starved", dead, "people in one year!!!\n")
                basic.color(DEFAULT_INK)
                print_result(.Very_Bad)
                quit_game()

            }

        }

}

govern :: proc() {

    init()

    print_annual_report(0)

    for year in 1 ..= YEARS {

        trade()
        feed()
        seed()
        check_rats()

        // Let's have some babies
        infants = int(random_1_to_5() * (20 * acres + bushels_in_store) / population / 100 + 1)

        check_starvation(year)

        pause("\nPress the Enter key to read the annual report. ")
        print_annual_report(year)

    }

}

main :: proc() {

    basic.randomize()

    term.clear_screen()
    print_credits()

    pause("\nPress the Enter key to read the instructions. ")
    term.clear_screen()
    print_instructions()

    pause("\nPress the Enter key to start. ")
    govern()

    pause("Press the Enter key to read the final report. ")
    print_final_report()
    say_bye()

}

In Pike

#!/usr/bin/env pike

// Hammurabi

// Description:
//  A simple text-based simulation game set in the ancient kingdom of Sumeria.

// Original program:
//  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
//
// BASIC port:
//  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
//  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
//
// More details:
//  - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
//  - https://www.mobygames.com/game/22232/hamurabi/

// This improved remake in Pike:
//  Copyright (c) 2025, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-03-11.
//
// Last modified: 20250731T1954+0200.
//
// Acknowledgment:
//  The following Python port was used as a reference of the original
//  variables: <https://github.com/jquast/hamurabi.py>.
//

// Modules {{{1
// =============================================================


// Terminal {{{1
// =============================================================================

constant BLACK = 0;
constant RED = 1;
constant GREEN = 2;
constant YELLOW = 3;
constant BLUE = 4;
constant MAGENTA = 5;
constant CYAN = 6;
constant WHITE = 7;
constant DEFAULT = 9;

constant STYLE_OFF = 20;
constant FOREGROUND = 30;
constant BACKGROUND = 40;
constant BRIGHT = 60;

constant NORMAL_STYLE = 0;

void move_cursor_home() {
    write("\x1B[H");
}

void set_style(int style) {
    write("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    write("\x1B[2J");
}

void clear_screen() {
    erase_screen();
    reset_attributes();
    move_cursor_home();
}

// Data {{{1
// =============================================================

constant ACRES_A_BUSHEL_CAN_SEED = 2; // yearly
constant ACRES_A_PERSON_CAN_SEED = 10; // yearly
constant ACRES_PER_PERSON = 10; // to calculate the initial acres of the city
constant BUSHELS_TO_FEED_A_PERSON = 20; // yearly
constant IRRITATION_LEVELS = 5; // after the switch in `show_irritation`
constant IRRITATION_STEP = MAX_IRRITATION / IRRITATION_LEVELS;
constant MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1;
constant MIN_HARVESTED_BUSHELS_PER_ACRE = 17;
constant MAX_IRRITATION = 16;
constant PLAGUE_CHANCE = 0.15; // 15% yearly
constant RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10;
constant YEARS = 10; // goverment period

constant DEFAULT_INK = FOREGROUND + WHITE;
constant INPUT_INK = FOREGROUND + BRIGHT + GREEN;
constant INSTRUCTIONS_INK = FOREGROUND + YELLOW;
constant RESULT_INK = FOREGROUND + BRIGHT + CYAN;
constant SPEECH_INK = FOREGROUND + BRIGHT + MAGENTA;
constant TITLE_INK = FOREGROUND + BRIGHT + WHITE;
constant WARNING_INK = FOREGROUND + BRIGHT + RED;

enum Result {
    VERY_GOOD,
    NOT_TOO_BAD,
    BAD,
    VERY_BAD
}

int acres = 0;
int bushels_eaten_by_rats = 0;
int bushels_harvested = 0;
int bushels_harvested_per_acre = 0;
int bushels_in_store = 0;
int bushels_to_feed_with = 0;
int dead = 0;
int infants = 0;
int irritation = 0; // counter (0 ..= 99)
int population = 0;
int starved_people_percentage = 0;
int total_dead = 0;

// Credits and instructions {{{1
// =============================================================

constant CREDITS =
"Hammurabi\n" +
"\n" +
"Original program:\n" +
"  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.\n" +
"\n" +
"BASIC port:\n" +
"  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.\n" +
"  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.\n" +
"\n" +
"This improved remake in Pike:\n" +
"  Copyright (c) 2025, Marcos Cruz (programandala.net)\n" +
"  SPDX-License-Identifier: Fair\n";

void print_credits() {
    set_style(TITLE_INK);
    write("%s\t", CREDITS);
    set_style(DEFAULT_INK);
}

constant INSTRUCTIONS =
"Hammurabi is a simulation game in which you, as the ruler of the ancient\n" +
"kingdom of Sumeria, Hammurabi, manage the resources.\n" +
"\n" +
"You may buy and sell land with your neighboring city-states for bushels of\n" +
"grain ― the price will vary between %d and %d bushels per acre.  You also must\n" +
"use grain to feed your people and as seed to plant the next year's crop.\n" +
"\n" +
"You will quickly find that a certain number of people can only tend a certain\n" +
"amount of land and that people starve if they are not fed enough.  You also\n" +
"have the unexpected to contend with such as a plague, rats destroying stored\n" +
"grain, and variable harvests.\n" +
"\n" +
"You will also find that managing just the few resources in this game is not a\n" +
"trivial job.  The crisis of population density rears its head very rapidly.\n" +
"\n" +
"Try your hand at governing ancient Sumeria for a %d-year term of office.\n";

void print_instructions() {
    set_style(INSTRUCTIONS_INK);
    write(
        INSTRUCTIONS,
        MIN_HARVESTED_BUSHELS_PER_ACRE,
        MAX_HARVESTED_BUSHELS_PER_ACRE,
        YEARS
        );
    set_style(DEFAULT_INK);
}

// User input {{{1
// =============================================================

string accept_string(string prompt) {
    write(prompt);
    return Stdio.stdin->gets();
}

void pause(void|string prompt ) {
    prompt = prompt || "> ";
    set_style(INPUT_INK);
    accept_string(prompt);
    set_style(DEFAULT_INK);
}

// Print the given prompt, accept a string from the user. If the whole typed
// string is a valid integer (excluding trailing and leading white spaces,
// i.e.: space, tab, newline, carriage return, form feed, vertical tab and all
// the white spaces defined in Unicode), then return the number; otherwise
// return the typed string.
//
int|string accept_integer(string prompt) {
    string s = String.trim(accept_string(prompt));
    int n = (int) s;
    if ((string) n == s) {
        return n;
    } else {
        return s;
    }
}

// Print the given prompt, accept a string from the user. If the whole typed
// string is a valid integer (excluding trailing and leading white spaces,
// i.e.: space, tab, newline, carriage return, form feed, vertical tab and all
// the white spaces defined in Unicode), then return the number; otherwise
// return -1.
//
int get_integer(string prompt) {
    set_style(INPUT_INK);
    int|string answer = accept_integer(prompt);
    set_style(DEFAULT_INK);
    if (stringp(answer)) {
        return -1;
    } else {
        return answer;
    }
}

// Random numbers {{{1
// =============================================================

// Return a random number from 1 to 5 (inclusive).
//
int random_1_to_5() {
    return random(5) + 1;
}

// Strings {{{1
// =============================================================

// Return a string with the proper wording for `n` persons, using the given or
// default words for singular and plural forms.
//
string persons(int n, string|void singular, string|void plural) {

    singular = singular || "person";
    plural = plural || "people";

    switch (n) {
        case 0: return "nobody";
        case 1: return sprintf("one %s", singular);
        default: return sprintf("%d %s", n, plural);
    }

}

string ordinal_suffix(int n) {
    switch (n) {
    case 1: return "st";
    case 2: return "nd";
    case 3: return "rd";
    default: return "th";
    }
}

// Game {{{1
// =============================================================================

// Return a string with the description of the given year as the previous one.
//
string previous(int year) {
    if (year == 0) {
        return "the previous year";
    } else {
        return sprintf("your %d%s year", year, ordinal_suffix(year));
    }
}

void print_annual_report(int year) {
    clear_screen();
    set_style(SPEECH_INK);
    write("Hammurabi, I beg to report to you.\n");
    set_style(DEFAULT_INK);

    string year_text = previous(year);
    string persons_text = persons(dead);
    string infants_text = persons(infants);

    write(
        "\nIn %s, %s starved and %s %s born.\n",
        year_text,
        persons_text,
        infants_text,
        (infants > 1) ? "were" : "was"
    );

    population += infants;

    if (year > 0 && random(1.0) <= PLAGUE_CHANCE) {
        population = (int) (population / 2);
        set_style(WARNING_INK);
        write("A horrible plague struck!  Half the people died.\n");
        set_style(DEFAULT_INK);
    }

    write("The population is %d.\n", population);
    write("The city owns %d acres.\n", acres);
    write(
        "You harvested %d bushels (%d per acre).\n",
        bushels_harvested,
        bushels_harvested_per_acre
    );
    if (bushels_eaten_by_rats > 0) {
        write("The rats ate %d bushels.\n", bushels_eaten_by_rats);
    }
    write("You have %d bushels in store.\n", bushels_in_store);
    bushels_harvested_per_acre =
        (int) ((float) RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * random(1.0)) +
        MIN_HARVESTED_BUSHELS_PER_ACRE;
    write("Land is trading at %d bushels per acre.\n\n", bushels_harvested_per_acre);
}

void say_bye() {
    set_style(DEFAULT_INK);
    write("\nSo long for now.\n\n");
}

void quit_game() {
    say_bye();
    exit(0);
}

void relinquish() {
    set_style(SPEECH_INK);
    write("\nHammurabi, I am deeply irritated and cannot serve you anymore.\n");
    write("Please, get yourself another steward!\n");
    set_style(DEFAULT_INK);
    quit_game();
}

void increase_irritation() {
    irritation += 1 + random(IRRITATION_STEP);
    if (irritation >= MAX_IRRITATION) {
        relinquish(); // this never returns
    }
}

void print_irritated(string adverb) {
    write("The steward seems %s irritated.\n", adverb);
}

void show_irritation() {
    if (irritation < IRRITATION_STEP * 2) {
        print_irritated("slightly");
    } else if (irritation < IRRITATION_STEP * 3) {
        print_irritated("quite");
    } else if (irritation < IRRITATION_STEP * 4) {
        print_irritated("very");
    } else {
        print_irritated("profoundly");
    }
}

// Print a message begging to repeat an ununderstandable input.
//
void beg_repeat() {
    increase_irritation(); // this may never return
    set_style(SPEECH_INK);
    write("I beg your pardon?  I did not understand your order.\n");
    set_style(DEFAULT_INK);
    show_irritation();
}

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
void beg_think_again(int n, string name) {
    increase_irritation(); // this may never return
    set_style(SPEECH_INK);
    write("I beg your pardon?  You have only %d %s.  Now then…\n", n, name);
    set_style(DEFAULT_INK);
    show_irritation();
}

// Buy or sell land.
//
void trade() {

    int acres_to_buy = 0;
    int acres_to_sell = 0;

    while (true) {
        acres_to_buy = get_integer("How many acres do you wish to buy? (0 to sell): ");
        if (acres_to_buy < 0) {
            beg_repeat(); // this may never return
        } else {
            if (bushels_harvested_per_acre * acres_to_buy <= bushels_in_store) {
                break;
            } else {
                beg_think_again(bushels_in_store, "bushels of grain");
            }
        }
    }

    if (acres_to_buy != 0) {
        write("You buy %d acres.\n", acres_to_buy);
        acres += acres_to_buy;
        bushels_in_store -= bushels_harvested_per_acre * acres_to_buy;
        write("You now have %d acres and %d bushels.\n", acres, bushels_in_store);
    } else {
        while (true) {
            acres_to_sell = get_integer("How many acres do you wish to sell?: ");
            if (acres_to_sell < 0) {
                beg_repeat(); // this may never return
            } else {
                if (acres_to_sell < acres) {
                    break;
                } else {
                    beg_think_again(acres, "acres");
                }
            }
        }

        if (acres_to_sell > 0) {
            write("You sell %d acres.\n", acres_to_sell);
            acres -= acres_to_sell;
            bushels_in_store += bushels_harvested_per_acre * acres_to_sell;
            write("You now have %d acres and %d bushels.\n", acres, bushels_in_store);
        }
    }
}

// Feed the people.
//
void feed() {
    while (true) {
        bushels_to_feed_with = get_integer("How many bushels do you wish to feed your people with?: ");
        if (bushels_to_feed_with < 0) {
            beg_repeat(); // this may never return
        } else {
            // Trying to use more grain than is in silos?
            if (bushels_to_feed_with <= bushels_in_store) {
                break;
            } else {
                beg_think_again(bushels_in_store, "bushels of grain");
            }
        }
    }

    write("You feed your people with %d bushels.\n", bushels_to_feed_with);
    bushels_in_store -= bushels_to_feed_with;
    write("You now have %d bushels.\n", bushels_in_store);
}

// Seed the land.
//
void seed() {

    int acres_to_seed = 0;

    while (true) {
        acres_to_seed = get_integer("How many acres do you wish to seed?: ");
        if (acres_to_seed < 0) {
            beg_repeat(); // this may never return
            continue;
        }
        if (acres_to_seed == 0) {
            break;
        }

        // Trying to seed more acres than you own?
        if (acres_to_seed > acres) {
            beg_think_again(acres, "acres");
            continue;
        }

        string message = sprintf(
            "bushels of grain,\nand one bushel can seed %d acres",
            ACRES_A_BUSHEL_CAN_SEED
        );

        // Enough grain for seed?
        if ((int) (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED) > bushels_in_store) {
            beg_think_again(bushels_in_store, message);
            continue;
        }

        // Enough people to tend the crops?
        if (acres_to_seed <= ACRES_A_PERSON_CAN_SEED * population) {
            break;
        }

        message = sprintf(
            "people to tend the fields,\nand one person can seed %d acres",
            ACRES_A_PERSON_CAN_SEED
        );

        beg_think_again(population, message);
    }

    int bushels_used_for_seeding = (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED);
    write("You seed %d acres using %d bushels.\n", acres_to_seed, bushels_used_for_seeding);
    bushels_in_store -= bushels_used_for_seeding;
    write("You now have %d bushels.\n", bushels_in_store);

    // A bountiful harvest!
    bushels_harvested_per_acre = random_1_to_5();
    bushels_harvested = acres_to_seed * bushels_harvested_per_acre;
    bushels_in_store += bushels_harvested;
}

bool is_even(int n) {
    return n % 2 == 0;
}

void check_rats() {
    int rat_chance = random_1_to_5();
    bushels_eaten_by_rats = is_even(rat_chance) ? (int) (bushels_in_store / rat_chance) : 0;
    bushels_in_store -= bushels_eaten_by_rats;
}

// Set the variables to their values in the first year.
//
void init() {
    dead = 0;
    total_dead = 0;
    starved_people_percentage = 0;
    population = 95;
    infants = 5;
    acres = ACRES_PER_PERSON * (population + infants);
    bushels_harvested_per_acre = 3;
    bushels_harvested = acres * bushels_harvested_per_acre;
    bushels_eaten_by_rats = 200;
    bushels_in_store = bushels_harvested - bushels_eaten_by_rats;
    irritation = 0;
}

void print_result(Result r) {
    set_style(RESULT_INK);

    switch (r) {
        case VERY_GOOD:
            write("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could\n");
            write("not have done better!\n");
            break;
        case NOT_TOO_BAD:
            write("Your performance could have been somewat better, but really wasn't too bad at\n");
            write(
                "all. %d people would dearly like to see you assassinated, but we all have our\n",
                (int) (population * 0.8 * random(1.0))
            );
            write("trivial problems.\n");
            break;
        case BAD:
            write("Your heavy-handed performance smacks of Nero and Ivan IV.  The people\n");
            write("(remaining) find you an unpleasant ruler and, frankly, hate your guts!\n");
            break;
        case VERY_BAD:
            write("Due to this extreme mismanagement you have not only been impeached and thrown\n");
            write("out of office but you have also been declared national fink!!!\n");
    }

    set_style(DEFAULT_INK);
}

void print_final_report() {
    clear_screen();

    if (starved_people_percentage > 0) {
        write(
            "In your %d-year term of office, %d percent of the\n",
            YEARS,
            starved_people_percentage
        );
        write(
            "population starved per year on the average, i.e., a total of %d people died!\n\n",
            total_dead
        );
    }

    int acres_per_person = acres / population;
    write(
        "You started with %d acres per person and ended with %d.\n\n",
        ACRES_PER_PERSON,
        acres_per_person
    );

    if (starved_people_percentage > 33 || acres_per_person < 7) {
        print_result(VERY_BAD);
    } else if (starved_people_percentage > 10 || acres_per_person < 9) {
        print_result(BAD);
    } else if (starved_people_percentage > 3 || acres_per_person < 10) {
        print_result(NOT_TOO_BAD);
    } else {
        print_result(VERY_GOOD);
    }
}

void check_starvation(int year) {
    // How many people has been fed?
    int fed_people = (bushels_to_feed_with / BUSHELS_TO_FEED_A_PERSON);

    if (population > fed_people) {
        dead = population - fed_people;
        starved_people_percentage = ((year - 1) * starved_people_percentage + dead * 100 / population) / year;
        population -= dead;
        total_dead += dead;

        // Starve enough for impeachment?
        if (dead > (int) (0.45 * population)) {
            set_style(WARNING_INK);
            write("\nYou starved %d people in one year!!!\n\n", dead);
            set_style(DEFAULT_INK);
            print_result(VERY_BAD);
            quit_game();
        }
    }
}

void govern() {
    init();

    print_annual_report(0);

    for (int year = 1; year <= YEARS; year += 1) {
        trade();
        feed();
        seed();
        check_rats();

        // Let's have some babies
        infants = (int) (random_1_to_5() * (20 * acres + bushels_in_store) / population / 100 + 1);

        check_starvation(year);

        pause("\nPress the Enter key to read the annual report. ");
        print_annual_report(year);
    }
}

// Main {{{1
// =============================================================

void main() {
    clear_screen();
    print_credits();
    pause("\nPress the Enter key to read the instructions. ");
    clear_screen();
    print_instructions();
    pause("\nPress the Enter key to start. ");
    govern();
    pause("Press the Enter key to read the final report. ");
    print_final_report();
    say_bye();
}

In Raku

# Hammurabi
#
# Description:
#   A simple text-based simulation game set in the ancient kingdom of Sumeria.
#
# Original program:
#   Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
#
# BASIC port:
#   Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
#   Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
#
# More details:
#   - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
#   - https://www.mobygames.com/game/22232/hamurabi/
#
# This improved remake in Raku:
#   Copyright (c) 2025, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2025-03-23, 2025-04-17, 2025-07-31.
#
# Last modified: 20250731T1922+0200.
#
# Acknowledgment:
#   The following Python port was used as a reference of the original
#   variables: <https://github.com/jquast/hamurabi.py>.
#

# ==============================================================================

constant $BLACK = 0;
constant $RED = 1;
constant $GREEN = 2;
constant $YELLOW = 3;
constant $BLUE = 4;
constant $MAGENTA = 5;
constant $CYAN = 6;
constant $WHITE = 7;
constant $DEFAULT = 9;

constant $STYLE_OFF = +20;
constant $FOREGROUND = +30;
constant $BACKGROUND = +40;
constant $BRIGHT = +60;

constant $NORMAL = 0;
constant $RESET_ALL = $NORMAL;
constant $BOLD = 1;
constant $DIM = 2;
constant $ITALIC = 3;
constant $UNDERLINE = 4;
constant $UNDERSCORE = $UNDERLINE;
constant $BLINK = 5;
constant $OVERLINE = 6;
constant $RAPID_BLINK = $OVERLINE;
constant $INVERT = 7;
constant $REVERSE = $INVERT;
constant $HIDDEN = 8;
constant $CROSSED_OUT = 9;
constant $STRIKE = $CROSSED_OUT;

constant $NO_STYLE = $STYLE_OFF;

sub move_cursor_home {
    print "\e[H";
}

sub erase_screen {
    print "\e[2J";
}

sub set_style(Int $style) {
    print "\e[{$style}m";
}

sub reset_attributes {
    set_style $RESET_ALL;
}

sub clear_screen {
    erase_screen;
    #reset_attributes;
    move_cursor_home;
}

sub hide_cursor {
    print "\e[?25l";
}

sub show_cursor {
    print "\e[?25h";
}

constant $ACRES_A_BUSHEL_CAN_SEED = 2; # yearly
constant $ACRES_A_PERSON_CAN_SEED = 10; # yearly
constant $ACRES_PER_PERSON = 10; # to calculate the initial acres of the city
constant $BUSHELS_TO_FEED_A_PERSON = 20; # yearly
constant $IRRITATION_LEVELS = 5; # after the switch in `show_irritation`
constant $MAX_IRRITATION = 16;
constant $IRRITATION_STEP = $MAX_IRRITATION / $IRRITATION_LEVELS;
constant $MIN_HARVESTED_BUSHELS_PER_ACRE = 17;
constant $RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10;
constant $MAX_HARVESTED_BUSHELS_PER_ACRE = $MIN_HARVESTED_BUSHELS_PER_ACRE + $RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1;
constant $PLAGUE_CHANCE = 0.15; # 15% yearly
constant $YEARS = 10; # goverment period

constant $DEFAULT_INK = $WHITE + $FOREGROUND;
constant $INPUT_INK = $BRIGHT + $GREEN + $FOREGROUND;
constant $INSTRUCTIONS_INK = $YELLOW + $FOREGROUND;
constant $RESULT_INK = $BRIGHT + $CYAN + $FOREGROUND;
constant $SPEECH_INK = $BRIGHT + $MAGENTA + $FOREGROUND;
constant $TITLE_INK = $BRIGHT + $WHITE + $FOREGROUND;
constant $WARNING_INK = $BRIGHT + $RED + $FOREGROUND;

enum Result <Very_Good, Not_Too_Bad, Bad, Very_Bad>;

my Int $acres;
my Int $bushels_eaten_by_rats;
my Int $bushels_harvested;
my Int $bushels_harvested_per_acre;
my Int $bushels_in_store;
my Int $bushels_to_feed_with;
my Int $dead;
my Int $infants;
my Int $irritation; # counter (0 .. 99)
my Int $population;
my Int $starved_people_percentage;
my Int $total_dead;

constant $INSTRUCTIONS =

"Hammurabi is a simulation game in which you, as the ruler of the ancient;
kingdom of Sumeria, Hammurabi, manage the resources.

You may buy and sell land with your neighboring city-states for bushels of;
grainthe price will vary between %d and %d bushels per acre.  You also must;
use grain to feed your people and as seed to plant the next year's crop.

You will quickly find that a certain number of people can only tend a certain;
amount of land and that people starve if they are not fed enough.  You also;
have the unexpected to contend with such as a plague, rats destroying stored;
grain, and variable harvests.

You will also find that managing just the few resources in this game is not a;
trivial job.  The crisis of population density rears its head very rapidly.

Try your hand at governing ancient Sumeria for a %d-year term of office.";

sub instructions(--> Str) {
    return sprintf($INSTRUCTIONS,
    $MIN_HARVESTED_BUSHELS_PER_ACRE, $MAX_HARVESTED_BUSHELS_PER_ACRE, $YEARS);
}

sub print_instructions {
    set_style($INSTRUCTIONS_INK);
    put instructions;
    set_style($DEFAULT_INK);
}

sub pause(Str $prompt = '> ') {
    set_style($INPUT_INK);
    my $s = prompt $prompt;
    set_style($DEFAULT_INK);
}

# Print the given prompt and wait until the user enters an integer.

sub input(Str $prompt --> Int) {
    my Int $n;
    set_style($INPUT_INK);
    loop {
        try {
            $n = +prompt $prompt;
            CATCH {
                default {
                    put 'Integer expected.';
                    next;
                }
            }
        }
        last;
    }
    set_style($DEFAULT_INK);
    return floor $n;
}

constant $CREDITS =

"Hammurabi

Original program:
  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

This improved remake in Raku:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair";

sub print_credits {
    set_style($TITLE_INK);
    put $CREDITS;
    set_style($DEFAULT_INK);
}

sub ordinal_suffix(Int $n --> Str) {
    given $n {
        when 1  { return 'st'; }
        when 2  { return 'nd'; }
        when 3  { return 'rd'; }
        default { return 'th'; }
    }
}

# Return the description of the given year as the previous one.
#
sub previous(Int $year --> Str) {

    if $year == 0 {
        return 'the previous year';
    } else {
        return sprintf("your %d%s year", $year, ordinal_suffix($year));
    }

}

# Return the proper wording for `n` persons, using the given or default words
# for singular and plural forms.
#
sub persons(Int $n, Str $singular = 'person', Str $plural = 'people' --> Str) {
    given $n {
        when 0  { return 'nobody'; }
        when 1  { return 'one ' ~ $singular; }
        default { return "$n " ~ $plural; }
    }
}

sub print_annual_report(Int $year) {

    clear_screen;
    set_style($SPEECH_INK);
    put 'Hammurabi, I beg to report to you.';
    set_style($DEFAULT_INK);

    put sprintf(
        "\nIn %s, %s starved and %s %s born.",
        previous($year),
        persons($dead),
        persons($infants, 'infant', 'infants'),
        $infants > 1  ?? 'were'  !! 'was'
    );

    $population += $infants;

    if $year > 0 and rand <= $PLAGUE_CHANCE {
        $population = ($population / 2).Int;
        set_style($WARNING_INK);
        put 'A horrible plague struck!  Half the people died.';
        set_style($DEFAULT_INK);
    }

    put "The population is $population.";
    put "The city owns $acres acres.";
    put "You harvested $bushels_harvested bushels ($bushels_harvested_per_acre per acre).";
    if $bushels_eaten_by_rats > 0 {
        put "The rats ate $bushels_eaten_by_rats bushels.";
    }
    put "You have $bushels_in_store bushels in store.";
    $bushels_harvested_per_acre =
        ($RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * rand).Int +
        $MIN_HARVESTED_BUSHELS_PER_ACRE;
    put "Land is trading at $bushels_harvested_per_acre bushels per acre.\n";

}

sub say_bye {

    set_style($DEFAULT_INK);
    put "\nSo long for now.\n";

}

sub quit_game {

    say_bye;
    exit(0);

}

sub relinquish {
    set_style($SPEECH_INK);
    put "\nHammurabi, I am deeply irritated and cannot serve you anymore.";
    put 'Please, get yourself another steward!';
    set_style($DEFAULT_INK);
    quit_game;
}

sub increase_irritation {
    $irritation += 1 + (0 .. $IRRITATION_STEP).pick;
    if $irritation >= $MAX_IRRITATION { relinquish; } # this never returns
}

sub print_irritated(Str $adverb) {

    printf("The steward seems %s irritated.\n", $adverb);

}

sub show_irritation {
    given True {
        when $irritation < $IRRITATION_STEP      { }
        when $irritation < $IRRITATION_STEP * 2  { print_irritated('slightly'); }
        when $irritation < $IRRITATION_STEP * 3  { print_irritated('quite'); }
        when $irritation < $IRRITATION_STEP * 4  { print_irritated('very'); }
        default { print_irritated('profoundly'); }
    }
}

# Print a message begging to repeat an ununderstandable input.
#
sub beg_repeat {

    increase_irritation; # this may never return
    set_style($SPEECH_INK);
    put 'I beg your pardon?  I did not understand your order.';
    set_style($DEFAULT_INK);
    show_irritation;

}

# Print a message begging to repeat a wrong input, because there's only `n`
# items of `name`.
#
sub beg_think_again(Int $n, Str $name) {

    increase_irritation; # this may never return
    set_style($SPEECH_INK);
    printf("I beg your pardon?  You have only %d %s.  Now then…\n", $n, $name);
    set_style($DEFAULT_INK);
    show_irritation;

}

# Buy or sell land.
#
sub trade {

    my Int $acres_to_buy;
    my Int $acres_to_sell;
    my Bool $ok;

    loop {
        $acres_to_buy = input('How many acres do you wish to buy? (0 to sell): ');
        if $acres_to_buy < 0 {
            beg_repeat; # this may never return
            next;
        }
        if $bushels_harvested_per_acre * $acres_to_buy <= $bushels_in_store { last; }
        beg_think_again($bushels_in_store, 'bushels of grain');
    }

    if $acres_to_buy != 0 {

        printf("You buy %d acres.\n", $acres_to_buy);
        $acres += $acres_to_buy;
        $bushels_in_store -= $bushels_harvested_per_acre * $acres_to_buy;
        printf("You now have %d acres and %d bushels.\n", $acres, $bushels_in_store);

    } else {

        loop {
            $acres_to_sell = input('How many acres do you wish to sell?: ');
            if $acres_to_sell < 0 {
                beg_repeat; # this may never return
                next;
            }
            if $acres_to_sell < $acres { last; }
            beg_think_again($acres, 'acres');
        }

        if $acres_to_sell > 0 {
            put sprintf("You sell %d acres.", $acres_to_sell);
            $acres -= $acres_to_sell;
            $bushels_in_store += $bushels_harvested_per_acre * $acres_to_sell;
            printf("You now have %d acres and %d bushels.\n", $acres, $bushels_in_store);
        }

    }

}

# Feed the people.
#
sub feed {

    my Bool $ok;

    loop {
        $bushels_to_feed_with = input('How many bushels do you wish to feed your people with?: ');
        if $bushels_to_feed_with < 0 {
            beg_repeat; # this may never return
            next;
        }
        # Trying to use more grain than is in silos?
        if $bushels_to_feed_with <= $bushels_in_store { last; }
        beg_think_again($bushels_in_store, 'bushels of grain');
    }

    printf("You feed your people with %d bushels.\n", $bushels_to_feed_with);
    $bushels_in_store -= $bushels_to_feed_with;
    printf("You now have %d bushels.\n", $bushels_in_store);

}

# Seed the land.
#
sub seed {

    my Int $acres_to_seed;
    my Bool $ok;

    loop {

        $acres_to_seed = input('How many acres do you wish to seed?: ');
        if $acres_to_seed < 0 {
            beg_repeat; # this may never return
            next;
        }
        if $acres_to_seed == 0 { last; }

        # Trying to seed more acres than you own?
        if $acres_to_seed > $acres {
            beg_think_again($acres, 'acres');
            next;
        }

        # Enough grain for seed?
        if ($acres_to_seed / $ACRES_A_BUSHEL_CAN_SEED).Int > $bushels_in_store {
            beg_think_again(
                $bushels_in_store,
                sprintf(
                    "bushels of grain,\nand one bushel can seed %d acres",
                    $ACRES_A_BUSHEL_CAN_SEED
                );
            );
            next;
        }

        # Enough people to tend the crops?
        if $acres_to_seed <= $ACRES_A_PERSON_CAN_SEED * $population { last; }

        beg_think_again(
            $population,
            sprintf(
                "people to tend the fields,\nand one person can seed %d acres",
                $ACRES_A_PERSON_CAN_SEED
            );
        );

    }

    my $bushels_used_for_seeding = ($acres_to_seed / $ACRES_A_BUSHEL_CAN_SEED).Int;
    printf("You seed %d acres using %d bushels.\n", $acres_to_seed, $bushels_used_for_seeding);
    $bushels_in_store -= $bushels_used_for_seeding;
    printf("You now have %d bushels.\n", $bushels_in_store);

    # A bountiful harvest!
    $bushels_harvested_per_acre = (1 .. 5).pick;
    $bushels_harvested = $acres_to_seed * $bushels_harvested_per_acre;
    $bushels_in_store += $bushels_harvested;

}

sub is_even(Int $n --> Bool) {

    return $n %% 2 == 0;

}

sub check_rats {

    my $rat_chance = (1 .. 5).pick;
    $bushels_eaten_by_rats = is_even($rat_chance)  ?? ($bushels_in_store / $rat_chance).Int  !! 0;
    $bushels_in_store -= $bushels_eaten_by_rats;

}

# Set the variables to their values in the first year.
#
sub init {
    $dead = 0;
    $total_dead = 0;
    $starved_people_percentage = 0;
    $population = 95;
    $infants = 5;
    $acres = $ACRES_PER_PERSON * ($population + $infants);
    $bushels_harvested_per_acre = 3;
    $bushels_harvested = $acres * $bushels_harvested_per_acre;
    $bushels_eaten_by_rats = 200;
    $bushels_in_store = $bushels_harvested - $bushels_eaten_by_rats;
    $irritation = 0;
}

sub print_result(Result $result) {
    set_style($RESULT_INK);
    given $result {
        when <Very_Good> {
            put 'A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could';
            put 'not have done better!';
        }
        when <Not_Too_Bad> {
            put "Your performance could have been somewat better, but really wasn't too bad at";
            printf(
                "all. %d people would dearly like to see you assassinated, but we all have our\n",
                Int($population * .8 * rand)
            );
            put 'trivial problems.';
        }
        when <Bad> {
            put 'Your heavy-handed performance smacks of Nero and Ivan IV.  The people';
            put '(remaining) find you an unpleasant ruler and, frankly, hate your guts!';
        }
        when <Very_Bad> {
            put 'Due to this extreme mismanagement you have not only been impeached and thrown';
            put 'out of office but you have also been declared national fink!!!';
        }
    }
    set_style($DEFAULT_INK);
}

sub print_final_report {

    clear_screen;

    if $starved_people_percentage > 0 {
        printf(
            "In your %d-year term of office, %d percent of the\n",
            $YEARS,
            $starved_people_percentage
        );
        printf(
            "population starved per year on the average, i.e., a total of %d people died!\n\n",
            $total_dead
        );
    }

    my $acres_per_person = $acres / $population;
    printf(
        "You started with %d acres per person and ended with %d.\n\n",
        $ACRES_PER_PERSON,
        $acres_per_person
    );

    given True {
        when $starved_people_percentage > 33, $acres_per_person < 7  { print_result(Result::<Very_Bad>); }
        when $starved_people_percentage > 10, $acres_per_person < 9  { print_result(Result::<Bad>); }
        when $starved_people_percentage > 3, $acres_per_person < 10  { print_result(Result::<Not_Too_Bad>); }
        default { print_result(Result::<Very_Good>); }
    }

}

sub check_starvation(Int $year) {

        # How many people has been fed?
        my $fed_people = ($bushels_to_feed_with / $BUSHELS_TO_FEED_A_PERSON).Int;

        if $population > $fed_people {

            $dead = $population - $fed_people;
            $starved_people_percentage = floor((($year - 1) * $starved_people_percentage + $dead * 100 / $population) / $year);
            $population -= $dead;
            $total_dead += $dead;

            # Starve enough for impeachment?
            if $dead > (.45 * $population).Int {
                set_style($WARNING_INK);
                put "\nYou starved $dead people in one year!!!\n";
                set_style($DEFAULT_INK);
                print_result(Result::<Very_Bad>);
                quit_game;
            }

        }

}

sub govern {

    init;

    print_annual_report(0);

    for 1 .. $YEARS  -> $year {

        trade;
        feed;
        seed;
        check_rats;

        # Let's have some babies
        $infants = ((1 .. 5).pick * (20 * $acres + $bushels_in_store) / $population / 100 + 1).Int;

        check_starvation($year);

        pause("\nPress the Enter key to read the annual report. ");
        print_annual_report($year);

    }

}

clear_screen;
print_credits;

pause("\nPress the Enter key to read the instructions. ");
clear_screen;
print_instructions;

pause("\nPress the Enter key to start. ");
govern;

pause('Press the Enter key to read the final report. ');
print_final_report;
say_bye;


In V

/*
Hammurabi

Description:
    A simple text-based simulation game set in the ancient kingdom of Sumeria.

Original program:
    Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.

BASIC port:
    Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
    Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.

More details:
    - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
    - https://www.mobygames.com/game/22232/hamurabi/

This improved remake in V:
    Copyright (c) 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2025-01-10.

Last modified: 20250122T0345+0100.

Acknowledgment:
    The following Python port was used as a reference of the original
    variables: <https://github.com/jquast/hamurabi.py>.

*/
import os
import rand
import strconv
import term

// Console {{{1
// =============================================================

const black = 0
const red = 1
const green = 2
const yellow = 3
const blue = 4
const magenta = 5
const cyan = 6
const white = 7
const default_color = 9

const foreground = 30
const background = 40
const bright = 60

// Set the given color.
//
fn set_color(color int) {
    print('\x1B[0;${color}m')
}

// Globals {{{1
// =============================================================

const acres_a_bushel_can_seed = 2 // yearly
const acres_a_person_can_seed = 10 // yearly
const initial_acres_per_person = 10
const bushels_to_feed_a_person = 20 // yearly
const irritation_levels = 5 // after the switch in `show_irritation`
const irritation_step = max_irritation / irritation_levels
const max_harvested_bushels_per_acre = min_harvested_bushels_per_acre +
    range_of_harvested_bushels_per_acre - 1
const min_harvested_bushels_per_acre = 17
const max_irritation = 16
const plague_chance = 0.15 // 15% yearly
const range_of_harvested_bushels_per_acre = 10
const years = 10 // goverment period

const default_ink = foreground + default_color
const input_ink = foreground + bright + green
const instructions_ink = foreground + yellow
const result_ink = foreground + bright + cyan
const speech_ink = foreground + bright + magenta
const title_ink = foreground + red
const warning_ink = foreground + bright + red

enum Result {
    very_good
    not_too_bad
    bad
    very_bad
}

struct Status {
mut:
    acres                      int
    bushels_eaten_by_rats      int
    bushels_harvested          int
    bushels_harvested_per_acre int
    bushels_in_store           int
    bushels_to_feed_with       int
    dead                       int
    infants                    int
    irritation                 int // counter (0 ... 99)
    population                 int
    starved_people_percentage  int
    total_dead                 int
}

// Return the proper wording for `n` persons, using the given or default words
// for singular and plural forms.
//
fn persons(n int, singular string, plural string) string {
    match n {
        0 {
            return 'nobody'
        }
        1 {
            return unsafe { strconv.v_sprintf('one ${singular}') }
        }
        else {
            return unsafe { strconv.v_sprintf('${n} ${plural}') }
        }
    }
}

// User input {{{1
// =============================================================

// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
    set_color(input_ink)
    defer { set_color(default_ink) }
    return os.input(prompt)
}

fn pause(prompt string) {
    input_string(prompt)
}

// Print the given prompt, wait until the user enters a string and return it
// parsed as an `int`; return also an ok flag which is `false` if no
// appropriate value could be found.
//
fn input_number(prompt string) (int, bool) {
    set_color(input_ink)
    defer { set_color(default_ink) }
    mut n := 0
    for {
        n = strconv.atoi(input_string(prompt)) or { return n, false }
        break
    }
    return n, true
}

// Title, credits and instructions {{{1
// =============================================================

fn print_title() {
    set_color(title_ink)
    defer { set_color(default_ink) }
    println('Hammurabi')
}

fn print_credits() {
    print_title()

    println('\nOriginal program:')
    println('  Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.')

    println('\nBASIC port:')
    println('  Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.')
    println('  Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.')

    println('\nThis improved remake in V:')
    println('  Copyright (c) 2025, Marcos Cruz (programandala.net)')
    println('  SPDX-License-Identifier: Fair')
}

fn print_instructions() {
    set_color(instructions_ink)
    defer { set_color(default_ink) }

    println('Hammurabi is a simulation game in which you, as the ruler of the ancient')
    println('kingdom of Sumeria, Hammurabi, manage the resources.')

    println('\nYou may buy and sell land with your neighboring city-states for bushels of')
    println('grain ― the price will vary between ${min_harvested_bushels_per_acre} and ${max_harvested_bushels_per_acre} bushels per acre.  You also must')
    println("use grain to feed your people and as seed to plant the next year's crop.")

    println('\nYou will quickly find that a certain number of people can only tend a certain')
    println('amount of land and that people starve if they are not fed enough.  You also')
    println('have the unexpected to contend with such as a plague, rats destroying stored')
    println('grain, and variable harvests.')

    println('\nYou will also find that managing just the few resources in this game is not a')
    println('trivial job.  The crisis of population density rears its head very rapidly.')
    println('\nTry your hand at governing ancient Sumeria for a ${years}-year term of office.')
}

// Stock functions {{{1
// =============================================================

// Return a random number from 1 to 5 (inclusive).
//
fn random_1_to_5() int {
    return rand.intn(5) or { 0 } + 1
}

fn ordinal_suffix(n int) string {
    match n {
        1 {
            return 'st'
        }
        2 {
            return 'nd'
        }
        3 {
            return 'rd'
        }
        else {
            return 'th'
        }
    }
}

fn is_even(n int) bool {
    return n % 2 == 0
}

// Game {{{1
// =============================================================

// Return the description of the given year as the previous one.
//
fn previous(year int) string {
    if year == 0 {
        return 'the previous year'
    } else {
        return unsafe { strconv.v_sprintf('your ${year}${ordinal_suffix(year)} year') }
    }
}

fn print_annual_report(year int, mut status Status) {
    term.clear()
    set_color(speech_ink)
    println('Hammurabi, I beg to report to you.')
    set_color(default_ink)

    print('\nIn ${previous(year)}, ${persons(status.dead, 'person', 'people')} starved and ${persons(status.infants,
        'infant', 'infants')} ${if status.infants > 1 { 'were' } else { 'was' }} born.\n')

    status.population += status.infants

    if year > 0 && rand.f64() <= plague_chance {
        status.population = int(status.population / 2)
        set_color(warning_ink)
        println('A horrible plague struck!  Half the people died.')
        set_color(default_ink)
    }

    println('The population is ${status.population}.')
    println('The city owns ${status.acres} acres.')
    print('You harvested ${status.bushels_harvested} bushels (${status.bushels_harvested_per_acre} per acre).\n')
    if status.bushels_eaten_by_rats > 0 {
        println('The rats ate ${status.bushels_eaten_by_rats} bushels.')
    }
    println('You have ${status.bushels_in_store} bushels in store.')
    status.bushels_harvested_per_acre = int(range_of_harvested_bushels_per_acre * rand.f64()) +
        min_harvested_bushels_per_acre
    println('Land is trading at ${status.bushels_harvested_per_acre} bushels per acre.\n')
}

fn say_bye() {
    set_color(default_ink)
    println('\nSo long for now.\n')
}

fn quit_game() {
    say_bye()
    exit(0)
}

fn relinquish() {
    set_color(speech_ink)
    println('\nHammurabi, I am deeply irritated and cannot serve you anymore.')
    println('Please, get yourself another steward!')
    set_color(default_ink)
    quit_game()
}

fn increase_irritation(mut status Status) {
    status.irritation += 1 + rand.intn(irritation_step) or { 0 }
    if status.irritation >= max_irritation {
        relinquish()
    }
    // this never returns
}

fn print_irritated(adverb string) {
    println('The steward seems ${adverb} irritated.')
}

fn show_irritation(status Status) {
    match true {
        status.irritation < irritation_step {}
        status.irritation < irritation_step * 2 {
            print_irritated('slightly')
        }
        status.irritation < irritation_step * 3 {
            print_irritated('quite')
        }
        status.irritation < irritation_step * 4 {
            print_irritated('very')
        }
        else {
            print_irritated('profoundly')
        }
    }
}

// Print a message begging to repeat an ununderstandable input.
//
fn beg_repeat(mut status Status) {
    increase_irritation(mut status) // this may never return
    set_color(speech_ink)
    println('I beg your pardon?  I did not understand your order.')
    set_color(default_ink)
    show_irritation(status)
}

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
fn beg_think_again(n int, name string, mut status Status) {
    increase_irritation(mut status) // this may never return
    set_color(speech_ink)
    println('I beg your pardon?  You have only ${n} ${name}.  Now then…')
    set_color(default_ink)
    show_irritation(status)
}

// Buy or sell land.
//
fn trade(mut status Status) {
    mut acres_to_buy := 0
    mut acres_to_sell := 0
    mut ok := false

    for {
        acres_to_buy, ok = input_number('How many acres do you wish to buy? (0 to sell): ')
        if !ok || acres_to_buy < 0 {
            beg_repeat(mut status) // this may never return
            continue
        }
        if status.bushels_harvested_per_acre * acres_to_buy <= status.bushels_in_store {
            break
        }
        beg_think_again(status.bushels_in_store, 'bushels of grain', mut status)
    }

    if acres_to_buy != 0 {
        println('You buy ${acres_to_buy} acres.')
        status.acres += acres_to_buy
        status.bushels_in_store -= status.bushels_harvested_per_acre * acres_to_buy
        println('You now have ${status.acres} acres and ${status.bushels_in_store} bushels.')
    } else {
        for {
            acres_to_sell, ok = input_number('How many acres do you wish to sell?: ')
            if !ok || acres_to_sell < 0 {
                beg_repeat(mut status) // this may never return
                continue
            }
            if acres_to_sell < status.acres {
                break
            }
            beg_think_again(status.acres, 'acres', mut status)
        }

        if acres_to_sell > 0 {
            println('You sell ${acres_to_sell} acres.')
            status.acres -= acres_to_sell
            status.bushels_in_store += status.bushels_harvested_per_acre * acres_to_sell
            println('You now have ${status.acres} acres and ${status.bushels_in_store} bushels.')
        }
    }
}

// Feed the people.
//
fn feed(mut status Status) {
    mut ok := false

    for {
        status.bushels_to_feed_with, ok = input_number('How many bushels do you wish to feed your people with?: ')
        if !ok || status.bushels_to_feed_with < 0 {
            beg_repeat(mut status) // this may never return
            continue
        }
        // Trying to use more grain than is in silos?
        if status.bushels_to_feed_with <= status.bushels_in_store {
            break
        }
        beg_think_again(status.bushels_in_store, 'bushels of grain', mut status)
    }

    println('You feed your people with ${status.bushels_to_feed_with} bushels.')
    status.bushels_in_store -= status.bushels_to_feed_with
    println('You now have ${status.bushels_in_store} bushels.')
}

// Seed the land.
//
fn seed(mut status Status) {
    mut acres_to_seed := 0
    mut ok := false

    for {
        acres_to_seed, ok = input_number('How many acres do you wish to seed?: ')
        if !ok || acres_to_seed < 0 {
            beg_repeat(mut status) // this may never return
            continue
        }
        if acres_to_seed == 0 {
            break
        }

        // Trying to seed more acres than you own?
        if acres_to_seed > status.acres {
            beg_think_again(status.acres, 'acres', mut status)
            continue
        }

        // Enough grain for seed?
        if int(acres_to_seed / acres_a_bushel_can_seed) > status.bushels_in_store {
            beg_think_again(status.bushels_in_store, unsafe { strconv.v_sprintf('bushels of grain,\nand one bushel can seed ${acres_a_bushel_can_seed} acres') }, mut
                status)
            continue
        }

        // Enough people to tend the crops?
        if acres_to_seed <= acres_a_person_can_seed * status.population {
            break
        }

        beg_think_again(status.population, unsafe { strconv.v_sprintf('people to tend the fields,\nand one person can seed ${acres_a_person_can_seed} acres') }, mut
            status)
    }

    bushels_used_for_seeding := int(acres_to_seed / acres_a_bushel_can_seed)
    println('You seed ${acres_to_seed} acres using ${bushels_used_for_seeding} bushels.')
    status.bushels_in_store -= bushels_used_for_seeding
    println('You now have ${status.bushels_in_store} bushels.')

    // A bountiful harvest!
    status.bushels_harvested_per_acre = random_1_to_5()
    status.bushels_harvested = acres_to_seed * status.bushels_harvested_per_acre
    status.bushels_in_store += status.bushels_harvested
}

fn check_rats(mut status Status) {
    rat_chance := random_1_to_5()
    status.bushels_eaten_by_rats = if is_even(rat_chance) {
        int(status.bushels_in_store / rat_chance)
    } else {
        0
    }
    status.bushels_in_store -= status.bushels_eaten_by_rats
}

fn print_result(result Result, status Status) {
    set_color(result_ink)

    match result {
        .very_good {
            println('A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could')
            println('not have done better!')
        }
        .not_too_bad {
            println("Your performance could have been somewat better, but really wasn't too bad at")
            print('all. ${int(f64(status.population) * .8 * rand.f64())} people would dearly like to see you assassinated, but we all have our\n')
            println('trivial problems.')
        }
        .bad {
            println('Your heavy-handed performance smacks of Nero and Ivan IV.  The people')
            println('(remaining) find you an unpleasant ruler and, frankly, hate your guts!')
        }
        .very_bad {
            println('Due to this extreme mismanagement you have not only been impeached and thrown')
            println('out of office but you have also been declared national fink!!!')
        }
    }

    set_color(default_ink)
}

fn print_final_report(status Status) {
    term.clear()

    if status.starved_people_percentage > 0 {
        print('In your ${years}-year term of office, ${status.starved_people_percentage} percent of the\n')
        print('population starved per year on the average, i.e., a total of ${status.total_dead} people died!\n\n')
    }

    acres_per_person := status.acres / status.population
    print('You started with ${acres_per_person} acres per person and ended with ${acres_per_person}.\n\n')

    match true {
        status.starved_people_percentage > 33, acres_per_person < 7 {
            print_result(.very_bad, status)
        }
        status.starved_people_percentage > 10, acres_per_person < 9 {
            print_result(.bad, status)
        }
        status.starved_people_percentage > 3, acres_per_person < 10 {
            print_result(.not_too_bad, status)
        }
        else {
            print_result(.very_good, status)
        }
    }
}

fn check_starvation(year int, mut status Status) {
    // How many people has been fed?
    fed_people := int(status.bushels_to_feed_with / bushels_to_feed_a_person)

    if status.population > fed_people {
        status.dead = status.population - fed_people
        status.starved_people_percentage = ((year - 1) * status.starved_people_percentage +
            status.dead * 100 / status.population) / year
        status.population -= status.dead
        status.total_dead += status.dead

        // Starve enough for impeachment?
        if status.dead > int(.45 * f64(status.population)) {
            set_color(warning_ink)
            println('\nYou starved ${status.dead} people in one year!!!\n')
            set_color(default_ink)
            print_result(.very_bad, status)
            quit_game()
        }
    }
}

fn govern() {
    mut status := Status{
        dead:                       0
        total_dead:                 0
        starved_people_percentage:  0
        population:                 95
        infants:                    5
        bushels_harvested_per_acre: 3
        bushels_eaten_by_rats:      200
        irritation:                 0
    }

    status.acres = initial_acres_per_person * (status.population + status.infants)
    status.bushels_harvested = status.acres * status.bushels_harvested_per_acre
    status.bushels_in_store = status.bushels_harvested - status.bushels_eaten_by_rats

    print_annual_report(0, mut status)

    for year in 1 .. years + 1 {
        trade(mut status)
        feed(mut status)
        seed(mut status)
        check_rats(mut status)

        // Let's have some babies
        status.infants = int(random_1_to_5() * (20 * status.acres +
            status.bushels_in_store) / status.population / 100 + 1)

        check_starvation(year, mut status)

        pause('\nPress the Enter key to read the annual report. ')
        print_annual_report(year, mut status)
    }

    pause('Press the Enter key to read the final report. ')
    print_final_report(status)
}

// Main {{{1
// =============================================================

fn main() {
    term.clear()
    print_credits()

    pause('\nPress the Enter key to read the instructions. ')
    term.clear()
    print_instructions()

    pause('\nPress the Enter key to start. ')
    govern()

    say_bye()
}

Págines relatet

Basics off
Metaprojecte pri li projectes «Basics of…».

Extern ligamentes relatet