Basics of Raku

Description of the page content

Conversion of old BASIC programs to Raku 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 Raku:
#    Copyright (c) 2024, Marcos Cruz (programandala.net)
#    SPDX-License-Identifier: Fair

# Written on 2024-12-02.

# Last modified: 20241211T1232+0100.

constant $WIDTH = 56;

sub clear_screen {
    print "\e[0;0H\e[2J";
}

sub print_credits {
    put "3D Plot\n";
    put 'Original version in BASIC:';
    put "    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n";
    put 'This version in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair\n";
    put 'Press Enter to start the program.';
    prompt '';
}

sub a($z) {
    30 * exp(-$z * $z / 100);
}

sub draw {
    for -30, -28.5 ... 30 -> $x {
        my $line = ' ' x $WIDTH;
        my $l = 0;
        my $y1 = 5 * (sqrt(900 - $x * $x) / 5).Int;
        for $y1, $y1 - 5 ... -$y1 -> $y {
            my $z = (25 + a(sqrt($x * $x + $y * $y)) - .7 * $y).Int;
            if $z > $l {
                $l = $z;
                $line.substr-rw($z, 1) = '*';
            }
        }
        put $line;
    }
}

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 Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12.
#
# Last modified: 20241211T1933+0100.

sub clear_screen {

    print "\e[0;0H\e[2J";

}

sub print_credits {

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

}

sub print_instructions {

    clear_screen;
    put "Bagels";
    put "Number guessing game\n";
    put "I am thinking of a three-digit number that has no two digits the same.";
    put "Try to guess it and I will give you clues as follows:\n";
    put "   PICO   - one digit correct but in the wrong position";
    put "   FERMI  - one digit correct and in the right position";
    put "   BAGELS - no digits correct";
    prompt "\nPress Enter to start. ";

}

constant $DIGITS = 3;

# Return three random digits in an integer array.
sub random_number(--> Array) {

    my Int @random_digit = ();

    while @random_digit.elems < $DIGITS {
        loop {
            @random_digit.push((0 .. 9).pick);
            if @random_digit.unique.elems == @random_digit.elems {
                last;
            }
            @random_digit.pop
        }
    }

    return @random_digit;

}

# Print the given prompt and get a three-digit number from the user.
sub input(Str $prompt --> Array) {

    my Int @user_digit = ();
    PROMPT: loop {
        my $input = prompt($prompt);
        if $input.chars != $DIGITS {
            put "Remember it's a {$DIGITS}-digit number.";
            next PROMPT;
        }
        for $input.comb -> $digit {
            try {
                @user_digit.push(+$digit);
                CATCH {
                    default {
                        put "What?";
                        next PROMPT;
                    }
                }
            }
        }
        if @user_digit.unique.elems < $DIGITS {
            put "Remember my number has no two digits the same.";
            next PROMPT;
        }
        last;
    }
    return @user_digit;

}

# Return `True` if the given string is "yes" or a synonym.
sub is_yes(Str $answer --> Bool ) {

    return $answer.lc (elem) ["ok", "y", "yeah", "yes"]

}

# Return `True` if the given string is "no" or a synonym.
sub is_no(Str $answer --> Bool) {

    return $answer.lc (elem) ["n", "no", "nope"];

}

# Print the given prompt, wait until the user enters a valid yes/no
# string, and return `True` for "yes" or `False` for "no".
sub yes(Str $prompt --> Bool) {

    loop {
        my $answer = prompt($prompt);
        if is_yes($answer) { return True };
        if is_no($answer) { return False };
    }

}

# Init and run the game loop.
sub play {

    constant $TRIES = 20;
    my Int $score = 0;
    my Int $fermi; # counter
    my Int $pico;  # counter
    loop {
        clear_screen;
        my @computer_number = random_number;
        put "O.K.  I have a number in mind.";
        for 1 .. $TRIES -> $guess {
            my @user_number = input(sprintf("Guess #%02d: ", $guess));
            $fermi = 0;
            $pico = 0;
            for 0 ..^ $DIGITS -> $i {
                for 0 ..^ $DIGITS -> $j {
                    if @user_number[$i] == @computer_number[$j] {
                        if $i == $j {
                            $fermi += 1;
                        } else {
                            $pico += 1;
                        }
                    }
                }
            }
            print "PICO " x $pico;
            print "FERMI " x $fermi;
            if $pico + $fermi == 0 { print "BAGELS" };
            put "";
            if $fermi == $DIGITS { last };
        }
        if $fermi == $DIGITS {
            put "You got it!!!";
            $score += 1;
        } else {
            put "Oh well.";
            put "That's $TRIES guesses.  My number was {@computer_number.join}.";
        }
        if !yes("Play again? ") { last };
    }
    if $score != 0 {
        put "A {$score}-point bagels, buff!!";
    }
    put "Hope you had fun.  Bye.";

}

print_credits;
print_instructions;
play;

Bug

# Bug

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

# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12-11/12.
#
# Last modified: 20241212T1012+0100.

class Bug {

    has Bool $.body is rw = False;
    has Bool $.neck is rw = False;
    has Bool $.head is rw = False;
    has Int $.feelers is rw = 0;
    has Str $.feeler_type is rw = '';
    has Bool $.tail is rw = False;
    has Int $.legs is rw = 0;

}

class Player {

    has Str $.pronoun is rw = '';
    has Str $.possessive is rw = '';
    has Bug $.bug is rw = Bug.new();

}

my Player $computer = Player.new;
my Player $human = Player.new;

# Bug body parts.
#
constant $BODY = 0;
constant $NECK = 1;
constant $HEAD = 2;
constant $FEELER = 3;
constant $TAIL = 4;
constant $LEG = 5;
constant @PART_NAME = ('body', 'neck', 'head', 'feeler', 'tail', 'leg');
constant @PART_QUANTITY = (1, 1, 1, 2, 1, 6);

# Bug body attributes.
#
constant $BODY_HEIGHT = 2;
constant $FEELER_LENGTH = 4;
constant $LEG_LENGTH = 2;
constant $MAX_FEELERS = 2;
constant $MAX_LEGS = 6;
constant $NECK_LENGTH = 2;

# Move the cursor to the home position.
#
sub move_cursor_home {

    print "\e[H";

}

# Erase the screen.
#
sub erase_screen {

    print "\e[2J";

}

# Set the color.
#
sub set_color(Int $color) {

    print "\e[{$color}m";

}

# Reset the attributes.
#
sub reset_attributes {

    constant $RESET_ALL = 0;
    set_color($RESET_ALL);

}

# Erase the screen, reset the attributes and move the cursor to the home position.
sub clear_screen {

    erase_screen;
    reset_attributes;
    move_cursor_home;

}

# Move the cursor up the given number of positions (1 by default).
sub move_cursor_up(Int $n = 1) {

    print "\e[{$n}A";

}

# Erase the entire current line and move the cursor to the start of the line.
#
sub erase_line {

    print "\e[2K";

}

# Move the cursor to the previous row, without changing the column position,
# and erase its line.
#
sub erase_previous_line {

    move_cursor_up;
    erase_line;

}

# Clear the screen, display the credits and wait for a keypress.
#
sub print_credits {

    clear_screen;
    put "Bug\n";
    put 'Original version in BASIC:';
    put '    Brian Leibowitz, 1978.';
    put "    Creative computing (Morristown, New Jersey, USA), 1978.\n";
    put 'This version in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair\n";
    prompt 'Press Enter to read the instructions. ';

}

constant $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:
';

# Return the given string padded with the given char at the right, up to the
# given width.
#
sub left_justify(Str $s, Int $width, Str $char --> Str) {

    return $s ~ (' ' x ($width - $s.chars));

}

# Print a table with the bug parts' description.
#
sub print_parts_table {

    constant $COLUMNS = 3;
    constant $COLUMN_WIDTH = 8;
    constant $COLUMN_SEPARATION = 2;

    # Headers
    my $header = ('Number', 'Part', 'Quantity');
    for 0 ..^ $COLUMNS -> $i {
        print left_justify($header[$i], $COLUMN_WIDTH + $COLUMN_SEPARATION, ' ');
    }
    put '';

    # Rulers
    for 0 ..^ $COLUMNS -> $i {
        print '-' x $COLUMN_WIDTH;
        print $i == $COLUMNS ?? ''  !! ' ' x $COLUMN_SEPARATION;
    }
    put '';

    # Data
    for $BODY .. $LEG -> $part {
        print left_justify(($part + 1).Str, $COLUMN_WIDTH + $COLUMN_SEPARATION, ' ');
        print left_justify(@PART_NAME[$part].tc, $COLUMN_WIDTH + $COLUMN_SEPARATION, ' ');
        put @PART_QUANTITY[$part];
    }

}

# Clear the screen, print the instructions and wait for a keypress.
#
sub print_instructions {

    clear_screen;
    put 'Bug';
    put $INSTRUCTIONS;
    print_parts_table;
    prompt "\nPress Enter to start. ";

}

