Basics of Raku

Descripción del contenido de la página

Conversión de antiguos programas de BASIC a Raku para aprender los rudimentos de este lenguaje.

Etiquetas:

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, "'";

Páginas relacionadas

Basics off
Metaproyecto sobre los proyectos «Basics of…».
Basics of 8th
Conversión de antiguos programas de BASIC a 8th para aprender los rudimentos de este lenguaje.
Basics of Ada
Conversión de antiguos programas de BASIC a Ada para aprender los rudimentos de este lenguaje.
Basics of Arturo
Conversión de antiguos programas de BASIC a Arturo para aprender los rudimentos de este lenguaje.
Basics of C#
Conversión de antiguos programas de BASIC a C# para aprender los rudimentos de este lenguaje.
Basics of C3
Conversión de antiguos programas de BASIC a C3 para aprender los rudimentos de este lenguaje.
Basics of Chapel
Conversión de antiguos programas de BASIC a Chapel para aprender los rudimentos de este lenguaje.
Basics of Clojure
Conversión de antiguos programas de BASIC a Clojure para aprender los rudimentos de este lenguaje.
Basics of Crystal
Conversión de antiguos programas de BASIC a Crystal para aprender los rudimentos de este lenguaje.
Basics of D
Conversión de antiguos programas de BASIC a D para aprender los rudimentos de este lenguaje.
Basics of Elixir
Conversión de antiguos programas de BASIC a Elixir para aprender los rudimentos de este lenguaje.
Basics of F#
Conversión de antiguos programas de BASIC a F# para aprender los rudimentos de este lenguaje.
Basics of Factor
Conversión de antiguos programas de BASIC a Factor para aprender los rudimentos de este lenguaje.
Basics of FreeBASIC
Conversión de antiguos programas de BASIC a FreeBASIC para aprender los rudimentos de este lenguaje.
Basics of Gleam
Conversión de antiguos programas de BASIC a Gleam para aprender los rudimentos de este lenguaje.
Basics of Go
Conversión de antiguos programas de BASIC a Go para aprender los rudimentos de este lenguaje.
Basics of Hare
Conversión de antiguos programas de BASIC a Hare para aprender los rudimentos de este lenguaje.
Basics of Haxe
Conversión de antiguos programas de BASIC a Haxe para aprender los rudimentos de este lenguaje.
Basics of Icon
Conversión de antiguos programas de BASIC a Icon para aprender los rudimentos de este lenguaje.
Basics of Io
Conversión de antiguos programas de BASIC a Io para aprender los rudimentos de este lenguaje.
Basics of Janet
Conversión de antiguos programas de BASIC a Janet para aprender los rudimentos de este lenguaje.
Basics of Julia
Conversión de antiguos programas de BASIC a Julia para aprender los rudimentos de este lenguaje.
Basics of Kotlin
Conversión de antiguos programas de BASIC a Kotlin para aprender los rudimentos de este lenguaje.
Basics of Lobster
Conversión de antiguos programas de BASIC a Lobster para aprender los rudimentos de este lenguaje.
Basics of Lua
Conversión de antiguos programas de BASIC a Lua para aprender los rudimentos de este lenguaje.
Basics of Nature
Conversión de antiguos programas de BASIC a Nature para aprender los rudimentos de este lenguaje.
Basics of Neat
Conversión de antiguos programas de BASIC a Neat para aprender los rudimentos de este lenguaje.
Basics of Neko
Conversión de antiguos programas de BASIC a Neko para aprender los rudimentos de este lenguaje.
Basics of Nelua
Conversión de antiguos programas de BASIC a Nelua para aprender los rudimentos de este lenguaje.
Basics of Nim
Conversión de antiguos programas de BASIC a Nim para aprender los rudimentos de este lenguaje.
Basics of Nit
Conversión de antiguos programas de BASIC a Nit para aprender los rudimentos de este lenguaje.
Basics of Oberon-07
Conversión de antiguos programas de BASIC a Oberon-07 para aprender los rudimentos de este lenguaje.
Basics of OCaml
Conversión de antiguos programas de BASIC a OCaml para aprender los rudimentos de este lenguaje.
Basics of Odin
Conversión de antiguos programas de BASIC a Odin para aprender los rudimentos de este lenguaje.
Basics of Pike
Conversión de antiguos programas de BASIC a Pike para aprender los rudimentos de este lenguaje.
Basics of Pony
Conversión de antiguos programas de BASIC a Pony para aprender los rudimentos de este lenguaje.
Basics of Python
Conversión de antiguos programas de BASIC a Python para aprender los rudimentos de este lenguaje.
Basics of Racket
Conversión de antiguos programas de BASIC a Racket para aprender los rudimentos de este lenguaje.
Basics of Retro
Conversión de antiguos programas de BASIC a Retro para aprender los rudimentos de este lenguaje.
Basics of Rexx
Conversión de antiguos programas de BASIC a Rexx para aprender los rudimentos de este lenguaje.
Basics of Ring
Conversión de antiguos programas de BASIC a Ring para aprender los rudimentos de este lenguaje.
Basics of Rust
Conversión de antiguos programas de BASIC a Rust para aprender los rudimentos de este lenguaje.
Basics of Scala
Conversión de antiguos programas de BASIC a Scala para aprender los rudimentos de este lenguaje.
Basics of Scheme
Conversión de antiguos programas de BASIC a Scheme para aprender los rudimentos de este lenguaje.
Basics of Styx
Conversión de antiguos programas de BASIC a Styx para aprender los rudimentos de este lenguaje.
Basics of Swift
Conversión de antiguos programas de BASIC a Swift para aprender los rudimentos de este lenguaje.
Basics of V
Conversión de antiguos programas de BASIC a V para aprender los rudimentos de este lenguaje.
Basics of Vala
Conversión de antiguos programas de BASIC a Vala para aprender los rudimentos de este lenguaje.
Basics of Zig
Conversión de antiguos programas de BASIC a Zig para aprender los rudimentos de este lenguaje.

Enlaces externos relacionados