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