Basics of Vala

Descrition del contenete del págine

Conversion de old BASIC-programas a Vala 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 Vala:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-08-30/31.

Last modified 20260828T1048+0200.
*/

using GLib; // Math needed

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void reset_screen_attributes() {
    stdout.printf("\x1B[0m");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

void clear_screen() {
    erase_screen();
    reset_screen_attributes();
    move_cursor_home();
}

void print_credits() {
    stdout.printf("3D Plot\n\n");
    stdout.printf("Original version in BASIC:\n");
    stdout.printf("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2023, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n\n");
    stdout.printf("Press Enter to start the program.\n");
    stdin.read_line();
}

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

void draw() {
    const int width = 56;
    const char space = ' ';
    const char dot   = '*';
    char line[width];
    int l = 0;
    int z = 0;
    int y1 = 0;
    for (double x = -30.0; x <= 30.0; x += 1.5) {
        for (int pos = 0; pos < width; pos++) {
            line[pos] = space;
        }
        l = 0;
        y1 = 5 * (int)(Math.sqrt(900 - x * x) / 5);
        for (int y = y1; y >= -y1; y += -5) {
            z = (int)(25 + a(Math.sqrt(x * x + (y * y))) - 0.7 * y);
            if (z > l) {
                l = z;
                line[z] = dot;
            }
        } // y loop
        for (int pos = 0; pos < width; pos++) {
            stdout.printf(line[pos].to_string());
        }
        stdout.printf("\n");
    } // x loop
}

void main() {
    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 Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-30.
//
// Last modified: 20260830T1340+0200.

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

void print_credits() {
    clear_screen();
    stdout.printf("Bagels\n");
    stdout.printf("Number guessing game\n\n");
    stdout.printf("Original source unknown but suspected to be:\n");
    stdout.printf("    Lawrence Hall of Science, U.C. Berkely.\n\n");
    stdout.printf("Original version in BASIC:\n");
    stdout.printf("    D. Resek, P. Rowe, 1978.\n");
    stdout.printf("    Creative computing (Morristown, New Jersey, USA), 1978.\n\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2026, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n\n");
    accept_string("Press Enter to read the instructions. ");
}

void print_instructions() {
    clear_screen();
    stdout.printf("Bagels\n");
    stdout.printf("Number guessing game\n\n");
    stdout.printf("I am thinking of a three-digit number that has no two digits the same.\n");
    stdout.printf("Try to guess it and I will give you clues as follows:\n\n");
    stdout.printf("   PICO   - one digit correct but in the wrong position\n");
    stdout.printf("   FERMI  - one digit correct and in the right position\n");
    stdout.printf("   BAGELS - no digits correct\n\n");
    accept_string("Press Enter to start. ");
}

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool is_no(string s) {
    switch (s.down()) {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

bool yes(string prompt) {
    while (true) {
        string answer = accept_string(prompt);
        if (is_yes(answer)) {
            return true;
        }
        if (is_no(answer)) {
            return false;
        }
    }
}

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

const int DIGITS = 3;

bool is_digit(int ascii_code) {
    return (ascii_code >= '0') && (ascii_code <= '9');
}

int[] accept_input(string prompt)
    ensures(result.length == DIGITS)
{
    const char ASCII_0 = '0';
    int[] user_digit = new int[DIGITS];

    while (true) {
        string input = accept_string(prompt);
        if (input.length != DIGITS) {
            stdout.printf("Remember it's a %d-digit number.\n", DIGITS);
            continue;
        }
        {
            bool error = false;
            for (int pos = 0; pos < input.length; pos++) {
                int digit = input[pos];
                if (is_digit(digit)) {
                    user_digit[pos] = digit - ASCII_0;
                } else {
                    stdout.printf("What?\n");
                    error = true;
                    break;
                }
            }
            if (error) {
                continue;
            }
        }
        if (is_any_repeated(user_digit)) {
            stdout.printf("Remember my number has no two digits the same.\n");
            continue;
        } else {
            break;
        }
    }
    return user_digit;
}

// Return three random digits.

int[] random_number() {
    int[] random_digit = new int[DIGITS];
    for (int i = 0; i < DIGITS; i++) {
        bool found = true;
        do {
            random_digit[i] = GLib.Random.int_range(0, 10);
            for (int j = 0; j < i; j++) {
                if (i != j && random_digit[i] == random_digit[j]) {
                    found = false;
                    break;
                }
            }
        } while (!found);
    }
    return random_digit;
}

bool is_any_repeated(int[] number)
    requires(number.length == DIGITS)
{
    for (int i = 0; i < DIGITS; i++) {
        for (int j = i + 1; j < DIGITS; j++) {
            if (number[i] == number[j]) {
                return true;
            }
        }
    }
    return false;
}

string replicate(string s, int count) {
    string result = "";
    for (int i = 0; i < count; i++) {
        result += s;
    }
    return result;
}

void play() {
    const int TRIES = 20;

    int score = 0;
    int fermi = 0; // counter
    int pico = 0; // counter
    int[] computer_number = new int[DIGITS];
    int[] user_number = new int[DIGITS];

    while (true) {
        clear_screen();
        computer_number = random_number();
        stdout.printf("O.K.  I have a number in mind.\n");
        for (int guess = 1; guess < TRIES + 1; guess++) {
            user_number = accept_input(@"Guess #$guess: ");
            fermi = 0;
            pico = 0;
            for (int i = 0; i < DIGITS; i++) {
                for (int j = 0; j < DIGITS; j++) {
                    if (user_number[i] == computer_number[j]) {
                        if (i == j) {
                            fermi += 1;
                        }
                        else {
                            pico += 1;
                        }
                    }
                }
            }
            if (pico + fermi == 0) {
                stdout.printf("BAGELS\n");
            }
            else {
                stdout.printf(
                    "%s%s\n",
                    replicate("PICO ", pico),
                    replicate("FERMI ", fermi)
                );
                if (fermi == DIGITS) {
                    break;
                }
            }
        }
        if (fermi == DIGITS) {
            stdout.printf("You got it!!!\n");
            score += 1;
        }
        else {
            stdout.printf("Oh well.\n");
            stdout.printf("That's %d guesses.  My number was ", TRIES);
            for (int i = 0; i < DIGITS; i++) {
                stdout.printf(computer_number[i].to_string());
            }
            stdout.printf(".\n");
        }
        if (!yes("Play again? ")) {
            break;
        }
    }
    if (score != 0) {
        stdout.printf("A %d-point bagels, buff!!\n", score);
    }
    stdout.printf("Hope you had fun.  Bye.\n");
}

void main() {
    print_credits();
    print_instructions();
    play();
}

Bug

/*
Bug

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

This version in Vala:
    Copyright (c) 2023, 2026, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-10, 2023-12, 2026-08.

Last modified 20260831T0149+0200.
*/

using GLib;

struct bug_type {
    bool body;
    bool neck;
    bool head;
    int feelers;
    char feeler_kind;
    bool tail;
    int legs;
    bool finished;
}

struct player_type {
    string pronoun;
    string possessive;
    bug_type bug;
}

player_type computer;
player_type human;

enum part_id { body = 1, neck, head, feeler, tail, leg }

const int body_height = 2;
const int feeler_length = 4;
const int leg_length = 2;
const int max_feelers = 2;
const int max_legs = 6;
const int neck_length = 2;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void reset_screen_attributes() {
    stdout.printf("\x1B[0m");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

void clear_screen() {
    erase_screen();
    reset_screen_attributes();
    move_cursor_home();
}

// Move the cursor up by a number of rows, without changing the column
// position.
void move_cursor_up(int rows = 1) {
    stdout.printf("\033[%iA", rows);
}

// Erase the current line, without moving the cursor position.
void erase_line() {
    stdout.printf("\033[2K");
}

void erase_previous_line() {
    move_cursor_up();
    erase_line();
}

void print_credits() {
    clear_screen();
    stdout.printf("Bug\n\n");
    stdout.printf("Original version in BASIC:\n");
    stdout.printf("    Brian Leibowitz, 1978.\n");
    stdout.printf("    Creative computing (Morristown, New Jersey, USA), 1978.\n\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2023, 2026, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n\n");
    stdout.printf("Press Enter to read the instructions. ");
    stdin.read_line();
}

string left_justify(string s, int width) {
    return s + repeat(" ", width - s.length);
}

string repeat(string s, int times) {
    string result = "";
    for (int t = 0; t < times; t++) {
        result = result + s;
    }
    return result;
}

string capitalized(string s) {
    return
        s.slice(0, 1).up()
        + (s.length > 1 ? s.slice(1, s.length) : "");
}

string part_name(part_id part) {
    string result = "";
    switch (part) {
        case part_id.body:
            result = "body";
            break;
        case part_id.neck:
            result = "neck";
            break;
        case part_id.head:
            result = "head";
            break;
        case part_id.feeler:
            result = "feeler";
            break;
        case part_id.tail:
            result = "tail";
            break;
        case part_id.leg:
            result = "leg";
            break;
    }
    return result;
}

void print_parts_table() {

    const int columns = 3;
    const int column_width = 8;
    const int column_separation = 2;

    // Headers
    string[] header = {"Number", "Part", "Quantity"};
    for (int i = 0; i < columns; i++) {
        stdout.printf(left_justify(header[i], column_width + column_separation));
    }
    stdout.printf("\n");

    // Rulers
    for (int i = 0; i < columns; i++) {
        stdout.printf(
            (i == 0 ? "" : repeat(" ", column_separation))
            + repeat("-", column_width)
        );
    }
    stdout.printf("\n");

    // Data
    int part_quantity[6];
    part_quantity[part_id.body] = 1;
    part_quantity[part_id.neck] = 1;
    part_quantity[part_id.head] = 1;
    part_quantity[part_id.feeler] = 2;
    part_quantity[part_id.tail] = 1;
    part_quantity[part_id.leg] = 6;
    for (int part = part_id.body; part <= part_id.leg; part++) {
        stdout.printf(
            left_justify("%i".printf(part), column_width + column_separation)
            + left_justify(capitalized(part_name(part)), column_width + column_separation)
            + "%i".printf(part_quantity[part])
            + "\n"
        );
    }

}

const string instructions = """
The object is to finish your bug before I finish mine. Each number
stands for a part of the bug body.

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

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

""";

void print_instructions() {
    clear_screen();
    stdout.printf("Bug\n");
    stdout.printf(instructions);
    print_parts_table();
    stdout.printf("\nPress Enter to start. ");
    stdin.read_line();
}

void print_feelers(bug_type bug) {
    for (int i = 0; i < feeler_length; i++) {
        stdout.printf("        ");
        for (int j = 0; j < bug.feelers; j++) {
            stdout.printf(" %c", bug.feeler_kind);
        }
        stdout.printf("\n");
    }
}

void print_head() {
    stdout.printf("        HHHHHHH\n");
    stdout.printf("        H     H\n");
    stdout.printf("        H O O H\n");
    stdout.printf("        H     H\n");
    stdout.printf("        H  V  H\n");
    stdout.printf("        HHHHHHH\n");
}

void print_neck() {
    for (int i = 0; i < neck_length; i++) {
        stdout.printf("          N N\n");
    }
}

void print_body(bug_type bug) {
    stdout.printf("     BBBBBBBBBBBB\n");
    for (int i = 0; i < body_height; i++) {
        stdout.printf("     B          B\n");
    }
    if (bug.tail == true) {
        stdout.printf("TTTTTB          B\n");
    }
    stdout.printf("     BBBBBBBBBBBB\n");
}

void print_legs(bug_type bug) {
    for (int i = 0; i < leg_length; i++) {
        stdout.printf("    ");
        for (int j = 0; j < bug.legs; j++) {
            stdout.printf(" L");
        }
        stdout.printf("\n");
    }
}

void print_bug(bug_type bug) {
    if (bug.feelers > 0) {
        print_feelers(bug);
    }
    if (bug.head == true) {
        print_head();
    }
    if (bug.neck == true) {
        print_neck();
    }
    if (bug.body == true) {
        print_body(bug);
    }
    if (bug.legs > 0) {
        print_legs(bug);
    }
}

bool finished(bug_type bug) {
    return bug.feelers == max_feelers && bug.tail && bug.legs == max_legs;
}

int dice() {
    return GLib.Random.int_range(1, 7);
}

const string[] as_text = {
    "no",
    "a",
    "two",
    "three",
    "four",
    "five",
    "six" }; // max_legs

string plural(int number, string noun) {
    return as_text[number] + " " + noun + ((number > 1) ? "s" : "");
}

bool add_part(part_id part, ref player_type player) {
    bool changed = false;
    switch (part) {
        case part_id.body:
            if (player.bug.body) {
                stdout.printf(", but " + player.pronoun + " already have a body.\n");
            } else {
                stdout.printf("; " + player.pronoun + " now have a body:\n");
                player.bug.body = true;
                changed = true;
            }
            break;
        case part_id.neck:
            if (player.bug.neck) {
                stdout.printf(", but " + player.pronoun + " you already have a neck.\n");
            } else if (!player.bug.body) {
                stdout.printf(", but " + player.pronoun + " need a body first.\n");
            } else {
                stdout.printf("; " + player.pronoun + " now have a neck:\n");
                player.bug.neck = true;
                changed = true;
            }
            break;
        case part_id.head:
            if (player.bug.head) {
                stdout.printf(", but " + player.pronoun + " already have a head.\n");
            } else if (!player.bug.neck) {
                stdout.printf(", but " + player.pronoun + " need a a neck first.\n");
            } else {
                stdout.printf("; " + player.pronoun + " now have a head:\n");
                player.bug.head = true;
                changed = true;
            }
            break;
        case part_id.feeler:
            if (player.bug.feelers == max_feelers) {
                stdout.printf(", but " + player.pronoun + " have two feelers already.\n");
            } else if (!player.bug.head) {
                stdout.printf(", but " + player.pronoun + " need a head first.\n");
            } else {
                player.bug.feelers++;
                stdout.printf("; " + player.pronoun + " now have " +
                        plural(player.bug.feelers, "feeler") + ":\n");
                changed = true;
            }
            break;
        case part_id.tail:
            if (player.bug.tail) {
                stdout.printf(", but " + player.pronoun + " already have a tail.\n");
            } else if (!player.bug.body) {
                stdout.printf(", but " + player.pronoun + " need a body first.\n");
            } else {
                stdout.printf("; " + player.pronoun + " now have a tail:\n");
                player.bug.tail = true;
                changed = true;
            }
            break;
        case part_id.leg:
            if (player.bug.legs == max_legs) {
                stdout.printf(", but " + player.pronoun + " have " +
                        as_text[max_legs], " feet already.\n");
            } else if (!player.bug.body) {
                stdout.printf(", but " + player.pronoun + " need a body first.\n");
            } else {
                player.bug.legs++;
                stdout.printf("; " + player.pronoun + " now have " +
                        plural(player.bug.legs, "leg") + ":\n");
                changed = true;
            }
            break;
    }
    return changed;
}

void prompt() {
    stdout.printf("Press Enter to roll the dice. ");
    stdin.read_line();
    erase_previous_line();
}

void turn(ref player_type player) {
    prompt();
    int number = dice();
    part_id part = (part_id)number;
    stdout.printf("%s rolled a %i (%s)", capitalized(player.pronoun), number, part_name(part));
    if (add_part(part, ref player)) {
        stdout.printf("\n");
        print_bug(player.bug);
        player.bug.finished = finished(player.bug);
    }
    stdout.printf("\n");
}

void print_winner() {
    if (human.bug.finished && computer.bug.finished) {
        stdout.printf("Both of our bugs are finished in the same number of turns!\n");
    } else if (finished(human.bug)) {
        stdout.printf(human.possessive + " bug is finished.\n");
    } else if (finished(computer.bug)) {
        stdout.printf(computer.possessive + " bug is finished.\n");
    }
}

bool game_over() {
    return human.bug.finished || computer.bug.finished;
}

void play() {
    clear_screen();
    do {
        turn(ref human);
        turn(ref computer);
    } while (!game_over());
    print_winner();
}

void init() {
    human.pronoun = "you";
    human.possessive = "Your";
    human.bug.feeler_kind = 'A';
    human.bug.finished = false;
    computer.pronoun = "I";
    computer.possessive = "My";
    computer.bug.feeler_kind = 'F';
    computer.bug.finished = false;
}

void main() {
    init();
    print_credits();
    print_instructions();
    play();
    stdout.printf("I hope you enjoyed the game, play it again soon!!\n");
}

Bunny

/*
Bunny

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

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

Written in 2023-08-31.

Last modified 20260828T1048+0200.
*/

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void reset_screen_attributes() {
    stdout.printf("\x1B[0m");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

void clear_screen() {
    erase_screen();
    reset_screen_attributes();
    move_cursor_home();
}

void print_credits() {
    stdout.printf("Bunny\n\n");
    stdout.printf("Original version in BASIC:\n");
    stdout.printf("    Creative Computing (Morristown, New Jersey, USA), 1978.\n\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2023, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n\n");
    stdout.printf("Press Enter to start the program.\n");
    stdin.read_line();
}

const int width = 53;
unichar line_buffer[width];
int first_column;

void clear_line_buffer() {
    for (int column = 0; column < width; column++) {
        line_buffer[column] = ' ';
    }
    first_column = 0;
}

void print_line_buffer() {
    for (int column = 0; column < width; column++) {
        stdout.printf(line_buffer[column].to_string());
    }
    stdout.printf("\n");
}

const string word = "BUNNY";
const int EOL = -1; // end of line identifier
const int[] data = {
    1, 2, EOL, 0, 2, 45, 50, EOL, 0, 5, 43, 52, EOL, 0, 7, 41, 52, EOL,
    1, 9, 37, 50, EOL, 2, 11, 36, 50, EOL, 3, 13, 34, 49, EOL, 4, 14,
    32, 48, EOL, 5, 15, 31, 47, EOL, 6, 16, 30, 45, EOL, 7, 17, 29, 44,
    EOL, 8, 19, 28, 43, EOL, 9, 20, 27, 41, EOL, 10, 21, 26, 40, EOL,
    11, 22, 25, 38, EOL, 12, 22, 24, 36, EOL, 13, 34, EOL, 14, 33, EOL,
    15, 31, EOL, 17, 29, EOL, 18, 27, EOL, 19, 26, EOL, 16, 28, EOL,
    13, 30, EOL, 11, 31, EOL, 10, 32, EOL, 8, 33, EOL, 7, 34, EOL, 6,
    13, 16, 34, EOL, 5, 12, 16, 35, EOL, 4, 12, 16, 35, EOL, 3, 12, 15,
    35, EOL, 2, 35, EOL, 1, 35, EOL, 2, 34, EOL, 3, 34, EOL, 4, 33,
    EOL, 6, 33, EOL, 10, 32, 34, 34, EOL, 14, 17, 19, 25, 28, 31, 35,
    35, EOL, 15, 19, 23, 30, 36, 36, EOL, 14, 18, 21, 21, 24, 30, 37, 37,
    EOL, 13, 18, 23, 29, 33, 38, EOL, 12, 29, 31, 33, EOL, 11, 13, 17,
    17, 19, 19, 22, 22, 24, 31, EOL, 10, 11, 17, 18, 22, 22, 24, 24, 29,
    29, EOL, 22, 23, 26, 29, EOL, 27, 29, EOL, 28, 29, EOL };

void draw() {
    int last_column;
    uint data_index = 0;
    clear_line_buffer();
    while (data_index < data.length) {
        first_column = data[data_index];
        data_index += 1;
        if (first_column == EOL) {
            print_line_buffer();
            clear_line_buffer();
        } else {
            last_column = data[data_index];
            data_index += 1;
            for (int column = first_column; column <= last_column; column++) {
                line_buffer[column] = word[column % word.length];
            }
        }
    }
}

void main() {
    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 Vala:
//  Copyright (c) 2026, Marcos Cruz (programandala.net)
//  SPDX-License-Identifier: Fair
//
//  Written in 2026-08-28/30.
//  Last modified: 20260830T0041+0200.

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

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_cursor_position(int line, int col) {
    stdout.printf("\x1B[%d;%dH", line, col);
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_line_to_end() {
    stdout.printf("\x1B[K");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

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

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

char[,] arena;

const char EMPTY = ' ';
const char FENCE = 'X';
const char MACHINE = 'm';
const char HUMAN = '@';

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

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

End the_end = End.NOT_YET;

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

struct Machine {
    int x;
    int y;
    bool operative;
}

Machine[] machine;

int destroyed_machines = 0; // counter

int human_x = 0;
int human_y = 0;

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

string get_string(string prompt) {
    set_style(INPUT_INK);
    string s = accept_string(prompt);
    set_style(DEFAULT_INK);
    return s;
}

void press_enter(string prompt) {
    accept_string(prompt);
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool is_no(string s) {
    switch (s.down()) {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".

bool yes(string prompt) {
    while (true) {
        string answer = get_string(prompt);
        if (is_yes(answer)) {
            return true;
        }
        if (is_no(answer)) {
            return false;
        }
    }
}

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

const string TITLE = "Chase";

void print_title() {
    set_style(TITLE_INK);
    stdout.printf("%s\n", TITLE);
    set_style(DEFAULT_INK);
}

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

void print_instructions() {
    print_title();
    set_style(INSTRUCTIONS_INK);
    stdout.printf("\nYou (%c) are in a high voltage maze with %d\n", HUMAN, MACHINES);
    stdout.printf("security machines (%c) trying to kill you.\n", MACHINE);
    stdout.printf("You must maneuver them into the maze (%c) to survive.\n\n", FENCE);
    stdout.printf("Good luck!\n\n");
    stdout.printf("The movement commands are the following:\n\n");
    stdout.printf("    ↖  ↑  ↗\n");
    stdout.printf("    NW N NE\n");
    stdout.printf("  ←  W   E  →\n");
    stdout.printf("    SW S SE\n");
    stdout.printf("    ↙  ↓  ↘\n");
    stdout.printf("\nPlus 'Q' to end the game.\n");
    set_style(DEFAULT_INK);
}

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

void print_arena() {
    set_cursor_position(ARENA_ROW, 1);
    for (int y = 0; y <= ARENA_LAST_Y; y++) {
        for (int x = 0; x <= ARENA_LAST_X; x++) {
            stdout.printf("%c", arena[y, x]);
        }
        stdout.printf("\n");
    }
}

bool is_border(int y, int x) {
    return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}

int random_int_in_inclusive_range(int min, int max) {
    return GLib.Random.int_range(min, min + max);
}

// Place the given string at a random empty position of the arena and update the
// coordinates.

void place(char s, ref int y, ref int x) {
    do {
        y = random_int_in_inclusive_range(1, ARENA_LAST_Y - 1);
        x = random_int_in_inclusive_range(1, ARENA_LAST_X - 1);
    } while (arena[y, x] != EMPTY);
    arena[y, x] = s;
}

void place_fences() {
    int y = 0;
    int x = 0;
    for (int i = 0; i < FENCES; i++) {
        place(FENCE, ref y, ref x);
    }
}

void place_machines() {
    for (int m = 0; m < MACHINES; m++) {
        place(MACHINE, ref machine[m].y, ref machine[m].x);
        machine[m].operative = true;
    }
}

void inhabit_arena() {
    place_machines();
    place_fences();
    place(HUMAN, ref human_y, ref human_x);
}

void clean_arena() {
    for (int y = 0; y <= ARENA_LAST_Y; y++) {
        for (int x = 0; x <= ARENA_LAST_X; x++) {
            arena[y, x] = is_border(y, x) ? FENCE : EMPTY;
        }
    }
}

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

void init_once() {
    arena = new char[ARENA_HEIGHT, ARENA_WIDTH];
    machine = new Machine[MACHINES];
}

void init_game() {
    clean_arena();
    inhabit_arena();
    destroyed_machines = 0;
    the_end = End.NOT_YET;
}

void move_machine(int m) {
    int maybe = 0;

    arena[machine[m].y, machine[m].x] = EMPTY;

    maybe = GLib.Random.int_range(0, 2);
    if (machine[m].y > human_y) {
        machine[m].y -= maybe;
    }
    else if (machine[m].y < human_y) {
        machine[m].y += maybe;
    }

    maybe = GLib.Random.int_range(0, 2);
    if (machine[m].x > human_x) {
        machine[m].x -= maybe;
    }
    else if (machine[m].x < human_x) {
        machine[m].x += maybe;
    }

    if (arena[machine[m].y, machine[m].x] == EMPTY) {
        arena[machine[m].y, machine[m].x] = MACHINE;
    }
    else if (arena[machine[m].y, machine[m].x] == FENCE) {
        machine[m].operative = false;
        destroyed_machines += 1;
        if (destroyed_machines == MACHINES) {
            the_end = End.VICTORY;
        }
    }
    else if (arena[machine[m].y, machine[m].x] == HUMAN) {
        the_end = End.KILLED;
    }

}

void maybe_move_machine(int m) {
    if (GLib.Random.int_range(0, MACHINES_DRAG) == 0) {
        move_machine(m);
    }
}

void move_machines() {
    for (int m = 0; m < MACHINES; m++) {
        if (machine[m].operative) {
            maybe_move_machine(m);
        }
    }
}

// Read a user command; update `the_end` accordingly and set the direction
// increments.

void set_move(ref int y_inc, ref int x_inc) {
    stdout.printf("\n");
    erase_line_to_end();
    string command = get_string("Command: ").down();

    switch (command) {
        case "q":
            the_end = End.QUIT;
            break;
        case "sw":
            y_inc = 1;
            x_inc = -1;
            break;
        case "s":
            y_inc = 1;
            x_inc = 0;
            break;
        case "se":
            y_inc = 1;
            x_inc = 1;
            break;
        case "w":
            y_inc = 0;
            x_inc = -1;
            break;
        case "e":
            y_inc = 0;
            x_inc = 1;
            break;
        case "nw":
            y_inc = -1;
            x_inc = -1;
            break;
        case "n":
            y_inc = -1;
            x_inc = 0;
            break;
        case "ne":
            y_inc = -1;
            x_inc = 1;
            break;
        default:
            y_inc = 0;
            x_inc = 0;
            break;
    }
}

void play() {
    init_once();
    int y_inc = 0;
    int x_inc = 0;

    do {

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

        while (the_end == End.NOT_YET) {

            print_arena();
            set_move(ref y_inc, ref x_inc);

            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();
                    }
                }
            }

            switch (the_end) {
                case End.NOT_YET:
                    break;
                case End.QUIT:
                    stdout.printf("\nSorry to see you quit.\n");
                    break;
                case End.ELECTRIFIED:
                    stdout.printf("\nZap! You touched the fence!\n");
                    break;
                case End.KILLED:
                    stdout.printf("\nYou have been killed by a lucky machine.\n");
                    break;
                case End.VICTORY:
                    stdout.printf("\nYou are lucky, you destroyed all machines.\n");
                    break;
            }

        } // action loop;
    } while (yes("\nDo you want to play again? "));

    stdout.printf("\nHope you don't feel fenced in.\n");
    stdout.printf("Try again sometime.\n");
}

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

Written in 2023-08-30/31.

Last modified 20260828T1048+0200.
*/

void main() {

    const int lines = 17;
    int i = 1;
    int j;

    while (i <= lines / 2 + 1) {
        j = 1;
        while (j <= (lines + 1) / 2 - i + 1) {
            stdout.printf(" ");
            j += 1;
        }
        j = 1;
        while (j <= i * 2 - 1) {
            stdout.printf("*");
            j += 1;
        }
        stdout.printf("\n");
        i += 1;
    }
    i = 1;
    while (i <= lines / 2) {
        j = 1;
        while (j <= i + 1) {
            stdout.printf(" ");
            j += 1;
        }
        j = 1;
        while (j <= ((lines + 1) / 2 - i) * 2 - 1) {
            stdout.printf("*");
            j += 1;
        }
        stdout.printf("\n");
        i += 1;
    }
}

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 Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-30.
//
// Last modified: 20260831T0147+0200.
//
// Acknowledgment:
//     The following Python port was used as a reference of the original
//     variables: <https://github.com/jquast/hamurabi.py>.

using Gsl;

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

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

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

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

enum Result { VERY_GOOD, NOT_TOO_BAD, BAD, VERY_BAD }

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

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

const string CREDITS =
"""Hammurabi

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

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

This improved remake in Vala:
  Copyright (c) 2026, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair""";

void print_credits() {
    set_style(TITLE_INK);
    stdout.printf("%s\t", CREDITS);
    set_style(DEFAULT_INK);
}

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

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

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

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

Try your hand at governing ancient Sumeria for a %d-year term of office.""";

void print_instructions() {
    set_style(INSTRUCTIONS_INK);
    stdout.printf(
        INSTRUCTIONS,
        MIN_HARVESTED_BUSHELS_PER_ACRE,
        MAX_HARVESTED_BUSHELS_PER_ACRE,
        YEARS
        );
    set_style(DEFAULT_INK);
}

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

void pause(string prompt = "> ") {
    set_style(INPUT_INK);
    accept_string(prompt);
    set_style(DEFAULT_INK);
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

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

int random_1_to_5() {
    return GLib.Random.int_range(1, 6);
}

Gsl.RNGType* T;
Gsl.RNG rng;

void init_rng() {
    Gsl.RNG.env_setup ();
    T = Gsl.RNGTypes.@default;
    rng = new Gsl.RNG (T);
}

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

// Return a string with the proper wording for `n` persons, using the given or
// default words for singular and plural forms.

string persons(int n, string singular = "person", string plural = "people") {
    switch (n) {
        case 0: return "nobody";
        case 1: return "one " + singular;
        default: return n.to_string() + " " + plural;
    }
}

string ordinal_suffix(int n) {
    switch (n) {
        case 1: return "st";
        case 2: return "nd";
        case 3: return "rd";
        default: return "th";
    }
}

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

// Return a string with the description of the given year as the previous one.

string previous(int year) {
    if (year == 0) {
        return "the previous year";
    }
    else {
        return "your " + year.to_string() + ordinal_suffix(year) + " year";
    }
}

void print_annual_report(int year) {
    clear_screen();
    set_style(SPEECH_INK);
    stdout.printf("Hammurabi, I beg to report to you.\n");
    set_style(DEFAULT_INK);

    string year_text = previous(year);
    string persons_text = persons(dead);
    string infants_text = persons(infants);

    stdout.printf(
        "\nIn %s, %s starved and %s %s born.\n",
        year_text,
        persons_text,
        infants_text,
        (infants > 1) ? "were" : "was"
    );

    population += infants;

    if (year > 0 && rng.uniform() <= PLAGUE_CHANCE) {
        population = (int)(population / 2);
        set_style(WARNING_INK);
        stdout.printf("A horrible plague struck!  Half the people died.\n");
        set_style(DEFAULT_INK);
    }

    stdout.printf("The population is %d.\n", population);
    stdout.printf("The city owns %d acres.\n", acres);
    stdout.printf(
        "You harvested %d bushels (%d per acre).\n",
        bushels_harvested,
        bushels_harvested_per_acre
    );
    if (bushels_eaten_by_rats > 0) {
        stdout.printf("The rats ate %d bushels.\n", bushels_eaten_by_rats);
    }
    stdout.printf("You have %d bushels in store.\n", bushels_in_store);
    bushels_harvested_per_acre =
        (int)((double)RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * rng.uniform()) +
        MIN_HARVESTED_BUSHELS_PER_ACRE;
    stdout.printf("Land is trading at %d bushels per acre.\n\n", bushels_harvested_per_acre);
}

void say_bye() {
    set_style(DEFAULT_INK);
    stdout.printf("\nSo long for now.\n");
}

void quit_game() {
    // XXX TODO replace with catched exception in `main`?
    say_bye();
    GLib.Process.exit(0);
}

void relinquish() {
    set_style(SPEECH_INK);
    stdout.printf("\nHammurabi, I am deeply irritated and cannot serve you anymore.\n");
    stdout.printf("Please, get yourself another steward!\n");
    set_style(DEFAULT_INK);
    quit_game();
}

void increase_irritation() {
    irritation += GLib.Random.int_range(1, IRRITATION_STEP + 1);
    if (irritation >= MAX_IRRITATION) {
        relinquish(); // this never returns
    }
}

void print_irritated(string adverb) {
    stdout.printf("The steward seems %s irritated.\n", adverb);
}

void show_irritation() {
    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.

void beg_repeat() {
    increase_irritation(); // this may never return
    set_style(SPEECH_INK);
    stdout.printf("I beg your pardon?  I did not understand your order.\n");
    set_style(DEFAULT_INK);
    show_irritation();
}

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.

void beg_think_again(int n, string name) {
    increase_irritation(); // this may never return
    set_style(SPEECH_INK);
    stdout.printf("I beg your pardon?  You have only %d %s.  Now then…\n", n, name);
    set_style(DEFAULT_INK);
    show_irritation();
}

void trade_land() {
    int acres_to_buy = 0;
    int acres_to_sell = 0;

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

    if (acres_to_buy != 0) {
        stdout.printf("You buy %d acres.\n", acres_to_buy);
        acres += acres_to_buy;
        bushels_in_store -= bushels_harvested_per_acre * acres_to_buy;
        stdout.printf("You now have %d acres and %d bushels.\n", acres, bushels_in_store);
    }
    else {
        while (true) {
            acres_to_sell = accept_integer("How many acres do you wish to sell?: ");
            if (acres_to_sell < 0) {
                beg_repeat(); // this may never return
            }
            else {
                if (acres_to_sell < acres) {
                    break;
                }
                else {
                    beg_think_again(acres, "acres");
                }
            }
        }

        if (acres_to_sell > 0) {
            stdout.printf("You sell %d acres.\n", acres_to_sell);
            acres -= acres_to_sell;
            bushels_in_store += bushels_harvested_per_acre * acres_to_sell;
            stdout.printf("You now have %d acres and %d bushels.\n", acres, bushels_in_store);
        }
    }
}

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

    stdout.printf("You feed your people with %d bushels.\n", bushels_to_feed_with);
    bushels_in_store -= bushels_to_feed_with;
    stdout.printf("You now have %d bushels.\n", bushels_in_store);
}

void seed_land() {
    int acres_to_seed = 0;

    while (true) {
        acres_to_seed = accept_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;
        }

        string message =
            "bushels of grain,\nand one bushel can seed "
            + ACRES_A_BUSHEL_CAN_SEED.to_string()
            + " acres";

        // Enough grain for seed?
        if ((int)(acres_to_seed / ACRES_A_BUSHEL_CAN_SEED) > 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;
        }

        message =
            "people to tend the fields,\nand one person can seed "
            + ACRES_A_PERSON_CAN_SEED.to_string()
            + " acres";

        beg_think_again(population, message);
    }

    int bushels_used_for_seeding = (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED);
    stdout.printf("You seed %d acres using %d bushels.\n", acres_to_seed, bushels_used_for_seeding);
    bushels_in_store -= bushels_used_for_seeding;
    stdout.printf("You now have %d bushels.\n", 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;
}

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

void check_rats() {
    int rat_chance = random_1_to_5();
    bushels_eaten_by_rats = is_even(rat_chance) ? (int)(bushels_in_store / rat_chance) : 0;
    bushels_in_store -= bushels_eaten_by_rats;
}

void init_first_year() {
    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;
}

void init() {
    init_rng();
    init_first_year();
}

void print_result(Result r) {
    set_style(RESULT_INK);

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

    set_style(DEFAULT_INK);
}

void print_final_report() {
    clear_screen();

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

    int acres_per_person = acres / population;
    stdout.printf(
        "You started with %d acres per person and ended with %d.\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);
    }
}

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

    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 > (int)(0.45 * population)) {
            set_style(WARNING_INK);
            stdout.printf("\nYou starved %d people in one year!!!\n\n", dead);
            set_style(DEFAULT_INK);
            print_result(Result.VERY_BAD);
            quit_game();
        }
    }
}

void govern() {
    init();

    print_annual_report(0);

    for (int year = 1; year < YEARS + 1; year++) {
        trade_land();
        feed_people();
        seed_land();
        check_rats();

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

        check_starvation(year);

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

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

void main() {
    clear_screen();
    print_credits();
    pause("\n\nPress the Enter key to read the instructions. ");
    clear_screen();
    print_instructions();
    pause("\n\nPress the Enter key to start. ");
    govern();
    pause("\nPress 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 Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-27.
//
// Last modified: 20260828T1048+0200.

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

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

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

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

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

int player_bullets = 0;
int opponent_bullets = 0;

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    set_style(INPUT_INK);
    string s = stdin.read_line().strip();
    set_style(DEFAULT_INK);
    return s;
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok", "y", "yeah", "yes":
            return true;
        default:
            return false;
    }
}

bool is_no(string s) {
    switch (s.down()) {
        case "n", "no", "nope":
            return true;
        default:
            return false;
    }
}

bool yes(string prompt) {
    while (true) {
        string answer = accept_string(prompt);
        if (is_yes(answer)) {
            return true;
        }
        if (is_no(answer)) {
            return false;
        }
    }
}

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

void print_title() {
    set_style(TITLE_INK);
    stdout.printf("High Noon\n");
    set_style(DEFAULT_INK);
}

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

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

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

string plural_suffix(int n) {
    switch (n) {
        case 1: return "";
        default: return "s";
    }
}

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

int random_max(int max) {
    return GLib.Random.int_range(0, max + 1);
}

void print_check() {
    stdout.printf("******************************************************\n");
    stdout.printf("*                                                    *\n");
    stdout.printf("*                 BANK OF DODGE CITY                 *\n");
    stdout.printf("*                  CASHIER'S RECEIT                  *\n");
    stdout.printf("*                                                    *\n");
    stdout.printf("* CHECK NO. %04d                   AUGUST %dTH, 1889 *\n",
        random_max(999),
        10 + random_max(9));
    stdout.printf("*                                                    *\n");
    stdout.printf("*                                                    *\n");
    stdout.printf("*       PAY TO THE BEARER ON DEMAND THE SUM OF       *\n");
    stdout.printf("*                                                    *\n");
    stdout.printf("* TWENTY THOUSAND DOLLARS-------------------$20,000  *\n");
    stdout.printf("*                                                    *\n");
    stdout.printf("******************************************************\n");
}

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

void move_the_opponent() {
    int paces = 2 + random_max(7);
    stdout.printf("Black Bart moves %d paces.\n", paces);
    distance -= paces;
}

// Maybe move the opponent; if so, return `true`, otherwise return `false`. A
// true `silent` flag allows to omit the message when the opponent doesn't
// move.

bool maybe_move_the_opponent(bool silent) {
    if (random_max(1) == 0) { // 50% chances
        move_the_opponent();
        return true;
    }
    else {
        if (!silent) {
            stdout.printf("Black Bart stands still.\n");
        }
        return false;
    }
}

bool missed_shot() {
    return random_max(9) <= (distance / 10);
}

// Handle the opponent's shot and return a flag with the result: if the
// opponent kills the player, return `true`; otherwise return `false`.

bool the_opponent_fires_and_kills(string player_strategy) {
    stdout.printf("Black Bart fires…\n");
    opponent_bullets -= 1;
    if (missed_shot()) {
        stdout.printf("A miss…\n");
        switch (opponent_bullets) {
            case 3:
                stdout.printf("Whew, were you lucky. That bullet just missed your head.\n");
                break;
            case 2:
                stdout.printf("But Black Bart got you in the right shin.\n");
                break;
            case 1:
                stdout.printf("Though Black Bart got you on the left side of your jaw.\n");
                break;
            case 0:
                stdout.printf("Black Bart must have jerked the trigger.\n");
                break;
        }
    }
    else {
        if (player_strategy == "j") {
            stdout.printf("That trick just saved yout life. Black Bart's bullet\n");
            stdout.printf("was stopped by the wood sides of the trough.\n");
        }
        else {
            stdout.printf("Black Bart shot you right through the heart that time.\n");
            stdout.printf("You went kickin' with your boots on.\n");
            return true;
        }
    }
    return false;
}

// Handle the opponent's strategy and return a flag with the result: if the
// opponent runs or kills the player, return `true`; otherwise return `false`.

bool the_opponent_kills_or_runs(string player_strategy) {
    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_max(1) == 0) { // 50% chances
                stdout.printf("Now is your chance, Black Bart is out of bullets.\n");
            }
            else {
                stdout.printf("Black Bart just hi-tailed it out of town rather than face you\n");
                stdout.printf("without a loaded gun. You can rest assured that Black Bart\n");
                stdout.printf("won't ever show his face around this town again.\n");
                return true;
            }
        }
    }
    return false;
}

void play() {
    distance = INITIAL_DISTANCE;
    int watering_troughs = 0;
    player_bullets = INITIAL_BULLETS;
    opponent_bullets = INITIAL_BULLETS;
    bool end_showdown = false;

    while (true) {
        stdout.printf("You are now %d paces apart from Black Bart.\n", distance);
        print_shells_left();
        set_style(INSTRUCTIONS_INK);
        stdout.printf("\nStrategies:\n");
        stdout.printf("  [A]dvance\n");
        stdout.printf("  [S]tand still\n");
        stdout.printf("  [F]ire\n");
        stdout.printf("  [J]ump behind the watering trough\n");
        stdout.printf("  [G]ive up\n");
        stdout.printf("  [T]urn tail and run\n");
        set_style(DEFAULT_INK);

        string player_strategy = accept_string("What is your strategy? ").down();

        switch (player_strategy) {
            case "a": // advance

                while (true) {
                    int paces = accept_integer("How many paces do you advance? ");
                    if (paces < 0) {
                        stdout.printf("None of this negative stuff, partner, only positive numbers.\n");
                    }
                    else if (paces > 10) {
                        stdout.printf("Nobody can walk that fast.\n");
                    }
                    else {
                        distance -= paces;
                        break;
                    }
                }
                break;

            case "s": // stand still

                stdout.printf("That move made you a perfect stationary target.\n");
                break;

            case "f": // fire

                if (player_bullets == 0) {
                    stdout.printf("You don't have any bullets left.\n");

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

                }
                break;

            case "j": // jump

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

            case "g": // give up

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

            case "t": // turn tail and run

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

            default:
                stdout.printf("You sure aren't going to live very long if you can't even follow directions.\n");
                break;

        } // strategy switch

        if (end_showdown || the_opponent_kills_or_runs(player_strategy)) {
            break;
        } else if (player_bullets + opponent_bullets == 0) {
            stdout.printf("The showdown must end, because nobody has bullets left.\n");
            break;
        } else {
            stdout.printf("\n");
        }
    } // showdown loop

}

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

void main() {
    clear_screen();
    print_credits();
    accept_string("\nPress the Enter key to read the instructions. ");
    clear_screen();
    print_instructions();
    accept_string("\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 Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-26.
//
// Last modified: 20260828T1048+0200.

using GLib; // Math

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_float(string s) {
    bool has_decimal_point = false;
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (s[c] == '.') {
            if (has_decimal_point) {
                return false;
            } else {
                has_decimal_point = true;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

float accept_float(string prompt) {
    float result;
    while (true) {
        string s = accept_string(prompt);
        if (is_float(s)) {
            result = float.parse(s);
            break;
        } else {
            stdout.printf("Real number expected.\n");
        }
    }
    return result;
}

float abs_(float number) {
    if (number >= 0) {
        return number;
    } else {
        return number * -1;
    }
}

int sign(float number) {
    if (number > 0) {
        return 1;
    } else if (number < 0) {
        return -1;
    } else {
        return 0;
    }
}

void main() {
    float n = accept_float("Enter a number: ");
    stdout.printf(@"ABS($n) -> abs_($n) /* ad hoc function */ -> $(abs_(n))\n");
    stdout.printf(@"ATN($n) -> Math.atan($n) -> $(Math.atan(n))\n");
    stdout.printf(@"COS($n) -> Math.cos($n) -> $(Math.cos(n))\n");
    stdout.printf(@"EXP($n) -> Math.exp($n) -> $(Math.exp(n))\n");
    stdout.printf(@"INT($n) -> (int) $n -> $((int) n)\n");
    stdout.printf(@"LOG($n) -> Math.log($n) -> $(Math.log(n))\n");
    stdout.printf(@"SGN($n) -> sign($n) /* ad hoc function */ -> $(sign(n))\n");
    stdout.printf(@"SQR($n) -> Math.sqrt($n) -> $(Math.sqrt(n))\n");
    stdout.printf(@"TAN($n) -> Math.tan($n) -> $(Math.tan(n))\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 Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-30.
//
// Last modified: 20260901T1122+0200.

using GLib; // Math

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

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

struct Mugwump {
    int x;
    int y;
    bool hidden;
}

Mugwump[] mugwump;

int found = 0; // counter

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool is_no(string s) {
    switch (s.down()) {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

bool yes(string prompt) {
    while (true) {
        string answer = accept_string(prompt);
        if (is_yes(answer)) {
            return true;
        }
        if (is_no(answer)) {
            return false;
        }
    }
}

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

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

void print_instructions() {
    clear_screen();
    stdout.printf("Mugwump\n\n");
    stdout.printf("The object of this game is to find four mugwumps\n");
    stdout.printf("hidden on a 10 by 10 grid.  Homebase is position 0,0.\n");
    stdout.printf("Any guess you make must be two numbers with each\n");
    stdout.printf("number between 0 and 9, inclusive.  First number\n");
    stdout.printf("is distance to right of homebase and second number\n");
    stdout.printf("is distance above homebase.\n\n");
    stdout.printf("You get %d tries.  After each try, you will see\n", TURNS);
    stdout.printf("how far you are from each mugwump.\n\n");
    accept_string("Press Enter to start. ");
}

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

void hide_mugwumps() {
    for (int m = 0; m < MUGWUMPS; m++) {
        mugwump[m].x = GLib.Random.int_range(0, GRID_SIZE);
        mugwump[m].y = GLib.Random.int_range(0, GRID_SIZE);
        mugwump[m].hidden = true;
    }
    found = 0; // counter
}

int accept_coordinate(string prompt) {
    int coord = 0;
    while (true) {
        coord = accept_integer(prompt);
        if (coord < 0 || coord >= GRID_SIZE) {
            stdout.printf("Invalid value %d: not in range [0, %d].\n", coord, GRID_SIZE - 1);
        }
        else {
            break;
        }
    }
    return coord;
}

bool is_here(int m, int x, int y) {
    return mugwump[m].hidden && mugwump[m].x == x && mugwump[m].y == y;
}

// Return the distance between the given mugwump and the given coords.

int distance(int m, int x, int y) {
    return (int) Math.sqrt(
        Math.pow((mugwump[m].x - x), 2.0) +
        Math.pow((mugwump[m].y - y), 2.0));
}

string plural(int n, string plural_suffix = "s", string singular_suffix = "") {
    return n > 1 ? plural_suffix : singular_suffix;
}

void play() {
    int x = 0;
    int y = 0;
    int turn = 0; // counter

    while (true) { // game

        clear_screen();
        hide_mugwumps();

        bool quit_turns = false;
        for (turn = 1; turn <= TURNS; turn += 1) {
            stdout.printf("Turn number %d\n\n", turn);
            stdout.printf("What is your guess (in range [0, %d])?\n", GRID_SIZE - 1);
            x = accept_coordinate("Distance right of homebase (x-axis): ");
            y = accept_coordinate("Distance above homebase (y-axis): ");
            stdout.printf("\nYour guess is (%d, %d).\n", x, y);

            for (int m = 0; m < MUGWUMPS; m++) {
                if (is_here(m, x, y)) {
                    mugwump[m].hidden = false;
                    found += 1;
                    stdout.printf("You have found mugwump %d!\n", m);
                    if (found == MUGWUMPS) {
                        quit_turns = true;
                        break;
                    }
                }
            }

            if (quit_turns) {
                break;
            } else {
                for (int m = 0; m < MUGWUMPS; m++) {
                    if (mugwump[m].hidden) {
                        stdout.printf("You are %d units from mugwump %d.\n", distance(m, x, y) , m);
                    }
                }
                stdout.printf("\n");
            }
        } // turns

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

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

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

void init() {
    mugwump = new Mugwump[MUGWUMPS];
}

void main() {
    print_credits();
    print_instructions();
    init();
    play();
}

Name

// Name

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

// This version in Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-26.
//
// Last modified: 20260828T1048+0200.

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

void main() {
    string name = accept_string("What is your name? ");
    int number = accept_integer("Enter a number: ");
    for (int i = 0; i < number; i++) {
        stdout.printf("Hello, %s!\n", name);
    }
}

Poetry

// Poetry

// Original version in BASIC:
//     Unknown author.
//     Modified and reworked by Jim Bailey, Peggy Ewing, and Dave Ahl at DEC.
//     Published in "BASIC Computer Games", Creative Computing (Morristown, New Jersey, USA), 1978.
//     https://archive.org/details/Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
//     https://github.com/chaosotter/basic-games/tree/master/games/BASIC%20Computer%20Games/Poetry
//     http://vintage-basic.net/games.html

// This improved remake in Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-26.
//
// Last modified: 20260828T1048+0200.

using GLib;

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

void print_title() {
    set_style(TITLE_INK);
    stdout.printf("Poetry\n");
    set_style(DEFAULT_INK);
}

void print_credits() {
    print_title();
    stdout.printf("\nOriginal version in BASIC:\n");
    stdout.printf("    Unknown author.\n");
    stdout.printf("    Published in \"BASIC Computer Games\",\n");
    stdout.printf("    Creative Computing (Morristown, New Jersey, USA), 1978.\n\n");
    stdout.printf("This improved remake in Vala:\n");
    stdout.printf("    Copyright (c) 2026, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n");
}

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

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

int random_max(int max) {
    return GLib.Random.int_range(0, max + 1);
}

void wait() {
    Thread.usleep(250000); // 250 ms
}

void play() {
    int MAX_PHRASES_AND_VERSES = 20;

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

    while (true) {
        bool skip_the_rest_of_the_loop = false;
        bool manage_the_verse_continuation = true;
        bool maybe_add_comma = true;

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

        if (!skip_the_rest_of_the_loop) {
            if (manage_the_verse_continuation) {
                wait();
                if (maybe_add_comma && !(verse_chunks == 0 || random_max(99) > 19)) {
                    stdout.printf(",");
                    verse_chunks = 2;
                }
                if (random_max(99) > 65) {
                    stdout.printf("\n");
                    verse_chunks = 0;
                }
                else {
                    stdout.printf(" ");
                    verse_chunks += 1;
                }
            }

            action += 1;
            phrase = random_max(4);
            phrases_and_verses += 1;

            if (!(verse_chunks > 0 || is_even(action))) {
                stdout.printf("     ");
            }
        }
    }
}

void main() {
    clear_screen();
    print_credits();
    accept_string("\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 Vala:
//    Copyright (c) 2026, Marcos Cruz (programandala.net)
//    SPDX-License-Identifier: Fair
//
// Written on 2026-08-27.
//
// Last modified: 20260828T1048+0200.

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

void press_enter_to_start() {
    accept_string("Press Enter to start. ");
}

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

void print_credits() {
    clear_screen();
    stdout.printf("Russian Roulette\n\n");
    stdout.printf("Original version in BASIC:\n");
    stdout.printf("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2026, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n\n");
    press_enter_to_start();
}

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

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

void play() {
    int times = 0;
    while (true) {
        bool quit = false;
        print_instructions();
        times = 0;
        while (!quit) {
            string command = accept_string("> ");
            switch (command) {
                case "f": // fire
                    if (GLib.Random.int_range(0, 100) > 83) {
                        stdout.printf("Bang! You're dead!\n");
                        stdout.printf("Condolences will be sent to your relatives.\n");
                        quit = true;
                    }
                    else {
                        times += 1;
                        if (times > 10) {
                            stdout.printf("You win!\n");
                            stdout.printf("Let someone else blow his brains out.\n");
                            quit = true;
                        }
                        else {
                            stdout.printf("Click.\n");
                        }
                    }
                    break;
                case "g": // give up
                    stdout.printf("Chicken!\n");
                    quit = true;
                    break;
                case "q": // quit
                    return;
                default:
                    continue;
            }
        }
        press_enter_to_start();
    }
}

void bye() {
    stdout.printf("Bye!\n");
}

void main() {
    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 Vala:
//   Copyright (c) 2026, Marcos Cruz (programandala.net)
//   SPDX-License-Identifier: Fair
//
// Written on 2026-08-27
//
// Last modified: 20260828T1048+0200.

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

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_cursor_position(int y, int x) {
    stdout.printf("\x1B[%d;%dH", y, x);
}

void hide_cursor() {
    stdout.printf("\x1B[?25l");
}

void show_cursor() {
    stdout.printf("\x1B[?25h");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_line_to_end() {
    stdout.printf("\x1B[K");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

const string TITLE = "Seance";

const int MAX_SCORE = 50;

const int MAX_MESSAGE_LENGTH = 6;
const int MIN_MESSAGE_LENGTH = 3;

const char BASE_CHARACTER = '@';
const char PLANCHETTE = '*';
const char SPACE = ' ';

const int FIRST_LETTER_NUMBER = 1;
const int LAST_LETTER_NUMBER = 26;

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

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

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

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

const int MESSAGES_Y = INPUT_Y;

const int MISTAKE_EFFECT_PAUSE = 3000000; // microseconds

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

void press_enter(string prompt) {
    accept_string(prompt);
}

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

void print_title() {
    set_style(TITLE_INK);
    stdout.printf("%s\n", TITLE);
    set_style(DEFAULT_INK);
}

void print_credits() {
    print_title();
    stdout.printf("\nOriginal version in BASIC:\n");
    stdout.printf("    Written by Chris Oxlade, 1983.\n");
    stdout.printf("    https://archive.org/details/seance.qb64\n");
    stdout.printf("    https://github.com/chaosotter/basic-games\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2026, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n");
}

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

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

int random_int_in_inclusive_range(int min, int max) {
    return GLib.Random.int_range(min, max + 1);
}

// Return the x coordinate to print the given text centered on the board.

int board_centered_x(string text) {
    return (int) (BOARD_X + (BOARD_ACTUAL_WIDTH - text.length) / 2);
}

// Print the given text on the given row, centered on the board.

void print_centered(string text, int y) {
    set_cursor_position(y, board_centered_x(text));
    stdout.printf("%s\n", text);
}

// Print the title on the given row, centered on the board.

void print_centered_title(int y) {
    set_style(TITLE_INK);
    print_centered(TITLE, y);
    set_style(DEFAULT_INK);
}

void print_character(int y, int x, int char_code) {
    set_cursor_position(y + BOARD_Y, x + BOARD_X);
    stdout.printf("%s", ((char) char_code).to_string());
}

void print_board() {
    set_style(BOARD_INK);
    for (int i = 1; i <= BOARD_WIDTH; i++) {
        print_character(0, i + 1, BASE_CHARACTER + i); // top border
        print_character(BOARD_BOTTOM_Y, i + 1, BASE_CHARACTER + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1); // bottom border
    }
    for (int i = 1; i <= BOARD_HEIGHT; i++) {
        print_character(i , 0, BASE_CHARACTER + LAST_LETTER_NUMBER - i + 1); // left border
        print_character(i , 3 + BOARD_WIDTH, BASE_CHARACTER + BOARD_WIDTH + i); // right border
    }
    stdout.printf("\n");
    set_style(DEFAULT_INK);
}

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

void print_mistake_effect(string effect) {
    int x = board_centered_x(effect);
    hide_cursor();
    set_cursor_position(MESSAGES_Y, x);
    set_style(MISTAKE_EFFECT_INK);
    stdout.printf("%s\n", effect);
    set_style(DEFAULT_INK);
    Thread.usleep(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.

string message(int length) {
    const int LETTER_PAUSE = 1000000; // microseconds
    int y = 0;
    int x = 0;
    string letters = "";
    hide_cursor();
    for (int i = 0; i < length; i++) {
        int letter_number = random_int_in_inclusive_range(
            FIRST_LETTER_NUMBER,
            LAST_LETTER_NUMBER);
        letters += ((char) (BASE_CHARACTER + letter_number)).to_string();
        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);
        Thread.usleep(LETTER_PAUSE);
        set_style(DEFAULT_INK);
        print_character(y, x, SPACE);
    }
    show_cursor();
    return letters;
}

string accept_message() {
    set_style(INPUT_INK);
    set_cursor_position(INPUT_Y, INPUT_X);
    string result = accept_string("? ").up();
    set_style(DEFAULT_INK);
    erase_line_from(INPUT_Y, INPUT_X);
    return result;
}

void play() {
    int score = 0;
    int mistakes = 0;

    print_centered_title(1);
    print_board();

    while (true) {
        int message_length = random_int_in_inclusive_range(
            MIN_MESSAGE_LENGTH,
            MAX_MESSAGE_LENGTH
            );
        string message_received = message(message_length);
        string message_understood = accept_message();
        if (message_received != message_understood) {
            mistakes += 1;
            switch (mistakes) {
                case 1:
                    print_mistake_effect("The table begins to shake!");
                    break;
                case 2:
                    print_mistake_effect("The light bulb shatters!");
                    break;
                case 3:
                    print_mistake_effect("Oh, no!  A pair of clammy hands grasps your neck!");
                    return;

            }
        }
        else {
            score += message_length;
            if (score >= MAX_SCORE) {
                print_centered("Whew!  The spirits have gone!", MESSAGES_Y);
                print_centered("You live to face another day!", MESSAGES_Y + 1);
                return;
            }
        }
    }
}

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

void main() {
    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();
    stdout.printf("\n");
}

Sine Wave

/*
Sine Wave

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

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

Written in 2023-08, 2023-09.

Last modified 20260828T1048+0200.
*/

using GLib; // Math needed

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void reset_screen_attributes() {
    stdout.printf("\x1B[0m");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

void clear_screen() {
    erase_screen();
    reset_screen_attributes();
    move_cursor_home();
}

void print_credits() {
    stdout.printf("Sine Wave\n\n");
    stdout.printf("Original version in BASIC:\n");
    stdout.printf("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n\n");
    stdout.printf("This version in Vala:\n");
    stdout.printf("    Copyright (c) 2023, Marcos Cruz (programandala.net)\n");
    stdout.printf("    SPDX-License-Identifier: Fair\n\n");
    stdout.printf("Press Enter to start the program.\n");
    stdin.read_line();
}

string word[2];

void get_words() {
    string order[2] = {"first", "second"};
    for (int i = 0; i < word.length; i++) {
        stdout.printf(@"Enter the $(order[i]) word: ");
        word[i] = stdin.read_line();
    }

}

string repeat_char(char c, int times) {
    string s = "";
    for (int i = 0; i < times; i++) {
        s += c.to_string();
    }
    return s;
}

void draw() {
    bool even = false;
    double angle = 0.0;
    for (angle = 0.0; angle <= 40.0; angle += 0.25) {
        stdout.printf(repeat_char(' ', (int) (26 + 25 * Math.sin(angle))));
        stdout.printf("%s\n", word[(int) even]);
        even = !even;
    }
}

void main() {
    clear_screen();
    print_credits();
    clear_screen();
    get_words();
    clear_screen();
    draw();
}

Slots

// Slots
//     A slot machine simulation.

// Original version in BASIC:
//     Creative Computing (Morristown, New Jersey, USA).
//     Produced by Fred Mirabelle and Bob Harper on 1973-01-29.

// This version in Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-30.
//
// Last modified: 20260830T2333+0200.

using GLib;

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

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void hide_cursor() {
    stdout.printf("\x1B[?25l");
}

void show_cursor() {
    stdout.printf("\x1B[?25h");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

const int REELS = 3;
int reel[REELS];

const int IMAGES = 6;
string image[IMAGES];
const int BAR = 0; // position of "BAR" in `image`; forced in `init_once`
int color[IMAGES];
const int MAX_BET = 100;
const int MIN_BET = 1;

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

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

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

void print_instructions() {
    clear_screen();
    stdout.printf("You are in the H&M casino, in front of one of our\n");
    stdout.printf("one-arm bandits. Bet from %d to %d USD (or 0 to quit).\n\n",
        MIN_BET, MAX_BET);
    accept_string("Press Enter to start. ");
}

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

int won(int prize, int bet) {
    switch (prize) {
        case 2:
            stdout.printf("DOUBLE!\n");
            break;
        case 5:
            stdout.printf("*DOUBLE BAR*\n");
            break;
        case 10:
            stdout.printf("**TOP DOLLAR**\n");
            break;
        case 100:
            stdout.printf("***JACKPOT***\n");
            break;
        default:
            assert_not_reached();
    }
    stdout.printf("You won!\n");
    return (prize + 1) * bet;
}

void show_standings(int usd) {
    stdout.printf("Your standings are %d USD.\n", usd);
}

void print_reels() {
    move_cursor_home();
    for (int r = 0; r < REELS; r++) {
        set_style(color[reel[r]]);
        stdout.printf("[%s] ", image[reel[r]]);
    }
    set_style(NORMAL_STYLE);
    stdout.printf("\n");
}

void init_reels() {
    for (int i = 0; i < REELS; i++) {
        reel[i] = GLib.Random.int_range(0, IMAGES);
    }
}

void spin_reels() {
    const int SECONDS = 2;
    DateTime start_time = new GLib.DateTime.now_local();
    hide_cursor();
    do {
        init_reels();
        print_reels();
    } while
        (
            start_time.add_seconds(SECONDS)
            .compare(new GLib.DateTime.now_local())
            == 1
        );
    show_cursor();
}

int max(int n1, int n2) {
    return n1 > n2 ? n1 : n2;
}

void set_prize(ref int equals, ref int bars) {
    for (int i = 0; i < IMAGES; i++) {
        int count = 0;
        for (int r = 0; r < REELS; r++) {
            count += reel[r] == i ? 1 : 0;
        }
        equals = max(equals, count);
    }
    for (int r = 0; r < REELS; r++) {
        bars += reel[r] == BAR ? 1 : 0;
    }
}

void play() {
    int standings = 0;
    int bet = 0;

    init_reels();

    bool quit = false;
    do {
        while (true) {
            clear_screen();
            print_reels();
            bet = accept_integer("Your bet (or 0 to quit): ");
            if (bet > MAX_BET) {
                stdout.printf("House limits are %d USD.\n", MAX_BET);
                accept_string("Press Enter to try again. ");
            }
            else if (bet < MIN_BET) {
                string confirmation = accept_string("Type \"q\" to confirm you want to quit. ");
                if (confirmation == "q" || confirmation == "Q") {
                    quit = true;
                    break;
                }
            } else {
                break;
            }
        }

        if (!quit) {
            clear_screen();
            spin_reels();
            int equals = 0;
            int bars = 0;
            set_prize(ref equals, ref bars);

            switch (equals) {
                case 3:
                    if (bars == 3) {
                        standings += won(100, bet);
                    }
                    else {
                        standings += won(10, bet);
                    }
                    break;
                case 2:
                    if (bars == 2) {
                        standings += won(5, bet);
                    }
                    else {
                        standings += won(2, bet);
                    }
                    break;
                default:
                    stdout.printf("You lost.\n");
                    standings -= bet;
                    break;
            } // prize check

            show_standings(standings);
            accept_string("Press Enter to continue. ");
        }

    } while (!quit);

    show_standings(standings);

    if (standings < 0) {
        stdout.printf("Pay up!  Please leave your money on the terminal.\n");
    }
    else if (standings > 0) {
        stdout.printf("Collect your winnings from the H&M cashier.\n");
    }
    else {
        stdout.printf("Hey, you broke even.\n");
    }
}

void init_once() {
    const string BAR_IMAGE = " BAR  ";
    image = {BAR_IMAGE, " BELL ", "ORANGE", "LEMON ", " PLUM ", "CHERRY"};
    assert(image[BAR] == BAR_IMAGE);
    color = {
        FOREGROUND + WHITE,
        FOREGROUND + CYAN,
        FOREGROUND + YELLOW,
        FOREGROUND + BRIGHT + YELLOW,
        FOREGROUND + BRIGHT + WHITE,
        FOREGROUND + BRIGHT + RED };
}

void main() {
    print_credits();
    print_instructions();
    init_once();
    play();
}

Stars

// Stars

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

// This version in Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-26.
//
// Last modified: 20260828T1048+0200.

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok", "y", "yeah", "yes":
            return true;
        default:
            return false;
    }
}

void print_stars(int number) {
    for (int c = 0; c < number; c++) {
        stdout.printf("*");
    }
    stdout.printf("\n");
}

void main() {
    string name = accept_string("What is your name? ");
    stdout.printf("Hello, %s.\n", name);
    do {
        print_stars(accept_integer("How many stars do you want? "));
    } while (is_yes(accept_string("Do you want more stars? ")));
}

Strings

// Strings

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

// This version in Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-26.
//
// Last modified: 20260828T1048+0200.

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

string spc(int number) {
    var builder = new StringBuilder();
    for (int i = 0; i < number; i++) {
        builder.append(" ");
    }
    return builder.str;
}

void main() {
    string s = accept_string("Enter a string: ");
    int n = accept_integer("Enter an integer: ");

    // XXX TODO limit the bounds of `left$`, `mid$` and `right$`.
    // XXX TODO catch the errors of `val` and return 0 instead.

    stdout.printf(@"ASC(\"$s\") --> (int) \"$s\"[0] --> $((int) s[0])\n");
    stdout.printf(@"CHR$$($n) --> (char) $n --> \"$((char) n)\"\n");
    stdout.printf(@"LEFT$$(\"$s\", $n) --> \"$s\"[0 : $n] --> \"$(s[0 : n])\"\n");
    stdout.printf(@"LEFT$$(\"$s\", $n) --> \"$s\".substring(0, $n) --> \"$(s.substring(0,n))\"\n");
    stdout.printf(@"MID$$(\"$s\", $n) --> \"$s\"[$n :] --> \"$(s[n :])\"\n");
    stdout.printf(@"MID$$(\"$s\", $n) --> \"$s\".substring($n) --> \"$(s.substring(n))\"\n");
    stdout.printf(@"MID$$(\"$s\", $n, 3) --> \"$s\"[$n : $n + 3] --> \"$(s[n : n + 3])\"\n");
    stdout.printf(@"MID$$(\"$s\", $n, 3) --> \"$s\".substring($n, 3) --> \"$(s.substring(n, 3))\"\n");
    stdout.printf(@"RIGHT$$(\"$s\", $n) --> \"$s\"[-$n :] --> \"$(s[-n :])\"\n");
    stdout.printf(@"RIGHT$$(\"$s\", $n) --> \"$s\".substring(-$n) --> \"$(s.substring(-n))\"\n");
    stdout.printf(@"LEN(\"$s\") --> \"$s\".length --> $(s.length)\n");
    stdout.printf(@"VAL(\"$s\") --> float.parse(\"$s\") --> $(float.parse(s))\n");
    stdout.printf(@"STR$$($n) --> $n.to_string() --> \"$(n.to_string())\"\n");
    stdout.printf(@"SPC($n) --> spc($n) /* ad hoc method */ --> \"$(spc(n))\"");
}

Xchange

// Xchange

// Original version in BASIC:
//     Written by Thomas C. McIntire, 1979.
//     Published in "The A to Z Book of Computer Games", 1979.
//     https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up
//     https://github.com/chaosotter/basic-games

// This improved remake in Vala:
//     Copyright (c) 2026, Marcos Cruz (programandala.net)
//     SPDX-License-Identifier: Fair
//
// Written on 2026-08-30.
//
// Last modified: 20260830T1627+0200.

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

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

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

const int NORMAL_STYLE = 0;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_cursor_position(int line, int column) {
    stdout.printf("\x1B[%d;%dH", line, column);
}

void set_cursor_coordinate(Coordinate coordinate) {
    set_cursor_position(coordinate.y, coordinate.x);
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_line_to_end() {
    stdout.printf("\x1B[K");
}

void erase_screen_to_end() {
    stdout.printf("\x1B[J");
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

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

const string BLANK = "*";

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

const int CELLS = GRID_WIDTH * GRID_HEIGHT;

string[] pristine_grid;

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

const int FIRST_PLAYER = 0;
const int MAX_PLAYERS = 4;

string[, ] grid;

bool[] is_playing;

int players = 0;

const string QUIT_COMMAND = "X";

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

string accept_string(string prompt) {
    stdout.printf(prompt);
    return stdin.read_line().strip();
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

string accept_command(string prompt) {
    set_style(INPUT_INK);
    string command = accept_string(prompt);
    set_style(DEFAULT_INK);
    return command;
}

void press_enter(string prompt) {
    accept_string(prompt);
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok":
        case "y":
        case "yeah":
        case "yes":
            return true;
        default:
            return false;
    }
}

bool is_no(string s) {
    switch (s.down()) {
        case "n":
        case "no":
        case "nope":
            return true;
        default:
            return false;
    }
}

bool yes(string prompt) {
    while (true) {
        string answer = accept_command(prompt);
        if (is_yes(answer)) {
            return true;
        }
        if (is_no(answer)) {
            return false;
        }
    }
}

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

void print_title() {
    set_style(TITLE_INK);
    stdout.printf("Xchange\n");
    set_style(DEFAULT_INK);
}

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

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

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

void print_grid_title(int player) {
    set_cursor_position(GRIDS_Y, GRIDS_X + (player * GRIDS_GAP));
    stdout.printf("Player %d", player + 1);
}

struct Coordinate {
    int y;
    int x;
}

Coordinate cell_coordinate(int player, int cell) {
    int grid_y = cell / GRID_HEIGHT;
    int grid_x = cell % GRID_WIDTH;
    int title_margin = players > 1 ? 2 : 0;
    int y = GRIDS_Y + title_margin + grid_y;
    int x = GRIDS_X + (grid_x * CELLS_GAP) + (player * GRIDS_GAP);
    Coordinate result = Coordinate() { y = y, x = x };
    return result;
}

Coordinate grid_prompt_coordinate(int player) {
    Coordinate result = cell_coordinate(player, CELLS);
    result.y += 1;
    return result;
}

void print_grid(int player, int color = BOARD_INK) {
    if (players > 1) {
        print_grid_title(player);
    }
    set_style(color);
    for (int cell = 0; cell < CELLS; cell++) {
        set_cursor_coordinate(cell_coordinate(player, cell));
        stdout.printf(grid[player, cell]);
    }
    set_style(DEFAULT_INK);
}

void print_grids() {
    for (int player = 0; player < players; player++) {
        if (is_playing[player]) {
            print_grid(player);
        }
    }
    stdout.printf("\n");
    erase_screen_to_end();
}

void scramble_grid(int player) {
    for (int cell = 0; cell < CELLS; cell++) {
        int random_cell = GLib.Random.int_range(0, CELLS);
        // Exchange the contents of the current cell with that of the random one.
        string temp = grid[player, cell];
        grid[player, cell] = grid[player, random_cell];
        grid[player, random_cell] = temp;
    }
}

void init_grids() {
    for (int i = 0; i < pristine_grid.length; i++) {
        grid[0, i] = pristine_grid[i];
    }
    scramble_grid(0);
    for (int player = 1; player < players; player++) {
        for (int i = 0; i < pristine_grid.length; i++) {
            grid[player, i] = grid[0, i];
        }
    }
}

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

string player_prefix(int player) {
    return players > 1 ? @"Player $(player + 1): " : "";
}

Coordinate message_coordinate(int player, int y_inc = 0 ) {
    Coordinate result = grid_prompt_coordinate(player);
    result.y += 2 + y_inc;
    result.x = 1;
    return result;
}

void print_message(string message, int player, int y_inc = 0) {
    set_cursor_coordinate(message_coordinate(player, y_inc));
    stdout.printf("%s%s", player_prefix(player), message);
    erase_line_to_end();
    stdout.printf("\n");
}

void erase_message(int player) {
    set_cursor_coordinate(message_coordinate(player));
    erase_line_to_end();
}

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

string players_range_message() {
    return MAX_PLAYERS == 2 ? "1 or 2" : @"from 1 to $MAX_PLAYERS";
}

int number_of_players() {
    int players = 0;
    print_title();
    stdout.printf("\n");
    if (MAX_PLAYERS == 1) {
        players = 1;
    }
    else {
        while (players < 1 || players > MAX_PLAYERS) {
            string prompt = @"Number of players ($(players_range_message())): ";
            players = accept_integer(prompt);
        }
    }
    return players;
}

bool is_first_cell_of_grid_row(int cell) {
    return cell % GRID_WIDTH == 0;
}

bool is_last_cell_of_grid_row(int cell) {
    return (cell + 1) % GRID_WIDTH == 0;
}

bool are_cells_adjacent(int cell1, int cell2) {
    return (cell2 == cell1 + 1 && !is_first_cell_of_grid_row(cell2)) ||
        (cell2 == cell1 + GRID_WIDTH) ||
        (cell2 == cell1 - 1 && !is_last_cell_of_grid_row(cell2)) ||
        (cell2 == cell1 - GRID_WIDTH);
}

const int INVALID_POSITION = -1;

// If the given player's character cell is a valid move, i.e. it is adjacent to
// the blank cell, return the blank cell; otherwise return -1.

int position_to_cell(int player, int char_cell) {
    for (int cell = 0; cell < CELLS; cell++) {
        if (grid[player, cell] == BLANK) {
            if (are_cells_adjacent(char_cell, cell)) {
                return cell;
            }
            else {
                break;
            }
        }
    }
    print_message(@"Illegal move \"$(grid[player, char_cell])\".", player);
    return INVALID_POSITION;
}

// If the given player's command is valid, i.e. a grid character, return its
// position; otherwise return `INVALID_POSITION`.

int command_to_position(int player, string command) {
    if (command != BLANK) {
        for (int position = 0; position < CELLS; position++) {
            if (command == grid[player, position]) {
                return position;
            }
        }
    }
    print_message(@"Invalid character \"$command\".", player);
    return INVALID_POSITION;
}

void forget_player(int player) {
    is_playing[player] = false;
    print_grid(player, DEFAULT_INK);
}

void play_turn(int player) {
    int blank_position = 0;
    int character_position = 0;

    if (is_playing[player]) {
        while (true) {
            while (true) {
                Coordinate coordinate = grid_prompt_coordinate(player);
                set_cursor_coordinate(coordinate);
                erase_line_to_end();
                set_cursor_coordinate(coordinate);
                string command = accept_command("Move: ").up();
                if (command == QUIT_COMMAND) {
                    forget_player(player);
                    return;
                }
                int position = command_to_position(player, command);
                if (position != INVALID_POSITION) {
                    character_position = position;
                    break;
                }
            }
            int position = position_to_cell(player, character_position);
            if (position != INVALID_POSITION) {
                blank_position = position;
                break;
            }
        }
        erase_message(player);
        grid[player, blank_position] = grid[player, character_position];
        grid[player, character_position] = BLANK;
    }
}

void play_turns() {
    for (int player = 0; player < players; player++) {
        play_turn(player);
    }
}

bool is_someone_playing() {
    for (int player = 0; player < players; player++) {
        if (is_playing[player]) {
            return true;
        }
    }
    return false;
}

bool has_an_empty_grid(int player) {
    for (int i = 0; i < CELLS; i++) {
        if (grid[player, i] != "") {
            return false;
        }
    }
    return true;
}

// If someone has won, print a message for every winner and return `true`
// otherwise just return `false`.

bool has_someone_won() {
    int winners = 0;
    for (int player = 0; player < players; player++) {
        if (is_playing[player]) {
            if (has_an_empty_grid(player)) {
                winners += 1;
                if (winners > 0) {
                    print_message(
                        "You're the winner" + (winners > 1 ? ", too!" : "!"),
                        player,
                        winners - 1);
                }
            }
        }
    }
    return winners > 0;
}

void init_game() {
    clear_screen();
    players = number_of_players();
    for (int player = 0; player < players; player++) {
        is_playing[player] = true;
    }
    clear_screen();
    print_title();
    init_grids();
    print_grids();
}

void play() {
    init_game();
    while (is_someone_playing()) {
        play_turns();
        print_grids();
        if (has_someone_won()) {
            break;
        }
    }
}

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

void init_once() {
    is_playing = new bool[MAX_PLAYERS];
    grid = new string[MAX_PLAYERS, CELLS];
    pristine_grid = new string[CELLS];
    const int FIRST_CHAR_CODE = (int) 'A';
    for (int cell = 0; cell < CELLS - 1; cell++) {
        pristine_grid[cell] = ((char) (FIRST_CHAR_CODE + cell)).to_string();
    }
    pristine_grid[CELLS - 1] = BLANK;
}

bool enough() {
    set_cursor_coordinate(grid_prompt_coordinate(FIRST_PLAYER));
    return !yes("Another game? ");
}

void main() {
    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. ");

    while (true) {
        play();
        if (enough()) {
            break;
        }
    }
    stdout.printf("So long…\n");
}

Z-End

// Z-End

// Original version in BASIC:

// A to Z Book of Computer Games, by Thomas C. McIntire, 1979.
//  - https://archive.org/details/A_to_Z_Book_of_Computer_Games_1979_Thomas_C_McIntire/page/n293/mode/2up
//  - https://github.com/chaosotter/basic-games/tree/master/games/A%20to%20Z%20Book%20of%20Computer%20Games/Z-End

// This version in Vala:
//   Copyright (c) 2026, Marcos Cruz (programandala.net)
//   SPDX-License-Identifier: Fair
//
// Written on 2026-08-27.
//
// Last modified: 20260828T1048+0200.


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

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

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

const int NORMAL_STYLE = 0;

const int DEFAULT_COLOR = WHITE;
const int ALPHABET_COLOR = MAGENTA;
const int INPUT_COLOR = GREEN;
const int INSTRUCTIONS_COLOR = YELLOW;
const int TITLE_COLOR = RED;

void move_cursor_home() {
    stdout.printf("\x1B[H");
}

void set_style(int style) {
    stdout.printf("\x1B[%dm", style);
}

void set_color(int n) {
    set_style(n + FOREGROUND);
}

void reset_attributes() {
    set_style(NORMAL_STYLE);
}

void erase_screen() {
    stdout.printf("\x1B[2J");
}

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

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

string accept_string(string prompt) {
    string result;
    set_color(INPUT_COLOR);
    stdout.printf(prompt);
    result = stdin.read_line().strip();
    set_color(DEFAULT_COLOR);
    return result;
}

bool is_sign(char character) {
    return character == '+' || character == '-';
}

bool is_digit(char character) {
    return character >= '0' && character <= '9';
}

bool is_integer(string s) {
    for (int c = 0; c < s.length; c++) {
        if (c == 0) {
            if (!is_sign(s[c]) && !is_digit(s[c])) {
                return false;
            }
        } else if (!is_digit(s[c])) {
            return false;
        }
    }
    return true;
}

int accept_integer(string prompt) {
    int result;
    while (true) {
        string s = accept_string(prompt);
        if (is_integer(s)) {
            result = int.parse(s);
            break;
        } else {
            stdout.printf("Integer expected.\n");
        }
    }
    return result;
}

bool is_yes(string s) {
    switch (s.down()) {
        case "ok", "y", "yeah", "yes":
            return true;
        default:
            return false;
    }
}

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

enum player_id {
    computer,
    human,
}

const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

void print_rules() {
    clear_screen();
    set_color(TITLE_COLOR);
    stdout.printf("Z-End\n\n");
    string answer = accept_string("Skip the rules? (Y/N) ");
    if (!is_yes(answer)) {
        set_color(INSTRUCTIONS_COLOR);
        stdout.printf("\n");
        stdout.printf("I'll print the alphabet, and you're first.  You type the number of letters\n");
        stdout.printf("that I should omit next time.  We take turns, and the limit per turn is five.\n");
        stdout.printf("The one that gets the 'Z' is the loser, and that's Z-End!\n");
        stdout.printf("\n");
        stdout.printf("Good luck, cuz I'm clever...\n");
        set_color(DEFAULT_COLOR);
    }
    stdout.printf("\n");
}

int first_letter;

int computer_pick() {
    int picked;
    int remaining_letters = (int) (alphabet.length - first_letter);
    if (remaining_letters < 6) {
        picked = remaining_letters - 1;
    }
    else if (remaining_letters > 10) {
        picked = GLib.Random.int_range(1, 5 + 1);
    }
    else {
        picked = 1;
    }
    stdout.printf(@"My pick is $picked.\n");
    return picked;
}

int human_pick() {
    int picked;
    while (true) {
        picked = accept_integer("Your turn (1-5) ");
        set_color(DEFAULT_COLOR);
        if (picked < 1 || picked > 5) {
            stdout.printf("Illegal entry -- must be in range 1 to 5!\n");
        }
        else {
            break;
        }
    }
    return picked;
}

bool game_over() {
    return first_letter == alphabet.length - 1;
}

void print_alphabet(int omitted_letters = 0) {
    first_letter += omitted_letters;
    set_color(ALPHABET_COLOR);
    stdout.printf(alphabet[first_letter :]);
    stdout.printf("\n\n");
    set_color(DEFAULT_COLOR);
}

void print_result(player_id player) {
    stdout.printf("Z-End -- ");
    switch (player) {
        case player_id.computer:
            stdout.printf("Ha ha!\n");
            break;
        case player_id.human:
            stdout.printf("Oops!\n");
            break;
    }
}

int pick(player_id player) {
    switch (player) {
        case player_id.computer:
            return computer_pick();
        case player_id.human:
            return human_pick();
    }
    assert_not_reached();
}

bool playing(player_id player) {
    int picked = pick(player);
    print_alphabet(picked);
    if (game_over()) {
        print_result(player);
    }
    return !game_over();
}

void play() {
    first_letter = 0;
    print_alphabet();
    while (playing(player_id.human) && playing(player_id.computer)) { }
}

bool again() {
    stdout.printf("\n");
    string answer = accept_string("Do it again (Y/N) ");
    return is_yes(answer);
}

void main() {
    print_rules();
    do {
        play();
    } while (again());
    stdout.printf("\nGoodbye.\n");
}

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 Harbour
Conversion de old BASIC-programas a Harbour por aprender lu elementari de ti-ci lingue.
Basics of Hare
Conversion de old BASIC-programas a Hare 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 Zen C
Conversion de old BASIC-programas a Zen C 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