Basics of D

Descripción del contenido de la página

Conversión de antiguos programas de BASIC a D para aprender los rudimentos de este lenguaje.

Etiquetas:

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.");
}

Páginas relacionadas

Basics off
Metaproyecto sobre los proyectos «Basics of…».
Basics of 8th
Conversión de antiguos programas de BASIC a 8th para aprender los rudimentos de este lenguaje.
Basics of Ada
Conversión de antiguos programas de BASIC a Ada para aprender los rudimentos de este lenguaje.
Basics of Arturo
Conversión de antiguos programas de BASIC a Arturo para aprender los rudimentos de este lenguaje.
Basics of C#
Conversión de antiguos programas de BASIC a C# para aprender los rudimentos de este lenguaje.
Basics of C3
Conversión de antiguos programas de BASIC a C3 para aprender los rudimentos de este lenguaje.
Basics of Chapel
Conversión de antiguos programas de BASIC a Chapel para aprender los rudimentos de este lenguaje.
Basics of Clojure
Conversión de antiguos programas de BASIC a Clojure para aprender los rudimentos de este lenguaje.
Basics of Crystal
Conversión de antiguos programas de BASIC a Crystal para aprender los rudimentos de este lenguaje.
Basics of Elixir
Conversión de antiguos programas de BASIC a Elixir para aprender los rudimentos de este lenguaje.
Basics of F#
Conversión de antiguos programas de BASIC a F# para aprender los rudimentos de este lenguaje.
Basics of Factor
Conversión de antiguos programas de BASIC a Factor para aprender los rudimentos de este lenguaje.
Basics of FreeBASIC
Conversión de antiguos programas de BASIC a FreeBASIC para aprender los rudimentos de este lenguaje.
Basics of Gleam
Conversión de antiguos programas de BASIC a Gleam para aprender los rudimentos de este lenguaje.
Basics of Go
Conversión de antiguos programas de BASIC a Go para aprender los rudimentos de este lenguaje.
Basics of Hare
Conversión de antiguos programas de BASIC a Hare para aprender los rudimentos de este lenguaje.
Basics of Haxe
Conversión de antiguos programas de BASIC a Haxe para aprender los rudimentos de este lenguaje.
Basics of Icon
Conversión de antiguos programas de BASIC a Icon para aprender los rudimentos de este lenguaje.
Basics of Io
Conversión de antiguos programas de BASIC a Io para aprender los rudimentos de este lenguaje.
Basics of Janet
Conversión de antiguos programas de BASIC a Janet para aprender los rudimentos de este lenguaje.
Basics of Julia
Conversión de antiguos programas de BASIC a Julia para aprender los rudimentos de este lenguaje.
Basics of Kotlin
Conversión de antiguos programas de BASIC a Kotlin para aprender los rudimentos de este lenguaje.
Basics of Lobster
Conversión de antiguos programas de BASIC a Lobster para aprender los rudimentos de este lenguaje.
Basics of Lua
Conversión de antiguos programas de BASIC a Lua para aprender los rudimentos de este lenguaje.
Basics of Nature
Conversión de antiguos programas de BASIC a Nature para aprender los rudimentos de este lenguaje.
Basics of Neat
Conversión de antiguos programas de BASIC a Neat para aprender los rudimentos de este lenguaje.
Basics of Neko
Conversión de antiguos programas de BASIC a Neko para aprender los rudimentos de este lenguaje.
Basics of Nelua
Conversión de antiguos programas de BASIC a Nelua para aprender los rudimentos de este lenguaje.
Basics of Nim
Conversión de antiguos programas de BASIC a Nim para aprender los rudimentos de este lenguaje.
Basics of Nit
Conversión de antiguos programas de BASIC a Nit para aprender los rudimentos de este lenguaje.
Basics of Oberon-07
Conversión de antiguos programas de BASIC a Oberon-07 para aprender los rudimentos de este lenguaje.
Basics of OCaml
Conversión de antiguos programas de BASIC a OCaml para aprender los rudimentos de este lenguaje.
Basics of Odin
Conversión de antiguos programas de BASIC a Odin para aprender los rudimentos de este lenguaje.
Basics of Pike
Conversión de antiguos programas de BASIC a Pike para aprender los rudimentos de este lenguaje.
Basics of Pony
Conversión de antiguos programas de BASIC a Pony para aprender los rudimentos de este lenguaje.
Basics of Python
Conversión de antiguos programas de BASIC a Python para aprender los rudimentos de este lenguaje.
Basics of Racket
Conversión de antiguos programas de BASIC a Racket para aprender los rudimentos de este lenguaje.
Basics of Raku
Conversión de antiguos programas de BASIC a Raku para aprender los rudimentos de este lenguaje.
Basics of Retro
Conversión de antiguos programas de BASIC a Retro para aprender los rudimentos de este lenguaje.
Basics of Rexx
Conversión de antiguos programas de BASIC a Rexx para aprender los rudimentos de este lenguaje.
Basics of Ring
Conversión de antiguos programas de BASIC a Ring para aprender los rudimentos de este lenguaje.
Basics of Rust
Conversión de antiguos programas de BASIC a Rust para aprender los rudimentos de este lenguaje.
Basics of Scala
Conversión de antiguos programas de BASIC a Scala para aprender los rudimentos de este lenguaje.
Basics of Scheme
Conversión de antiguos programas de BASIC a Scheme para aprender los rudimentos de este lenguaje.
Basics of Styx
Conversión de antiguos programas de BASIC a Styx para aprender los rudimentos de este lenguaje.
Basics of Swift
Conversión de antiguos programas de BASIC a Swift para aprender los rudimentos de este lenguaje.
Basics of V
Conversión de antiguos programas de BASIC a V para aprender los rudimentos de este lenguaje.
Basics of Vala
Conversión de antiguos programas de BASIC a Vala para aprender los rudimentos de este lenguaje.
Basics of Zig
Conversión de antiguos programas de BASIC a Zig para aprender los rudimentos de este lenguaje.

Enlaces externos relacionados