Basics of C#

Description of the page content

Conversion of old BASIC programs to C# in order to learn the basics of this language.

Tags:

3D Plot

// 3D Plot

// Original version in BASIC, "3D Plot":
//  3-D Plot (by Mark Bramhall), 1978.
//  Creative Computing's BASIC Games.
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/3dplot.bas
//  - https://www.atariarchives.org/basicgames/showpage.php?page=167

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-20.
//
// Last modified: 20251205T1528+0100.

using System;

class D3Plot
{
    static void PrintCredits()
    {
        Console.WriteLine("3D Plot\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        Console.Write("Press any key to start. ");
        Console.ReadKey();
    }

    static double A(double z)
    {
        return 30 * Math.Exp(-z * z / 100);
    }

    static void Draw()
    {
        const int WIDTH = 56;
        const char SPACE = ' ';
        const char DOT = '*';

        int l = 0;
        int z = 0;
        int y = 0;
        int y1 = 0;
        char[] line = new char[WIDTH];

        double x = -30.0;
        while (x <= 30.0)
        {
            for (int i = 0; i < WIDTH; i++)
            {
                line[i] = SPACE;
            }

            l = 0;
            y1 = 5 * (int)(Math.Sqrt(900 - x * x) / 5);

            y = y1;
            while (y >= -y1)
            {
                z = (int)(25 + A(Math.Sqrt(x * x + (double)(y * y))) - 0.7 * y);
                if (z > l)
                {
                    l = z;
                    line[z] = DOT;
                };
                y += -5;
            } // y loop

            foreach (char c in line)
            {
                Console.Write(c);
            }
            Console.WriteLine();

            x += 1.5;
        } // x loop
    }

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

Bagels

// Bagels

// Original version in BASIC:
//  By D. Resek, P. Rowe, 1978.
//  Creative Computing's BASIC Games.
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/bagels.bas
//  - http://www.atariarchives.org/basicgames/showpage.php?page=9

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-25.
//
// Last modified: 20251205T1530+0100.

using System;
using System.Linq;

class bagels
{
    // Clear the screen, display the credits and wait for a keypress.
    //
    static void PrintCredits()
    {
        Console.Clear();
        Console.WriteLine("Bagels");
        Console.WriteLine("Number guessing game\n");
        Console.WriteLine("Original source unknown but suspected to be:");
        Console.WriteLine("    Lawrence Hall of Science, U.C. Berkely.\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    D. Resek, P. Rowe, 1978.");
        Console.WriteLine("    Creative computing (Morristown, New Jersey, USA), 1978.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        Console.Write("Press Enter to read the instructions. ");
        Console.ReadKey();
    }

    // Clear the screen, print the instructions and wait for a keypress.
    //
    static void PrintInstructions()
    {
        Console.Clear();
        Console.WriteLine("Bagels");
        Console.WriteLine("Number guessing game\n");
        Console.WriteLine("I am thinking of a three-digit number that has no two digits the same.");
        Console.WriteLine("Try to guess it and I will give you clues as follows:\n");
        Console.WriteLine("   PICO   - one digit correct but in the wrong position");
        Console.WriteLine("   FERMI  - one digit correct and in the right position");
        Console.WriteLine("   BAGELS - no digits correct");
        Console.Write("\nPress Enter to start. ");
        Console.ReadKey();
    }

    const int DIGITS = 3;

    static int[] randomDigit = new int[DIGITS];

    // Return three random digits.
    //
    static int[] RandomDigits()
    {
        Random rand = new Random();

        for (int i = 0; i < DIGITS; i++)
        {
            while (true)
            {
                digitLoop:
                randomDigit[i] = rand.Next(10);
                for (int j = 0; j < i; j++)
                {
                    if (i != j && randomDigit[i] == randomDigit[j])
                    {
                        goto digitLoop;
                    }
                }
                break;
            }
        }
        return randomDigit;
    }

    // Return `true` if any of the elements of the given array, which contains
    // only numbers in inclusive range 0..9, is repeated; otherwise return
    // `false`.
    //
    static bool IsAnyRepeated(int[] num)
    {
        bool[] present = new bool[10];
        foreach (int n in num)
        {
            if (present[n])
                return true;
            else
                present[n] = true;
        }
        return false;
    }

    // Print the given prompt and update the array whose address is given with a
    // three-digit number from the user.
    //
    static void GetInput(string prompt, ref int[] userDigit)
    {
        // XXX TODO Create a local array, appending every digit after checking
        // the contents, making `IsAnyRepeated` unnecessary.

        while (true)
        {
            getLoop:
            Console.Write(prompt);
            string input = Console.ReadLine();
            if (input.Length != DIGITS)
            {
                Console.WriteLine($"Remember it's a {DIGITS}-digit number.");
                continue;
            }
            char digit;
            for (int i = 0; i < input.Length; i++)
            {
                digit = input[i];
                if (Char.IsDigit(digit))
                {
                    userDigit[i] = ((int) digit) - ((int) '0');
                }
                else
                {
                    Console.WriteLine("What?");
                    goto getLoop;
                }
            }
            if (IsAnyRepeated(userDigit))
            {
                Console.WriteLine("Remember my number has no two digits the same.");
                continue;
            }
            break;
        }
    }

    // Return `true` if the given string is "yes" or a synonym.
    //
    static bool IsYes(string s)
    {
        string[] validOptions = {"ok", "y", "yeah", "yes"};
        return validOptions.Contains(s.ToLower());
    }

    // Return `true` if the given string is "no" or a synonym.
    //
    static bool IsNo(string s)
    {
        string[] validOptions = {"n", "no", "nope"};
        return validOptions.Contains(s.ToLower());
    }

    // Print the given prompt, wait until the user enters a valid yes/no string,
    // and return `true` for "yes" or `false` for "no".
    //
    static bool Yes(string prompt)
    {
        while (true)
        {
            Console.Write(prompt);
            string answer = Console.ReadLine();
            if (IsYes(answer))
            {
                return true;
            }
            if (IsNo(answer))
            {
                return false;
            }
        }
    }

    static void Play()
    {
        const int TRIES = 20;
        int score = 0;
        int fermi = 0; // counter
        int pico = 0; // counter
        int[] userNumber = new int[DIGITS];
        while (true)
        {
            Console.Clear();
            int[] computerNumber = RandomDigits();
            Console.WriteLine("O.K.  I have a number in mind.");
            for (int guess = 1; guess <= TRIES; guess++)
            {
                GetInput($"Guess #{guess}: ", ref userNumber);
                fermi = 0;
                pico = 0;
                for (int i = 0; i < DIGITS; i++)
                {
                    for (int j = 0; j < DIGITS; j++)
                    {
                        if (userNumber[i] == computerNumber[j])
                        {
                            if (i == j)
                            {
                                fermi += 1;
                            }
                            else
                            {
                                pico += 1;
                            }
                        }
                    }
                }
                for (int i = 0; i < pico; i++)
                    Console.Write("PICO ");
                for (int i = 0; i < fermi; i++)
                    Console.Write("FERMI ");
                if (pico + fermi == 0)
                    Console.Write("BAGELS");
                Console.WriteLine();
                if (fermi == DIGITS)
                {
                    break;
                }
            }
            if (fermi == DIGITS)
            {
                Console.WriteLine("You got it!!!");
                score += 1;
            }
            else
            {
                Console.WriteLine("Oh well.");
                Console.Write($"That's {TRIES} guesses.  My number was ");
                for (int i = 0; i < DIGITS; i++)
                {
                    Console.Write(computerNumber[i]);
                }
                Console.WriteLine(".");
            }
            if (!Yes("Play again? "))
            {
                break;
            }
        }
        if (score != 0)
        {
            Console.WriteLine($"A {score}-point bagels, buff!!");
        }
        Console.WriteLine("Hope you had fun.  Bye.");
    }

    static void Main()
    {
        PrintCredits();
        PrintInstructions();
        Play();
    }
}

Bug

// Bug

// Original version in BASIC:
//  Brian Leibowitz, 1978.
//  Creative Computing's BASIC Games.
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/bug.bas
//  - https://www.atariarchives.org/basicgames/showpage.php?page=30
//  - http://www.retroarchive.org/cpm/games/ccgames.zip

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written in 2024-12-20/21.
//
// Last modified: 20251205T1532+0100.

using System;

class BugGame
{
    class ABug
    {
        internal bool body = false;
        internal bool neck = false;
        internal bool head = false;
        internal int feelers = 0;
        internal char feelerType = ' ';
        internal bool tail = false;
        internal int legs = 0;
    }

    class Player
    {
        internal string pronoun = "";
        internal string possessive = "";
        internal ABug bug = new ABug();
    }

    static Player computer = new Player();
    static Player human = new Player();

    enum Part {body = 1, neck, head, feeler, tail, leg};
    static int parts = Enum.GetNames(typeof(Part)).Length;

    // Bug body attributes.
    //
    const int BODY_HEIGHT = 2;
    const int FEELER_LENGTH = 4;
    const int LEG_LENGTH = 2;
    const int MAX_FEELERS = 2;
    const int MAX_LEGS = 6;
    const int NECK_LENGTH = 2;

    static void PrintCredits()
    {
        Console.Clear();
        Console.WriteLine("Bug\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Brian Leibowitz, 1978.");
        Console.WriteLine("    Creative computing (Morristown, New Jersey, USA), 1978.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        Console.Write("Press Enter to read the instructions. ");
        Console.ReadKey();
    }

    // Return the given string with its first character in uppercase and the
    // rest in lowercase.
    //
    static string Capitalized(string str)
    {
        if (string.IsNullOrEmpty(str))
        {
            return string.Empty;
        }
        else
        {
            return char.ToUpper(str[0]) + str.Substring(1).ToLower();
        }
    }

    // Write the given string the given number of times.
    //
    static void WriteTimes(string s, int n)
    {
        for (int t = 0; t < n; t++)
        {
            Console.Write(s);
        }
    }

    // Print a table with the description of the bug parts.
    //
    static void PrintPartsTable()
    {
        const int COLUMNS = 3;
        const int COLUMN_WIDTH = 8;
        const int COLUMN_SEPARATION = 2;

        // Headers

        string[] header = {"Number", "Part", "Quantity"};
        for (int i = 0; i < COLUMNS; i++)
        {
            Console.Write(header[i].PadRight(COLUMN_WIDTH + COLUMN_SEPARATION));
        }
        Console.WriteLine();

        // Rulers

        for (int i = 0; i < COLUMNS; i++)
        {
            WriteTimes("-", COLUMN_WIDTH);
            if (i != COLUMNS - 1)
            {
                WriteTimes(" ", COLUMN_SEPARATION);
            }
        }
        Console.WriteLine();

        // Data

        int[] partQuantity = new int[parts + 1];

        partQuantity[(int) Part.body] = 1;
        partQuantity[(int) Part.neck] = 1;
        partQuantity[(int) Part.head] = 1;
        partQuantity[(int) Part.feeler] = 2;
        partQuantity[(int) Part.tail] = 1;
        partQuantity[(int) Part.leg] = 6;

        for (Part p = Part.body; (int) p < parts; p++)
        {
            Console.Write($"{(int) p}".PadRight(COLUMN_WIDTH + COLUMN_SEPARATION));
            Console.Write(Capitalized(Enum.GetName(typeof(Part), p).PadRight(COLUMN_WIDTH + COLUMN_SEPARATION)));
            Console.WriteLine(partQuantity[(int) p]);
        }
    }

    static void PrintInstructions()
    {
        Console.Clear();
        Console.WriteLine("Bug\n");
        Console.WriteLine("The object is to finish your bug before I finish mine. Each number");
        Console.WriteLine("stands for a part of the bug body.\n");
        Console.WriteLine("I will roll the die for you, tell you what I rolled for you, what the");
        Console.WriteLine("number stands for, and if you can get the part. If you can get the");
        Console.WriteLine("part I will give it to you. The same will happen on my turn.\n");
        Console.WriteLine("If there is a change in either bug I will give you the option of");
        Console.WriteLine("seeing the pictures of the bugs. The numbers stand for parts as");
        Console.WriteLine("follows:\n");
        PrintPartsTable();
        Console.Write("\nPress Enter to start. ");
        Console.ReadKey();
    }

    // Print the given bug.
    //
    static void PrintBug(ABug bug)
    {
        if (bug.feelers > 0)
        {
            for (int i = 0; i < FEELER_LENGTH; i++)
            {
                Console.Write("        ");
                for (int j = 0; j < bug.feelers; j++)
                {
                    Console.Write($" {bug.feelerType}");
                }
                Console.WriteLine();
            }
        }
        if (bug.head)
        {
            Console.WriteLine("        HHHHHHH");
            Console.WriteLine("        H     H");
            Console.WriteLine("        H O O H");
            Console.WriteLine("        H     H");
            Console.WriteLine("        H  V  H");
            Console.WriteLine("        HHHHHHH");
        }
        if (bug.neck)
        {
            for (int i = 0; i < NECK_LENGTH; i++)
            {
                Console.WriteLine("          N N");
            }
        }
        if (bug.body)
        {
            Console.WriteLine("     BBBBBBBBBBBB");
            for (int i = 0; i < BODY_HEIGHT; i++)
            {
                Console.WriteLine("     B          B");
            }
            if (bug.tail)
            {
                Console.WriteLine("TTTTTB          B");
            }
            Console.WriteLine("     BBBBBBBBBBBB");
        }
        if (bug.legs > 0)
        {
            for (int i = 0; i < LEG_LENGTH; i++)
            {
                Console.Write("    ");
                for (int j = 0; j < bug.legs; j++)
                {
                    Console.Write(" L");
                }
                Console.WriteLine();
            }
        }
    }

    // Return `true` if the given bug is finished; otherwise return `false`.
    //
    static bool Finished(ABug bug)
    {
        return bug.feelers == MAX_FEELERS && bug.tail && bug.legs == MAX_LEGS;
    }

    // Return a random number from 1 to 6 (inclusive).
    //
    static int Dice()
    {
        Random rand = new Random();
        return rand.Next(6) + 1;
    }

    // Array to convert a number to its equilavent text.
    //
    static string[] asText =
    {
        "no",
        "a",
        "two",
        "three",
        "four",
        "five",
        "six" }; // MAX_LEGS

    // Return a string containing the given number and noun in their proper
    // form.
    //
    static string Plural(int number, string noun)
    {
        return $"{asText[number]} {noun}{((number > 1) ? "s" : "")}";
    }

    // Add the given part to the given player's bug.
    //
    static bool AddPart(Part part, Player player)
    {
        bool changed = false;
        switch (part)
        {
        case Part.body:
            if (player.bug.body)
            {
                Console.WriteLine($", but {player.pronoun} already have a body.");
            }
            else
            {
                Console.WriteLine($"; {player.pronoun} now have a body:");
                player.bug.body = true;
                changed = true;
            }
            break;
        case Part.neck:
            if (player.bug.neck)
            {
                Console.WriteLine($", but {player.pronoun} already have a neck.");
            }
            else if (!player.bug.body)
            {
                Console.WriteLine($", but {player.pronoun} need a body first.");
            }
            else
            {
                Console.WriteLine($"; {player.pronoun} now have a neck:");
                player.bug.neck = true;
                changed = true;
            }
            break;
        case Part.head:
            if (player.bug.head)
            {
                Console.WriteLine($", but {player.pronoun} already have a head.");
            }
            else if (!player.bug.neck)
            {
                Console.WriteLine($", but {player.pronoun} need a a neck first.");
            }
            else
            {
                Console.WriteLine($"; {player.pronoun} now have a head:");
                player.bug.head = true;
                changed = true;
            }
            break;
        case Part.feeler:
            if (player.bug.feelers == MAX_FEELERS)
            {
                Console.WriteLine($", but {player.pronoun} have two feelers already.");
            }
            else if (!player.bug.head)
            {
                Console.WriteLine($", but {player.pronoun} need a head first.");
            }
            else
            {
                player.bug.feelers += 1;
                Console.Write($"; {player.pronoun} now have",
                    Plural(player.bug.feelers, "feeler"));
                Console.WriteLine(":");
                changed = true;
            }
            break;
        case Part.tail:
            if (player.bug.tail)
            {
                Console.WriteLine($", but {player.pronoun} already have a tail.");
            }
            else if (!player.bug.body)
            {
                Console.WriteLine($", but {player.pronoun} need a body first.");
            }
            else
            {
                Console.WriteLine($"; {player.pronoun} now have a tail:");
                player.bug.tail = true;
                changed = true;
            }
            break;
        case Part.leg:
            if (player.bug.legs == MAX_LEGS)
            {
                Console.WriteLine($", but {player.pronoun} have {asText[MAX_LEGS]} feet already.");
            }
            else if (!player.bug.body)
            {
                Console.WriteLine($", but {player.pronoun} need a body first.");
            }
            else
            {
                player.bug.legs += 1;
                Console.Write($"; {player.pronoun} now have {Plural(player.bug.legs, "leg")}");
                Console.WriteLine(":");
                changed = true;
            }
            break;
        }
        return changed;
    }

    // Ask the user to press the Enter key, wait for the input, then erase the;
    // prompt text.
    //
    static void Prompt()
    {
        Console.Write("Press Enter to roll the dice. ");
        Console.ReadKey();
    }

    // Play one turn for the given player, rolling the dice and updating his bug.
    //
    static void Turn(Player player)
    {
        Prompt();
        int number = Dice();
        Part part = (Part) number;
        Console.Write($"{Capitalized(player.pronoun)} rolled a {number} ({part})");
        if (AddPart(part, player))
        {
            Console.WriteLine();
            PrintBug(player.bug);
        }
        Console.WriteLine();
    }

    // Print a message about the winner.
    //
    static void PrintWinner()
    {
        if (Finished(human.bug) && Finished(computer.bug))
        {
            Console.WriteLine("Both of our bugs are finished in the same number of turns!");
        }
        else if (Finished(human.bug))
        {
            Console.WriteLine($"{human.possessive} bug is finished.");
        }
        else if (Finished(computer.bug))
        {
            Console.WriteLine($"{computer.possessive} bug is finished.");
        }
    }

    static bool GameOver()
    {
        return Finished(human.bug) || Finished(computer.bug);
    }

    // Execute the game loop.
    //
    static void Play()
    {
        Console.Clear();
        while (!GameOver())
        {
            Turn(human);
            Turn(computer);
        }
        PrintWinner();
    }

    // Init the players' data before a new game.
    //
    static void Init()
    {
        human.pronoun = "you";
        human.possessive = "Your";
        human.bug.feelerType = 'A';
        computer.pronoun = "I";
        computer.possessive = "My";
        computer.bug.feelerType = 'F';
    }

    static void Main()
    {
        Init();
        PrintCredits();
        PrintInstructions();
        Play();
        Console.WriteLine("I hope you enjoyed the game, play it again soon!!");
    }
}

Bunny

// Bunny

// Original version in BASIC:
//  Anonymous, 1978.
//  Creative Computing's BASIC Games.
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/bunny.bas
//  - http://www.retroarchive.org/cpm/games/ccgames.zip

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-20.
//
// Last modified: 20251205T1532+0100.

using System;

class Bunny
{
    static void PrintCredits()
    {
        Console.WriteLine("Bunny\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Creative Computing (Morristown, New Jersey, USA), 1978.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        Console.Write("Press Enter to start. ");
        Console.ReadKey();
    }

    const int WIDTH = 53;
    static char[] line = new char[WIDTH]; // buffer

    static void clearLine()
    {
        for (int c = 0; c < WIDTH; c++)
        {
            line[c] = ' ';
        }
    }

    static char[] letter = {'B', 'U', 'N', 'N', 'Y'};

    const int EOL = -1; // end of line identifier

    static int[] data =
    {
        1, 2, EOL, 0, 2, 45, 50, EOL, 0, 5, 43, 52, EOL, 0, 7, 41, 52, EOL,
        1, 9, 37, 50, EOL, 2, 11, 36, 50, EOL, 3, 13, 34, 49, EOL, 4, 14,
        32, 48, EOL, 5, 15, 31, 47, EOL, 6, 16, 30, 45, EOL, 7, 17, 29, 44,
        EOL, 8, 19, 28, 43, EOL, 9, 20, 27, 41, EOL, 10, 21, 26, 40, EOL,
        11, 22, 25, 38, EOL, 12, 22, 24, 36, EOL, 13, 34, EOL, 14, 33, EOL,
        15, 31, EOL, 17, 29, EOL, 18, 27, EOL, 19, 26, EOL, 16, 28, EOL,
        13, 30, EOL, 11, 31, EOL, 10, 32, EOL, 8, 33, EOL, 7, 34, EOL, 6,
        13, 16, 34, EOL, 5, 12, 16, 35, EOL, 4, 12, 16, 35, EOL, 3, 12, 15,
        35, EOL, 2, 35, EOL, 1, 35, EOL, 2, 34, EOL, 3, 34, EOL, 4, 33,
        EOL, 6, 33, EOL, 10, 32, 34, 34, EOL, 14, 17, 19, 25, 28, 31, 35,
        35, EOL, 15, 19, 23, 30, 36, 36, EOL, 14, 18, 21, 21, 24, 30, 37, 37,
        EOL, 13, 18, 23, 29, 33, 38, EOL, 12, 29, 31, 33, EOL, 11, 13, 17,
        17, 19, 19, 22, 22, 24, 31, EOL, 10, 11, 17, 18, 22, 22, 24, 24, 29,
        29, EOL, 22, 23, 26, 29, EOL, 27, 29, EOL, 28, 29, EOL };

    static int dataIndex = 0;

    static int datum()
    {
        dataIndex += 1;
        return data[dataIndex - 1];
    }

    static void Draw()
    {
        clearLine();
        while (dataIndex < data.Length)
        {
            int firstColumn = datum();
            if (firstColumn == EOL)
            {
                Console.WriteLine(line);
                clearLine();
            }
            else
            {
                int lastColumn = datum();
                for (int c = firstColumn; c <= lastColumn; c++)
                {
                    line[c] = letter[c % letter.Length];
                }
            }
        }
    }

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

Chase

// Chase

// Original version in BASIC:
//  Anonymous.
//  Published in 1977 in "The Best of Creative Computing", Volume 2:
//  https://www.atariarchives.org/bcc2/showpage.php?page=253

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-01-01.
//
// Last modified 20251205T1534+0100.

using System;
using System.Linq;

class Chase
{
    // Globals {{{1
    // =============================================================

    // Colors

    const ConsoleColor DEFAULT_INK = ConsoleColor.White;
    const ConsoleColor INPUT_INK = ConsoleColor.Green;
    const ConsoleColor INSTRUCTIONS_INK = ConsoleColor.DarkYellow;
    const ConsoleColor TITLE_INK = ConsoleColor.Red;

    // Arena

    const int ARENA_WIDTH = 20;
    const int ARENA_HEIGHT = 10;
    const int ARENA_LAST_X = ARENA_WIDTH - 1;
    const int ARENA_LAST_Y = ARENA_HEIGHT - 1;
    const int ARENA_ROW = 2;

    static string[,] arena = new string[ARENA_HEIGHT, ARENA_WIDTH];

    const string EMPTY = " ";
    const string FENCE = "X";
    const string MACHINE = "m";
    const string HUMAN = "@";

    const int FENCES = 15; // inner obstacles, not the border

    // The end

    enum End
    {
        NotYet,
        Quit,
        Electrified,
        Killed,
        Victory,
    }

    static End theEnd;

    // Machines

    const int MACHINES = 5;
    const int MACHINES_DRAG = 2; // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.

    static int[] machineX = new int[MACHINES];
    static int[] machineY = new int[MACHINES];
    static bool[] operative = new bool[MACHINES];
    static int destroyedMachines; // counter

    // Human

    static int humanX;
    static int humanY;

    // Random numbers generator

    static Random rand = new Random();

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

    // Print the given prompt and wait until the user enters a string.
    //
    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 and wait until the user presses Enter.
    //
    static void PressEnter(string prompt)
    {
        InputString(prompt);
    }

    // Return `true` if the given string is "yes" or a synonym.
    //
    static bool IsYes(string s)
    {
        string[] validOptions = {"ok", "y", "yeah", "yes"};
        return validOptions.Contains(s.ToLower());
    }

    // Return `true` if the given string is "no" or a synonym.
    //
    static bool IsNo(string s)
    {
        string[] validOptions = {"n", "no", "nope"};
        return validOptions.Contains(s);
    }

    // Print the given prompt, wait until the user enters a valid yes/no string,
    // and return `true` for "yes" or `false` for "no".
    //
    static bool Yes(string prompt)
    {
        while (true)
        {
            string answer = InputString(prompt);
            if (IsYes(answer))
            {
                return true;
            }
            if (IsNo(answer))
            {
                return false;
            }
        }
    }

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

    const string TITLE = "Chase";

    // Print the title at the current cursor position.
    //
    static void PrintTitle()
    {
        Console.ForegroundColor = TITLE_INK;
        Console.WriteLine(TITLE);
        Console.ForegroundColor = DEFAULT_INK;
    }

    static void PrintCredits()
    {
        PrintTitle();
        Console.WriteLine("\nOriginal version in BASIC:");
        Console.WriteLine("    Anonymous.");
        Console.WriteLine("    Published in \"The Best of Creative Computing\", Volume 2, 1977.");
        Console.WriteLine("    https://www.atariarchives.org/bcc2/showpage.php?page=253");
        Console.WriteLine("\nThis version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair");
    }

    // Print the game instructions and wait for a key.
    //
    static void PrintInstructions()
    {
        PrintTitle();
        Console.ForegroundColor = INSTRUCTIONS_INK;
        Console.WriteLine($"\nYou ({HUMAN}) are in a high voltage maze with {MACHINES}");
        Console.WriteLine($"security machines ({MACHINE}) trying to kill you.");
        Console.WriteLine($"You must maneuver them into the maze ({FENCE}) to survive.\n");
        Console.WriteLine("Good luck!\n");
        Console.WriteLine("The movement commands are the following:\n");
        Console.WriteLine("    ↖  ↑  ↗");
        Console.WriteLine("    NW N NE");
        Console.WriteLine("  ←  W   E  →");
        Console.WriteLine("    SW S SE");
        Console.WriteLine("    ↙  ↓  ↘");
        Console.WriteLine("\nPlus 'Q' to end the game.");
        Console.ForegroundColor = DEFAULT_INK;
    }

    // Arena {{{1
    // =============================================================

    // Display the arena at the top left corner of the screen.
    //
    static void PrintArena()
    {
        Console.SetCursorPosition(0, ARENA_ROW);
        for (int y = 0; y <= ARENA_LAST_Y; y++)
        {
            for (int x = 0; x <= ARENA_LAST_X; x++)
            {
                Console.Write(arena[y, x]);
            }
            Console.WriteLine();
        }
    }

    // If the given arena coordinates `y` and `x` are part of the border arena
    // (i.e. its surrounding fence), return `true`, otherwise return `false`.
    //
    static bool IsBorder(int y, int x)
    {
        return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
    }

    // Return a random integer in the given inclusive range.
    //
    static int RandomIntInInclusiveRange(int min, int max)
    {
        return rand.Next(max - min + 1) + min;
    }

    // Place the given string at a random empty position of the arena and return
    // the coordinates.
    //
    static (int y, int x) Place(string s)
    {
        int y;
        int x;

        while (true)
        {
            y = RandomIntInInclusiveRange(1, ARENA_LAST_Y - 1);
            x = RandomIntInInclusiveRange(1, ARENA_LAST_X - 1);
            if (arena[y, x] == EMPTY)
            {
                break;
            }
        }

        arena[y, x] = s;
        return (y, x);
    }

    // Inhabit the arena with the machines, the inner fences and the human.
    //
    static void InhabitArena()
    {
        for (int m = 0; m < MACHINES; m++)
        {
            (machineY[m], machineX[m]) = Place(MACHINE);
            operative[m] = true;
        }
        for (int _ = 0; _ < FENCES; _++) Place(FENCE);
        (humanY, humanX) = Place(HUMAN);
    }

    // Clean the arena, i.e. empty it and add a surrounding fence.
    //
    static void CleanArena()
    {
        for (int y = 0; y <= ARENA_LAST_Y; y++)
        {
            for (int x = 0; x <= ARENA_LAST_X; x++)
            {
                arena[y, x] = IsBorder(y, x) ? FENCE : EMPTY;
            }
        }
    }

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

    // Init the game.
    //
    static void InitGame()
    {
        CleanArena();
        InhabitArena();
        destroyedMachines = 0;
        theEnd = End.NotYet;
    }

    // Move the given machine.
    //
    static void MoveMachine(int m)
    {
        int maybe;
        arena[machineY[m], machineX[m]] = EMPTY;

        maybe = rand.Next(2);
        if (machineY[m] > humanY)
        {
            machineY[m] -= maybe;
        }
        else if (machineY[m] < humanY)
        {
            machineY[m] += maybe;
        }

        maybe = rand.Next(2);
        if ((machineX[m] > humanX))
        {
            machineX[m] -= maybe;
        }
        else if ((machineX[m] < humanX))
        {
            machineX[m] += maybe;
        }

        if (arena[machineY[m], machineX[m]] == EMPTY)
        {
            arena[machineY[m], machineX[m]] = MACHINE;
        }
        else if (arena[machineY[m], machineX[m]] == FENCE)
        {
            operative[m] = false;
            destroyedMachines += 1;
            if (destroyedMachines == MACHINES)
            {
                theEnd = End.Victory;
            }
        }
        else if (arena[machineY[m], machineX[m]] == HUMAN)
        {
            theEnd = End.Killed;
        }
    }

    // Maybe move the given operative machine.
    //
    static void MaybeMoveMachine(int m)
    {
        if (rand.Next(MACHINES_DRAG) == 0)
        {
            MoveMachine(m);
        }
    }

    // Move the operative machines.
    //
    static void MoveMachines()
    {
        for (int m = 0; m < MACHINES; m++)
        {
            if (operative[m])
            {
                MaybeMoveMachine(m);
            }
        }
    }

    // Erase the current line to the right from the position of the cursor.
    //
    static void EraseLineRight()
    {
        int y = Console.CursorTop;
        int x = Console.CursorLeft;
        string blank = new String(' ', Console.WindowWidth - x - 1);

        Console.Write(blank);
        Console.SetCursorPosition(x, y);
    }

    // Read a user command; update `theEnd` accordingly and return the direction
    // increments.
    //
    static (int yInc, int xInc) GetMove()
    {
        int yInc = 0;
        int xInc = 0;

        Console.WriteLine();
        EraseLineRight();
        string command = InputString("Command: ").ToLower();

        switch (command)
        {
            case "q"  : theEnd = End.Quit; break;
            case "sw" : yInc = +1; xInc = -1; break;
            case "s"  : yInc = +1; break;
            case "se" : yInc = +1; xInc = +1; break;
            case "w"  :             xInc = -1; break;
            case "e"  :             xInc = +1; break;
            case "nw" : yInc = -1; xInc = -1; break;
            case "n"  : yInc = -1; break;
            case "ne" : yInc = -1; xInc = +1; break;
        }
        return (yInc, xInc);
    }

    static void Play()
    {
        int yInc;
        int xInc;

        while (true) { // game loop

            Console.Clear();
            PrintTitle();
            InitGame();

            while (true) { // action loop
                PrintArena();
                (yInc, xInc) = GetMove();
                if (theEnd == End.NotYet)
                {
                    if (yInc != 0 || xInc != 0)
                    {
                        arena[humanY, humanX] = EMPTY;
                        if (arena[humanY + yInc, humanX + xInc] == FENCE)
                        {
                            theEnd = End.Electrified;
                        }
                        else if (arena[humanY + yInc, humanX + xInc] == MACHINE)
                        {
                            theEnd = End.Killed;
                        }
                        else
                        {
                            arena[humanY, humanX] = EMPTY;
                            humanY = humanY + yInc;
                            humanX = humanX + xInc;
                            arena[humanY, humanX] = HUMAN;
                            PrintArena();
                            MoveMachines();
                        }
                    }
                }
                if (theEnd != End.NotYet)
                {
                    break;
                }
            }// action loop;

            switch (theEnd)
            {
                case End.Quit :
                    Console.WriteLine("\nSorry to see you quit.");
                    break;
                case End.Electrified :
                    Console.WriteLine("\nZap! You touched the fence!");
                    break;
                case End.Killed :
                    Console.WriteLine("\nYou have been killed by a lucky machine.");
                    break;
                case End.Victory :
                    Console.WriteLine("\nYou are lucky, you destroyed all machines.");
                    break;
            }

            if (! Yes("\nDo you want to play again? "))
            {
                break;
            }
        }; // game loop

        Console.WriteLine("\nHope you don't feel fenced in.");
        Console.WriteLine("Try again sometime.");
    }

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

    static void Main()
    {
        Console.ForegroundColor = DEFAULT_INK;

        Console.Clear();
        PrintCredits();

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

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

Diamond

// Diamond

// Original version in BASIC:
//  Example included in Vintage BASIC 1.0.3.
//  http://www.vintage-basic.net

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-20.
//
// Last modified 20251205T1534+0100.

using System;

class Diamond
{
    static void Main()
    {
        const int LINES = 17;
        int i = 1;
        int j = 0;

        i = 1;
        while (i <= LINES / 2 + 1)
        {
            j = 1;
            while (j <= (LINES + 1) / 2 - i + 1)
            {
                Console.Write(" ");
                j += 1;
            }
            j = 1;
            while (j <= i * 2 - 1)
            {
                Console.Write("*");
                j += 1;
            }
            Console.WriteLine();
            i += 1;
        }
        i = 1;
        while (i <= LINES / 2)
        {
            j = 1;
            while (j <= i + 1)
            {
                Console.Write(" ");
                j += 1;
            }
            j = 1;
            while (j <= ((LINES + 1) / 2 - i) * 2 - 1)
            {
                Console.Write("*");
                j += 1;
            }
            Console.WriteLine();
            i += 1;
        }
    }
}

Hammurabi

// 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();
    }
}

High Noon

// High Noon

// Original version in BASIC:
//  Designed and programmed by Chris Gaylo, Syosset High School, New York, 1970-09-12.
//  http://mybitbox.com/highnoon-1970/
//  http://mybitbox.com/highnoon/

// Transcriptions:
//  https://github.com/MrMethor/Highnoon-BASIC/
//  https://github.com/mad4j/basic-highnoon/

// Version modified for QB64:
//  By Daniele Olmisani, 2014.
//  https://github.com/mad4j/basic-highnoon/

// This improved remake in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written in 2024-12-25/26.
//
// Last modified: 20251205T1540+0100.

using System;
using System.Linq;

class HighNoon
{
    static Random rand = new Random();

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

    const ConsoleColor DEFAULT_COLOR = ConsoleColor.White;
    const ConsoleColor INPUT_COLOR = ConsoleColor.Green;
    const ConsoleColor INSTRUCTIONS_COLOR = ConsoleColor.DarkYellow;
    const ConsoleColor TITLE_COLOR = ConsoleColor.Red;

    const int INITIAL_DISTANCE = 100;
    const int INITIAL_BULLETS = 4;
    const int MAX_WATERING_TROUGHS = 3;

    static int distance; // distance between both gunners, in paces
    static string strategy; // player's strategy

    static int playerBullets;
    static int opponentBullets;

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

    // Print the given prompt and wait until the user enters a string.
    //
    static string GetString(string prompt = "")
    {
        Console.ForegroundColor = INPUT_COLOR;
        Console.Write(prompt);
        string input = Console.ReadLine();
        Console.ForegroundColor = DEFAULT_COLOR;
        return input;
    }

    // Print the given prompt and wait until the user enters an integer.
    //
    static int GetInteger(string prompt = "")
    {
        int n = 0;

        Console.ForegroundColor = INPUT_COLOR;

        while (true)
        {
            try
            {
                n = Int32.Parse(GetString(prompt));
                break;
            }
            catch
            {
            }
        }

        Console.ForegroundColor = DEFAULT_COLOR;
        return n;
    }

    // Return `true` if the given string is "yes" or a synonym.
    //
    static bool IsYes(string s)
    {
        string[] validOptions = {"ok", "y", "yeah", "yes"};
        return validOptions.Contains(s.ToLower());
    }

    // Return `true` if the given string is "no" or a synonym.
    //
    static bool IsNo(string s)
    {
        string[] validOptions = {"n", "no", "nope"};
        return validOptions.Contains(s.ToLower());
    }

    // Print the given prompt, wait until the user enters a valid yes/no string,
    // and return `true` for "yes" or `false` for "no".
    //
    static bool Yes(string prompt)
    {
        while (true)
        {
            string answer = GetString(prompt);
            if (IsYes(answer))
            {
                return true;
            }
            if (IsNo(answer))
            {
                return false;
            }
        }
    }

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

    // Print the title at the current cursor position.
    //
    static void PrintTitle()
    {
        Console.ForegroundColor = TITLE_COLOR;
        Console.WriteLine("High Noon");
        Console.ForegroundColor = DEFAULT_COLOR;
    }

    static void PrintCredits()
    {
        PrintTitle();
        Console.WriteLine("\nOriginal version in BASIC:");
        Console.WriteLine("    Designed and programmend by Chris Gaylo, 1970.");
        Console.WriteLine("    http://mybitbox.com/highnoon-1970/");
        Console.WriteLine("    http://mybitbox.com/highnoon/");
        Console.WriteLine("Transcriptions:");
        Console.WriteLine("    https://github.com/MrMethor/Highnoon-BASIC/");
        Console.WriteLine("    https://github.com/mad4j/basic-highnoon/");
        Console.WriteLine("Version modified for QB64:");
        Console.WriteLine("    By Daniele Olmisani, 2014.");
        Console.WriteLine("    https://github.com/mad4j/basic-highnoon/");
        Console.WriteLine("This improved remake in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair");
    }

    static void PrintInstructions()
    {
        PrintTitle();
        Console.ForegroundColor = INSTRUCTIONS_COLOR;
        Console.WriteLine("\nYou have been challenged to a showdown by Black Bart, one of");
        Console.WriteLine("the meanest desperadoes west of the Allegheny mountains.");
        Console.WriteLine("\nWhile you are walking down a dusty, deserted side street,");
        Console.WriteLine("Black Bart emerges from a saloon one hundred paces away.");
        Console.Write($"\nBy agreement, you each have {INITIAL_BULLETS} bullets in your six-guns.");
        Console.WriteLine("\nYour marksmanship equals his. At the start of the walk nei-");
        Console.WriteLine("ther of you can possibly hit the other, and at the end of");
        Console.WriteLine("the walk, neither can miss. the closer you get, the better");
        Console.WriteLine("your chances of hitting black Bart, but he also has beter");
        Console.WriteLine("chances of hitting you.");
        Console.ForegroundColor = DEFAULT_COLOR;
    }

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

    static string PluralSuffix(int n)
    {
        return n == 1 ? "" : "s";
    }

    static void PrintShellsLeft()
    {
        if (playerBullets == opponentBullets)
        {
            Console.WriteLine($"Both of you have {playerBullets} bullets.");
        }
        else
        {
            Console.WriteLine
            (
                "You now have {0} bullet{1} to Black Bart's {2} bullet{3}.",
                playerBullets,
                PluralSuffix(playerBullets),
                opponentBullets,
                PluralSuffix(opponentBullets)
            );
        }
    }

    static void PrintCheck()
    {
        Console.WriteLine("******************************************************");
        Console.WriteLine("*                                                    *");
        Console.WriteLine("*                 BANK OF DODGE CITY                 *");
        Console.WriteLine("*                  CASHIER'S RECEIT                  *");
        Console.WriteLine("*                                                    *");
        Console.WriteLine("* CHECK NO. {0:0000}                   AUGUST {1}TH, 1889 *",
            rand.Next(1000),
            10 + rand.Next(10));
        Console.WriteLine("*                                                    *");
        Console.WriteLine("*                                                    *");
        Console.WriteLine("*       PAY TO THE BEARER ON DEMAND THE SUM OF       *");
        Console.WriteLine("*                                                    *");
        Console.WriteLine("* TWENTY THOUSAND DOLLARS-------------------$20,000  *");
        Console.WriteLine("*                                                    *");
        Console.WriteLine("******************************************************");
    }

    static void GetReward()
    {
        Console.WriteLine("As mayor of Dodge City, and on behalf of its citizens,");
        Console.WriteLine("I extend to you our thanks, and present you with this");
        Console.WriteLine("reward, a check for $20,000, for killing Black Bart.\n\n");
        PrintCheck();
        Console.WriteLine("\n\nDon't spend it all in one place.");
    }

    static void MoveTheOpponent()
    {
        int paces = 2 + rand.Next(8);
        Console.WriteLine($"Black Bart moves {paces} paces.");
        distance -= paces;
    }

    // Maybe move the opponent; if so, return `true`, otherwise return `false`. A
    // true `silent` flag allows to omit the message when the opponent doesn't
    // move.
    //
    static bool MaybeMoveTheOpponent(bool silent = false)
    {
        if (rand.Next(2) == 0) { // 50% chances
            MoveTheOpponent();
            return true;
        }
        else
        {
            if (! silent)
            {
                Console.WriteLine("Black Bart stands still.");
            }
            return false;
        }
    }

    static bool MissedShot()
    {
        return rand.NextDouble() * 10 <= (double) distance / 10;
    }

    // Handle the opponent's shot and return a flag with the result: if the
    // opponent kills the player, return `true`; otherwise return `false`.
    //
    static bool TheOpponentFiresAndKills()
    {
        Console.WriteLine("Black Bart fires…");
        opponentBullets -= 1;
        if (MissedShot())
        {
            Console.WriteLine("A miss…");
            switch (opponentBullets)
            {
                case 3:
                    Console.WriteLine("Whew, were you lucky. That bullet just missed your head.");
                    break;
                case 2:
                    Console.WriteLine("But Black Bart got you in the right shin.");
                    break;
                case 1:
                    Console.WriteLine("Though Black Bart got you on the left side of your jaw.");
                    break;
                case 0:
                    Console.WriteLine("Black Bart must have jerked the trigger.");
                    break;
            }
        }
        else
        {
            if (strategy == "j")
            {
                Console.WriteLine("That trick just saved yout life. Black Bart's bullet");
                Console.WriteLine("was stopped by the wood sides of the trough.");
            }
            else
            {
                Console.WriteLine("Black Bart shot you right through the heart that time.");
                Console.WriteLine("You went kickin' with your boots on.");
                return true;
            }
        }
        return false;
    }

    // Handle the opponent's strategy and return a flag with the result: if the
    // opponent runs or kills the player, return `true`; otherwise return `false`.
    //
    static bool TheOpponentKillsOrRuns()
    {
        if (distance >= 10 || playerBullets == 0)
        {
            if (MaybeMoveTheOpponent(silent: true))
            {
                return false;
            }
        }
        if (opponentBullets > 0)
        {
            return TheOpponentFiresAndKills();
        }
        else
        {
            if (playerBullets > 0)
            {
                if (rand.Next(2) == 0)
                {
                    {;
                } // 50% chances
                    Console.WriteLine("Now is your chance, Black Bart is out of bullets.");
                }
                else
                {
                    Console.WriteLine("Black Bart just hi-tailed it out of town rather than face you");
                    Console.WriteLine("without a loaded gun. You can rest assured that Black Bart");
                    Console.WriteLine("won't ever show his face around this town again.");
                    return true;
                }
            }
        }
        return false;
    }

    static void Play()
    {
        distance = INITIAL_DISTANCE;
        int wateringTroughs = 0;
        playerBullets = INITIAL_BULLETS;
        opponentBullets = INITIAL_BULLETS;

        while (true)
        {
            Console.WriteLine($"You are now {distance} paces apart from Black Bart.");
            PrintShellsLeft();
            Console.ForegroundColor = INSTRUCTIONS_COLOR;
            Console.WriteLine("\nStrategies:");
            Console.WriteLine("  [A]dvance");
            Console.WriteLine("  [S]tand still");
            Console.WriteLine("  [F]ire");
            Console.WriteLine("  [J]ump behind the watering trough");
            Console.WriteLine("  [G]ive up");
            Console.WriteLine("  [T]urn tail and run");
            Console.ForegroundColor = DEFAULT_COLOR;

            strategy = (GetString("What is your strategy? ")).ToLower();

            switch (strategy)
            {
                case "a": // advance

                    while (true)
                    {
                        int paces = GetInteger("How many paces do you advance? ");
                        if (paces < 0)
                        {
                            Console.WriteLine("None of this negative stuff, partner, only positive numbers.");
                        }
                        else if (paces > 10)
                        {
                            Console.WriteLine("Nobody can walk that fast.");
                        }
                        else
                        {
                            distance -= paces;
                            break;
                        }
                    }
                    break;

                case "s": // stand still

                    Console.WriteLine("That move made you a perfect stationary target.");
                    break;

                case "f": // fire

                    if (playerBullets == 0)
                    {
                        Console.WriteLine("You don't have any bullets left.");
                    }
                    else
                    {
                        playerBullets -= 1;
                        if (MissedShot())
                        {
                            switch (playerBullets)
                            {
                                case 2:
                                    Console.WriteLine("Grazed Black Bart in the right arm.");
                                    break;
                                case 1:
                                    Console.WriteLine("He's hit in the left shoulder, forcing him to use his right");
                                    Console.WriteLine("hand to shoot with.");
                                    break;
                            }
                            Console.WriteLine("What a lousy shot.");
                            if (playerBullets == 0)
                            {
                                Console.WriteLine("Nice going, ace, you've run out of bullets.");
                                if (opponentBullets != 0)
                                {
                                    Console.WriteLine("Now Black Bart won't shoot until you touch noses.");
                                    Console.WriteLine("You better think of something fast (like run).");
                                }
                            }
                        }
                        else
                        {
                            Console.WriteLine("What a shot, you got Black Bart right between the eyes.");
                            GetString("\nPress the Enter key to get your reward. ");
                            Console.Clear();
                            GetReward();
                            return;
                        }
                    }
                    break;

                case "j": // jump

                    if (wateringTroughs == MAX_WATERING_TROUGHS)
                    {
                        Console.WriteLine("How many watering troughs do you think are on this street?");
                        strategy = "";
                    }
                    else
                    {
                        wateringTroughs += 1;
                        Console.WriteLine("You jump behind the watering trough.");
                        Console.WriteLine("Not a bad maneuver to threw Black Bart's strategy off.");
                    }
                    break;

                case "g": // give up

                    Console.WriteLine("Black Bart accepts. The conditions are that he won't shoot you");
                    Console.WriteLine("if you take the first stage out of town and never come back.");
                    if (Yes("Agreed? "))
                    {
                        Console.WriteLine("A very wise decision.");
                        return;
                    }
                    else
                    {
                        Console.WriteLine("Oh well, back to the showdown.");
                    }
                    break;

                case "t": // turn tail and run

                    // The more bullets of the opponent, the less chances to escape.
                    if (rand.Next(opponentBullets + 2) == 0)
                    {
                        Console.WriteLine("Man, you ran so fast even dogs couldn't catch you.");
                    }
                    else
                    {
                        switch (opponentBullets)
                        {
                            case 0:
                                Console.WriteLine("You were lucky, Black Bart can only throw his gun at you, he");
                                Console.WriteLine("doesn't have any bullets left. You should really be dead.");
                                break;
                            case 1:
                                Console.WriteLine("Black Bart fires his last bullet…");
                                Console.WriteLine("He got you right in the back. That's what you deserve, for running.");
                                break;
                            case 2:
                                Console.WriteLine("Black Bart fires and got you twice: in your back");
                                Console.WriteLine("and your ass. Now you can't even rest in peace.");
                                break;
                            case 3:
                                Console.WriteLine("Black Bart unloads his gun, once in your back");
                                Console.WriteLine("and twice in your ass. Now you can't even rest in peace.");
                                break;
                            case 4:
                                Console.WriteLine("Black Bart unloads his gun, once in your back");
                                Console.WriteLine("and three times in your ass. Now you can't even rest in peace.");
                                break;
                        }
                        opponentBullets = 0;
                    }
                    return;

                default:

                    Console.WriteLine("You sure aren't going to live very long if you can't even follow directions.");
                    break;
            }; // strategy switch

            if (TheOpponentKillsOrRuns())
            {
                break;
            }

            if (playerBullets + opponentBullets == 0)
            {
                Console.WriteLine("The showdown must end, because nobody has bullets left.");
                break;
            }

            Console.WriteLine();
        }; // showdown loop
    }

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

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

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

        GetString("\nPress the Enter key to start. ");
        Console.Clear();
        Play();
    }
}

Math

// Math

// Original version in BASIC:
//  Example included in Vintage BASIC 1.0.3.
//  http://www.vintage-basic.net

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-21.
//
// Last modified: 20251205T1540+0100.

using System;

class vintageBasicMath
{
    static void Main()
    {
        double n;

        while (true)
        {
            Console.Write("Enter a number: ");
            try
            {
                // XXX TODO Support any culture-specific number notation.
                n = Double.Parse(Console.ReadLine());
                break;
            }
            catch
            {
            }
        }

        Console.WriteLine($"ABS({n}) -> Math.Abs({n}) -> {Math.Abs(n)}");
        Console.WriteLine($"ATN({n}) -> Math.Atan({n}) -> {Math.Atan(n)}");
        Console.WriteLine($"COS({n}) -> Math.Cos({n}) -> {Math.Cos(n)}");
        Console.WriteLine($"EXP({n}) -> Math.Exp({n}) -> {Math.Exp(n)}");
        Console.WriteLine($"INT({n}) -> (int) {n}) -> {(int) n}");
        Console.WriteLine($"LOG({n}) -> Math.Log({n}, Math.E) -> {Math.Log(n, Math.E)}");
        Console.WriteLine($"SGN({n}) -> Math.Sign({n}) -> {Math.Sign(n)}");
        Console.WriteLine($"SQR({n}) -> Math.Sqrt({n}) -> {Math.Sqrt(n)}");
        Console.WriteLine($"TAN({n}) -> Math.Tan({n}) -> {Math.Tan(n)}");
    }
}

Mugwump

// Mugwump

// Original version in BASIC:
//  Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).
//  Slightly modified by Bob Albrecht of People's Computer Company.
//  Published by Creative Computing (Morristown, New Jersey, USA), 1978.
//  - https://www.atariarchives.org/basicgames/showpage.php?page=114
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/mugwump.bas

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-25.
//
// Last modified: 20251205T1542+0100.

using System;
using System.Linq;

class MugwumpGame
{
    const int GRID_SIZE = 10;
    const int TURNS = 10;
    const int MUGWUMPS = 4;

    class Mugwump
    {
        internal int x = 0;
        internal int y = 0;
        internal bool hidden = false;
    }

    static Mugwump[] mugwump = new Mugwump[MUGWUMPS];
    static int found; // counter

    static string GetString(string prompt)
    {
        Console.Write(prompt);
        return Console.ReadLine();
    }

    // Print the given prompt, wait until the user enters a valid integer and
    // return it.
    //
    static int GetNumber(string prompt)
    {
        int n = 0;

        while (true)
        {
            try
            {
                n = Int32.Parse(GetString(prompt));
                break;
            }
            catch
            {
            }
        }

        return n;
    }

    // Return `true` if the given string is "yes" or a synonym.
    //
    static bool IsYes(string s)
    {
        string[] validOptions = {"ok", "y", "yeah", "yes"};
        return validOptions.Contains(s.ToLower());
    }

    // Return `true` if the given string is "no" or a synonym.
    //
    static bool IsNo(string s)
    {
        string[] validOptions = {"n", "no", "nope"};
        return validOptions.Contains(s.ToLower());
    }

    // Print the given prompt, wait until the user enters a valid yes/no string,
    // and return `true` for "yes" or `false` for "no".
    //
    static bool Yes(string prompt)
    {
        while (true)
        {
            string answer = GetString(prompt);
            if (IsYes(answer))
            {
                return true;
            }
            if (IsNo(answer))
            {
                return false;
            }
        }
    }

    // Clear the screen, print the credits and ask the user to press enter.
    //
    static void PrintCredits()
    {
        Console.Clear();
        Console.WriteLine("Mugwump\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).");
        Console.WriteLine("    Slightly modified by Bob Albrecht of People's Computer Company.");
        Console.WriteLine("    Published by Creative Computing (Morristown, New Jersey, USA), 1978.");
        Console.WriteLine("    - https://www.atariarchives.org/basicgames/showpage.php?page=114");
        Console.WriteLine("    - http://vintage-basic.net/games.html\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        GetString("Press Enter to read the instructions. ");
    }

    // Clear the screen, print the instructions and ask the user to press enter.
    //
    static void PrintInstructions()
    {
        Console.Clear();
        Console.WriteLine("Mugwump\n");
        Console.WriteLine("The object of this game is to find four mugwumps");
        Console.WriteLine("hidden on a 10 by 10 grid.  Homebase is position 0,0.");
        Console.WriteLine("Any guess you make must be two numbers with each");
        Console.WriteLine("number between 0 and 9, inclusive.  First number");
        Console.WriteLine("is distance to right of homebase and second number");
        Console.WriteLine("is distance above homebase.\n");
        Console.WriteLine($"You get {TURNS} tries.  After each try, you will see");
        Console.WriteLine("how far you are from each mugwump.\n");
        GetString("Press Enter to start. ");
    }

    static void InitMugwumps()
    {
        for (int m = 0; m < MUGWUMPS; m++)
        {
            mugwump[m] = new Mugwump();
        }
    }

    // Init the mugwumps' positions, `hidden` flags and count.
    //
    static void HideMugwumps()
    {
        Random rand = new Random();

        for (int m = 0; m < MUGWUMPS; m++)
        {
            mugwump[m].x = rand.Next(GRID_SIZE);
            mugwump[m].y = rand.Next(GRID_SIZE);
            mugwump[m].hidden = true;
        }
        found = 0; // counter
    }

    // Print the given prompt, wait until the user enters a valid coord and return
    // it.
    //
    static int GetCoord(string prompt)
    {
        int coord = 0;
        while (true)
        {
            coord = GetNumber(prompt);
            if (coord < 0 || coord >= GRID_SIZE)
            {
                Console.WriteLine($"Invalid value {coord}: not in range [0, {GRID_SIZE - 1}].");
            }
            else
            {
                break;
            }
        }
        return coord;
    }

    // Return `true` if the given mugwump is hidden in the given coords.
    //
    static bool IsHere(int m, int x, int y)
    {
        return mugwump[m].hidden && mugwump[m].x == x && mugwump[m].y == y;
    }

    // Return the distance between the given mugwump and the given coords.
    //
    static int Distance(int m, int x, int y)
    {
        return (int) Math.Sqrt
            (
                Math.Pow((double) (mugwump[m].x - x), 2) +
                Math.Pow((double) (mugwump[m].y - y), 2)
            );
    }

    static string Plural(int n, string plural = "s", string singular = "")
    {
        return n > 1 ? plural : singular;
    }

    // Run the game.
    //
    static void Play()
    {
        int turn = 1; // counter
        InitMugwumps();
        while (true) {; // game
            Console.Clear();
            HideMugwumps();
            while (turn <= TURNS)
            {
                Console.WriteLine($"Turn number {turn}\n");
                Console.WriteLine($"What is your guess (in range [0, {GRID_SIZE - 1}])?");
                int x = GetCoord("Distance right of homebase (x-axis): ");
                int y = GetCoord("Distance above homebase (y-axis): ");
                Console.WriteLine($"\nYour guess is ({x}, {y}).");
                for (int m = 0; m < MUGWUMPS; m++)
                {
                    if (IsHere(m, x, y))
                    {
                        mugwump[m].hidden = false;
                        found++;
                        Console.WriteLine($"You have found mugwump {m}!");
                        if (found == MUGWUMPS)
                        {
                            goto endTurns;
                        }
                    }
                }
                for (int m = 0; m < MUGWUMPS; m++)
                {
                    if (mugwump[m].hidden)
                    {
                        Console.WriteLine($"You are {Distance(m, x, y)} units from mugwump {m}.");
                    }
                }
                Console.WriteLine();
                turn++;
            }; // turns
            endTurns:
            if (found == MUGWUMPS)
            {
                Console.WriteLine($"\nYou got them all in {turn} turn{Plural(turn)}!\n");
                Console.WriteLine("That was fun! let's play again…");
                Console.WriteLine("Four more mugwumps are now in hiding.");
            }
            else
            {
                Console.WriteLine($"\nSorry, that's {TURNS} tr{Plural(TURNS, "ies", "y")}.\n");
                Console.WriteLine("Here is where they're hiding:");
                for (int m = 0; m < MUGWUMPS; m++)
                {
                    if (mugwump[m].hidden)
                    {
                        Console.WriteLine($"Mugwump {m} is at ({mugwump[m].x}, {mugwump[m].y}).");
                    }
                }
            }
            Console.WriteLine();
            if (!Yes("Do you want to play again? "))
            {
                break;
            }
        }; // game
    }

    static void Main()
    {
        PrintCredits();
        PrintInstructions();
        Play();
    }
}

Name

// Name

// Original version in BASIC:
//  Example included in Vintage BASIC 1.0.3.
//  http://www.vintage-basic.net

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-21.
//
// Last modified: 20251205T1543+0100.

using System;

class Name
{
    static void Main()
    {
        Console.Write("What is your name? ");
        string name = Console.ReadLine();

        double number;

        while (true)
        {
            Console.Write("Enter a number: ");
            try
            {
                number = Int64.Parse(Console.ReadLine());
                break;
            }
            catch
            {
                Console.WriteLine("Integer expected.");
            }
        }
        for (int i = 0; i < number; i++)
        {
            Console.WriteLine($"Hello, {name}!");
        }
    }
}

Poetry

// Poetry

// Original version in BASIC:
//  Unknown author.
//  Modified and reworked by Jim Bailey, Peggy Ewing, and Dave Ahl at DEC.
//  Published in "BASIC Computer Games", Creative Computing (Morristown, New Jersey, USA), 1978.
//  - https://archive.org/details/Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
//  - https://github.com/chaosotter/basic-games/tree/master/games/BASIC%20Computer%20Games/Poetry
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/poetry.bas

// This improved remake in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written in 2024-12-24/25.
//
// Last modified: 20251205T1545+0100.

using System;
using System.Threading;

class poetry
{
    // Globals {{{1
    // =============================================================

    const ConsoleColor DEFAULT_COLOR = ConsoleColor.White;
    const ConsoleColor INPUT_COLOR = ConsoleColor.Green;
    const ConsoleColor TITLE_COLOR = ConsoleColor.Red;

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

    // Print the given prompt and wait until the user enters a string.
    //
    static string InputString(string prompt = "")
    {
        Console.ForegroundColor = INPUT_COLOR;
        Console.Write(prompt);
        string result = Console.ReadLine();
        Console.ForegroundColor = DEFAULT_COLOR;
        return result;
    }

    // Print the given prompt and wait until the user presses Enter.
    //
    static void PressEnter(string prompt)
    {
        InputString(prompt);
    }

    // Title and credits {{{1
    // =============================================================

    // Print the title at the current cursor position.
    //
    static void PrintTitle()
    {
        Console.ForegroundColor = TITLE_COLOR;
        Console.WriteLine("Poetry");
        Console.ForegroundColor = DEFAULT_COLOR;
    }

    // Print the credits at the current cursor position.
    //
    static void PrintCredits()
    {
        PrintTitle();
        Console.WriteLine("\nOriginal version in BASIC:");
        Console.WriteLine("    Unknown author.");
        Console.WriteLine("    Published in \"BASIC Computer Games\",");
        Console.WriteLine("    Creative Computing (Morristown, New Jersey, USA), 1978.\n");

        Console.WriteLine("This improved remake in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair");
    }

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

    // Is the given integer even?
    //
    static bool IsEven(int n)
    {
        return (n % 2) == 0;
    }

    static void Play()
    {
        const int MAX_PHRASES_AND_VERSES = 20;

        // counters:
        int action = 0;
        int phrase = 0;
        int phrasesAndVerses = 0;
        int verseChunks = 0;

        while (true)
        {
            verse:

            bool manageTheVerseContinuation = true;
            bool maybeAddComma = true;

            switch (action)
            {
                case 0:
                case 1:
                    switch (phrase)
                    {
                        case 0: Console.Write("MIDNIGHT DREARY"); break;
                        case 1: Console.Write("FIERY EYES"); break;
                        case 2: Console.Write("BIRD OR FIEND"); break;
                        case 3: Console.Write("THING OF EVIL"); break;
                        case 4: Console.Write("PROPHET"); break;
                    }
                    break;
                case 2:
                    switch (phrase)
                    {
                        case 0:
                            Console.Write("BEGUILING ME");
                            verseChunks = 2;
                            break;
                        case 1:
                            Console.Write("THRILLED ME");
                            break;
                        case 2:
                            Console.Write("STILL SITTING…");
                            maybeAddComma = false;
                            break;
                        case 3:
                            Console.Write("NEVER FLITTING");
                            verseChunks = 2;
                            break;
                        case 4:
                            Console.Write("BURNED");
                            break;
                    }
                    break;
                case 3:
                    switch (phrase)
                    {
                        case 0:
                            Console.Write("AND MY SOUL");
                            break;
                        case 1:
                            Console.Write("DARKNESS THERE");
                            break;
                        case 2:
                            Console.Write("SHALL BE LIFTED");
                            break;
                        case 3:
                            Console.Write("QUOTH THE RAVEN");
                            break;
                        case 4:
                            if (verseChunks != 0)
                            {
                                Console.Write("SIGN OF PARTING");
                            }
                            break;
                    }
                    break;
                case 4:
                    switch (phrase)
                    {
                        case 0: Console.Write("NOTHING MORE"); break;
                        case 1: Console.Write("YET AGAIN"); break;
                        case 2: Console.Write("SLOWLY CREEPING"); break;
                        case 3: Console.Write("…EVERMORE"); break;
                        case 4: Console.Write("NEVERMORE"); break;
                    }
                    break;
                case 5:
                    action = 0;
                    Console.WriteLine();
                    if (phrasesAndVerses > MAX_PHRASES_AND_VERSES)
                    {
                        Console.WriteLine();
                        verseChunks = 0;
                        phrasesAndVerses = 0;
                        action = 2;
                        goto verse;
                    }
                    else
                    {
                        manageTheVerseContinuation = false;
                    }
                    break;
            }

            Random rand = new Random();

            if (manageTheVerseContinuation)
            {
                Thread.Sleep(250); // milliseconds

                if (maybeAddComma && ! (verseChunks == 0 || rand.NextDouble() > 0.19))
                {
                    Console.Write(",");
                    verseChunks = 2;
                }

                if (rand.NextDouble() > 0.65)
                {
                    Console.WriteLine();
                    verseChunks = 0;
                }
                else
                {
                    Console.Write(" ");
                    verseChunks += 1;
                }
            }

            action += 1;
            phrase = rand.Next(5);
            phrasesAndVerses += 1;

            if (! (verseChunks > 0 || IsEven(action)))
            {
                Console.Write("     ");
            }
        }
        // verse loop
    }

    static void Main()
    {
        Console.Clear();
        PrintCredits();
        PressEnter("\nPress the Enter key to start. ");
        Console.Clear();
        Play();
    }
}

Russian Roulette

// Russian Roulette

// Original version in BASIC:
//  By Tom Adametx, 1978.
//  Creative Computing's BASIC Games.
//  - https://www.atariarchives.org/basicgames/showpage.php?page=141
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/russianroulette.bas
//  - http://www.retroarchive.org/cpm/games/ccgames.zip

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-21.
//
// Last modified: 20251205T1547+0100.

using System;

class RussianRoulette
{
    static void PressEnterToStart()
    {
        Console.Write("Press Enter to start. ");
        Console.ReadKey();
    }

    static void PrintCredits()
    {
        Console.Clear();
        Console.WriteLine("Russian Roulette\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        PressEnterToStart();
    }

    static void PrintInstructions()
    {
        Console.Clear();
        Console.WriteLine("Here is a revolver.");
        Console.WriteLine("Type 'f' to spin chamber and pull trigger.");
        Console.WriteLine("Type 'g' to give up, and play again.");
        Console.WriteLine("Type 'q' to quit.\n");
    }

    static void Play()
    {
        Random rand = new Random();

        int times;

        while (true) { // game loop
            PrintInstructions();
            times = 0;
            bool playing = true;
            while (playing)
            {
                Console.Write("> ");
                switch (Console.ReadLine())
                {
                    case "f" : // fire
                        if (rand.Next(100) > 83)
                        {
                            Console.WriteLine("Bang! You're dead!");
                            Console.WriteLine("Condolences will be sent to your relatives.");
                            playing = false;
                        }
                        else
                        {
                            times += 1;
                            if (times > 10)
                            {
                                Console.WriteLine("You win!");
                                Console.WriteLine("Let someone else blow his brains out.");
                                playing = false;
                            }
                            else
                            {
                                Console.WriteLine("Click.");
                            }
                        }
                        break;
                    case "g" :; // give up
                        Console.WriteLine("Chicken!");
                        playing = false; // force exit
                        break;
                    case "q" :; // quit
                        return;
                }
            }; // play loop
            PressEnterToStart();
        }; // game loop
    }

    static void Main()
    {
        PrintCredits();
        Play();
        Console.WriteLine("Bye!");
    }
}

Seance

// Seance

// Original version in BASIC:
//  By Chris Oxlade, 1983.
//  Creepy Computer Games (Usborne Publishing Ltd.)
//  - https://archive.org/details/seance.qb64
//  - https://github.com/chaosotter/basic-games

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-25.
//
// Last modified: 20251205T1547+0100.

using System;
using System.Threading;

class Seance
{
    const int MAX_SCORE = 50;

    const int MAX_MESSAGE_LENGTH = 6;
    const int MIN_MESSAGE_LENGTH = 3;

    const int BASE_CHARACTER_CODE = (int) '@';
    const char PLANCHETTE = '*';

    const int FIRST_LETTER_NUMBER = 1;
    const int LAST_LETTER_NUMBER = 26;

    const ConsoleColor BOARD_COLOR = ConsoleColor.Cyan;
    const ConsoleColor DEFAULT_COLOR = ConsoleColor.White;
    const ConsoleColor INPUT_COLOR = ConsoleColor.Green;
    const ConsoleColor INSTRUCTIONS_COLOR = ConsoleColor.DarkYellow;
    const ConsoleColor MISTAKE_EFFECT_COLOR = ConsoleColor.Red;
    const ConsoleColor PLANCHETTE_COLOR = ConsoleColor.DarkYellow;
    const ConsoleColor TITLE_COLOR = ConsoleColor.Red;

    const int INPUT_X = BOARD_X;
    const int INPUT_Y = BOARD_Y + BOARD_BOTTOM_Y + 4;

    const int MESSAGES_Y = INPUT_Y;

    const int MISTAKE_EFFECT_PAUSE = 3; // seconds

    const int BOARD_ACTUAL_WIDTH = BOARD_WIDTH + 2 * BOARD_PAD; // screen columns
    const int BOARD_BOTTOM_Y = BOARD_HEIGHT + 1; // relative to the board
    const int BOARD_HEIGHT = 5; // characters displayed on the left and right borders
    const int BOARD_PAD = 1; // blank characters separating the board from its left and right borders
    const int BOARD_WIDTH = 8; // characters displayed on the top and bottom borders
    const int BOARD_X = 29; // screen column
    const int BOARD_Y = 5; // screen line

    // Print the given prompt and wait until the user enters a string.
    //
    static string Input(string prompt = "")
    {
        Console.ForegroundColor = INPUT_COLOR;
        Console.Write(prompt);
        string result = Console.ReadLine();
        Console.ForegroundColor = DEFAULT_COLOR;
        return result;
    }

    // Print the given prompt and wait until the user presses Enter.
    //
    static void PressEnter(string prompt)
    {
        Input(prompt);
    }

    // Return the x coordinate to print the given text centered on the board.
    //
    static int boardCenteredX(string text)
    {
        return BOARD_X + (BOARD_ACTUAL_WIDTH - text.Length) / 2;
    }

    // Print the given text on the given row, centered on the board.
    //
    static void PrintBoardCentered(string text, int y)
    {
        Console.SetCursorPosition(boardCenteredX(text), y);
        Console.WriteLine(text);
    }

    const string TITLE = "Seance";

    // Print the title at the current cursor position.
    //
    static void PrintTitle()
    {
        Console.ForegroundColor = TITLE_COLOR;
        Console.WriteLine(TITLE);
        Console.ForegroundColor = DEFAULT_COLOR;
    }

    // Print the title on the given row, centered on the board.
    //
    static void PrintBoardCenteredTitle(int y)
    {
        Console.ForegroundColor = TITLE_COLOR;
        PrintBoardCentered(TITLE, y);
        Console.ForegroundColor = DEFAULT_COLOR;
    }

    static void PrintCredits()
    {
        PrintTitle();
        Console.WriteLine("\nOriginal version in BASIC:");
        Console.WriteLine("    Written by Chris Oxlade, 1983.");
        Console.WriteLine("    https://archive.org/details/seance.qb64");
        Console.WriteLine("    https://github.com/chaosotter/basic-games");
        Console.WriteLine("\nThis version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair");
    }

    static void PrintInstructions()
    {
        PrintTitle();
        Console.ForegroundColor = INSTRUCTIONS_COLOR;
        Console.WriteLine("\nMessages from the spirits are coming through, letter by letter.  They want you");
        Console.WriteLine("to remember the letters and type them into the computer in the correct order.");
        Console.WriteLine("If you make mistakes, they will be angry -- very angry...");
        Console.WriteLine();
        Console.WriteLine("Watch for stars on your screen -- they show the letters in the spirits'");
        Console.WriteLine("messages.");
        Console.ForegroundColor = DEFAULT_COLOR;
    }

    // Print the given letter at the given board coordinates.
    //
    static void PrintCharacter(int y, int x, char a)
    {
        Console.SetCursorPosition(x + BOARD_X, y + BOARD_Y);
        Console.Write(a);
    }

    static void PrintBoard()
    {
        Console.ForegroundColor = BOARD_COLOR;
        for (int i = 1; i <= BOARD_WIDTH; i++)
        {
            PrintCharacter(0, i + 1, (char) (BASE_CHARACTER_CODE + i)); // top border
            PrintCharacter(BOARD_BOTTOM_Y, i + 1, (char) (BASE_CHARACTER_CODE + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1)); // bottom border
        }
        for (int i = 1; i <= BOARD_HEIGHT; i++)
        {
            PrintCharacter(i , 0, (char) (BASE_CHARACTER_CODE + LAST_LETTER_NUMBER - i + 1)); // left border
            PrintCharacter(i , 3 + BOARD_WIDTH, (char) (BASE_CHARACTER_CODE + BOARD_WIDTH + i)); // right border
        }
        Console.WriteLine();
        Console.ForegroundColor = DEFAULT_COLOR;
    }

    // Return a random integer in the given inclusive range.
    //
    static int RandomInInclusiveRange(int min, int max)
    {
        Random rand = new Random();
        return rand.Next(max - min + 1) + min;
    }

    // Print the given mistake effect, wait a configured number of seconds and
    // finally erase it.
    //
    static void PrintMistakeEffect(string effect)
    {
        int x = boardCenteredX(effect);

        Console.CursorVisible = false;

        Console.SetCursorPosition(x, MESSAGES_Y);
        Console.ForegroundColor = MISTAKE_EFFECT_COLOR;
        Console.WriteLine(effect);

        Thread.Sleep(1000 * MISTAKE_EFFECT_PAUSE); // milliseconds

        Console.ForegroundColor = DEFAULT_COLOR;
        Console.SetCursorPosition(x, MESSAGES_Y);
        string spaces = new string(' ', effect.Length);
        Console.Write(spaces);

        Console.CursorVisible = true;
    }

    // Return a new message of the given length, after marking its letters on the
    // board.
    //
    static string Message(int length)
    {
        int y;
        int x;
        string message = "";
        Console.CursorVisible = false;
        for (int i = 1; i <= length; i++)
        {
            int letterNumber = RandomInInclusiveRange(FIRST_LETTER_NUMBER, LAST_LETTER_NUMBER);
            char letter = (char) (BASE_CHARACTER_CODE + letterNumber);
            message = $"{message}{letter}";
            if (letterNumber <= BOARD_WIDTH)
            {
                // top border
                y = 1;
                x = letterNumber + 1;
            }
            else if (letterNumber <= BOARD_WIDTH + BOARD_HEIGHT)
            {
                // right border
                y = letterNumber - BOARD_WIDTH;
                x = 2 + BOARD_WIDTH;
            }
            else if (letterNumber <= BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH)
            {
                // bottom border
                y = BOARD_BOTTOM_Y - 1;
                x = 2 + BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH - letterNumber;
            }
            else
            {
                // left border
                y = 1 + LAST_LETTER_NUMBER - letterNumber;
                x = 1;
            }
            Console.ForegroundColor = PLANCHETTE_COLOR;
            PrintCharacter(y, x, PLANCHETTE);
            Console.ForegroundColor = DEFAULT_COLOR;
            Thread.Sleep(1000); // milliseconds
            PrintCharacter(y, x, ' ');
        }
        Console.CursorVisible = true;
        return message;
    }

    // Accept a string from the user, erase it from the screen and return it.
    //
    static string MessageUnderstood()
    {
        string prompt = "? ";

        Console.SetCursorPosition(INPUT_X, INPUT_Y);
        string message = Input(prompt).ToUpper();

        string spaces = new string(' ', prompt.Length + message.Length);
        Console.SetCursorPosition(INPUT_X, INPUT_Y);
        Console.Write(spaces);

        return message;
    }

    static void Play()
    {
        int score = 0;
        int mistakes = 0;
        PrintBoardCenteredTitle(1);
        PrintBoard();

        while (true)
        {
            int messageLength = RandomInInclusiveRange(MIN_MESSAGE_LENGTH, MAX_MESSAGE_LENGTH);
            if (Message(messageLength) != MessageUnderstood())
            {
                mistakes++;
                switch (mistakes)
                {
                    case 1:
                        PrintMistakeEffect("The table begins to shake!");
                        break;
                    case 2:
                        PrintMistakeEffect("The light bulb shatters!");
                        break;
                    case 3:
                        PrintMistakeEffect("Oh, no!  A pair of clammy hands grasps your neck!");
                        return;
                }
            }
            else
            {
                score += messageLength;
                if (score >= MAX_SCORE)
                {
                    PrintBoardCentered("Whew!  The spirits have gone!", MESSAGES_Y);
                    PrintBoardCentered("You live to face another day!", MESSAGES_Y + 1);
                    return;
                }
            }
        }
    }

    static void Main()
    {
        Console.ForegroundColor = DEFAULT_COLOR;
        Console.Clear();
        PrintCredits();

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

        PressEnter("\nPress the Enter key to start. ");
        Console.Clear();
        Play();
        Console.WriteLine("\n");
    }
}

Sine Wave

// Sine Wave

// Original version in BASIC:
//  Anonymous, 1978.
//  Creative Computing's BASIC Games.
//  - https://www.atariarchives.org/basicgames/showpage.php?page=146
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/sinewave.bas
//  - http://www.retroarchive.org/cpm/games/ccgames.zip

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-20.
//
// Last modified: 20251205T1548+0100.

using System;

class SineWave
{
    static string[] word = {"", ""};

    // Ask the user to enter two words and store them into the `word` array.
    //
    static void getWords()
    {
        string[] order = {"first", "second"};

        int n = 0;
        while (n < 2)
        {
            while (word[n] == "")
            {
                Console.Write($"Enter the {order[n]} word: ");
                word[n] = Console.ReadLine();
            }
            n++;
        }
    }

    static void printCredits()
    {
        Console.WriteLine("Sine Wave\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        Console.Write("Press Enter to start. ");
        Console.ReadKey();
    }

    static void draw()
    {
        bool even = false;
        double angle = 0.0;

        while (angle <= 40.0)
        {
            int i = 0;
            while (i < (int) (26 + 25 * Math.Sin(angle)))
            {
                Console.Write(" ");
                i++;
            }
            Console.WriteLine(word[even ? 1 : 0]);
            even = ! even;
            angle += 0.25;
        }
    }

    static void Main()
    {
        Console.Clear();
        printCredits();
        Console.Clear();
        getWords();
        Console.Clear();
        draw();
    }
}

Slots

// Slots
// A slot machine simulation.

// Original version in BASIC:
//  By Fred Mirabelle and Bob Harper, 1973-01-29.
//  Creative Computing's BASIC Games.
//  - https://www.atariarchives.org/basicgames/showpage.php?page=149
//  - http://vintage-basic.net/games.html
//  - http://vintage-basic.net/bcg/slots.bas

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written in 2024-12-22/24.
//
// Last modified: 20251205T1549+0100.

using System;
using System.Diagnostics;

class Slots
{
    static string[] image = {" BAR  ", " BELL ", "ORANGE", "LEMON ", " PLUM ", "CHERRY"};
    const int BAR = 0; // position of "BAR" in `image`.
    static ConsoleColor[] color =
    {
        ConsoleColor.White,
        ConsoleColor.DarkCyan,
        ConsoleColor.DarkYellow,
        ConsoleColor.Yellow,
        ConsoleColor.White,
        ConsoleColor.Red };
    const int MAX_BET = 100;
    const int MIN_BET = 1;

    static void PressEnter(string prompt)
    {
        Console.Write(prompt);
        Console.ReadLine();
    }

    static void PrintCredits()
    {
        Console.Clear();
        Console.WriteLine("Slots");
        Console.WriteLine("A slot machine simulation.\n");
        Console.WriteLine("Original version in BASIC:");
        Console.WriteLine("    Creative computing (Morristown, New Jersey, USA).");
        Console.WriteLine("    Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n");
        Console.WriteLine("This version in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair\n");
        PressEnter("Press Enter for instructions.");
    }

    static void PrintInstructions()
    {
        Console.Clear();
        Console.WriteLine("You are in the H&M casino, in front of one of our");
        Console.WriteLine($"one-arm bandits. Bet from {MIN_BET} to {MAX_BET} USD (or 0 to quit).\n");
        PressEnter("Press Enter to start.");
    }

    static int Won(int prize, int bet)
    {
        switch (prize)
        {
            case   2: Console.WriteLine("DOUBLE!"); break;
            case   5: Console.WriteLine("*DOUBLE BAR*"); break;
            case  10: Console.WriteLine("**TOP DOLLAR**"); break;
            case 100: Console.WriteLine("***JACKPOT***"); break;
        }
        Console.WriteLine("You won!");
        return (prize + 1) * bet;
    }

    static void ShowStandings(int usd)
    {
        Console.WriteLine($"Your standings are {usd} USD.");
    }

    static void PrintReels(int[] reel)
    {
        ConsoleColor currentBackgroundColor = Console.BackgroundColor;
        ConsoleColor currentForegroundColor = Console.ForegroundColor;
        Console.SetCursorPosition(0, 0);
        foreach (int r in reel)
        {
            Console.ForegroundColor = color[r];
            Console.Write($"[{image[r]}] ");
        }
        Console.BackgroundColor = currentBackgroundColor;
        Console.ForegroundColor = currentForegroundColor;
        Console.WriteLine("");
    }

    static void InitReels(ref int[] reel)
    {
        Random rand = new Random();
        int images = image.Length;
        for (int i = 0; i < reel.Length; i++)
        {
            reel[i] = rand.Next(images);
        }
    }

    static void SpinReels(ref int[] reel)
    {
        const int MILLISECONDS = 2000;
        Console.CursorVisible = false;
        Stopwatch timer = new Stopwatch();
        timer.Start();
        while(timer.ElapsedMilliseconds < MILLISECONDS)
        {
            InitReels(ref reel);
            PrintReels(reel);
        }
        Console.CursorVisible = true;
    }

    // Return the number of equals and bars in the given array.
    //
    static (int, int) Prize(int[] reel)
    {
        int equals = 0;
        int bars = 0;

        for (int i = 0; i < reel.Length; i++)
        {
            for (int j = i + 1; j < reel.Length; j++)
            {
                equals +=  Convert.ToInt32(reel[i] == reel[j]);
            }
        }
        if (equals > 0 && equals < reel.Length)
        {
            equals++;
        }

        foreach (int i in reel)
        {
            bars += Convert.ToInt32(i == BAR);
        }
        return (equals, bars);
    }

    static string GetString(string prompt)
    {
        Console.Write(prompt);
        return Console.ReadLine();
    }

    static int GetInteger(string prompt)
    {
        int n = 0;

        while (true)
        {
            try
            {
                n = Int32.Parse(GetString(prompt));
                break;
            }
            catch { }
        }

        return n;
    }

    static void Play()
    {
        int standings = 0;
        int bet = 0;
        int[] reel = {0, 0, 0};
        int equals = 0;
        int bars = 0;

        InitReels(ref reel);

        while (true) { // play loop

            while (true) { // bet loop
                Console.Clear();
                PrintReels(reel);
                bet = GetInteger("Your bet (or 0 to quit): ");
                if (bet > MAX_BET)
                {
                    Console.WriteLine($"House limits are {MAX_BET} USD.");
                    PressEnter("Press Enter to try again.");
                }
                else if (bet < MIN_BET)
                {
                    Console.WriteLine("Type \"q\" to confirm you want to quit.");
                    string confirm = Console.ReadLine().ToLower();
                    if (confirm == "q")
                    {
                        goto showStandings;
                    }
                }
                else
                {
                    break; // bet loop
                };
            };

            Console.Clear();
            SpinReels(ref reel);
            (equals, bars) = Prize(reel);

            switch (equals)
            {
                case 3:
                    if (bars == 3)
                    {
                        standings += Won(100, bet);
                    }
                    else
                    {
                        standings += Won(10, bet);
                    };
                    break;
                case 2:
                    if (bars == 2)
                    {
                        standings += Won(5, bet);
                    }
                    else
                    {
                        standings += Won(2, bet);
                    }
                    break;
                default:
                    Console.WriteLine("You lost.");
                    standings -= bet;
                    break;
            };

            ShowStandings(standings);
            PressEnter("Press Enter to continue.");
        }; // play loop

        showStandings:

        ShowStandings(standings);
        if (standings < 0)
            Console.WriteLine("Pay up!  Please leave your money on the terminal.");
        else if (standings == 0)
            Console.WriteLine("Hey, you broke even.");
        else if (standings > 0)
            Console.WriteLine("Collect your winnings from the H&M cashier.");
    }

    static void Main()
    {
        PrintCredits();
        PrintInstructions();
        Play();
    }
}

Stars

// Stars

// Original version in BASIC:
//  Example included in Vintage BASIC 1.0.3.
//  http://www.vintage-basic.net

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2024-12-21.
//
// Last modified: 20251205T1549+0100.

using System;
using System.Linq;

class Stars
{
    static void Main()
    {
        Console.Write("What is your name? ");
        string name = Console.ReadLine();
        Console.WriteLine($"Hello, {name}.");

        string answer;
        string[] afirmativeAnswers = {"ok", "yeah", "yes", "y"};

        do
        {
            double number;

            while (true)
            {
                Console.Write("Enter a number: ");
                try
                {
                    number = Int64.Parse(Console.ReadLine());
                    break;
                }
                catch
                {
                    Console.WriteLine("Integer expected.");
                }
            }

            for (int i = 0; i < number; i++)
            {
                Console.Write("*");
            }
            Console.WriteLine();

            Console.Write("Do you want more stars? ");
            answer = Console.ReadLine().ToLower();
        }
        while (afirmativeAnswers.Contains(answer));
    }
}

Strings

// Strings

// Original version in BASIC:
//  Example included in Vintage BASIC 1.0.3.
//  http://www.vintage-basic.net

// This version in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written in 2024-12-21/25.
//
// Last modified: 20251205T1549+0100.

using System;

class vintageBasicStrings
{
    static string GetString(string prompt)
    {
        Console.Write(prompt);
        return Console.ReadLine();
    }

    static int GetInteger(string prompt)
    {
        int n = 0;

        while (true)
        {
            try
            {
                n = Int32.Parse(GetString(prompt));
                break;
            }
            catch
            {
            }
        }

        return n;
    }

    static void Main()
    {
        string s = GetString("Enter a string: ");
        int n = GetInteger("Enter an integer: ");

        Console.WriteLine();

        Console.Write($"ASC(\"{s}\") --> ");
        Console.Write($"(int) \"{s}\"[0] --> ");
        Console.WriteLine((int) s[0]);

        Console.Write($"CHR$({n}) --> ");
        Console.Write($"(char) ({n}) --> ");
        Console.WriteLine("'" + (char) n + "'");

        Console.Write($"LEFT$(\"{s}\", {n}) --> ");
        Console.Write($"\"{s}\".Substring(0, Math.Min({n}, \"{s}\".Length) --> ");
        Console.WriteLine("\"" + s.Substring(0, Math.Min(n, s.Length)) + "\"");

        Console.Write($"MID$(\"{s}\", {n}) --> ");
        Console.Write($"{n} > \"{s}\".Length ? \"\" : \"{s}\".Substring({n} - 1) --> ");
        Console.WriteLine("\"" + (n > s.Length ? "" : s.Substring(n - 1)) + "\"");

        Console.Write($"MID$(\"{s}\", {n}, 3) --> ");
        Console.Write($"{n} > \"{s}\".Length ? \"\" : \"{s}\".Substring({n} - 1, Math.Min(3, \"{s}\".Length - {n} + 1)) --> ");
        Console.WriteLine("\"" + (n > s.Length ? "" : s.Substring(n - 1, Math.Min(3, s.Length - n + 1)) + "\""));

        Console.Write($"RIGHT$(\"{s}\", {n}) --> ");
        Console.Write($"\"{s}\".Substring(Math.Max(0, \"{s}\".Length - {n}) --> ");
        Console.WriteLine("\"" + s.Substring(Math.Max(0, s.Length - n)) + "\"");

        Console.Write($"LEN(\"{s}\") --> ");
        Console.Write($"\"{s}\".Length --> ");
        Console.WriteLine(s.Length);

        Console.Write($"VAL(\"{s}\") --> ");
        int v;
        try{ v = Int32.Parse(s); } catch { v = 0; };
        Console.WriteLine($"int v; try{{ v = Int32.Parse(\"{s}\"); }} catch {{ v = 0; }} --> {v}");

        Console.Write($"STR$({n}) --> ");
        Console.Write($"Convert.ToString({n}) --> ");
        Console.WriteLine("\"" + Convert.ToString(n) + "\"");

        Console.Write($"SPC({n}) --> ");
        Console.Write($"new string(' ', {n}) --> ");
        Console.WriteLine("\"" + new string(' ', n) + "\"");
    }
}

Xchange

// Xchange

// Original version in BASIC:
//  By Thomas C. McIntire, 1979.
//  Published in "The A to Z Book of Computer Games", 1979.
//  - https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up
//  - https://github.com/chaosotter/basic-games

// This improved remake in C#:
//  Copyright (c) 2024, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written in 2024-12-29/2025-01-02.
//
// Last modified: 20251205T1550+0100.

using System;
using System.Linq;

class xchange
{
    // Globals {{{1
    // =============================================================

    const ConsoleColor BOARD_INK = ConsoleColor.Cyan;
    const ConsoleColor DEFAULT_INK = ConsoleColor.White;
    const ConsoleColor INPUT_INK = ConsoleColor.Green;
    const ConsoleColor INSTRUCTIONS_INK = ConsoleColor.DarkYellow;
    const ConsoleColor TITLE_INK = ConsoleColor.Red;

    const string BLANK = "*";

    const int GRID_HEIGHT = 3; // cell rows
    const int GRID_WIDTH = 3; // cell columns

    const int CELLS = GRID_WIDTH * GRID_HEIGHT;

    static string[] pristineGrid = new string[CELLS];

    const int GRIDS_ROW = 3; // screen row where the grids are printed
    const int GRIDS_COLUMN = 5; // screen column where the left grid is printed
    const int CELLS_GAP = 2; // distance between the grid cells, in screen rows or columns
    const int GRIDS_GAP = 16; // screen columns between equivalent cells of the grids

    const int MAX_PLAYERS = 4;

    static string[][] grid = new string[MAX_PLAYERS][];

    static bool[] isPlaying = new bool[MAX_PLAYERS];

    static int players;

    const string QUIT_COMMAND = "X";

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

    // Print the given prompt and wait until the user enters an integer.
    //
    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;
    }

    // Print the given prompt and wait until the user enters a string.
    //
    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 and wait until the user presses Enter.
    //
    static void PressEnter(string prompt)
    {
        InputString(prompt);
    }

    // Return `true` if the given string is "yes" or a synonym.
    //
    static bool IsYes(string s)
    {
        string[] validOptions = {"ok", "y", "yeah", "yes"};
        return validOptions.Contains(s.ToLower());
    }

    // Return `true` if the given string is "no" or a synonym.
    //
    static bool IsNo(string s)
    {
        string[] validOptions = {"n", "no", "nope"};
        return validOptions.Contains(s);
    }

    // Print the given prompt, wait until the user enters a valid yes/no string,
    // and return `true` for "yes" or `false` for "no".
    //
    static bool Yes(string prompt)
    {
        while (true)
        {
            string answer = InputString(prompt);
            if (IsYes(answer))
            {
                return true;
            }
            if (IsNo(answer))
            {
                return false;
            }
        }
    }

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

    // Print the title at the current cursor position.
    //
    static void PrintTitle()
    {
        Console.ForegroundColor = TITLE_INK;
        Console.WriteLine("Xchange");
        Console.ForegroundColor = DEFAULT_INK;
    }

    // Print the credits at the current cursor position.
    //
    static void PrintCredits()
    {
        PrintTitle();
        Console.WriteLine("\nOriginal version in BASIC:");
        Console.WriteLine("    Written by Thomas C. McIntire, 1979.");
        Console.WriteLine("    Published in \"The A to Z Book of Computer Games\", 1979.");
        Console.WriteLine("    https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up");
        Console.WriteLine("    https://github.com/chaosotter/basic-games");
        Console.WriteLine("\nThis improved remake in C#:");
        Console.WriteLine("    Copyright (c) 2024, Marcos Cruz (programandala.net)");
        Console.WriteLine("    SPDX-License-Identifier: Fair");
    }

    // Print the instructions at the current cursor position.
    //
    static void PrintInstructions()
    {
        PrintTitle();
        Console.ForegroundColor = INSTRUCTIONS_INK;
        Console.WriteLine("\nOne or two may play.  If two, you take turns.  A grid looks like this:\n");
        Console.ForegroundColor = BOARD_INK;
        Console.WriteLine("    F G D");
        Console.Write($"    A H {BLANK}\n");
        Console.WriteLine("    E B C\n");
        Console.ForegroundColor = INSTRUCTIONS_INK;
        Console.WriteLine("But it should look like this:\n");
        Console.ForegroundColor = BOARD_INK;
        Console.WriteLine("    A B C");
        Console.WriteLine("    D E F");
        Console.Write($"    G H {BLANK}\n\n");
        Console.ForegroundColor = INSTRUCTIONS_INK;
        Console.Write($"You may exchange any one letter with the '{BLANK}', but only one that's adjacent:\n");
        Console.WriteLine("above, below, left, or right.  Not all puzzles are possible, and you may enter");
        Console.Write($"'{QUIT_COMMAND}' to give up.\n\n");
        Console.WriteLine("Here we go...");
        Console.ForegroundColor = DEFAULT_INK;
    }

    // Grids {{{1
    // =============================================================

    // Print the given player's grid title.
    //
    static void PrintGridTitle(int player)
    {
        Console.SetCursorPosition(GRIDS_COLUMN + (player * GRIDS_GAP), GRIDS_ROW);
        Console.Write($"Player {player + 1}");
    }

    // Return the cursor position of the given player's grid cell.
    //
    static (int row, int column) CellPosition(int player, int cell)
    {
        int gridRow = cell / GRID_HEIGHT;
        int gridColumn = cell % GRID_WIDTH;
        int titleMargin = players > 1 ? 2 : 0;
        return (GRIDS_ROW + titleMargin + gridRow, GRIDS_COLUMN + (gridColumn * CELLS_GAP) + (player * GRIDS_GAP));
    }

    // Return the cursor position of the given player's grid prompt.
    //
    static (int row, int column) GridPromptPosition(int player)
    {
        int gridRow = CELLS / GRID_HEIGHT;
        int gridColumn = CELLS % GRID_WIDTH;
        int titleMargin = players > 1 ? 2 : 0;
        return (GRIDS_ROW + titleMargin + gridRow + 1, GRIDS_COLUMN + (gridColumn * CELLS_GAP) + (player * GRIDS_GAP));
    }

    // Print the given player's grid, in the given or default color.
    //
    static void PrintGrid(int player, ConsoleColor color = BOARD_INK)
    {
        if (players > 1)
        {
            PrintGridTitle(player);
        }
        Console.ForegroundColor = color;
        for (int cell = 0; cell < CELLS; cell++)
        {
            (int y, int x) = CellPosition(player, cell);
            Console.SetCursorPosition(x, y);
            Console.Write(grid[player][cell]);
        }
        Console.ForegroundColor = DEFAULT_INK;
    }

    // Print the current players' grids.
    //
    static void PrintGrids()
    {
        for (int player = 0; player < players; player++)
        {
            if (isPlaying[player])
            {
                PrintGrid(player);
            }
        }
        Console.WriteLine();
        // EraseScreenDown(); // XXX TODO
    }

    // Scramble the grid of the given player.
    //
    static void ScrambleGrid(int player)
    {
        Random rand = new Random();
        for (int cell = 0; cell < CELLS; cell++)
        {
            int randomCell = rand.Next(CELLS);
            // Exchange the contents of the current cell with that of the random one.
            string tmp = grid[player][cell];
            grid[player][cell] = grid[player][randomCell];
            grid[player][randomCell] = tmp;
        }
    }

    // Init the grids.
    //
    static void InitGrids()
    {
        grid[0] = (string[]) pristineGrid.Clone();
        ScrambleGrid(0);
        for (int player = 0 + 1; player < players; player++)
        {
            grid[player] = (string[]) grid[0].Clone();
        }
    }

    // Messages {{{1
    // =============================================================

    // Return a message prefix for the given player.
    //
    static string PlayerPrefix(int player)
    {
        return players > 1 ? $"Player {player + 1}: " : "";
    }

    // Return the cursor position of the given player's messages, adding the given
    // row increment, which defaults to zero.
    //
    static (int row, int column) MessagePosition(int player, int rowInc = 0)
    {
        (int promptRow, _) = GridPromptPosition(player);
        return (promptRow + 2 + rowInc, 1);
    }

    // Erase the current line to the right from the position of the cursor.
    //
    static void EraseLineRight()
    {
        int y = Console.CursorTop;
        int x = Console.CursorLeft;
        string blank = new String(' ', Console.WindowWidth - x - 1);

        Console.Write(blank);
        Console.SetCursorPosition(x, y);
    }

    // Print the given message about the given player, adding the given row
    // increment, which defaults to zero, to the default cursor coordinates.
    //
    static void PrintMessage(string message, int player, int rowInc = 0)
    {
        (int y, int x) = MessagePosition(player, rowInc);
        Console.SetCursorPosition(x, y);
        Console.Write($"{PlayerPrefix(player)}{message}");
        EraseLineRight();
        Console.WriteLine();
    }

    // Erase the last message about the given player.
    //
    static void EraseMessage(int player)
    {
        (int y, int x) = MessagePosition(player);
        Console.SetCursorPosition(x, y);
        EraseLineRight();
    }

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

    // Return a message with the players range.
    //
    static string PlayersRangeMessage()
    {
        return (MAX_PLAYERS == 2) ? "1 or 2" : $"from 1 to {MAX_PLAYERS}";
    }

    // Return the number of players, asking the user if needed.
    //
    static int NumberOfPlayers()
    {
        int players = 0;
        PrintTitle();
        Console.WriteLine();
        while (players < 1 || players > MAX_PLAYERS)
        {
            players = InputInt($"Number of players ({PlayersRangeMessage()}): ");
        }
        return players;
    }

    // Is the given cell the first one on a grid row?
    //
    static bool isFirstCellOfA_gridRow(int cell)
    {
        return cell % GRID_WIDTH == 0;
    }

    // Is the given cell the last one on a grid row?
    //
    static bool isLastCellOfA_gridRow(int cell)
    {
        return (cell + 1) % GRID_WIDTH == 0;
    }

    // Are the given cells adjacent?
    //
    static bool AreCellsAdjacent(int cell_1, int cell_2)
    {
        if (cell_2 == cell_1 + 1 && ! isFirstCellOfA_gridRow(cell_2))
            return true;
        else if (cell_2 == cell_1 + GRID_WIDTH)
            return true;
        else if (cell_2 == cell_1 - 1  && ! isLastCellOfA_gridRow(cell_2))
            return true;
        else if (cell_2 == cell_1 - GRID_WIDTH)
            return true;
        else
            return false;
    }

    // Is the given player's character cell a valid move, i.e. is it adjacent to
    // the blank cell? If so, return the blank cell of the grid and `true`
    // otherwise return a fake cell and `false`.
    //
    static (int blankCell, bool ok) IsLegalMove(int player, int charCell)
    {
        const int NOWHERE = -1;

        for (int cell = 0; cell < CELLS; cell++)
        {
            if (grid[player][cell] == BLANK)
            {
                if (AreCellsAdjacent(charCell, cell))
                {
                    return (cell, true);
                }
                break;
            }
        }

        PrintMessage($"Illegal move \"{grid[player][charCell]}\".", player);
        return (NOWHERE, false);
    }

    // Is the given player's command valid, i.e. a grid character? If so, return
    // its position in the grid and `true`; otherwise print an error message and
    // return a fake position and `false`.
    //
    static (int position, bool ok) IsValidCommand(int player, string command)
    {
        // XXX FIXME

        const int NOWHERE = -1;
        if (command != BLANK)
        {
            for (int i = 0; i < CELLS; i++)
            {
                if (grid[player][i] == command)
                {
                    return (i, true);
                }
            }
        }
        PrintMessage($"Invalid character \"{command}\".", player);
        return (NOWHERE, false);
    }

    // Forget the given player, who quitted.
    //
    static void ForgetPlayer(int player)
    {
        isPlaying[player] = false;
        PrintGrid(player, DEFAULT_INK);
    }

    // Play the turn of the given player.
    //
    static void PlayTurn(int player)
    {
        int blankPosition;
        int characterPosition = 0;

        if (isPlaying[player])
        {
            while (true)
            {
                string command;
                bool ok = false;
                while (! ok)
                {
                    (int y, int x) = GridPromptPosition(player);
                    Console.SetCursorPosition(x, y);
                    EraseLineRight();
                    Console.SetCursorPosition(x, y);
                    command = InputString("Move: ").Trim().ToUpper();
                    if (command == QUIT_COMMAND)
                    {
                        ForgetPlayer(player);
                        return;
                    }
                    (characterPosition, ok) = IsValidCommand(player, command);
                }
                (blankPosition, ok) = IsLegalMove(player, characterPosition);
                if (ok)
                {
                    break;
                }
            }
            EraseMessage(player);
            grid[player][blankPosition] = grid[player][characterPosition];
            grid[player][characterPosition] = BLANK;
        }
    }

    // Play the turns of all players.
    //
    static void PlayTurns()
    {
        for (int player = 0; player < players; player++) PlayTurn(player);
    }

    // Is someone playing?
    //
    static bool IsSomeonePlaying()
    {
        for (int player = 0; player < players; player++)
        {
            if (isPlaying[player])
            {
                return true;
            }
        }
        return false;
    }

    // Has someone won? If so, print a message for every winner and return `true`
    // otherwise just return `false`.
    //
    static bool HasSomeoneWon()
    {
        int winners = 0;

        for (int player = 0; player < players; player++)
        {
            if (isPlaying[player])
            {
                if (grid[player].SequenceEqual(pristineGrid))
                {
                    winners += 1;
                    if (winners > 0)
                    {
                        string too = winners > 1 ? ", too" : "";
                        PrintMessage
                        (
                            $"You're the winner{too}!",
                            player,
                            winners - 1
                        );
                    }
                }
            }
        }

        return winners > 0;
    }

    // Init the game.
    //
    static void InitGame()
    {
        Console.Clear();
        players = NumberOfPlayers();
        for (int player = 0; player < players; player++)
        {
            isPlaying[player] = true;
        }
        Console.Clear();
        PrintTitle();
        InitGrids();
        PrintGrids();
    }

    // Play the game.
    //
    static void Play()
    {
        InitGame();
        while (IsSomeonePlaying())
        {
            PlayTurns();
            PrintGrids();
            if (HasSomeoneWon())
            {
                break;
            }
        }
    }

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

    // Init the program, i.e. just once before the first game.
    //
    static void InitOnce()
    {
        // Init the pristine grid.
        const int FIRST_CHAR_CODE = (int) 'A';
        for (int i = 0; i < CELLS - 1; i++)
        {
            pristineGrid[i] = ((char) (FIRST_CHAR_CODE + i)).ToString();
        }
        pristineGrid[CELLS - 1] = BLANK;

        // Init the grid
        for (int i = 0; i < MAX_PLAYERS; i++)
        {
            grid[i] = new string[CELLS];
            grid[i] = pristineGrid;
        }
    }

    // Return `true` if the player does not want to play another game; otherwise
    // return `false`.
    //
    static bool Enough()
    {
        (int y, int x) = (GridPromptPosition(player : 0));
        Console.SetCursorPosition(x, y);
        return ! Yes("Another game? ");
    }

    static void Main()
    {
        InitOnce();

        Console.Clear();
        PrintCredits();

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

        PressEnter("\nPress the Enter key to start. ");
        while (true)
        {
            Play();
            if (Enough())
            {
                break;
            }
        }
        Console.WriteLine("So long…");
    }
}

Related pages

Basics off
Metaproject about the "Basics of…" projects.
Basics of 8th
Conversion of old BASIC programs to 8th in order to learn the basics of this language.
Basics of Ada
Conversion of old BASIC programs to Ada in order to learn the basics of this language.
Basics of Arturo
Conversion of old BASIC programs to Arturo in order to learn the basics of this language.
Basics of C3
Conversion of old BASIC programs to C3 in order to learn the basics of this language.
Basics of Chapel
Conversion of old BASIC programs to Chapel in order to learn the basics of this language.
Basics of Clojure
Conversion of old BASIC programs to Clojure in order to learn the basics of this language.
Basics of Crystal
Conversion of old BASIC programs to Crystal in order to learn the basics of this language.
Basics of D
Conversion of old BASIC programs to D in order to learn the basics of this language.
Basics of Elixir
Conversion of old BASIC programs to Elixir in order to learn the basics of this language.
Basics of F#
Conversion of old BASIC programs to F# in order to learn the basics of this language.
Basics of Factor
Conversion of old BASIC programs to Factor in order to learn the basics of this language.
Basics of FreeBASIC
Conversion of old BASIC programs to FreeBASIC in order to learn the basics of this language.
Basics of Gleam
Conversion of old BASIC programs to Gleam in order to learn the basics of this language.
Basics of Go
Conversion of old BASIC programs to Go in order to learn the basics of this language.
Basics of Hare
Conversion of old BASIC programs to Hare in order to learn the basics of this language.
Basics of Haxe
Conversion of old BASIC programs to Haxe in order to learn the basics of this language.
Basics of Icon
Conversion of old BASIC programs to Icon in order to learn the basics of this language.
Basics of Io
Conversion of old BASIC programs to Io in order to learn the basics of this language.
Basics of Janet
Conversion of old BASIC programs to Janet in order to learn the basics of this language.
Basics of Julia
Conversion of old BASIC programs to Julia in order to learn the basics of this language.
Basics of Kotlin
Conversion of old BASIC programs to Kotlin in order to learn the basics of this language.
Basics of Lobster
Conversion of old BASIC programs to Lobster in order to learn the basics of this language.
Basics of Lua
Conversion of old BASIC programs to Lua in order to learn the basics of this language.
Basics of Nature
Conversion of old BASIC programs to Nature in order to learn the basics of this language.
Basics of Neat
Conversion of old BASIC programs to Neat in order to learn the basics of this language.
Basics of Neko
Conversion of old BASIC programs to Neko in order to learn the basics of this language.
Basics of Nelua
Conversion of old BASIC programs to Nelua in order to learn the basics of this language.
Basics of Nim
Conversion of old BASIC programs to Nim in order to learn the basics of this language.
Basics of Nit
Conversion of old BASIC programs to Nit in order to learn the basics of this language.
Basics of Oberon-07
Conversion of old BASIC programs to Oberon-07 in order to learn the basics of this language.
Basics of OCaml
Conversion of old BASIC programs to OCaml in order to learn the basics of this language.
Basics of Odin
Conversion of old BASIC programs to Odin in order to learn the basics of this language.
Basics of Pike
Conversion of old BASIC programs to Pike in order to learn the basics of this language.
Basics of Pony
Conversion of old BASIC programs to Pony in order to learn the basics of this language.
Basics of Python
Conversion of old BASIC programs to Python in order to learn the basics of this language.
Basics of Racket
Conversion of old BASIC programs to Racket in order to learn the basics of this language.
Basics of Raku
Conversion of old BASIC programs to Raku in order to learn the basics of this language.
Basics of Retro
Conversion of old BASIC programs to Retro in order to learn the basics of this language.
Basics of Rexx
Conversion of old BASIC programs to Rexx in order to learn the basics of this language.
Basics of Ring
Conversion of old BASIC programs to Ring in order to learn the basics of this language.
Basics of Rust
Conversion of old BASIC programs to Rust in order to learn the basics of this language.
Basics of Scala
Conversion of old BASIC programs to Scala in order to learn the basics of this language.
Basics of Scheme
Conversion of old BASIC programs to Scheme in order to learn the basics of this language.
Basics of Styx
Conversion of old BASIC programs to Styx in order to learn the basics of this language.
Basics of Swift
Conversion of old BASIC programs to Swift in order to learn the basics of this language.
Basics of V
Conversion of old BASIC programs to V in order to learn the basics of this language.
Basics of Vala
Conversion of old BASIC programs to Vala in order to learn the basics of this language.
Basics of Zig
Conversion of old BASIC programs to Zig in order to learn the basics of this language.

External related links