# Print a bug head.
#
sub print_head {

    put '        HHHHHHH';
    put '        H     H';
    put '        H O O H';
    put '        H     H';
    put '        H  V  H';
    put '        HHHHHHH';

}

# Print the given bug.
#
sub print_bug(Bug $bug) {

    if $bug.feelers > 0 {
        for 0 ..^ $FEELER_LENGTH {
            print '        ';
            for 0 ..^ $bug.feelers {
                print ' ', $bug.feeler_type;
            }
            put '';
        }
    }
    if $bug.head {
        print_head;
    }
    if $bug.neck {
        for 0 ..^ $NECK_LENGTH {
            put '          N N';
        }
    }
    if $bug.body {
        put '     BBBBBBBBBBBB';
        for 0 ..^ $BODY_HEIGHT -> $i {
            put '     B          B';
        }
        if $bug.tail {
            put 'TTTTTB          B';
        }
        put '     BBBBBBBBBBBB';
    }
    if $bug.legs > 0 {
        for 0 ..^ $LEG_LENGTH -> $i {
            print '    ';
            for 0 ..^ $bug.legs -> $j {
                print ' L';
            }
            put '';
        }
    }

}

# Return `True` if the given bug is finished; otherwise return `False`.
#
sub finished(Bug $bug --> Bool) {

    return ($bug.feelers == $MAX_FEELERS and $bug.tail and $bug.legs == $MAX_LEGS);

}

# Array to convert a number to its equilavent text.
#
constant @AS_TEXT = (
    'no',
    'a',
    'two',
    'three',
    'four',
    'five',
    'six' ); # $MAX_LEGS

# Return a string containing the given number and noun in their proper form.
#
sub plural(Int $number, Str $noun --> Str) {

    return "@AS_TEXT[$number] $noun {($number > 1)  ?? 's'  !! ''}";

}

# Add the given part to the given player's bug.
#
sub add_part(Int $part, Player $player --> Bool) {

    my Bool $changed = False;
    given ($part) {
        when $BODY {
            if $player.bug.body {
                put ", but {$player.pronoun} already have a body.";
            } else {
                put "; {$player.pronoun} now have a body:";
                $player.bug.body = True;
                $changed = True;
            }
        }
        when $NECK {
            if $player.bug.neck {
                put ", but {$player.pronoun} already have a neck.";
            } elsif not $player.bug.body {
                put ", but {$player.pronoun} need a body first.";
            } else {
                put "; {$player.pronoun} now have a neck:";
                $player.bug.neck = True;
                $changed = True;
            }
        }
        when $HEAD {
            if $player.bug.head {
                put ", but {$player.pronoun} already have a head.";
            } elsif not $player.bug.neck {
                put ", but {$player.pronoun} need a a neck first.";
            } else {
                put "; {$player.pronoun} now have a head:";
                $player.bug.head = True;
                $changed = True;
            }
        }
        when $FEELER {
            if $player.bug.feelers == $MAX_FEELERS {
                put ", but {$player.pronoun} have two feelers already.";
            } elsif not $player.bug.head {
                put ", but  {$player.pronoun} need a head first.";
            } else {
                $player.bug.feelers += 1;
                put "; {$player.pronoun} now have {plural($player.bug.feelers, 'feeler')}:";
                $changed = True;
            }
        }
        when $TAIL {
            if $player.bug.tail {
                put ", but {$player.pronoun} already have a tail.";
            } elsif not $player.bug.body {
                put ", but {$player.pronoun} need a body first.";
            } else {
                put "; {$player.pronoun} now have a tail:";
                $player.bug.tail = True;
                $changed = True;
            }
        }
        when $LEG {
            if $player.bug.legs == $MAX_LEGS {
                put ", but {$player.pronoun} have {@AS_TEXT[$MAX_LEGS]} feet already.";
            } elsif not $player.bug.body {
                put ", but, {$player.pronoun} need a body first.";
            } else {
                $player.bug.legs += 1;
                put "; {$player.pronoun} now have {plural($player.bug.legs, 'leg')}:";
                $changed = True;
            }
        }
    }
    return $changed;

}

# Ask the user to press the Enter key, wait for the input, then erase the
# prompt text.
#
sub prompt_dice {

    prompt('Press Enter to roll the dice. ');
    erase_previous_line;

}

# Play one turn for the given player, rolling the dice and updating his bug.
#
sub turn(Player $player) {

    prompt_dice;
    my Int $part = (0 .. 5).pick;
    print "{$player.pronoun.tc} rolled a {$part + 1} ({@PART_NAME[$part]})";
    if add_part($part, $player) {
        put '';
        print_bug($player.bug);
    }
    put '';

}

# Print a message about the winner.
#
sub print_winner {

    if finished($human.bug) and finished($computer.bug) {
        put 'Both of our bugs are finished in the same number of turns!';
    } elsif finished($human.bug) {
        put "{$human.possessive} bug is finished.";
    } elsif finished($computer.bug) {
        put "{$computer.possessive} bug is finished.";
    }

}

# Return `True` if either bug is finished, i.e. the game ending condition.
#
sub game_over(--> Bool) {

    return finished($human.bug) or finished($computer.bug);

}

# Execute the game loop.
#
sub play {

    clear_screen;
    while not game_over() {
        turn($human);
        turn($computer);
    };
    print_winner;

}

# Init the players' data before a new game.
#
sub init {

    $human.pronoun = 'you';
    $human.possessive = 'Your';
    $human.bug.feeler_type = 'A';
    $computer.pronoun = 'I';
    $computer.possessive = 'My';
    $computer.bug.feeler_type = 'F';

}

init;
print_credits;
print_instructions;
play;
put 'I hope you enjoyed the game, play it again soon!!';

Bunny

# Bunny
#
# Original version in BASIC:
#   Creative Computing (Morristown, New Jersey, USA), 1978.
#
# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2024-12-03.
#
# Last modified: 20241211T1235+0100.

sub clear_screen {

    print "\e[0;0H\e[2J";

}

sub print_credits {

    put "Bunny\n";
    put 'Original version in BASIC:';
    put "    Creative Computing (Morristown, New Jersey, USA), 1978.\n";
    put 'This version in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair\n";
    prompt 'Press Enter to start the program. ';

}

constant $WIDTH = 53;
my $line = ' ' x $WIDTH;

constant LETTER = 'B', 'U', 'N', 'N', 'Y';
constant LETTERS = LETTER.elems;

constant EOL = -1; # end of line identifier;

constant 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;

constant DATA_LEN = DATA.elems;

my $data_pointer = 0;

sub datum(--> Int) {

    $data_pointer += 1;
    return DATA[$data_pointer - 1];

}

sub draw {

    while $data_pointer < DATA_LEN {
        my $first_column = datum;
        if $first_column == EOL {
            put $line;
            $line = ' ' x $WIDTH;
        } else {
            my $last_column = datum;
            for $first_column .. $last_column -> $column {
                $line.substr-rw($column, 1) = LETTER[$column % LETTERS];
            }
        }
    }

}

clear_screen;
print_credits;
clear_screen;
draw;

Diamond

# Diamond

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

# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair

# Written on 2024-12-02.

# Last modified: 20241211T1235+0100.

constant $LINES = 17;

for 1..($LINES / 2 + 1) -> $i {
    for 1 .. ($LINES + 1) / 2 - $i + 1 -> $j {
        print ' ';
    }
    for 1 .. $i * 2 - 1 -> $j {
        print '*';
    }
    put '';
}

for 1 .. $LINES / 2 -> $i {
    for  1 .. $i + 1 -> $j {
        print ' ';
    }
    for 1 .. (($LINES + 1) / 2 - $i) * 2 - 1 -> $j {
        print '*';
    }
    put '';
}

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 Raku:
#   Copyright (c) 2025, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2025-03-23, 2025-04-17, 2025-07-31.
#
# Last modified: 20250731T1922+0200.
#
# Acknowledgment:
#   The following Python port was used as a reference of the original
#   variables: <https://github.com/jquast/hamurabi.py>.
#

# ==============================================================================

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

constant $STYLE_OFF = +20;
constant $FOREGROUND = +30;
constant $BACKGROUND = +40;
constant $BRIGHT = +60;

constant $NORMAL = 0;
constant $RESET_ALL = $NORMAL;
constant $BOLD = 1;
constant $DIM = 2;
constant $ITALIC = 3;
constant $UNDERLINE = 4;
constant $UNDERSCORE = $UNDERLINE;
constant $BLINK = 5;
constant $OVERLINE = 6;
constant $RAPID_BLINK = $OVERLINE;
constant $INVERT = 7;
constant $REVERSE = $INVERT;
constant $HIDDEN = 8;
constant $CROSSED_OUT = 9;
constant $STRIKE = $CROSSED_OUT;

