Basics of Vala

Description of the page content

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

Tags:

3D Plot

/*
3D Plot

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

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

Related pages

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

External related links