Basics of Hare

Descrition del contenete del págine

Conversion de old BASIC-programas a Hare por aprender lu elementari de ti-ci lingue.

Etiquettes:

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

Págines relatet

Basics off
Metaprojecte pri li projectes «Basics of…».
Basics of 8th
Conversion de old BASIC-programas a 8th por aprender lu elementari de ti-ci lingue.
Basics of Ada
Conversion de old BASIC-programas a Ada por aprender lu elementari de ti-ci lingue.
Basics of Arturo
Conversion de old BASIC-programas a Arturo por aprender lu elementari de ti-ci lingue.
Basics of C#
Conversion de old BASIC-programas a C# por aprender lu elementari de ti-ci lingue.
Basics of C3
Conversion de old BASIC-programas a C3 por aprender lu elementari de ti-ci lingue.
Basics of Chapel
Conversion de old BASIC-programas a Chapel por aprender lu elementari de ti-ci lingue.
Basics of Clojure
Conversion de old BASIC-programas a Clojure por aprender lu elementari de ti-ci lingue.
Basics of Crystal
Conversion de old BASIC-programas a Crystal por aprender lu elementari de ti-ci lingue.
Basics of D
Conversion de old BASIC-programas a D por aprender lu elementari de ti-ci lingue.
Basics of Elixir
Conversion de old BASIC-programas a Elixir por aprender lu elementari de ti-ci lingue.
Basics of F#
Conversion de old BASIC-programas a F# por aprender lu elementari de ti-ci lingue.
Basics of Factor
Conversion de old BASIC-programas a Factor por aprender lu elementari de ti-ci lingue.
Basics of FreeBASIC
Conversion de old BASIC-programas a FreeBASIC por aprender lu elementari de ti-ci lingue.
Basics of Gleam
Conversion de old BASIC-programas a Gleam por aprender lu elementari de ti-ci lingue.
Basics of Go
Conversion de old BASIC-programas a Go por aprender lu elementari de ti-ci lingue.
Basics of Haxe
Conversion de old BASIC-programas a Haxe por aprender lu elementari de ti-ci lingue.
Basics of Icon
Conversion de old BASIC-programas a Icon por aprender lu elementari de ti-ci lingue.
Basics of Io
Conversion de old BASIC-programas a Io por aprender lu elementari de ti-ci lingue.
Basics of Janet
Conversion de old BASIC-programas a Janet por aprender lu elementari de ti-ci lingue.
Basics of Julia
Conversion de old BASIC-programas a Julia por aprender lu elementari de ti-ci lingue.
Basics of Kotlin
Conversion de old BASIC-programas a Kotlin por aprender lu elementari de ti-ci lingue.
Basics of Lobster
Conversion de old BASIC-programas a Lobster por aprender lu elementari de ti-ci lingue.
Basics of Lua
Conversion de old BASIC-programas a Lua por aprender lu elementari de ti-ci lingue.
Basics of Nature
Conversion de old BASIC-programas a Nature por aprender lu elementari de ti-ci lingue.
Basics of Neat
Conversion de old BASIC-programas a Neat por aprender lu elementari de ti-ci lingue.
Basics of Neko
Conversion de old BASIC-programas a Neko por aprender lu elementari de ti-ci lingue.
Basics of Nelua
Conversion de old BASIC-programas a Nelua por aprender lu elementari de ti-ci lingue.
Basics of Nim
Conversion de old BASIC-programas a Nim por aprender lu elementari de ti-ci lingue.
Basics of Nit
Conversion de old BASIC-programas a Nit por aprender lu elementari de ti-ci lingue.
Basics of Oberon-07
Conversion de old BASIC-programas a Oberon-07 por aprender lu elementari de ti-ci lingue.
Basics of OCaml
Conversion de old BASIC-programas a OCaml por aprender lu elementari de ti-ci lingue.
Basics of Odin
Conversion de old BASIC-programas a Odin por aprender lu elementari de ti-ci lingue.
Basics of Pike
Conversion de old BASIC-programas a Pike por aprender lu elementari de ti-ci lingue.
Basics of Pony
Conversion de old BASIC-programas a Pony por aprender lu elementari de ti-ci lingue.
Basics of Python
Conversion de old BASIC-programas a Python por aprender lu elementari de ti-ci lingue.
Basics of Racket
Conversion de old BASIC-programas a Racket por aprender lu elementari de ti-ci lingue.
Basics of Raku
Conversion de old BASIC-programas a Raku por aprender lu elementari de ti-ci lingue.
Basics of Retro
Conversion de old BASIC-programas a Retro por aprender lu elementari de ti-ci lingue.
Basics of Rexx
Conversion de old BASIC-programas a Rexx por aprender lu elementari de ti-ci lingue.
Basics of Ring
Conversion de old BASIC-programas a Ring por aprender lu elementari de ti-ci lingue.
Basics of Rust
Conversion de old BASIC-programas a Rust por aprender lu elementari de ti-ci lingue.
Basics of Scala
Conversion de old BASIC-programas a Scala por aprender lu elementari de ti-ci lingue.
Basics of Scheme
Conversion de old BASIC-programas a Scheme por aprender lu elementari de ti-ci lingue.
Basics of Styx
Conversion de old BASIC-programas a Styx por aprender lu elementari de ti-ci lingue.
Basics of Swift
Conversion de old BASIC-programas a Swift por aprender lu elementari de ti-ci lingue.
Basics of V
Conversion de old BASIC-programas a V por aprender lu elementari de ti-ci lingue.
Basics of Vala
Conversion de old BASIC-programas a Vala por aprender lu elementari de ti-ci lingue.
Basics of Zig
Conversion de old BASIC-programas a Zig por aprender lu elementari de ti-ci lingue.

Extern ligamentes relatet