constant $NO_STYLE = $STYLE_OFF;

sub move_cursor_home {
    print "\e[H";
}

sub erase_screen {
    print "\e[2J";
}

sub set_style(Int $style) {
    print "\e[{$style}m";
}

sub reset_attributes {
    set_style $RESET_ALL;
}

sub clear_screen {
    erase_screen;
    #reset_attributes;
    move_cursor_home;
}

sub hide_cursor {
    print "\e[?25l";
}

sub show_cursor {
    print "\e[?25h";
}

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

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

enum Result <Very_Good, Not_Too_Bad, Bad, Very_Bad>;

my Int $acres;
my Int $bushels_eaten_by_rats;
my Int $bushels_harvested;
my Int $bushels_harvested_per_acre;
my Int $bushels_in_store;
my Int $bushels_to_feed_with;
my Int $dead;
my Int $infants;
my Int $irritation; # counter (0 .. 99)
my Int $population;
my Int $starved_people_percentage;
my Int $total_dead;

constant $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;
grainthe 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.";

sub instructions(--> Str) {
    return sprintf($INSTRUCTIONS,
    $MIN_HARVESTED_BUSHELS_PER_ACRE, $MAX_HARVESTED_BUSHELS_PER_ACRE, $YEARS);
}

sub print_instructions {
    set_style($INSTRUCTIONS_INK);
    put instructions;
    set_style($DEFAULT_INK);
}

sub pause(Str $prompt = '> ') {
    set_style($INPUT_INK);
    my $s = prompt $prompt;
    set_style($DEFAULT_INK);
}

# Print the given prompt and wait until the user enters an integer.

sub input(Str $prompt --> Int) {
    my Int $n;
    set_style($INPUT_INK);
    loop {
        try {
            $n = +prompt $prompt;
            CATCH {
                default {
                    put 'Integer expected.';
                    next;
                }
            }
        }
        last;
    }
    set_style($DEFAULT_INK);
    return floor $n;
}

constant $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 Raku:
  Copyright (c) 2025, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair";

sub print_credits {
    set_style($TITLE_INK);
    put $CREDITS;
    set_style($DEFAULT_INK);
}

sub ordinal_suffix(Int $n --> Str) {
    given $n {
        when 1  { return 'st'; }
        when 2  { return 'nd'; }
        when 3  { return 'rd'; }
        default { return 'th'; }
    }
}

# Return the description of the given year as the previous one.
#
sub previous(Int $year --> Str) {

    if $year == 0 {
        return 'the previous year';
    } else {
        return sprintf("your %d%s year", $year, ordinal_suffix($year));
    }

}

# Return the proper wording for `n` persons, using the given or default words
# for singular and plural forms.
#
sub persons(Int $n, Str $singular = 'person', Str $plural = 'people' --> Str) {
    given $n {
        when 0  { return 'nobody'; }
        when 1  { return 'one ' ~ $singular; }
        default { return "$n " ~ $plural; }
    }
}

sub print_annual_report(Int $year) {

    clear_screen;
    set_style($SPEECH_INK);
    put 'Hammurabi, I beg to report to you.';
    set_style($DEFAULT_INK);

    put sprintf(
        "\nIn %s, %s starved and %s %s born.",
        previous($year),
        persons($dead),
        persons($infants, 'infant', 'infants'),
        $infants > 1  ?? 'were'  !! 'was'
    );

    $population += $infants;

    if $year > 0 and rand <= $PLAGUE_CHANCE {
        $population = ($population / 2).Int;
        set_style($WARNING_INK);
        put 'A horrible plague struck!  Half the people died.';
        set_style($DEFAULT_INK);
    }

    put "The population is $population.";
    put "The city owns $acres acres.";
    put "You harvested $bushels_harvested bushels ($bushels_harvested_per_acre per acre).";
    if $bushels_eaten_by_rats > 0 {
        put "The rats ate $bushels_eaten_by_rats bushels.";
    }
    put "You have $bushels_in_store bushels in store.";
    $bushels_harvested_per_acre =
        ($RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * rand).Int +
        $MIN_HARVESTED_BUSHELS_PER_ACRE;
    put "Land is trading at $bushels_harvested_per_acre bushels per acre.\n";

}

sub say_bye {

    set_style($DEFAULT_INK);
    put "\nSo long for now.\n";

}

sub quit_game {

    say_bye;
    exit(0);

}

sub relinquish {
    set_style($SPEECH_INK);
    put "\nHammurabi, I am deeply irritated and cannot serve you anymore.";
    put 'Please, get yourself another steward!';
    set_style($DEFAULT_INK);
    quit_game;
}

sub increase_irritation {
    $irritation += 1 + (0 .. $IRRITATION_STEP).pick;
    if $irritation >= $MAX_IRRITATION { relinquish; } # this never returns
}

sub print_irritated(Str $adverb) {

    printf("The steward seems %s irritated.\n", $adverb);

}

sub show_irritation {
    given True {
        when $irritation < $IRRITATION_STEP      { }
        when $irritation < $IRRITATION_STEP * 2  { print_irritated('slightly'); }
        when $irritation < $IRRITATION_STEP * 3  { print_irritated('quite'); }
        when $irritation < $IRRITATION_STEP * 4  { print_irritated('very'); }
        default { print_irritated('profoundly'); }
    }
}

# Print a message begging to repeat an ununderstandable input.
#
sub beg_repeat {

    increase_irritation; # this may never return
    set_style($SPEECH_INK);
    put 'I beg your pardon?  I did not understand your order.';
    set_style($DEFAULT_INK);
    show_irritation;

}

# Print a message begging to repeat a wrong input, because there's only `n`
# items of `name`.
#
sub beg_think_again(Int $n, Str $name) {

    increase_irritation; # this may never return
    set_style($SPEECH_INK);
    printf("I beg your pardon?  You have only %d %s.  Now then…\n", $n, $name);
    set_style($DEFAULT_INK);
    show_irritation;

}

# Buy or sell land.
#
sub trade {

    my Int $acres_to_buy;
    my Int $acres_to_sell;
    my Bool $ok;

    loop {
        $acres_to_buy = input('How many acres do you wish to buy? (0 to sell): ');
        if $acres_to_buy < 0 {
            beg_repeat; # this may never return
            next;
        }
        if $bushels_harvested_per_acre * $acres_to_buy <= $bushels_in_store { last; }
        beg_think_again($bushels_in_store, 'bushels of grain');
    }

    if $acres_to_buy != 0 {

        printf("You buy %d acres.\n", $acres_to_buy);
        $acres += $acres_to_buy;
        $bushels_in_store -= $bushels_harvested_per_acre * $acres_to_buy;
        printf("You now have %d acres and %d bushels.\n", $acres, $bushels_in_store);

    } else {

        loop {
            $acres_to_sell = input('How many acres do you wish to sell?: ');
            if $acres_to_sell < 0 {
                beg_repeat; # this may never return
                next;
            }
            if $acres_to_sell < $acres { last; }
            beg_think_again($acres, 'acres');
        }

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

    }

}

# Feed the people.
#
sub feed {

    my Bool $ok;

    loop {
        $bushels_to_feed_with = input('How many bushels do you wish to feed your people with?: ');
        if $bushels_to_feed_with < 0 {
            beg_repeat; # this may never return
            next;
        }
        # Trying to use more grain than is in silos?
        if $bushels_to_feed_with <= $bushels_in_store { last; }
        beg_think_again($bushels_in_store, 'bushels of grain');
    }

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

}

# Seed the land.
#
sub seed {

    my Int $acres_to_seed;
    my Bool $ok;

    loop {

        $acres_to_seed = input('How many acres do you wish to seed?: ');
        if $acres_to_seed < 0 {
            beg_repeat; # this may never return
            next;
        }
        if $acres_to_seed == 0 { last; }

        # Trying to seed more acres than you own?
        if $acres_to_seed > $acres {
            beg_think_again($acres, 'acres');
            next;
        }

        # Enough grain for seed?
        if ($acres_to_seed / $ACRES_A_BUSHEL_CAN_SEED).Int > $bushels_in_store {
            beg_think_again(
                $bushels_in_store,
                sprintf(
                    "bushels of grain,\nand one bushel can seed %d acres",
                    $ACRES_A_BUSHEL_CAN_SEED
                );
            );
            next;
        }

        # Enough people to tend the crops?
        if $acres_to_seed <= $ACRES_A_PERSON_CAN_SEED * $population { last; }

        beg_think_again(
            $population,
            sprintf(
                "people to tend the fields,\nand one person can seed %d acres",
                $ACRES_A_PERSON_CAN_SEED
            );
        );

    }

    my $bushels_used_for_seeding = ($acres_to_seed / $ACRES_A_BUSHEL_CAN_SEED).Int;
    printf("You seed %d acres using %d bushels.\n", $acres_to_seed, $bushels_used_for_seeding);
    $bushels_in_store -= $bushels_used_for_seeding;
    printf("You now have %d bushels.\n", $bushels_in_store);

    # A bountiful harvest!
    $bushels_harvested_per_acre = (1 .. 5).pick;
    $bushels_harvested = $acres_to_seed * $bushels_harvested_per_acre;
    $bushels_in_store += $bushels_harvested;

}

