Basics of Chapel

Priskribo de la ĉi-paĝa enhavo

Konverto de malnovaj BASIC-programoj al Chapel por lerni la fundamentojn de ĉi-tiu lingvo.

Etikedoj:

3D Plot

/*
3D Plot

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

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

Written on 2025-04-04.

Last modified 20250405T2002+0200.
*/

import IO;
import Math;

const space = " ";
const dot = "*";
const width = 56;

// Erase the screen, reset the attributes and move the cursor to the home
// position.
proc clear() {
    write("\x1B[2J\x1B[0m\x1B[H");
}

// Clear the screen, display the credits and wait for a keypress.
proc printCredits() {
    clear();
    writeln("3D Plot\n");
    writeln("Original version in BASIC:");
    writeln("  Creative computing (Morristown, New Jersey, USA), ca. 1980.\n");
    writeln("This version in Chapel:");
    writeln("  Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("  SPDX-License-Identifier: Fair\n");
    writeln("Press Enter to start the program.");
    IO.readLine();
}

proc a(z: real): real {
    return 30 * Math.exp(-z * z / 100);
}

proc draw() {
    var l: int = 0;
    var z: int = 0;
    var y1: int = 0;
    var line: [0 ..< width] string;
    clear();
    var x: real = -30.0;
    while x <= 30.0 {
        for pos in 0 ..< width {
            line[pos] = space;
        }
        l = 0;
        y1 = 5 * (sqrt(900 - x * x) / 5): int;
        var y: int = y1;
        while y >= -y1 {
            z = (25 + a(sqrt(x * x + (y * y): real)) - 0.7 * (y): real): int;
            if z > l {
                l = z;
                line[z] = dot;
            }
            y += -5;
        } // y loop
        for pos in 0 ..< width {
            write(line[pos]);
        }
        writeln("");
        x += 1.5;
    } // x loop
}

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

Bagels

// Bagels

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

// This version in Chapel:
//  Copyright (c) 2025, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-04-06.
//
// Last modified: 20250406T1921+0200.

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

import IO;
import Random;

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

const NORMAL_STYLE = 0;

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

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

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

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

// Return `true` if the given string is "yes" or a synonym.
//
proc isYes(s: string): bool {
    select s.toLower() {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise do return false;
    }
}

// Return `true` if the given string is "no" or a synonym.
//
proc isNo(s: string): bool {
    select s.toLower() {
        when "n", "no", "nope" do return true;
        otherwise do 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".
//
proc yes(prompt: string): bool {
    var result: bool;
    while true {
        var answer: string = acceptString(prompt);
        if isYes(answer) {
            result = true;
            break;
        } else if isNo(answer) {
            result = false;
            break;
        }
    }
    return result;
}

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

// Clear the screen, display the credits and wait for a keypress.
//
proc printCredits() {
    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 Chapel:");
    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.
//
proc printInstructions() {
    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. ");
}

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

const DIGITS = 3;

// Print the given prompt and return an array with a three-digit number type by
// the user.
//
proc getInput(prompt: string): [0 ..< DIGITS] int {
    var userDigit: [0 ..< DIGITS] int;
    const ASCII_0 = '0';
    label getLoop while true {
        var input: string = acceptString(prompt);
        if input.size != DIGITS {
            writef("Remember it's a %i-digit number.\n", DIGITS);
            continue getLoop;
        }
        for pos in 0 ..< input.size {
            var digit: string = input[pos];
            if input[pos].isDigit() {
                userDigit[pos] = digit: int;
            } 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.
//
proc randomNumber(): [0 ..< DIGITS] int {
    var rsInt = new Random.randomStream(int);
    var randomDigit: [0 ..< DIGITS] int;
    for i in 0 ..< DIGITS {
        label digitLoop while true {
            randomDigit[i] = rsInt.next(0, 10 - 1);
            for j in 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`.
//
proc isAnyRepeated(numbers: [] int): bool {
    for i0 in 0 ..< numbers.size {
        for i1 in i0 + 1 ..< numbers.size {
            if numbers[i0] == numbers[i1] {
                return true;
            }
        }
    }
    return false;
}

// Init and run the game loop.
//
proc play() {
    const TRIES = 20;
    var score: int = 0;
    var fermi: int = 0; // counter
    var pico: int = 0; // counter
    var computerNumber: [0 ..< DIGITS] int;
    var userNumber: [0 ..< DIGITS] int;
    while true {
        clearScreen();
        computerNumber = randomNumber();
        writeln("O.K.  I have a number in mind.");
        for guess in 1 ..< TRIES + 1 {
            //userNumber = getInput("Guess #%02i: ".format(guess)); // XXX TODO
            userNumber = getInput("Guess #" + guess: string + ": "); // XXX TMP
            fermi = 0;
            pico = 0;
            for i in 0 ..< DIGITS {
                for j in 0 ..< DIGITS {
                    if userNumber[i] == computerNumber[j] {
                        if i == j {
                            fermi += 1;
                        } else {
                            pico += 1;
                        }
                    }
                }
            }
            if pico + fermi == 0 {
                writeln("BAGELS");
            } else {
                writeln( "PICO " * pico, "FERMI " * fermi);
                if fermi == DIGITS {
                    break;
                }
            }
        }
        if fermi == DIGITS {
            writeln("You got it!!!");
            score += 1;
        } else {
            writeln("Oh well.");
            writef("That's %i guesses.  My number was ", TRIES);
            for i in 0 ..< DIGITS {
                write(computerNumber[i]);
            }
            writeln(".");
        }
        if !yes("Play again? ") {
            break;
        }
    }
    if score != 0 {
        writef("A %i-point bagels, buff!!\n", score);
    }
    writeln("Hope you had fun.  Bye.");
}

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

Bug

/*
Bug

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

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

Written on 2025-04-07.

Last modified 20250407T0201+0200.
*/

import IO;
import Random;

record Bug {
    var body: bool;
    var neck: bool;
    var head: bool;
    var feelers: int;
    var feelerType: string;
    var tail: bool;
    var legs: int;
    var finished: bool;
}

record Player {
    var pronoun: string;
    var possessive: string;
    var bug: Bug;
}

var computer: Player;
var human: Player;

/// Bug body parts.
enum Part { body = 1, neck, head, feeler, tail, leg }

// Bug body attributes.
const bodyHeight = 2;
const feelerLength = 4;
const legLength = 2;
const maxFeelers = 2;
const maxLegs = 6;
const neckLength = 2;

/// Clear the screen, reset the attributes and move the cursor to the
/// home position.
proc clear() {
    write("\x1B[2J\x1B[0m\x1B[H");
}

/// Move the cursor up by the given number of rows (defaults to 1),
/// without changing the column position.
proc cursorUp(rows: int = 1) {
    write("\x1B[", rows, "A");
}

/// Erase the current line, without moving the cursor position.
proc eraseLine() {
    write("\x1B[2K");
}

/// Move the cursor to the previous row, without changing
/// the column position, and erase its line.
proc erasePreviousLine() {
    cursorUp();
    eraseLine();
}

proc pressEnter(prompt: string) {
    write(prompt);
    IO.stdout.flush();
    IO.readLine();
}

/// Clear the screen, display the credits and wait for a keypress.
proc printCredits() {
    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 Chapel:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    pressEnter("Press Enter to read the instructions. ");
}

const instructions: string = """
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:
""";

proc leftJustified(s: string, width: int): string {
    return s + " " * (width - s.size);
}

/// Print a table with the bug parts' description.
proc printPartsTable() {

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

    // Headers
    var header: [0 .. 2] string = ["Number", "Part", "Quantity"];
    for i in 0 ..< columns {
        write(leftJustified(header[i], columnWidth + columnSeparation));
    }
    writeln();

    // Rulers
    for i in 1 .. columns {
        write(
                "-" * columnWidth,
                (if i < columns then " " * columnSeparation else "")
        );
    }
    writeln();

    // Data
    var partQuantity: [Part.body .. Part.leg] int;
    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 in Part {
        writeln(
            leftJustified((part: int): string, columnWidth + columnSeparation),
            leftJustified(((part): string).toTitle(), columnWidth + columnSeparation),
            partQuantity[part]
        );
    }

}

/// Clear the screen, print the instructions and wait for a keypress.
proc printInstructions() {
    clear();
    writeln("Bug");
    writeln(instructions);
    printPartsTable();
    pressEnter("\nPress Enter to start. ");
}

/// Print a bug head.
proc printHead() {
    writeln("        HHHHHHH");
    writeln("        H     H");
    writeln("        H O O H");
    writeln("        H     H");
    writeln("        H  V  H");
    writeln("        HHHHHHH");
}

/// Print the given bug.
proc printBug(bug: Bug) {
    if bug.feelers {
        for 0 ..< feelerLength {
            write("        ");
            for 0 ..< bug.feelers {
                write(" ", bug.feelerType);
            }
            writeln();
        }
    }
    if bug.head {
        printHead();
    }
    if bug.neck {
        for 0 ..< neckLength {
            writeln("          N N");
        }
    }
    if bug.body {
        writeln("     BBBBBBBBBBBB");
        for 0 ..< bodyHeight {
            writeln("     B          B");
        }
        if bug.tail {
            writeln("TTTTTB          B");
        }
        writeln("     BBBBBBBBBBBB");
    }
    if bug.legs {
        for 0 ..< legLength {
            write("    ");
            for 0 ..< bug.legs {
                write(" L");
            }
            writeln();
        }
    }
}

/// Return `true` if the given bug is finished; otherwise return `false`.
proc finished(bug: Bug): bool {
    return bug.feelers == maxFeelers && bug.tail && bug.legs == maxLegs;
}

var rsInt = new Random.randomStream(int);

/// Return a random number between 1 and 6 (inclusive).
proc dice(): int {
    return rsInt.next(1, 6);
}

/// Array to convert a number to its equilavent text.
var asText: [0 .. 6] string = [
    "no",
    "a",
    "two",
    "three",
    "four",
    "five",
    "six" ]; // maxLegs

/// Return a string containing the given number
/// and the given noun in their proper form.
proc plural(number: int, noun: string): string {
    return asText[number] + " " + noun + (if number > 1 then "s" else "");
}

/// Add the given part to the given player's bug.
proc addPart(part: Part, inout player: Player): bool {
    var changed: bool = false;
    select part {
        when 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;
            }
        }
        when 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;
            }
        }
        when 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;
            }
        }
        when 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 += 1;
                writeln("; ", player.pronoun, " now have ",
                        plural(player.bug.feelers, "feeler"), ":");
                changed = true;
            }
        }
        when 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;
            }
        }
        when 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 += 1;
                writeln("; ", player.pronoun, " now have ",
                        plural(player.bug.legs, "leg"), ":");
                changed = true;
            }
        }
    }
    return changed;
}

/// Ask the user to press the Enter key, wait for the input
/// and then erase the prompt text.
proc prompt() {
    pressEnter("Press Enter to roll the dice. ");
    erasePreviousLine();
}

/// Play one turn for the given player, rolling the dice
/// and updating his bug.
proc turn(inout player: Player) {
    prompt();
    var number: int = dice();
    var part: Part = number: Part;
    write((player.pronoun).toTitle(), " 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.
proc printWinner() {
    select true {
        when human.bug.finished && computer.bug.finished do
            writeln("Both of our bugs are finished in the same number of turns!");
        when finished(human.bug) do
            writeln(human.possessive, " bug is finished.");
        when finished(computer.bug) do
            writeln(computer.possessive, " bug is finished.");
    }
}

/// Return `true` if either bug is finished, i.e. the game ending condition.
proc gameOver(): bool {
    return human.bug.finished || computer.bug.finished;
}

/// Execute the game loop.
proc play() {
    clear();
    do {
        turn(human);
        turn(computer);
    } while !gameOver();
    printWinner();
}

/// Init player data before a new game.
proc initData() {
    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;
}

proc main() {
    initData();
    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 Chapel:
    Copyright (c) 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2025-04-05.

Last modified 20250405T2255+0200.
*/

import IO;

// Erase the screen, reset the attributes and move the cursor to the home
// position.
proc clearScreen() {
    write("\x1B[2J\x1B[0m\x1B[H");
}

// Clear the screen, print the credits and wait for a keypress.
proc printCredits() {
    writeln("Bunny\n");
    writeln("Original version in BASIC:");
    writeln("    Creative Computing (Morristown, New Jersey, USA), 1978.\n");
    writeln("This version in Chapel:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    writeln("Press Enter to start the program.");
    IO.readLine();
}

param Width = 53;
var line: [0 ..< Width] string; // buffer
var col: int; // first col to print from

proc clearLine() {
    for c in 0 ..< Width {
        line[c] = " ";
    }
    col = 0;
}

const letter: string = "BUNNY";

param EOL: uint(8) = 127; // end of line identifier

const 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`.
proc draw() {
    const dataLen: int = data.size;
    const letters: int = letter.size;
    var toCol: int; // last col to print to
    var d: int = 0; // data pointer
    clearLine();
    while d < dataLen {
        col = data[d];
        d += 1;
        if col == EOL {
            for c in line {
                write(c);
            }
            writeln();
            clearLine();
        }
        else {
            toCol = data[d];
            d += 1;
            for c in col .. toCol {
                line[c] = letter[c - letters * (c / letters)];
            }
        }
    }
}

proc 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 Chapel:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
//     Written on 2025-04-08.
//     Last modified: 20250731T1954+0200.

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

import IO;
import Random;

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

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

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

const NORMAL_STYLE = 0;

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

proc setCursorPosition(line: int, col: int) {
    writef("\x1B[%i;%iH", line, col);
}

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

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

proc eraseLineToEnd() {
    write("\x1B[K");
}

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

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

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

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

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

// Arena

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

const arenaDomain: domain(2) = {0 ..< ARENA_HEIGHT, 0 ..< ARENA_WIDTH};
var arena: [arenaDomain] string;

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

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

// The end

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

var theEnd: End = End.NOT_YET;

// Machines

const MACHINES = 5;

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

var machineX: [0 ..< MACHINES] int;
var machineY: [0 ..< MACHINES] int;
var operative: [0 ..< MACHINES] bool;

var destroyedMachines: int = 0; // counter

// Human

var humanX: int = 0;
var humanY: int = 0;

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc getString(prompt: string): string {
    setStyle(INPUT_INK);
    defer setStyle(DEFAULT_INK);
    return acceptString(prompt);
}

proc pressEnter(prompt: string) {
    acceptString(prompt);
}

proc isYes(s: string): bool {
    select s.toLower() {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise do return false;
    }
}

proc isNo(s: string): bool {
    select s.toLower() {
        when "n", "no", "nope" do return true;
        otherwise do 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".
//
proc yes(prompt: string): bool {
    var result: bool;
    while true {
        var answer: string = getString(prompt);
        if isYes(answer) {
            result = true;
            break;
        }
        if isNo(answer) {
            result = false;
            break;
        }
    }
    return result;
}

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

const TITLE = "Chase";

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

proc printCredits() {
    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 Chapel:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

proc printInstructions() {
    printTitle();
    setStyle(INSTRUCTIONS_INK);
    writef("\nYou (%s) are in a high voltage maze with %i\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
// =============================================================

proc printArena() {
    setCursorPosition(ARENA_ROW, 1);
    for y in 0 .. ARENA_LAST_Y {
        for x in 0 .. ARENA_LAST_X {
            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`.
//
proc isBorder(y: int, x: int): bool {
    return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}

var rsInt = new Random.randomStream(int);

proc randomIntInInclusiveRange(min: int, max: int): int {
    return rsInt.next(min, min + max - 1);
}

// Place the given string at a random empty position of the arena and return
// the coordinates.
//
proc place(s: string): [] int {
    var y: int = 0;
    var x: int = 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.
//
proc inhabitArena() {
    var coords: [0 .. 1] int;
    for m in 0 ..< MACHINES {
        coords = place(MACHINE);
        machineY[m] = coords[0];
        machineX[m] = coords[1];
        operative[m] = true;
    }
    for 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.
//
proc cleanArena() {
    for y in 0 ..< ARENA_HEIGHT {
        for x in 0 ..< ARENA_WIDTH {
            arena[y, x] = if isBorder(y, x) then FENCE else EMPTY;
        }
    }
}

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

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

proc moveMachine(m: int) {

    var maybe: int = 0;

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

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

    maybe = rsInt.next(0, 2 - 1);
    if machineX[m] > humanX {
        machineX[m] -= maybe;
    } else if machineX[m] < humanX {
        machineX[m] += maybe;
    }

    select true {
        when arena[machineY[m], machineX[m]] == EMPTY {
            arena[machineY[m], machineX[m]] = MACHINE;
        }
        when arena[machineY[m], machineX[m]] == FENCE {
            operative[m] = false;
            destroyedMachines += 1;
            if destroyedMachines == MACHINES {
                theEnd = End.VICTORY;
            }
        }
        when arena[machineY[m], machineX[m]] == HUMAN {
            theEnd = End.KILLED;
        }
    }

}

proc maybeMoveMachine(m: int) {
    if rsInt.next(0, MACHINES_DRAG - 1) == 0 {
        moveMachine(m);
    }
}

proc moveMachines() {
    for m in 0 ..< MACHINES {
        if operative[m] {
            maybeMoveMachine(m);
        }
    }
}

// Read a user command; update `theEnd` accordingly and return the direction
// increments.
//
proc getMove(): [] int {
    var yInc: int = 0;
    var xInc: int = 0;
    write("\n");
    eraseLineToEnd();
    var command: string = getString("Command: ").toLower();
    select command {
        when "q" {
            theEnd = End.QUIT;
        }
        when "sw" {
            yInc = 1;
            xInc = -1;
        }
        when "s" {
            yInc = 1;
        }
        when "se" {
            yInc = 1;
            xInc = 1;
        }
        when "w" {
            xInc = -1;
        }
        when "e" {
            xInc = 1;
        }
        when "nw" {
            yInc = -1;
            xInc = -1;
        }
        when "n" {
            yInc = -1;
        }
        when "ne" {
            yInc = -1;
            xInc = 1;
        }
    }
    return [yInc, xInc];
}

proc play() {

    var yInc: int = 0;
    var xInc: int = 0;

    while true { // game loop

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

        label actionLoop while true {

            printArena();
            var coords: [0 .. 1] int = 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();
                    }
                }
            }

            select theEnd {
                when End.QUIT {
                    writeln("\nSorry to see you quit.");
                    break actionLoop;
                }
                when End.ELECTRIFIED {
                    writeln("\nZap! You touched the fence!");
                    break actionLoop;
                }
                when End.KILLED {
                    writeln("\nYou have been killed by a lucky machine.");
                    break actionLoop;
                }
                when 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
// =============================================================

proc 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 Chapel:
    Copyright (c) 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2025-01-19, 2025-04-04.

Last modified 20250405T2002+0200.
*/

import IO;

param lines = 17;

proc main() {
    for i in 1 ..< lines / 2 + 2 {
        for 1 ..< (lines + 1) / 2 - i + 2 {
            write(" ");
        }
        for 1 ..< i * 2 {
            write("*");
        }
        writeln();
    }
    for i in 1 ..< lines / 2 + 1 {
        for 1 ..< i + 2 {
            write(" ");
        }
        for 1 ..< ((lines + 1) / 2 - i) * 2 {
            write("*");
        }
        writeln();
    }
}

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/

// Improved remake in D:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair

// This improved remake in Chapel:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-02-08.
//
// Last modified: 20260208T2232+0100.

use IO;
use Random;

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

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

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

const NORMAL_STYLE = 0;

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

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

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

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

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

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

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

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

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

var playerBullets: int = 0;
var opponentBullets: int = 0;

var rsInt = new randomStream(int);
var rsReal = new randomStream(real);

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

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

proc getString(prompt: string): string {
    setStyle(INPUT_INK);
    var s: string = 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.

proc getInteger(prompt: string): int {
    var result: int;
    var s: string = acceptString(prompt);
    try {
        result = (s): int;
    }
    catch exc {
        result = 0;
    }
    return result;
}

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

proc isYes(s: string): bool {
    select s.toLower() {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise do return false;
    }
}

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

proc isNo(s: string): bool {
    select s.toLower() {
        when "n", "no", "nope" do return true;
        otherwise do 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".

proc yes(prompt: string): bool {
    var result: bool;
    while true {
        var answer: string = getString(prompt);
        if isYes(answer) {
            result = true;
            break;
        }
        if isNo(answer) {
            result = false;
            break;
        }
    }
    return result;
}


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

proc printTitle() {
    setStyle(TITLE_INK);
    writeln("High Noon");
    setStyle(DEFAULT_INK);
}

proc printCredits() {
    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("Improved remake in D:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
    writeln("This improved remake in Chapel:");
    writeln("    Copyright (c) 2026, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

proc printInstructions() {
    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 %i 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
// =============================================================

proc pluralSuffix(n: int): string {
    select n {
        when 1 { return ""; }
        otherwise { return "s"; }
    }
}

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

proc printCheck() {
    writeln("******************************************************");
    writeln("*                                                    *");
    writeln("*                 BANK OF DODGE CITY                 *");
    writeln("*                  CASHIER'S RECEIT                  *");
    writeln("*                                                    *");
    writef("* CHECK NO. %04i                   AUGUST %iTH, 1889 *\n",
        rsInt.next(0, 1000 - 1),
        10 + rsInt.next(0, 10 - 1));
    writeln("*                                                    *");
    writeln("*                                                    *");
    writeln("*       PAY TO THE BEARER ON DEMAND THE SUM OF       *");
    writeln("*                                                    *");
    writeln("* TWENTY THOUSAND DOLLARS-------------------$20,000  *");
    writeln("*                                                    *");
    writeln("******************************************************");
}

proc getReward() {
    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.");
}

proc moveTheOpponent() {
    var paces: int = 2 + rsInt.next(0, 8 - 1);
    writef("Black Bart moves %i paces.\n", 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.

proc maybeMoveTheOpponent(silent: bool): bool {
    if rsInt.next(0, 2 - 1) == 0 { // 50% chances
        moveTheOpponent();
        return true;
    } else {
        if !silent {
            writeln("Black Bart stands still.");
        }
        return false;
    }
}

proc missedShot(): bool {
    return rsReal.next() * 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`.

proc theOpponentFiresAndKills(playerStrategy: string): bool {
    writeln("Black Bart fires…");
    opponentBullets -= 1;
    if missedShot() {
        writeln("A miss…");
        select opponentBullets {
            when 3 {
                writeln("Whew, were you lucky. That bullet just missed your head.");
            }
            when 2 {
                writeln("But Black Bart got you in the right shin.");
            }
            when 1 {
                writeln("Though Black Bart got you on the left side of your jaw.");
            }
            when 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`.

proc theOpponentKillsOrRuns(playerStrategy: string): bool {
    if distance >= 10 || playerBullets == 0 {
        if maybeMoveTheOpponent(true) {
            return false;
        }
    }
    if opponentBullets > 0 {
        return theOpponentFiresAndKills(playerStrategy);
    } else {
        if playerBullets > 0 {
            if rsInt.next(0, 2 - 1) == 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;
}

proc play() {
    distance = INITIAL_DISTANCE;
    var wateringTroughs: int = 0;
    playerBullets = INITIAL_BULLETS;
    opponentBullets = INITIAL_BULLETS;

    label showdown while true {

        writef("You are now %i paces apart from Black Bart.\n", 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);

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

        select playerStrategy {
            when "a" { // advance
                while true {
                    var paces: int = 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;
                    }
                }
            }
            when "s" { // stand still
                writeln("That move made you a perfect stationary target.");
            }
            when "f" { // fire
                if playerBullets == 0 {
                    writeln("You don't have any bullets left.");
                } else {
                    playerBullets -= 1;
                    if missedShot() {
                        select playerBullets {
                            when 2 {
                                writeln("Grazed Black Bart in the right arm.");
                            }
                            when 1 {
                                writeln("He's hit in the left shoulder, forcing him to use his right");
                                writeln("hand to shoot with.");
                            }
                        }
                        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;
                    }

                }

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

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

            }
            when "t" { // turn tail and run

                // The more bullets of the opponent, the less chances to escape.
                if rsInt.next(0, (opponentBullets + 2 - 1)) == 0 {
                    writeln("Man, you ran so fast even dogs couldn't catch you.");
                } else {
                    select opponentBullets {
                        when 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.");
                        }
                        when 1 {
                            writeln("Black Bart fires his last bullet…");
                            writeln("He got you right in the back. That's what you deserve, for running.");
                        }
                        when 2 {
                            writeln("Black Bart fires and got you twice: in your back");
                            writeln("and your ass. Now you can't even rest in peace.");
                        }
                        when 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.");
                        }
                        when 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;

            } otherwise {

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

proc 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 Chapel:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-04-05
//
// Last modified: 20250405T2337+0200.

import IO;
import Math;

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc acceptValidReal(prompt: string): real {
    var result: real;
    while true {
        var s: string = acceptString(prompt);
        try {
            result = s: real;
            break;
        }
        catch exc {
            writeln("Real number expected.");
        }
    }
    return result;
}

proc main() {

    var n: real = acceptValidReal("Enter a number: ");

    writef("ABS(%r) -> Math.abs(%r) -> %r\n", n, n, Math.abs(n));
    writef("ATN(%r) -> Math.atan(%r) -> %r\n", n, n, Math.atan(n));
    writef("COS(%r) -> Math.cos(%r) -> %r\n", n, n, Math.cos(n));
    writef("EXP(%r) -> Math.exp(%r) -> %r\n", n, n, Math.exp(n));
    writef("INT(%r) -> %r: int -> %i\n", n, n, n: int);
    writef("LOG(%r) -> Math.log(%r) -> %r\n", n, n, Math.log(n));
    writef("SGN(%r) -> Math.sgn(%r)) -> %i\n", n, n, Math.sgn(n));
    writef("SQR(%r) -> Math.sqrt(%r) -> %r\n", n, n, Math.sqrt(n));
    writef("TAN(%r) -> Math.tan(%r) -> %r\n", n, n, Math.tan(n));

}

Mugwump

// Mugwump

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

// This version in Chapel:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-04-07.
//
// Last modified: 20250731T1954+0200.

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

import IO;
import Math;
import Random;

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

const NORMAL_STYLE = 0;

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

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

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

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

const GRID_SIZE = 10;
const TURNS = 10;
const MUGWUMPS = 4;

record Mugwump {
    var x: int;
    var y: int;
    var hidden: bool;
}

var mugwump: [0 ..< MUGWUMPS] Mugwump;
var found: int = 0; // counter

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc acceptValidInteger(prompt: string): int {
    var result: int;
    while true {
        var s: string = acceptString(prompt);
        try {
            result = (s): int;
            break;
        }
        catch exc {
            writeln("Integer expected.");
        }
    }
    return result;
}

proc isYes(s: string): bool {
    select s.toLower() {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise do return false;
    }
}

proc isNo(s: string): bool {
    select s.toLower() {
        when "n", "no", "nope" do return true;
        otherwise do 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".
//
proc yes(prompt: string): bool {
    var result: bool;
    while true {
        var answer: string = acceptString(prompt);
        if isYes(answer) {
            result = true;
            break;
        }
        if isNo(answer) {
            result = false;
            break;
        }
    }
    return result;
}

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

// Clear the screen, print the credits and ask the user to press enter.
//
proc printCredits() {
    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 Chapel:");
    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.
//
proc printInstructions() {
    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");
    writef("You get %i tries.  After each try, you will see\n", TURNS);
    writeln("how far you are from each mugwump.\n");
    acceptString("Press Enter to start. ");
}

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

proc hideMugwumps() {
    var rsInt = new Random.randomStream(int);
    for m in 0 ..< MUGWUMPS {
        mugwump[m].x = rsInt.next(0, GRID_SIZE - 1);
        mugwump[m].y = rsInt.next(0, GRID_SIZE - 1);
        mugwump[m].hidden = true;
    }
    found = 0; // counter
}

// Print the given prompt, wait until the user enters a valid coord and return
// it.
//
proc getCoord(prompt: string): int {
    var coord: int = 0;
    while true {
        coord = acceptValidInteger(prompt);
        if coord < 0 || coord >= GRID_SIZE {
            writef("Invalid value %i: not in range [0, %i].\n", coord, GRID_SIZE - 1);
        } else {
            break;
        }
    }
    return coord;
}

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

// Return the distance between the given mugwump and the given coords.
//
proc distance(m: int, x: int, y: int): int {
    return sqrt(
        (mugwump[m].x - x) ** 2.0 +
        (mugwump[m].y - y) ** 2.0): int;
}

// Return a plural suffix (default: "s") if the given number is greater than 1
// otherwise return a singular suffix (default: an empty string).
//
proc plural(n: int, pluralSuffix: string = "s", singularSuffix: string = ""): string {
    return if n > 1 then pluralSuffix else singularSuffix;
}

// Run the game.
//
proc play() {

    var x: int = 0;
    var y: int = 0;

    while true { // game

        clearScreen();
        hideMugwumps();

        var turn: int = 1;

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

            for m in 0 ..< MUGWUMPS {
                if isHere(m, x, y) {
                    mugwump[m].hidden = false;
                    found += 1;
                    writef("You have found mugwump %i!\n", m);
                    if found == MUGWUMPS {
                        break turnsLoop;
                    }
                }
            }

            for m in 0 ..< MUGWUMPS {
                if mugwump[m].hidden {
                    writef("You are %i units from mugwump %i.\n", distance(m, x, y) , m);
                }
            }
            writeln("");
            turn += 1;
        } // turns

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

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

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

proc initData() {
    // Build the `mugwump` array.
    for i in 0 ..< MUGWUMPS {
        mugwump[i] = new Mugwump();
    }
}

proc main() {
    printCredits();
    printInstructions();
    initData();
    play();
}

Name

// Name

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

// This version in Chapel:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2002+0200.

import IO;

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc acceptValidInteger(prompt: string): int {
    var result: int;
    while true {
        var s: string = acceptString(prompt);
        try {
            result = s: int;
            break;
        }
        catch exc {
            writeln("Integer expected.");
        }
    }
    return result;
}

proc main() {
    var name: string = acceptString("What is your name? ");
    var number: int = acceptValidInteger("Enter a number: ");
    for 1 .. 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 Chapel:
//   Copyright (c) 2025, Marcos Cruz (programandala.net)
//   SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2226+0200.

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

import IO;
import Random;
import Time;

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

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

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

const NORMAL_STYLE = 0;

proc moveCursorHome() {
    writef("\x1B[H");
}

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

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

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

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

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

proc printTitle() {
    setStyle(TITLE_INK);
    writeln("Poetry");
    setStyle(DEFAULT_INK);
}

proc printCredits() {
    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 Chapel:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}


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

proc play() {

    var MAX_PHRASES_AND_VERSES: int = 20;

    // counters:
    var action: int = 0;
    var phrase: int = 0;
    var phrasesAndVerses: int = 0;
    var verseChunks: int = 0;
    var rsInt = new Random.randomStream(int);
    var rsReal = new Random.randomStream(real);

    label verse while true {

        var manageTheVerseContinuation: bool = true;
        var maybeAddComma: bool = true;

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

        if manageTheVerseContinuation {

            Time.sleep(0.250);

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

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

        }

        action += 1;
        phrase = rsInt.next(0, 4);
        phrasesAndVerses += 1;

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

    } // verse loop

}

proc 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 Chapel:
//    Copyright (c) 2025, Marcos Cruz (programandala.net)
//    SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2249+0200.

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

import IO;
import Random;

const normalStyle = 0;

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(normalStyle);
}

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

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

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

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

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

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

proc printInstructions() {
    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
// =============================================================================

proc play() {

    var rsInt = new Random.randomStream(int);

    while true { // game loop

        printInstructions();

        var times: int = 0;

        label playLoop while true {

            var command: string = acceptString("> ");

            select command {
                when "f" { // fire
                    if rsInt.next(0, 99) > 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.");
                        }
                    }
                }
                when "g" { // give up
                    writeln("Chicken!");
                    break playLoop;
                }
                when "q" { // quit
                    return;
                }
            }

        } // play loop

        pressEnterToStart();

    } // game loop

}

proc bye() {
    writeln("Bye!");
}

proc 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 Chapel:
//  Copyright (c) 2025, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-04-09.
//
// Last modified: 20250731T1954+0200.

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

import IO;
import Random;
import Time;

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

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

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

const NORMAL_STYLE = 0;

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

proc setCursorPosition(y: int, x: int) {
    writef("\x1B[%i;%iH", y, x);
}

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

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

proc eraseLineToEnd() {
    write("\x1B[K");
}

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

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

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

const TITLE = "Seance";

const MAX_SCORE = 50;

const MAX_MESSAGE_LENGTH = 6;
const MIN_MESSAGE_LENGTH = 3;

const BASE_CHARACTER = '@'.toCodepoint();
const PLANCHETTE = '*'.toCodepoint();
const SPACE = ' '.toCodepoint();

const FIRST_LETTER_NUMBER = 1;
const LAST_LETTER_NUMBER = 26;

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

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

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

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

const MESSAGES_Y = INPUT_Y;

const MISTAKE_EFFECT_PAUSE = 3.0; // seconds

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc pressEnter(prompt: string) {
    acceptString(prompt);
}

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

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

proc printCredits() {
    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 Chapel:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

proc printInstructions() {
    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
// =============================================================================

var rsInt = new Random.randomStream(int);

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

// Print the given text on the given row, centered on the board.
//
proc printCentered(text: string, y: int) {
    setCursorPosition(y, boardCenteredX(text));
    writef("%s\n", text);
}

// Print the title on the given row, centered on the board.
//
proc printCenteredTitle(y: int) {
    setStyle(TITLE_INK);
    printCentered(TITLE, y);
    setStyle(DEFAULT_INK);
}

// Print the given letter at the given board coordinates.
//
proc printCharacter(y: int, x: int, charCode: int) {
    setCursorPosition(y + BOARD_Y, x + BOARD_X);
    write(codepointToString(charCode: int(32)));
}

proc printBoard() {
    setStyle(BOARD_INK);
    for i in 1 .. BOARD_WIDTH {
        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
    }
    for i in 1 .. BOARD_HEIGHT {
        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);
}

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

// Print the given mistake effect, do a pause and erase it.
//
proc printMistakeEffect(effect: string) {
    var x: int = boardCenteredX(effect);
    hideCursor();
    setCursorPosition(MESSAGES_Y, x);
    setStyle(MISTAKE_EFFECT_INK);
    writeln(effect);
    setStyle(DEFAULT_INK);
    Time.sleep(MISTAKE_EFFECT_PAUSE);
    eraseLineFrom(MESSAGES_Y, x);
    showCursor();
}

// Return a new message of the given length, after marking its letters on the
// board.
//
proc message(length: int): string {
    const LETTER_PAUSE: real = 1.0; // seconds
    var y: int = 0;
    var x: int = 0;
    var letters: string;
    hideCursor();
    for i in 0 .. length {
        var letterNumber: int = rsInt.next(
            FIRST_LETTER_NUMBER,
            LAST_LETTER_NUMBER
        );
        letters += codepointToString((BASE_CHARACTER + letterNumber): int(32));
        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();
        Time.sleep(LETTER_PAUSE);
        setStyle(DEFAULT_INK);
        printCharacter(y, x, SPACE);
    }
    showCursor();
    return letters;

}

proc acceptMessage(): string {
    setStyle(INPUT_INK);
    setCursorPosition(INPUT_Y, INPUT_X);
    var result: string = acceptString("? ").toUpper();
    setStyle(DEFAULT_INK);
    eraseLineFrom(INPUT_Y, INPUT_X);
    return result;
}

proc play() {

    var score: int = 0;
    var mistakes: int = 0;

    printCenteredTitle(1);
    printBoard();

    while true {
        var messageLength: int = rsInt.next(
            MIN_MESSAGE_LENGTH,
            MAX_MESSAGE_LENGTH
        );
        var messageReceived: string = message(messageLength);
        var messageUnderstood: string = acceptMessage();
        if messageReceived != messageUnderstood {
            mistakes += 1;
            select mistakes {
                when 1 {
                    printMistakeEffect("The table begins to shake!");
                }
                when 2 {
                    printMistakeEffect("The light bulb shatters!");
                }
                when 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
// =============================================================================

proc main() {

    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 Chapel:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-03.
Updated to Chapel 2.3.0 on 2025-01-19.

Last modified 20250405T2002+0200.
*/

import IO;
import Math;

var word: [0 .. 1] string;

// `clear` clears the screen and moves the cursor to its home position,
// by printing ANSI codes.
proc clear() {
    write("\x1B[2J\x1B[H");
}

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

proc getWords() {
    const order = ("first", "second");
    clear();
    for w in 0 .. 1 {
        write("Enter the ", order[w], " word ");
        IO.readLine(word[w]);
        // N.B. The input includes the carriage return character.
    }
}

proc draw() {
    clear();
    var even = false;
    var angle = 0.0;
    while angle <= 40.0 {
        // N.B. `write` is used because the word has a trailing carriage return
        // character.
        write((26.0 + 25.0 * Math.sin(angle)):int * " ", word[even:int]);
        even = !even;
        angle += 0.25;
    }
}

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 Chapel:
//  Copyright (c) 2025, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-04-07.
//
// Last modified: 20250407T1916+0200.

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

import IO;
import Random;
import Time;

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

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

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

const NORMAL_STYLE = 0;

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

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

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

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

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

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

param REELS = 3;
param IMAGES = 6;

var images: range = 0 ..< IMAGES;

const image: [images] string = [
    " BAR  ",
    " BELL ",
    "ORANGE",
    "LEMON ",
    " PLUM ",
    "CHERRY"
];

const BAR = 0; // position of "BAR" in `image`.

const color: [0 .. 5] int = [
    FOREGROUND + WHITE,
    FOREGROUND + CYAN,
    FOREGROUND + YELLOW,
    FOREGROUND + BRIGHT + YELLOW,
    FOREGROUND + BRIGHT + WHITE,
    FOREGROUND + BRIGHT + RED
];

param MAX_BET = 100;
param MIN_BET = 1;

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

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

// Print the given prompt, accept a string from the user. If the typed string
// is a valid integer return it; otherwise return 0.
//
proc acceptInteger(prompt: string): int {
    var result: int;
    var s: string = acceptString(prompt);
    try {
        result = (s): int;
    }
    catch exc {
        result = 0;
    }
    return result;
}

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

proc printCredits() {
    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 Chapel:");
    writeln("    Copyright (c) 2025, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair\n");
    acceptString("Press Enter for instructions. ");
}

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

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

proc won(prize: int, bet: int): int {
    select prize {
        when 2 do writeln("DOUBLE!");
        when 5 do writeln("*DOUBLE BAR*");
        when 10 do writeln("**TOP DOLLAR**");
        when 100 do writeln("***JACKPOT***");
    }
    writeln("You won!");
    return (prize + 1) * bet;
}

proc showStandings(usd: int) {
    writef("Your standings are %i USD.\n", usd);
}

proc printReels(reel: [] int) {
    moveCursorHome();
    for r in reel {
        setStyle(color[r]);
        writef("[%s] ", image[r]);
    }
    setStyle(NORMAL_STYLE);
    writeln("");
}

var rsInt = new Random.randomStream(int);

proc initReels(ref reel: [] int) {
    var images: int = (image.size): int;
    for i in 0 ..< reel.size {
        reel[i] = rsInt.next(0, images - 1);
    }
}

proc spinReels(ref reel: [] int) {
    const SECONDS = 2;
    hideCursor();
    var startSecond: int = Time.timeSinceEpoch().seconds;
    var currentSecond: int;
    do {
        initReels(reel);
        printReels(reel);
        currentSecond = Time.timeSinceEpoch().seconds;
    } while currentSecond - startSecond <= SECONDS;
    showCursor();
}

proc play() {

    var standings: int = 0;
    var bet: int = 0;
    var reel: [0 ..< REELS] int;

    initReels(reel);

    label playLoop while true {

        label betLoop while true {
            clearScreen();
            printReels(reel);
            bet = acceptInteger("Your bet (or 0 to quit): ");
            if bet > MAX_BET {
                writef("House limits are %i USD.\n", MAX_BET);
                acceptString("Press Enter to try again. ");
            } else if bet < MIN_BET {
                var confirmation: string =
                    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);

        var bars: int = reel.count(BAR);
        var equals: int;
        for image in images {
            equals = max(equals, reel.count(image));
        }

        select equals {
            when 3 {
                if bars == 3 {
                    standings += won(100, bet);
                } else {
                    standings += won(10, bet);
                }
            }
            when 2 {
                if bars == 2 {
                    standings += won(5, bet);
                } else {
                    standings += won(2, bet);
                }
            }
            otherwise {
                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
// =============================================================================

proc 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 Chapel:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2255+0200.

import IO;

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc acceptValidInteger(prompt: string): int {
    var result: int;
    while true {
        var s: string = acceptString(prompt);
        try {
            result = s: int;
            break;
        }
        catch exc {
            writeln("Integer expected.");
        }
    }
    return result;
}

proc isYes(s: string): bool {
    select(s.toLower()) {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise do return false;
    }
}

proc main() {
    var name: string = acceptString("What is your name? ");
    writef("Hello, %s.\n", name);
    do {
        var number: int = acceptValidInteger("How many stars do you want? ");
        writeln("*" * 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 Chapel:
//     Copyright (c) 2025, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2025-04-06.
//
// Last modified: 20250407T1346+0200.

import IO;

proc acceptString(prompt: string): string {
    write(prompt);
    IO.stdout.flush();
    return IO.readLine().strip();
}

proc acceptValidInteger(prompt: string): int {
    var result: int;
    while true {
        var s: string = acceptString(prompt);
        try {
            result = (s): int;
            break;
        }
        catch exc {
            writeln("Integer expected.");
        }
    }
    return result;
}

proc main() {

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

    writef(
        'ASC("%s") --> "%s"[0].toCodepoint() --> %i\n',
        s, s, s[0].toCodepoint()
    );
    writef(
        'CHR$(%i) --> codepointToString(%i: int(32)) --> "%s"\n',
        n, n, codepointToString(n: int(32))
    );
    writef(
        'LEFT$("%s", %i) --> "%s"[0 .. min(%i - 1, "%s".size - 1)] --> "%s"\n',
        s, n, s, n, s, s[0 .. min(n - 1, s.size - 1)]
    );
    writef(
        'MID$("%s", %i) --> "%s"[%i - 1 ..] --> "%s"\n',
        s, n, s, n, s[n - 1 ..]
    );
    writef(
        'MID$("%s", %i, 3) --> "%s"[%i - 1 .. min(%i - 1 + 3 - 1, "%s".size - 1)] --> "%s"\n',
        s, n, s, n, n, s, s[n - 1 .. min(n - 1 + 3 - 1, s.size - 1)]
    );
    writef(
        'RIGHT$("%s", %i) --> "%s"[max(0, "%s".size - %i) .. "%s".size - 1] --> "%s"\n',
        s, n, s, s, n, s, s[max(0, s.size - n) .. s.size - 1]
    );
    writef(
        'LEN("%s") --> "%s".size --> %i\n',
        s, s, s.size
    );
    var sToReal: real;
    try {
        sToReal = s: real;
        writef(
            'VAL("%s") --> "%s": real --> %r\n',
            s, s, sToReal
        );
    }
    catch ext {
        writef(
            'VAL("%s") --> "%s": real /* error catched by `catch` */ --> %r\n',
            s, s, sToReal
        );
    }
    writef(
        'STR$(%i) --> %i: string --> "%s"\n',
        n,n, n: string
    );
    writef(
        'SPC(%i) --> " " * %i --> "%s"',
        n, n, " " * 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 Chapel:
//  Copyright (c) 2025, 2026, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
// Written on 2025-04-09 and in 2026-02-07/08.
//
// Last modified: 20260208T2107+0100.

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

import Random;
use IO;
use IO.FormattedIO; // format

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

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

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

const NORMAL_STYLE = 0;

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

proc setCursorPosition(line: int, column: int) {
    writef("\x1B[%i;%iH", line, column);
}

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

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

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

proc eraseLineToEnd() {
    write("\x1B[K");
}

proc eraseScreenToEnd() {
    write("\x1B[J");
}

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

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

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

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

const BLANK = "*";

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

const CELLS = GRID_WIDTH * GRID_HEIGHT;
const gridRange = 0 ..< CELLS;

var pristineGrid: [gridRange] string;

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

const FIRST_PLAYER = 0;
const MAX_PLAYERS = 4;
const playerRange = FIRST_PLAYER ..< MAX_PLAYERS;

var grid: [playerRange, gridRange] string;

var isPlaying: [0 ..< MAX_PLAYERS] bool;

var players: int = 0;

const QUIT_COMMAND = "X";

// used with coord arrays instead of 0 and 1, for clarity
const Y = 0;
const X = 1;

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

proc acceptString(prompt: string): string {
    write(prompt);
    stdout.flush();
    return readLine().strip();
}

// Print the given prompt, accept a string from the user. If the typed string
// is a valid integer return it; otherwise return 0.
//
proc getInteger(prompt: string = ""): int {
    var result: int;
    var s: string = acceptString(prompt);
    try {
        result = (s): int;
    }
    catch exc {
        result = 0;
    }
    return result;
}

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

proc pressEnter(prompt: string) {
    acceptString(prompt);
}

proc isYes(s: string): bool {
    select s.toLower() {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise do return false;
    }
}

proc isNo(s: string): bool {
    select s.toLower() {
        when "n", "no", "nope" do return true;
        otherwise do 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".
//
proc yes(prompt: string): bool {
    var result: bool;
    while true {
        var answer: string = getString(prompt);
        if isYes(answer) {
            result = true;
            break;
        }
        if isNo(answer) {
            result = false;
            break;
        }
    }
    return result;
}

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

proc printTitle() {
    setStyle(TITLE_INK);
    writeln("Xchange");
    setStyle(DEFAULT_INK);
}

proc printCredits() {
    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 Chapel:");
    writeln("    Copyright (c) 2025, 2026, Marcos Cruz (programandala.net)");
    writeln("    SPDX-License-Identifier: Fair");
}

proc printInstructions() {
    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");
    writef("    A H %s\n", 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");
    writef("    G H %s\n\n", BLANK);
    setStyle(INSTRUCTIONS_INK);
    writef("You may exchange any one letter with the '%s', but only one that's adjacent:\n", BLANK);
    writeln("above, below, left, or right.  Not all puzzles are possible, and you may enter");
    writef("'%s' to give up.\n\n", QUIT_COMMAND);
    writeln("Here we go...");
    setStyle(DEFAULT_INK);
}

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

// Print the given player's grid title.
//
proc printGridTitle(player: int) {
    setCursorPosition(GRIDS_ROW, GRIDS_COLUMN + (player * GRIDS_GAP));
    writef("Player %i", player + 1);
}

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

// Return the cursor position of the given player's grid prompt.
//
proc gridPromptPositionOf(player: int): (int, int) {
    var coord: (int, int) = cellPosition(player, CELLS);
    var row: int = coord[0];
    var column: int = coord[1];
    return (row + 1, column);
}

// Print the given player's grid, in the given or default color.
//
proc printGrid(player: int, color: int = BOARD_INK) {
    if players > 1 {
        printGridTitle(player);
    }
    setStyle(color);
    for cell in gridRange {
        setCursorCoord(cellPosition(player, cell));
        write(grid[player, cell]);
    }
    setStyle(DEFAULT_INK);
}

// Print the current players' grids.
//
proc printGrids() {
    for player in 0 ..< players {
        if isPlaying[player] {
            printGrid(player);
        }
    }
    writeln("");
    eraseScreenToEnd();
}

// Init the grids.
//
proc initGrids() {
    Random.shuffle(pristineGrid);
    for player in 0 ..< players {
        for cell in gridRange {
            grid[player, cell] = pristineGrid[cell];
        }
    }
}

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

// Return a message prefix for the given player.
//
proc playerPrefix(player: int): string {
    return if players > 1 then "Player %i: ".format(player + 1) else "";
}

// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.
//
proc messagePosition(player: int, rowInc: int = 0 ): (int, int) {
    var promptCoord: (int, int) = gridPromptPositionOf(player);
    return (promptCoord[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.
//
proc printMessage(message: string, player: int, rowInc: int = 0) {
    setCursorCoord(messagePosition(player, rowInc));
    writef("%s%s", playerPrefix(player), message);
    eraseLineToEnd();
    writeln("");
}

// Erase the last message about the given player.
//
proc eraseMessage(player: int) {
    setCursorCoord(messagePosition(player));
    eraseLineToEnd();
}

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

// Return a message with the players range.
//
proc playersRangeMessage(): string {
    if MAX_PLAYERS == 2 {
        return "1 or 2";
    } else {
        return "from 1 to %i".format(MAX_PLAYERS);
    }
}

// Return the number of players, asking the user if needed.
//
proc numberOfPlayers(): int {
    var players: int = 0;
    printTitle();
    writeln("");
    if MAX_PLAYERS == 1 {
        players = 1;
    } else {
        while players < 1 || players > MAX_PLAYERS {
            var prompt: string = "Number of players (%s): ".format(playersRangeMessage());
            players = getInteger(prompt);
        }
    }
    return players;
}

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

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

// Are the given cells adjacent?
//
proc areCellsAdjacent(cell1: int, cell2: int): bool {
    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.
//
proc positionToCell(player: int, charCell: int): int {
    for cell in gridRange {
        if grid[player, cell] == BLANK {
            if areCellsAdjacent(charCell, cell) {
                return cell;
            } else {
                break;
            }
        }
    }
    printMessage("Illegal move \"%s\".".format(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.
//
proc commandToPosition(player: int, command: string): int {
    if command != BLANK {
        for position in gridRange {
            if command == grid[player, position] {
                return position;
            }
        }
    }
    printMessage("Invalid character \"%s\".".format(command), player);
    return -1;
}

// Forget the given player, who quitted.
//
proc forgetPlayer(player: int) {
    isPlaying[player] = false;
    printGrid(player, DEFAULT_INK);
}

// Play the turn of the given player.
//
proc playTurn(player: int) {
    var blankPosition: int = 0;
    var characterPosition: int = 0;

    if isPlaying[player] {
        while true {
            while true {
                var coord: (int, int) = gridPromptPositionOf(player);
                setCursorCoord(coord);
                eraseLineToEnd();
                setCursorCoord(coord);
                var command: string = getString("Move: ").toUpper();
                if command == QUIT_COMMAND {
                    forgetPlayer(player);
                    return;
                }
                var position: int = commandToPosition(player, command);
                if position >= 0 {
                    characterPosition = position;
                    break;
                }
            }
            var position: int = 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.
//
proc playTurns() {
    for player in 0 ..< players {
        playTurn(player);
    }
}

// Is someone playing?
//
proc isSomeonePlaying(): bool {
    for player in 0 ..< players {
        if isPlaying[player] {
            return true;
        }
    }
    return false;
}

proc hasAnEmptyGrid(player: int): bool {
    for i in gridRange {
        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`.
//
proc hasSomeoneWon(): bool {
    var winners: int = 0;
    for player in 0 ..< players {
        if isPlaying[player] {
            if hasAnEmptyGrid(player) {
                winners += 1;
                if winners > 0 {
                    printMessage(
                        "You're the winner%s!".format(if winners > 1 then ", too" else ""),
                        player,
                        winners - 1);
                }
            }
        }
    }
    return winners > 0;
}

// Init the game.
//
proc initGame() {
    clearScreen();
    players = numberOfPlayers();
    for player in 0 ..< players {
        isPlaying[player] = true;
    }
    clearScreen();
    printTitle();
    initGrids();
    printGrids();
}

// Play the game.
//
proc play() {
    initGame();
    while isSomeonePlaying() {
        playTurns();
        printGrids();
        if hasSomeoneWon() {
            break;
        }
    }
}

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

proc initOnce() {

    // Init the pristine grid.
    const FIRST_CHAR_CODE = 'A'.toCodepoint();
    for cell in gridRange {
        pristineGrid[cell] = codepointToString((FIRST_CHAR_CODE + cell): int(32));
    }
    pristineGrid[CELLS - 1] = BLANK;
}

// Return `true` if the player does not want to play another game; otherwise
// return `false`.
//
proc enough(): bool {
    setCursorCoord(gridPromptPositionOf(FIRST_PLAYER));
    return !yes("Another game? ");
}

proc main() {
    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 Chapel:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-02-09.
//
// Last modified: 20260209T1321+0100.

use IO;
use Random;

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

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

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

const NORMAL_STYLE = 0;

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

proc setStyle(style: int) {
    writef("\x1B[%im", style);
}

proc color(n: int) {
    setStyle(n + FOREGROUND);
}

proc resetAttributes() {
    setStyle(NORMAL_STYLE);
}

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

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

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

proc acceptString(prompt: string): string {
    color(GREEN);
    defer {
        color(WHITE);
    }
    write(prompt);
    IO.stdout.flush();
    return readLine().strip();
}

proc acceptValidInteger(prompt: string): int {
    var result: int;
    while true {
        var s: string = acceptString(prompt);
        try {
            result = (s): int;
            break;
        }
        catch exc {
            writeln("Integer expected.");
        }
    }
    return result;
}

proc isYes(answer: string): bool {
    select answer.toLower() {
        when "ok", "y", "yeah", "yes" do return true;
        otherwise                     do return false;
    }
}

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

enum Player {
    computer,
    human,
}

const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

proc printRules() {
    clearScreen();
    color(RED);
    writeln("Z-End\n");
    var answer: string = 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();
}

var rsInt = new randomStream(int);
var firstLetter: int;

proc computerPick(): int {
    var pick: int;
    var remainingLetters: int = (alphabet.size - firstLetter): int;
    if remainingLetters < 6 {
        pick = remainingLetters - 1;
    } else if remainingLetters > 10 {
        pick = rsInt.next(1, 5);
    } else {
        pick = 1;
    }
    writeln("My pick is ", pick, ".");
    return pick;
}

proc humanPick(): int {
    var pick: int;
    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;
}

proc gameOver(): bool {
    return firstLetter == alphabet.size - 1;
}

proc printAlphabet(omittedLetters: int = 0) {
    firstLetter += omittedLetters;
    color(MAGENTA);
    writeln(alphabet[firstLetter ..]);
    writeln();
    color(WHITE);
}

proc printResult(player: Player) {
    write("Z-End -- ");
    select player {
        when Player.computer do writeln("Ha ha!");
        when Player.human    do writeln("Oops!");
    }
}

proc pick(player: Player): int {
    select player {
        when Player.computer do return computerPick();
        otherwise            do return humanPick();
    }
}

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

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

proc again(): bool {
    writeln();
    var answer: string = acceptString("Do it again (Y/N) ");
    return isYes(answer);
}

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

Rilataj paĝoj

Basics off
Metaprojekto pri la projektoj «Basics of…».
Basics of 8th
Konverto de malnovaj BASIC-programoj al 8th por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Ada
Konverto de malnovaj BASIC-programoj al Ada por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Arturo
Konverto de malnovaj BASIC-programoj al Arturo por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of C#
Konverto de malnovaj BASIC-programoj al C# por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of C3
Konverto de malnovaj BASIC-programoj al C3 por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Clojure
Konverto de malnovaj BASIC-programoj al Clojure por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Crystal
Konverto de malnovaj BASIC-programoj al Crystal por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of D
Konverto de malnovaj BASIC-programoj al D por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Elixir
Konverto de malnovaj BASIC-programoj al Elixir por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of F#
Konverto de malnovaj BASIC-programoj al F# por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Factor
Konverto de malnovaj BASIC-programoj al Factor por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of FreeBASIC
Konverto de malnovaj BASIC-programoj al FreeBASIC por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Gleam
Konverto de malnovaj BASIC-programoj al Gleam por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Go
Konverto de malnovaj BASIC-programoj al Go por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Hare
Konverto de malnovaj BASIC-programoj al Hare por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Haxe
Konverto de malnovaj BASIC-programoj al Haxe por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Icon
Konverto de malnovaj BASIC-programoj al Icon por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Io
Konverto de malnovaj BASIC-programoj al Io por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Janet
Konverto de malnovaj BASIC-programoj al Janet por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Julia
Konverto de malnovaj BASIC-programoj al Julia por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Kotlin
Konverto de malnovaj BASIC-programoj al Kotlin por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Lobster
Konverto de malnovaj BASIC-programoj al Lobster por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Lua
Konverto de malnovaj BASIC-programoj al Lua por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nature
Konverto de malnovaj BASIC-programoj al Nature por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Neat
Konverto de malnovaj BASIC-programoj al Neat por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Neko
Konverto de malnovaj BASIC-programoj al Neko por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nelua
Konverto de malnovaj BASIC-programoj al Nelua por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nim
Konverto de malnovaj BASIC-programoj al Nim por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nit
Konverto de malnovaj BASIC-programoj al Nit por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Oberon-07
Konverto de malnovaj BASIC-programoj al Oberon-07 por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of OCaml
Konverto de malnovaj BASIC-programoj al OCaml por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Odin
Konverto de malnovaj BASIC-programoj al Odin por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Pike
Konverto de malnovaj BASIC-programoj al Pike por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Pony
Konverto de malnovaj BASIC-programoj al Pony por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Python
Konverto de malnovaj BASIC-programoj al Python por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Racket
Konverto de malnovaj BASIC-programoj al Racket por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Raku
Konverto de malnovaj BASIC-programoj al Raku por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Retro
Konverto de malnovaj BASIC-programoj al Retro por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Rexx
Konverto de malnovaj BASIC-programoj al Rexx por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Ring
Konverto de malnovaj BASIC-programoj al Ring por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Rust
Konverto de malnovaj BASIC-programoj al Rust por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Scala
Konverto de malnovaj BASIC-programoj al Scala por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Scheme
Konverto de malnovaj BASIC-programoj al Scheme por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Styx
Konverto de malnovaj BASIC-programoj al Styx por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Swift
Konverto de malnovaj BASIC-programoj al Swift por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of V
Konverto de malnovaj BASIC-programoj al V por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Vala
Konverto de malnovaj BASIC-programoj al Vala por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Zig
Konverto de malnovaj BASIC-programoj al Zig por lerni la fundamentojn de ĉi-tiu lingvo.

Eksteraj rilataj ligiloj