Basics of D

Description of the page content

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

Tags:

3D Plot

/*
3D Plot

Original version in BASIC:
    Creative Computing (Morristown, New Jersey, USA), ca. 1980.

This version in D:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-03-25/26.

Last modified 20251220T0654+0100.
*/

module three_d_plot;

/// Erase the screen, reset the attributes and move the cursor to the home
/// position.

void clear()
{
    import std.stdio : write;

    write("\033[2J\033[0m\033[H");
}

/// Clear the screen, display the credits and wait for a keypress.

void printCredits()
{
    import std.stdio : readln;
    import std.stdio : writeln;

    clear();
    writeln("3D Plot\n");
    writeln("Original version in BASIC:");
    writeln("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2023, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    writeln("Press Enter to start the program.");
    readln();
}

float a(float z)
{
    import std.math : exp;

    return 30 * exp(-z * z / 100);
}

void draw()
{
    import std.stdio : write;
    import std.stdio : writeln;
    import std.math : sqrt;

    enum char dot   = '*';
    enum char space = ' ';
    enum int width = 56;

    int l = 0;
    int z = 0;
    int y1 = 0;
    char[width] line;
    clear();
    for (float x = -30.0; x <= 30.0; x += 1.5)
    {
        for (int pos = 0; pos<width; pos++)
        {
            line[pos] = space;
        }
        l = 0;
        y1 = 5 * cast(int)(sqrt(900 - x * x) / 5);
        for (int y = y1; y >= -y1; y += -5)
        {
            z = cast(int)(25 + a(sqrt(x * x + cast(float)(y * y))) - 0.7 * cast(float)(y));
            if (z > l)
            {
                l = z;
                line[z] = dot;
            }
        } // y loop
        for (int pos = 0; pos<width; pos++)
        {
            write(line[pos]);
        }
        writeln();
    } // x loop
}

void main()
{
    printCredits();
    draw();
}

Bagels

// Bagels

// Original version in BASIC:
//     D. Resek, P. Rowe, 1978.
//     Creative Computing (Morristown, New Jersey, USA), 1978.

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-24.
//
// Last modified: 20251220T0636+0100.

module bagels;

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

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

// Clear the screen, display the credits and wait for a keypress.
//
void printCredits()
{
    import std.stdio : writeln;

    clearScreen();
    writeln("Bagels");
    writeln("Number guessing game\n");
    writeln("Original source unknown but suspected to be:");
    writeln("    Lawrence Hall of Science, U.C. Berkely.\n");
    writeln("Original version in BASIC:");
    writeln("    D. Resek, P. Rowe, 1978.");
    writeln("    Creative computing (Morristown, New Jersey, USA), 1978.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    acceptString("Press Enter to read the instructions. ");
}

// Clear the screen, print the instructions and wait for a keypress.
//
void printInstructions()
{
    import std.stdio : writeln;

    clearScreen();
    writeln("Bagels");
    writeln("Number guessing game\n");
    writeln("I am thinking of a three-digit number that has no two digits the same.");
    writeln("Try to guess it and I will give you clues as follows:\n");
    writeln("   PICO   - one digit correct but in the wrong position");
    writeln("   FERMI  - one digit correct and in the right position");
    writeln("   BAGELS - no digits correct\n");
    acceptString("Press Enter to start. ");
}

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

// Return `true` if the given string is "yes" or a synonym.
//
bool isYes(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

// Return `true` if the given string is "no" or a synonym.
//
bool isNo(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

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

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

enum DIGITS = 3;

bool isDigit(int asciiCode)
{
    return (asciiCode >= '0') && (asciiCode <= '9');
}

// Print the given prompt and return an array with a three-digit number typed
// by the user.

int[DIGITS] getInput(string prompt)
{
    import std.stdio : writefln;
    import std.stdio : writeln;

    enum char ASCII_0 = '0';
    int[DIGITS] userDigit;

    getLoop: while (true)
    {
        string input = acceptString(prompt);
        if (input.length != DIGITS)
        {
            writefln("Remember it's a %d-digit number.", DIGITS);
            continue getLoop;
        }
        foreach (int pos; 0 .. cast(int)input.length)
        {
            int digit = input[pos];
            if (isDigit(digit))
            {
                userDigit[pos] = digit - ASCII_0;
            }
            else
            {
                writeln("What?");
                continue getLoop;
            }
        }
        if (isAnyRepeated(userDigit))
        {
            writeln("Remember my number has no two digits the same.");
            continue getLoop;
        }
        break;
    }
    return userDigit;
}

// Return three random digits.

int[DIGITS] randomNumber()
{
    import std.random : uniform;

    int[DIGITS] randomDigit;
    foreach (int i; 0 .. DIGITS)
    {
        digitLoop: while (true)
        {
            randomDigit[i] = uniform(0, 10);
            foreach (int j; 0 .. i)
            {
                if (i != j && randomDigit[i] == randomDigit[j])
                {
                    continue digitLoop;
                }
            }
            break;
        }
    }
    return randomDigit;
}

// Return `true` if any of the given numbers is repeated; otherwise return
// `false`.

bool isAnyRepeated(int[] numbers)
{
    import std.algorithm.comparison : equal;
    import std.algorithm.iteration : uniq;

    auto filteredNumbers = uniq(numbers);
    return !equal(filteredNumbers, numbers);
}

// Init and run the game loop.

void play()
{
    import std.array : replicate;
    import std.format : format;
    import std.stdio : write;
    import std.stdio : writef;
    import std.stdio : writefln;
    import std.stdio : writeln;

    enum TRIES = 20;

    int score = 0;
    int fermi = 0; // counter
    int pico = 0; // counter
    int[DIGITS] computerNumber;
    int[DIGITS] userNumber;

    while (true)
    {
        clearScreen();
        computerNumber = randomNumber();
        writeln("O.K.  I have a number in mind.");
        foreach (int guess; 1 .. TRIES + 1)
        {
            userNumber = getInput(format("Guess #%02d: ", guess));
            fermi = 0;
            pico = 0;
            foreach (int i; 0 .. DIGITS)
            {
                foreach (int j; 0 .. DIGITS)
                {
                    if (userNumber[i] == computerNumber[j])
                    {
                        if (i == j)
                        {
                            fermi += 1;
                        }
                        else
                        {
                            pico += 1;
                        }
                    }
                }
            }
            if (pico + fermi == 0)
            {
                writeln("BAGELS");
            }
            else
            {
                writeln(
                        replicate("PICO ", pico),
                        replicate("FERMI ", fermi)
                        );
                if (fermi == DIGITS)
                {
                    break;
                }
            }
        }
        if (fermi == DIGITS)
        {
            writeln("You got it!!!");
            score += 1;
        }
        else
        {
            writeln("Oh well.");
            writef("That's %d guesses.  My number was ", TRIES);
            foreach (int i; 0 .. DIGITS)
            {
                write(computerNumber[i]);
            }
            writeln(".");
        }
        if (!yes("Play again? "))
        {
            break;
        }
    }
    if (score != 0)
    {
        writefln("A %d-point bagels, buff!!", score);
    }
    writeln("Hope you had fun.  Bye.");
}

void main()
{
    printCredits();
    printInstructions();
    play();
}

Bug

/*
Bug

Original version in BASIC:
    Brian Leibowitz, 1978.
    Creative Computing (Morristown, New Jersey, USA), 1978.

This version in D:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-03-26/29.

Last modified 20251220T0637+0100.
*/

module bug;

/// Bug type.

struct Bug
{
    bool body;
    bool neck;
    bool head;
    int feelers;
    char feelerType;
    bool tail;
    int legs;
    bool finished;
}

/// Player type.

struct Player
{
    string pronoun;
    string possessive;
    Bug bug;
}

/// Players.

Player computer, human;

/// Bug body parts.

enum Part { body = 1, neck, head, feeler, tail, leg }

// Bug body attributes.

enum bodyHeight = 2;
enum feelerLength = 4;
enum legLength = 2;
enum maxFeelers = 2;
enum maxLegs = 6;
enum neckLength = 2;

/// Clear the screen, reset the attributes and move the cursor to the
/// home position.

void clear()
{
    import std.stdio : write;

    write("\033[2J\033[0m\033[H");
}

/// Move the cursor up by the given number of rows (defaults to 1),
/// without changing the column position.

void cursorUp(int rows = 1)
{
    import std.stdio : write;

    write("\033[", rows, "A");
}

/// Erase the current line, without moving the cursor position.

void eraseLine()
{
    import std.stdio : write;

    write("\033[2K");
}

/// Move the cursor to the previous row, without changing
/// the column position, and erase its line.

void erasePreviousLine()
{
    cursorUp();
    eraseLine();
}

/// Clear the screen, display the credits and wait for a keypress.

void printCredits()
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.stdio : writeln;

    clear();
    writeln("Bug\n");
    writeln("Original version in BASIC:");
    writeln("    Brian Leibowitz, 1978.");
    writeln("    Creative computing (Morristown, New Jersey, USA), 1978.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2023, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    write("Press Enter to read the instructions. ");
    readln();
}

string instructions = "
The object is to finish your bug before I finish mine. Each number
stands for a part of the bug body.

I will roll the die for you, tell you what I rolled for you, what the
number stands for, and if you can get the part. If you can get the
part I will give it to you. The same will happen on my turn.

If there is a change in either bug I will give you the option of
seeing the pictures of the bugs. The numbers stand for parts as
follows:
";

/// Print a table with the bug parts' description.

void printPartsTable()
{

    import std.array : replicate;
    import std.conv : to;
    import std.stdio : write;
    import std.stdio : writeln;
    import std.string : capitalize, leftJustify;

    enum columns = 3;
    enum columnWidth = 8;
    enum columnSeparation = 2;

    // Headers
    string[] header = ["Number", "Part", "Quantity"];
    foreach (i; 0 .. columns)
    {
        write(leftJustify(header[i], columnWidth + columnSeparation));
    }
    writeln();

    // Rulers
    foreach (i; 0 .. columns)
    {
        write(
                replicate("-", columnWidth),
                (i == columns - 1 ? "" : replicate(" ", columnSeparation)));
    }
    writeln();

    // Data
    int[Part] partQuantity;
    partQuantity[Part.body] = 1;
    partQuantity[Part.neck] = 1;
    partQuantity[Part.head] = 1;
    partQuantity[Part.feeler] = 2;
    partQuantity[Part.tail] = 1;
    partQuantity[Part.leg] = 6;
    for (Part part = Part.min; part <= Part.max; ++part)
    {
        writeln(
                leftJustify(to!string(to!int(part)), columnWidth + columnSeparation),
                leftJustify(capitalize(to!string(part)), columnWidth + columnSeparation),
                partQuantity[part]
                );
    }

}

/// Clear the screen, print the instructions and wait for a keypress.

void printInstructions()
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.stdio : writeln;

    clear();
    writeln("Bug");
    writeln(instructions);
    printPartsTable();
    write("\nPress Enter to start. ");
    readln();
}

/// Print a bug head.

void printHead()
{
    import std.stdio : writeln;

    writeln("        HHHHHHH");
    writeln("        H     H");
    writeln("        H O O H");
    writeln("        H     H");
    writeln("        H  V  H");
    writeln("        HHHHHHH");
}

/// Print the given bug.

void printBug(Bug bug)
{
    import std.stdio : write;
    import std.stdio : writeln;

    if (bug.feelers)
    {
        foreach (i; 0 .. feelerLength)
        {
            write("        ");
            foreach (j; 0 .. bug.feelers)
            {
                write(" ", bug.feelerType);
            }
            writeln();
        }
    }
    if (bug.head)
    {
        printHead();
    }
    if (bug.neck)
    {
        foreach (i; 0 .. neckLength)
        {
            writeln("          N N");
        }
    }
    if (bug.body)
    {
        writeln("     BBBBBBBBBBBB");
        foreach (i; 0 .. bodyHeight)
        {
            writeln("     B          B");
        }
        if (bug.tail)
        {
            writeln("TTTTTB          B");
        }
        writeln("     BBBBBBBBBBBB");
    }
    if (bug.legs)
    {
        foreach (i; 0 .. legLength)
        {
            write("    ");
            foreach (j; 0 .. bug.legs)
            {
                write(" L");
            }
            writeln();
        }
    }
}

/// Return `true` if the given bug is finished; otherwise return `false`.

bool finished(Bug bug)
{
    return bug.feelers == maxFeelers && bug.tail && bug.legs == maxLegs;
}

/// Return a random number between 1 and 6 (inclusive).

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

    return uniform(1, 7);
}

/// Array to convert a number to its equilavent text.

string[] asText = [
    "no",
    "a",
    "two",
    "three",
    "four",
    "five",
    "six" ]; // maxLegs

/// Return a string containing the given number
/// and the given noun in their proper form.

string plural(int number, string noun)
{
    return asText[number] ~ " " ~ noun ~ ((number > 1) ? "s" : "");
}

/// Add the given part to the given player's bug.

bool addPart(Part part, ref Player player)
{
    import std.stdio : writeln;

    bool changed = false;
    final switch (part)
    {
    case Part.body:
        if (player.bug.body)
        {
            writeln(", but ", player.pronoun, " already have a body.");
        }
        else
        {
            writeln("; ", player.pronoun, " now have a body:");
            player.bug.body = true;
            changed = true;
        }
        break;
    case Part.neck:
        if (player.bug.neck)
        {
            writeln(", but ", player.pronoun, " you already have a neck.");
        }
        else if (!player.bug.body)
        {
            writeln(", but ", player.pronoun, " need a body first.");
        }
        else
        {
            writeln("; ", player.pronoun, " now have a neck:");
            player.bug.neck = true;
            changed = true;
        }
        break;
    case Part.head:
        if (player.bug.head)
        {
            writeln(", but ", player.pronoun, " already have a head.");
        }
        else if (!player.bug.neck)
        {
            writeln(", but ", player.pronoun, " need a a neck first.");
        }
        else
        {
            writeln("; ", player.pronoun, " now have a head:");
            player.bug.head = true;
            changed = true;
        }
        break;
    case Part.feeler:
        if (player.bug.feelers == maxFeelers)
        {
            writeln(", but ", player.pronoun, " have two feelers already.");
        }
        else if (!player.bug.head)
        {
            writeln(", but ", player.pronoun, " need a head first.");
        }
        else
        {
            player.bug.feelers++;
            writeln("; ", player.pronoun, " now have ",
                    plural(player.bug.feelers, "feeler"), ":");
            changed = true;
        }
        break;
    case Part.tail:
        if (player.bug.tail)
        {
            writeln(", but ", player.pronoun, " already have a tail.");
        }
        else if (!player.bug.body)
        {
            writeln(", but ", player.pronoun, " need a body first.");
        }
        else
        {
            writeln("; ", player.pronoun, " now have a tail:");
            player.bug.tail = true;
            changed = true;
        }
        break;
    case Part.leg:
        if (player.bug.legs == maxLegs)
        {
            writeln(", but ", player.pronoun, " have ",
                    asText[maxLegs], " feet already.");
        }
        else if (!player.bug.body)
        {
            writeln(", but ", player.pronoun, " need a body first.");
        }
        else
        {
            player.bug.legs++;
            writeln("; ", player.pronoun, " now have ",
                    plural(player.bug.legs, "leg"), ":");
            changed = true;
        }
        break;
    }
    return changed;
}

/// Ask the user to press the Enter key, wait for the input
/// and then erase the prompt text.

void prompt()
{
    import std.stdio : readln;
    import std.stdio : write;

    write("Press Enter to roll the dice. ");
    readln();
    erasePreviousLine();
}

/// Play one turn for the given player, rolling the dice
/// and updating his bug.

void turn(ref Player player)
{
    import std.stdio : write;
    import std.stdio : writeln;
    import std.string : capitalize;
    import std.string : strip;

    prompt();
    int number = dice();
    Part part = cast(Part)number;
    write(capitalize(player.pronoun), " rolled a ", number, " (", part, ")");
    if (addPart(part, player))
    {
            writeln();
            printBug(player.bug);
            player.bug.finished = finished(player.bug);
    }
    writeln();
}

/// Print a message about the winner.

void printWinner()
{
    import std.stdio : writeln;

    if (human.bug.finished && computer.bug.finished)
    {
        writeln("Both of our bugs are finished in the same number of turns!");
    }
    else if (finished(human.bug))
    {
        writeln(human.possessive, " bug is finished.");
    }
    else if (finished(computer.bug))
    {
        writeln(computer.possessive, " bug is finished.");
    }
}

/// Return `true` if either bug is finished, i.e. the game ending condition.

bool gameOver()
{
    return human.bug.finished || computer.bug.finished;
}

/// Execute the game loop.

void play()
{
    clear();
    do
    {
        turn(human);
        turn(computer);
    } while (!gameOver());
    printWinner();
}

/// Init player data before a new game.

void init()
{
    human.pronoun = "you";
    human.possessive = "Your";
    human.bug.feelerType = 'A';
    human.bug.finished = false;
    computer.pronoun = "I";
    computer.possessive = "My";
    computer.bug.feelerType = 'F';
    computer.bug.finished = false;
}

void main()
{
    import std.stdio : writeln;

    init();
    printCredits();
    printInstructions();
    play();
    writeln("I hope you enjoyed the game, play it again soon!!");
}

Bunny

/*
Bunny

Original version in BASIC:
    Creative Computing (Morristown, New Jersey, USA), 1978.

This version in D:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-03-23/26, 2023-08-29/31.

Last modified 20251219T2132+0100.
*/

module bunny;

// Erase the screen, reset the attributes and move the cursor to the home position.

void clearScreen()
{
    import std.stdio : write;

    write("\033[2J\033[0m\033[H");
}

// Clear the screen, print the credits and wait for a keypress.

void printCredits()
{
    import std.stdio : readln;
    import std.stdio : writeln;

    writeln("Bunny\n");
    writeln("Original version in BASIC:");
    writeln("    Creative Computing (Morristown, New Jersey, USA), 1978.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2023, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    writeln("Press Enter to start the program.");
    readln();
}

enum Width = 53;
ubyte[Width] line; // buffer
int col; // first col to print from

// Clear the line buffer with spaces.

void clearLine()
{
    foreach (c; 0 .. Width)
    {
        line[c] = ' ';
    }
    col = 0;
}

string letter = "BUNNY";

enum EOL = 127; // end of line identifier
ubyte[] 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 ];

// Draw the graphic out of `data` and `letter`.

void draw()
{
    import std.stdio : writef;

    int dataLen = cast(int)data.length;
    int letters = cast(int)letter.length;
    int toCol; // last col to print to
    int d = 0; // data pointer
    clearLine();
    while (d < dataLen)
    {
        col = data[d];
        d += 1;
        if (col == EOL)
        {
            writef("%s\n", cast(string)line);
            clearLine();
        }
        else
        {
            toCol = data[d];
            d += 1;
            foreach (int c; col .. toCol + 1)
            {
                line[c] = letter[c % letters];
            }
        }
    }
}

void main()
{
    clearScreen();
    printCredits();
    clearScreen();
    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 D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
//     Written on 2025-03-23.
//     Last modified: 20251220T0637+0100.

module chase;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setCursorPosition(int line, int col)
{
    import std.stdio : writef;

    writef("\x1B[%d;%dH", line, col);
}

void hideCursor()
{
    import std.stdio : write;

    write("\x1B[?25l");
}

void showCursor()
{
    import std.stdio : write;

    write("\x1B[?25h");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseLineToEnd()
{
    import std.stdio : write;

    write("\x1B[K");
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

// Config {{{1
// =============================================================

enum DEFAULT_INK = WHITE + FOREGROUND;
enum INPUT_INK = BRIGHT + GREEN + FOREGROUND;
enum INSTRUCTIONS_INK = YELLOW + FOREGROUND;
enum TITLE_INK = BRIGHT + RED + FOREGROUND;

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

// Arena

enum ARENA_WIDTH = 20;
enum ARENA_HEIGHT = 10;
enum ARENA_LAST_X = ARENA_WIDTH - 1;
enum ARENA_LAST_Y = ARENA_HEIGHT - 1;
enum ARENA_ROW = 3;

//string[ARENA_HEIGHT][ARENA_WIDTH] arena;
string[ARENA_WIDTH][ARENA_HEIGHT] arena;

enum EMPTY = " ";
enum FENCE = "X";
enum MACHINE = "m";
enum HUMAN = "@";

enum FENCES = 15; // inner obstacles, not the border

// The end

enum End
{
    NOT_YET,
    QUIT,
    ELECTRIFIED,
    KILLED,
    VICTORY
}

End theEnd = End.NOT_YET;

// Machines

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

int[MACHINES] machineX;
int[MACHINES] machineY;
bool[MACHINES] operative;

int destroyedMachines = 0; // counter

// Human

int humanX = 0;
int humanY = 0;

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

string getString(string prompt)
{
    setStyle(INPUT_INK);
    string s = acceptString(prompt);
    setStyle(DEFAULT_INK);
    return s;
}

void pressEnter(string prompt)
{
    acceptString(prompt);
}

bool isYes(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool isNo(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".

bool yes(string prompt)
{
    while (true)
    {
        string answer = getString(prompt);
        if (isYes(answer))
        {
            return true;
        }
        if (isNo(answer))
        {
            return false;
        }
    }
}

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

enum TITLE = "Chase";

void printTitle()
{
    import std.stdio : writef;

    setStyle(TITLE_INK);
    writef("%s\n", TITLE);
    setStyle(DEFAULT_INK);
}

void printCredits()
{
    import std.stdio : writeln;

    printTitle();
    writeln("\nOriginal version in BASIC:");
    writeln("    Anonymous.");
    writeln("    Published in \"The Best of Creative Computing\", Volume 2, 1977.");
    writeln("    https://www.atariarchives.org/bcc2/showpage.php?page=253");
    writeln("This version in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

void printInstructions()
{
    import std.stdio : writef;
    import std.stdio : writeln;

    printTitle();
    setStyle(INSTRUCTIONS_INK);
    writef("\nYou (%s) are in a high voltage maze with %d\n", HUMAN, MACHINES);
    writef("security machines (%s) trying to kill you.\n", MACHINE);
    writef("You must maneuver them into the maze (%s) to survive.\n\n", FENCE);
    writeln("Good luck!\n");
    writeln("The movement commands are the following:\n");
    writeln("    ↖  ↑  ↗");
    writeln("    NW N NE");
    writeln("  ←  W   E  →");
    writeln("    SW S SE");
    writeln("    ↙  ↓  ↘");
    writeln("\nPlus 'Q' to end the game.");
    setStyle(DEFAULT_INK);
}

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

void printArena()
{
    import std.stdio : write;

    setCursorPosition(ARENA_ROW, 1);
    foreach (int y; 0 .. ARENA_LAST_Y + 1)
    {
        foreach (int x; 0 .. ARENA_LAST_X + 1)
        {
            write(arena[y][x]);
        }
        write("\n");
    }
}

// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.

bool isBorder(int y, int x)
{
    return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}

int randomIntInInclusiveRange(int min, int max)
{
    import std.random : uniform;

    return uniform(min, min + max);
}

// Place the given string at a random empty position of the arena and return
// the coordinates.

int[] place(string s)
{
    int y = 0;
    int x = 0;
    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.

void inhabitArena()
{
    int[] coords;
    foreach (int m; 0 .. MACHINES)
    {
        coords = place(MACHINE);
        machineY[m] = coords[0];
        machineX[m] = coords[1];
        operative[m] = true;
    }
    foreach (int i; 0 .. FENCES)
    {
        place(FENCE);
    }
    coords = place(HUMAN);
    humanY = coords[0];
    humanX = coords[1];
}

// Clean the arena, i.e. empty it and add a surrounding fence.

void cleanArena()
{
    foreach (int y; 0 .. ARENA_LAST_Y + 1)
    {
        foreach (int x; 0 .. ARENA_LAST_X + 1)
        {
            arena[y][x] = isBorder(y, x) ? FENCE : EMPTY;
        }
    }
}

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

void initGame()
{
    cleanArena();
    inhabitArena();
    destroyedMachines = 0;
    theEnd = End.NOT_YET;
}

void moveMachine(int m)
{
    import std.random : uniform;

    int maybe = 0;

    arena[machineY[m]][machineX[m]] = EMPTY;

    maybe = uniform(0, 2);
    if (machineY[m] > humanY)
    {
        machineY[m] -= maybe;
    }
    else if (machineY[m] < humanY)
    {
        machineY[m] += maybe;
    }

    maybe = uniform(0, 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;
    }

}

void maybeMoveMachine(int m)
{
    import std.random : uniform;

    if (uniform(0, MACHINES_DRAG) == 0)
    {
        moveMachine(m);
    }
}

void moveMachines()
{
    foreach (int m; 0 .. MACHINES)
    {
        if (operative[m])
        {
            maybeMoveMachine(m);
        }
    }
}

// Read a user command; update `theEnd` accordingly and return the direction
// increments.

int[] getMove()
{
    import std.stdio : write;
    import std.uni : toLower;

    int yInc = 0;
    int xInc = 0;
    write("\n");
    eraseLineToEnd();
    string command = toLower(getString("Command: "));

    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;
        default:
            break;

    }
    return [yInc, xInc];
}

void play()
{
    import std.stdio : writeln;

    int yInc = 0;
    int xInc = 0;

    while (true) { // game loop

        clearScreen();
        printTitle();
        initGame();

actionLoop:

        while (true) {

            printArena();
            int[] coords = getMove();
            yInc = coords[0];
            xInc = coords[1];

            if (theEnd == End.NOT_YET)
            {
                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();
                    }
                }
            }

            final switch (theEnd)
            {
                case End.NOT_YET:
                    break;
                case End.QUIT:
                    writeln("\nSorry to see you quit.");
                    break actionLoop;
                case End.ELECTRIFIED:
                    writeln("\nZap! You touched the fence!");
                    break actionLoop;
                case End.KILLED:
                    writeln("\nYou have been killed by a lucky machine.");
                    break actionLoop;
                case End.VICTORY:
                    writeln("\nYou are lucky, you destroyed all machines.");
                    break actionLoop;
            }

        } // action loop;

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

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

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

void main()
{
    setStyle(DEFAULT_INK);
    clearScreen();
    printCredits();
    pressEnter("\nPress the Enter key to read the instructions. ");
    clearScreen();
    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 D:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-03-22/26.

Last modified 20251219T2131+0100.
*/

module diamond;

void main()
{
    import std.stdio : write;
    import std.stdio : writeln;

    enum lines = 17;

    foreach (i; 1 .. lines / 2 + 2)
    {
        foreach (j; 1 .. (lines + 1) / 2 - i + 2)
        {
            write(" ");
        }
        foreach (j; 1 .. i * 2)
        {
            write("*");
        }
        writeln();
    }
    foreach (i; 1 .. lines / 2 + 1)
    {
        foreach (j; 1 .. i + 2)
        {
            write(" ");
        }
        foreach (j; 1 .. ((lines + 1) / 2 - i) * 2)
        {
            write("*");
        }
        writeln();
    }
}

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.
//
// More details:
//     - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
//     - https://www.mobygames.com/game/22232/hamurabi/

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

module hammurabi;

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

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

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

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

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

enum NORMAL_STYLE = 0;

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

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

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

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

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

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

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

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

enum Result { VERY_GOOD, NOT_TOO_BAD, BAD, VERY_BAD }

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

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

enum CREDITS =
"Hammurabi

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

    return uniform(1, 6);
}

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

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

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

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

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

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

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

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

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

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

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

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

    population += infants;

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

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

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

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

    sayBye();
    exit(0);
}

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

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

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

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

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

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

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

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

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

/// Buy or sell land.

void trade()
{

    int acresToBuy = 0;
    int acresToSell = 0;

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

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

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

/// Feed the people.

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

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

/// Seed the land.

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

    int acresToSeed = 0;

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

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

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

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

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

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

        begThinkAgain(population, message);
    }

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

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

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

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

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

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

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

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

    setStyle(DEFAULT_INK);
}

void printFinalReport()
{
    clearScreen();

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

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

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

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

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

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

void govern()
{
    init();

    printAnnualReport(0);

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

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

        checkStarvation(year);

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

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

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

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 D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-23.
//
// Last modified: 20251220T0654+0100.

module high_noon;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void hideCursor()
{
    import std.stdio : write;

    write("\x1B[?25l");
}

void showCursor()
{
    import std.stdio : write;

    write("\x1B[?25h");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

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

enum DEFAULT_INK = FOREGROUND + WHITE;
enum INPUT_INK = FOREGROUND + BRIGHT + GREEN;
enum INSTRUCTIONS_INK = FOREGROUND + YELLOW;
enum TITLE_INK = FOREGROUND + BRIGHT + RED;

enum INITIAL_DISTANCE = 100;
enum INITIAL_BULLETS = 4;
enum MAX_WATERING_TROUGHS = 3;

int distance = 0; // distance between both gunners, in paces

int playerBullets = 0;
int opponentBullets = 0;

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

/// Print the given prompt and wait until the user enters a string.

string getString(string prompt)
{
    setStyle(INPUT_INK);
    string s = acceptString(prompt);
    setStyle(DEFAULT_INK);
    return s;
}

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

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

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

/// Return `true` if the given string is "yes" or a synonym.

bool isYes(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

/// Return `true` if the given string is "no" or a synonym.

bool isNo(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

/// Print the given prompt, wait until the user enters a valid yes/no string,
/// and return `true` for "yes" or `false` for "no".

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
// =============================================================

void printTitle()
{
    import std.stdio : writeln;

    setStyle(TITLE_INK);
    writeln("High Noon");
    setStyle(DEFAULT_INK);
}

void printCredits()
{
    import std.stdio : writeln;

    printTitle();
    writeln("\nOriginal version in BASIC:");
    writeln("    Designed and programmend by Chris Gaylo, 1970.");
    writeln("    http://mybitbox.com/highnoon-1970/");
    writeln("    http://mybitbox.com/highnoon/");
    writeln("Transcriptions:");
    writeln("    https://github.com/MrMethor/Highnoon-BASIC/");
    writeln("    https://github.com/mad4j/basic-highnoon/");
    writeln("Version modified for QB64:");
    writeln("    By Daniele Olmisani, 2014.");
    writeln("    https://github.com/mad4j/basic-highnoon/");
    writeln("This improved remake in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

void printInstructions()
{
    import std.stdio : writef;
    import std.stdio : writeln;

    printTitle();
    setStyle(INSTRUCTIONS_INK);
    writeln("\nYou have been challenged to a showdown by Black Bart, one of");
    writeln("the meanest desperadoes west of the Allegheny mountains.");
    writeln("\nWhile you are walking down a dusty, deserted side street,");
    writeln("Black Bart emerges from a saloon one hundred paces away.");
    writef("\nBy agreement, you each have %d bullets in your six-guns.", INITIAL_BULLETS);
    writeln("\nYour marksmanship equals his. At the start of the walk nei-");
    writeln("ther of you can possibly hit the other, and at the end of");
    writeln("the walk, neither can miss. the closer you get, the better");
    writeln("your chances of hitting black Bart, but he also has beter");
    writeln("chances of hitting you.");
    setStyle(DEFAULT_INK);
}

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

string pluralSuffix(int n)
{
    switch (n)
    {
        case 1: return "";
        default: return "s";
    }
}

void printShellsLeft()
{
    import std.stdio : writef;
    import std.stdio : writefln;

    if (playerBullets == opponentBullets)
    {
        writefln("Both of you have %d bullets.", playerBullets);
    }
    else
    {
        writef(
            "You now have %d bullet%s to Black Bart's %d bullet%s.\n",
            playerBullets,
            pluralSuffix(playerBullets),
            opponentBullets,
            pluralSuffix(opponentBullets));
    }
}

void printCheck()
{
    import std.random : uniform;
    import std.stdio : writefln;
    import std.stdio : writeln;

    writeln("******************************************************");
    writeln("*                                                    *");
    writeln("*                 BANK OF DODGE CITY                 *");
    writeln("*                  CASHIER'S RECEIT                  *");
    writeln("*                                                    *");
    writefln("* CHECK NO. %04d                   AUGUST %dTH, 1889 *",
        uniform(0, 1000),
        10 + uniform(0, 10));
    writeln("*                                                    *");
    writeln("*                                                    *");
    writeln("*       PAY TO THE BEARER ON DEMAND THE SUM OF       *");
    writeln("*                                                    *");
    writeln("* TWENTY THOUSAND DOLLARS-------------------$20,000  *");
    writeln("*                                                    *");
    writeln("******************************************************");
}

void getReward()
{
    import std.stdio : writeln;

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

void moveTheOpponent()
{
    import std.random : uniform;
    import std.stdio : writefln;

    int paces = 2 + uniform(0, 8);
    writefln("Black Bart moves %d 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.

bool maybeMoveTheOpponent(bool silent)
{
    import std.random : uniform;
    import std.stdio : writeln;

    if (uniform(0, 2) == 0) { // 50% chances
        moveTheOpponent();
        return true;
    }
    else
    {
        if (!silent)
        {
            writeln("Black Bart stands still.");
        }
        return false;
    }
}

bool missedShot()
{
    import std.random : uniform01;

    return uniform01() * 10.0 <= (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`.

bool theOpponentFiresAndKills(string playerStrategy)
{
    import std.stdio : writeln;

    writeln("Black Bart fires…");
    opponentBullets -= 1;
    if (missedShot())
    {
        writeln("A miss…");
        final switch (opponentBullets)
        {
            case 3:
                writeln("Whew, were you lucky. That bullet just missed your head.");
                break;
            case 2:
                writeln("But Black Bart got you in the right shin.");
                break;
            case 1:
                writeln("Though Black Bart got you on the left side of your jaw.");
                break;
            case 0:
                writeln("Black Bart must have jerked the trigger.");
        }
    }
    else
    {
        if (playerStrategy == "j")
        {
            writeln("That trick just saved yout life. Black Bart's bullet");
            writeln("was stopped by the wood sides of the trough.");
        }
        else
        {
            writeln("Black Bart shot you right through the heart that time.");
            writeln("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`.

bool theOpponentKillsOrRuns(string playerStrategy)
{
    import std.random : uniform;
    import std.stdio : writeln;

    if (distance >= 10 || playerBullets == 0)
    {
        if (maybeMoveTheOpponent(true))
        {
            return false;
        }
    }
    if (opponentBullets > 0)
    {
        return theOpponentFiresAndKills(playerStrategy);
    }
    else
    {
        if (playerBullets > 0)
        {
            if (uniform(0, 2) == 0) { // 50% chances
                writeln("Now is your chance, Black Bart is out of bullets.");
            }
            else
            {
                writeln("Black Bart just hi-tailed it out of town rather than face you");
                writeln("without a loaded gun. You can rest assured that Black Bart");
                writeln("won't ever show his face around this town again.");
                return true;
            }
        }
    }
    return false;
}

void play()
{
    import std.random : uniform;
    import std.stdio : writefln;
    import std.stdio : writeln;
    import std.uni : toLower;

    distance = INITIAL_DISTANCE;
    int wateringTroughs = 0;
    playerBullets = INITIAL_BULLETS;
    opponentBullets = INITIAL_BULLETS;

    showdown: while (true)
    {

        writefln("You are now %d paces apart from Black Bart.", distance);
        printShellsLeft();
        setStyle(INSTRUCTIONS_INK);
        writeln("\nStrategies:");
        writeln("  [A]dvance");
        writeln("  [S]tand still");
        writeln("  [F]ire");
        writeln("  [J]ump behind the watering trough");
        writeln("  [G]ive up");
        writeln("  [T]urn tail and run");
        setStyle(DEFAULT_INK);

        string playerStrategy = toLower(getString("What is your strategy? "));

        switch (playerStrategy)
        {

            case "a": // advance

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

            case "s": // stand still

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

            case "f": // fire

                if (playerBullets == 0)
                {

                    writeln("You don't have any bullets left.");

                }
                else
                {

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

                }
                break;

            case "j": // jump

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

            case "g": // give up

                writeln("Black Bart accepts. The conditions are that he won't shoot you");
                writeln("if you take the first stage out of town and never come back.");
                if (yes("Agreed? "))
                {
                    writeln("A very wise decision.");
                    break showdown;
                }
                else
                {
                    writeln("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 (uniform(0, (opponentBullets + 2)) == 0)
                {
                    writeln("Man, you ran so fast even dogs couldn't catch you.");
                }
                else
                {
                    final switch (opponentBullets)
                    {
                        case 0:
                            writeln("You were lucky, Black Bart can only throw his gun at you, he");
                            writeln("doesn't have any bullets left. You should really be dead.");
                            break;
                        case 1:
                            writeln("Black Bart fires his last bullet…");
                            writeln("He got you right in the back. That's what you deserve, for running.");
                            break;
                        case 2:
                            writeln("Black Bart fires and got you twice: in your back");
                            writeln("and your ass. Now you can't even rest in peace.");
                            break;
                        case 3:
                            writeln("Black Bart unloads his gun, once in your back");
                            writeln("and twice in your ass. Now you can't even rest in peace.");
                            break;
                        case 4:
                            writeln("Black Bart unloads his gun, once in your back");
                            writeln("and three times in your ass. Now you can't even rest in peace.");
                    }
                    opponentBullets = 0;
                }
                break showdown;

            default:

                writeln("You sure aren't going to live very long if you can't even follow directions.");

        } // strategy switch

        if (theOpponentKillsOrRuns(playerStrategy))
        {
            break;
        }

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

        writeln();

    } // showdown loop

}

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

void main()
{
    clearScreen();
    printCredits();
    acceptString("\nPress the Enter key to read the instructions. ");
    clearScreen();
    printInstructions();
    acceptString("\nPress the Enter key to start. ");
    clearScreen();
    play();
}

Math

// Math

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

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-22.
//
// Last modified: 20251219T2147+0100.

module math;

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

float acceptValidFloat(string prompt)
{
    import std.conv : to;
    import std.stdio : writeln;

    float result;
    while (true)
    {
        string s = acceptString(prompt);
        try
        {
            result = to!float(s);
            break;
        }
        catch (Exception exc)
        {
            writeln("Real number expected.");
        }
    }
    return result;
}


void main()
{
    import std.conv : to;
    import std.math : abs;
    import std.math : atan;
    import std.math : cos;
    import std.math : exp;
    import std.math : log;
    import std.math : sgn;
    import std.math : sqrt;
    import std.math : tan;
    import std.stdio : writefln;

    float n = acceptValidFloat("Enter a number: ");

    writefln("ABS(%1$f) -> abs(%1$f) -> %2$f", n, abs(n));
    writefln("ATN(%1$f) -> atan(%1$f) -> %2$f", n, atan(n));
    writefln("COS(%1$f) -> cos(%1$f) -> %2$f", n, cos(n));
    writefln("EXP(%1$f) -> exp(%1$f) -> %2$f", n, exp(n));
    writefln("INT(%1$f) -> to!int(%1$f) -> %2$d", n, to!int(n));
    writefln("LOG(%1$f) -> log(%1$f) -> %2$f", n, log(n));
    writefln("SGN(%1$f) -> to!int(sgn(%1$f)) -> %2$d", n, to!int(sgn(n)));
    writefln("SQR(%1$f) -> sqrt(%1$f) -> %2$f", n, sqrt(n));
    writefln("TAN(%1$f) -> tan(%1$f) -> %2$f", n, 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

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-24.
//
// Last modified: 20251220T0620+0100.

module mugwump;

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

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

enum GRID_SIZE = 10;
enum TURNS = 10;
enum MUGWUMPS = 4;

class Mugwump
{
    int x;
    int y;
    bool hidden;
}

Mugwump[MUGWUMPS] mugwump;

int found = 0; // counter

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

int acceptValidInteger(string prompt)
{
    import std.conv : to;
    import std.stdio : writeln;

    int result;
    while (true)
    {
        string s = acceptString(prompt);
        try
        {
            result = to!int(s);
            break;
        }
        catch (Exception exc)
        {
            writeln("Integer expected.");
        }
    }
    return result;
}

bool isYes(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool isNo(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

/// Print the given prompt, wait until the user enters a valid yes/no string;
/// and return `true` for "yes" or `false` for "no".

bool yes(string prompt)
{
    while (true)
    {
        string answer = acceptString(prompt);
        if (isYes(answer))
        {
            return true;
        }
        if (isNo(answer))
        {
            return false;
        }
    }
}

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

/// Clear the screen, print the credits and ask the user to press enter.

void printCredits()
{
    import std.stdio : writeln;

    clearScreen();
    writeln("Mugwump\n");
    writeln("Original version in BASIC:");
    writeln("    Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).");
    writeln("    Slightly modified by Bob Albrecht of People's Computer Company.");
    writeln("    Published by Creative Computing (Morristown, New Jersey, USA), 1978.");
    writeln("    - https://www.atariarchives.org/basicgames/showpage.php?page=114");
    writeln("    - http://vintage-basic.net/games.html\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    acceptString("Press Enter to read the instructions. ");
}

/// Clear the screen, print the instructions and ask the user to press enter.

void printInstructions()
{
    import std.stdio : writefln;
    import std.stdio : writeln;

    clearScreen();
    writeln("Mugwump\n");
    writeln("The object of this game is to find four mugwumps");
    writeln("hidden on a 10 by 10 grid.  Homebase is position 0,0.");
    writeln("Any guess you make must be two numbers with each");
    writeln("number between 0 and 9, inclusive.  First number");
    writeln("is distance to right of homebase and second number");
    writeln("is distance above homebase.\n");
    writefln("You get %d tries.  After each try, you will see", TURNS);
    writeln("how far you are from each mugwump.\n");
    acceptString("Press Enter to start. ");
}

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

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

    foreach (int m; 0 .. MUGWUMPS)
    {
        mugwump[m].x = uniform(0, GRID_SIZE);
        mugwump[m].y = uniform(0, GRID_SIZE);
        mugwump[m].hidden = true;
    }
    found = 0; // counter
}

/// Print the given prompt, wait until the user enters a valid coord and return
/// it.

int getCoord(string prompt)
{
    import std.stdio : writefln;

    int coord = 0;
    while (true)
    {
        coord = acceptValidInteger(prompt);
        if (coord < 0 || coord >= GRID_SIZE)
        {
            writefln("Invalid value %d: not in range [0, %d].", coord, GRID_SIZE - 1);
        }
        else
        {
            break;
        }
    }
    return coord;
}

// Return `true` if the given mugwump is hidden in the given coords.

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.

int distance(int m, int x, int y)
{
    import std.math : pow;
    import std.math : sqrt;

    return cast(int)sqrt(
        pow((mugwump[m].x - x), 2.0) +
        pow((mugwump[m].y - y), 2.0));
}

/// Return a plural suffix (default: "s") if the given number is greater than 1
/// otherwise return a singular suffix (default: an empty string).

string plural(int n, string pluralSuffix = "s", string singularSuffix = "")
{
    return n > 1 ? pluralSuffix : singularSuffix;
}

/// Run the game.

void play()
{
    import std.stdio : writefln;
    import std.stdio : writeln;

    int x = 0;
    int y = 0;
    int turn = 0; // counter

    while (true) { // game

        clearScreen();
        hideMugwumps();

        turnsLoop: for (turn = 1; turn <= TURNS; turn += 1)
        {
            writefln("Turn number %d\n", turn);
            writefln("What is your guess (in range [0, %d])?", GRID_SIZE - 1);
            x = getCoord("Distance right of homebase (x-axis): ");
            y = getCoord("Distance above homebase (y-axis): ");
            writefln("\nYour guess is (%d, %d).", x, y);

            foreach (int m; 0 .. MUGWUMPS)
            {
                if (isHere(m, x, y))
                {
                    mugwump[m].hidden = false;
                    found += 1;
                    writefln("You have found mugwump %d!", m);
                    if (found == MUGWUMPS)
                    {
                        break turnsLoop;
                    }
                }
            }

            foreach (int m; 0 .. MUGWUMPS)
            {
                if (mugwump[m].hidden)
                {
                    writefln("You are %d units from mugwump %d.", distance(m, x, y) , m);
                }
            }
            writeln();
        } // turns

        if (found == MUGWUMPS)
        {
            writefln("\nYou got them all in %d turn%s!\n", turn, plural(turn));
            writeln("That was fun! let's play again…");
            writeln("Four more mugwumps are now in hiding.");
        }
        else
        {
            writefln("\nSorry, that's %d tr%s.\n", TURNS, plural(TURNS, "ies", "y"));
            writeln("Here is where they're hiding:");
            foreach (int m; 0 .. MUGWUMPS)
            {
                if (mugwump[m].hidden)
                {
                    writefln("Mugwump %d is at (%d, %d).", m, mugwump[m].x, mugwump[m].y);
                }
            }
        }

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

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

void init()
{
    // Build the `mugwump` array.
    foreach (int i; 0 .. MUGWUMPS)
    {
        mugwump[i] = new Mugwump();
    }
}

void main()
{
    printCredits();
    printInstructions();
    init();
    play();
}

Name

// Name

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

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-22.
//
// Last modified: 20251219T2153+0100.

module name;

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

int acceptValidInteger(string prompt)
{
    import std.conv : to;
    import std.stdio : writeln;

    int result;
    while (true)
    {
        string s = acceptString(prompt);
        try
        {
            result = to!int(s);
            break;
        }
        catch (Exception exc)
        {
            writeln("Integer expected.");
        }
    }
    return result;
}

void main()
{
    import std.stdio : writef;

    string name = acceptString("What is your name? ");
    int number = acceptValidInteger("Enter a number: ");
    foreach (_; 0 .. number)
    {
        writef("Hello, %s!\n", 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

// This improved remake in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-22.
//
// Last modified: 20251220T0654+0100.

module poetry;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : writef;

    writef("\x1B[H");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

enum DEFAULT_INK = FOREGROUND + WHITE;
enum INPUT_INK = FOREGROUND + BRIGHT + GREEN;
enum TITLE_INK = FOREGROUND + BRIGHT + RED;

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

void printTitle()
{
    import std.stdio : writeln;

    setStyle(TITLE_INK);
    writeln("Poetry");
    setStyle(DEFAULT_INK);
}

void printCredits()
{
    import std.stdio : writeln;

    printTitle();
    writeln("\nOriginal version in BASIC:");
    writeln("    Unknown author.");
    writeln("    Published in \"BASIC Computer Games\",");
    writeln("    Creative Computing (Morristown, New Jersey, USA), 1978.\n");
    writeln("This improved remake in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

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

void play()
{
    import core.thread.osthread : Thread;
    import core.time : msecs;
    import std.random : uniform;
    import std.random : uniform01;
    import std.stdio : write;
    import std.stdio : writeln;

    int MAX_PHRASES_AND_VERSES = 20;

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

    verse: while (true)
    {

        bool manageTheVerseContinuation = true;
        bool maybeAddComma = true;

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

        if (manageTheVerseContinuation)
        {

            Thread.sleep(250.msecs);

            if (maybeAddComma && !(verseChunks == 0 || uniform01() > 0.19))
            {
                write(",");
                verseChunks = 2;
            }

            if (uniform01() > 0.65)
            {
                writeln();
                verseChunks = 0;
            }
            else
            {
                write(" ");
                verseChunks += 1;
            }

        }

        action += 1;
        phrase = uniform(0, 5);
        phrasesAndVerses += 1;

        if (!(verseChunks > 0 || isEven(action)))
        {
            write("     ");
        }

    } // verse loop

}

void main()
{
    clearScreen();
    printCredits();
    acceptString("\nPress the Enter key to start. ");
    clearScreen();
    play();
}

Russian Roulette

// Russian Roulette

// Original version in BASIC:
//    Creative Computing (Morristown, New Jersey, USA), ca. 1980.

// This version in D:
//    Copyright (c) 2025, Marcos Cruz (programandala.net)
//    SPDX-License-Identifier: Fair
//
// Written on 2025-03-22.
//
// Last modified: 20251220T0625+0100.

module russian_roulette;

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

enum normalStyle = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(normalStyle);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

void pressEnterToStart()
{
    acceptString("Press Enter to start. ");
}

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

void printCredits()
{
    import std.stdio : writeln;

    clearScreen();
    writeln("Russian Roulette\n");
    writeln("Original version in BASIC:");
    writeln("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    pressEnterToStart();
}

void printInstructions()
{
    import std.stdio : writeln;

    clearScreen();
    writeln("Here is a revolver.");
    writeln("Type 'f' to spin chamber and pull trigger.");
    writeln("Type 'g' to give up, and play again.");
    writeln("Type 'q' to quit.\n");
}

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

void play()
{
    import std.random : uniform;
    import std.stdio : writeln;

    int times = 0;

    while (true) // game loop
    {

        printInstructions();
        times = 0;

        playLoop: while (true)
        {

            string command = acceptString("> ");

            switch (command)
            {
                case "f": // fire
                    if (uniform(0, 100) > 83)
                    {
                        writeln("Bang! You're dead!");
                        writeln("Condolences will be sent to your relatives.");
                        break playLoop;
                    }
                    else
                    {
                        times += 1;
                        if (times > 10)
                        {
                            writeln("You win!");
                            writeln("Let someone else blow his brains out.");
                            break playLoop;
                        }
                        else
                        {
                            writeln("Click.");
                        }
                    }
                    break;
                case "g": // give up
                    writeln("Chicken!");
                    break playLoop;
                case "q": // quit
                    return;
                default:
                    continue;
            }

        } // play loop

        pressEnterToStart();

    } // game loop

}

void bye()
{
    import std.stdio : writeln;

    writeln("Bye!");
}

void main()
{
    printCredits();
    play();
    bye();
}

Seance

// Seance

// Original version in BASIC:
//     By Chris Oxlade, 1983.
//     https://archive.org/details/seance.qb64
//     https://github.com/chaosotter/basic-games

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-24.
//
// Last modified: 20251220T0654+0100.

module seance;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setCursorPosition(int y, int x)
{
    import std.stdio : writef;

    writef("\x1B[%d;%dH", y, x);
}

void hideCursor()
{
    import std.stdio : write;

    write("\x1B[?25l");
}

void showCursor()
{
    import std.stdio : write;

    write("\x1B[?25h");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseLineToEnd()
{
    import std.stdio : write;

    write("\x1B[K");
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

// Config {{{1
// =============================================================================

enum TITLE = "Seance";

enum MAX_SCORE = 50;

enum MAX_MESSAGE_LENGTH = 6;
enum MIN_MESSAGE_LENGTH = 3;

enum BASE_CHARACTER = '@';
enum PLANCHETTE = '*';
enum SPACE = ' ';

enum FIRST_LETTER_NUMBER = 1;
enum LAST_LETTER_NUMBER = 26;

enum BOARD_INK = BRIGHT + CYAN + FOREGROUND;
enum DEFAULT_INK = WHITE + FOREGROUND;
enum INPUT_INK = BRIGHT + GREEN + FOREGROUND;
enum INSTRUCTIONS_INK = YELLOW + FOREGROUND;
enum MISTAKE_EFFECT_INK = BRIGHT + RED + FOREGROUND;
enum PLANCHETTE_INK = YELLOW + FOREGROUND;
enum TITLE_INK = BRIGHT + RED + FOREGROUND;

enum BOARD_X = 29; // screen column
enum BOARD_Y = 5; // screen line
enum BOARD_HEIGHT = 5; // characters displayed on the left and right borders
enum BOARD_WIDTH = 8; // characters displayed on the top and bottom borders
enum BOARD_PAD = 1; // blank characters separating the board from its left and right borders

enum BOARD_ACTUAL_WIDTH = BOARD_WIDTH + 2 * BOARD_PAD; // screen columns
enum BOARD_BOTTOM_Y = BOARD_HEIGHT + 1; // relative to the board

enum INPUT_X = BOARD_X;
enum INPUT_Y = BOARD_Y + BOARD_BOTTOM_Y + 4;

enum MESSAGES_Y = INPUT_Y;

enum MISTAKE_EFFECT_PAUSE = 3000; // ms

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

void pressEnter(string prompt)
{
    acceptString(prompt);
}

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

void printTitle()
{
    import std.stdio : writefln;

    setStyle(TITLE_INK);
    writefln("%s", TITLE);
    setStyle(DEFAULT_INK);
}

void printCredits()
{
    import std.stdio : writeln;

    printTitle();
    writeln("\nOriginal version in BASIC:");
    writeln("    Written by Chris Oxlade, 1983.");
    writeln("    https://archive.org/details/seance.qb64");
    writeln("    https://github.com/chaosotter/basic-games");
    writeln("This version in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

void printInstructions()
{
    import std.stdio : writeln;

    printTitle();
    setStyle(INSTRUCTIONS_INK);
    writeln("\nMessages from the Spirits are coming through, letter by letter.  They want you");
    writeln("to remember the letters and type them into the computer in the correct order.");
    writeln("If you make mistakes, they will be angry -- very angry...");
    writeln();
    writeln("Watch for stars on your screen -- they show the letters in the Spirits'");
    writeln("messages.");
    setStyle(DEFAULT_INK);
}

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

int randomIntInInclusiveRange(int min, int max)
{
    import std.random : uniform;

    return uniform(min, max + 1);
}

/// Return the x coordinate to print the given text centered on the board.

int boardCenteredX(string text)
{
    return cast(int)(BOARD_X + (BOARD_ACTUAL_WIDTH - text.length) / 2);
}

/// Print the given text on the given row, centered on the board.

void printCentered(string text, int y)
{
    import std.stdio : writefln;

    setCursorPosition(y, boardCenteredX(text));
    writefln("%s", text);
}

/// Print the title on the given row, centered on the board.

void printCenteredTitle(int y)
{
    setStyle(TITLE_INK);
    printCentered(TITLE, y);
    setStyle(DEFAULT_INK);
}

// Print the given letter at the given board coordinates.

void printCharacter(int y, int x, int charCode)
{
    import std.conv : to;
    import std.stdio : write;

    setCursorPosition(y + BOARD_Y, x + BOARD_X);
    write(to!(char)(charCode));
}

void printBoard()
{
    import std.stdio : writeln;

    setStyle(BOARD_INK);
    foreach (int i; 1 .. BOARD_WIDTH + 1)
    {
        printCharacter(0, i + 1, BASE_CHARACTER + i); // top border
        printCharacter(BOARD_BOTTOM_Y, i + 1, BASE_CHARACTER + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1); // bottom border
    }
    foreach (int i; 1 .. BOARD_HEIGHT + 1)
    {
        printCharacter(i , 0, BASE_CHARACTER + LAST_LETTER_NUMBER - i + 1); // left border
        printCharacter(i , 3 + BOARD_WIDTH, BASE_CHARACTER + BOARD_WIDTH + i); // right border
    }
    writeln();
    setStyle(DEFAULT_INK);
}

void eraseLineFrom(int line, int column)
{
    setCursorPosition(line, column);
    eraseLineToEnd();
}

/// Print the given mistake effect, do a pause and erase it.

void printMistakeEffect(string effect)
{
    import core.thread.osthread : Thread;
    import core.time : msecs;
    import std.stdio : writeln;

    int x = boardCenteredX(effect);
    hideCursor();
    setCursorPosition(MESSAGES_Y, x);
    setStyle(MISTAKE_EFFECT_INK);
    writeln(effect);
    setStyle(DEFAULT_INK);
    Thread.sleep(MISTAKE_EFFECT_PAUSE.msecs);
    eraseLineFrom(MESSAGES_Y, x);
    showCursor();
}

/// Return a new message of the given length, after marking its letters on the
/// board.

string message(int length)
{
    import core.thread.osthread : Thread;
    import core.time : msecs;
    import std.conv : to;
    import std.stdio : writeln;

    const(int) LETTER_PAUSE = 1000; // milliseconds
    int y = 0;
    int x = 0;
    char[] letters;
    hideCursor();
    foreach (int i; 0 .. length + 1)
    {
        int letterNumber = randomIntInInclusiveRange(
            FIRST_LETTER_NUMBER,
            LAST_LETTER_NUMBER);
        letters ~= to!(char)(BASE_CHARACTER + letterNumber);
        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;
        }
        setStyle(PLANCHETTE_INK);
        printCharacter(y, x, PLANCHETTE);
        writeln();
        Thread.sleep(LETTER_PAUSE.msecs);
        setStyle(DEFAULT_INK);
        printCharacter(y, x, SPACE);
    }
    showCursor();
    return cast(string)(letters);

}

string acceptMessage()
{
    import std.uni : toUpper;

    setStyle(INPUT_INK);
    setCursorPosition(INPUT_Y, INPUT_X);
    string result = toUpper(acceptString("? "));
    setStyle(DEFAULT_INK);
    eraseLineFrom(INPUT_Y, INPUT_X);
    return result;
}

void play()
{

    int score = 0;
    int mistakes = 0;

    printCenteredTitle(1);
    printBoard();

    while (true)
    {
        int messageLength = randomIntInInclusiveRange(
            MIN_MESSAGE_LENGTH,
            MAX_MESSAGE_LENGTH
            );
        string messageReceived = message(messageLength);
        string messageUnderstood = acceptMessage();
        if (messageReceived != messageUnderstood)
        {
            mistakes += 1;
            final 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)
            {
                printCentered("Whew!  The spirits have gone!", MESSAGES_Y);
                printCentered("You live to face another day!", MESSAGES_Y + 1);
                return;
            }
        }
    }
}

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

void main()
{
    import std.stdio : writeln;

    setStyle(DEFAULT_INK);
    clearScreen();
    printCredits();

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

    pressEnter("\nPress the Enter key to start. ");
    clearScreen();
    play();
    writeln();

}

Sine Wave

/*
Sine Wave

Original version in BASIC:
    Creative Computing (Morristown, New Jersey, USA), ca. 1980.

This version in D:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-03, 2023-09.

Last modified 20251219T2130+0100.
*/

module sine_wave;

// Erase the screen, reset the attributes and move the cursor to the home
// position.

void clear()
{
    import std.stdio : write;

    write("\033[2J\033[0m\033[H");
}

void printCredits()
{
    import std.stdio : readln;
    import std.stdio : writeln;

    clear();
    writeln("Sine Wave\n");
    writeln("Original version in BASIC:");
    writeln("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2023, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    writeln("Press Enter to start the program.");
    readln();
}

string[2] word; // user words

void getWords()
{
    import std.stdio : readln;
    import std.stdio : writef;
    import std.string : strip;

    clear();
    string[2] order = ["first", "second"];
    for (int i = 0; i < word.length; i++)
    {
        writef("Enter the %s word: ", order[i]);
        word[i] = strip(readln());
    }

}

void draw()
{
    import std.conv : to;
    import std.math : sin;
    import std.range : repeat;
    import std.stdio : write;
    import std.stdio : writeln;

    clear();
    bool even = false;
    double angle = 0.0;
    for (angle = 0.0; angle <= 40.0; angle += 0.25)
    {
        write(repeat(' ', to!int(26 + 25 * sin(angle))));
        writeln(word[to!int(even)]);
        even = !even;
    }
}

void main()
{
    printCredits();
    getWords();
    draw();
}

Slots

// Slots
//     A slot machine simulation.

// Original version in BASIC:
//     Creative Computing (Morristown, New Jersey, USA).
//     Produced by Fred Mirabelle and Bob Harper on 1973-01-29.

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-24.
//
// Last modified: 20251220T0632+0100.

module slots;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void hideCursor()
{
    import std.stdio : write;

    write("\x1B[?25l");
}

void showCursor()
{
    import std.stdio : write;

    write("\x1B[?25h");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

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

enum REELS = 3;

string[] image = [" BAR  ", " BELL ", "ORANGE", "LEMON ", " PLUM ", "CHERRY"];
enum BAR = 0; // position of "BAR" in `image`.
const(int[]) color = [
    FOREGROUND + WHITE,
    FOREGROUND + CYAN,
    FOREGROUND + YELLOW,
    FOREGROUND + BRIGHT + YELLOW,
    FOREGROUND + BRIGHT + WHITE,
    FOREGROUND + BRIGHT + RED ];
enum MAX_BET = 100;
enum MIN_BET = 1;

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

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

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

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

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

void printCredits()
{
    import std.stdio : writeln;

    clearScreen();
    writeln("Slots");
    writeln("A slot machine simulation.\n");
    writeln("Original version in BASIC:");
    writeln("    Creative computing (Morristown, New Jersey, USA).");
    writeln("    Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n");
    writeln("This version in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    acceptString("Press Enter for instructions. ");
}

void printInstructions()
{
    import std.stdio : writefln;
    import std.stdio : writeln;

    clearScreen();
    writeln("You are in the H&M casino, in front of one of our");
    writefln("one-arm bandits. Bet from %d to %d USD (or 0 to quit).\n",
        MIN_BET, MAX_BET);
    acceptString("Press Enter to start. ");
}

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

int won(int prize, int bet)
{
    import std.stdio : writeln;

    final switch (prize)
    {
        case 2:
            writeln("DOUBLE!");
            break;
        case 5:
            writeln("*DOUBLE BAR*");
            break;
        case 10:
            writeln("**TOP DOLLAR**");
            break;
        case 100:
            writeln("***JACKPOT***");
    }
    writeln("You won!");
    return (prize + 1) * bet;
}

void showStandings(int usd)
{
    import std.stdio : writefln;

    writefln("Your standings are %d USD.", usd);
}

void printReels(int[REELS] reel)
{
    import std.stdio : writef;
    import std.stdio : writeln;

    moveCursorHome();
    foreach (int r; reel)
    {
        setStyle(color[r]);
        writef("[%s] ", image[r]);
    }
    setStyle(NORMAL_STYLE);
    writeln();
}

void initReels(ref int[REELS] reel)
{
    import std.random : uniform;

    int images = cast(int)(image.length);
    foreach (int i; 0 .. reel.length)
    {
        reel[i] = uniform(0, images);
    }
}

void spinReels(ref int[REELS] reel)
{
    import core.time : MonoTime;

    int SECONDS = 2;
    hideCursor();
    auto startTime = MonoTime.currTime;
    long startSecond = (startTime - MonoTime.zero).total!"seconds";
    long currentSecond;
    do
    {
        initReels(reel);
        printReels(reel);
        auto currentTime = MonoTime.currTime;
        currentSecond = (currentTime - MonoTime.zero).total!"seconds";
    } while (currentSecond - startSecond <= SECONDS);
    showCursor();
}

void play()
{
    import std.algorithm.iteration : uniq;
    import std.algorithm.searching : count;
    import std.algorithm.sorting : sort;
    import std.stdio : writefln;
    import std.stdio : writeln;

    int standings = 0;
    int bet = 0;
    int[REELS] reel;

    initReels(reel);

    playLoop: while (true)
    {

        betLoop: while (true)
        {
            clearScreen();
            printReels(reel);
            bet = acceptInteger("Your bet (or 0 to quit): ");
            if (bet > MAX_BET)
            {
                writefln("House limits are %d USD.", MAX_BET);
                acceptString("Press Enter to try again. ");
            }
            else if (bet < MIN_BET)
            {
                string confirmation = acceptString("Type \"q\" to confirm you want to quit. ");
                if (confirmation == "q" || confirmation == "Q")
                {
                    break playLoop;
                }
            }
            else
            {
                break betLoop;
            } // bet check

        } // bet loop

        clearScreen();
        spinReels(reel);
        int equals = REELS - cast(int)(reel[0 .. REELS].sort().uniq.count);
        equals += cast(int)(equals > 0);
        int bars = cast(int)(count(reel[0 .. REELS], BAR));

        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:
                writeln("You lost.");
                standings -= bet;
        } // prize check

        showStandings(standings);
        acceptString("Press Enter to continue. ");

    } // play loop

    showStandings(standings);

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

}

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

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 D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-03-22.
//
// Last modified: 20251220T0633+0100.

module stars;

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

int acceptValidInteger(string prompt)
{
    import std.conv : to;
    import std.stdio : writeln;

    int result;
    while (true)
    {
        string s = acceptString(prompt);
        try
        {
            result = to!int(s);
            break;
        }
        catch (Exception exc)
        {
            writeln("Integer expected.");
        }
    }
    return result;
}

bool isYes(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "ok", "y", "yeah", "yes":
            return true;
        default:
            return false;
    }
}

void main()
{
    import std.range : repeat;
    import std.stdio : writef;
    import std.stdio : writeln;

    string name = acceptString("What is your name? ");
    writef("Hello, %s.\n", name);
    do
    {
        int number = acceptValidInteger("How many stars do you want? ");
        writeln(repeat('*', number));
    } while (isYes(acceptString("Do you want more stars? ")));
}

Strings

// Strings

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

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written in 2025-03-22/23.
//
// Last modified: 20251220T0653+0100.

module strings;

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

int acceptValidInteger(string prompt)
{
    import std.conv : to;
    import std.stdio : writeln;

    int result;
    while (true)
    {
        string s = acceptString(prompt);
        try
        {
            result = to!int(s);
            break;
        }
        catch (Exception exc)
        {
            writeln("Integer expected.");
        }
    }
    return result;
}

void main()
{
    import std.array : replicate;
    import std.conv : to;
    import std.range : repeat;
    import std.stdio : writefln;

    string s = acceptString("Enter a string: ");
    int n = acceptValidInteger("Enter an integer: ");

    writefln("ASC(\"%1$s\") --> to!(int)(\"%1$s\"[0]) --> %2$d", s, to!(int)(s[0]));
    writefln("CHR$(%1$d) --> to!char(%1$d) --> \"%2$s\"", n, to!char(n));
    writefln("LEFT$(\"%1$s\", %2$d) --> \"%1$s\"[0 .. %2$d] --> \"%3$s\"", s, n, s[0 .. n]); // XXX TODO utf-8
    writefln("MID$(\"%1$s\", %2$d) --> \"%1$s\"[%2$d - 1 .. \"%1$s\".length] --> \"%3$s\"", s, n, s[n - 1 .. s.length]); // XXX TODO utf-8
    writefln("MID$(\"%1$s\", %2$d, 3) --> \"%1$s\"[%2$d - 1 .. %2$d - 1 + 3] --> \"%3$s\"", s, n, s[n - 1 .. n - 1 + 3]); // XXX TODO utf-8
    writefln("RIGHT$(\"%1$s\", %2$d) --> \"%1$s\"[\"%1$s\".length - %2$d .. \"%2s\".length] --> \"%3$s\"", s, n, s[s.length - n .. s.length]); // XXX TODO utf-8
    writefln("LEN(\"%1$s\") --> \"%1$s\".length --> %2$d", s, s.length);
    writefln("VAL(\"%1$s\") --> to!(float)(\"%1$s\") --> %2$f", s, to!(float)(s)); // XXX TODO catch error
    writefln("STR$(%1$d) --> to!(string)(%1$d) --> \"%2$s\"", n, to!(string)(n));
    writefln("SPC(%1$d) --> repeat(' ', %1$d) --> \"%2$s\"", n, repeat(' ', n));
    writefln("SPC(%1$d) --> replicate(\" \", %1$d) --> \"%2$s\"", n, replicate(" ", n));
}

Xchange

// Xchange

// Original version in BASIC:
//     Written 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 D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written in 2025-03.
//
// Last modified: 20251220T0654+0100.

module xchange;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setCursorPosition(int line, int column)
{
    import std.stdio : writef;

    writef("\x1B[%d;%dH", line, column);
}

void setCursorCoord(int[] coord)
{
    setCursorPosition(coord[0], coord[1]);
}

void hideCursor()
{
    import std.stdio : write;

    write("\x1B[?25l");
}

void showCursor()
{
    import std.stdio : write;

    write("\x1B[?25h");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseLineToEnd()
{
    import std.stdio : write;

    write("\x1B[K");
}

void eraseScreenToEnd()
{
    import std.stdio : write;

    write("\x1B[J");
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

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

enum BOARD_INK = FOREGROUND + BRIGHT + CYAN;
enum DEFAULT_INK = FOREGROUND + WHITE;
enum INPUT_INK = FOREGROUND + BRIGHT + GREEN;
enum INSTRUCTIONS_INK = FOREGROUND + YELLOW;
enum TITLE_INK = FOREGROUND + BRIGHT + RED;

enum BLANK = "*";

enum GRID_HEIGHT = 3; // cell rows
enum GRID_WIDTH = 3; // cell columns

enum CELLS = GRID_WIDTH * GRID_HEIGHT;

string[CELLS] pristineGrid;

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

enum FIRST_PLAYER = 0;
enum MAX_PLAYERS = 4;

string[][] grid = new string[][](MAX_PLAYERS, CELLS);

bool[MAX_PLAYERS] isPlaying;

int players = 0;

enum QUIT_COMMAND = "X";

enum axis { Y, X }; // to use with coord arrays instead of 0 and 1, for clarity

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

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    write(prompt);
    return strip(readln());
}

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

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

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

string getString(string prompt = "")
{
    setStyle(INPUT_INK);
    string s = acceptString(prompt);
    setStyle(DEFAULT_INK);
    return s;
}

void pressEnter(string prompt)
{
    acceptString(prompt);
}

bool isYes(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool isNo(string s)
{
    import std.uni : toLower;

    switch (toLower(s))
    {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".

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
// =============================================================

void printTitle()
{
    import std.stdio : writeln;

    setStyle(TITLE_INK);
    writeln("Xchange");
    setStyle(DEFAULT_INK);
}

void printCredits()
{
    import std.stdio : writeln;

    printTitle();
    writeln("\nOriginal version in BASIC:");
    writeln("    Written by Thomas C. McIntire, 1979.");
    writeln("    Published in \"The A to Z Book of Computer Games\", 1979.");
    writeln("    https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up");
    writeln("    https://github.com/chaosotter/basic-games");
    writeln("This improved remake in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

void printInstructions()
{
    import std.stdio : writefln;
    import std.stdio : writeln;

    printTitle();
    setStyle(INSTRUCTIONS_INK);
    writeln("\nOne or two may play.  If two, you take turns.  A grid looks like this:\n");
    setStyle(BOARD_INK);
    writeln("    F G D");
    writefln("    A H %s", BLANK);
    writeln("    E B C\n");
    setStyle(INSTRUCTIONS_INK);
    writeln("But it should look like this:\n");
    setStyle(BOARD_INK);
    writeln("    A B C");
    writeln("    D E F");
    writefln("    G H %s\n", BLANK);
    setStyle(INSTRUCTIONS_INK);
    writefln("You may exchange any one letter with the '%s', but only one that's adjacent:", BLANK);
    writeln("above, below, left, or right.  Not all puzzles are possible, and you may enter");
    writefln("'%s' to give up.\n", QUIT_COMMAND);
    writeln("Here we go...");
    setStyle(DEFAULT_INK);
}

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

// Print the given player's grid title.

void printGridTitle(int player)
{
    import std.stdio : writef;

    setCursorPosition(GRIDS_ROW, GRIDS_COLUMN + (player * GRIDS_GAP));
    writef("Player %d", player + 1);
}

// Return the cursor position of the given player's grid cell.

int[] cellPosition(int player, int cell)
{
    int gridRow = cell / GRID_HEIGHT;
    int gridColumn = cell % GRID_WIDTH;
    int titleMargin = players > 1 ? 2 : 0;
    int row = GRIDS_ROW + titleMargin + gridRow;
    int column = GRIDS_COLUMN +
        (gridColumn * CELLS_GAP) +
        (player * GRIDS_GAP);
    return [row, column];
}

// Return the cursor position of the given player's grid prompt.

int[] gridPromptPositionOf(int player)
{
    int[] coord = cellPosition(player, CELLS);
    int row = coord[0];
    int column = coord[1];
    return [row + 1, column];
}

// Print the given player's grid, in the given or default color.

void printGrid(int player, int color = BOARD_INK)
{
    import std.stdio : write;

    if (players > 1)
    {
        printGridTitle(player);
    }
    setStyle(color);
    foreach (int cell; 0 .. CELLS)
    {
        setCursorCoord(cellPosition(player, cell));
        write(grid[player][cell]);
    }
    setStyle(DEFAULT_INK);
}

// Print the current players' grids.

void printGrids()
{
    import std.stdio : writeln;

    foreach (int player; 0 .. players)
    {
        if (isPlaying[player])
        {
            printGrid(player);
        }
    }
    writeln();
    eraseScreenToEnd();
}

// Init the grids.

void initGrids()
{
    import std.random : randomShuffle;

    grid[0] = pristineGrid.dup[0 .. $].randomShuffle();
    foreach (int player; 1 .. players)
    {
        grid[player] = grid[0].dup;
    }
}

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

// Return a message prefix for the given player.

string playerPrefix(int player)
{
    import std.format : format;

    return players > 1 ? format("Player %d: ", player + 1) : "";
}

// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.

int[] messagePosition(int player, int rowInc = 0 )
{
    int[] promptCoord = gridPromptPositionOf(player);
    return [promptCoord[axis.Y] + 2 + rowInc, 1];
}

// Print the given message about the given player, adding the given row
// increment, which defaults to zero, to the default cursor coordinates.

void printMessage(string message, int player, int rowInc = 0)
{
    import std.stdio : writef;
    import std.stdio : writeln;

    setCursorCoord(messagePosition(player, rowInc));
    writef("%s%s", playerPrefix(player), message);
    eraseLineToEnd();
    writeln();
}

// Erase the last message about the given player.

void eraseMessage(int player)
{
    setCursorCoord(messagePosition(player));
    eraseLineToEnd();
}

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

// Return a message with the players range.

string playersRangeMessage()
{
    import std.format : format;

    if (MAX_PLAYERS == 2)
    {
        return "1 or 2";
    }
    else
    {
        return format("from 1 to %d", MAX_PLAYERS);
    }
}

// Return the number of players, asking the user if needed.

int numberOfPlayers()
{
    import std.format : format;
    import std.stdio : writeln;

    int players = 0;
    printTitle();
    writeln();
    if (MAX_PLAYERS == 1)
    {
        players = 1;
    }
    else
    {
        while (players < 1 || players > MAX_PLAYERS)
        {
            string prompt = format("Number of players (%s): ", playersRangeMessage());
            players = getInteger(prompt);
        }
    }
    return players;
}

// Is the given cell the first one on a grid row?

bool isFirstCellOfGridRow(int cell)
{
    return cell % GRID_WIDTH == 0;
}

// Is the given cell the last one on a grid row?

bool isLastCellOfGridRow(int cell)
{
    return (cell + 1) % GRID_WIDTH == 0;
}

// Are the given cells adjacent?

bool areCellsAdjacent(int cell1, int cell2)
{
    return (cell2 == cell1 + 1 && !isFirstCellOfGridRow(cell2)) ||
        (cell2 == cell1 + GRID_WIDTH) ||
        (cell2 == cell1 - 1 && !isLastCellOfGridRow(cell2)) ||
        (cell2 == cell1 - GRID_WIDTH);
}

// If the given player's character cell is a valid move, i.e. it is adjacent to
// the blank cell, return the blank cell; otherwise return -1.

int positionToCell(int player, int charCell)
{
    import std.format : format;

    foreach (int cell; 0 .. CELLS)
    {
        if (grid[player][cell] == BLANK)
        {
            if (areCellsAdjacent(charCell, cell))
            {
                return cell;
            }
            else
            {
                break;
            }
        }
    }
    printMessage(format("Illegal move \"%s\".", grid[player][charCell]), player);
    return -1;
}

// If the given player's command is valid, i.e. a grid character, return its
// position; otherwise return -1.

int commandToPosition(int player, string command)
{
    import std.format : format;

    if (command != BLANK)
    {
        foreach (int position;  0 .. CELLS)
        {
            if (command == grid[player][position])
            {
                return position;
            }
        }
    }
    printMessage(format("Invalid character \"%s\".", command), player);
    return -1;
}

// Forget the given player, who quitted.

void forgetPlayer(int player)
{
    isPlaying[player] = false;
    printGrid(player, DEFAULT_INK);
}

// Play the turn of the given player.

void playTurn(int player)
{
    import std.uni : toUpper;

    int blankPosition = 0;
    int characterPosition = 0;

    if (isPlaying[player])
    {
        while (true)
        {
            while (true)
            {
                int[] coord = gridPromptPositionOf(player);
                setCursorCoord(coord);
                eraseLineToEnd();
                setCursorCoord(coord);
                string command = toUpper(getString("Move: "));
                if (command == QUIT_COMMAND)
                {
                    forgetPlayer(player);
                    return;
                }
                int position = commandToPosition(player, command);
                if (position >= 0)
                {
                    characterPosition = position;
                    break;
                }
            }
            int position = positionToCell(player, characterPosition);
            if (position >= 0)
            {
                blankPosition = position;
                break;
            }
        }
        eraseMessage(player);
        grid[player][blankPosition] = grid[player][characterPosition];
        grid[player][characterPosition] = BLANK;
    }
}

// Play the turns of all players.

void playTurns()
{
    foreach (player; 0 .. players)
    {
        playTurn(player);
    }
}

// Is someone playing?

bool isSomeonePlaying()
{
    foreach (int player; 0 .. players)
    {
        if (isPlaying[player])
        {
            return true;
        }
    }
    return false;
}

bool hasAnEmptyGrid(int player)
{
    foreach (int i; 0 .. CELLS)
    {
        if (grid[player][i] != "")
        {
            return false;
        }
    }
    return true;
}

// Has someone won? If so, print a message for every winner and return `true`
// otherwise just return `false`.

bool hasSomeoneWon()
{
    import std.format : format;

    int winners = 0;
    foreach (int player; 0 .. players)
    {
        if (isPlaying[player])
        {
            if (hasAnEmptyGrid(player))
            {
                winners += 1;
                if (winners > 0)
                {
                    printMessage(
                        format("You're the winner%s!", (winners > 1) ? ", too" : ""),
                        player,
                        winners - 1);
                }
            }
        }
    }
    return winners > 0;
}

// Init the game.

void initGame()
{
    clearScreen();
    players = numberOfPlayers();
    foreach (int player; 0 .. players)
    {
        isPlaying[player] = true;
    }
    clearScreen();
    printTitle();
    initGrids();
    printGrids();
}

// Play the game.

void play()
{
    initGame();
    while (isSomeonePlaying())
    {
        playTurns();
        printGrids();
        if (hasSomeoneWon())
        {
            break;
        }
    }
}

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

void initOnce()
{
    import std.conv : to;
    import std.format : format;

    // Init the pristine grid.
    enum FIRST_CHAR_CODE = 'A';
    foreach (int cell; 0 .. CELLS - 1)
    {
        pristineGrid[cell] = format("%s", to!char(FIRST_CHAR_CODE + cell));
    }
    pristineGrid[CELLS - 1] = BLANK;
}

// Return `true` if the player does not want to play another game; otherwise
// return `false`.

bool enough()
{
    setCursorCoord(gridPromptPositionOf(FIRST_PLAYER));
    return !yes("Another game? ");
}

void main()
{
    import std.stdio : writeln;

    initOnce();

    clearScreen();
    printCredits();
    pressEnter("\nPress the Enter key to read the instructions. ");

    clearScreen();
    printInstructions();
    pressEnter("\nPress the Enter key to start. ");

    while (true)
    {
        play();
        if (enough())
        {
            break;
        }
    }
    writeln("So long…");
}

Z-End

// Z-End

// Original version in BASIC:

// A to Z Book of Computer Games, by Thomas C. McIntire, 1979.
//  - https://archive.org/details/A_to_Z_Book_of_Computer_Games_1979_Thomas_C_McIntire/page/n293/mode/2up
//  - https://github.com/chaosotter/basic-games/tree/master/games/A%20to%20Z%20Book%20of%20Computer%20Games/Z-End

// This version in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-12-19.
//
// Last modified: 20251219T1644+0100.

module z_end;

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

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

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

enum NORMAL_STYLE = 0;

void moveCursorHome()
{
    import std.stdio : write;

    write("\x1B[H");
}

void setStyle(int style)
{
    import std.stdio : writef;

    writef("\x1B[%dm", style);
}

void color(int n)
{
    setStyle(n + FOREGROUND);
}

void resetAttributes()
{
    setStyle(NORMAL_STYLE);
}

void eraseScreen()
{
    import std.stdio : write;

    write("\x1B[2J");
}

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

// Input {{{1
// =============================================================================

string acceptString(string prompt)
{
    import std.stdio : readln;
    import std.stdio : write;
    import std.string : strip;

    color(GREEN);
    scope (exit)
    {
        color(WHITE);
    }
    write(prompt);
    return strip(readln());
}

int acceptValidInteger(string prompt)
{
    int result;
    while (true)
    {
        string s = acceptString(prompt);
        try
        {
            import std.conv : to;

            result = to!int(s);
            break;
        }
        catch (Exception exc)
        {
            import std.stdio : writeln;

            writeln("Integer expected.");
        }
    }
    return result;
}

bool isYes(string answer)
{
    import std.uni : toLower;

    switch (toLower(answer))
    {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

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

enum Player
{
    computer,
    human,
}

enum alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

void printRules()
{
    import std.stdio : writeln;

    clearScreen();
    color(RED);
    writeln("Z-End\n");
    string answer = acceptString("Skip the rules? (Y/N) ");
    if (!isYes(answer))
    {
        color(YELLOW);
        writeln();
        writeln("I'll print the alphabet, and you're first.  You type the number of letters");
        writeln("that I should omit next time.  We take turns, and the limit per turn is five.");
        writeln("The one that gets the 'Z' is the loser, and that's Z-End!");
        writeln();
        writeln("Good luck, cuz I'm clever...");
        color(WHITE);
    }
    writeln();
}

int firstLetter;

int computerPick()
{
    import std.conv : to;
    import std.stdio : writeln;

    int pick;
    int remainingLetters = to!int(alphabet.length - firstLetter);
    if (remainingLetters < 6)
    {
        pick = remainingLetters - 1;
    }
    else if (remainingLetters > 10)
    {
        import std.random : uniform;
        pick = uniform(1, 5 + 1);
    }
    else
    {
        pick = 1;
    }
    writeln("My pick is ", pick, ".");
    return pick;
}

int humanPick()
{
    import std.stdio : writeln;

    int pick;
    while (true)
    {
        pick = acceptValidInteger("Your turn (1-5) ");
        color(WHITE);
        if (pick < 1 || pick > 5)
        {
            writeln("Illegal entry -- must be in range 1 to 5!");
        }
        else
        {
            break;
        }
    }
    return pick;
}

bool gameOver()
{
    return firstLetter == alphabet.length - 1;
}

void printAlphabet(int omittedLetters = 0)
{
    import std.stdio : writeln;

    firstLetter += omittedLetters;
    color(MAGENTA);
    writeln(alphabet[firstLetter .. $]);
    writeln();
    color(WHITE);
}

void printResult(Player player)
{
    import std.stdio : write;
    import std.stdio : writeln;

    write("Z-End -- ");
    final switch (player)
    {
        case Player.computer:
            writeln("Ha ha!");
            break;
        case Player.human:
            writeln("Oops!");
            break;
    }
}

int pick(Player player)
{
    final switch (player)
    {
        case Player.computer:
            return computerPick();
        case Player.human:
            return humanPick();
    }
}

bool playing(Player player)
{
    int pick = pick(player);
    printAlphabet(omittedLetters: pick);
    if (gameOver())
    {
        printResult(player);
    }
    return !gameOver();
}

void play()
{
    firstLetter = 0;
    printAlphabet();
    while (playing(Player.human) && playing(Player.computer)) { }
}

bool again()
{
    import std.stdio : writeln;

    writeln();
    string answer = acceptString("Do it again (Y/N) ");
    return isYes(answer);
}

void main()
{
    import std.stdio : writeln;

    printRules();
    do
    {
        play();
    } while (again());
    writeln("\nGoodbye.");
}

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 C#
Conversion of old BASIC programs to C# 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 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