sub is_even(Int $n --> Bool) {

    return $n %% 2 == 0;

}

sub check_rats {

    my $rat_chance = (1 .. 5).pick;
    $bushels_eaten_by_rats = is_even($rat_chance)  ?? ($bushels_in_store / $rat_chance).Int  !! 0;
    $bushels_in_store -= $bushels_eaten_by_rats;

}

# Set the variables to their values in the first year.
#
sub init {
    $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;
}

sub print_result(Result $result) {
    set_style($RESULT_INK);
    given $result {
        when <Very_Good> {
            put 'A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could';
            put 'not have done better!';
        }
        when <Not_Too_Bad> {
            put "Your performance could have been somewat better, but really wasn't too bad at";
            printf(
                "all. %d people would dearly like to see you assassinated, but we all have our\n",
                Int($population * .8 * rand)
            );
            put 'trivial problems.';
        }
        when <Bad> {
            put 'Your heavy-handed performance smacks of Nero and Ivan IV.  The people';
            put '(remaining) find you an unpleasant ruler and, frankly, hate your guts!';
        }
        when <Very_Bad> {
            put 'Due to this extreme mismanagement you have not only been impeached and thrown';
            put 'out of office but you have also been declared national fink!!!';
        }
    }
    set_style($DEFAULT_INK);
}

sub print_final_report {

    clear_screen;

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

    my $acres_per_person = $acres / $population;
    printf(
        "You started with %d acres per person and ended with %d.\n\n",
        $ACRES_PER_PERSON,
        $acres_per_person
    );

    given True {
        when $starved_people_percentage > 33, $acres_per_person < 7  { print_result(Result::<Very_Bad>); }
        when $starved_people_percentage > 10, $acres_per_person < 9  { print_result(Result::<Bad>); }
        when $starved_people_percentage > 3, $acres_per_person < 10  { print_result(Result::<Not_Too_Bad>); }
        default { print_result(Result::<Very_Good>); }
    }

}

sub check_starvation(Int $year) {

        # How many people has been fed?
        my $fed_people = ($bushels_to_feed_with / $BUSHELS_TO_FEED_A_PERSON).Int;

        if $population > $fed_people {

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

            # Starve enough for impeachment?
            if $dead > (.45 * $population).Int {
                set_style($WARNING_INK);
                put "\nYou starved $dead people in one year!!!\n";
                set_style($DEFAULT_INK);
                print_result(Result::<Very_Bad>);
                quit_game;
            }

        }

}

sub govern {

    init;

    print_annual_report(0);

    for 1 .. $YEARS  -> $year {

        trade;
        feed;
        seed;
        check_rats;

        # Let's have some babies
        $infants = ((1 .. 5).pick * (20 * $acres + $bushels_in_store) / $population / 100 + 1).Int;

        check_starvation($year);

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

    }

}

clear_screen;
print_credits;

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

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

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


High Noon

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

# Terminal {{{1
# ==============================================================

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

constant $STYLE_OFF = +20;
constant $FOREGROUND = +30;
constant $BACKGROUND = +40;
constant $BRIGHT = +60;

constant $NORMAL = 0;
constant $RESET_ALL = $NORMAL;
constant $BOLD = 1;
constant $DIM = 2;
constant $ITALIC = 3;
constant $UNDERLINE = 4;
constant $UNDERSCORE = $UNDERLINE;
constant $BLINK = 5;
constant $OVERLINE = 6;
constant $RAPID_BLINK = $OVERLINE;
constant $INVERT = 7;
constant $REVERSE = $INVERT;
constant $HIDDEN = 8;
constant $CROSSED_OUT = 9;
constant $STRIKE = $CROSSED_OUT;

constant $NO_STYLE = $STYLE_OFF;

# Move the cursor to the home position.
sub move_cursor_home {

    print "\e[H";

}

# Erase the screen.
sub erase_screen {

    print "\e[2J";

}

# Reset the attributes.
sub reset_attributes {

    set_color $RESET_ALL;

}

# Erase the screen, reset the attributes and move the cursor to the home position.
sub clear_screen {

    erase_screen;
    reset_attributes;
    move_cursor_home;

}

# Set the color.
sub set_color(Int $color) {

    print "\e[{$color}m";

}

# Set the attribute.
sub set_attribute(Int $attr) {

    print "\e[0;{$attr}m";

}

# Make the cursor invisible.
sub hide_cursor {

    print "\e[?25l";

}

# Make the cursor visible.
sub show_cursor {

    print "\e[?25h";

}

# Global variables and constants {{{1
# =============================================================

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

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

my $distance = 0; # distance between both gunners, in paces
my $strategy = ''; # player's strategy

my $player_bullets = 0;
my $opponent_bullets = 0;

# User input {{{1
# =============================================================

# Print the given prompt and wait until the user enters an integer.
#
sub input_int(Str $prompt = '' --> Int) {

    my Int $n;
    set_color INPUT_INK;
    loop {
        try {
            $n = +prompt $prompt;
            CATCH {
                default {
                    put 'Integer expected.';
                    next;
                }
            }
        }
        last;
    }
    set_color DEFAULT_INK;
    return floor $n;

}

# Print the given prompt and wait until the user enters a string.
#
sub input_string(Str $prompt = '' --> Str) {

    set_color INPUT_INK;
    my $result = prompt $prompt;
    set_color DEFAULT_INK;
    return $result;

}

# Print the given prompt and wait until the user presses Enter.
#
sub press_enter(Str $prompt) {

    input_string($prompt);

}

# Return `True` if the given string is 'yes' or a synonym.
#
sub is_yes(Str $s --> Bool) {

    return $s.lc (elem) ["ok", "y", "yeah", "yes"]

}

# Return `True` if the given string is 'no' or a synonym.
#
sub is_no(Str $s --> Bool) {

    return $s.lc (elem) ["n", "no", "nope"];

}

# Print the given prompt, wait until the user enters a valid yes/no string,
# and return `True` for 'yes' or `False` for 'no'.
#
sub yes(Str $prompt --> Bool) {

    loop {
        my $answer = input_string prompt;
        if is_yes $answer { return True; }
        if is_no $answer { return False; }
    }

}

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

# Print the title at the current cursor position.
#
sub print_title {

    set_color TITLE_INK;
    put 'High Noon';
    set_color DEFAULT_INK;

}

sub print_credits {

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

}

sub print_instructions {

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

}

# Game loop {{{1
# =============================================================

sub plural_suffix(Int $n --> Str) {

    given $n {
        when 1  { return ''; }
        default { return 's'; }
    }

}

sub print_shells_left {

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

}

sub print_check {

    put '******************************************************';
    put '*                                                    *';
    put '*                 BANK OF DODGE CITY                 *';
    put "*                  CASHIER'S RECEIT                  *";
    put '*                                                    *';
    printf("* CHECK NO. %04i                   AUGUST %iTH, 1889 *\n",
        (0 .. 1000).pick,
        10 + (0 .. 10).pick);
    put '*                                                    *';
    put '*                                                    *';
    put '*       PAY TO THE BEARER ON DEMAND THE SUM OF       *';
    put '*                                                    *';
    put '* TWENTY THOUSAND DOLLARS-------------------$20,000  *';
    put '*                                                    *';
    put '******************************************************';

}

sub get_reward {

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

}

