Basics of Pike
Descripción del contenido de la página
Conversión de antiguos programas de BASIC a Pike para aprender los rudimentos de este lenguaje.
Etiquetas:
3D Plot
#! /usr/bin/env pike
// 3D Plot
// Original version in BASIC:
// Creative Computing (Morristown, New Jersey, USA), ca. 1980.
// This version in Pike:
// Copyright (c) 2024, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2024-12-17.
//
// Last modified: 20250311T0155+0100.
constant SPACE = " ";
constant DOT = "*";
constant WIDTH = 56;
void clear_screen() {
write("\x1B[0;0H\x1B[2J");
}
void prompt(string s) {
write(s);
Stdio.stdin->gets();
}
void print_credits() {
write("3D Plot\n\n");
write("Original version in BASIC:\n");
write(" Creative computing (Morristown, New Jersey, USA), ca. 1980.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2024, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
prompt("Press Enter to start. ");
}
float a(float z) {
return 30.0 * exp(-z * z / 100.0);
}
void draw() {
int l = 0;
int z = 0;
int y1 = 0;
array(string) line = allocate(WIDTH);
for (float x = -30.0; x <= 30.0; x += 1.5) {
for (int i = 0; i < WIDTH; i += 1)
line[i] = SPACE;
l = 0;
y1 = 5 * (int) (sqrt(900.0 - x * x) / 5.0);
for (int y = y1; y >= -y1; y += -5) {
z = (int) (25.0 + a(sqrt(x * x + (float) (y * y))) - 0.7 * (float) y);
if (z > l) {
l = z;
line[z] = DOT;
}
} // y loop
for (int pos = 0; pos < WIDTH; pos += 1)
write(line[pos]);
write("\n");
} // x loop
}
void main() {
clear_screen();
print_credits();
clear_screen();
draw();
}
Bagels
#!/usr/bin/env pike
// Bagels
// Original version in BASIC:
// D. Resek, P. Rowe, 1978.
// Creative Computing (Morristown, New Jersey, USA), 1978.
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-10.
//
// Last modified: 20250324T1737+0100.
// Terminal {{{1
// =============================================================================
constant NORMAL_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Credits and instructions {{{1
// =============================================================================
// Clear the screen, display the credits and wait for a keypress.
//
void print_credits() {
clear_screen();
write("Bagels\n");
write("Number guessing game\n\n");
write("Original source unknown but suspected to be:\n");
write(" Lawrence Hall of Science, U.C. Berkely.\n\n");
write("Original version in BASIC:\n");
write(" D. Resek, P. Rowe, 1978.\n");
write(" Creative computing (Morristown, New Jersey, USA), 1978.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
accept_string("Press Enter to read the instructions. ");
}
// Clear the screen, print the instructions and wait for a keypress.
//
void print_instructions() {
clear_screen();
write("Bagels\n");
write("Number guessing game\n\n");
write("I am thinking of a three-digit number that has no two digits the same.\n");
write("Try to guess it and I will give you clues as follows:\n\n");
write(" PICO - one digit correct but in the wrong position\n");
write(" FERMI - one digit correct and in the right position\n");
write(" BAGELS - no digits correct\n\n");
accept_string("Press Enter to start. ");
}
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
// Return `true` if the given string is "yes" or a synonym.
//
bool is_yes(string s) {
switch(lower_case(s)) {
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
// Return `true` if the given string is "no" or a synonym.
//
bool is_no(string s) {
switch(lower_case(s)) {
case "n":
case "no":
case "nope":
return true;
default:
return false;
}
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
bool yes(string prompt) {
while (true) {
string answer = accept_string(prompt);
if (is_yes(answer)) {
return true;
}
if (is_no(answer)) {
return false;
}
}
}
// Main {{{1
// =============================================================================
constant DIGITS = 3;
bool is_digit(int ascii_code) {
return (ascii_code >= '0') && (ascii_code <= '9');
}
// Print the given prompt and return an array with a three-digit number type by
// the user.
//
array(int) get_input(string prompt) {
array(int) user_digit = allocate(DIGITS);
constant ASCII_0 = '0';
get_loop: while (true) {
string input = accept_string(prompt);
if (sizeof(input) != DIGITS) {
write("Remember it's a %d-digit number.\n", DIGITS);
continue get_loop;
}
for (int pos = 0; pos < sizeof(input); pos += 1) {
int digit = input[pos];
if (is_digit(digit)) {
user_digit[pos] = digit - ASCII_0;
} else {
write("What?\n");
continue get_loop;
}
}
if (is_any_repeated(user_digit)) {
write("Remember my number has no two digits the same.\n");
continue get_loop;
}
break;
}
return user_digit;
}
// Return three random digits.
//
array(int) random_number() {
array(int) random_digit = allocate(DIGITS);
for (int i = 0; i < DIGITS; i += 1) {
digit_loop: while (true) {
random_digit[i] = random(10);
for (int j = 0; j < i; j += 1) {
if (i != j && random_digit[i] == random_digit[j]) {
continue digit_loop;
}
}
break;
}
}
return random_digit;
}
// Return `true` if any of the given numbers is repeated; otherwise return
// `false`.
//
bool is_any_repeated(array(int) numbers) {
array(int) filtered_numbers = Array.uniq(numbers);
return !equal(filtered_numbers, numbers);
}
// Init and run the game loop.
//
void play() {
constant TRIES = 20;
int score = 0;
int fermi = 0; // counter
int pico = 0; // counter
array(int) computer_number = allocate(DIGITS);
array(int) user_number = allocate(DIGITS);
while (true) {
clear_screen();
array(int) computer_number = random_number();
write("O.K. I have a number in mind.\n");
for (int guess = 1; guess <= TRIES; guess += 1) {
// XXX TMP
// write("My number: ");
// for (int i = 0; i < DIGITS; i += 1) {
// write("%d", computer_number[i]);
// }
// write("\n");
user_number = get_input(sprintf("Guess #%02d: ", guess));
fermi = 0;
pico = 0;
for (int i = 0; i < DIGITS; i += 1) {
for (int j = 0; j < DIGITS; j += 1) {
if (user_number[i] == computer_number[j]) {
if (i == j) {
fermi += 1;
} else {
pico += 1;
}
}
}
}
if (pico + fermi == 0) {
write("BAGELS\n");
} else {
write("%s%s\n", "PICO " * pico, "FERMI " * fermi);
if (fermi == DIGITS) {
break;
}
}
}
if (fermi == DIGITS) {
write("You got it!!!\n");
score += 1;
} else {
write("Oh well.\n");
write("That's %d guesses. My number was ", TRIES);
for (int i = 0; i < DIGITS; i += 1) {
write("%d", computer_number[i]);
}
write(".\n");
}
if (!yes("Play again? ")) {
break;
}
}
if (score != 0) {
write("A %d-point bagels, buff!!\n", score);
}
write("Hope you had fun. Bye.\n");
}
void main() {
print_credits();
print_instructions();
play();
}
Bug
#!/usr/bin/env pike
// Bug
// Original version in BASIC:
// Brian Leibowitz, 1978.
// Creative Computing (Morristown, New Jersey, USA), 1978.
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-10.
//
// Last modified: 20250312T1154+0100.
// Data {{{1
// =============================================================================
class Bug {
bool body;
bool neck;
bool head;
int feelers;
string feeler_type;
bool tail;
int legs;
}
class Player {
string pronoun;
string possessive;
Bug bug;
}
Player computer = Player();
Player human = Player();
void init_data() {
computer.pronoun = "I";
computer.possessive = "My";
computer.bug = Bug();
computer.bug.body = false;
computer.bug.neck = false;
computer.bug.head = false;
computer.bug.feelers = 0;
computer.bug.feeler_type = "F";
computer.bug.tail = false;
computer.bug.legs = 0;
human.pronoun = "you";
human.possessive = "Your";
human.bug = Bug();
human.bug.body = false;
human.bug.neck = false;
human.bug.head = false;
human.bug.feelers = 0;
human.bug.feeler_type = "A";
human.bug.tail = false;
human.bug.legs = 0;
}
enum Bug_part {
BODY = 1,
NECK,
HEAD,
FEELER,
TAIL,
LEG,
}
constant FIRST_PART = BODY;
constant LAST_PART = LEG;
int part_quantity(Bug_part part) {
switch (part) {
case BODY: return 1;
case NECK: return 1;
case HEAD: return 1;
case FEELER: return 2;
case TAIL: return 1;
case LEG: return 6;
}
}
string part_name(Bug_part part) {
switch (part) {
case BODY: return "body";
case NECK: return "neck";
case HEAD: return "head";
case FEELER: return "feeler";
case TAIL: return "tail";
case LEG: return "leg";
}
}
// 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;
// Terminal {{{1
// =============================================================================
constant NORMAL_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void move_cursor_up() {
write("\x1B[1A");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Erase the entire current line and move the cursor to the start of the line.
//
void erase_line() {
write("\x1B[2K");
}
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin.gets();
}
// Credits and instructions {{{1
// =============================================================================
// Clear the screen, display the credits and wait for a keypress.
//
void print_credits() {
clear_screen();
write("Bug\n\n");
write("Original version in BASIC:\n");
write(" Brian Leibowitz, 1978.\n");
write(" Creative computing (Morristown, New Jersey, USA), 1978.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
accept_string("Press Enter to read the instructions. ");
}
constant INSTRUCTIONS =
"The object is to finish your bug before I finish mine. Each number\n" +
"stands for a part of the bug body.\n" +
"\n" +
"I will roll the die for you, tell you what I rolled for you, what the\n" +
"number stands for, and if you can get the part. If you can get the\n" +
"part I will give it to you. The same will happen on my turn.\n" +
"\n" +
"If there is a change in either bug I will give you the option of\n" +
"seeing the pictures of the bugs. The numbers stand for parts as\n" +
"follows:\n";
// Clear the screen, print the instructions and wait for a keypress.
//
void print_instructions() {
clear_screen();
write("Bug\n\n");
write("%s\n", INSTRUCTIONS);
print_parts_table();
accept_string("\nPress Enter to start. ");
}
// Pseudo-random numbers {{{1
// =============================================================================
// Return a random number from 1 to 6 (inclusive).
//
int dice() {
return (random(6) + 1);
}
// Main {{{1
// =============================================================================
// Print a table with the description of the bug parts.
//
void print_parts_table() {
constant COLUMNS = 3;
constant COLUMN_WIDTH = 8;
constant COLUMN_SEPARATION = 2;
string format_string = "%-" + (string) (COLUMN_WIDTH + COLUMN_SEPARATION) + "s";
// Headers
array(string) header = ({"Number", "Part", "Quantity"});
for (int i = 0; i < COLUMNS; i += 1) {
write(format_string, header[i]);
}
write("\n");
// Rulers
string ruler = "-" * COLUMN_WIDTH;
string padding = " " * COLUMN_SEPARATION;
for (int i = 0; i < COLUMNS; i += 1) {
write("%s%s", ruler, i == COLUMNS - 1 ? "" : padding);
}
write("\n");
// Data
for (Bug_part part = FIRST_PART; part <= LAST_PART; part += 1) {
write(format_string, (string) part);
string name = String.capitalize(part_name(part));
write(format_string, name);
write(format_string, (string) part_quantity(part));
write("\n");
}
}
// Print a bug head.
//
void print_head() {
write(" HHHHHHH\n");
write(" H H\n");
write(" H O O H\n");
write(" H H\n");
write(" H V H\n");
write(" HHHHHHH\n");
}
// Print the given bug.
//
void print_bug(Bug bug) {
if (bug.feelers > 0) {
for (int i = 0; i < FEELER_LENGTH; i += 1) {
write(" ");
for (int i = 0; i < bug.feelers; i += 1) {
write(" %s", bug.feeler_type);
}
write("\n");
}
}
if (bug.head) {
print_head();
}
if (bug.neck) {
for (int i = 0; i < NECK_LENGTH; i += 1) {
write(" N N\n");
}
}
if (bug.body) {
write(" BBBBBBBBBBBB\n");
for (int i = 0; i < BODY_HEIGHT; i += 1) {
write(" B B\n");
}
if (bug.tail) {
write("TTTTTB B\n");
}
write(" BBBBBBBBBBBB\n");
}
if (bug.legs > 0) {
for (int i = 0; i < LEG_LENGTH; i += 1) {
write(" ");
for (int i = 0; i < bug.legs; i += 1) {
write(" L");
}
write("\n");
}
}
}
// Return `true` if the given bug is finished; otherwise return `false`.
//
bool finished(Bug bug) {
return bug.feelers == MAX_FEELERS && bug.tail && bug.legs == MAX_LEGS;
}
// Array to convert a number to its equilavent text.
//
array(string) as_text = ({
"no",
"a",
"two",
"three",
"four",
"five",
"six" }); // MAX_LEGS
// Return a string containing the given number and noun in their proper form.
//
string plural(int number, string noun) {
return as_text[number] + " " + noun + ((number > 1) ? "s" : "");
}
// Add the given part to the given player's bug.
//
bool add_part(Bug_part part, Player player) {
bool changed = false;
switch (part) {
case BODY:
if (player.bug.body) {
write(", but %s already have a body.\n", player.pronoun);
} else {
write("; %s now have a body:\n", player.pronoun);
player.bug.body = true;
changed = true;
}
break;
case NECK:
if (player.bug.neck) {
write(", but %s already have a neck.\n", player.pronoun);
} else if (!player.bug.body) {
write(", but %s need a body first.\n", player.pronoun);
} else {
write("; %s now have a neck:\n", player.pronoun);
player.bug.neck = true;
changed = true;
}
break;
case HEAD:
if (player.bug.head) {
write(", but %s already have a head.\n", player.pronoun);
} else if (!player.bug.neck) {
write(", but %s need a a neck first.\n", player.pronoun);
} else {
write("; %s now have a head:\n", player.pronoun);
player.bug.head = true;
changed = true;
}
break;
case FEELER:
if (player.bug.feelers == MAX_FEELERS) {
write(", but %s have two feelers already.\n", player.pronoun);
} else if (!player.bug.head) {
write(", but %s need a head first.\n", player.pronoun);
} else {
player.bug.feelers += 1;
write("; %s now have %s", player.pronoun, plural(player.bug.feelers, "feeler"));
write(":\n");
changed = true;
}
break;
case TAIL:
if (player.bug.tail) {
write(", but %s already have a tail.\n", player.pronoun);
} else if (!player.bug.body) {
write(", but %s need a body first.\n", player.pronoun);
} else {
write("; %s now have a tail:\n", player.pronoun);
player.bug.tail = true;
changed = true;
}
break;
case LEG:
if (player.bug.legs == MAX_LEGS) {
write(", but %s have %s feet already.", player.pronoun, as_text[MAX_LEGS]);
} else if (!player.bug.body) {
write(", but %s need a body first.\n", player.pronoun);
} else {
player.bug.legs += 1;
write("; %s now have %s", player.pronoun, plural(player.bug.legs, "leg"));
write(":\n");
changed = true;
}
}
return changed;
}
// Play one turn for the given player, rolling the dice and updating his bug.
//
void turn(Player player) {
accept_string("Press Enter to roll the dice. ");
move_cursor_up();
erase_line();
int part = dice();
string pronoun = String.capitalize(player.pronoun);
write("%s rolled a %d (%s)", pronoun, part, part_name(part));
if (add_part(part, player)) {
write("\n");
print_bug(player.bug);
}
write("\n");
}
// Print a message about the winner.
//
void print_winner() {
if (finished(human.bug) && finished(computer.bug)) {
write("Both of our bugs are finished in the same number of turns!\n");
} else if (finished(human.bug)) {
write("%s bug is finished.\n", human.possessive);
} else if (finished(computer.bug)) {
write("%s bug is finished.\n", computer.possessive);
}
}
// Return `true` if either bug is finished, i.e. the game ending condition.
//
bool game_over() {
return finished(human.bug) || finished(computer.bug);
}
// Execute the game loop.
//
void play() {
clear_screen();
while (!game_over()) {
turn(human);
turn(computer);
}
print_winner();
}
void bye() {
write("I hope you enjoyed the game, play it again soon!!\n");
}
void main() {
init_data();
print_credits();
print_instructions();
play();
bye();
}
Bunny
#! /usr/bin/env pike
// Bunny
// Original version in BASIC:
// Creative Computing (Morristown, New Jersey, USA), 1978.
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-08.
//
// Last modified: 20250310T0301+0100.
void clear_screen() {
write("\x1B[0;0H\x1B[2J");
}
void press_enter(string prompt) {
write(prompt);
Stdio.stdin->gets();
}
void print_credits() {
write("Bunny\n\n");
write("Original version in BASIC:\n");
write(" Creative Computing (Morristown, New Jersey, USA), 1978.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
press_enter("Press Enter to start the program. ");
}
constant WIDTH = 53; // size of the line buffer
array(string) line = ({}); // line buffer
// Init the line buffer.
//
void init_line() {
for (int column = 0; column < WIDTH; column += 1) {
line += ({" "});
}
}
// Clear the line buffer with spaces.
//
void clear_line() {
for (int column = 0; column < WIDTH; column += 1) {
line[column] = " ";
}
}
array(string) letter = ({"B", "U", "N", "N", "Y"});
int LETTERS = sizeof(letter);
constant EOL = -1; // end of line identifier
array(int) data = ({
1, 2, EOL, 0, 2, 45, 50, EOL, 0, 5, 43, 52, EOL, 0, 7, 41, 52, EOL,
1, 9, 37, 50, EOL, 2, 11, 36, 50, EOL, 3, 13, 34, 49, EOL, 4, 14,
32, 48, EOL, 5, 15, 31, 47, EOL, 6, 16, 30, 45, EOL, 7, 17, 29, 44,
EOL, 8, 19, 28, 43, EOL, 9, 20, 27, 41, EOL, 10, 21, 26, 40, EOL,
11, 22, 25, 38, EOL, 12, 22, 24, 36, EOL, 13, 34, EOL, 14, 33, EOL,
15, 31, EOL, 17, 29, EOL, 18, 27, EOL, 19, 26, EOL, 16, 28, EOL,
13, 30, EOL, 11, 31, EOL, 10, 32, EOL, 8, 33, EOL, 7, 34, EOL, 6,
13, 16, 34, EOL, 5, 12, 16, 35, EOL, 4, 12, 16, 35, EOL, 3, 12, 15,
35, EOL, 2, 35, EOL, 1, 35, EOL, 2, 34, EOL, 3, 34, EOL, 4, 33,
EOL, 6, 33, EOL, 10, 32, 34, 34, EOL, 14, 17, 19, 25, 28, 31, 35,
35, EOL, 15, 19, 23, 30, 36, 36, EOL, 14, 18, 21, 21, 24, 30, 37, 37,
EOL, 13, 18, 23, 29, 33, 38, EOL, 12, 29, 31, 33, EOL, 11, 13, 17,
17, 19, 19, 22, 22, 24, 31, EOL, 10, 11, 17, 18, 22, 22, 24, 24, 29,
29, EOL, 22, 23, 26, 29, EOL, 27, 29, EOL, 28, 29, EOL });
int data_index = 0;
int datum() {
return data[data_index++];
}
void draw() {
init_line();
while (data_index < sizeof(data)) {
int first_column = datum();
if (first_column == EOL) {
foreach(line, string c) {
write("%s", c);
}
write("\n");
clear_line();
} else {
int last_column = datum();
for (int column = first_column; column <= last_column; column += 1) {
line[column] = letter[column % LETTERS];
}
}
}
}
void main() {
clear_screen();
print_credits();
clear_screen();
draw();
}
Chase
#!/usr/bin/env pike
// Chase
// Original version in BASIC:
// Anonymous.
// Published in 1977 in "The Best of Creative Computing", Volume 2.
// https://www.atariarchives.org/bcc2/showpage.php?page=253
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-11.
// Last modified: 20250731T1954+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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_cursor_position(int line, int col) {
write("\x1B[%d;%dH", line, col);
}
void hide_cursor() {
write("\x1B[?25l");
}
void show_cursor() {
write("\x1B[?25h");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_line_to_end() {
write("\x1B[K");
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Config {{{1
// =============================================================
constant DEFAULT_INK = WHITE + FOREGROUND;
constant INPUT_INK = BRIGHT + GREEN + FOREGROUND;
constant INSTRUCTIONS_INK = YELLOW + FOREGROUND;
constant TITLE_INK = BRIGHT + RED + FOREGROUND;
// Data {{{1
// =============================================================
// Arena
constant ARENA_WIDTH = 20;
constant ARENA_HEIGHT = 10;
constant ARENA_LAST_X = ARENA_WIDTH - 1;
constant ARENA_LAST_Y = ARENA_HEIGHT - 1;
constant ARENA_ROW = 3;
array(array(string)) arena = allocate(ARENA_HEIGHT, allocate(ARENA_WIDTH, ""));
constant EMPTY = " ";
constant FENCE = "X";
constant MACHINE = "m";
constant HUMAN = "@";
constant FENCES = 15; // inner obstacles, not the border
// The end
enum End {
NOT_YET,
QUIT,
ELECTRIFIED,
KILLED,
VICTORY
}
End the_end = NOT_YET;
// Machines
constant MACHINES = 5;
constant MACHINES_DRAG = 2; // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
array(int) machine_x = allocate(MACHINES);
array(int) machine_y = allocate(MACHINES);
array(bool) operative = allocate(MACHINES);
int destroyed_machines = 0; // counter
// Human
int human_x = 0;
int human_y = 0;
// User input {{{1
// =============================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
string get_string(string prompt) {
set_style(INPUT_INK);
string s = accept_string(prompt);
set_style(DEFAULT_INK);
return s;
}
void press_enter(string prompt) {
accept_string(prompt);
}
bool is_yes(string s) {
switch(lower_case(s)) {
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
bool is_no(string s) {
switch(lower_case(s)) {
case "n":
case "no":
case "nope":
return true;
default:
return false;
}
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
bool yes(string prompt) {
while (true) {
string answer = get_string(prompt);
if (is_yes(answer)) {
return true;
}
if (is_no(answer)) {
return false;
}
}
}
// Title, credits and instructions {{{1
// =============================================================
constant TITLE = "Chase";
void print_title() {
set_style(TITLE_INK);
write("%s\n", TITLE);
set_style(DEFAULT_INK);
}
void print_credits() {
print_title();
write("\nOriginal version in BASIC:\n");
write(" Anonymous.\n");
write(" Published in \"The Best of Creative Computing\", Volume 2, 1977.\n");
write(" https://www.atariarchives.org/bcc2/showpage.php?page=253\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n");
}
void print_instructions() {
print_title();
set_style(INSTRUCTIONS_INK);
write("\nYou (%s) are in a high voltage maze with %d\n", HUMAN, MACHINES);
write("security machines (%s) trying to kill you.\n", MACHINE);
write("You must maneuver them into the maze (%s) to survive.\n\n", FENCE);
write("Good luck!\n\n");
write("The movement commands are the following:\n\n");
write(" ↖ ↑ ↗\n");
write(" NW N NE\n");
write(" ← W E →\n");
write(" SW S SE\n");
write(" ↙ ↓ ↘\n");
write("\nPlus 'Q' to end the game.\n");
set_style(DEFAULT_INK);
}
// Arena {{{1
// =============================================================
void print_arena() {
set_cursor_position(ARENA_ROW, 1);
for (int y = 0; y <= ARENA_LAST_Y; y += 1) {
for (int x = 0; x <= ARENA_LAST_X; x += 1) {
write(arena[y][x]);
}
write("\n");
}
}
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
bool is_border(int y, int x) {
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}
int random_int_in_inclusive_range(int min, int max) {
return random(max - min) + min;
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
array(int) place(string s) {
int y = 0;
int x = 0;
while (true) {
y = random_int_in_inclusive_range(1, ARENA_LAST_Y - 1);
x = random_int_in_inclusive_range(1, ARENA_LAST_X - 1);
if (arena[y][x] == EMPTY) {
break;
}
}
arena[y][x] = s;
return ({y, x});
}
// Inhabit the arena with the machines, the inner fences and the human.
//
void inhabit_arena() {
array(int) coords;
for (int m = 0; m < MACHINES; m += 1) {
coords = place(MACHINE);
machine_y[m] = coords[0];
machine_x[m] = coords[1];
operative[m] = true;
}
for (int i = 0; i < FENCES; i += 1) {
place(FENCE);
}
coords = place(HUMAN);
human_y = coords[0];
human_x = coords[1];
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
void clean_arena() {
for (int y = 0; y <= ARENA_LAST_Y; y += 1) {
for (int x = 0; x <= ARENA_LAST_X; x += 1) {
arena[y][x] = is_border(y, x) ? FENCE : EMPTY;
}
}
}
// Game {{{1
// =============================================================
void init_game() {
clean_arena();
inhabit_arena();
destroyed_machines = 0;
the_end = NOT_YET;
}
void move_machine(int m) {
int maybe = 0;
arena[machine_y[m]][machine_x[m]] = EMPTY;
maybe = random(2);
if (machine_y[m] > human_y) {
machine_y[m] -= maybe;
} else if (machine_y[m] < human_y) {
machine_y[m] += maybe;
}
maybe = random(2);
if (machine_x[m] > human_x) {
machine_x[m] -= maybe;
} else if (machine_x[m] < human_x) {
machine_x[m] += maybe;
}
if (arena[machine_y[m]][machine_x[m]] == EMPTY) {
arena[machine_y[m]][machine_x[m]] = MACHINE;
} else if (arena[machine_y[m]][machine_x[m]] == FENCE) {
operative[m] = false;
destroyed_machines += 1;
if (destroyed_machines == MACHINES) {
the_end = VICTORY;
}
} else if (arena[machine_y[m]][machine_x[m]] == HUMAN) {
the_end = KILLED;
}
}
void maybe_move_machine(int m) {
if (random(MACHINES_DRAG) == 0) {
move_machine(m);
}
}
void move_machines() {
for (int m = 0; m < MACHINES; m += 1) {
if (operative[m]) {
maybe_move_machine(m);
}
}
}
// Read a user command; update `the_end` accordingly and return the direction
// increments.
//
array(int) get_move() {
int y_inc = 0;
int x_inc = 0;
write("\n");
erase_line_to_end();
string command = lower_case(get_string("Command: "));
switch (command) {
case "q":
the_end = QUIT;
break;
case "sw":
y_inc = 1;
x_inc = -1;
break;
case "s":
y_inc = 1;
break;
case "se":
y_inc = 1;
x_inc = 1;
break;
case "w":
x_inc = -1;
break;
case "e":
x_inc = 1;
break;
case "nw":
y_inc = -1;
x_inc = -1;
break;
case "n":
y_inc = -1;
break;
case "ne":
y_inc = -1;
x_inc = 1;
}
return ({y_inc, x_inc});
}
void play() {
int y_inc = 0;
int x_inc = 0;
while (true) { // game loop
clear_screen();
print_title();
init_game();
while (true) { // action loop
print_arena();
array(int) coords = get_move();
y_inc = coords[0];
x_inc = coords[1];
if (the_end == NOT_YET) {
if (y_inc != 0 || x_inc != 0) {
arena[human_y][human_x] = EMPTY;
if (arena[human_y + y_inc][human_x + x_inc] == FENCE) {
the_end = ELECTRIFIED;
} else if (arena[human_y + y_inc][human_x + x_inc] == MACHINE) {
the_end = KILLED;
} else {
arena[human_y][human_x] = EMPTY;
human_y = human_y + y_inc;
human_x = human_x + x_inc;
arena[human_y][human_x] = HUMAN;
print_arena();
move_machines();
}
}
}
if (the_end != NOT_YET) {
break;
}
} // action loop;
switch (the_end) {
case QUIT:
write("\nSorry to see you quit.\n");
break;
case ELECTRIFIED:
write("\nZap! You touched the fence!\n");
break;
case KILLED:
write("\nYou have been killed by a lucky machine.\n");
break;
case VICTORY:
write("\nYou are lucky, you destroyed all machines.\n");
}
if (!yes("\nDo you want to play again? ")) {
break;
}
} // game loop
write("\nHope you don't feel fenced in.\n");
write("Try again sometime.\n");
}
// Main {{{1
// =============================================================
void main() {
set_style(DEFAULT_INK);
clear_screen();
print_credits();
press_enter("\nPress the Enter key to read the instructions. ");
clear_screen();
print_instructions();
press_enter("\nPress the Enter key to start. ");
play();
}
Diamond
#! /usr/bin/env pike
// Diamond
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Pike:
// Copyright (c) 2024, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
// Written on 2024-12-17.
// Last modified: 20250310T0308+0100
constant LINES = 17;
void main() {
for (int i = 1; i <= LINES / 2 + 1; i += 1) {
for (int j = 1; j <= (LINES + 1) / 2 - i + 1; j += 1)
write(" ");
for (int j = 1; j <= i * 2 - 1; j += 1)
write("*");
write("\n");
}
for (int i = 1; i <= LINES / 2; i += 1) {
for (int j = 1; j <= i + 1; j += 1)
write(" ");
for (int j = 1; j <= ((LINES + 1) / 2 - i) * 2 - 1; j += 1)
write("*");
write("\n");
}
}
Hammurabi
#!/usr/bin/env pike
// 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 Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-11.
//
// Last modified: 20250731T1954+0200.
//
// Acknowledgment:
// The following Python port was used as a reference of the original
// variables: <https://github.com/jquast/hamurabi.py>.
//
// Modules {{{1
// =============================================================
// 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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Data {{{1
// =============================================================
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 IRRITATION_STEP = MAX_IRRITATION / IRRITATION_LEVELS;
constant MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1;
constant MIN_HARVESTED_BUSHELS_PER_ACRE = 17;
constant MAX_IRRITATION = 16;
constant PLAGUE_CHANCE = 0.15; // 15% yearly
constant RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10;
constant YEARS = 10; // goverment period
constant DEFAULT_INK = FOREGROUND + WHITE;
constant INPUT_INK = FOREGROUND + BRIGHT + GREEN;
constant INSTRUCTIONS_INK = FOREGROUND + YELLOW;
constant RESULT_INK = FOREGROUND + BRIGHT + CYAN;
constant SPEECH_INK = FOREGROUND + BRIGHT + MAGENTA;
constant TITLE_INK = FOREGROUND + BRIGHT + WHITE;
constant WARNING_INK = FOREGROUND + BRIGHT + RED;
enum Result {
VERY_GOOD,
NOT_TOO_BAD,
BAD,
VERY_BAD
}
int acres = 0;
int bushels_eaten_by_rats = 0;
int bushels_harvested = 0;
int bushels_harvested_per_acre = 0;
int bushels_in_store = 0;
int bushels_to_feed_with = 0;
int dead = 0;
int infants = 0;
int irritation = 0; // counter (0 ..= 99)
int population = 0;
int starved_people_percentage = 0;
int total_dead = 0;
// Credits and instructions {{{1
// =============================================================
constant CREDITS =
"Hammurabi\n" +
"\n" +
"Original program:\n" +
" Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.\n" +
"\n" +
"BASIC port:\n" +
" Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.\n" +
" Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.\n" +
"\n" +
"This improved remake in Pike:\n" +
" Copyright (c) 2025, Marcos Cruz (programandala.net)\n" +
" SPDX-License-Identifier: Fair\n";
void print_credits() {
set_style(TITLE_INK);
write("%s\t", CREDITS);
set_style(DEFAULT_INK);
}
constant INSTRUCTIONS =
"Hammurabi is a simulation game in which you, as the ruler of the ancient\n" +
"kingdom of Sumeria, Hammurabi, manage the resources.\n" +
"\n" +
"You may buy and sell land with your neighboring city-states for bushels of\n" +
"grain ― the price will vary between %d and %d bushels per acre. You also must\n" +
"use grain to feed your people and as seed to plant the next year's crop.\n" +
"\n" +
"You will quickly find that a certain number of people can only tend a certain\n" +
"amount of land and that people starve if they are not fed enough. You also\n" +
"have the unexpected to contend with such as a plague, rats destroying stored\n" +
"grain, and variable harvests.\n" +
"\n" +
"You will also find that managing just the few resources in this game is not a\n" +
"trivial job. The crisis of population density rears its head very rapidly.\n" +
"\n" +
"Try your hand at governing ancient Sumeria for a %d-year term of office.\n";
void print_instructions() {
set_style(INSTRUCTIONS_INK);
write(
INSTRUCTIONS,
MIN_HARVESTED_BUSHELS_PER_ACRE,
MAX_HARVESTED_BUSHELS_PER_ACRE,
YEARS
);
set_style(DEFAULT_INK);
}
// User input {{{1
// =============================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
void pause(void|string prompt ) {
prompt = prompt || "> ";
set_style(INPUT_INK);
accept_string(prompt);
set_style(DEFAULT_INK);
}
// Print the given prompt, accept a string from the user. If the whole typed
// string is a valid integer (excluding trailing and leading white spaces,
// i.e.: space, tab, newline, carriage return, form feed, vertical tab and all
// the white spaces defined in Unicode), then return the number; otherwise
// return the typed string.
//
int|string accept_integer(string prompt) {
string s = String.trim(accept_string(prompt));
int n = (int) s;
if ((string) n == s) {
return n;
} else {
return s;
}
}
// Print the given prompt, accept a string from the user. If the whole typed
// string is a valid integer (excluding trailing and leading white spaces,
// i.e.: space, tab, newline, carriage return, form feed, vertical tab and all
// the white spaces defined in Unicode), then return the number; otherwise
// return -1.
//
int get_integer(string prompt) {
set_style(INPUT_INK);
int|string answer = accept_integer(prompt);
set_style(DEFAULT_INK);
if (stringp(answer)) {
return -1;
} else {
return answer;
}
}
// Random numbers {{{1
// =============================================================
// Return a random number from 1 to 5 (inclusive).
//
int random_1_to_5() {
return random(5) + 1;
}
// Strings {{{1
// =============================================================
// Return a string with the proper wording for `n` persons, using the given or
// default words for singular and plural forms.
//
string persons(int n, string|void singular, string|void plural) {
singular = singular || "person";
plural = plural || "people";
switch (n) {
case 0: return "nobody";
case 1: return sprintf("one %s", singular);
default: return sprintf("%d %s", n, plural);
}
}
string ordinal_suffix(int n) {
switch (n) {
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}
// Game {{{1
// =============================================================================
// Return a string with the description of the given year as the previous one.
//
string previous(int year) {
if (year == 0) {
return "the previous year";
} else {
return sprintf("your %d%s year", year, ordinal_suffix(year));
}
}
void print_annual_report(int year) {
clear_screen();
set_style(SPEECH_INK);
write("Hammurabi, I beg to report to you.\n");
set_style(DEFAULT_INK);
string year_text = previous(year);
string persons_text = persons(dead);
string infants_text = persons(infants);
write(
"\nIn %s, %s starved and %s %s born.\n",
year_text,
persons_text,
infants_text,
(infants > 1) ? "were" : "was"
);
population += infants;
if (year > 0 && random(1.0) <= PLAGUE_CHANCE) {
population = (int) (population / 2);
set_style(WARNING_INK);
write("A horrible plague struck! Half the people died.\n");
set_style(DEFAULT_INK);
}
write("The population is %d.\n", population);
write("The city owns %d acres.\n", acres);
write(
"You harvested %d bushels (%d per acre).\n",
bushels_harvested,
bushels_harvested_per_acre
);
if (bushels_eaten_by_rats > 0) {
write("The rats ate %d bushels.\n", bushels_eaten_by_rats);
}
write("You have %d bushels in store.\n", bushels_in_store);
bushels_harvested_per_acre =
(int) ((float) RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * random(1.0)) +
MIN_HARVESTED_BUSHELS_PER_ACRE;
write("Land is trading at %d bushels per acre.\n\n", bushels_harvested_per_acre);
}
void say_bye() {
set_style(DEFAULT_INK);
write("\nSo long for now.\n\n");
}
void quit_game() {
say_bye();
exit(0);
}
void relinquish() {
set_style(SPEECH_INK);
write("\nHammurabi, I am deeply irritated and cannot serve you anymore.\n");
write("Please, get yourself another steward!\n");
set_style(DEFAULT_INK);
quit_game();
}
void increase_irritation() {
irritation += 1 + random(IRRITATION_STEP);
if (irritation >= MAX_IRRITATION) {
relinquish(); // this never returns
}
}
void print_irritated(string adverb) {
write("The steward seems %s irritated.\n", adverb);
}
void show_irritation() {
if (irritation < IRRITATION_STEP * 2) {
print_irritated("slightly");
} else if (irritation < IRRITATION_STEP * 3) {
print_irritated("quite");
} else if (irritation < IRRITATION_STEP * 4) {
print_irritated("very");
} else {
print_irritated("profoundly");
}
}
// Print a message begging to repeat an ununderstandable input.
//
void beg_repeat() {
increase_irritation(); // this may never return
set_style(SPEECH_INK);
write("I beg your pardon? I did not understand your order.\n");
set_style(DEFAULT_INK);
show_irritation();
}
// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
void beg_think_again(int n, string name) {
increase_irritation(); // this may never return
set_style(SPEECH_INK);
write("I beg your pardon? You have only %d %s. Now then…\n", n, name);
set_style(DEFAULT_INK);
show_irritation();
}
// Buy or sell land.
//
void trade() {
int acres_to_buy = 0;
int acres_to_sell = 0;
while (true) {
acres_to_buy = get_integer("How many acres do you wish to buy? (0 to sell): ");
if (acres_to_buy < 0) {
beg_repeat(); // this may never return
} else {
if (bushels_harvested_per_acre * acres_to_buy <= bushels_in_store) {
break;
} else {
beg_think_again(bushels_in_store, "bushels of grain");
}
}
}
if (acres_to_buy != 0) {
write("You buy %d acres.\n", acres_to_buy);
acres += acres_to_buy;
bushels_in_store -= bushels_harvested_per_acre * acres_to_buy;
write("You now have %d acres and %d bushels.\n", acres, bushels_in_store);
} else {
while (true) {
acres_to_sell = get_integer("How many acres do you wish to sell?: ");
if (acres_to_sell < 0) {
beg_repeat(); // this may never return
} else {
if (acres_to_sell < acres) {
break;
} else {
beg_think_again(acres, "acres");
}
}
}
if (acres_to_sell > 0) {
write("You sell %d acres.\n", acres_to_sell);
acres -= acres_to_sell;
bushels_in_store += bushels_harvested_per_acre * acres_to_sell;
write("You now have %d acres and %d bushels.\n", acres, bushels_in_store);
}
}
}
// Feed the people.
//
void feed() {
while (true) {
bushels_to_feed_with = get_integer("How many bushels do you wish to feed your people with?: ");
if (bushels_to_feed_with < 0) {
beg_repeat(); // this may never return
} else {
// Trying to use more grain than is in silos?
if (bushels_to_feed_with <= bushels_in_store) {
break;
} else {
beg_think_again(bushels_in_store, "bushels of grain");
}
}
}
write("You feed your people with %d bushels.\n", bushels_to_feed_with);
bushels_in_store -= bushels_to_feed_with;
write("You now have %d bushels.\n", bushels_in_store);
}
// Seed the land.
//
void seed() {
int acres_to_seed = 0;
while (true) {
acres_to_seed = get_integer("How many acres do you wish to seed?: ");
if (acres_to_seed < 0) {
beg_repeat(); // this may never return
continue;
}
if (acres_to_seed == 0) {
break;
}
// Trying to seed more acres than you own?
if (acres_to_seed > acres) {
beg_think_again(acres, "acres");
continue;
}
string message = sprintf(
"bushels of grain,\nand one bushel can seed %d acres",
ACRES_A_BUSHEL_CAN_SEED
);
// Enough grain for seed?
if ((int) (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED) > bushels_in_store) {
beg_think_again(bushels_in_store, message);
continue;
}
// Enough people to tend the crops?
if (acres_to_seed <= ACRES_A_PERSON_CAN_SEED * population) {
break;
}
message = sprintf(
"people to tend the fields,\nand one person can seed %d acres",
ACRES_A_PERSON_CAN_SEED
);
beg_think_again(population, message);
}
int bushels_used_for_seeding = (acres_to_seed / ACRES_A_BUSHEL_CAN_SEED);
write("You seed %d acres using %d bushels.\n", acres_to_seed, bushels_used_for_seeding);
bushels_in_store -= bushels_used_for_seeding;
write("You now have %d bushels.\n", bushels_in_store);
// A bountiful harvest!
bushels_harvested_per_acre = random_1_to_5();
bushels_harvested = acres_to_seed * bushels_harvested_per_acre;
bushels_in_store += bushels_harvested;
}
bool is_even(int n) {
return n % 2 == 0;
}
void check_rats() {
int rat_chance = random_1_to_5();
bushels_eaten_by_rats = is_even(rat_chance) ? (int) (bushels_in_store / rat_chance) : 0;
bushels_in_store -= bushels_eaten_by_rats;
}
// Set the variables to their values in the first year.
//
void 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;
}
void print_result(Result r) {
set_style(RESULT_INK);
switch (r) {
case VERY_GOOD:
write("A fantastic performance! Charlemagne, Disraeli and Jefferson combined could\n");
write("not have done better!\n");
break;
case NOT_TOO_BAD:
write("Your performance could have been somewat better, but really wasn't too bad at\n");
write(
"all. %d people would dearly like to see you assassinated, but we all have our\n",
(int) (population * 0.8 * random(1.0))
);
write("trivial problems.\n");
break;
case BAD:
write("Your heavy-handed performance smacks of Nero and Ivan IV. The people\n");
write("(remaining) find you an unpleasant ruler and, frankly, hate your guts!\n");
break;
case VERY_BAD:
write("Due to this extreme mismanagement you have not only been impeached and thrown\n");
write("out of office but you have also been declared national fink!!!\n");
}
set_style(DEFAULT_INK);
}
void print_final_report() {
clear_screen();
if (starved_people_percentage > 0) {
write(
"In your %d-year term of office, %d percent of the\n",
YEARS,
starved_people_percentage
);
write(
"population starved per year on the average, i.e., a total of %d people died!\n\n",
total_dead
);
}
int acres_per_person = acres / population;
write(
"You started with %d acres per person and ended with %d.\n\n",
ACRES_PER_PERSON,
acres_per_person
);
if (starved_people_percentage > 33 || acres_per_person < 7) {
print_result(VERY_BAD);
} else if (starved_people_percentage > 10 || acres_per_person < 9) {
print_result(BAD);
} else if (starved_people_percentage > 3 || acres_per_person < 10) {
print_result(NOT_TOO_BAD);
} else {
print_result(VERY_GOOD);
}
}
void check_starvation(int year) {
// How many people has been fed?
int fed_people = (bushels_to_feed_with / BUSHELS_TO_FEED_A_PERSON);
if (population > fed_people) {
dead = population - fed_people;
starved_people_percentage = ((year - 1) * starved_people_percentage + dead * 100 / population) / year;
population -= dead;
total_dead += dead;
// Starve enough for impeachment?
if (dead > (int) (0.45 * population)) {
set_style(WARNING_INK);
write("\nYou starved %d people in one year!!!\n\n", dead);
set_style(DEFAULT_INK);
print_result(VERY_BAD);
quit_game();
}
}
}
void govern() {
init();
print_annual_report(0);
for (int year = 1; year <= YEARS; year += 1) {
trade();
feed();
seed();
check_rats();
// Let's have some babies
infants = (int) (random_1_to_5() * (20 * acres + bushels_in_store) / population / 100 + 1);
check_starvation(year);
pause("\nPress the Enter key to read the annual report. ");
print_annual_report(year);
}
}
// Main {{{1
// =============================================================
void main() {
clear_screen();
print_credits();
pause("\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
#!/usr/bin/env pike
// 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 Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-10.
//
// Last modified: 20250323T2314+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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void hide_cursor() {
write("\x1B[?25l");
}
void show_cursor() {
write("\x1B[?25h");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// 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;
int distance = 0; // distance between both gunners, in paces
int player_bullets = 0;
int opponent_bullets = 0;
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
// Print the given prompt and wait until the user enters a string.
//
string get_string(string prompt) {
set_style(INPUT_INK);
string s = accept_string(prompt);
set_style(DEFAULT_INK);
return s;
}
int accept_integer(string prompt) {
// NB The casting accepts any valid integer at the start of the string
// and ignores the rest; if no valid integer is found at the start of
// the string, zero is returned.
return (int) accept_string(prompt);
}
// Print the given prompt and wait until the user enters an integer.
//
int get_integer(string prompt) {
set_style(INPUT_INK);
int n = accept_integer(prompt);
set_style(DEFAULT_INK);
return n;
}
// Return `true` if the given string is "yes" or a synonym.
//
bool is_yes(string s) {
switch(lower_case(s)) {
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
// Return `true` if the given string is "no" or a synonym.
//
bool is_no(string s) {
switch(lower_case(s)) {
case "n":
case "no":
case "nope":
return true;
default:
return false;
}
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
bool yes(string prompt) {
while (true) {
string answer = get_string(prompt);
if (is_yes(answer)) {
return true;
}
if (is_no(answer)) {
return false;
}
}
}
// Title, instructions and credits {{{1
// =============================================================
void print_title() {
set_style(TITLE_INK);
write("High Noon\n");
set_style(DEFAULT_INK);
}
void print_credits() {
print_title();
write("\nOriginal version in BASIC:\n");
write(" Designed and programmend by Chris Gaylo, 1970.\n");
write(" http://mybitbox.com/highnoon-1970/\n");
write(" http://mybitbox.com/highnoon/\n");
write("Transcriptions:\n");
write(" https://github.com/MrMethor/Highnoon-BASIC/\n");
write(" https://github.com/mad4j/basic-highnoon/\n");
write("Version modified for QB64:\n");
write(" By Daniele Olmisani, 2014.\n");
write(" https://github.com/mad4j/basic-highnoon/\n");
write("This improved remake in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n");
}
void print_instructions() {
print_title();
set_style(INSTRUCTIONS_INK);
write("\nYou have been challenged to a showdown by Black Bart, one of\n");
write("the meanest desperadoes west of the Allegheny mountains.\n");
write("\nWhile you are walking down a dusty, deserted side street,\n");
write("Black Bart emerges from a saloon one hundred paces away.\n");
write("\nBy agreement, you each have %d bullets in your six-guns.", INITIAL_BULLETS);
write("\nYour marksmanship equals his. At the start of the walk nei-\n");
write("ther of you can possibly hit the other, and at the end of\n");
write("the walk, neither can miss. the closer you get, the better\n");
write("your chances of hitting black Bart, but he also has beter\n");
write("chances of hitting you.\n");
set_style(DEFAULT_INK);
}
// Game loop {{{1
// =============================================================
string plural_suffix(int n) {
switch (n) {
case 1: return "";
default: return "s";
}
}
void print_shells_left() {
if (player_bullets == opponent_bullets) {
write("Both of you have %d bullets.\n", player_bullets);
} else {
write(
"You now have %d bullet%s to Black Bart's %d bullet%s.\n",
player_bullets,
plural_suffix(player_bullets),
opponent_bullets,
plural_suffix(opponent_bullets));
}
}
void print_check() {
write("******************************************************\n");
write("* *\n");
write("* BANK OF DODGE CITY *\n");
write("* CASHIER'S RECEIT *\n");
write("* *\n");
write("* CHECK NO. %04d AUGUST %dTH, 1889 *\n",
random(1000),
10 + random(10));
write("* *\n");
write("* *\n");
write("* PAY TO THE BEARER ON DEMAND THE SUM OF *\n");
write("* *\n");
write("* TWENTY THOUSAND DOLLARS-------------------$20,000 *\n");
write("* *\n");
write("******************************************************\n");
}
void get_reward() {
write("As mayor of Dodge City, and on behalf of its citizens,\n");
write("I extend to you our thanks, and present you with this\n");
write("reward, a check for $20,000, for killing Black Bart.\n\n\n");
print_check();
write("\n\nDon't spend it all in one place.\n");
}
void move_the_opponent() {
int paces = 2 + random(8);
write("Black Bart moves %d paces.\n", paces);
distance -= paces;
}
// Maybe move the opponent; if so, return `true`, otherwise return `false`. A
// true `silent` flag allows to omit the message when the opponent doesn't
// move.
//
bool maybe_move_the_opponent(bool silent) {
if (random(2) == 0) { // 50% chances
move_the_opponent();
return true;
} else {
if (!silent) {
write("Black Bart stands still.\n");
}
return false;
}
}
bool missed_shot() {
return random(1.0) * 10.0 <= (distance / 10);
}
// Handle the opponent's shot and return a flag with the result: if the
// opponent kills the player, return `true`; otherwise return `false`.
//
bool the_opponent_fires_and_kills(string player_strategy) {
write("Black Bart fires…\n");
opponent_bullets -= 1;
if (missed_shot()) {
write("A miss…\n");
switch (opponent_bullets) {
case 3:
write("Whew, were you lucky. That bullet just missed your head.\n");
break;
case 2:
write("But Black Bart got you in the right shin.\n");
break;
case 1:
write("Though Black Bart got you on the left side of your jaw.\n");
break;
case 0:
write("Black Bart must have jerked the trigger.\n");
}
} else {
if (player_strategy == "j") {
write("That trick just saved yout life. Black Bart's bullet\n");
write("was stopped by the wood sides of the trough.\n");
} else {
write("Black Bart shot you right through the heart that time.\n");
write("You went kickin' with your boots on.\n");
return true;
}
}
return false;
}
// Handle the opponent's strategy and return a flag with the result: if the
// opponent runs or kills the player, return `true`; otherwise return `false`.
//
bool the_opponent_kills_or_runs(string player_strategy) {
if (distance >= 10 || player_bullets == 0) {
if (maybe_move_the_opponent(true)) {
return false;
}
}
if (opponent_bullets > 0) {
return the_opponent_fires_and_kills(player_strategy);
} else {
if (player_bullets > 0) {
if (random(2) == 0) { // 50% chances
write("Now is your chance, Black Bart is out of bullets.\n");
} else {
write("Black Bart just hi-tailed it out of town rather than face you\n");
write("without a loaded gun. You can rest assured that Black Bart\n");
write("won't ever show his face around this town again.\n");
return true;
}
}
}
return false;
}
void play() {
distance = INITIAL_DISTANCE;
int watering_troughs = 0;
player_bullets = INITIAL_BULLETS;
opponent_bullets = INITIAL_BULLETS;
showdown: while (true) {
write("You are now %d paces apart from Black Bart.\n", distance);
print_shells_left();
set_style(INSTRUCTIONS_INK);
write("\nStrategies:\n");
write(" [A]dvance\n");
write(" [S]tand still\n");
write(" [F]ire\n");
write(" [J]ump behind the watering trough\n");
write(" [G]ive up\n");
write(" [T]urn tail and run\n");
set_style(DEFAULT_INK);
string player_strategy = lower_case(get_string("What is your strategy? "));
switch (player_strategy) {
case "a": // advance
while (true) {
int paces = get_integer("How many paces do you advance? ");
if (paces < 0) {
write("None of this negative stuff, partner, only positive numbers.\n");
} else if (paces > 10) {
write("Nobody can walk that fast.\n");
} else {
distance -= paces;
break;
}
}
break;
case "s": // stand still
write("That move made you a perfect stationary target.\n");
break;
case "f": // fire
if (player_bullets == 0) {
write("You don't have any bullets left.\n");
} else {
player_bullets -= 1;
if (missed_shot()) {
switch (player_bullets) {
case 2:
write("Grazed Black Bart in the right arm.\n");
break;
case 1:
write("He's hit in the left shoulder, forcing him to use his right\n");
write("hand to shoot with.\n");
}
write("What a lousy shot.\n");
if (player_bullets == 0) {
write("Nice going, ace, you've run out of bullets.\n");
if (opponent_bullets != 0) {
write("Now Black Bart won't shoot until you touch noses.\n");
write("You better think of something fast (like run).\n");
}
}
} else {
write("What a shot, you got Black Bart right between the eyes.\n");
accept_string("\nPress the Enter key to get your reward. ");
clear_screen();
get_reward();
break showdown;
}
}
break;
case "j": // jump
if (watering_troughs == MAX_WATERING_TROUGHS) {
write("How many watering troughs do you think are on this street?\n");
player_strategy = "";
} else {
watering_troughs += 1;
write("You jump behind the watering trough.\n");
write("Not a bad maneuver to threw Black Bart's strategy off.\n");
}
break;
case "g": // give up
write("Black Bart accepts. The conditions are that he won't shoot you\n");
write("if you take the first stage out of town and never come back.\n");
if (yes("Agreed? ")) {
write("A very wise decision.\n");
break showdown;
} else {
write("Oh well, back to the showdown.\n");
}
break;
case "t": // turn tail and run
// The more bullets of the opponent, the less chances to escape.
if (random((opponent_bullets + 2)) == 0) {
write("Man, you ran so fast even dogs couldn't catch you.\n");
} else {
switch (opponent_bullets) {
case 0:
write("You were lucky, Black Bart can only throw his gun at you, he\n");
write("doesn't have any bullets left. You should really be dead.\n");
break;
case 1:
write("Black Bart fires his last bullet…\n");
write("He got you right in the back. That's what you deserve, for running.\n");
break;
case 2:
write("Black Bart fires and got you twice: in your back\n");
write("and your ass. Now you can't even rest in peace.\n");
break;
case 3:
write("Black Bart unloads his gun, once in your back\n");
write("and twice in your ass. Now you can't even rest in peace.\n");
break;
case 4:
write("Black Bart unloads his gun, once in your back\n");
write("and three times in your ass. Now you can't even rest in peace.\n");
}
opponent_bullets = 0;
}
break showdown;
default:
write("You sure aren't going to live very long if you can't even follow directions.\n");
} // strategy switch
if (the_opponent_kills_or_runs(player_strategy)) {
break;
}
if (player_bullets + opponent_bullets == 0) {
write("The showdown must end, because nobody has bullets left.\n");
break;
}
write("\n");
} // showdown loop
}
// Main {{{1
// =============================================================
void main() {
clear_screen();
print_credits();
accept_string("\nPress the Enter key to read the instructions. ");
clear_screen();
print_instructions();
accept_string("\nPress the Enter key to start. ");
clear_screen();
play();
}
Math
#! /usr/bin/env pike
// Math
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-09.
//
// Last modified: 20250309T2257+0100.
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
float accept_float(string prompt) {
// NB The casting accepts any valid float at the start of the string
// and ignores the rest; if no valid float is found at the start of
// the string, zero is returned.
return (float) accept_string(prompt);
}
float accept_valid_float(string prompt) {
float n = 0;
while (n <= 0) {
n = accept_float(prompt);
if (n <= 0) {
write("Float greater than zero expected.\n");
}
}
return n;
}
void main() {
float n = accept_valid_float("Enter a number: ");
write("ABS(%[0]f) -> abs(%[0]f) -> %[1]f\n", n, abs(n));
write("ATN(%[0]f) -> atan(%[0]f) -> %[1]f\n", n, atan(n));
write("COS(%[0]f) -> cos(%[0]f) -> %[1]f\n", n, cos(n));
write("EXP(%[0]f) -> exp(%[0]f) -> %[1]f\n", n, exp(n));
write("INT(%[0]f) -> (int) (%[0]f) -> %[1]d\n", n, (int) n);
write("LOG(%[0]f) -> log(%[0]f) -> %[1]f\n", n, log(n));
write("SGN(%[0]f) -> sgn(%[0]f) -> %[1]d\n", n, sgn(n));
write("SQR(%[0]f) -> sqrt(%[0]f) -> %[1]f\n", n, sqrt(n));
write("TAN(%[0]f) -> tan(%[0]f) -> %[1]f\n", n, tan(n));
}
Mugwump
#!/usr/bin/env pike
// 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 Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written in 2025-03-11/12.
//
// Last modified: 20250731T1954+0200.
// Terminal {{{1
// =============================================================================
constant NORMAL_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Data {{{1
// =============================================================================
constant GRID_SIZE = 10;
constant TURNS = 10;
constant MUGWUMPS = 4;
class Mugwump {
int x;
int y;
bool hidden;
}
array(Mugwump) mugwump = allocate(MUGWUMPS);
int found = 0; // counter
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
// Print the given prompt, accept a string from the user. If the whole typed
// string is a valid integer (excluding trailing and leading white spaces,
// i.e.: space, tab, newline, carriage return, form feed, vertical tab and all
// the white spaces defined in Unicode), then return the number; otherwise
// return the typed string.
//
int|string accept_integer(string prompt) {
string s = String.trim(accept_string(prompt));
int n = (int) s;
if ((string) n == s) {
return n;
} else {
return s;
}
}
int prompted_valid_integer(string prompt) {
while (true) {
int|string answer = accept_integer(prompt);
if (stringp(answer)) {
write("Integer expected.\n");
} else {
return answer;
}
}
}
bool is_yes(string s) {
switch(lower_case(s)) {
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
bool is_no(string s) {
switch(lower_case(s)) {
case "n":
case "no":
case "nope":
return true;
default:
return false;
}
}
// Print the given prompt, wait until the user enters a valid yes/no string;
// and return `true` for "yes" or `false` for "no".
//
bool yes(string prompt) {
while (true) {
string answer = accept_string(prompt);
if (is_yes(answer)) {
return true;
}
if (is_no(answer)) {
return false;
}
}
}
// Credits and instructions {{{1
// =============================================================================
// Clear the screen, print the credits and ask the user to press enter.
//
void print_credits() {
clear_screen();
write("Mugwump\n\n");
write("Original version in BASIC:\n");
write(" Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).\n");
write(" Slightly modified by Bob Albrecht of People's Computer Company.\n");
write(" Published by Creative Computing (Morristown, New Jersey, USA), 1978.\n");
write(" - https://www.atariarchives.org/basicgames/showpage.php?page=114\n");
write(" - http://vintage-basic.net/games.html\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
accept_string("Press Enter to read the instructions. ");
}
// Clear the screen, print the instructions and ask the user to press enter.
//
void print_instructions() {
clear_screen();
write("Mugwump\n\n");
write("The object of this game is to find four mugwumps\n");
write("hidden on a 10 by 10 grid. Homebase is position 0,0.\n");
write("Any guess you make must be two numbers with each\n");
write("number between 0 and 9, inclusive. First number\n");
write("is distance to right of homebase and second number\n");
write("is distance above homebase.\n\n");
write("You get %d tries. After each try, you will see\n", TURNS);
write("how far you are from each mugwump.\n\n");
accept_string("Press Enter to start. ");
}
// Game {{{1
// =============================================================================
void hide_mugwumps() {
for (int m = 0; m < MUGWUMPS; m += 1) {
mugwump[m]->x = random(GRID_SIZE);
mugwump[m]->y = random(GRID_SIZE);
mugwump[m]->hidden = true;
}
found = 0; // counter
}
// Print the given prompt, wait until the user enters a valid coord and return
// it.
//
int get_coord(string prompt) {
int coord = 0;
while (true) {
coord = prompted_valid_integer(prompt);
if (coord < 0 || coord >= GRID_SIZE) {
write("Invalid value %d: not in range [0, %d].\n", coord, GRID_SIZE - 1);
} else {
break;
}
}
return coord;
}
// Return `true` if the given mugwump is hidden in the given coords.
//
bool is_here(int m, int x, int y) {
return mugwump[m]->hidden && mugwump[m]->x == x && mugwump[m]->y == y;
}
// Return the distance between the given mugwump and the given coords.
//
int distance(int m, int x, int y) {
return (int) sqrt(
pow((mugwump[m]->x - x), 2.0) +
pow((mugwump[m]->y - y), 2.0));
}
// Return a plural suffix (default: "s") if the given number is greater than 1
// otherwise return a singular suffix (default: an empty string).
//
string plural(int n, string|void plural_suffix, string|void singular_suffix) {
plural_suffix = plural_suffix || "s";
singular_suffix = singular_suffix || "";
return n > 1 ? plural_suffix : singular_suffix;
}
// Run the game.
//
void play() {
int x = 0;
int y = 0;
int turn = 0; // counter
while (true) { // game
clear_screen();
hide_mugwumps();
turns_loop: for (int turn = 1; turn <= TURNS; turn += 1) {
write("Turn number %d\n\n", turn);
write("What is your guess (in range [0, %d])?\n", GRID_SIZE - 1);
x = get_coord("Distance right of homebase (x-axis): ");
y = get_coord("Distance above homebase (y-axis): ");
write("\nYour guess is (%d, %d).\n", x, y);
for (int m = 0; m < MUGWUMPS; m += 1) {
if (is_here(m, x, y)) {
mugwump[m]->hidden = false;
found += 1;
write("You have found mugwump %d!\n", m);
if (found == MUGWUMPS) {
break turns_loop;
}
}
}
for (int m = 0; m < MUGWUMPS; m += 1) {
if (mugwump[m]->hidden) {
write("You are %d units from mugwump %d.\n", distance(m, x, y) , m);
}
}
write("\n");
} // turns
if (found == MUGWUMPS) {
write("\nYou got them all in %d turn%s!\n\n", turn, plural(turn));
write("That was fun! let's play again…\n");
write("Four more mugwumps are now in hiding.\n");
} else {
write("\nSorry, that's %d tr%s.\n\n", TURNS, plural(TURNS, "ies", "y"));
write("Here is where they're hiding:\n");
for (int m = 0; m < MUGWUMPS; m += 1) {
if (mugwump[m]->hidden) {
write("Mugwump %d is at (%d, %d).\n", m, mugwump[m]->x, mugwump[m]->y);
}
}
}
if (!yes("\nDo you want to play again? ")) {
break;
}
} // game
}
// Main {{{1
// =============================================================================
void init() {
// Build the `mugwump` array.
for (int i = 0; i < MUGWUMPS; i += 1) {
mugwump[i] = Mugwump();
}
}
void main() {
print_credits();
print_instructions();
init();
play();
}
Name
#! /usr/bin/env pike
// Name
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-08.
//
// Last modified: 20250308T1621+0100.
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
int accept_integer(string prompt) {
// NB The casting accepts any valid integer at the start of the string
// and ignores the rest; if no valid integer is found at the start of
// the string, zero is returned.
return (int) accept_string(prompt);
}
int accept_valid_integer(string prompt) {
int n = 0;
while (n <= 0) {
n = accept_integer(prompt);
if (n <= 0) {
write("Integer greater than zero expected.\n");
}
}
return n;
}
void main() {
string name = accept_string("What is your name? ");
int number = accept_valid_integer("Enter a number: ");
for (int i = 0; i < (number); i += 1) {
write("Hello, %s!\n", name);
}
}
Poetry
#! /usr/bin/env pike
// 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 Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-08.
//
// Last modified: 20250310T0302+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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
constant DEFAULT_INK = FOREGROUND + WHITE;
constant INPUT_INK = FOREGROUND + BRIGHT + GREEN;
constant TITLE_INK = FOREGROUND + BRIGHT + RED;
// Title and credits {{{1
// =============================================================================
void print_title() {
set_style(TITLE_INK);
write("Poetry\n");
set_style(DEFAULT_INK);
}
void print_credits() {
print_title();
write("\nOriginal version in BASIC:\n");
write(" Unknown author.\n");
write(" Published in \"BASIC Computer Games\",\n");
write(" Creative Computing (Morristown, New Jersey, USA), 1978.\n\n");
write("This improved remake in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n");
}
// Main {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
bool is_even(int n) {
return n % 2 == 0;
}
void play() {
int MAX_PHRASES_AND_VERSES = 20;
// counters:
int action = 0;
int phrase = 0;
int phrases_and_verses = 0;
int verse_chunks = 0;
verse: while (true) {
bool manage_the_verse_continuation = true;
bool maybe_add_comma = true;
switch (action) {
case 0:
case 1:
switch (phrase) {
case 0:
write("MIDNIGHT DREARY");
break;
case 1:
write("FIERY EYES");
break;
case 2:
write("BIRD OR FIEND");
break;
case 3:
write("THING OF EVIL");
break;
case 4:
write("PROPHET");
}
break;
case 2:
switch (phrase) {
case 0:
write("BEGUILING ME");
verse_chunks = 2;
break;
case 1:
write("THRILLED ME");
break;
case 2:
write("STILL SITTING…");
maybe_add_comma = false;
break;
case 3:
write("NEVER FLITTING");
verse_chunks = 2;
break;
case 4:
write("BURNED");
}
break;
case 3:
switch (phrase) {
case 0:
write("AND MY SOUL");
break;
case 1:
write("DARKNESS THERE");
break;
case 2:
write("SHALL BE LIFTED");
break;
case 3:
write("QUOTH THE RAVEN");
break;
case 4:
if (verse_chunks != 0) {
write("SIGN OF PARTING");
}
}
break;
case 4:
switch (phrase) {
case 0:
write("NOTHING MORE");
break;
case 1:
write("YET AGAIN");
break;
case 2:
write("SLOWLY CREEPING");
break;
case 3:
write("…EVERMORE");
break;
case 4: write("NEVERMORE");
}
break;
case 5:
action = 0;
write("\n");
if (phrases_and_verses > MAX_PHRASES_AND_VERSES) {
write("\n");
verse_chunks = 0;
phrases_and_verses = 0;
action = 2;
continue verse;
} else {
manage_the_verse_continuation = false;
}
}
if (manage_the_verse_continuation) {
sleep(.25);
if (maybe_add_comma && !(verse_chunks == 0 || random(1.0) > 0.19)) {
write(",");
verse_chunks = 2;
}
if (random(1.0) > 0.65) {
write("\n");
verse_chunks = 0;
} else {
write(" ");
verse_chunks += 1;
}
}
action += 1;
phrase = random(5);
phrases_and_verses += 1;
if (!(verse_chunks > 0 || is_even(action))) {
write(" ");
}
} // verse loop
}
void main() {
clear_screen();
print_credits();
accept_string("\nPress the Enter key to start. ");
clear_screen();
play();
}
Russian Roulette
#! /usr/bin/env pike
// Russian Roulette
// Original version in BASIC:
// Creative Computing (Morristown, New Jersey, USA), ca. 1980.
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-10.
//
// Last modified: 20250310T0310+0100.
// Terminal {{{1
// =============================================================================
constant NORMAL_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
void press_enter_to_start() {
accept_string("Press Enter to start. ");
}
// Credits and instructions {{{1
// =============================================================================
void print_credits() {
clear_screen();
write("Russian Roulette\n\n");
write("Original version in BASIC:\n");
write(" Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
press_enter_to_start();
}
void print_instructions() {
clear_screen();
write("Here is a revolver.\n");
write("Type 'f' to spin chamber and pull trigger.\n");
write("Type 'g' to give up, and play again.\n");
write("Type 'q' to quit.\n\n");
}
// Main {{{1
// =============================================================================
void play() {
int times = 0;
while (true) { // game loop
print_instructions();
times = 0;
play_loop: while (true) {
string command = accept_string("> ");
switch (command) {
case "f": // fire
if (random(100) > 83) {
write("Bang! You're dead!\n");
write("Condolences will be sent to your relatives.\n");
break play_loop;
} else {
times += 1;
if (times > 10) {
write("You win!\n");
write("Let someone else blow his brains out.\n");
break play_loop;
} else {
write("Click.\n");
}
}
break;
case "g": // give up
write("Chicken!\n");
break play_loop;
case "q": // quit
return;
default:
continue;
}
} // play loop
press_enter_to_start();
} // game loop
}
void bye() {
write("Bye!\n");
}
void main() {
print_credits();
play();
bye();
}
Seance
#!/usr/bin/env pike
// Seance
// Original version in BASIC:
// By Chris Oxlade, 1983.
// https://archive.org/details/seance.qb64
// https://github.com/chaosotter/basic-games
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-12.
//
// Last modified: 20250731T1954+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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_cursor_position(int y, int x) {
write("\x1B[%d;%dH", y, x);
}
void hide_cursor() {
write("\x1B[?25l");
}
void show_cursor() {
write("\x1B[?25h");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_line_to_end() {
write("\x1B[K");
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Config {{{1
// =============================================================================
constant TITLE = "Seance";
constant MAX_SCORE = 50;
constant MAX_MESSAGE_LENGTH = 6;
constant MIN_MESSAGE_LENGTH = 3;
constant BASE_CHARACTER = '@';
constant PLANCHETTE = '*';
constant SPACE = ' ';
constant FIRST_LETTER_NUMBER = 1;
constant LAST_LETTER_NUMBER = 26;
constant BOARD_INK = BRIGHT + CYAN + FOREGROUND;
constant DEFAULT_INK = WHITE + FOREGROUND;
constant INPUT_INK = BRIGHT + GREEN + FOREGROUND;
constant INSTRUCTIONS_INK = YELLOW + FOREGROUND;
constant MISTAKE_EFFECT_INK = BRIGHT + RED + FOREGROUND;
constant PLANCHETTE_INK = YELLOW + FOREGROUND;
constant TITLE_INK = BRIGHT + RED + FOREGROUND;
constant BOARD_X = 29; // screen column
constant BOARD_Y = 5; // screen line
constant BOARD_HEIGHT = 5; // characters displayed on the left and right borders
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_BOTTOM_Y = BOARD_HEIGHT + 1; // relative to the board
constant INPUT_X = BOARD_X;
constant INPUT_Y = BOARD_Y + BOARD_BOTTOM_Y + 4;
constant MESSAGES_Y = INPUT_Y;
constant MISTAKE_EFFECT_PAUSE = 3; // seconds
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
void press_enter(string prompt) {
accept_string(prompt);
}
// Credits and instructions {{{1
// =============================================================================
void print_title() {
set_style(TITLE_INK);
write("%s\n", TITLE);
set_style(DEFAULT_INK);
}
void print_credits() {
print_title();
write("\nOriginal version in BASIC:\n");
write(" Written by Chris Oxlade, 1983.\n");
write(" https://archive.org/details/seance.qb64\n");
write(" https://github.com/chaosotter/basic-games\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n");
}
void print_instructions() {
print_title();
set_style(INSTRUCTIONS_INK);
write("\nMessages from the Spirits are coming through, letter by letter. They want you\n");
write("to remember the letters and type them into the computer in the correct order.\n");
write("If you make mistakes, they will be angry -- very angry...\n");
write("\n");
write("Watch for stars on your screen -- they show the letters in the Spirits'\n");
write("messages.\n");
set_style(DEFAULT_INK);
}
// Game {{{1
// =============================================================================
int random_int_in_inclusive_range(int min, int max) {
return random((max - min)) + min;
}
// Return the x coordinate to print the given text centered on the board.
//
int board_centered_x(string text) {
return (BOARD_X + (BOARD_ACTUAL_WIDTH - sizeof(text)) / 2);
}
// Print the given text on the given row, centered on the board.
//
void print_centered(string text, int y) {
set_cursor_position(y, board_centered_x(text));
write("%s\n", text);
}
// Print the title on the given row, centered on the board.
//
void print_centered_title(int y) {
set_style(TITLE_INK);
print_centered(TITLE, y);
set_style(DEFAULT_INK);
}
// Print the given letter at the given board coordinates.
//
void print_character(int y, int x, int char) {
set_cursor_position(y + BOARD_Y, x + BOARD_X);
write(sprintf("%c", char));
}
void print_board() {
set_style(BOARD_INK);
for (int i = 1; i <= BOARD_WIDTH; i += 1) {
print_character(0, i + 1, BASE_CHARACTER + i); // top border
print_character(BOARD_BOTTOM_Y, i + 1, BASE_CHARACTER + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1); // bottom border
}
for (int i = 1; i <= BOARD_HEIGHT; i += 1) {
print_character(i , 0, BASE_CHARACTER + LAST_LETTER_NUMBER - i + 1); // left border
print_character(i , 3 + BOARD_WIDTH, BASE_CHARACTER + BOARD_WIDTH + i); // right border
}
write("\n");
set_style(DEFAULT_INK);
}
void erase_line_from(int line, int column) {
set_cursor_position(line, column);
erase_line_to_end();
}
// Print the given mistake effect, wait a configured number of seconds and
// finally erase it.
//
void print_mistake_effect(string effect) {
int x = board_centered_x(effect);
hide_cursor();
set_cursor_position(MESSAGES_Y, x);
set_style(MISTAKE_EFFECT_INK);
write("%s\n", effect);
set_style(DEFAULT_INK);
sleep(MISTAKE_EFFECT_PAUSE);
erase_line_from(MESSAGES_Y, x);
show_cursor();
}
// Return a new message of the given length, after marking its letters on the
// board.
//
string message(int length) {
int y = 0;
int x = 0;
string letters = "";
hide_cursor();
for (int i = 0; i <= length; i += 1) {
int letter_number = random_int_in_inclusive_range(
FIRST_LETTER_NUMBER,
LAST_LETTER_NUMBER
);
letters += sprintf("%c", BASE_CHARACTER + letter_number);
if (letter_number <= BOARD_WIDTH) {
// top border
y = 1;
x = letter_number + 1;
} else if (letter_number <= BOARD_WIDTH + BOARD_HEIGHT) {
// right border
y = letter_number - BOARD_WIDTH;
x = 2 + BOARD_WIDTH;
} else if (letter_number <= BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH) {
// bottom border
y = BOARD_BOTTOM_Y - 1;
x = 2 + BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH - letter_number;
} else {
// left border
y = 1 + LAST_LETTER_NUMBER - letter_number;
x = 1;
}
set_style(PLANCHETTE_INK);
print_character(y, x, PLANCHETTE);
set_style(DEFAULT_INK);
sleep(1);
print_character(y, x, SPACE);
}
show_cursor();
return letters;
}
string accept_message() {
set_style(INPUT_INK);
set_cursor_position(INPUT_Y, INPUT_X);
string result = upper_case(accept_string("? "));
set_style(DEFAULT_INK);
erase_line_from(INPUT_Y, INPUT_X);
return result;
}
void play() {
int score = 0;
int mistakes = 0;
print_centered_title(1);
print_board();
while (true) {
int message_length = random_int_in_inclusive_range(
MIN_MESSAGE_LENGTH,
MAX_MESSAGE_LENGTH
);
string message_received = message(message_length);
string message_understood = accept_message();
if (message_received != message_understood) {
mistakes += 1;
switch (mistakes) {
case 1:
print_mistake_effect("The table begins to shake!");
break;
case 2:
print_mistake_effect("The light bulb shatters!");
break;
case 3:
print_mistake_effect("Oh, no! A pair of clammy hands grasps your neck!");
return;
}
} else {
score += message_length;
if (score >= MAX_SCORE) {
print_centered("Whew! The spirits have gone!", MESSAGES_Y);
print_centered("You live to face another day!", MESSAGES_Y + 1);
return;
}
}
}
}
// Main {{{1
// =============================================================================
void main() {
set_style(DEFAULT_INK);
clear_screen();
print_credits();
press_enter("\nPress the Enter key to read the instructions. ");
clear_screen();
print_instructions();
press_enter("\nPress the Enter key to start. ");
clear_screen();
play();
write("\n");
}
Sine Wave
#! /usr/bin/env pike
// Sine Wave
// Original version in BASIC:
// Creative Computing (Morristown, New Jersey, USA), ca. 1980.
// This version in Pike:
// Copyright (c) 2024, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2024-12-17.
//
// Last modified: 20250313T0101+0100.
void clear_screen() {
write("\x1B[0;0H\x1B[2J");
}
array(string) word = ({"", ""});
void set_words() {
clear_screen();
array(string) order = ({"first", "second"});
for (int n = 0; n <= 1; n += 1) {
while (word[n] == "") {
write("Enter the " + order[n] + " word: ");
word[n] = Stdio.stdin->gets();
}
}
}
void print_credits() {
clear_screen();
write("Sine Wave\n\n");
write("Original version in BASIC:\n");
write(" Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2024, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
write("Press Enter to start the program. ");
Stdio.stdin->gets();
}
void draw() {
clear_screen();
bool even = false;
for (float angle = 0.0; angle <= 40.0; angle += 0.25) {
int spaces = (int) (26.0 + floor(25.0 * sin(angle)));
for (int i = 0; i < spaces; i += 1)
write(" ");
write(word[even] + "\n");
even = !even;
}
}
void main() {
print_credits();
set_words();
draw();
}
Slots
#!/usr/bin/env pike
// 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 Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-11.
//
// Last modified: 20250324T2051+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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void hide_cursor() {
write("\x1B[?25l");
}
void show_cursor() {
write("\x1B[?25h");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Data {{{1
// =============================================================================
constant REELS = 3;
array(string) image = ({" BAR ", " BELL ", "ORANGE", "LEMON ", " PLUM ", "CHERRY"});
constant BAR = 0; // position of "BAR" in `image`.
array(int) color = ({
FOREGROUND + WHITE,
FOREGROUND + CYAN,
FOREGROUND + YELLOW,
FOREGROUND + BRIGHT + YELLOW,
FOREGROUND + BRIGHT + WHITE,
FOREGROUND + BRIGHT + RED });
constant MAX_BET = 100;
constant MIN_BET = 1;
// User input {{{1
// =============================================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
int accept_integer(string prompt) {
// NB The casting accepts any valid integer at the start of the string
// and ignores the rest; if no valid integer is found at the start of
// the string, zero is returned.
return (int) accept_string(prompt);
}
// Credits and instructions {{{1
// =============================================================================
void print_credits() {
clear_screen();
write("Slots\n");
write("A slot machine simulation.\n\n");
write("Original version in BASIC:\n");
write(" Creative computing (Morristown, New Jersey, USA).\n");
write(" Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n\n");
write("This version in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n\n");
accept_string("Press Enter for instructions. ");
}
void print_instructions() {
clear_screen();
write("You are in the H&M casino, in front of one of our\n");
write("one-arm bandits. Bet from %d to %d USD (or 0 to quit).\n\n",
MIN_BET, MAX_BET);
accept_string("Press Enter to start. ");
}
// Game {{{1
// =============================================================================
int won(int prize, int bet) {
switch (prize) {
case 2:
write("DOUBLE!\n");
break;
case 5:
write("*DOUBLE BAR*\n");
break;
case 10:
write("**TOP DOLLAR**\n");
break;
case 100:
write("***JACKPOT***\n");
}
write("You won!\n");
return (prize + 1) * bet;
}
void show_standings(int usd) {
write("Your standings are %d USD.\n", usd);
}
void print_reels(array(int) reel) {
move_cursor_home();
foreach (reel, int r) {
set_style(color[r]);
write("[%s] ", image[r]);
}
set_style(NORMAL_STYLE);
write("\n");
}
void init_reels(array(int) reel) {
int images = (sizeof(image));
for (int i = 0; i < sizeof(reel); i += 1) {
reel[i] = random(images);
}
}
void spin_reels(array(int) reel) {
constant DURATION_IN_SECONDS = 2;
hide_cursor();
int first_second = time();
while (time() - first_second < DURATION_IN_SECONDS) {
init_reels(reel);
print_reels(reel);
}
show_cursor();
}
void play() {
int standings = 0;
int bet = 0;
array(int) reel = allocate(REELS);
int equals = 0;
int bars = 0;
init_reels(reel);
play_loop: while (true) {
bet_loop: while (true) {
clear_screen();
print_reels(reel);
bet = accept_integer("Your bet (or 0 to quit): ");
if (bet > MAX_BET) {
write("House limits are %d USD.\n", MAX_BET);
accept_string("Press Enter to try again. ");
} else if (bet < MIN_BET) {
string confirmation = accept_string("Type \"q\" to confirm you want to quit. ");
if (confirmation == "q" || confirmation == "Q") {
break play_loop;
}
} else {
break bet_loop;
} // bet check
} // bet loop
clear_screen();
spin_reels(reel);
equals = sizeof(reel) - sizeof(Array.uniq(reel));
equals += (equals > 0);
bars = Array.count(reel, BAR);
switch (equals) {
case 3:
if (bars == 3) {
standings += won(100, bet);
} else {
standings += won(10, bet);
}
break;
case 2:
if (bars == 2) {
standings += won(5, bet);
} else {
standings += won(2, bet);
}
break;
default:
write("You lost.\n");
standings -= bet;
} // prize check
show_standings(standings);
accept_string("Press Enter to continue. ");
} // play loop
show_standings(standings);
if (standings < 0) {
write("Pay up! Please leave your money on the terminal.\n");
} else if (standings > 0) {
write("Collect your winnings from the H&M cashier.\n");
} else {
write("Hey, you broke even.\n");
}
}
// Main {{{1
// =============================================================================
void main() {
print_credits();
print_instructions();
play();
}
Stars
#! /usr/bin/env pike
// Stars
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-08.
//
// Last modified: 20250308T1701+0100.
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
int accept_integer(string prompt) {
// NB The casting accepts any valid integer at the start of the string
// and ignores the rest; if no valid integer is found at the start of
// the string, zero is returned.
return (int) accept_string(prompt);
}
int accept_valid_integer(string prompt) {
int n = 0;
while (n <= 0) {
n = accept_integer(prompt);
if (n <= 0) {
write("Integer greater than zero expected.\n");
}
}
return n;
}
bool is_yes(string s) {
switch(lower_case(s)) {
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
void main() {
string name = accept_string("What is your name? ");
write("Hello, %s.\n", name);
do {
int number = accept_valid_integer("How many stars do you want? ");
write("%s\n", "*" * number);
} while (is_yes(accept_string("Do you want more stars? ")));
}
Strings
#! /usr/bin/env pike
// Strings
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-10, 2025-03-12.
//
// Last modified: 20250312T1809+0100.
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
int accept_integer(string prompt) {
// NB The casting accepts any valid integer at the start of the string
// and ignores the rest; if no valid integer is found at the start of
// the string, zero is returned.
return (int) accept_string(prompt);
}
int accept_valid_integer(string prompt) {
int n = 0;
while (n <= 0) {
n = accept_integer(prompt);
if (n <= 0) {
write("Integer greater than zero expected.\n");
}
}
return n;
}
void main() {
string s = accept_string("Enter a string: ");
int n = accept_valid_integer("Enter an integer: ");
write("ASC(\"%[0]s\") --> (int) \"%[0]s\"[0] --> %[1]d\n", s, (int) s[0]);
write("CHR$(%[0]d) --> sprintf(\"%%c\", %[0]d) --> \"%[1]s\"\n", n, sprintf("%c", n));
write("LEFT$(\"%[0]s\", %[1]d) --> \"%[0]s\"[..%[1]d - 1] --> \"%[2]s\"\n", s, n, s[..n - 1]);
write("MID$(\"%[0]s\", %[1]d) --> \"%[0]s\"[%[1]d - 1..] --> \"%[2]s\"\n", s, n, s[n - 1..]);
write("MID$(\"%[0]s\", %[1]d, 3) --> \"%[0]s\"[%[1]d - 1 .. %[1]d - 1 + 3 - 1] --> \"%[2]s\"\n", s, n, s[n - 1 .. n - 1 + 3 - 1]);
write("RIGHT$(\"%[0]s\", %[1]d) --> \"%[0]s\"[sizeof(%[0]s) - %[1]d..] --> \"%[2]s\"\n", s, n, s[sizeof(s) - n..]);
write("LEN(\"%[0]s\") --> sizeof(\"%[0]s\") --> %[1]d\n", s, sizeof(s));
write("VAL(\"%[0]s\") --> (float) \"%[0]s\" --> %[1]f\n", s, (float) s);
write("STR$(%[0]d) --> (string) %[0]d --> \"%[1]s\"\n", n, (string) n);
write("SPC(%[0]d) --> \" \" * %[0]d -> \"%[1]s\"\n", n, " " * n);
}
Xchange
#!/usr/bin/env pike
// Xchange
// Original version in BASIC:
// Written by Thomas C. McIntire, 1979.
// Published in "The A to Z Book of Computer Games", 1979.
// https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up
// https://github.com/chaosotter/basic-games
// This improved remake in Pike:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-12.
//
// Last modified: 20250731T1954+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_STYLE = 0;
void move_cursor_home() {
write("\x1B[H");
}
void set_cursor_position(int line, int column) {
write("\x1B[%d;%dH", line, column);
}
void set_cursor_coord(array(int) coord) {
set_cursor_position(coord[0], coord[1]);
}
void hide_cursor() {
write("\x1B[?25l");
}
void show_cursor() {
write("\x1B[?25h");
}
void set_style(int style) {
write("\x1B[%dm", style);
}
void reset_attributes() {
set_style(NORMAL_STYLE);
}
void erase_line_to_end() {
write("\x1B[K");
}
void erase_screen_to_end() {
write("\x1B[J");
}
void erase_screen() {
write("\x1B[2J");
}
void clear_screen() {
erase_screen();
reset_attributes();
move_cursor_home();
}
// Data {{{1
// =============================================================
constant BOARD_INK = FOREGROUND + BRIGHT + CYAN;
constant DEFAULT_INK = FOREGROUND + WHITE;
constant INPUT_INK = FOREGROUND + BRIGHT + GREEN;
constant INSTRUCTIONS_INK = FOREGROUND + YELLOW;
constant TITLE_INK = FOREGROUND + BRIGHT + RED;
constant BLANK = "*";
constant GRID_HEIGHT = 3; // cell rows
constant GRID_WIDTH = 3; // cell columns
constant CELLS = GRID_WIDTH * GRID_HEIGHT;
array(string) pristine_grid = allocate(CELLS, "");
constant GRIDS_ROW = 3; // screen row where the grids are printed
constant GRIDS_COLUMN = 5; // screen column where the left grid is printed
constant CELLS_GAP = 2; // distance between the grid cells, in screen rows or columns
constant GRIDS_GAP = 16; // screen columns between equivalent cells of the grids
constant FIRST_PLAYER = 0;
constant MAX_PLAYERS = 4;
array(array(string)) grid = allocate(MAX_PLAYERS, allocate(CELLS, ""));
array(bool) is_playing = allocate(MAX_PLAYERS, false);
int players = 0;
constant QUIT_COMMAND = "X";
enum axis { Y, X }; // to use with coord arrays instead of 0 and 1, for clarity
// User input {{{1
// =============================================================
string accept_string(string prompt) {
write(prompt);
return Stdio.stdin->gets();
}
int accept_integer_or_0(string prompt) {
// NB The casting accepts any valid integer at the start of the string
// and ignores the rest; if no valid integer is found at the start of
// the string, zero is returned.
return (int) accept_string(prompt);
}
int get_integer(string|void prompt) {
prompt = prompt || "";
set_style(INPUT_INK);
int n = accept_integer_or_0(prompt);
set_style(DEFAULT_INK);
return n;
}
string get_string(string|void prompt) {
prompt = prompt || "";
set_style(INPUT_INK);
string s = accept_string(prompt);
set_style(DEFAULT_INK);
return s;
}
void press_enter(string prompt) {
accept_string(prompt);
}
bool is_yes(string s) {
switch(lower_case(s)) {
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
bool is_no(string s) {
switch(lower_case(s)) {
case "n":
case "no":
case "nope":
return true;
default:
return false;
}
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
bool yes(string prompt) {
while (true) {
string answer = get_string(prompt);
if (is_yes(answer)) {
return true;
}
if (is_no(answer)) {
return false;
}
}
}
// Title, instructions and credits {{{1
// =============================================================
void print_title() {
set_style(TITLE_INK);
write("Xchange\n");
set_style(DEFAULT_INK);
}
void print_credits() {
print_title();
write("\nOriginal version in BASIC:\n");
write(" Written by Thomas C. McIntire, 1979.\n");
write(" Published in \"The A to Z Book of Computer Games\", 1979.\n");
write(" https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up\n");
write(" https://github.com/chaosotter/basic-games\n");
write("This improved remake in Pike:\n");
write(" Copyright (c) 2025, Marcos Cruz (programandala.net)\n");
write(" SPDX-License-Identifier: Fair\n");
}
void print_instructions() {
print_title();
set_style(INSTRUCTIONS_INK);
write("\nOne or two may play. If two, you take turns. A grid looks like this:\n\n");
set_style(BOARD_INK);
write(" F G D\n");
write(" A H %s\n", BLANK);
write(" E B C\n\n");
set_style(INSTRUCTIONS_INK);
write("But it should look like this:\n\n");
set_style(BOARD_INK);
write(" A B C\n");
write(" D E F\n");
write(" G H %s\n\n", BLANK);
set_style(INSTRUCTIONS_INK);
write("You may exchange any one letter with the '%s', but only one that's adjacent:\n", BLANK);
write("above, below, left, or right. Not all puzzles are possible, and you may enter\n");
write("'%s' to give up.\n\n", QUIT_COMMAND);
write("Here we go...\n");
set_style(DEFAULT_INK);
}
// Grids {{{1
// =============================================================
// Print the given player's grid title.
//
void print_grid_title(int player) {
set_cursor_position(GRIDS_ROW, GRIDS_COLUMN + (player * GRIDS_GAP));
write("Player %d", player + 1);
}
// Return the cursor position of the given player's grid cell.
//
array(int) cell_position(int player, int cell) {
int grid_row = cell / GRID_HEIGHT;
int grid_column = cell % GRID_WIDTH;
int title_margin = players > 1 ? 2 : 0;
int row = GRIDS_ROW + title_margin + grid_row;
int column = GRIDS_COLUMN +
(grid_column * CELLS_GAP) +
(player * GRIDS_GAP);
return ({ row, column });
}
// Return the cursor position of the given player's grid prompt.
//
array(int) grid_prompt_position_of(int player) {
array(int) coord = cell_position(player, CELLS);
int row = coord[0];
int column = coord[1];
return ({ row + 1, column });
}
// Print the given player's grid, in the given or default color.
//
void print_grid(int player, int|void color) {
color = color || BOARD_INK;
if (players > 1) {
print_grid_title(player);
}
set_style(color);
for (int cell = 0; cell < CELLS; cell += 1) {
set_cursor_coord(cell_position(player, cell));
write(grid[player][cell]);
}
set_style(DEFAULT_INK);
}
// Print the current players' grids.
//
void print_grids() {
for (int player = 0; player < players; player += 1) {
if (is_playing[player]) {
print_grid(player);
}
}
write("\n");
erase_screen_to_end();
}
// Scramble the grid of the given player.
//
void scramble_grid(int player) {
for (int cell = 0; cell < CELLS; cell += 1) {
int random_cell = random(CELLS);
// Exchange the contents of the current cell with that of the random one.
string tmp = grid[player][cell];
grid[player][cell] = grid[player][random_cell];
grid[player][random_cell] = tmp;
}
}
// Init the grids.
//
void init_grids() {
grid[0] = copy_value(pristine_grid);
scramble_grid(0);
for (int player = 1; player < players; player += 1) {
grid[player] = copy_value(grid[0]);
}
}
// Messages {{{1
// =============================================================
// Return a message prefix for the given player.
//
string player_prefix(int player) {
return players > 1 ? sprintf("Player %d: ", player + 1) : "";
}
// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.
//
array(int) message_position(int player, int|void row_inc) {
row_inc = row_inc || 0;
array(int) prompt_coord = grid_prompt_position_of(player);
return ({ prompt_coord[Y] + 2 + row_inc, 1 });
}
// Print the given message about the given player, adding the given row
// increment, which defaults to zero, to the default cursor coordinates.
//
void print_message(string message, int player, int|void row_inc) {
row_inc = row_inc || 0;
set_cursor_coord(message_position(player, row_inc));
write("%s%s", player_prefix(player), message);
erase_line_to_end();
write("\n");
}
// Erase the last message about the given player.
//
void erase_message(int player) {
set_cursor_coord(message_position(player));
erase_line_to_end();
}
// Game loop {{{1
// =============================================================
// Return a message with the players range.
//
string players_range_message() {
if (MAX_PLAYERS == 2) {
return "1 or 2";
} else {
return sprintf("from 1 to %d", MAX_PLAYERS);
}
}
// Return the number of players, asking the user if needed.
//
int number_of_players() {
int players = 0;
print_title();
write("\n");
if (MAX_PLAYERS == 1) {
players = 1;
} else {
while (players < 1 || players > MAX_PLAYERS) {
string prompt = sprintf("Number of players (%s): ", players_range_message());
players = get_integer(prompt);
}
}
return players;
}
// Is the given cell the first one on a grid row?
//
bool is_first_cell_of_a_grid_row(int cell) {
return cell % GRID_WIDTH == 0;
}
// Is the given cell the last one on a grid row?
//
bool is_last_cell_of_a_grid_row(int cell) {
return (cell + 1) % GRID_WIDTH == 0;
}
// Are the given cells adjacent?
//
bool are_cells_adjacent(int cell_1, int cell_2) {
return (cell_2 == cell_1 + 1 && !is_first_cell_of_a_grid_row(cell_2)) ||
(cell_2 == cell_1 + GRID_WIDTH) ||
(cell_2 == cell_1 - 1 && !is_last_cell_of_a_grid_row(cell_2)) ||
(cell_2 == cell_1 - GRID_WIDTH);
}
// If the given player's character cell is a valid move, i.e. it is adjacent to
// the blank cell, return the blank cell; otherwise return an empty string.
//
int|string position_to_cell(int player, int char_cell) {
for (int cell = 0; cell < CELLS; cell += 1) {
if (grid[player][cell] == BLANK) {
if (are_cells_adjacent(char_cell, cell)) {
return cell;
} else {
break;
}
}
}
print_message(sprintf("Illegal move \"%s\".", grid[player][char_cell]), player);
return "";
}
// If the given player's command is valid, i.e. a grid character, return its
// position; otherwise return an empty string.
//
int|string command_to_position(int player, string command) {
if (command != BLANK) {
for (int position = 0; position < CELLS; position += 1) {
if (command == grid[player][position]) {
return position;
}
}
}
print_message(sprintf("Invalid character \"%s\".", command), player);
return "";
}
// Forget the given player, who quitted.
//
void forget_player(int player) {
is_playing[player] = false;
print_grid(player, DEFAULT_INK);
}
// Play the turn of the given player.
//
void play_turn(int player) {
int blank_position = 0;
int character_position = 0;
if (is_playing[player]) {
while (true) {
while (true) {
array(int) coord = grid_prompt_position_of(player);
set_cursor_coord(coord);
erase_line_to_end();
set_cursor_coord(coord);
string command = upper_case(String.trim(get_string("Move: ")));
if (command == QUIT_COMMAND) {
forget_player(player);
return;
}
int|string position = command_to_position(player, command);
if (intp(position)) {
character_position = position;
break;
}
}
int|string position = position_to_cell(player, character_position);
if (intp(position)) {
blank_position = position;
break;
}
}
erase_message(player);
grid[player][blank_position] = grid[player][character_position];
grid[player][character_position] = BLANK;
}
}
// Play the turns of all players.
//
void play_turns() {
for (int player = 0; player < players; player += 1) {
play_turn(player);
}
}
// Is someone playing?
//
bool is_someone_playing() {
for (int player = 0; player < players; player += 1) {
if (is_playing[player]) {
return true;
}
}
return false;
}
bool has_an_empty_grid(int player) {
for (int i = 0; i < CELLS; i += 1) {
if (grid[player][i] != "") {
return false;
}
}
return true;
}
// Has someone won? If so, print a message for every winner and return `true`
// otherwise just return `false`.
//
bool has_someone_won() {
int winners = 0;
for (int player = 0; player < players; player += 1) {
if (is_playing[player]) {
if (has_an_empty_grid(player)) {
winners += 1;
if (winners > 0) {
print_message(
sprintf("You're the winner%s!", (winners > 1) ? ", too" : ""),
player,
winners - 1);
}
}
}
}
return winners > 0;
}
// Init the game.
//
void init_game() {
clear_screen();
players = number_of_players();
for (int player = 0; player < players; player += 1) {
is_playing[player] = true;
}
clear_screen();
print_title();
init_grids();
print_grids();
}
// Play the game.
//
void play() {
init_game();
while (is_someone_playing()) {
play_turns();
print_grids();
if (has_someone_won()) {
break;
}
}
}
// Main {{{1
// =============================================================
void init_once() {
// Init the pristine grid.
constant FIRST_CHAR_CODE = 'A';
for (int cell = 0; cell < CELLS - 1; cell += 1) {
pristine_grid[cell] = sprintf("%c", FIRST_CHAR_CODE + cell);
}
pristine_grid[CELLS - 1] = BLANK;
}
// Return `true` if the player does not want to play another game; otherwise
// return `false`.
//
bool enough() {
set_cursor_coord(grid_prompt_position_of(FIRST_PLAYER));
return !yes("Another game? ");
}
void main() {
init_once();
clear_screen();
print_credits();
press_enter("\nPress the Enter key to read the instructions. ");
clear_screen();
print_instructions();
press_enter("\nPress the Enter key to start. ");
while (true) {
play();
if (enough()) {
break;
}
}
write("So long…\n");
}
