Basics of Hare

Description of the page content

Conversion of old BASIC programs to Hare 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 Hare:
//      Copyright (c) 2024, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2024-12-14/15, 2025-03-07.
//
// Last modified: 20260213T1645+0100.

use bufio;
use fmt;
use math;
use os;
use strings;

def SPACE = ' ';
def DOT = '*';
def WIDTH = 56;

fn clear_screen() void = {
        fmt::print("\x1B[0;0H\x1B[2J")!;
};

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;

};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

fn print_credits() void = {
        fmt::println("3D Plot\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2024, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter("Press Enter to start.");
};

fn a(z: f64) f64 = {
        return 30.0 * math::expf64(-z * z / 100.0);
};

fn draw() void = {
        let l = 0;
        let z = 0;
        let y1 = 0;

        let line: [WIDTH]rune = [SPACE...];

        for (let x = -30.0; x <= 30.0; x += 1.5) {

                line = [SPACE...];

                l = 0;
                y1 = 5 * (math::sqrtf64(900.0 - x * x) / 5.0): int;

                for (let y = y1; y >= -y1; y += -5) {
                        z = (25.0
                                + a(math::sqrtf64(x * x + (y * y): f64))
                                - 0.7 * (y: f64)): int;
                        if (z > l) {
                                l = z;
                                line[z] = DOT;
                        };
                };

                for (let pos = 0; pos < WIDTH; pos += 1) {
                        fmt::print(line[pos])!;
                };
                fmt::println()!;

        };
};

export fn main() void = {
        clear_screen();
        print_credits();
        clear_screen();
        draw();
};

Bagels

// Bagels
//
// Original version in BASIC:
//      D. Resek, P. Rowe, 1978.
//      Creative Computing (Morristown, New Jersey, USA), 1978.
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-14/15.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strings;
use time;

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

// Clear the screen, display the credits and wait for a keypress.
//
fn print_credits() void = {
        clear_screen();
        fmt::println("Bagels")!;
        fmt::println("Number guessing game\n")!;
        fmt::println("Original source unknown but suspected to be:")!;
        fmt::println("    Lawrence Hall of Science, U.C. Berkely.\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    D. Resek, P. Rowe, 1978.")!;
        fmt::println("    Creative computing (Morristown, New Jersey, USA), 1978.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter("Press Enter to read the instructions. ");
};

// Clear the screen, print the instructions and wait for a keypress.
//
fn print_instructions() void = {
        clear_screen();
        fmt::println("Bagels")!;
        fmt::println("Number guessing game\n")!;
        fmt::println("I am thinking of a three-digit number that has no two digits the same.")!;
        fmt::println("Try to guess it and I will give you clues as follows:\n")!;
        fmt::println("   PICO   - one digit correct but in the wrong position")!;
        fmt::println("   FERMI  - one digit correct and in the right position")!;
        fmt::println("   BAGELS - no digits correct\n")!;
        press_enter("Press Enter to start. ");
};

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

fn repeat(s: str, n: int) str = {
        let result = "";
        for (let i: int = 0; i < n; i += 1) {
                result = strings::concat(result, s)!;
        };
        return result;
};

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "ok", "y", "yeah", "yes" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "n", "no", "nope" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt: str) bool = {
        for (true) {
                let answer = accept_string(prompt);
                defer free(answer);
                if (is_yes(answer)) {
                        return true;
                };
                if (is_no(answer)) {
                        return false;
                };
        };
};

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

// Print the given prompt and update the array whose address is given with a
// three-digit number from the user.
//
fn get_input(prompt: str, user_digit: *[DIGITS]int) void = {
        def ASCII_0 = 48;
        for :get_loop (true) {
                let input = accept_string(prompt);
                defer free(input);
                if (len(input) != DIGITS) {
                        fmt::printfln("Remember it's a {}-digit number.", DIGITS)!;
                        continue :get_loop;
                };
                for (let pos = 0; pos < len(input): int; pos += 1) {
                        let digit = strings::toutf8(input)[pos];
                        if (ascii::isdigit(digit: rune)) {
                                user_digit[pos] = digit: int - ASCII_0;
                        } else {
                                fmt::println("What?")!;
                                continue :get_loop;
                        };
                };
                if (is_any_repeated(user_digit)) {
                        fmt::println("Remember my number has no two digits the same.")!;
                        continue :get_loop;
                };
                break;
        };
};

def DIGITS = 3;

let random_digit: [DIGITS]int = [0 ...];

// Return three random digits.
//
fn random_number() [DIGITS]int = {
        for (let i = 0; i < DIGITS; i += 1) {
                for :digit_loop (true) {
                        random_digit[i] = random::u64n(&rand, 10): int;
                        for (let j = 0; j < i; j += 1) {
                                if (i != j && random_digit[i] == random_digit[j]) {
                                        continue :digit_loop;
                                };
                        };
                        break;
                };
        };
        return random_digit;
};

// Return `true` if any of the given numbers is repeated; otherwise return
// `false`.
//
fn is_any_repeated(num: []int) bool = {
        const length: int = len(num): int;
        for (let i0 = 0; i0 < length; i0 += 1) {
                for (let n1 .. num[i0 + 1 .. length]) {
                        if (num[i0] == n1) {
                                return true;
                        };
                };
        };
        return false;
};

// Init and run the game loop.
//
fn play() void = {
        def TRIES = 20;
        let score = 0;
        let fermi = 0; // counter
        let pico = 0; // counter
        let user_number: [DIGITS]int = [0...];
        for (true) {
                clear_screen();
                let computer_number = random_number();
                fmt::println("O.K.  I have a number in mind.")!;
                for (let guess = 1; guess <= TRIES; guess += 1) {
                        // XXX TMP
                        // fmt::print("My number: ")!;
                        // for (let i = 0; i < DIGITS; i += 1) {
                        //      fmt::print(computer_number[i])!;
                        // };
                        // fmt::println()!;
                        get_input(fmt::asprintf("Guess #{:_02}: ", guess)!, &user_number);
                        fermi = 0;
                        pico = 0;
                        for (let i = 0; i < DIGITS; i += 1) {
                                for (let j = 0; j < DIGITS; j += 1) {
                                        if (user_number[i] == computer_number[j]) {
                                                if (i == j) {
                                                        fermi += 1;
                                                } else {
                                                        pico += 1;
                                                };
                                        };
                                };
                        };
                        let picos = repeat("PICO ", pico);
                        defer free(picos);
                        let fermis = repeat("FERMI ", fermi);
                        defer free(fermis);
                        fmt::print(picos)!;
                        fmt::print(fermis)!;
                        if (pico + fermi == 0) {
                                fmt::print("BAGELS")!;
                        };
                        fmt::println()!;
                        if (fermi == DIGITS) {
                                break;
                        };
                };
                if (fermi == DIGITS) {
                        fmt::println("You got it!!!")!;
                        score += 1;
                } else {
                        fmt::println("Oh well.")!;
                        fmt::printf("That's {} guesses.  My number was ", TRIES)!;
                        for (let i = 0; i < DIGITS; i += 1) {
                                fmt::print(computer_number[i])!;
                        };
                        fmt::println(".")!;
                };
                if (!yes("Play again? ")) {
                        break;
                };
        };
        if (score != 0) {
                fmt::printfln("A {}-point bagels, buff!!", score)!;
        };
        fmt::println("Hope you had fun.  Bye.")!;
};

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn init() void = {
        randomize();
};

export fn main() void = {
        init();
        print_credits();
        print_instructions();
        play();
};

Bug

// Bug
//
// Original version in BASIC:
//      Brian Leibowitz, 1978.
//      Creative Computing (Morristown, New Jersey, USA), 1978.
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-12/15.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

type Bug = struct {
        body: bool,
        neck: bool,
        head: bool,
        feelers: int,
        feeler_type: rune,
        tail: bool,
        legs: int,
};

type Player = struct {
        pronoun: str,
        possessive: str,
        bug: Bug,
};

let computer = Player {
        pronoun = "I",
        possessive = "My",
        bug = Bug {
                body = false,
                neck = false,
                head = false,
                feelers = 0,
                feeler_type = 'F',
                tail = false,
                legs = 0,
        }
};

let human = Player {
        pronoun = "you",
        possessive = "Your",
        bug = Bug {
                body = false,
                neck = false,
                head = false,
                feelers = 0,
                feeler_type = 'A',
                tail = false,
                legs = 0,
        }
};

type bug_part = enum int {BODY = 1, NECK, HEAD, FEELER, TAIL, LEG};

def FIRST_PART = bug_part::BODY: int;
def LAST_PART = bug_part::LEG: int;

fn part_quantity(part: bug_part) int = {
        return switch (part) {
                case bug_part::BODY => yield 1;
                case bug_part::NECK => yield 1;
                case bug_part::HEAD => yield 1;
                case bug_part::FEELER => yield 2;
                case bug_part::TAIL => yield 1;
                case bug_part::LEG => yield 6;
        };
};

fn part_name(part: bug_part) str = {
        return switch (part) {
                case bug_part::BODY => yield "body";
                case bug_part::NECK => yield "neck";
                case bug_part::HEAD => yield "head";
                case bug_part::FEELER => yield "feeler";
                case bug_part::TAIL => yield "tail";
                case bug_part::LEG => yield "leg";
        };
};

// Bug body attributes.
//
def BODY_HEIGHT = 2;
def FEELER_LENGTH = 4;
def LEG_LENGTH = 2;
def MAX_FEELERS = 2;
def MAX_LEGS = 6;
def NECK_LENGTH = 2;

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn move_cursor_up(lines: int = 1) void = {
        fmt::printf("\x1B[{}A", lines)!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

// Erase the entire current line and move the cursor to the start of the line.
//
fn erase_line() void = {
        fmt::print("\x1B[2K")!;
};

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

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

// Clear the screen, display the credits and wait for a keypress.
//
fn print_credits() void = {
        clear_screen();
        fmt::println("Bug\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Brian Leibowitz, 1978.")!;
        fmt::println("    Creative computing (Morristown, New Jersey, USA), 1978.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter("Press Enter to read the instructions. ");
};

def INSTRUCTIONS = `
The object is to finish your bug before I finish mine. Each number
stands for a part of the bug body.

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

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

// Clear the screen, print the instructions and wait for a keypress.
//
fn print_instructions() void = {
        clear_screen();
        fmt::println("Bug")!;
        fmt::println(INSTRUCTIONS)!;
        print_parts_table();
        press_enter("\nPress Enter to start. ");
};

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

fn to_capital(s: str) str = {
        const initial = ascii::strupper(strings::sub(s, 0, 1))!;
        defer free(initial);
        return strings::concat(initial, strings::sub(s, 1))!;
};

fn repeat(s: str, n: int) str = {
        let result = "";
        for (let i: int = 0; i < n; i += 1) {
                result = strings::concat(result, s)!;
        };
        return result;
};

// Pseudo-random numbers {{{1
// =============================================================================

let rand: random::random = 0;

// Return a random number from 1 to 6 (inclusive).
//
fn dice(r: *random::random) int = {
        return (random::u32n(r, 6) + 1): int;
};

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

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

// Print a table with the description of the bug parts.
//
fn print_parts_table() void = {
        def COLUMNS = 3;
        def COLUMN_WIDTH = 8;
        def COLUMN_SEPARATION = 2;

        const format_string = strings::concat(
                "{:-",
                strconv::itos(COLUMN_WIDTH + COLUMN_SEPARATION),
                "}")!;

        // Headers
        const header: [_]str = ["Number", "Part", "Quantity"];
        for (let i = 0; i < COLUMNS; i += 1) {
                fmt::printf(format_string, header[i])!;
        };
        fmt::println()!;

        // Rulers
        const ruler = repeat("-", COLUMN_WIDTH);
        defer free(ruler);
        const padding = repeat(" ", COLUMN_SEPARATION - 1);
        defer free(padding);
        for (let i = 0; i < COLUMNS; i += 1) {
                fmt::print(ruler, if (i == COLUMNS - 1) "" else padding)!;
        };
        fmt::println()!;

        // Data
        for (let p = FIRST_PART; p <= LAST_PART; p += 1) {
                let part = p: bug_part;
                fmt::printf(format_string, p)!;
                let name = to_capital(part_name(part));
                defer free(name);
                fmt::printf(format_string, name)!;
                fmt::printf(format_string, part_quantity(part))!;
                fmt::println()!;
        };
};

// Print a bug head.
//
fn print_head() void = {
        fmt::println("        HHHHHHH")!;
        fmt::println("        H     H")!;
        fmt::println("        H O O H")!;
        fmt::println("        H     H")!;
        fmt::println("        H  V  H")!;
        fmt::println("        HHHHHHH")!;
};

// Print the given bug.
//
fn print_bug(bug: Bug) void = {
        if (bug.feelers > 0) {
                for (let i = 0; i < FEELER_LENGTH; i += 1) {
                        fmt::print("        ")!;
                        for (let i = 0; i < bug.feelers; i += 1) {
                                fmt::print(" ", bug.feeler_type)!;
                        };
                        fmt::println()!;
                };
        };
        if (bug.head) {
                print_head();
        };
        if (bug.neck) {
                for (let i = 0; i < NECK_LENGTH; i += 1) {
                        fmt::println("          N N")!;
                };
        };
        if (bug.body) {
                fmt::println("     BBBBBBBBBBBB")!;
                for (let i = 0; i < BODY_HEIGHT; i += 1) {
                        fmt::println("     B          B")!;
                };
                if (bug.tail) {
                        fmt::println("TTTTTB          B")!;
                };
                fmt::println("     BBBBBBBBBBBB")!;
        };
        if (bug.legs > 0) {
                for (let i = 0; i < LEG_LENGTH; i += 1) {
                        fmt::print("    ")!;
                        for (let i = 0; i < bug.legs; i += 1) {
                                fmt::print(" L")!;
                        };
                        fmt::println()!;
                };
        };
};

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

// Array to convert a number to its equilavent text.
//
let as_text: [_]str = [
        "no",
        "a",
        "two",
        "three",
        "four",
        "five",
        "six" ]; // MAX_LEGS

// Return a string containing the given number and noun in their proper form.
//
fn plural(number: int, noun: str) str = {
        return strings::concat(
                as_text[number],
                " ",
                noun,
                if (number > 1) "s" else ""
        )!;
};

// Add the given part to the given player's bug.
//
fn add_part(part: bug_part, player: *Player) bool = {
        let changed: bool = false;
        switch (part) {
        case bug_part::BODY =>
                if (player.bug.body) {
                        fmt::println(", but", player.pronoun, "already have a body.")!;
                } else {
                        fmt::println(";", player.pronoun, "now have a body:")!;
                        player.bug.body = true;
                        changed = true;
                };
        case bug_part::NECK =>
                if (player.bug.neck) {
                        fmt::println(", but", player.pronoun, "already have a neck.")!;
                } else if (!player.bug.body) {
                        fmt::println(", but", player.pronoun, "need a body first.")!;
                } else {
                        fmt::println(";", player.pronoun, "now have a neck:")!;
                        player.bug.neck = true;
                        changed = true;
                };
        case bug_part::HEAD =>
                if (player.bug.head) {
                        fmt::println(", but", player.pronoun, "already have a head.")!;
                } else if (!player.bug.neck) {
                        fmt::println(", but", player.pronoun, "need a a neck first.")!;
                } else {
                        fmt::println(";", player.pronoun, "now have a head:")!;
                        player.bug.head = true;
                        changed = true;
                };
        case bug_part::FEELER =>
                if (player.bug.feelers == MAX_FEELERS) {
                        fmt::println(", but", player.pronoun, "have two feelers already.")!;
                } else if (!player.bug.head) {
                        fmt::println(", but", player.pronoun, "need a head first.")!;
                } else {
                        player.bug.feelers += 1;
                        fmt::print(";", player.pronoun, "now have",
                                plural(player.bug.feelers, "feeler"))!;
                        fmt::println(":")!;
                        changed = true;
                };
        case bug_part::TAIL =>
                if (player.bug.tail) {
                        fmt::println(", but", player.pronoun, "already have a tail.")!;
                } else if (!player.bug.body) {
                        fmt::println(", but", player.pronoun, "need a body first.")!;
                } else {
                        fmt::println(";", player.pronoun, "now have a tail:")!;
                        player.bug.tail = true;
                        changed = true;
                };
        case bug_part::LEG =>
                if (player.bug.legs == MAX_LEGS) {
                        fmt::println(", but", player.pronoun, "have",
                                as_text[MAX_LEGS], "feet already.")!;
                } else if (!player.bug.body) {
                        fmt::println(", but", player.pronoun, "need a body first.")!;
                } else {
                        player.bug.legs += 1;
                        fmt::print(";", player.pronoun, "now have",
                                plural(player.bug.legs, "leg"))!;
                        fmt::println(":")!;
                        changed = true;
                };
        };
        return changed;
};

// Play one turn for the given player, rolling the dice and updating his bug.
//
fn turn(player: *Player) void = {
        press_enter("Press Enter to roll the dice. ");
        move_cursor_up(1);
        erase_line();

        const number: int = dice(&rand);
        const part = number: bug_part;
        const pronoun = to_capital(player.pronoun);
        defer free(pronoun);
        fmt::printf("{} rolled a {} ({})", pronoun, number, part_name(part))!;
        if (add_part(part, player)) {
                fmt::println()!;
                print_bug(player.bug);
        };
        fmt::println()!;
};

// Print a message about the winner.
//
fn print_winner() void = {
        if (finished(human.bug) && finished(computer.bug)) {
                fmt::println("Both of our bugs are finished in the same number of turns!")!;
        } else if (finished(human.bug)) {
                fmt::println(human.possessive, "bug is finished.")!;
        } else if (finished(computer.bug)) {
                fmt::println(computer.possessive, "bug is finished.")!;
        };
};

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

// Execute the game loop.
//
fn play() void = {
        clear_screen();
        for (!game_over()) {
                turn(&human);
                turn(&computer);
        };
        print_winner();
};

fn init() void = {
        randomize();
};

fn bye() void = {
        fmt::println("I hope you enjoyed the game, play it again soon!!")!;
};

export fn main() void = {
        init();
        print_credits();
        print_instructions();
        play();
        bye();
};

Bunny

// Bunny
//
// Original version in BASIC:
//      Creative Computing (Morristown, New Jersey, USA), 1978.
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-12, 2025-02-15.
//
// Last modified: 20260213T1645+0100.

use bufio;
use fmt;
use os;
use strings;

fn clear_screen() void = {
        fmt::print("\x1B[0;0H\x1B[2J")!;
};

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

fn print_credits() void = {
        fmt::println("Bunny\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Creative Computing (Morristown, New Jersey, USA), 1978.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter("Press Enter to start the program. ");
};

def WIDTH = 53;
let line: [WIDTH]rune = [' '...]; // buffer

// Clear the line buffer with spaces.
//
fn clear_line() void = {
        for (let column: int = 0; column < WIDTH; column += 1) {
                line[column] = ' ';
        };
};

let letter: [_]rune = ['B', 'U', 'N', 'N', 'Y'];
def LETTERS = len(letter): int;

def EOL = -1; // end of line identifier
let data: [_]int = [
        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 ];
def DATA_LEN = len(data): int;

let data_index = 0;

fn datum() int = {
        defer data_index += 1;
        return data[data_index];
};

fn draw() void = {
        clear_line();
        for (data_index < DATA_LEN) {
                let first_column = datum();
                if (first_column == EOL) {
                        for (let c .. line) {
                                fmt::print(c)!;
                        };
                        fmt::println()!;
                        clear_line();
                } else {
                        let last_column = datum();
                        for (let column = first_column; column <= last_column; column += 1) {
                                line[column] = letter[column % LETTERS];
                        };
                };
        };
};

export fn main() void = {
        clear_screen();
        print_credits();
        clear_screen();
        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 Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
//      Written on 2025-02-17.
//      Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_cursor_position(line: int, column: int) void = {
        fmt::printf("\x1B[{};{}H", line, column)!;
};

fn hide_cursor() void = {
        fmt::print("\x1B[?25l")!;
};

fn show_cursor() void = {
        fmt::print("\x1B[?25h")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_line_to_end() void = {
        fmt::print("\x1B[K")!;
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

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

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

// Arena

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

let arena: [ARENA_HEIGHT][ARENA_WIDTH]str = [[""...]...];

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

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

// The end

type end = enum {
        NOT_YET,
        QUIT,
        ELECTRIFIED,
        KILLED,
        VICTORY,
};

let the_end: end = end::NOT_YET;

// Machines

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

let machine_x: [MACHINES]int = [0...];
let machine_y: [MACHINES]int = [0...];
let operative: [MACHINES]bool = [false...];
let destroyed_machines: int = 0; // counter

// Human

let human_x: int = 0;
let human_y: int = 0;

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn get_string(prompt: str = "") str = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return accept_string(prompt);
};

fn press_enter(prompt: str) void = {
        free(accept_string(prompt));
};

// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "ok", "y", "yeah", "yes" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "n", "no", "nope" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt: str) bool = {
        for (true) {
                let answer = get_string(prompt);
                defer free(answer);
                if (is_yes(answer)) {
                        return true;
                };
                if (is_no(answer)) {
                        return false;
                };
        };
};

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

def TITLE = "Chase";

// Print the title at the current cursor position.
//
fn print_title() void = {
        set_style(TITLE_INK);
        fmt::println(TITLE)!;
        set_style(DEFAULT_INK);
};

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

// Print the game instructions and wait for a key.
//
fn print_instructions() void = {
        print_title();
        set_style(INSTRUCTIONS_INK);
        defer set_style(DEFAULT_INK);
        fmt::printfln("\nYou ({}) are in a high voltage maze with {}", HUMAN, MACHINES)!;
        fmt::printfln("security machines ({}) trying to kill you.", MACHINE)!;
        fmt::printfln("You must maneuver them into the maze ({}) to survive.\n", FENCE)!;
        fmt::println("Good luck!\n")!;
        fmt::println("The movement commands are the following:\n")!;
        fmt::println("    ↖  ↑  ↗")!;
        fmt::println("    NW N NE")!;
        fmt::println("  ←  W   E  →")!;
        fmt::println("    SW S SE")!;
        fmt::println("    ↙  ↓  ↘")!;
        fmt::println("\nPlus 'Q' to end the game.")!;
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn random_int_in_inclusive_range(min: int, max: int) int = {
        return random::u32n(&rand, (max - min):u32): int + min;
};

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

// Display the arena at the top left corner of the screen.
//
fn print_arena() void = {
        set_cursor_position(ARENA_ROW, 1);
        for (let y = 0; y <= ARENA_LAST_Y; y += 1) {
                for (let x = 0; x <= ARENA_LAST_X; x += 1) {
                        fmt::print(arena[y][x])!;
                };
                fmt::println()!;
        };
};

// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
fn is_border(y: int, x: int) bool = {

        return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
};

// Place the given string at a random empty position of the arena and return
// the coordinates.
//
fn place(s: str) (int, int) = {
        let y: int = 0;
        let x: int = 0;
        for (true) {
                y = random_int_in_inclusive_range(1, ARENA_LAST_Y - 1);
                x = random_int_in_inclusive_range(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.
//
fn inhabit_arena() void = {

        for (let m = 0; m < MACHINES; m += 1) {
                let coords = place(MACHINE);
                machine_y[m] = coords.0;
                machine_x[m] = coords.1;
                operative[m] = true;
        };
        for (let i = 0; i < FENCES; i += 1) {
                place(FENCE);
        };
        let coords = place(HUMAN);
        human_y = coords.0;
        human_x = coords.1;
};

// Clean the arena, i.e. empty it and add a surrounding fence.
//
fn clean_arena() void = {
        for (let y = 0; y <= ARENA_LAST_Y; y += 1) {
                for (let x = 0; x <= ARENA_LAST_X; x += 1) {
                        arena[y][x] = if (is_border(y, x)) FENCE else EMPTY;
                };
        };
};

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

// Init the game.
//
fn init_game() void = {
        clean_arena();
        inhabit_arena();
        destroyed_machines = 0;
        the_end = end::NOT_YET;
};

// Move the given machine.
//
fn move_machine(m: int) void = {
        let maybe: int = 0;

        arena[machine_y[m]][machine_x[m]] = EMPTY;

        maybe = random::u32n(&rand, 2): int;
        if (machine_y[m] > human_y) {
                machine_y[m] -= maybe;
        } else if (machine_y[m] < human_y) {
                machine_y[m] += maybe;
        };

        maybe = random::u32n(&rand, 2): int;
        if (machine_x[m] > human_x) {
                machine_x[m] -= maybe;
        } else if (machine_x[m] < human_x) {
                machine_x[m] += maybe;
        };

        if (arena[machine_y[m]][machine_x[m]] == EMPTY) {
                arena[machine_y[m]][machine_x[m]] = MACHINE;
        } else if (arena[machine_y[m]][machine_x[m]] == FENCE) {
                operative[m] = false;
                destroyed_machines += 1;
                if (destroyed_machines == MACHINES) {
                        the_end = end::VICTORY;
                };
        } else if (arena[machine_y[m]][machine_x[m]] == HUMAN) {
                the_end = end::KILLED;
        };
};

// Maybe move the given operative machine.
//
fn maybe_move_machine(m: int) void = {
        if (random::u32n(&rand, MACHINES_DRAG) == 0) {
                move_machine(m);
        };
};

// Move the operative machines.
//
fn move_machines() void = {
        for (let m = 0; m < MACHINES; m += 1) {
                if (operative[m]) {
                        maybe_move_machine(m);
                };
        };
};

// Read a user command; update `the_end` accordingly and return the direction
// increments.
//
fn get_move() (int, int) = {
        let y_inc: int = 0;
        let x_inc: int = 0;
        fmt::println()!;
        erase_line_to_end();
        let command = ascii::strlower(get_string("Command: "))!;
        defer free(command);
        switch (command) {
        case "q" =>
                the_end = end::QUIT;
        case "sw" =>
                y_inc = 1;
                x_inc = -1;
        case "s" =>
                y_inc = 1;
        case "se" =>
                y_inc = 1;
                x_inc = 1;
        case "w" =>
                x_inc = -1;
        case "e" =>
                x_inc = 1;
        case "nw" =>
                y_inc = -1;
                x_inc = -1;
        case "n" =>
                y_inc = -1;
        case "ne" =>
                y_inc = -1;
                x_inc = 1;
        case =>
                        void;
        };
        return (y_inc, x_inc);
};

fn play() void = {
        let y_inc: int = 0;
        let x_inc: int = 0;

        for (true) { // game loop

                clear_screen();
                print_title();
                init_game();

                for (true) { // action loop
                        print_arena();
                        let coords = get_move();
                        y_inc = coords.0;
                        x_inc = coords.1;
                        if (the_end == end::NOT_YET) {
                                if (y_inc != 0 || x_inc != 0) {
                                        arena[human_y][human_x] = EMPTY;

                                        if (arena[human_y + y_inc][human_x + x_inc] == FENCE) {
                                                the_end = end::ELECTRIFIED;
                                        } else if (arena[human_y + y_inc][human_x + x_inc] == MACHINE) {
                                                the_end = end::KILLED;
                                        } else {
                                                arena[human_y][human_x] = EMPTY;
                                                human_y = human_y + y_inc;
                                                human_x = human_x + x_inc;
                                                arena[human_y][human_x] = HUMAN;
                                                print_arena();
                                                move_machines();
                                        };
                                };
                        };
                        if (the_end != end::NOT_YET) {
                                break;
                        };
                }; // action loop;

                switch (the_end) {
                case end::QUIT =>
                        fmt::println("\nSorry to see you quit.")!;
                case end::ELECTRIFIED =>
                        fmt::println("\nZap! You touched the fence!")!;
                case end::KILLED =>
                        fmt::println("\nYou have been killed by a lucky machine.")!;
                case end::VICTORY =>
                        fmt::println("\nYou are lucky, you destroyed all machines.")!;
                case =>
                        void;
                };

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

        }; // game loop

        fmt::println("\nHope you don't feel fenced in.")!;
        fmt::println("Try again sometime.")!;
};

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

export fn main() void = {
        randomize();
        set_style(DEFAULT_INK);

        clear_screen();
        print_credits();

        press_enter("\nPress the Enter key to read the instructions. ");
        clear_screen();
        print_instructions();

        press_enter("\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 Hare (v0.24.2):
//      Copyright (c) 2024, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair

// Written in 2024-12-14/15.

// Last modified: 20260213T1645+0100.

use fmt;

def LINES = 17;

export fn main() void = {
        for (let i = 1; i <= LINES / 2 + 1; i += 1) {
                for (let j = 1; j <= (LINES + 1) / 2 - i + 1; j += 1) {
                        fmt::print(" ")!;
                };
                for (let j = 1; j <= i * 2 - 1; j += 1) {
                        fmt::print("*")!;
                };
                fmt::println()!;
        };
        for (let i = 1; i <= LINES / 2; i += 1) {
                for (let j = 1; j <= i + 1; j += 1) {
                        fmt::print(" ")!;
                };
                for (let j = 1; j <= ((LINES + 1) / 2 - i) * 2 - 1; j += 1) {
                        fmt::print("*")!;
                };
                fmt::println()!;
        };
};

Hammurabi

// Hammurabi
//
// Description:
//      A simple text-based simulation game set in the ancient kingdom of Sumeria.
//
// Original program:
//      Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
//
// BASIC port:
//      Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
//      Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
//
// More details:
//      - https://en.wikipedia.org/wiki/Hamurabi_(video_game)
//      - https://www.mobygames.com/game/22232/hamurabi/
//
// This improved remake in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-19.
//
// Last modified: 20260213T1645+0100.
//
// Acknowledgment:
//      The following Python port was used as a reference of the original
//      variables: <https://github.com/jquast/hamurabi.py>.
//

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

use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

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

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

type result = enum {
        VERY_GOOD,
        NOT_TOO_BAD,
        BAD,
        VERY_BAD,
};

let acres: int = 0;
let bushels_eaten_by_rats: int = 0;
let bushels_harvested: int = 0;
let bushels_harvested_per_acre: int = 0;
let bushels_in_store: int = 0;
let bushels_to_feed_with: int = 0;
let dead: int = 0;
let infants: int = 0;
let irritation: int = 0; // counter (0 ..= 99)
let population: int = 0;
let starved_people_percentage: int = 0;
let total_dead: int = 0;

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

def CREDITS: str =
`Hammurabi

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

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

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

fn print_credits() void = {
        set_style(TITLE_INK);
        fmt::println(CREDITS)!;
        set_style(DEFAULT_INK);
};

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

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

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

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

Try your hand at governing ancient Sumeria for a {}-year term of office.`;

fn print_instructions() void = {
        set_style(INSTRUCTIONS_INK);
        fmt::printfln(
                INSTRUCTIONS,
                MIN_HARVESTED_BUSHELS_PER_ACRE,
                MAX_HARVESTED_BUSHELS_PER_ACRE,
                YEARS
                )!;
        set_style(DEFAULT_INK);
};

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn pause(prompt: str = "> ") void = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        free(accept_string(prompt));
};

fn accept_integer(prompt: str = "") (int | ...strconv::error) = {
        const s = accept_string(prompt);
        defer free(s);
        return strconv::stoi(s);
};

// Accept an integer from the user with the given prompt; if the input is an
// invalid integer, return -1.
//
fn get_integer(prompt: str) int = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return match (accept_integer(prompt)) {
                case let number: int => yield number;
                case => yield -1;
                };
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

// Return a random number from 1 to 5 (inclusive).
//
fn random_1_to_5() int = {
        return random::u64n(&rand, 5): int + 1;
};

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

// Return a string with the proper wording for `n` persons, using the given or
// default words for singular and plural forms, and a flag indicating whether
// the string must be freed by the caller or not.
//
fn persons(n: int, singular: str = "person", plural: str = "people") (str, bool) = {
        switch (n) {
        case 0 => return ("nobody", false);
        case 1 => return (fmt::asprintf("one {}", singular)!, true);
        case => return (fmt::asprintf("{} {}", n, plural)!, true);
        };
};

fn ordinal_suffix(n: int) str = {
        switch (n) {
        case 1 => return "st";
        case 2 => return "nd";
        case 3 => return "rd";
        case => return "th";
        };
};

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

// Return a string with the description of the given year as the previous one;
// return also a flag indicating whether the string wether must be freed by the
// caller or not.
//
fn previous(year: int) (str, bool) = {
        if (year == 0) {
                return ("the previous year", false);
        } else {
                return (
                        fmt::asprintf(
                                "your {}{} year",
                                year,
                                ordinal_suffix(year)
                        )!,
                        true
                );
        };
};

fn print_annual_report(year: int) void = {
        clear_screen();
        set_style(SPEECH_INK);
        fmt::println("Hammurabi, I beg to report to you.")!;
        set_style(DEFAULT_INK);

        const (year_text, year_text_must_be_freed) = previous(year);
        const (persons_text, persons_text_must_be_freed) = persons(dead);
        const (infants_text, infants_text_must_be_freed) = persons(infants);

        fmt::printf(
                "\nIn {}, {} starved and {} {} born.\n",
                year_text,
                persons_text,
                infants_text,
                if (infants > 1) "were" else "was"
        )!;
        if (year_text_must_be_freed) {
                free(year_text);
        };
        if (persons_text_must_be_freed) {
                free(persons_text);
        };
        if (infants_text_must_be_freed) {
                free(infants_text);
        };

        population += infants;

        if (year > 0 && random::f64rand(&rand) <= PLAGUE_CHANCE) {
                population = (population / 2): int;
                set_style(WARNING_INK);
                fmt::println("A horrible plague struck!  Half the people died.")!;
                set_style(DEFAULT_INK);
        };

        fmt::printfln("The population is {}.", population)!;
        fmt::println("The city owns", acres, "acres.")!;
        fmt::printf(
                "You harvested {} bushels ({} per acre).\n",
                bushels_harvested,
                bushels_harvested_per_acre
        )!;
        if (bushels_eaten_by_rats > 0) {
                fmt::println("The rats ate", bushels_eaten_by_rats, "bushels.")!;
        };
        fmt::println("You have", bushels_in_store, "bushels in store.")!;
        bushels_harvested_per_acre =
                (RANGE_OF_HARVESTED_BUSHELS_PER_ACRE: f64 * random::f64rand(&rand)): int +
                MIN_HARVESTED_BUSHELS_PER_ACRE;
        fmt::println("Land is trading at", bushels_harvested_per_acre, "bushels per acre.\n")!;
};

fn say_bye() void = {
        set_style(DEFAULT_INK);
        fmt::println("\nSo long for now.\n")!;
};

fn quit_game() void = {
        say_bye();
        os::exit(0);
};

fn relinquish() void = {
        set_style(SPEECH_INK);
        fmt::println("\nHammurabi, I am deeply irritated and cannot serve you anymore.")!;
        fmt::println("Please, get yourself another steward!")!;
        set_style(DEFAULT_INK);
        quit_game();
};

fn increase_irritation() void = {
        irritation += 1 + random::u64n(&rand, IRRITATION_STEP): int;
        if (irritation >= MAX_IRRITATION) {
                relinquish(); // this never returns
        };
};

fn print_irritated(adverb: str) void = {
        fmt::printfln("The steward seems {} irritated.", adverb)!;
};

fn show_irritation() void = {
        if (irritation < IRRITATION_STEP * 2) {
                print_irritated("slightly");
        } else if (irritation < IRRITATION_STEP * 3) {
                print_irritated("quite");
        } else if (irritation < IRRITATION_STEP * 4) {
                print_irritated("very");
        } else {
                print_irritated("profoundly");
        };
};

// Print a message begging to repeat an ununderstandable input.
//
fn beg_repeat() void = {
        increase_irritation(); // this may never return
        set_style(SPEECH_INK);
        fmt::println("I beg your pardon?  I did not understand your order.")!;
        set_style(DEFAULT_INK);
        show_irritation();
};

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
fn beg_think_again(n: int, name: str) void = {
        increase_irritation(); // this may never return
        set_style(SPEECH_INK);
        fmt::printfln("I beg your pardon?  You have only {} {}.  Now then…", n, name)!;
        set_style(DEFAULT_INK);
        show_irritation();
};

// Buy or sell land.
//
fn trade() void = {
        let acres_to_buy: int = 0;
        let acres_to_sell: int = 0;

        for (true) {
                acres_to_buy = get_integer("How many acres do you wish to buy? (0 to sell): ");
                if (acres_to_buy < 0) {
                        beg_repeat(); // this may never return
                        continue;
                };
                if (bushels_harvested_per_acre * acres_to_buy <= bushels_in_store) {
                        break;
                };
                beg_think_again(bushels_in_store, "bushels of grain");
        };

        if (acres_to_buy != 0) {

                fmt::printfln("You buy {} acres.", acres_to_buy)!;
                acres += acres_to_buy;
                bushels_in_store -= bushels_harvested_per_acre * acres_to_buy;
                fmt::printfln("You now have {} acres and {} bushels.", acres, bushels_in_store)!;

        } else {

                for (true) {
                        acres_to_sell = get_integer("How many acres do you wish to sell?: ");
                        if (acres_to_sell < 0) {
                                beg_repeat(); // this may never return
                                continue;
                        };
                        if (acres_to_sell < acres) {
                                break;
                        };
                        beg_think_again(acres, "acres");
                };

                if (acres_to_sell > 0) {
                        fmt::printfln("You sell {} acres.", acres_to_sell)!;
                        acres -= acres_to_sell;
                        bushels_in_store += bushels_harvested_per_acre * acres_to_sell;
                        fmt::printfln("You now have {} acres and {} bushels.", acres, bushels_in_store)!;
                };

        };
};

// Feed the people.
//
fn feed() void = {
        for (true) {
                bushels_to_feed_with = get_integer("How many bushels do you wish to feed your people with?: ");
                if (bushels_to_feed_with < 0) {
                        beg_repeat(); // this may never return
                        continue;
                };
                // Trying to use more grain than is in silos?
                if (bushels_to_feed_with <= bushels_in_store) {
                        break;
                };
                beg_think_again(bushels_in_store, "bushels of grain");
        };

        fmt::printfln("You feed your people with {} bushels.", bushels_to_feed_with)!;
        bushels_in_store -= bushels_to_feed_with;
        fmt::printfln("You now have {} bushels.", bushels_in_store)!;
};

// Seed the land.
//
fn seed() void = {
        let acres_to_seed:int = 0;

        for (true) {

                acres_to_seed = get_integer("How many acres do you wish to seed?: ");
                if (acres_to_seed < 0) {
                        beg_repeat(); // this may never return
                        continue;
                };
                if (acres_to_seed == 0) {
                        break;
                };

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

                const message = fmt::asprintf(
                        "bushels of grain,\nand one bushel can seed {} acres",
                        ACRES_A_BUSHEL_CAN_SEED
                )!;
                defer free(message);

                // Enough grain for seed?
                if ((acres_to_seed / ACRES_A_BUSHEL_CAN_SEED): int > bushels_in_store) {
                        beg_think_again(bushels_in_store, message);
                        continue;
                };

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

                const message = fmt::asprintf(
                        "people to tend the fields,\nand one person can seed {} acres",
                        ACRES_A_PERSON_CAN_SEED
                )!;
                defer free(message);

                beg_think_again(population, message);

        };

        let bushels_used_for_seeding = (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED): int;
        fmt::printfln("You seed {} acres using {} bushels.", acres_to_seed, bushels_used_for_seeding)!;
        bushels_in_store -= bushels_used_for_seeding;
        fmt::printfln("You now have {} bushels.", bushels_in_store)!;

        // A bountiful harvest!
        bushels_harvested_per_acre = random_1_to_5();
        bushels_harvested = acres_to_seed * bushels_harvested_per_acre;
        bushels_in_store += bushels_harvested;
};

fn is_even(n: int) bool = {
        return n % 2 == 0;
};

fn check_rats() void = {
        let rat_chance = random_1_to_5();
        bushels_eaten_by_rats = if (is_even(rat_chance)) (bushels_in_store / rat_chance): int else 0;
        bushels_in_store -= bushels_eaten_by_rats;
};

// Set the variables to their values in the first year.
//
fn init() void = {
        dead = 0;
        total_dead = 0;
        starved_people_percentage = 0;
        population = 95;
        infants = 5;
        acres = ACRES_PER_PERSON * (population + infants);
        bushels_harvested_per_acre = 3;
        bushels_harvested = acres * bushels_harvested_per_acre;
        bushels_eaten_by_rats = 200;
        bushels_in_store = bushels_harvested - bushels_eaten_by_rats;
        irritation = 0;
};

fn print_result(r: result) void = {
        set_style(RESULT_INK);

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

        set_style(DEFAULT_INK);
};

fn print_final_report() void = {
        clear_screen();

        if (starved_people_percentage > 0) {
                fmt::printf(
                        "In your {}-year term of office, {} percent of the\n",
                        YEARS,
                        starved_people_percentage
                )!;
                fmt::printf(
                        "population starved per year on the average, i.e., a total of {} people died!\n\n",
                        total_dead
                )!;
        };

        let acres_per_person = acres / population;
        fmt::printf(
                "You started with {} acres per person and ended with {}.\n\n",
                ACRES_PER_PERSON,
                acres_per_person
        )!;

        if (starved_people_percentage > 33 || acres_per_person < 7) {
                print_result(result::VERY_BAD);
        } else if (starved_people_percentage > 10 || acres_per_person < 9) {
                print_result(result::BAD);
        } else if (starved_people_percentage > 3 || acres_per_person < 10) {
                print_result(result::NOT_TOO_BAD);
        } else {
                print_result(result::VERY_GOOD);
        };
};

fn check_starvation(year: int) void = {
                // How many people has been fed?
                let fed_people = (bushels_to_feed_with / BUSHELS_TO_FEED_A_PERSON): int;

                if (population > fed_people) {

                        dead = population - fed_people;
                        starved_people_percentage = ((year - 1) * starved_people_percentage + dead * 100 / population) / year;
                        population -= dead;
                        total_dead += dead;

                        // Starve enough for impeachment?
                        if (dead > (0.45 * (population): f64): int) {

                                set_style(WARNING_INK);
                                fmt::println("\nYou starved", dead, "people in one year!!!\n")!;
                                set_style(DEFAULT_INK);
                                print_result(result::VERY_BAD);
                                quit_game();

                        };

                };
};

fn govern() void = {
        init();

        print_annual_report(0);

        for (let year = 1; year <= YEARS; year += 1) {

                trade();
                feed();
                seed();
                check_rats();

                // Let's have some babies
                infants = (random_1_to_5() * (20 * acres + bushels_in_store) / population / 100 + 1): int;

                check_starvation(year);

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

        };
};

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

export fn main() void = {
        randomize();

        clear_screen();
        print_credits();

        pause("\nPress the Enter key to read the instructions. ");
        clear_screen();
        print_instructions();

        pause("\nPress the Enter key to start. ");
        govern();

        pause("Press the Enter key to read the final report. ");
        print_final_report();
        say_bye();
};

High Noon

// High Noon
//
// Original version in BASIC:
//      Designed and programmed by Chris Gaylo, Syosset High School, New York, 1970-09-12.
//      http://mybitbox.com/highnoon-1970/
//      http://mybitbox.com/highnoon/
//
// Transcriptions:
//      https://github.com/MrMethor/Highnoon-BASIC/
//      https://github.com/mad4j/basic-highnoon/
//
// Version modified for QB64:
//      By Daniele Olmisani, 2014.
//      https://github.com/mad4j/basic-highnoon/
//
// This improved remake in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-17.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn hide_cursor() void = {
        fmt::print("\x1B[?25l")!;
};

fn show_cursor() void = {
        fmt::print("\x1B[?25h")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

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

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

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

let player_bullets: int = 0;
let opponent_bullets: int = 0;

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

// Print the given prompt and wait until the user enters a string.
//
fn get_string(prompt: str = "") str = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return accept_string(prompt);
};

fn press_enter(prompt: str) void = {
        free(accept_string(prompt));
};

fn accept_integer() (int | ...strconv::error) = {
        const s = accept_string();
        defer free(s);
        return strconv::stoi(s);
};

fn prompted_integer(prompt: str) (int | ...strconv::error) = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
        return accept_integer();
};

fn prompted_integer_or_0(prompt: str) int = {
        match (prompted_integer(prompt)) {
        case let i: int =>
                return i;
        case =>
                return 0;
        };
};

// Print the given prompt and wait until the user enters an integer.
//
fn get_integer(prompt: str = "") int = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return prompted_integer_or_0(prompt);
};

// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "ok", "y", "yeah", "yes" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "n", "no", "nope" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt: str) bool = {
        for (true) {
                let answer = get_string(prompt);
                defer free(answer);
                if (is_yes(answer)) {
                        return true;
                };
                if (is_no(answer)) {
                        return false;
                };
        };
};

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

// Print the title at the current cursor position.
//
fn print_title() void = {
        set_style(TITLE_INK);
        fmt::println("High Noon")!;
        set_style(DEFAULT_INK);
};

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

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

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

fn plural_suffix(n: int) str = {
        switch (n) {
        case 1 => return "";
        case => return "s";
        };
};

fn print_shells_left() void = {
        if (player_bullets == opponent_bullets) {
                fmt::printfln("Both of you have {} bullets.", player_bullets)!;
        } else {
                fmt::printf(
                        "You now have {} bullet{} to Black Bart's {} bullet{}.\n",
                        player_bullets,
                        plural_suffix(player_bullets),
                        opponent_bullets,
                        plural_suffix(opponent_bullets))!;
        };
};

fn print_check() void = {
        fmt::println("******************************************************")!;
        fmt::println("*                                                    *")!;
        fmt::println("*                 BANK OF DODGE CITY                 *")!;
        fmt::println("*                  CASHIER'S RECEIT                  *")!;
        fmt::println("*                                                    *")!;
        fmt::printfln("* CHECK NO. {:_04}                   AUGUST {}TH, 1889 *",
                random::u64n(&rand, 1000),
                10: u64 + random::u64n(&rand, 10: u64))!;
        fmt::println("*                                                    *")!;
        fmt::println("*                                                    *")!;
        fmt::println("*       PAY TO THE BEARER ON DEMAND THE SUM OF       *")!;
        fmt::println("*                                                    *")!;
        fmt::println("* TWENTY THOUSAND DOLLARS-------------------$20,000  *")!;
        fmt::println("*                                                    *")!;
        fmt::println("******************************************************")!;
};

fn get_reward() void = {
        fmt::println("As mayor of Dodge City, and on behalf of its citizens,")!;
        fmt::println("I extend to you our thanks, and present you with this")!;
        fmt::println("reward, a check for $20,000, for killing Black Bart.\n\n")!;
        print_check();
        fmt::println("\n\nDon't spend it all in one place.")!;
};

fn move_the_opponent() void = {
        let paces = 2 + random::u64n(&rand, 8): int;
        fmt::printfln("Black Bart moves {} paces.", paces)!;
        distance -= paces;
};

// Maybe move the opponent; if so, return `true`, otherwise return `false`. A
// true `silent` flag allows to omit the message when the opponent doesn't
// move.
//
fn maybe_move_the_opponent(silent: bool = false) bool = {
        if (random::u64n(&rand, 2) == 0) { // 50% chances
                move_the_opponent();
                return true;
        } else {
                if (! silent) {
                        fmt::println("Black Bart stands still.")!;
                };
                return false;
        };
};

fn missed_shot() bool = {
        return random::f64rand(&rand) * 10.0 <= (distance / 10): f64;
};

// Handle the opponent's shot and return a flag with the result: if the
// opponent kills the player, return `true`; otherwise return `false`.
//
fn the_opponent_fires_and_kills(player_strategy: str) bool = {
        fmt::println("Black Bart fires…")!;
        opponent_bullets -= 1;
        if (missed_shot()) {
                fmt::println("A miss…")!;
                switch (opponent_bullets) {
                case 3 =>
                        fmt::println("Whew, were you lucky. That bullet just missed your head.")!;
                case 2 =>
                        fmt::println("But Black Bart got you in the right shin.")!;
                case 1 =>
                        fmt::println("Though Black Bart got you on the left side of your jaw.")!;
                case 0 =>
                        fmt::println("Black Bart must have jerked the trigger.")!;
                case =>
                        void;
                };
        } else {
                if (player_strategy == "j") {
                        fmt::println("That trick just saved yout life. Black Bart's bullet")!;
                        fmt::println("was stopped by the wood sides of the trough.")!;
                } else {
                        fmt::println("Black Bart shot you right through the heart that time.")!;
                        fmt::println("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`.
//
fn the_opponent_kills_or_runs(player_strategy: str) bool = {
        if (distance >= 10 || player_bullets == 0) {
                if (maybe_move_the_opponent(true)) {
                        return false;
                };
        };
        if (opponent_bullets > 0) {
                return the_opponent_fires_and_kills(player_strategy);
        } else {
                if (player_bullets > 0) {
                        if (random::u64n(&rand, 2) == 0) { // 50% chances
                                fmt::println("Now is your chance, Black Bart is out of bullets.")!;
                        } else {
                                fmt::println("Black Bart just hi-tailed it out of town rather than face you")!;
                                fmt::println("without a loaded gun. You can rest assured that Black Bart")!;
                                fmt::println("won't ever show his face around this town again.")!;
                                return true;
                        };
                };
        };
        return false;
};

fn play() void = {
        distance = INITIAL_DISTANCE;
        let watering_troughs = 0;
        player_bullets = INITIAL_BULLETS;
        opponent_bullets = INITIAL_BULLETS;

        for :showdown (true) {

                fmt::printfln("You are now {} paces apart from Black Bart.", distance)!;
                print_shells_left();
                set_style(INSTRUCTIONS_INK);
                fmt::println("\nStrategies:")!;
                fmt::println("  [A]dvance")!;
                fmt::println("  [S]tand still")!;
                fmt::println("  [F]ire")!;
                fmt::println("  [J]ump behind the watering trough")!;
                fmt::println("  [G]ive up")!;
                fmt::println("  [T]urn tail and run")!;
                set_style(DEFAULT_INK);

                let player_strategy = ascii::strlower(get_string("What is your strategy?! "))!;
                defer free(player_strategy);

                switch (player_strategy) {

                case "a" => // advance

                        for (true) {
                                let paces = get_integer("How many paces do you advance? ");
                                if (paces < 0) {
                                        fmt::println("None of this negative stuff, partner, only positive numbers.")!;
                                } else if (paces > 10) {
                                        fmt::println("Nobody can walk that fast.")!;
                                } else {
                                        distance -= paces;
                                        break;
                                };
                        };

                case "s" => // stand still

                        fmt::println("That move made you a perfect stationary target.")!;

                case "f" => // fire

                        if (player_bullets == 0) {

                                fmt::println("You don't have any bullets left.")!;

                        } else {

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

                        };

                case "j" => // jump

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

                case "g" => // give up

                        fmt::println("Black Bart accepts. The conditions are that he won't shoot you")!;
                        fmt::println("if you take the first stage out of town and never come back.")!;
                        if (yes("Agreed? ")) {
                                fmt::println("A very wise decision.")!;
                                break :showdown;
                        } else {
                                fmt::println("Oh well, back to the showdown.")!;
                        };

                case "t" => // turn tail and run

                        // The more bullets of the opponent, the less chances to escape.
                        if (random::u64n(&rand, (opponent_bullets + 2): u64) == 0) {
                                fmt::println("Man, you ran so fast even dogs couldn't catch you.")!;
                        } else {
                                switch (opponent_bullets) {
                                case 0 =>
                                        fmt::println("You were lucky, Black Bart can only throw his gun at you, he")!;
                                        fmt::println("doesn't have any bullets left. You should really be dead.")!;
                                case 1 =>
                                        fmt::println("Black Bart fires his last bullet…")!;
                                        fmt::println("He got you right in the back. That's what you deserve, for running.")!;
                                case 2 =>
                                        fmt::println("Black Bart fires and got you twice: in your back")!;
                                        fmt::println("and your ass. Now you can't even rest in peace.")!;
                                case 3 =>
                                        fmt::println("Black Bart unloads his gun, once in your back")!;
                                        fmt::println("and twice in your ass. Now you can't even rest in peace.")!;
                                case 4 =>
                                        fmt::println("Black Bart unloads his gun, once in your back")!;
                                        fmt::println("and three times in your ass. Now you can't even rest in peace.")!;
                                case =>
                                        void;
                                };
                                opponent_bullets = 0;
                        };
                        break :showdown;

                case =>

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

                }; // strategy switch

                if (the_opponent_kills_or_runs(player_strategy)) {
                        break;
                };

                if (player_bullets + opponent_bullets == 0) {
                        fmt::println("The showdown must end, because nobody has bullets left.")!;
                        break;
                };

                fmt::println()!;

        }; // showdown loop
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn init() void = {
        randomize();
};

export fn main() void = {
        init();

        clear_screen();
        print_credits();

        press_enter("\nPress the Enter key to read the instructions. ");
        clear_screen();
        print_instructions();

        press_enter("\nPress the Enter key to start. ");
        clear_screen();
        play();
};

Math

// Math
//
// Original version in BASIC:
//      Example included in Vintage BASIC 1.0.3.
//      http://www.vintage-basic.net
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02.
//
// Last modified: 20260213T1645+0100.

use bufio;
use fmt;
use math;
use os;
use strconv;
use strings;

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn accept_f64(prompt: str = "") (f64 | ...strconv::error) = {
        const s: str = accept_string(prompt);
        defer free(s);
        return strconv::stof64(s);
};

fn accept_valid_f64(prompt: str = "", error: str = "Real number expected.") f64 = {
        for (true) {
                match (accept_f64(prompt)) {
                case let n: f64 =>
                        return n;
                case =>
                        fmt::println(error)!;
                };
        };
};

export fn main() void = {
        fmt::println()!;
        const n: f64 = accept_valid_f64("Enter a number: ");

        fmt::printfln("ABS({0}) -> math::abs({0}) -> {1}", n, math::absf64(n))!;
        fmt::printfln("ATN({0}) -> math::atan({0}) -> {1}", n, math::atanf64(n))!;
        fmt::printfln("COS({0}) -> math::cos_f64({0}) -> {1}", n, math::cosf64(n))!;
        fmt::printfln("EXP({0}) -> math::expf64({0}) -> {1}", n, math::expf64(n))!;
        fmt::printfln("INT({0}) -> ({0}): int -> {1}", n, n: int)!;
        fmt::printfln("LOG({0}) -> math::log_f64({0}) -> {1}", n, math::logf64(n))!;
        fmt::printfln(
                "SGN({0}) -> if ({0} == 0.0) 0 else math::sign_f64({0}) -> {1}",
                n,
                if (n == 0.0) 0 else math::signf64(n)
        )!;
        fmt::printfln("SQR({0}) -> math::sqrt_f64({0}) -> {1}", n, math::sqrtf64(n))!;
        fmt::printfln("TAN({0}) -> math::tan_f64({0}) -> {1}", n, math::tanf64(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 Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-14/15.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math;
use math::random;
use os;
use strconv;
use strings;
use time;

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

def GRID_SIZE = 10;
def TURNS = 10;
def MUGWUMPS = 4;

type Mugwump = struct {
        x: int,
        y: int,
        hidden: bool,

};

let mugwump: [MUGWUMPS]Mugwump = [Mugwump{x = 0, y = 0, hidden = false}...];
let found: int = 0; // counter

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn accept_integer() (int | ...strconv::error) = {
        const s = accept_string();
        defer free(s);
        return strconv::stoi(s);
};

fn prompted_integer(prompt: str) (int | ...strconv::error) = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
        return accept_integer();
};

fn prompted_valid_integer(prompt: str, error: str = "Integer expected.") int = {
        let result: int = 0;
        for (true) {
                match (prompted_integer(prompt)) {
                case let i: int =>
                        result = i;
                        break;
                case =>
                        fmt::println(error)!;
                };
        };
        return result;
};

// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "ok", "y", "yeah", "yes" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "n", "no", "nope" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt: str) bool = {
        for (true) {
                let answer = accept_string(prompt);
                defer free(answer);
                if (is_yes(answer)) {
                        return true;
                };
                if (is_no(answer)) {
                        return false;
                };
        };
};

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

// Clear the screen, print the credits and ask the user to press enter.
//
fn print_credits() void = {
        clear_screen();
        fmt::println("Mugwump\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).")!;
        fmt::println("    Slightly modified by Bob Albrecht of People's Computer Company.")!;
        fmt::println("    Published by Creative Computing (Morristown, New Jersey, USA), 1978.")!;
        fmt::println("    - https://www.atariarchives.org/basicgames/showpage.php?page=114")!;
        fmt::println("    - http://vintage-basic.net/games.html\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        accept_string("Press Enter to read the instructions. ");
};

// Clear the screen, print the instructions and ask the user to press enter.
//
fn print_instructions() void = {
        clear_screen();
        fmt::println("Mugwump\n")!;
        fmt::println("The object of this game is to find four mugwumps")!;
        fmt::println("hidden on a 10 by 10 grid.  Homebase is position 0,0.")!;
        fmt::println("Any guess you make must be two numbers with each")!;
        fmt::println("number between 0 and 9, inclusive.  First number")!;
        fmt::println("is distance to right of homebase and second number")!;
        fmt::println("is distance above homebase.\n")!;
        fmt::printfln("You get {} tries.  After each try, you will see", TURNS)!;
        fmt::println("how far you are from each mugwump.\n")!;
        accept_string("Press Enter to start. ");
};

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

// Init the mugwumps' positions, `hidden` flags and count.
//
fn hide_mugwumps() void = {
        for (let m = 0; m < MUGWUMPS; m += 1) {
                mugwump[m].x = random::u64n(&rand, GRID_SIZE): int;
                mugwump[m].y = random::u64n(&rand, GRID_SIZE): int;
                mugwump[m].hidden = true;
        };
        found = 0; // counter
};

// Print the given prompt, wait until the user enters a valid coord and return
// it.
//
fn get_coord(prompt: str) int = {
        let coord: int = 0;
        for (true) {
                coord = prompted_valid_integer(prompt);
                if (coord < 0 || coord >= GRID_SIZE) {
                        fmt::printfln("Invalid value {}: not in range [0, {}].", coord, GRID_SIZE - 1)!;
                } else {
                        break;
                };
        };
        return coord;
};

// Return `true` if the given mugwump is hidden in the given coords.
//
fn is_here(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.
//
fn distance(m: int, x: int, y: int) int = {
        return math::sqrtf64(
                math::powf64((mugwump[m].x - x): f64, 2.0) +
                math::powf64((mugwump[m].y - y): f64, 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).
//
fn plural(n: int, plural_suffix: str = "s", singular_suffix: str = "") str = {
        return if (n > 1) plural_suffix else singular_suffix;
};

// Run the game.
//
fn play() void = {
        let x: int = 0;
        let y: int = 0;
        let turn: int = 0; // counter

        for (true) { // game

                clear_screen();
                hide_mugwumps();

                for :turns_loop (let turn = 1; turn <= TURNS; turn += 1) {

                        fmt::printfln("Turn number {}\n", turn)!;
                        fmt::printfln("What is your guess (in range [0, {}])?", GRID_SIZE - 1)!;
                        x = get_coord("Distance right of homebase (x-axis): ");
                        y = get_coord("Distance above homebase (y-axis): ");
                        fmt::printfln("\nYour guess is ({}, {}).", x, y)!;

                        for (let m = 0; m < MUGWUMPS; m += 1) {
                                if (is_here(m, x, y)) {
                                        mugwump[m].hidden = false;
                                        found += 1;
                                        fmt::printfln("You have found mugwump {}!", m)!;
                                        if (found == MUGWUMPS) {
                                                break :turns_loop;
                                        };
                                };
                        };

                        for (let m = 0; m < MUGWUMPS; m += 1) {
                                if (mugwump[m].hidden) {
                                        fmt::printfln("You are {} units from mugwump {}.", distance(m, x, y) , m)!;
                                };
                        };
                        fmt::println()!;

                }; // turns

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

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

        }; // game
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn init() void = {
        randomize();
};

export fn main() void = {
        init();
        print_credits();
        print_instructions();
        play();
};

Name

// Name
//
// Original version in BASIC:
//      Example included in Vintage BASIC 1.0.3.
//      http://www.vintage-basic.net
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-14/15.
//
// Last modified: 20260213T1645+0100.

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

use bufio;
use fmt;
use os;
use strconv;
use strings;

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

fn print_prompt(prompt: str) void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn accept_integer(prompt: str = "") (int | ...strconv::error) = {
        const string: str = accept_string(prompt);
        defer free(string);
        return strconv::stoi(string);
};

fn accept_valid_integer(prompt: str = "", error: str = "Integer expected.") int = {
        for (true) {
                match (accept_integer(prompt)) {
                case let number: int =>
                        return number;
                case =>
                        fmt::println(error)!;
                };
        };
};

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

export fn main() void = {
        const name: str = accept_string("What is your name? ");
        defer free(name);
        const number: int = accept_valid_integer("Enter a number: ");

        for (let i = 0; i < (number): int; i += 1) {
                fmt::printfln("Hello, {}!", 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 Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-15.
//
// Last modified: 20260213T1645+0100.

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

use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

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

// Print the title at the current cursor position.
//
fn print_title() void = {
        set_style(TITLE_INK);
        fmt::println("Poetry")!;
        set_style(DEFAULT_INK);
};

// Print the credits at the current cursor position.
//
fn print_credits() void = {
        print_title();
        fmt::println("\nOriginal version in BASIC:")!;
        fmt::println("    Unknown author.")!;
        fmt::println("    Published in \"BASIC Computer Games\",")!;
        fmt::println("    Creative Computing (Morristown, New Jersey, USA), 1978.\n")!;

        fmt::println("This improved remake in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair")!;
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

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

// Is the given integer even?
//
fn is_even(n: int) bool = {
        return n % 2 == 0;
};

fn play() void = {
        randomize();

        def MAX_PHRASES_AND_VERSES = 20;

        // counters:
        let action = 0;
        let phrase = 0;
        let phrases_and_verses = 0;
        let verse_chunks = 0;

        for :verse (true) {

                let manage_the_verse_continuation = true;
                let maybe_add_comma = true;

                switch (action) {
                case 0, 1 =>
                        switch (phrase) {
                        case 0 => fmt::print("MIDNIGHT DREARY")!;
                        case 1 => fmt::print("FIERY EYES")!;
                        case 2 => fmt::print("BIRD OR FIEND")!;
                        case 3 => fmt::print("THING OF EVIL")!;
                        case 4 => fmt::print("PROPHET")!;
                        case => void;
                        };
                case 2 =>
                        switch (phrase) {
                        case 0 =>
                                fmt::print("BEGUILING ME")!;
                                verse_chunks = 2;
                        case 1 =>
                                fmt::print("THRILLED ME")!;
                        case 2 =>
                                fmt::print("STILL SITTING…")!;
                                maybe_add_comma = false;
                        case 3 =>
                                fmt::print("NEVER FLITTING")!;
                                verse_chunks = 2;
                        case 4 =>
                                fmt::print("BURNED")!;
                        case =>
                                void;
                        };
                case 3 =>
                        switch (phrase) {
                        case 0 =>
                                fmt::print("AND MY SOUL")!;
                        case 1 =>
                                fmt::print("DARKNESS THERE")!;
                        case 2 =>
                                fmt::print("SHALL BE LIFTED")!;
                        case 3 =>
                                fmt::print("QUOTH THE RAVEN")!;
                        case 4 =>
                                if (verse_chunks != 0) {
                                        fmt::print("SIGN OF PARTING")!;
                                };
                        case =>
                                void;
                        };
                case 4 =>
                        switch (phrase) {
                        case 0 => fmt::print("NOTHING MORE")!;
                        case 1 => fmt::print("YET AGAIN")!;
                        case 2 => fmt::print("SLOWLY CREEPING")!;
                        case 3 => fmt::print("…EVERMORE")!;
                        case 4 => fmt::print("NEVERMORE")!;
                        case => void;
                        };
                case 5 =>
                        action = 0;
                        fmt::println()!;
                        if (phrases_and_verses > MAX_PHRASES_AND_VERSES) {
                                fmt::println()!;
                                verse_chunks = 0;
                                phrases_and_verses = 0;
                                action = 2;
                                continue :verse;
                        } else {
                                manage_the_verse_continuation = false;
                        };
                case =>
                        void;
                };

                if (manage_the_verse_continuation) {

                        time::sleep(250 * time::MILLISECOND);

                        if (maybe_add_comma && !(verse_chunks == 0 || random::f64rand(&rand) > 0.19)) {
                                fmt::print(",")!;
                                verse_chunks = 2;
                        };

                        if (random::f64rand(&rand) > 0.65) {
                                fmt::println()!;
                                verse_chunks = 0;
                        } else {
                                fmt::print(" ")!;
                                verse_chunks += 1;
                        };

                };

                action += 1;
                phrase = random::u32n(&rand, 5): int;
                phrases_and_verses += 1;

                if (!(verse_chunks > 0 || is_even(action))) {
                        fmt::print("     ")!;
                };

        }; // verse loop
};

export fn main() void = {
        clear_screen();
        print_credits();
        press_enter("\nPress the Enter key to start. ");
        clear_screen();
        play();
};

Russian Roulette

// Russian Roulette
//
// Original version in BASIC:
//      Creative Computing (Morristown, New Jersey, USA), ca. 1980.
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-13, 2025-02-15.
//
// Last modified: 20260213T1645+0100.

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

use bufio;
use fmt;
use math::random;
use os;
use strings;
use time;

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

fn press_enter_to_start() void = {
        press_enter("Press Enter to start. ");
};

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

fn print_credits() void = {
        clear_screen();
        fmt::println("Russian Roulette\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter_to_start();
};

fn print_instructions() void = {
        clear_screen();
        fmt::println("Here is a revolver.")!;
        fmt::println("Type 'f' to spin chamber and pull trigger.")!;
        fmt::println("Type 'g' to give up, and play again.")!;
        fmt::println("Type 'q' to quit.\n")!;
};

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

fn play() void = {
        let times = 0;
        for (true) { // game loop
                print_instructions();
                times = 0;
                for :play_loop (true) {
                        let command = accept_string("> ");
                        defer free(command);
                        switch (command) {
                        case "f" => // fire
                                if (random::u32n(&rand, 100) > 83) {
                                        fmt::println("Bang! You're dead!")!;
                                        fmt::println("Condolences will be sent to your relatives.")!;
                                        break :play_loop;
                                } else {
                                        times += 1;
                                        if (times > 10) {
                                                fmt::println("You win!")!;
                                                fmt::println("Let someone else blow his brains out.")!;
                                                break :play_loop;
                                        } else {
                                                fmt::println("Click.")!;
                                        };
                                };
                        case "g" => // give up
                                fmt::println("Chicken!")!;
                                break :play_loop;
                        case "q" => // quit
                                return;
                        case =>
                                continue;
                        };
                }; // play loop
                press_enter_to_start();
        }; // game loop
};

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn init() void = {
        randomize();
};

fn bye() void = {
        fmt::println("Bye!")!;
};

export fn main() void = {
        init();
        print_credits();
        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 Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-17.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_cursor_position(line: int, column: int) void = {
        fmt::printf("\x1B[{};{}H", line, column)!;
};

fn hide_cursor() void = {
    fmt::print("\x1B[?25l")!;
};

fn show_cursor() void = {
    fmt::print("\x1B[?25h")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_line_to_end() void = {
        fmt::print("\x1B[K")!;
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

def TITLE = "Seance";

def MAX_SCORE = 50;

def MAX_MESSAGE_LENGTH: int = 6;
def MIN_MESSAGE_LENGTH: int = 3;

def BASE_CHARACTER = '@': int;
def PLANCHETTE = '*';

def FIRST_LETTER_NUMBER = 1;
def LAST_LETTER_NUMBER = 26;

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

def INPUT_X: int = BOARD_X;
def INPUT_Y: int = BOARD_Y + BOARD_BOTTOM_Y + 4;

def MESSAGES_Y = INPUT_Y;

def MISTAKE_EFFECT_PAUSE = 3; // seconds

def BOARD_ACTUAL_WIDTH = BOARD_WIDTH + 2 * BOARD_PAD; // screen columns
def BOARD_BOTTOM_Y = BOARD_HEIGHT + 1; // relative to the board
def BOARD_HEIGHT = 5; // characters displayed on the left and right borders
def BOARD_PAD = 1; // blank characters separating the board from its left and right borders
def BOARD_WIDTH = 8; // characters displayed on the top and bottom borders
def BOARD_X = 29; // screen column
def BOARD_Y = 5; // screen line

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return  strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str) void = {
        free(accept_string(prompt));
};

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

fn print_credits() void = {
        print_title();
        fmt::println("\nOriginal version in BASIC:")!;
        fmt::println("    Written by Chris Oxlade, 1983.")!;
        fmt::println("    https://archive.org/details/seance.qb64")!;
        fmt::println("    https://github.com/chaosotter/basic-games")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair")!;
};

fn print_instructions() void = {
        print_title();
        set_style(INSTRUCTIONS_INK);
        defer set_style(DEFAULT_INK);
        fmt::println("\nMessages from the Spirits are coming through, letter by letter.  They want you")!;
        fmt::println("to remember the letters and type them into the computer in the correct order.")!;
        fmt::println("If you make mistakes, they will be angry -- very angry...")!;
        fmt::println()!;
        fmt::println("Watch for stars on your screen -- they show the letters in the Spirits'")!;
        fmt::println("messages.")!;
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn random_int_in_inclusive_range(min: int, max: int) int = {
        return random::u32n(&rand, (max - min):u32): int + min;
};

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

// Return the x coordinate to print the given text centered on the board.
//
fn board_centered_x(text: str) int = {
        return (BOARD_X + (BOARD_ACTUAL_WIDTH - len(text)) / 2): int;
};

// Print the given text on the given row, centered on the board.
//
fn print_board_centered(text: str, y: int) void = {
        set_cursor_position(y, board_centered_x(text));
        fmt::println(text)!;
};

// Print the title at the current cursor position.
//
fn print_title() void = {
        set_style(TITLE_INK);
        fmt::println(TITLE)!;
        set_style(DEFAULT_INK);
};

// Print the title on the given row, centered on the board.
//
fn print_board_centered_title(y: int) void = {
        set_style(TITLE_INK);
        print_board_centered(TITLE, y);
        set_style(DEFAULT_INK);
};

// Print the given letter at the given board coordinates.
//
fn print_character(y: int, x: int, a: rune) void = {
        set_cursor_position(y + BOARD_Y, x + BOARD_X);
        fmt::print(a)!;
        bufio::flush(os::stdout)!;
};

fn print_board() void = {
        set_style(BOARD_INK);
        defer set_style(DEFAULT_INK);
        for (let i = 1; i <= BOARD_WIDTH; i += 1) {
                print_character(0, i + 1, (BASE_CHARACTER + i): rune); // top border
                print_character(BOARD_BOTTOM_Y, i + 1, (BASE_CHARACTER + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1): rune); // bottom border
        };
        for (let i = 1; i <= BOARD_HEIGHT; i += 1) {
                print_character(i , 0, (BASE_CHARACTER + LAST_LETTER_NUMBER - i + 1): rune); // left border
                print_character(i , 3 + BOARD_WIDTH, (BASE_CHARACTER + BOARD_WIDTH + i): rune); // right border
        };
        fmt::println()!;
};

fn erase_line_from(line: int, column: int) void = {
        set_cursor_position(line, column);
        erase_line_to_end();
};

fn wait_seconds(seconds: int) void = {
        time::sleep(seconds * time::SECOND);
};

// Print the given mistake effect, wait a configured number of seconds and
// finally erase it.
//
fn print_mistake_effect(effect: str) void = {
        let x = board_centered_x(effect);
        hide_cursor();
        set_cursor_position(MESSAGES_Y, x);
        set_style(MISTAKE_EFFECT_INK);
        fmt::println(effect)!;
        set_style(DEFAULT_INK);
        wait_seconds(MISTAKE_EFFECT_PAUSE);
        erase_line_from(MESSAGES_Y, x);
        show_cursor();
};

// Return a new message of the given length, after marking its letters on the
// board.
//
fn message(length: int) str = {
        let y: int = 0;
        let x: int = 0;
        let letters: []rune = [];
        hide_cursor();
        for (let i = 0; i <= length; i += 1) {
                let letter_number: int = random_int_in_inclusive_range(
                        FIRST_LETTER_NUMBER,
                        LAST_LETTER_NUMBER
                        );
                append(letters, (BASE_CHARACTER + letter_number): rune)!;
                if (letter_number <= BOARD_WIDTH) {
                        // top border
                        y = 1;
                        x = letter_number + 1;
                } else if (letter_number <= BOARD_WIDTH + BOARD_HEIGHT) {
                        // right border
                        y = letter_number - BOARD_WIDTH;
                        x = 2 + BOARD_WIDTH;
                } else if (letter_number <= BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH) {
                        // bottom border
                        y = BOARD_BOTTOM_Y - 1;
                        x = 2 + BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH - letter_number;
                } else {
                        // left border
                        y = 1 + LAST_LETTER_NUMBER - letter_number;
                        x = 1;
                };
                set_style(PLANCHETTE_INK);
                print_character(y, x, PLANCHETTE);
                set_style(DEFAULT_INK);
                wait_seconds(1);
                print_character(y, x, ' ');

        };
        show_cursor();
        return strings::fromrunes(letters)!;
};

fn accept_message() str = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        set_cursor_position(INPUT_Y, INPUT_X);
        defer erase_line_from(INPUT_Y, INPUT_X);
        return ascii::strupper(accept_string("? "))!;
};

fn play() void = {
        let score = 0;
        let mistakes = 0;
        print_board_centered_title(1);
        print_board();

        for (true) {
                let message_length = random_int_in_inclusive_range(
                        MIN_MESSAGE_LENGTH,
                        MAX_MESSAGE_LENGTH
                        );
                let message_received = message(message_length);
                defer free(message_received);
                let message_understood = accept_message();
                defer free(message_understood);
                if (message_received != message_understood) {
                        mistakes += 1;
                        switch (mistakes) {
                        case 1 =>
                                print_mistake_effect("The table begins to shake!");
                        case 2 =>
                                print_mistake_effect("The light bulb shatters!");
                        case 3 =>
                                print_mistake_effect("Oh, no!  A pair of clammy hands grasps your neck!");
                                return;
                        case =>
                                void;
                        };
                } else {
                        score += message_length;
                        if (score >= MAX_SCORE) {
                                print_board_centered("Whew!  The spirits have gone!", MESSAGES_Y);
                                print_board_centered("You live to face another day!", MESSAGES_Y + 1);
                                return;
                        };
                };
        };
};

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

export fn main() void = {
        randomize();

        set_style(DEFAULT_INK);
        clear_screen();
        print_credits();

        press_enter("\nPress the Enter key to read the instructions. ");
        clear_screen();
        print_instructions();

        press_enter("\nPress the Enter key to start. ");
        clear_screen();
        play();
        fmt::println()!;
};

Sine Wave

// Sine Wave
//
// Original version in BASIC:
//      Creative Computing (Morristown, New Jersey, USA), ca. 1980.
//
// This version in Hare:
//      Copyright (c) 2024, 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2024-12-15, 2025-02-12, 2025-02-15.
//
// Last modified: 20260213T1645+0100.

use bufio;
use encoding::utf8;
use fmt;
use io;
use math;
use os;
use strings;

fn clear_screen() void = {
        fmt::print("\x1B[0;0H\x1B[2J")!;
};

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str = "") void = {
        free(accept_string(prompt));
};

fn get_words() [2]str = {
        clear_screen();

        let word: [2]str = ["", ""];
        let order: [2]str = ["first", "second"];

        for (let n = 0; n <= 1; n += 1) {
                for (word[n] == "") {
                        fmt::printfln("Enter the {} word: ", order[n])!;
                        word[n] = accept_string();
                };
        };

        return word;
};

fn print_credits() void = {
        clear_screen();
        fmt::println("Sine Wave\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2024, 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter("Press Enter to start the program. ");
};

fn bool_to_int(flag: bool) int = {
        return if (flag) 1 else 0;
};

fn draw(word: [2]str) void = {
        clear_screen();
        let even = false;
        for (let angle = 0.0; angle <= 40.0; angle += 0.25) {
                let spaces = (26.0 + math::floorf64(25.0 * math::sinf64(angle))): int;
                for (let i = 0; i < spaces; i += 1) {
                        fmt::print(' ')!;
                };
                fmt::println(word[bool_to_int(even)])!;
                even = !even;
        };
};

export fn main() void = {
        print_credits();
        draw(get_words());
};

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 Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written on 2025-02-15, 2025-04-07.
//
// Last modified: 20260213T1645+0100.

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

use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn hide_cursor() void = {
        fmt::print("\x1B[?25l")!;
};

fn show_cursor() void = {
        fmt::print("\x1B[?25h")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

def REELS = 3;

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

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn press_enter(prompt: str) void = {
        free(accept_string(prompt));
};

fn accept_integer() (int | ...strconv::error) = {
        const s = accept_string();
        defer free(s);
        return strconv::stoi(s);
};

fn prompted_integer(prompt: str) (int | ...strconv::error) = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
        return accept_integer();
};

fn prompted_integer_or_0(prompt: str) int = {
        match (prompted_integer(prompt)) {
        case let i: int =>
                return i;
        case =>
                return 0;
        };
};

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

fn print_credits() void = {
        clear_screen();
        fmt::println("Slots")!;
        fmt::println("A slot machine simulation.\n")!;
        fmt::println("Original version in BASIC:")!;
        fmt::println("    Creative computing (Morristown, New Jersey, USA).")!;
        fmt::println("    Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n")!;
        fmt::println("This version in Hare:")!;
        fmt::println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
        fmt::println("    SPDX-License-Identifier: Fair\n")!;
        press_enter("Press Enter for instructions. ");
};

fn print_instructions() void = {
        clear_screen();
        fmt::println("You are in the H&M casino, in front of one of our")!;
        fmt::printf("one-arm bandits. Bet from {} to {} USD (or 0 to quit).\n\n",
                MIN_BET, MAX_BET)!;
        press_enter("Press Enter to start. ");
};

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

fn won(prize: int, bet: int) int = {
        switch (prize) {
        case   2 => fmt::println("DOUBLE!")!;
        case   5 => fmt::println("*DOUBLE BAR*")!;
        case  10 => fmt::println("**TOP DOLLAR**")!;
        case 100 => fmt::println("***JACKPOT***")!;
        case     => void ;
        };
        fmt::println("You won!")!;
        return (prize + 1) * bet;
};

fn show_standings(usd: int) void = {
        fmt::printfln("Your standings are {} USD.", usd)!;
};

fn print_reels(reel: *[REELS]int) void = {
        move_cursor_home();
        for (let r .. reel) {
                set_style(color[r]);
                fmt::printf("[{}] ", image[r])!;
        };
        set_style(NORMAL_STYLE);
        fmt::println("")!;
};

fn init_reels(reel: *[REELS]int) void = {
        let images = (len(image)): int;
        for (let i = 0; i < len(reel): int; i += 1) {
                reel[i] = random::u32n(&rand, images: u32): int;
        };
};

fn spin_reels(reel: *[REELS]int) void = {
        def DURATION_IN_SECONDS = 2;
        hide_cursor();
        let first_second = time::now(time::clock::MONOTONIC).sec;
        for ((time::now(time::clock::MONOTONIC).sec - first_second) < DURATION_IN_SECONDS) {
                init_reels(reel);
                print_reels(reel);
        };
        show_cursor();
};

fn bool_to_int(flag: bool) int = {
        return if (flag) 1 else 0;
};

// Return the number of equals and bars in the given `reel` array.
//
fn prize(reel: *[REELS]int) (int, int) = {
        // Count the number of equals
        let equals: int = 0;
        for (let first = 0; first < len(reel): int; first += 1) {
                for (let second = first + 1; second < len(reel): int; second += 1) {
                        equals += bool_to_int(reel[first] == reel[second]);
                };
        };
        equals += bool_to_int(equals > 0 && equals < len(reel): int);

        // Count the number of bars
        let bars: int = 0;
        for (let i = 0; i < len(reel): int; i += 1) {
                bars += bool_to_int(reel[i] == BAR);
        };

        return (equals, bars);
};

fn play() void = {
        let standings = 0;
        let bet = 0;
        let reel: [REELS]int = [0, 0, 0];
        let equals = 0;
        let bars = 0;
        init_reels(&reel);
        for :play_loop (true) {
                for :bet_loop (true) {
                        clear_screen();
                        print_reels(&reel);
                        bet = prompted_integer_or_0("Your bet (or 0 to quit): ");
                        if (bet > MAX_BET) {
                                fmt::printfln("House limits are {} USD.", MAX_BET)!;
                                press_enter("Press Enter to try again. ");
                        } else if (bet < MIN_BET) {
                                let confirmation = accept_string("Type \"q\" to confirm you want to quit. ");
                                defer free(confirmation);
                                if (confirmation == "q" || confirmation == "Q") {
                                        break :play_loop;
                                };
                        } else {
                                break :bet_loop;
                        }; // bet check
                }; // bet loop
                clear_screen();
                spin_reels(&reel);
                let prize_details = prize(&reel);
                equals = prize_details.0;
                bars = prize_details.1;
                switch (equals) {
                case 3 =>
                        if (bars == 3) {
                                standings += won(100, bet);
                        } else {
                                standings += won(10, bet);
                        };
                case 2 =>
                        if (bars == 2) {
                                standings += won(5, bet);
                        } else {
                                standings += won(2, bet);
                        };
                case =>
                        fmt::println("You lost.")!;
                        standings -= bet;
                }; // prize check
                show_standings(standings);
                press_enter("Press Enter to continue. ");
        }; // play loop
        show_standings(standings);
        if (standings < 0) {
                fmt::println("Pay up!  Please leave your money on the terminal.")!;
        } else if (standings > 0) {
                fmt::println("Collect your winnings from the H&M cashier.")!;
        } else {
                fmt::println("Hey, you broke even.")!;
        };
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

fn init() void = {
        randomize();
};

export fn main() void = {
        init();
        print_credits();
        print_instructions();
        play();
};

Stars

// Stars
//
// Original version in BASIC:
//      Example included in Vintage BASIC 1.0.3.
//      http://www.vintage-basic.net
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-14/15.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use os;
use strconv;
use strings;

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

fn print_prompt(prompt: str) void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn accept_integer() (int | ...strconv::error) = {
        const s = accept_string();
        defer free(s);
        return strconv::stoi(s);
};

fn prompted_integer(prompt: str) (int | ...strconv::error) = {
        print_prompt(prompt);
        return accept_integer();
};

fn prompted_valid_integer(prompt: str, error: str = "Integer expected.") int = {
        let result: int = 0;
        for (true) {
                match (prompted_integer(prompt)) {
                case let i: int =>
                        result = i;
                        break;
                case =>
                        fmt::println(error)!;
                };
        };
        return result;
};

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

fn is_yes(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "ok", "y", "yeah", "yes" =>
                        yield true;
                case =>
                        yield false;
        };
};

fn repeat(s: str, n: int) str = {
        let result = "";
        for (let i: int = 0; i < n; i += 1) {
                result = strings::concat(result, s)!;
        };
        return result;
};

export fn main() void = {
        const name: str = accept_string("What is your name? ");
        defer free(name);
        fmt::printfln("Hello, {}.", name)!;

        let number: int = 0;
        for (true) {
                number = prompted_valid_integer("How many stars do you want? ");
                let stars = repeat("*", number);
                defer free(stars);
                fmt::println(stars)!;

                print_prompt("Do you want more stars? ");
                let answer: str = accept_string();
                defer free(answer);
                if (!is_yes(answer)) {
                        break;
                };
        };
};

Strings

// Strings
//
// Original version in BASIC:
//      Example included in Vintage BASIC 1.0.3.
//      http://www.vintage-basic.net
//
// This version in Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-14/15.
//
// Last modified: 20260213T1645+0100.

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

use bufio;
use fmt;
use os;
use strconv;
use strings;

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

fn print_prompt(prompt: str) void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn accept_integer() (int | ...strconv::error) = {
        const s = accept_string();
        defer free(s);
        return strconv::stoi(s);
};

fn prompted_integer(prompt: str) (int | ...strconv::error) = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
        return accept_integer();
};

fn prompted_valid_integer(prompt: str, error: str = "Integer expected.") int = {
        let result: int = 0;
        for (true) {
                match (prompted_integer(prompt)) {
                case let i: int =>
                        result = i;
                        break;
                case =>
                        fmt::println(error)!;
                };
        };
        return result;
};

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

fn repeat(s: str, n: int) str = {
        let result = "";
        for (let i: int = 0; i < n; i += 1) {
                result = strings::concat(result, s)!;
        };
        return result;
};

export fn main() void = {
        let s = accept_string("Enter a string: ");
        defer free(s);
        let n = prompted_valid_integer("Enter an integer: ");

        fmt::println()!;

        fmt::printf("ASC(\"{}\") --> ", s)!;
        fmt::printf("strings::toutf8(\"{}\"[0]): int --> ", s)!;
        fmt::printfln("{}", strings::toutf8(s)[0]: int)!;

        fmt::printf("CHR$({}) --> ", n)!;
        fmt::printfln("{}: rune --> '{}'", n, n: rune)!;

        // XXX FIXME When `n` is greater than the string length, the behaviour
        // is different from the original `LEFT$`, `MID$` and `RIGHT$`.

        fmt::printf("LEFT$(\"{}\", {}) --> ", s, n)!;
        fmt::printf("strings::sub(\"{}\", 0, {}: size) --> ", s, n)!;
        fmt::printfln("\"{}\"", strings::sub(s, 0, n: size))!;

        fmt::printf("MID$(\"{}\", {}) --> ", s, n)!;
        fmt::printf("strings::sub(\"{0}\", ({1} - 1): size, len(\"{0}\")) --> ", s, n)!;
        fmt::printfln("\"{}\"", strings::sub(s, (n - 1): size, len(s)))!;

        fmt::printf("MID$(\"{}\", {}, 3) --> ", s, n)!;
        fmt::printf("strings::sub(\"{0}\", ({1} - 1): size, ({1} - 1 + 3): size) --> ", s, n)!;
        fmt::printfln("\"{}\"", strings::sub(s, (n - 1): size, (n - 1 + 3): size))!;

        fmt::printf("RIGHT$(\"{}\", {}) --> ", s, n)!;
        fmt::printf("strings::sub(\"{0}\", len(\"{0}\") - {1}: size, len(\"{0}\")) --> ", s, n)!;
        fmt::printfln("\"{}\"", strings::sub(s, len(s) - n: size, len(s)))!;

        fmt::printf("LEN(\"{}\") --> ", s)!;
        fmt::printfln("len(\"{}\") --> {}", s, len(s))!;

        fmt::printf("VAL(\"{}\") --> ", s)!;
        fmt::printf("match (strconv::stof64(\"{}\")) {{ case let i: int => yield i; case => yield 0; }}", s)!;
        fmt::printfln(" --> {}",
                match (strconv::stof64(s)) {
                case let i: f64 =>
                        yield i;
                case =>
                        yield 0;
                }
        )!;

        fmt::printf("STR$({}) --> ", n)!;
        fmt::printfln("fmt::asprint({}) --> \"{}\"", n, fmt::asprint(n)!)!;

        fmt::printf("SPC({}) --> ", n)!;
        fmt::printfln("repeat(\" \", {}) --> \"{}\"", n, repeat(" ", n))!;
        fmt::printf("NB: `repeat` is an ad hoc function.")!;
};

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 Hare:
//      Copyright (c) 2025, Marcos Cruz (programandala.net)
//      SPDX-License-Identifier: Fair
//
// Written in 2025-02-17/18.
//
// Last modified: 20260213T1645+0100.

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

use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strconv;
use strings;
use time;

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

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

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

def NORMAL_STYLE = 0;

fn move_cursor_home() void = {
        fmt::print("\x1B[H")!;
};

fn set_cursor_position(line: int, column: int) void = {
        fmt::printf("\x1B[{};{}H", line, column)!;
};

fn set_cursor_coord(coord: (int, int)) void = {
        const (line, column) = coord;
        set_cursor_position(line, column);
};

fn hide_cursor() void = {
        fmt::print("\x1B[?25l")!;
};

fn show_cursor() void = {
        fmt::print("\x1B[?25h")!;
};

fn set_style(style: int) void = {
        fmt::printf("\x1B[{}m", style)!;
};

fn reset_attributes() void = {
        set_style(NORMAL_STYLE);
};

fn erase_line_to_end() void = {
        fmt::print("\x1B[K")!;
};

fn erase_screen_to_end() void = {
        fmt::print("\x1B[J")!;
};

fn erase_screen() void = {
        fmt::print("\x1B[2J")!;
};

fn clear_screen() void = {
        erase_screen();
        reset_attributes();
        move_cursor_home();
};

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

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

def BLANK = "*";

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

def CELLS = GRID_WIDTH * GRID_HEIGHT;

const pristine_grid: [CELLS]str = [""...];

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

def FIRST_PLAYER = 0;
def MAX_PLAYERS = 4;

let grid: [MAX_PLAYERS][CELLS]str = [[""...]...];

let is_playing: [MAX_PLAYERS]bool = [false...];

let players: int = 0;

def QUIT_COMMAND = "X";

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

fn print_prompt(prompt: str = "") void = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
};

fn accept_string(prompt: str = "") str = {
        print_prompt(prompt);
        const buffer = match (bufio::read_line(os::stdin)!) {
                case let buffer: []u8 =>
                        yield buffer;
                case =>
                        return "";
                };
        defer free(buffer);
        return strings::dup(strings::fromutf8(buffer)!)!;
};

fn accept_integer() (int | ...strconv::error) = {
        const s = accept_string();
        defer free(s);
        return strconv::stoi(s);
};

fn prompted_integer(prompt: str) (int | ...strconv::error) = {
        fmt::print(prompt)!;
        bufio::flush(os::stdout)!;
        return accept_integer();
};

fn prompted_integer_or_0(prompt: str) int = {
        match (prompted_integer(prompt)) {
        case let i: int =>
                return i;
        case =>
                return 0;
        };
};

fn get_integer(prompt: str = "") int = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return prompted_integer_or_0(prompt);
};

fn get_string(prompt: str = "") str = {
        set_style(INPUT_INK);
        defer set_style(DEFAULT_INK);
        return accept_string(prompt);
};

fn press_enter(prompt: str) void = {
        free(accept_string(prompt));
};

// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "ok", "y", "yeah", "yes" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s: str) bool = {
        const lowercase_s = ascii::strlower(s)!;
        defer free(lowercase_s);
        return switch(lowercase_s) {
                case "n", "no", "nope" =>
                        yield true;
                case =>
                        yield false;
        };
};

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt: str) bool = {
        for (true) {
                let answer = get_string(prompt);
                if (is_yes(answer)) {
                        return true;
                };
                if (is_no(answer)) {
                        return false;
                };
        };
};

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

fn print_title() void = {
        set_style(TITLE_INK);
        fmt::println("Xchange")!;
        set_style(DEFAULT_INK);
};

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

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

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

// Print the given player's grid title.
//
fn print_grid_title(player: int) void = {
        set_cursor_position(GRIDS_ROW, GRIDS_COLUMN + (player * GRIDS_GAP));
        fmt::printf("Player {}", player + 1)!;
};

// Return the cursor position of the given player's grid cell.
//
fn cell_position(player: int, cell: int) (int, int) = {
        const grid_row: int = cell / GRID_HEIGHT;
        const grid_column: int = cell % GRID_WIDTH;
        const title_margin: int = if (players > 1) 2 else 0;
        const row: int = GRIDS_ROW + title_margin + grid_row;
        const column: int = GRIDS_COLUMN +
                (grid_column * CELLS_GAP) +
                (player * GRIDS_GAP);
        return (row, column);
};

// Return the cursor position of the given player's grid prompt.
//
fn grid_prompt_position_of(player: int) (int, int) = {
        const coord = cell_position(player, CELLS);
        const row = coord.0;
        const column = coord.1;
        return (row + 1, column);
};

// Print the given player's grid, in the given or default color.
//
fn print_grid(player: int, color: int = BOARD_INK) void = {
        if (players > 1) {
                print_grid_title(player);
        };
        set_style(color);
        defer set_style(DEFAULT_INK);
        for (let cell = 0; cell < CELLS; cell += 1) {
                set_cursor_coord(cell_position(player, cell));
                fmt::print(grid[player][cell])!;
        };
};

// Print the current players' grids.
//
fn print_grids() void = {
        for (let player = 0; player < players; player += 1) {
                if (is_playing[player]) {
                        print_grid(player);
                };
        };
        fmt::println()!;
        erase_screen_to_end();
};

// Scramble the grid of the given player.
//
fn scramble_grid(player: int) void = {
        for (let cell = 0; cell < CELLS; cell += 1) {
                const random_cell = random::u64n(&rand, CELLS);
                // Exchange the contents of the current cell with that of the random one.
                const tmp = grid[player][cell];
                grid[player][cell] = grid[player][random_cell];
                grid[player][random_cell] = tmp;
        };
};

// Init the grids.
//
fn init_grids() void = {
        grid[0] = pristine_grid;
        scramble_grid(0);
        for (let player = 1; player < players; player += 1) {
                grid[player] = grid[0];
        };
};

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

// Return a message prefix for the given player.
//
fn player_prefix(player: int) str = {
        return if (players > 1) fmt::asprintf("Player {}: ", player + 1)! else "";
};

// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.
//
fn message_position(player: int, row_inc: int = 0) (int, int) = {
        let (prompt_row, _) = grid_prompt_position_of(player);
        return (prompt_row + 2 + row_inc, 1);
};

// Print the given message about the given player, adding the given row
// increment, which defaults to zero, to the default cursor coordinates.
//
fn print_message(message: str, player: int, row_inc: int = 0) void = {
        set_cursor_coord(message_position(player, row_inc));
        fmt::printf("{}{}", player_prefix(player), message)!;
        erase_line_to_end();
        fmt::println()!;
};

// Erase the last message about the given player.
//
fn erase_message(player: int) void = {
        set_cursor_coord(message_position(player));
        erase_line_to_end();
};

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

// Return a message with the players range.
//
fn players_range_message() str = {
        if (MAX_PLAYERS == 2) {
                return "1 or 2";
        } else {
                return fmt::asprintf("from 1 to {}", MAX_PLAYERS)!;
        };
};

// Return the number of players, asking the user if needed.
//
fn number_of_players() int = {
        let players = 0;
        print_title();
        fmt::println()!;
        if (MAX_PLAYERS == 1) {
                players = 1;
        } else {
                for (players < 1 || players > MAX_PLAYERS) {
                        const prompt = fmt::asprintf("Number of players ({}): ", players_range_message())!;
                        defer free(prompt);
                        players = get_integer(prompt);
                };
        };
        return players;
};

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

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

// Are the given cells adjacent?
//
fn are_cells_adjacent(cell_1: int, cell_2: int) bool = {
        return (cell_2 == cell_1 + 1 && !is_first_cell_of_a_grid_row(cell_2)) ||
                (cell_2 == cell_1 + GRID_WIDTH) ||
                (cell_2 == cell_1 - 1 && !is_last_cell_of_a_grid_row(cell_2)) ||
                (cell_2 == cell_1 - GRID_WIDTH);
};

type invalid = !void;

// 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 the `invalid` type
// error.
//
fn position_to_cell(player: int, char_cell: int) (int | invalid) = {
        for (let cell = 0; cell < CELLS; cell += 1) {
                if (grid[player][cell] == BLANK) {
                        if (are_cells_adjacent(char_cell, cell)) {
                                return cell;
                        } else {
                                break;
                        };
                };
        };
        print_message(fmt::asprintf("Illegal move \"{}\".", grid[player][char_cell])!, player);
        return invalid;
};

// If the given player's command is valid, i.e. a grid character, return its
// position; otherwise return the `invalid` error type.
//
fn command_to_position(player: int, command: str) (int | invalid) = {
        if (command != BLANK) {
                for (let position = 0; position < CELLS; position += 1) {
                        if (command == grid[player][position]) {
                                return position;
                        };
                };
        };
        print_message(fmt::asprintf("Invalid character \"{}\".", command)!, player);
        return invalid;
};

// Forget the given player, who quitted.
//
fn forget_player(player: int) void = {
        is_playing[player] = false;
        print_grid(player, DEFAULT_INK);
};

// Play the turn of the given player.
//
fn play_turn(player: int) void = {
        let blank_position: int = 0;
        let character_position: int = 0;

        if (is_playing[player]) {

                for (true) {
                        for (true) {
                                const coord = grid_prompt_position_of(player);
                                set_cursor_coord(coord);
                                erase_line_to_end();
                                set_cursor_coord(coord);
                                const command = ascii::strupper(strings::trim(get_string("Move: ")))!;
                                defer free(command);
                                if (command == QUIT_COMMAND) {
                                        forget_player(player);
                                        return;
                                };
                                match (command_to_position(player, command)) {
                                case let position: int =>
                                        character_position = position;
                                        break;
                                case =>
                                        void;
                                };
                        };
                        match (position_to_cell(player, character_position)) {
                        case let position: int =>
                                blank_position = position;
                                break;
                        case =>
                                void;
                        };
                };
                erase_message(player);
                grid[player][blank_position] = grid[player][character_position];
                grid[player][character_position] = BLANK;

        };
};

// Play the turns of all players.
//
fn play_turns() void = {
        for (let player = 0; player < players; player += 1) {
                play_turn(player);
        };
};

// Is someone playing?
//
fn is_someone_playing() bool = {
        for (let player = 0; player < players; player += 1) {
                if (is_playing[player]) {
                        return true;
                };
        };
        return false;
};

fn has_an_empty_grid(player: int) bool = {
        for (let i = 0; i < CELLS; i += 1) {
                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`.
//
fn has_someone_won() bool = {
        let winners = 0;
        for (let player = 0; player < players; player += 1) {
                if (is_playing[player]) {
                        if (has_an_empty_grid(player)) {
                                winners += 1;
                                if (winners > 0) {
                                        print_message(
                                                fmt::asprintf("You're the winner{}!", if (winners > 1) ", too" else "")!,
                                                player,
                                                winners - 1);
                                };
                        };
                };
        };
        return winners > 0;
};

// Init the game.
//
fn init_game() void = {
        clear_screen();
        players = number_of_players();
        for (let player = 0; player < players; player += 1) {
                is_playing[player] = true;
        };
        clear_screen();
        print_title();
        init_grids();
        print_grids();
};

// Play the game.
//
fn play() void = {
        init_game();
        for (is_someone_playing()) {
                play_turns();
                print_grids();
                if (has_someone_won()) {
                        break;
                };
        };
};

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

let rand: random::random = 0;

fn randomize() void = {
        rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};

// Init the program, i.e. just once before the first game.
//
fn init_once() void = {
        randomize();

        // Init the pristine grid.
        def FIRST_CHAR_CODE = 'A': int;
        for (let cell = 0; cell < CELLS - 1; cell += 1) {
                pristine_grid[cell] = strings::fromrunes([(FIRST_CHAR_CODE + cell): rune])!;
        };
        pristine_grid[CELLS - 1] = BLANK;
};

// Return `true` if the player does not want to play another game; otherwise
// return `false`.
//
fn enough() bool = {
        set_cursor_coord(grid_prompt_position_of(FIRST_PLAYER));
        return !yes("Another game? ");
};

export fn main() void = {
        init_once();

        clear_screen();
        print_credits();
        press_enter("\nPress the Enter key to read the instructions. ");

        clear_screen();
        print_instructions();
        press_enter("\nPress the Enter key to start. ");

        for (true) {
                play();
                if (enough()) {
                        break;
                };
        };
        fmt::println("So long…")!;
};

Related pages

Basics off
Metaproject about the "Basics of…" projects.
Basics of 8th
Conversion of old BASIC programs to 8th in order to learn the basics of this language.
Basics of Ada
Conversion of old BASIC programs to Ada in order to learn the basics of this language.
Basics of Arturo
Conversion of old BASIC programs to Arturo in order to learn the basics of this language.
Basics of C#
Conversion of old BASIC programs to C# in order to learn the basics of this language.
Basics of C3
Conversion of old BASIC programs to C3 in order to learn the basics of this language.
Basics of Chapel
Conversion of old BASIC programs to Chapel in order to learn the basics of this language.
Basics of Clojure
Conversion of old BASIC programs to Clojure in order to learn the basics of this language.
Basics of Crystal
Conversion of old BASIC programs to Crystal in order to learn the basics of this language.
Basics of 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 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