sub move_the_opponent {

    my $paces = 2 + (0 .. 8).pick;
    printf("Black Bart moves %s 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.
#
sub maybe_move_the_opponent(Int $silent = False --> Bool) {

    if (0 .. 2).pick == 0 {; # 50% chances
        move_the_opponent;
        return True;
    } else {
        if not $silent {
            put 'Black Bart stands still.';
        }
        return False;
    }

}

sub missed_shot(--> Bool) {

    return 1.rand * 10 <= $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`.
#
sub the_opponent_fires_and_kills(--> Bool) {

    put 'Black Bart fires…';
    $opponent_bullets -= 1;
    if missed_shot() {
        put 'A miss…';
        given $opponent_bullets {
            }
            when 3  {
                put 'Whew, were you lucky. That bullet just missed your head.';
            }
            when 2  {
                put 'But Black Bart got you in the right shin.';
            }
            when 1  {
                put 'Though Black Bart got you on the left side of your jaw.';
            }
            when 0  {
                put 'Black Bart must have jerked the trigger.';
        }
    } else {
        if $strategy == 'j' {
            put "That trick just saved yout life. Black Bart's bullet";
            put 'was stopped by the wood sides of the trough.';
        } else {
            put 'Black Bart shot you right through the heart that time.';
            put "You went kickin' with your boots on.";
            return True;
        }
    }
    return False;

}

# Handle the opponent's strategy and return a flag with the result: if the
# opponent runs or kills the player, return `True`; otherwise return `False`.
#
sub the_opponent_kills_or_runs(--> Bool) {

    if $distance >= 10 or $player_bullets == 0 {
        if maybe_move_the_opponent(True) { return False; }
    }
    if $opponent_bullets > 0 {
        return the_opponent_fires_and_kills;
    } else {
        if $player_bullets > 0 {
            if (0 .. 2).pick == 0 {; # 50% chances
                put 'Now is your chance, Black Bart is out of bullets.';
            } else {
                put 'Black Bart just hi-tailed it out of town rather than face you';
                put 'without a loaded gun. You can rest assured that Black Bart';
                put "won't ever show his face around this town again.";
                return True;
            }
        }
    }
    return False;

}

sub play {

    $distance = INITIAL_DISTANCE;
    my $watering_troughs = 0;
    $player_bullets = INITIAL_BULLETS;
    $opponent_bullets = INITIAL_BULLETS;

    SHOWDOWN:
    loop {

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

        $strategy = input_string('What is your strategy? ').lc;

        given $strategy {
            when 'a' { # advance

                loop {
                    my $paces = input_int('How many paces do you advance? ');
                    if $paces < 0 {
                        put 'None of this negative stuff, partner, only positive numbers.';
                    } elsif $paces > 10 {
                        put 'Nobody can walk that fast.';
                    } else {
                        $distance -= $paces;
                        last;
                    }
                }

            }
            when 's' { # stand still

                put 'That move made you a perfect stationary target.';

            }
            when 'f' { # fire

                if $player_bullets == 0 {

                    put "You don't have any bullets left.";

                } else {

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

                }

            }
            when 'j' { # jump

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

            }
            when 'g' { # give up

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

            }
            when 't' { # turn tail and run

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

            }
            default {
                put "You sure aren't going to live very long if you can't even follow directions.";
            }

        } # strategy given

        if the_opponent_kills_or_runs() { last; }

        if $player_bullets + $opponent_bullets == 0 {
            put 'The showdown must end, because nobody has bullets left.';
            last;
        }

        put '';

    } # showdown loop

}

# Main {{{1
# =============================================================

clear_screen;
print_credits;

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

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

Math

# Math
#
# Original version in BASIC:
#   Example included in Vintage BASIC 1.0.3.
#   http://www.vintage-basic.net
#
# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12-03.
#
# Last modified: 20241203T1812+0100.

my $n;

loop {
    try {
        $n = +prompt 'Enter a number: ';
        CATCH {
            default {
                put 'Number expected.';
                next;
            }
        }
    }
    last;
}

put "ABS($n) -> abs($n) -> ", abs($n);
put "ATN($n) -> atan($n) -> ", atan($n);
put "COS($n) -> cos($n) -> ", cos($n);
put "EXP($n) -> exp($n) -> ", exp($n);
put "INT($n) -> floor($n) -> ", floor($n);
put "LOG($n) -> log($n) -> ", log($n);
put "SGN($n) -> sign($n) -> ", sign($n);
put "SQR($n) -> sqrt($n) -> ", sqrt($n);
put "TAN($n) -> tan($n) -> ", tan($n);

Mugwump

# Mugwump
#
# Original version in BASIC:
#   Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).
#   Slightly modified by Bob Albrecht of People's Computer Company.
#   Published by Creative Computing (Morristown, New Jersey, USA), 1978.
#   - https://www.atariarchives.org/basicgames/showpage.php?page=114
#   - http://vintage-basic.net/games.html
#
# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2024-12-07/11.
#
# Last modified: 20241211T1237+0100.

sub clear_screen {

    print "\e[0;0H\e[2J";

}

constant $GRID_SIZE = 10;
constant $TURNS = 10;
constant $MUGWUMPS = 4;

class Mugwump {
    has Int $.x is rw = 0;
    has Int $.y is rw = 0;
    has Bool $.hidden is rw = False;
}

my @mugwump of Mugwump;

for 0 ..^ $MUGWUMPS {
    push @mugwump, Mugwump.new;
}

my Int $found; # counter

# Print the given prompt, wait until the user enters a valid integer and
# return it.
#
sub prompt_number(Str $prompt --> Int) {

    my Int $n;
    my Str $s = '';
    constant $ERROR = 'Valid integer expected.';

    loop {
        loop {
            $s = prompt($prompt);
            if not $s eq '' {
                last;
            }
            put $ERROR;
        }
        try {
            $n = (+$s).Int;
            CATCH {
                default {
                    put $ERROR;
                    next;
                }
            }
        }
        last;
    }

    return $n;

}

# Return `True` if the given string is "yes" or a synonym.
#
sub is_yes(Str $answer --> Bool) {

    return $answer.lc (elem) ["ok", "y", "yeah", "yes"]

}

# Return `True` if the given string is "no" or a synonym.
#
sub is_no(Str $answer --> Bool) {

    return $answer.lc (elem) ["n", "no", "nope"];

}

# Print the given prompt, wait until the user enters a valid yes/no string,
# and return `True` for "yes" or `False` for "no".
#
sub yes(Str $prompt --> Bool) {

    loop {
        my $answer = prompt($prompt);
        if is_yes($answer) { return True };
        if is_no($answer) { return False };
    }

}

# Clear the screen, print the credits and ask the user to press enter.
#
sub print_credits {

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

}

# Clear the screen, print the instructions and ask the user to press enter.
#
sub print_instructions {

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

}

# Init the mugwumps' positions, `hidden` flags and count.
#
sub hide_mugwumps {

    #say @mugwump[0]; # XXX TMP --> Mugwump.new
    for 0 ..^ $MUGWUMPS  -> $m {
        @mugwump[$m].x = (0 ..^ $GRID_SIZE).pick;
        @mugwump[$m].y = (0 ..^ $GRID_SIZE).pick;
        @mugwump[$m].hidden = True;
    }
    $found = 0 ; # counter

}

# Print the given prompt, wait until the user enters a valid coord and return
# it.
#
sub get_coord(Str $prompt --> Int) {

    my Int $coord;
    loop {
        $coord = prompt_number($prompt);
        if $coord < 0 or $coord >= $GRID_SIZE {
            put "Invalid value $coord: not in range [0, {$GRID_SIZE - 1}].";
        } else {
            last;
        }
    }
    return $coord;

}

# Return `True` if the given mugwump is hidden in the given coords.
#
sub is_here(Int $m, Int $x, Int $y --> Bool) {

    return (@mugwump[$m].hidden and @mugwump[$m].x == $x and @mugwump[$m].y == $y);

}

# Return the distance between the given mugwump and the given coords.
#
sub distance(Int $m, Int $x, Int $y --> Int) {

    return Int(sqrt(((@mugwump[$m].x - $x) ** 2) + ((@mugwump[$m].y - $y) ** 2)));

}

# Return a plural ending (default: "s") if the given number is greater than 1;
# otherwise return a singular ending (default: an empty string).
#
sub plural(Int $n, $plural = 's', $singular = '' --> Str) {

    return $n > 1 ?? $plural !! $singular;

}

# Run the game.
#
sub play {

    my Int $x;
    my Int $y;
    my Int $turn; # counter
    loop { # game
        clear_screen;
        hide_mugwumps;
        TURNS_LOOP: for 1 ..^ $TURNS -> $t {
            $turn = $t;
            put "Turn number $turn\n";
            put "What is your guess (in range [0, {$GRID_SIZE - 1}])?";
            $x = get_coord('Distance right of homebase (x-axis): ');
            $y = get_coord('Distance above homebase (y-axis): ');
            put "\nYour guess is ($x, $y).";
            for 0 ..^ $MUGWUMPS  -> $m {
                if is_here($m, $x, $y) {
                    @mugwump[$m].hidden = False;
                    $found += 1;
                    put "You have found mugwump $m!";
                    if $found == $MUGWUMPS {
                        last TURNS_LOOP;
                    }
                }
            }
            for 0 ..^ $MUGWUMPS  -> $m {
                if @mugwump[$m].hidden {
                    put "You are {distance($m, $x, $y)} units from mugwump $m.";
                }
            }
            put '';
        } # turns
        if $found == $MUGWUMPS {
            put "You got them all in $turn turn{plural($turn)}!\n";
            put "That was fun! let's play again…";
            put 'Four more mugwumps are now in hiding.';
        } else {
            put "Sorry, that's $TURNS tr{plural($TURNS, "ies", "y")}.\n";
            put "Here is where they're hiding:";
            for 0 ..^ $MUGWUMPS  -> $m {
                if @mugwump[$m].hidden {
                    put "Mugwump $m is at ({@mugwump[$m].x}, {@mugwump[$m].y}).";
                }
            }
        }
        put '';
        if not yes('Do you want to play again? ') {
            last;
        }
    } # game

}

print_credits;
print_instructions;
play;

Name

# Name

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

# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair

# Written on 2024-12-02.

# Last modified: 20241203T1831+0100.

my $name = prompt 'What is your name? ';

my Int $number;

loop {
    try {
        $number = +prompt 'Enter a number: ';
        CATCH {
            default {
                put 'Number expected.';
                next;
            }
        }
    }
    last;
}

for 1 .. $number {
    put "Hello, $name!";
}

Poetry

# Poetry

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

# This improved remake in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12-12/13.
#
# Last modified: 20241213T1544+0100.

# Terminal {{{1
# ==============================================================

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

constant $STYLE_OFF = +20;
constant $FOREGROUND = +30;
constant $BACKGROUND = +40;
constant $BRIGHT = +60;

constant $NORMAL = 0;
constant $RESET_ALL = $NORMAL;

# Move the cursor to the home position.
sub move_cursor_home {

    print "\e[H";

}

# Erase the screen.
sub erase_screen {

    print "\e[2J";

}

# Set the color.
sub set_color(Int $color) {

    print "\e[{$color}m";

}

# Reset the attributes.
sub reset_attributes {

    set_color($RESET_ALL);

}

# Erase the screen, reset the attributes and move the cursor to the home position.
sub clear_screen {

    erase_screen;
    reset_attributes;
    move_cursor_home;

}

# Globals {{{1
# =============================================================

constant $DEFAULT_INK = $FOREGROUND + $WHITE;
constant $INPUT_INK = $FOREGROUND + $BRIGHT + $GREEN;
constant $TITLE_INK = $FOREGROUND + $BRIGHT + $RED;

# Title and credits {{{1
# =============================================================

sub print_title {

    set_color($TITLE_INK);
    put 'Poetry';
    set_color($DEFAULT_INK);

}

sub print_credits {

    print_title;
    put "\nOriginal version in BASIC:";
    put '    Unknown author.';
    put "    Published in \"BASIC Computer Games\",";
    put "    Creative Computing (Morristown, New Jersey, USA), 1978.\n";

    put 'This improved remake in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put '    SPDX-License-Identifier: Fair';

}

sub press_enter {

    set_color($INPUT_INK);
    prompt "\nPress the Enter key to start. ";
    set_color($DEFAULT_INK);

}

# Main {{{1
# =============================================================

# Is the given integer even?
#
sub is_even(Int $n --> Bool ) {

    return $n % 2 == 0;

}

sub play {

    constant $MAX_PHRASES_AND_VERSES = 20;

    my $action = 0;
    my $phrase = 0;
    my $phrases_and_verses = 0;
    my $verse_chunks = 0;

    VERSE: loop {

        my $manage_the_verse_continuation = True;
        my $maybe_add_comma = True;

        given $action {
            when 0 .. 1  {
                given $phrase {
                    when 0 { print 'MIDNIGHT DREARY' }
                    when 1 { print 'FIERY EYES' }
                    when 2 { print 'BIRD OR FIEND' }
                    when 3 { print 'THING OF EVIL' }
                    when 4 { print 'PROPHET' }
                }
            }
            when 2  {
                given $phrase {
                    when 0 { print 'BEGUILING ME'; $verse_chunks = 2 }
                    when 1 { print 'THRILLED ME' }
                    when 2 { print 'STILL SITTING…'; $maybe_add_comma = False }
                    when 3 { print 'NEVER FLITTING'; $verse_chunks = 2 }
                    when 4 { print 'BURNED' }
                }
            }
            when 3  {
                given $phrase {
                    when 0 { print 'AND MY SOUL' }
                    when 1 { print 'DARKNESS THERE' }
                    when 2 { print 'SHALL BE LIFTED' }
                    when 3 { print 'QUOTH THE RAVEN' }
                    when 4 { if $verse_chunks != 0 { print 'SIGN OF PARTING' } }
                }
            }
            when 4  {
                given $phrase {
                    when 0 { print 'NOTHING MORE' }
                    when 1 { print 'YET AGAIN' }
                    when 2 { print 'SLOWLY CREEPING' }
                    when 3 { print '…EVERMORE' }
                    when 4 { print 'NEVERMORE' }
                }
            }
            when 5  {
                $action = 0;
                put '';
                if $phrases_and_verses > $MAX_PHRASES_AND_VERSES {
                    put '';
                    $verse_chunks = 0;
                    $phrases_and_verses = 0;
                    $action = 2;
                    next VERSE;
                } else {
                    $manage_the_verse_continuation = False;
                }
            }
        }

        if $manage_the_verse_continuation {

            sleep 0.250; # 250 ms

            if $maybe_add_comma and not ($verse_chunks == 0 or rand > 0.19) {
                print ',';
                $verse_chunks = 2;
            }

            if rand > 0.65 {
                put '';
                $verse_chunks = 0;
            } else {
                print ' ';
                $verse_chunks += 1;
            }

        }

        $action += 1;
        $phrase = (0 .. 4).pick;
        $phrases_and_verses += 1;

        if not ($verse_chunks > 0 or is_even($action)) {
            print '     ';
        }

    } # verse loop

}

clear_screen;
print_credits;
press_enter;
clear_screen;
play;

Russian Roulette

# Russian Roulette
#
# Original version in BASIC:
#   Creative Computing (Morristown, New Jersey, USA), ca. 1980.
#
# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12.
#
# Last modified: 20241210T1323+0100.

sub clear_screen {

    print "\e[0;0H\e[2J";

}

sub press_enter_to_start {

    prompt 'Press Enter to start. ';

}

sub print_credits {

    clear_screen;
    put "Russian Roulette\n";
    put 'Original version in BASIC:';
    put "    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n";
    put 'This version in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair\n";
    press_enter_to_start;

}

sub print_instructions {

    clear_screen;
    put 'Here is a revolver.';
    put "Type 'f' to spin chamber and pull trigger.";
    put "Type 'g' to give up, and play again.";
    put "Type 'q' to quit.\n";

}

sub play {

    clear_screen;
    my $times = 0;
    loop { # game loop
        print_instructions;
        $times = 0;
        PLAY_LOOP: loop {
            given (prompt '> ').lc {
                when 'f' { # fire
                    if (0 .. 99).pick > 83 {
                        put "Bang! You're dead!";
                        put 'Condolences will be sent to your relatives.';
                        last PLAY_LOOP;
                    } else {
                        $times += 1;
                        if $times > 10 {
                            put 'You win!';
                            put 'Let someone else blow his brains out.';
                            last PLAY_LOOP;
                        } else {
                            put 'Click.';
                        }
                    }
                }
                when 'g' { # give up
                    put 'Chicken!';
                    last PLAY_LOOP;
                }
                when 'q' { # quit
                    return;
                }
            }
        } # play loop
        press_enter_to_start;
    } # game loop

}

print_credits;
play;
put '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 Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2024-12-14.
#
# Last modified: 20241214T1104+0100.

# Terminal {{{1
# ==============================================================

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

constant $STYLE_OFF = +20;
constant $FOREGROUND = +30;
constant $BACKGROUND = +40;
constant $BRIGHT = +60;

constant $NORMAL = 0;
constant $RESET_ALL = $NORMAL;

# Move the cursor to the home position.
#
sub move_cursor_home {

    print "\e[H";

}

# Erase the screen.
#
sub erase_screen {

    print "\e[2J";

}

# Set the color.
#
sub set_color(Int $color) {

    print "\e[{$color}m";

}

# Reset the attributes.
#
sub reset_attributes {

    set_color($RESET_ALL);

}

# Erase the screen, reset the attributes and move the cursor to the home position.
#
sub clear_screen {

    erase_screen;
    reset_attributes;
    move_cursor_home;

}

# Erase from the current cursor position to the end of the current line.
#
sub erase_line_right {

    print "\e[K";

}

# Erase the given line to the right of the given column.
#
sub erase_line_right_from(Int $line, Int $column) {

    set_cursor_position($line, $column);
    erase_line_right;

}

# Make the cursor invisible.
#
sub hide_cursor {

    print "\e[?25l";

}

# Make the cursor visible.
#
sub show_cursor {

    print "\e[?25h";

}

# Set the cursor position to the given coordinates (the top left position is 1, 1).
#
sub set_cursor_position(Int $line, Int $column) {

    print "\e[$line;{$column}H";

}

# Globals {{{1
# =============================================================

constant $TITLE = 'Seance';

constant $MAX_SCORE = 50;

constant $MAX_MESSAGE_LENGTH = 6;
constant $MIN_MESSAGE_LENGTH = 3;

constant $BASE_CHARACTER = ord('@');
constant $PLANCHETTE = '*';

constant $FIRST_LETTER_NUMBER = 1;
constant $LAST_LETTER_NUMBER = 26;

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

constant $MISTAKE_EFFECT_PAUSE = 3; # seconds

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

constant $INPUT_X = $BOARD_X;
constant $INPUT_Y = $BOARD_Y + $BOARD_BOTTOM_Y + 4;

constant $MESSAGES_Y = $INPUT_Y;

# Input {{{1
# =============================================================

# Print the given prompt and wait until the user enters a string.
#
sub input(Str $prompt = '' --> Str) {

    set_color($INPUT_INK);
    my $result = prompt $prompt;
    set_color($DEFAULT_INK);
    return $result;

}

# Board {{{1
# =============================================================

# Return the x coordinate to print the given text centered on the board.
#
sub board_centered_x(Str $text --> Int) {

    return ($BOARD_X + ($BOARD_ACTUAL_WIDTH - $text.chars) / 2).Int;

}

# Print the given text on the given row, centered on the board.
#
sub print_board_centered(Str $text, Int $y) {

    set_cursor_position($y, board_centered_x($text));
    put $text;

}

# Print the title at the current cursor position.
#
sub print_title {

    set_color($TITLE_INK);
    put $TITLE;
    set_color($DEFAULT_INK);

}

# Print the title on the given row, centered on the board.
#
sub print_board_centered_title(Int $y) {

    set_color($TITLE_INK);
    print_board_centered($TITLE, $y);
    set_color($DEFAULT_INK);

}

# Info {{{1
# =============================================================

sub print_credits {

    print_title;
    put "\nOriginal version in BASIC:";
    put '    Written by Chris Oxlade, 1983.';
    put '    https://archive.org/details/seance.qb64';
    put '    https://github.com/chaosotter/basic-games';
    put '';
    put 'This version in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair";

}

sub print_instructions {

    print_title;
    set_color($INSTRUCTIONS_INK);
    put "\nMessages from the Spirits are coming through, letter by letter.  They want you";
    put 'to remember the letters and type them into the computer in the correct order.';
    put 'If you make mistakes, they will be angry ― very angry…';
    put '';
    put "Watch for stars on your screen ― they show the letters in the Spirits'";
    put 'messages.';
    set_color($DEFAULT_INK);

}

# Board {{{1
# =============================================================

# Print the given character at the given board coordinates.
#
sub print_character(Int $y, Int $x, Str $a) {

    set_cursor_position($y + $BOARD_Y, $x + $BOARD_X);
    print $a;

}

sub print_board {

    set_color($BOARD_INK);

    for 1 .. $BOARD_WIDTH  -> $i {
        print_character(0, $i + 1, ($BASE_CHARACTER + $i).chr); # top border
        print_character($BOARD_BOTTOM_Y, $i + 1, ($BASE_CHARACTER + $LAST_LETTER_NUMBER - $BOARD_HEIGHT - $i + 1).chr); # bottom border
    }

    for 1 .. $BOARD_HEIGHT  -> $i {
        print_character($i , 0, ($BASE_CHARACTER + $LAST_LETTER_NUMBER - $i + 1).chr); # left border
        print_character($i , 3 + $BOARD_WIDTH, ($BASE_CHARACTER + $BOARD_WIDTH + $i).chr); # right border
    }

    put '';
    set_color($DEFAULT_INK);

}

# Output {{{1
# =============================================================

# Print the given mistake effect, wait a configured number of seconds and
# finally erase it.
#
sub print_mistake_effect(Str $effect) {

    my $x = board_centered_x($effect);
    hide_cursor;
    set_cursor_position($MESSAGES_Y, $x);
    set_color($MISTAKE_EFFECT_INK);
    put $effect;
    set_color($DEFAULT_INK);
    sleep $MISTAKE_EFFECT_PAUSE;
    erase_line_right_from($MESSAGES_Y, $x);
    show_cursor;

}

# Return a new message of the given length, after marking its letters on the
# board.
#
sub message(Int $length --> Str) {

    my Int $y;
    my Int $x;
    my Str $message = '';
    hide_cursor;
    for 1 .. $length  -> $i {
        my $letter_number = ($FIRST_LETTER_NUMBER .. $LAST_LETTER_NUMBER).pick;
        my $letter = ($BASE_CHARACTER + $letter_number).chr;
        $message = "$message$letter";
        given True {
            when $letter_number <= $BOARD_WIDTH  {
                # top border;
                $y = 1;
                $x = $letter_number + 1;
            }
            when $letter_number <= $BOARD_WIDTH + $BOARD_HEIGHT  {
                # right border;
                $y = $letter_number - $BOARD_WIDTH;
                $x = 2 + $BOARD_WIDTH;
            }
            when $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;
            }
            default {
                # left border;
                $y = 1 + $LAST_LETTER_NUMBER - $letter_number;
                $x = 1;
            }
        }
        set_color($PLANCHETTE_INK);
        print_character($y, $x, $PLANCHETTE);
        set_color($DEFAULT_INK);
        sleep 1;
        print_character($y, $x, ' ');

    }
    show_cursor;
    return $message;

}

# Accept a string from the user, erase it from the screen and return it.
#
sub message_understood(--> Str) {

    set_cursor_position($INPUT_Y, $INPUT_X);
    my $result = (prompt '? ').uc;
    erase_line_right_from($INPUT_Y, $INPUT_X);
    return $result;

}

# Main {{{1
# =============================================================

sub play {

    my $score = 0;
    my $mistakes = 0;
    print_board_centered_title(1);
    print_board;

    loop {
        my $message_length = ($MIN_MESSAGE_LENGTH .. $MAX_MESSAGE_LENGTH).pick;
        if message($message_length) ne message_understood() {
            $mistakes += 1;
            given $mistakes {
                when 1  {
                    print_mistake_effect('The table begins to shake!');
                }
                when 2  {
                    print_mistake_effect('The light bulb shatters!');
                }
                when 3  {
                    print_mistake_effect('Oh, no!  A pair of clammy hands grasps your neck!');
                    return;
                }
            }
        } else {
            $score += $message_length;
            if $score >= $MAX_SCORE {
                print_board_centered('Whew!  The spirits have gone!', $MESSAGES_Y);
                print_board_centered('You live to face another day!', $MESSAGES_Y + 1);
                return;
            }
        }
    }

}

set_color($DEFAULT_INK);
clear_screen;
print_credits;

prompt "\nPress the Enter key to read the instructions. ";
clear_screen;
print_instructions;

prompt "\nPress the Enter key to start. ";
clear_screen;
play;
put "\n";

Sine Wave

# Sine Wave

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

# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair

# Written on 2024-12-03.

# Last modified: 20241203T1832+0100.

sub clear_screen {

    print "\e[0;0H\e[2J";

}

my @word = '', '';

sub get_words {

    my @order = 'first', 'second';
    for 0, 1 -> $n {
        while @word[$n] == '' {
            @word[$n] = prompt "Enter the @order[$n] word: ";
        }
    }

}

sub print_credits {

    put "Sine Wave\n";
    put 'Original version in BASIC:';
    put "    Creative Computing (Morristown, New Jersey, USA, ca. 1980.\n";
    put 'This version in Raku:';
    put '    Copyright (c 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair\n";
    prompt 'Press Enter to start the program. ';

}

sub draw {

    my $even = False;
    loop (my $angle = 0.0; $angle <= 40.0; $angle += 0.25) {
        print ' ' x (26 + 25 * sin($angle)).Int;
        put @word[$even.Int];
        $even = not $even;
    }

}

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 Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12.
#
# Last modified: 20241211T1519+0100.

# Terminal {{{1
# ==============================================================

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

constant $STYLE_OFF = +20;
constant $FOREGROUND = +30;
constant $BACKGROUND = +40;
constant $BRIGHT = +60;

constant $NORMAL = 0;
constant $RESET_ALL = $NORMAL;
constant $BOLD = 1;
constant $DIM = 2;
constant $ITALIC = 3;
constant $UNDERLINE = 4;
constant $UNDERSCORE = $UNDERLINE;
constant $BLINK = 5;
constant $OVERLINE = 6;
constant $RAPID_BLINK = $OVERLINE;
constant $INVERT = 7;
constant $REVERSE = $INVERT;
constant $HIDDEN = 8;
constant $CROSSED_OUT = 9;
constant $STRIKE = $CROSSED_OUT;

constant $NO_STYLE = $STYLE_OFF;

# Move the cursor to the home position.
sub move_cursor_home {

    print "\e[H";

}

# Erase the screen.
sub erase_screen {

    print "\e[2J";

}

# Reset the attributes.
sub reset_attributes {

    set_color($RESET_ALL);

}

# Erase the screen, reset the attributes and move the cursor to the home position.
sub clear_screen {

    erase_screen;
    reset_attributes;
    move_cursor_home;

}

# Set the color.
sub set_color(Int $color) {

    print "\e[{$color}m";

}

# Set the attribute.
sub set_attribute(Int $attr) {

    print "\e[0;{$attr}m";

}

# Make the cursor invisible.
sub hide_cursor {

    print "\e[?25l";

}

# Make the cursor visible.
sub show_cursor {

    print "\e[?25h";

}

# Globals {{{1
# ==============================================================

constant @image = (' BAR  ', ' BELL ', 'ORANGE', 'LEMON ', ' PLUM ', 'CHERRY');
constant $BAR = 0; # position of "BAR" in `image`.

constant @color = (
    $FOREGROUND + $WHITE,
    $FOREGROUND + $CYAN,
    $FOREGROUND + $YELLOW,
    $FOREGROUND + $BRIGHT + $YELLOW,
    $FOREGROUND + $BRIGHT + $WHITE,
    $FOREGROUND + $BRIGHT + $RED);

constant $MAX_BET = 100;
constant $MIN_BET = 1;

# Info {{{1
# ==============================================================

sub print_credits {

    clear_screen;
    put 'Slots';
    put "A slot machine simulation.\n";
    put 'Original version in BASIC:';
    put '    Creative computing (Morristown, New Jersey, USA).';
    put "    Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n";
    put 'This version in Raku:';
    put '    Copyright (c) 2024, Marcos Cruz (programandala.net)';
    put "    SPDX-License-Identifier: Fair\n";
    prompt 'Press Enter for instructions.';

}

sub print_instructions {

    clear_screen;
    put 'You are in the H&M casino, in front of one of our';
    put "one-arm bandits. Bet from $MIN_BET to $MAX_BET USD (or 0 to quit).\n\n";
    prompt 'Press Enter to start.';

}

# Main {{{1
# ==============================================================

sub won(Int $prize, Int $bet --> Int) {

    given $prize {
        when 2  { put 'DOUBLE!' };
        when 5  { put '*DOUBLE BAR*' };
        when 10  { put '**TOP DOLLAR**' };
        when 100  { put '***JACKPOT***' };
    };
    put 'You won!';
    return ($prize + 1) * $bet;

}

sub show_standings(Int $usd) {

    print "Your standings are $usd USD.\n";

}

sub print_reels(@reel) {

    move_cursor_home;
    for @reel -> $r {
        set_color(@color[$r]);
        print "[@image[$r]]";
    }
    set_attribute($NORMAL);
    put '';

}

sub init_reels(@reel) {

    my $images = @image.elems;
    for 0 ..^ @reel.elems -> $i {
        @reel[$i] = (0 ..^ $images).pick;
    }

}

sub spin_reels(@reel) {

    constant $SECONDS = 2;
    my $first_second = now;

    hide_cursor;
    while (now - $first_second) < $SECONDS {
        init_reels(@reel);
        print_reels(@reel);
        sleep 0.1
    }
    show_cursor;

}

# Return the number of repeated elements in the given list.
#
sub repeated(@list) {

    my $count = @list.repeated.elems;
    return $count > 0 ?? $count + 1 !! 0;

}

# Return the number of equals and bars in the given `reel` array.
#
sub prize(@reel) {

    my $equals = repeated(@reel);
    my $bars = repeated(grep $BAR, @reel);
    return ($equals, $bars);

}

sub prompt_number(Str $prompt --> Int) {

    my Int $n;

    loop {
        try {
            $n = (+prompt $prompt).Int;
            CATCH {
                default {
                    return 0;
                }
            }
        }
        last;
    }
    return $n;

}

sub play {

    my $standings = 0;
    my $bet = 0;
    my @reel = (0, 0, 0);
    my $equals = 0;
    my $bars = 0;

    init_reels(@reel);

    PLAY: loop {

        BET: loop {
            clear_screen;
            print_reels(@reel);
            $bet = prompt_number('Your bet (or 0 to quit): ');
            if $bet > $MAX_BET {
                print "House limits are $MAX_BET USD.\n";
                prompt 'Press Enter to try again.';
            } elsif $bet < $MIN_BET  {
                if (prompt "Type \"q\" to confirm you want to quit.").lc eq 'q' {
                    last PLAY;
                }
            } else {
                last BET;
            }
        }

        clear_screen;
        spin_reels(@reel);
        ($equals, $bars) = prize(@reel);

        given $equals {
            when 3  {
                if $bars == 3 {
                    $standings += won(100, $bet);
                } else {
                    $standings += won(10, $bet);
                }
            }
            when 2  {
                if $bars == 2 {
                    $standings += won(5, $bet);
                } else {
                    $standings += won(2, $bet);
                }
            }
            default {
                put 'You lost.';
                $standings -= $bet;
            }
        } # prize check
        show_standings($standings);
        prompt 'Press Enter to continue.';
    } # play loop

    show_standings($standings);

    if $standings < 0  {
        put 'Pay up!  Please leave your money on the terminal.';
    } elsif $standings == 0  {
        put 'Hey, you broke even.';
    } elsif $standings > 0  {
        put 'Collect your winnings from the H&M cashier.';
    }

}

print_credits;
print_instructions;
play;

Stars

# Stars
#
# Original version in BASIC:
#   Example included in Vintage BASIC 1.0.3.
#   http://www.vintage-basic.net
#
# This version in Raku:
#   Copyright (c) 2024, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written on 2024-12-03.
#
# Last modified: 20241203T1833+0100.

my $name = prompt 'What is your name? ';
print "Hello, $name.\n";

STARS: loop {

    my Int $number;

    loop {
        try {
            $number = +prompt 'How many stars do you want? ';
            CATCH {
                default {
                    put 'Number expected.';
                    next;
                }
            }
        }
        last;
    }
    put '*' x $number;

    my $answer = (prompt 'Do you want more stars? ').lc;
    if not $answer  ('ok', 'y', 'yeah', 'yes') {
        last STARS;
    }

}

Strings

# Strings
#
# Original version in BASIC:
#   Example included in Vintage BASIC 1.0.3.
#   http://www.vintage-basic.net
#
# This version in Raku:
#   Copyright (c) 2024, 2025, Marcos Cruz (programandala.net)
#   SPDX-License-Identifier: Fair
#
# Written in 2024-12.
#
# Last modified: 20250731T1726+0200.

sub prompt_number(Str $prompt --> Int) {

    my Int $n;

    loop {
        try {
            $n = +prompt $prompt;
            CATCH {
                default {
                    put 'Integer expected.';
                    next;
                }
            }
        }
        last;
    }
    return floor $n;

}

my $s = prompt 'Enter a string: ';
my $n = prompt_number 'Enter an integer: ';

put "";

put "ASC(\"$s\") --> ord(\"$s\") --> $(ord($s))";
put "CHR\$($n) --> $n.chr --> \"", $n.chr, '"';
put "LEFT\$(\"$s\", $n) --> \"$s\".comb.head($n).join --> '", $s.comb.head($n).join, "'";

put "MID\$(\"$s\", $n) --> \"$s\".comb[$n - 1 .. *].join --> '", $s.comb[$n - 1 .. *].join, "'";
put "MID\$(\"$s\", $n, 3) --> \"$s\".comb[$n - 1 .. $n -1 + 3 - 1].join --> '", $s.comb[$n - 1 .. $n - 1 + 3 - 1].join, "'";

put "RIGHT\$(\"$s\", $n) --> \"$s\".comb.tail($n).join --> '", $s.comb.tail($n).join, "'";

put "LEN(\"$s\") --> \"$s\".chars --> ", $s.chars;

put "VAL(\"$s\") --> val(\"$s\") --> ", val($s);

sub val_or_0(Str $s) {
    my Any $n;
    try { $n = +$s; CATCH { default { $n = 0 } } }
    return $n
}

put "VAL(\"$s\") --> val_or_0(\"$s\") --> ", val_or_0($s), " # custom subroutine with `try`";

put "STR$($n) --> $n.Str --> \"", $n.Str, '"';

put "SPC($n) --> ' ' x $n --> '", ' ' x $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 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 Retro
Conversion of old BASIC programs to Retro in order to learn the basics of this language.
Basics of Rexx
Conversion of old BASIC programs to Rexx in order to learn the basics of this language.
Basics of Ring
Conversion of old BASIC programs to Ring in order to learn the basics of this language.
Basics of Rust
Conversion of old BASIC programs to Rust in order to learn the basics of this language.
Basics of Scala
Conversion of old BASIC programs to Scala in order to learn the basics of this language.
Basics of Scheme
Conversion of old BASIC programs to Scheme in order to learn the basics of this language.
Basics of Styx
Conversion of old BASIC programs to Styx in order to learn the basics of this language.
Basics of Swift
Conversion of old BASIC programs to Swift in order to learn the basics of this language.
Basics of V
Conversion of old BASIC programs to V in order to learn the basics of this language.
Basics of Vala
Conversion of old BASIC programs to Vala in order to learn the basics of this language.
Basics of Zig
Conversion of old BASIC programs to Zig in order to learn the basics of this language.

External related links