Basics of Chapel

Description of the page content

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

Tags:

3D Plot

/*
3D Plot

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

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

Related pages

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

External related links