Basics of Chapel
Descrition del contenete del págine
Conversion de old BASIC-programas a Chapel por aprender lu elementari de ti-ci lingue.
Etiquettes:
3D Plot
/*
3D Plot
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in Chapel:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-04-04.
Last modified 20250405T2002+0200.
*/
import IO;
import Math;
const space = " ";
const dot = "*";
const width = 56;
// Erase the screen, reset the attributes and move the cursor to the home
// position.
proc clear() {
write("\x1B[2J\x1B[0m\x1B[H");
}
// Clear the screen, display the credits and wait for a keypress.
proc printCredits() {
clear();
writeln("3D Plot\n");
writeln("Original version in BASIC:");
writeln(" Creative computing (Morristown, New Jersey, USA), ca. 1980.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
writeln("Press Enter to start the program.");
IO.readLine();
}
proc a(z: real): real {
return 30 * Math.exp(-z * z / 100);
}
proc draw() {
var l: int = 0;
var z: int = 0;
var y1: int = 0;
var line: [0 ..< width] string;
clear();
var x: real = -30.0;
while x <= 30.0 {
for pos in 0 ..< width {
line[pos] = space;
}
l = 0;
y1 = 5 * (sqrt(900 - x * x) / 5): int;
var y: int = y1;
while y >= -y1 {
z = (25 + a(sqrt(x * x + (y * y): real)) - 0.7 * (y): real): int;
if z > l {
l = z;
line[z] = dot;
}
y += -5;
} // y loop
for pos in 0 ..< width {
write(line[pos]);
}
writeln("");
x += 1.5;
} // x loop
}
proc main() {
printCredits();
draw();
}
Bagels
// Bagels
// Original version in BASIC:
// D. Resek, P. Rowe, 1978.
// Creative Computing (Morristown, New Jersey, USA), 1978.
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-06.
//
// Last modified: 20250406T1921+0200.
// Modules {{{1
// =============================================================================
import IO;
import Random;
// Terminal {{{1
// =============================================================================
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// User input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
// Return `true` if the given string is "yes" or a synonym.
//
proc isYes(s: string): bool {
select s.toLower() {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
// Return `true` if the given string is "no" or a synonym.
//
proc isNo(s: string): bool {
select s.toLower() {
when "n", "no", "nope" do return true;
otherwise do 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".
//
proc yes(prompt: string): bool {
var result: bool;
while true {
var answer: string = acceptString(prompt);
if isYes(answer) {
result = true;
break;
} else if isNo(answer) {
result = false;
break;
}
}
return result;
}
// Credits and instructions {{{1
// =============================================================================
// Clear the screen, display the credits and wait for a keypress.
//
proc printCredits() {
clearScreen();
writeln("Bagels");
writeln("Number guessing game\n");
writeln("Original source unknown but suspected to be:");
writeln(" Lawrence Hall of Science, U.C. Berkely.\n");
writeln("Original version in BASIC:");
writeln(" D. Resek, P. Rowe, 1978.");
writeln(" Creative computing (Morristown, New Jersey, USA), 1978.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
acceptString("Press Enter to read the instructions. ");
}
// Clear the screen, print the instructions and wait for a keypress.
//
proc printInstructions() {
clearScreen();
writeln("Bagels");
writeln("Number guessing game\n");
writeln("I am thinking of a three-digit number that has no two digits the same.");
writeln("Try to guess it and I will give you clues as follows:\n");
writeln(" PICO - one digit correct but in the wrong position");
writeln(" FERMI - one digit correct and in the right position");
writeln(" BAGELS - no digits correct\n");
acceptString("Press Enter to start. ");
}
// Main {{{1
// =============================================================================
const DIGITS = 3;
// Print the given prompt and return an array with a three-digit number type by
// the user.
//
proc getInput(prompt: string): [0 ..< DIGITS] int {
var userDigit: [0 ..< DIGITS] int;
const ASCII_0 = '0';
label getLoop while true {
var input: string = acceptString(prompt);
if input.size != DIGITS {
writef("Remember it's a %i-digit number.\n", DIGITS);
continue getLoop;
}
for pos in 0 ..< input.size {
var digit: string = input[pos];
if input[pos].isDigit() {
userDigit[pos] = digit: int;
} else {
writeln("What?");
continue getLoop;
}
}
if isAnyRepeated(userDigit) {
writeln("Remember my number has no two digits the same.");
continue getLoop;
}
break;
}
return userDigit;
}
// Return three random digits.
//
proc randomNumber(): [0 ..< DIGITS] int {
var rsInt = new Random.randomStream(int);
var randomDigit: [0 ..< DIGITS] int;
for i in 0 ..< DIGITS {
label digitLoop while true {
randomDigit[i] = rsInt.next(0, 10 - 1);
for j in 0 ..< i {
if i != j && randomDigit[i] == randomDigit[j] {
continue digitLoop;
}
}
break;
}
}
return randomDigit;
}
// Return `true` if any of the given numbers is repeated; otherwise return
// `false`.
//
proc isAnyRepeated(numbers: [] int): bool {
for i0 in 0 ..< numbers.size {
for i1 in i0 + 1 ..< numbers.size {
if numbers[i0] == numbers[i1] {
return true;
}
}
}
return false;
}
// Init and run the game loop.
//
proc play() {
const TRIES = 20;
var score: int = 0;
var fermi: int = 0; // counter
var pico: int = 0; // counter
var computerNumber: [0 ..< DIGITS] int;
var userNumber: [0 ..< DIGITS] int;
while true {
clearScreen();
computerNumber = randomNumber();
writeln("O.K. I have a number in mind.");
for guess in 1 ..< TRIES + 1 {
//userNumber = getInput("Guess #%02i: ".format(guess)); // XXX TODO
userNumber = getInput("Guess #" + guess: string + ": "); // XXX TMP
fermi = 0;
pico = 0;
for i in 0 ..< DIGITS {
for j in 0 ..< DIGITS {
if userNumber[i] == computerNumber[j] {
if i == j {
fermi += 1;
} else {
pico += 1;
}
}
}
}
if pico + fermi == 0 {
writeln("BAGELS");
} else {
writeln( "PICO " * pico, "FERMI " * fermi);
if fermi == DIGITS {
break;
}
}
}
if fermi == DIGITS {
writeln("You got it!!!");
score += 1;
} else {
writeln("Oh well.");
writef("That's %i guesses. My number was ", TRIES);
for i in 0 ..< DIGITS {
write(computerNumber[i]);
}
writeln(".");
}
if !yes("Play again? ") {
break;
}
}
if score != 0 {
writef("A %i-point bagels, buff!!\n", score);
}
writeln("Hope you had fun. Bye.");
}
proc main() {
printCredits();
printInstructions();
play();
}
Bug
/*
Bug
Original version in BASIC:
Brian Leibowitz, 1978.
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in Chapel:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-04-07.
Last modified 20250407T0201+0200.
*/
import IO;
import Random;
record Bug {
var body: bool;
var neck: bool;
var head: bool;
var feelers: int;
var feelerType: string;
var tail: bool;
var legs: int;
var finished: bool;
}
record Player {
var pronoun: string;
var possessive: string;
var bug: Bug;
}
var computer: Player;
var human: Player;
/// Bug body parts.
enum Part { body = 1, neck, head, feeler, tail, leg }
// Bug body attributes.
const bodyHeight = 2;
const feelerLength = 4;
const legLength = 2;
const maxFeelers = 2;
const maxLegs = 6;
const neckLength = 2;
/// Clear the screen, reset the attributes and move the cursor to the
/// home position.
proc clear() {
write("\x1B[2J\x1B[0m\x1B[H");
}
/// Move the cursor up by the given number of rows (defaults to 1),
/// without changing the column position.
proc cursorUp(rows: int = 1) {
write("\x1B[", rows, "A");
}
/// Erase the current line, without moving the cursor position.
proc eraseLine() {
write("\x1B[2K");
}
/// Move the cursor to the previous row, without changing
/// the column position, and erase its line.
proc erasePreviousLine() {
cursorUp();
eraseLine();
}
proc pressEnter(prompt: string) {
write(prompt);
IO.stdout.flush();
IO.readLine();
}
/// Clear the screen, display the credits and wait for a keypress.
proc printCredits() {
clear();
writeln("Bug\n");
writeln("Original version in BASIC:");
writeln(" Brian Leibowitz, 1978.");
writeln(" Creative computing (Morristown, New Jersey, USA), 1978.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
pressEnter("Press Enter to read the instructions. ");
}
const instructions: string = """
The object is to finish your bug before I finish mine. Each number
stands for a part of the bug body.
I will roll the die for you, tell you what I rolled for you, what the
number stands for, and if you can get the part. If you can get the
part I will give it to you. The same will happen on my turn.
If there is a change in either bug I will give you the option of
seeing the pictures of the bugs. The numbers stand for parts as
follows:
""";
proc leftJustified(s: string, width: int): string {
return s + " " * (width - s.size);
}
/// Print a table with the bug parts' description.
proc printPartsTable() {
const columns = 3;
const columnWidth = 8;
const columnSeparation = 2;
// Headers
var header: [0 .. 2] string = ["Number", "Part", "Quantity"];
for i in 0 ..< columns {
write(leftJustified(header[i], columnWidth + columnSeparation));
}
writeln();
// Rulers
for i in 1 .. columns {
write(
"-" * columnWidth,
(if i < columns then " " * columnSeparation else "")
);
}
writeln();
// Data
var partQuantity: [Part.body .. Part.leg] int;
partQuantity[Part.body] = 1;
partQuantity[Part.neck] = 1;
partQuantity[Part.head] = 1;
partQuantity[Part.feeler] = 2;
partQuantity[Part.tail] = 1;
partQuantity[Part.leg] = 6;
for part in Part {
writeln(
leftJustified((part: int): string, columnWidth + columnSeparation),
leftJustified(((part): string).toTitle(), columnWidth + columnSeparation),
partQuantity[part]
);
}
}
/// Clear the screen, print the instructions and wait for a keypress.
proc printInstructions() {
clear();
writeln("Bug");
writeln(instructions);
printPartsTable();
pressEnter("\nPress Enter to start. ");
}
/// Print a bug head.
proc printHead() {
writeln(" HHHHHHH");
writeln(" H H");
writeln(" H O O H");
writeln(" H H");
writeln(" H V H");
writeln(" HHHHHHH");
}
/// Print the given bug.
proc printBug(bug: Bug) {
if bug.feelers {
for 0 ..< feelerLength {
write(" ");
for 0 ..< bug.feelers {
write(" ", bug.feelerType);
}
writeln();
}
}
if bug.head {
printHead();
}
if bug.neck {
for 0 ..< neckLength {
writeln(" N N");
}
}
if bug.body {
writeln(" BBBBBBBBBBBB");
for 0 ..< bodyHeight {
writeln(" B B");
}
if bug.tail {
writeln("TTTTTB B");
}
writeln(" BBBBBBBBBBBB");
}
if bug.legs {
for 0 ..< legLength {
write(" ");
for 0 ..< bug.legs {
write(" L");
}
writeln();
}
}
}
/// Return `true` if the given bug is finished; otherwise return `false`.
proc finished(bug: Bug): bool {
return bug.feelers == maxFeelers && bug.tail && bug.legs == maxLegs;
}
var rsInt = new Random.randomStream(int);
/// Return a random number between 1 and 6 (inclusive).
proc dice(): int {
return rsInt.next(1, 6);
}
/// Array to convert a number to its equilavent text.
var asText: [0 .. 6] string = [
"no",
"a",
"two",
"three",
"four",
"five",
"six" ]; // maxLegs
/// Return a string containing the given number
/// and the given noun in their proper form.
proc plural(number: int, noun: string): string {
return asText[number] + " " + noun + (if number > 1 then "s" else "");
}
/// Add the given part to the given player's bug.
proc addPart(part: Part, inout player: Player): bool {
var changed: bool = false;
select part {
when Part.body {
if player.bug.body {
writeln(", but ", player.pronoun, " already have a body.");
} else {
writeln("; ", player.pronoun, " now have a body:");
player.bug.body = true;
changed = true;
}
}
when Part.neck {
if player.bug.neck {
writeln(", but ", player.pronoun, " you already have a neck.");
} else if !player.bug.body {
writeln(", but ", player.pronoun, " need a body first.");
} else {
writeln("; ", player.pronoun, " now have a neck:");
player.bug.neck = true;
changed = true;
}
}
when Part.head {
if player.bug.head {
writeln(", but ", player.pronoun, " already have a head.");
} else if !player.bug.neck {
writeln(", but ", player.pronoun, " need a a neck first.");
} else {
writeln("; ", player.pronoun, " now have a head:");
player.bug.head = true;
changed = true;
}
}
when Part.feeler {
if player.bug.feelers == maxFeelers {
writeln(", but ", player.pronoun, " have two feelers already.");
} else if !player.bug.head {
writeln(", but ", player.pronoun, " need a head first.");
} else {
player.bug.feelers += 1;
writeln("; ", player.pronoun, " now have ",
plural(player.bug.feelers, "feeler"), ":");
changed = true;
}
}
when Part.tail {
if player.bug.tail {
writeln(", but ", player.pronoun, " already have a tail.");
} else if !player.bug.body {
writeln(", but ", player.pronoun, " need a body first.");
} else {
writeln("; ", player.pronoun, " now have a tail:");
player.bug.tail = true;
changed = true;
}
}
when Part.leg {
if player.bug.legs == maxLegs {
writeln(", but ", player.pronoun, " have ",
asText[maxLegs], " feet already.");
} else if !player.bug.body {
writeln(", but ", player.pronoun, " need a body first.");
} else {
player.bug.legs += 1;
writeln("; ", player.pronoun, " now have ",
plural(player.bug.legs, "leg"), ":");
changed = true;
}
}
}
return changed;
}
/// Ask the user to press the Enter key, wait for the input
/// and then erase the prompt text.
proc prompt() {
pressEnter("Press Enter to roll the dice. ");
erasePreviousLine();
}
/// Play one turn for the given player, rolling the dice
/// and updating his bug.
proc turn(inout player: Player) {
prompt();
var number: int = dice();
var part: Part = number: Part;
write((player.pronoun).toTitle(), " rolled a ", number, " (", part, ")");
if addPart(part, player) {
writeln();
printBug(player.bug);
player.bug.finished = finished(player.bug);
}
writeln();
}
/// Print a message about the winner.
proc printWinner() {
select true {
when human.bug.finished && computer.bug.finished do
writeln("Both of our bugs are finished in the same number of turns!");
when finished(human.bug) do
writeln(human.possessive, " bug is finished.");
when finished(computer.bug) do
writeln(computer.possessive, " bug is finished.");
}
}
/// Return `true` if either bug is finished, i.e. the game ending condition.
proc gameOver(): bool {
return human.bug.finished || computer.bug.finished;
}
/// Execute the game loop.
proc play() {
clear();
do {
turn(human);
turn(computer);
} while !gameOver();
printWinner();
}
/// Init player data before a new game.
proc initData() {
human.pronoun = "you";
human.possessive = "Your";
human.bug.feelerType = 'A';
human.bug.finished = false;
computer.pronoun = "I";
computer.possessive = "My";
computer.bug.feelerType = 'F';
computer.bug.finished = false;
}
proc main() {
initData();
printCredits();
printInstructions();
play();
writeln("I hope you enjoyed the game, play it again soon!!");
}
Bunny
/*
Bunny
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in Chapel:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-04-05.
Last modified 20250405T2255+0200.
*/
import IO;
// Erase the screen, reset the attributes and move the cursor to the home
// position.
proc clearScreen() {
write("\x1B[2J\x1B[0m\x1B[H");
}
// Clear the screen, print the credits and wait for a keypress.
proc printCredits() {
writeln("Bunny\n");
writeln("Original version in BASIC:");
writeln(" Creative Computing (Morristown, New Jersey, USA), 1978.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
writeln("Press Enter to start the program.");
IO.readLine();
}
param Width = 53;
var line: [0 ..< Width] string; // buffer
var col: int; // first col to print from
proc clearLine() {
for c in 0 ..< Width {
line[c] = " ";
}
col = 0;
}
const letter: string = "BUNNY";
param EOL: uint(8) = 127; // end of line identifier
const 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 ];
// Draw the graphic out of `data` and `letter`.
proc draw() {
const dataLen: int = data.size;
const letters: int = letter.size;
var toCol: int; // last col to print to
var d: int = 0; // data pointer
clearLine();
while d < dataLen {
col = data[d];
d += 1;
if col == EOL {
for c in line {
write(c);
}
writeln();
clearLine();
}
else {
toCol = data[d];
d += 1;
for c in col .. toCol {
line[c] = letter[c - letters * (c / letters)];
}
}
}
}
proc main() {
clearScreen();
printCredits();
clearScreen();
draw();
}
Chase
// Chase
// Original version in BASIC:
// Anonymous.
// Published in 1977 in "The Best of Creative Computing", Volume 2.
// https://www.atariarchives.org/bcc2/showpage.php?page=253
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-08.
// Last modified: 20250731T1954+0200.
// Modules {{{1
// =============================================================================
import IO;
import Random;
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setCursorPosition(line: int, col: int) {
writef("\x1B[%i;%iH", line, col);
}
proc hideCursor() {
write("\x1B[?25l");
}
proc showCursor() {
write("\x1B[?25h");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseLineToEnd() {
write("\x1B[K");
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Config {{{1
// =============================================================
const DEFAULT_INK = WHITE + FOREGROUND;
const INPUT_INK = BRIGHT + GREEN + FOREGROUND;
const INSTRUCTIONS_INK = YELLOW + FOREGROUND;
const TITLE_INK = BRIGHT + RED + FOREGROUND;
// Data {{{1
// =============================================================
// Arena
const ARENA_WIDTH = 20;
const ARENA_HEIGHT = 10;
const ARENA_LAST_X = ARENA_WIDTH - 1;
const ARENA_LAST_Y = ARENA_HEIGHT - 1;
const ARENA_ROW = 3;
const arenaDomain: domain(2) = {0 ..< ARENA_HEIGHT, 0 ..< ARENA_WIDTH};
var arena: [arenaDomain] string;
const EMPTY = " ";
const FENCE = "X";
const MACHINE = "m";
const HUMAN = "@";
const FENCES = 15; // inner obstacles, not the border
// The end
enum End {
NOT_YET,
QUIT,
ELECTRIFIED,
KILLED,
VICTORY
}
var theEnd: End = End.NOT_YET;
// Machines
const MACHINES = 5;
// probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
const MACHINES_DRAG = 2;
var machineX: [0 ..< MACHINES] int;
var machineY: [0 ..< MACHINES] int;
var operative: [0 ..< MACHINES] bool;
var destroyedMachines: int = 0; // counter
// Human
var humanX: int = 0;
var humanY: int = 0;
// User input {{{1
// =============================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc getString(prompt: string): string {
setStyle(INPUT_INK);
defer setStyle(DEFAULT_INK);
return acceptString(prompt);
}
proc pressEnter(prompt: string) {
acceptString(prompt);
}
proc isYes(s: string): bool {
select s.toLower() {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
proc isNo(s: string): bool {
select s.toLower() {
when "n", "no", "nope" do return true;
otherwise do 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".
//
proc yes(prompt: string): bool {
var result: bool;
while true {
var answer: string = getString(prompt);
if isYes(answer) {
result = true;
break;
}
if isNo(answer) {
result = false;
break;
}
}
return result;
}
// Title, credits and instructions {{{1
// =============================================================
const TITLE = "Chase";
proc printTitle() {
setStyle(TITLE_INK);
writef("%s\n", TITLE);
setStyle(DEFAULT_INK);
}
proc printCredits() {
printTitle();
writeln("\nOriginal version in BASIC:");
writeln(" Anonymous.");
writeln(' Published in "The Best of Creative Computing", Volume 2, 1977.');
writeln(" https://www.atariarchives.org/bcc2/showpage.php?page=253");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
}
proc printInstructions() {
printTitle();
setStyle(INSTRUCTIONS_INK);
writef("\nYou (%s) are in a high voltage maze with %i\n", HUMAN, MACHINES);
writef("security machines (%s) trying to kill you.\n", MACHINE);
writef("You must maneuver them into the maze (%s) to survive.\n\n", FENCE);
writeln("Good luck!\n");
writeln("The movement commands are the following:\n");
writeln(" ↖ ↑ ↗");
writeln(" NW N NE");
writeln(" ← W E →");
writeln(" SW S SE");
writeln(" ↙ ↓ ↘");
writeln("\nPlus 'Q' to end the game.");
setStyle(DEFAULT_INK);
}
// Arena {{{1
// =============================================================
proc printArena() {
setCursorPosition(ARENA_ROW, 1);
for y in 0 .. ARENA_LAST_Y {
for x in 0 .. ARENA_LAST_X {
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`.
//
proc isBorder(y: int, x: int): bool {
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}
var rsInt = new Random.randomStream(int);
proc randomIntInInclusiveRange(min: int, max: int): int {
return rsInt.next(min, min + max - 1);
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
proc place(s: string): [] int {
var y: int = 0;
var x: int = 0;
while true {
y = randomIntInInclusiveRange(1, ARENA_LAST_Y - 1);
x = randomIntInInclusiveRange(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.
//
proc inhabitArena() {
var coords: [0 .. 1] int;
for m in 0 ..< MACHINES {
coords = place(MACHINE);
machineY[m] = coords[0];
machineX[m] = coords[1];
operative[m] = true;
}
for 0 ..< FENCES {
place(FENCE);
}
coords = place(HUMAN);
humanY = coords[0];
humanX = coords[1];
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
proc cleanArena() {
for y in 0 ..< ARENA_HEIGHT {
for x in 0 ..< ARENA_WIDTH {
arena[y, x] = if isBorder(y, x) then FENCE else EMPTY;
}
}
}
// Game {{{1
// =============================================================
proc initGame() {
cleanArena();
inhabitArena();
destroyedMachines = 0;
theEnd = End.NOT_YET;
}
proc moveMachine(m: int) {
var maybe: int = 0;
arena[machineY[m], machineX[m]] = EMPTY;
maybe = rsInt.next(0, 2 - 1);
if machineY[m] > humanY {
machineY[m] -= maybe;
} else if machineY[m] < humanY {
machineY[m] += maybe;
}
maybe = rsInt.next(0, 2 - 1);
if machineX[m] > humanX {
machineX[m] -= maybe;
} else if machineX[m] < humanX {
machineX[m] += maybe;
}
select true {
when arena[machineY[m], machineX[m]] == EMPTY {
arena[machineY[m], machineX[m]] = MACHINE;
}
when arena[machineY[m], machineX[m]] == FENCE {
operative[m] = false;
destroyedMachines += 1;
if destroyedMachines == MACHINES {
theEnd = End.VICTORY;
}
}
when arena[machineY[m], machineX[m]] == HUMAN {
theEnd = End.KILLED;
}
}
}
proc maybeMoveMachine(m: int) {
if rsInt.next(0, MACHINES_DRAG - 1) == 0 {
moveMachine(m);
}
}
proc moveMachines() {
for m in 0 ..< MACHINES {
if operative[m] {
maybeMoveMachine(m);
}
}
}
// Read a user command; update `theEnd` accordingly and return the direction
// increments.
//
proc getMove(): [] int {
var yInc: int = 0;
var xInc: int = 0;
write("\n");
eraseLineToEnd();
var command: string = getString("Command: ").toLower();
select command {
when "q" {
theEnd = End.QUIT;
}
when "sw" {
yInc = 1;
xInc = -1;
}
when "s" {
yInc = 1;
}
when "se" {
yInc = 1;
xInc = 1;
}
when "w" {
xInc = -1;
}
when "e" {
xInc = 1;
}
when "nw" {
yInc = -1;
xInc = -1;
}
when "n" {
yInc = -1;
}
when "ne" {
yInc = -1;
xInc = 1;
}
}
return [yInc, xInc];
}
proc play() {
var yInc: int = 0;
var xInc: int = 0;
while true { // game loop
clearScreen();
printTitle();
initGame();
label actionLoop while true {
printArena();
var coords: [0 .. 1] int = getMove();
yInc = coords[0];
xInc = coords[1];
if theEnd == End.NOT_YET {
if yInc != 0 || xInc != 0 {
arena[humanY, humanX] = EMPTY;
if arena[humanY + yInc, humanX + xInc] == FENCE {
theEnd = End.ELECTRIFIED;
} else if arena[humanY + yInc, humanX + xInc] == MACHINE {
theEnd = End.KILLED;
} else {
arena[humanY, humanX] = EMPTY;
humanY = humanY + yInc;
humanX = humanX + xInc;
arena[humanY, humanX] = HUMAN;
printArena();
moveMachines();
}
}
}
select theEnd {
when End.QUIT {
writeln("\nSorry to see you quit.");
break actionLoop;
}
when End.ELECTRIFIED {
writeln("\nZap! You touched the fence!");
break actionLoop;
}
when End.KILLED {
writeln("\nYou have been killed by a lucky machine.");
break actionLoop;
}
when End.VICTORY {
writeln("\nYou are lucky, you destroyed all machines.");
break actionLoop;
}
}
} // action loop;
if !yes("\nDo you want to play again? ") {
break;
}
} // game loop
writeln("\nHope you don't feel fenced in.");
writeln("Try again sometime.");
}
// Main {{{1
// =============================================================
proc main() {
setStyle(DEFAULT_INK);
clearScreen();
printCredits();
pressEnter("\nPress the Enter key to read the instructions. ");
clearScreen();
printInstructions();
pressEnter("\nPress the Enter key to start. ");
play();
}
Diamond
/*
Diamond
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in Chapel:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-19, 2025-04-04.
Last modified 20250405T2002+0200.
*/
import IO;
param lines = 17;
proc main() {
for i in 1 ..< lines / 2 + 2 {
for 1 ..< (lines + 1) / 2 - i + 2 {
write(" ");
}
for 1 ..< i * 2 {
write("*");
}
writeln();
}
for i in 1 ..< lines / 2 + 1 {
for 1 ..< i + 2 {
write(" ");
}
for 1 ..< ((lines + 1) / 2 - i) * 2 {
write("*");
}
writeln();
}
}
High Noon
// High Noon
// Original version in BASIC:
// Designed and programmed by Chris Gaylo, Syosset High School, New York, 1970-09-12.
// http://mybitbox.com/highnoon-1970/
// http://mybitbox.com/highnoon/
// Transcriptions:
// https://github.com/MrMethor/Highnoon-BASIC/
// https://github.com/mad4j/basic-highnoon/
// Version modified for QB64:
// By Daniele Olmisani, 2014.
// https://github.com/mad4j/basic-highnoon/
// Improved remake in D:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
// This improved remake in Chapel:
// Copyright (c) 2026, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2026-02-08.
//
// Last modified: 20260208T2232+0100.
use IO;
use Random;
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc hideCursor() {
write("\x1B[?25l");
}
proc showCursor() {
write("\x1B[?25h");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Global variables and constants {{{1
// =============================================================
const DEFAULT_INK = FOREGROUND + WHITE;
const INPUT_INK = FOREGROUND + BRIGHT + GREEN;
const INSTRUCTIONS_INK = FOREGROUND + YELLOW;
const TITLE_INK = FOREGROUND + BRIGHT + RED;
const INITIAL_DISTANCE = 100;
const INITIAL_BULLETS = 4;
const MAX_WATERING_TROUGHS = 3;
var distance: int = 0; // distance between both gunners, in paces
var playerBullets: int = 0;
var opponentBullets: int = 0;
var rsInt = new randomStream(int);
var rsReal = new randomStream(real);
// User input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
/// Print the given prompt and wait until the user enters a string.
proc getString(prompt: string): string {
setStyle(INPUT_INK);
var s: string = acceptString(prompt);
setStyle(DEFAULT_INK);
return s;
}
/// Print the given prompt, accept a string from the user. If the typed string
/// is a valid integer return it; otherwise return 0.
proc getInteger(prompt: string): int {
var result: int;
var s: string = acceptString(prompt);
try {
result = (s): int;
}
catch exc {
result = 0;
}
return result;
}
/// Return `true` if the given string is "yes" or a synonym.
proc isYes(s: string): bool {
select s.toLower() {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
/// Return `true` if the given string is "no" or a synonym.
proc isNo(s: string): bool {
select s.toLower() {
when "n", "no", "nope" do return true;
otherwise do 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".
proc yes(prompt: string): bool {
var result: bool;
while true {
var answer: string = getString(prompt);
if isYes(answer) {
result = true;
break;
}
if isNo(answer) {
result = false;
break;
}
}
return result;
}
// Title, instructions and credits {{{1
// =============================================================
proc printTitle() {
setStyle(TITLE_INK);
writeln("High Noon");
setStyle(DEFAULT_INK);
}
proc printCredits() {
printTitle();
writeln("\nOriginal version in BASIC:");
writeln(" Designed and programmend by Chris Gaylo, 1970.");
writeln(" http://mybitbox.com/highnoon-1970/");
writeln(" http://mybitbox.com/highnoon/");
writeln("Transcriptions:");
writeln(" https://github.com/MrMethor/Highnoon-BASIC/");
writeln(" https://github.com/mad4j/basic-highnoon/");
writeln("Version modified for QB64:");
writeln(" By Daniele Olmisani, 2014.");
writeln(" https://github.com/mad4j/basic-highnoon/");
writeln("Improved remake in D:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
writeln("This improved remake in Chapel:");
writeln(" Copyright (c) 2026, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
}
proc printInstructions() {
printTitle();
setStyle(INSTRUCTIONS_INK);
writeln("\nYou have been challenged to a showdown by Black Bart, one of");
writeln("the meanest desperadoes west of the Allegheny mountains.");
writeln("\nWhile you are walking down a dusty, deserted side street,");
writeln("Black Bart emerges from a saloon one hundred paces away.");
writef("\nBy agreement, you each have %i bullets in your six-guns.", INITIAL_BULLETS);
writeln("\nYour marksmanship equals his. At the start of the walk nei-");
writeln("ther of you can possibly hit the other, and at the end of");
writeln("the walk, neither can miss. the closer you get, the better");
writeln("your chances of hitting black Bart, but he also has beter");
writeln("chances of hitting you.");
setStyle(DEFAULT_INK);
}
// Game loop {{{1
// =============================================================
proc pluralSuffix(n: int): string {
select n {
when 1 { return ""; }
otherwise { return "s"; }
}
}
proc printShellsLeft() {
if playerBullets == opponentBullets {
writef("Both of you have %i bullets.\n", playerBullets);
} else {
writef(
"You now have %i bullet%s to Black Bart's %i bullet%s.\n",
playerBullets,
pluralSuffix(playerBullets),
opponentBullets,
pluralSuffix(opponentBullets));
}
}
proc printCheck() {
writeln("******************************************************");
writeln("* *");
writeln("* BANK OF DODGE CITY *");
writeln("* CASHIER'S RECEIT *");
writeln("* *");
writef("* CHECK NO. %04i AUGUST %iTH, 1889 *\n",
rsInt.next(0, 1000 - 1),
10 + rsInt.next(0, 10 - 1));
writeln("* *");
writeln("* *");
writeln("* PAY TO THE BEARER ON DEMAND THE SUM OF *");
writeln("* *");
writeln("* TWENTY THOUSAND DOLLARS-------------------$20,000 *");
writeln("* *");
writeln("******************************************************");
}
proc getReward() {
writeln("As mayor of Dodge City, and on behalf of its citizens,");
writeln("I extend to you our thanks, and present you with this");
writeln("reward, a check for $20,000, for killing Black Bart.\n\n");
printCheck();
writeln("\n\nDon't spend it all in one place.");
}
proc moveTheOpponent() {
var paces: int = 2 + rsInt.next(0, 8 - 1);
writef("Black Bart moves %i 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.
proc maybeMoveTheOpponent(silent: bool): bool {
if rsInt.next(0, 2 - 1) == 0 { // 50% chances
moveTheOpponent();
return true;
} else {
if !silent {
writeln("Black Bart stands still.");
}
return false;
}
}
proc missedShot(): bool {
return rsReal.next() * 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`.
proc theOpponentFiresAndKills(playerStrategy: string): bool {
writeln("Black Bart fires…");
opponentBullets -= 1;
if missedShot() {
writeln("A miss…");
select opponentBullets {
when 3 {
writeln("Whew, were you lucky. That bullet just missed your head.");
}
when 2 {
writeln("But Black Bart got you in the right shin.");
}
when 1 {
writeln("Though Black Bart got you on the left side of your jaw.");
}
when 0 {
writeln("Black Bart must have jerked the trigger.");
}
}
} else {
if playerStrategy == "j" {
writeln("That trick just saved yout life. Black Bart's bullet");
writeln("was stopped by the wood sides of the trough.");
} else {
writeln("Black Bart shot you right through the heart that time.");
writeln("You went kickin' with your boots on.");
return true;
}
}
return false;
}
/// Handle the opponent's strategy and return a flag with the result: if the
/// opponent runs or kills the player, return `true`; otherwise return `false`.
proc theOpponentKillsOrRuns(playerStrategy: string): bool {
if distance >= 10 || playerBullets == 0 {
if maybeMoveTheOpponent(true) {
return false;
}
}
if opponentBullets > 0 {
return theOpponentFiresAndKills(playerStrategy);
} else {
if playerBullets > 0 {
if rsInt.next(0, 2 - 1) == 0 { // 50% chances
writeln("Now is your chance, Black Bart is out of bullets.");
} else {
writeln("Black Bart just hi-tailed it out of town rather than face you");
writeln("without a loaded gun. You can rest assured that Black Bart");
writeln("won't ever show his face around this town again.");
return true;
}
}
}
return false;
}
proc play() {
distance = INITIAL_DISTANCE;
var wateringTroughs: int = 0;
playerBullets = INITIAL_BULLETS;
opponentBullets = INITIAL_BULLETS;
label showdown while true {
writef("You are now %i paces apart from Black Bart.\n", distance);
printShellsLeft();
setStyle(INSTRUCTIONS_INK);
writeln("\nStrategies:");
writeln(" [A]dvance");
writeln(" [S]tand still");
writeln(" [F]ire");
writeln(" [J]ump behind the watering trough");
writeln(" [G]ive up");
writeln(" [T]urn tail and run");
setStyle(DEFAULT_INK);
var playerStrategy: string = (getString("What is your strategy? ")).toLower();
select playerStrategy {
when "a" { // advance
while true {
var paces: int = getInteger("How many paces do you advance? ");
if paces < 0 {
writeln("None of this negative stuff, partner, only positive numbers.");
} else if paces > 10 {
writeln("Nobody can walk that fast.");
} else {
distance -= paces;
break;
}
}
}
when "s" { // stand still
writeln("That move made you a perfect stationary target.");
}
when "f" { // fire
if playerBullets == 0 {
writeln("You don't have any bullets left.");
} else {
playerBullets -= 1;
if missedShot() {
select playerBullets {
when 2 {
writeln("Grazed Black Bart in the right arm.");
}
when 1 {
writeln("He's hit in the left shoulder, forcing him to use his right");
writeln("hand to shoot with.");
}
}
writeln("What a lousy shot.");
if playerBullets == 0 {
writeln("Nice going, ace, you've run out of bullets.");
if opponentBullets != 0 {
writeln("Now Black Bart won't shoot until you touch noses.");
writeln("You better think of something fast (like run).");
}
}
} else {
writeln("What a shot, you got Black Bart right between the eyes.");
acceptString("\nPress the Enter key to get your reward. ");
clearScreen();
getReward();
break showdown;
}
}
}
when "j" { // jump
if wateringTroughs == MAX_WATERING_TROUGHS {
writeln("How many watering troughs do you think are on this street?");
playerStrategy = "";
} else {
wateringTroughs += 1;
writeln("You jump behind the watering trough.");
writeln("Not a bad maneuver to threw Black Bart's strategy off.");
}
}
when "g" { // give up
writeln("Black Bart accepts. The conditions are that he won't shoot you");
writeln("if you take the first stage out of town and never come back.");
if yes("Agreed? ") {
writeln("A very wise decision.");
break showdown;
} else {
writeln("Oh well, back to the showdown.");
}
}
when "t" { // turn tail and run
// The more bullets of the opponent, the less chances to escape.
if rsInt.next(0, (opponentBullets + 2 - 1)) == 0 {
writeln("Man, you ran so fast even dogs couldn't catch you.");
} else {
select opponentBullets {
when 0 {
writeln("You were lucky, Black Bart can only throw his gun at you, he");
writeln("doesn't have any bullets left. You should really be dead.");
}
when 1 {
writeln("Black Bart fires his last bullet…");
writeln("He got you right in the back. That's what you deserve, for running.");
}
when 2 {
writeln("Black Bart fires and got you twice: in your back");
writeln("and your ass. Now you can't even rest in peace.");
}
when 3 {
writeln("Black Bart unloads his gun, once in your back");
writeln("and twice in your ass. Now you can't even rest in peace.");
}
when 4 {
writeln("Black Bart unloads his gun, once in your back");
writeln("and three times in your ass. Now you can't even rest in peace.");
}
}
opponentBullets = 0;
}
break showdown;
} otherwise {
writeln("You sure aren't going to live very long if you can't even follow directions.");
}
} // strategy switch
if theOpponentKillsOrRuns(playerStrategy) {
break;
}
if playerBullets + opponentBullets == 0 {
writeln("The showdown must end, because nobody has bullets left.");
break;
}
writeln();
} // showdown loop
}
// Main {{{1
// =============================================================
proc main() {
clearScreen();
printCredits();
acceptString("\nPress the Enter key to read the instructions. ");
clearScreen();
printInstructions();
acceptString("\nPress the Enter key to start. ");
clearScreen();
play();
}
Math
// Math
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-05
//
// Last modified: 20250405T2337+0200.
import IO;
import Math;
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc acceptValidReal(prompt: string): real {
var result: real;
while true {
var s: string = acceptString(prompt);
try {
result = s: real;
break;
}
catch exc {
writeln("Real number expected.");
}
}
return result;
}
proc main() {
var n: real = acceptValidReal("Enter a number: ");
writef("ABS(%r) -> Math.abs(%r) -> %r\n", n, n, Math.abs(n));
writef("ATN(%r) -> Math.atan(%r) -> %r\n", n, n, Math.atan(n));
writef("COS(%r) -> Math.cos(%r) -> %r\n", n, n, Math.cos(n));
writef("EXP(%r) -> Math.exp(%r) -> %r\n", n, n, Math.exp(n));
writef("INT(%r) -> %r: int -> %i\n", n, n, n: int);
writef("LOG(%r) -> Math.log(%r) -> %r\n", n, n, Math.log(n));
writef("SGN(%r) -> Math.sgn(%r)) -> %i\n", n, n, Math.sgn(n));
writef("SQR(%r) -> Math.sqrt(%r) -> %r\n", n, n, Math.sqrt(n));
writef("TAN(%r) -> Math.tan(%r) -> %r\n", n, n, Math.tan(n));
}
Mugwump
// Mugwump
// Original version in BASIC:
// Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).
// Slightly modified by Bob Albrecht of People's Computer Company.
// Published by Creative Computing (Morristown, New Jersey, USA), 1978.
// - https://www.atariarchives.org/basicgames/showpage.php?page=114
// - http://vintage-basic.net/games.html
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-07.
//
// Last modified: 20250731T1954+0200.
// Modules {{{1
// =============================================================
import IO;
import Math;
import Random;
// Terminal {{{1
// =============================================================================
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Data {{{1
// =============================================================================
const GRID_SIZE = 10;
const TURNS = 10;
const MUGWUMPS = 4;
record Mugwump {
var x: int;
var y: int;
var hidden: bool;
}
var mugwump: [0 ..< MUGWUMPS] Mugwump;
var found: int = 0; // counter
// User input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc acceptValidInteger(prompt: string): int {
var result: int;
while true {
var s: string = acceptString(prompt);
try {
result = (s): int;
break;
}
catch exc {
writeln("Integer expected.");
}
}
return result;
}
proc isYes(s: string): bool {
select s.toLower() {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
proc isNo(s: string): bool {
select s.toLower() {
when "n", "no", "nope" do return true;
otherwise do 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".
//
proc yes(prompt: string): bool {
var result: bool;
while true {
var answer: string = acceptString(prompt);
if isYes(answer) {
result = true;
break;
}
if isNo(answer) {
result = false;
break;
}
}
return result;
}
// Credits and instructions {{{1
// =============================================================================
// Clear the screen, print the credits and ask the user to press enter.
//
proc printCredits() {
clearScreen();
writeln("Mugwump\n");
writeln("Original version in BASIC:");
writeln(" Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).");
writeln(" Slightly modified by Bob Albrecht of People's Computer Company.");
writeln(" Published by Creative Computing (Morristown, New Jersey, USA), 1978.");
writeln(" - https://www.atariarchives.org/basicgames/showpage.php?page=114");
writeln(" - http://vintage-basic.net/games.html\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
acceptString("Press Enter to read the instructions. ");
}
// Clear the screen, print the instructions and ask the user to press enter.
//
proc printInstructions() {
clearScreen();
writeln("Mugwump\n");
writeln("The object of this game is to find four mugwumps");
writeln("hidden on a 10 by 10 grid. Homebase is position 0,0.");
writeln("Any guess you make must be two numbers with each");
writeln("number between 0 and 9, inclusive. First number");
writeln("is distance to right of homebase and second number");
writeln("is distance above homebase.\n");
writef("You get %i tries. After each try, you will see\n", TURNS);
writeln("how far you are from each mugwump.\n");
acceptString("Press Enter to start. ");
}
// Game {{{1
// =============================================================================
proc hideMugwumps() {
var rsInt = new Random.randomStream(int);
for m in 0 ..< MUGWUMPS {
mugwump[m].x = rsInt.next(0, GRID_SIZE - 1);
mugwump[m].y = rsInt.next(0, GRID_SIZE - 1);
mugwump[m].hidden = true;
}
found = 0; // counter
}
// Print the given prompt, wait until the user enters a valid coord and return
// it.
//
proc getCoord(prompt: string): int {
var coord: int = 0;
while true {
coord = acceptValidInteger(prompt);
if coord < 0 || coord >= GRID_SIZE {
writef("Invalid value %i: not in range [0, %i].\n", coord, GRID_SIZE - 1);
} else {
break;
}
}
return coord;
}
// Return `true` if the given mugwump is hidden in the given coords.
//
proc isHere(m: int, x: int, y: int): bool {
return mugwump[m].hidden && mugwump[m].x == x && mugwump[m].y == y;
}
// Return the distance between the given mugwump and the given coords.
//
proc distance(m: int, x: int, y: int): int {
return sqrt(
(mugwump[m].x - x) ** 2.0 +
(mugwump[m].y - y) ** 2.0): int;
}
// Return a plural suffix (default: "s") if the given number is greater than 1
// otherwise return a singular suffix (default: an empty string).
//
proc plural(n: int, pluralSuffix: string = "s", singularSuffix: string = ""): string {
return if n > 1 then pluralSuffix else singularSuffix;
}
// Run the game.
//
proc play() {
var x: int = 0;
var y: int = 0;
while true { // game
clearScreen();
hideMugwumps();
var turn: int = 1;
label turnsLoop while turn <= TURNS {
writef("Turn number %i\n\n", turn);
writef("What is your guess (in range [0, %i])?\n", GRID_SIZE - 1);
x = getCoord("Distance right of homebase (x-axis): ");
y = getCoord("Distance above homebase (y-axis): ");
writef("\nYour guess is (%i, %i).\n", x, y);
for m in 0 ..< MUGWUMPS {
if isHere(m, x, y) {
mugwump[m].hidden = false;
found += 1;
writef("You have found mugwump %i!\n", m);
if found == MUGWUMPS {
break turnsLoop;
}
}
}
for m in 0 ..< MUGWUMPS {
if mugwump[m].hidden {
writef("You are %i units from mugwump %i.\n", distance(m, x, y) , m);
}
}
writeln("");
turn += 1;
} // turns
if found == MUGWUMPS {
writef("\nYou got them all in %i turn%s!\n\n", turn, plural(turn));
writeln("That was fun! let's play again…");
writeln("Four more mugwumps are now in hiding.");
} else {
writef("\nSorry, that's %i tr%s.\n\n", TURNS, plural(TURNS, "ies", "y"));
writeln("Here is where they're hiding:");
for m in 0 ..< MUGWUMPS {
if mugwump[m].hidden {
writef("Mugwump %i is at (%i, %i).\n", m, mugwump[m].x, mugwump[m].y);
}
}
}
if !yes("\nDo you want to play again? ") {
break;
}
} // game
}
// Main {{{1
// =============================================================================
proc initData() {
// Build the `mugwump` array.
for i in 0 ..< MUGWUMPS {
mugwump[i] = new Mugwump();
}
}
proc main() {
printCredits();
printInstructions();
initData();
play();
}
Name
// Name
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2002+0200.
import IO;
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc acceptValidInteger(prompt: string): int {
var result: int;
while true {
var s: string = acceptString(prompt);
try {
result = s: int;
break;
}
catch exc {
writeln("Integer expected.");
}
}
return result;
}
proc main() {
var name: string = acceptString("What is your name? ");
var number: int = acceptValidInteger("Enter a number: ");
for 1 .. number {
writef("Hello, %s!\n", name);
}
}
Poetry
// Poetry
// Original version in BASIC:
// Unknown author.
// Modified and reworked by Jim Bailey, Peggy Ewing, and Dave Ahl at DEC.
// Published in "BASIC Computer Games", Creative Computing (Morristown, New Jersey, USA), 1978.
// https://archive.org/details/Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
// https://github.com/chaosotter/basic-games/tree/master/games/BASIC%20Computer%20Games/Poetry
// http://vintage-basic.net/games.html
// This improved remake in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2226+0200.
// Modules {{{1
// =============================================================================
import IO;
import Random;
import Time;
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
writef("\x1B[H");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
const DEFAULT_INK = FOREGROUND + WHITE;
const INPUT_INK = FOREGROUND + BRIGHT + GREEN;
const TITLE_INK = FOREGROUND + BRIGHT + RED;
// Title and credits {{{1
// =============================================================================
proc printTitle() {
setStyle(TITLE_INK);
writeln("Poetry");
setStyle(DEFAULT_INK);
}
proc printCredits() {
printTitle();
writeln("\nOriginal version in BASIC:");
writeln(" Unknown author.");
writeln(" Published in \"BASIC Computer Games\",");
writeln(" Creative Computing (Morristown, New Jersey, USA), 1978.\n");
writeln("This improved remake in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
}
// Main {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc isEven(n: int): bool {
return n % 2 == 0;
}
proc play() {
var MAX_PHRASES_AND_VERSES: int = 20;
// counters:
var action: int = 0;
var phrase: int = 0;
var phrasesAndVerses: int = 0;
var verseChunks: int = 0;
var rsInt = new Random.randomStream(int);
var rsReal = new Random.randomStream(real);
label verse while true {
var manageTheVerseContinuation: bool = true;
var maybeAddComma: bool = true;
select action {
when 0, 1 {
select phrase {
when 0 do write("MIDNIGHT DREARY");
when 1 do write("FIERY EYES");
when 2 do write("BIRD OR FIEND");
when 3 do write("THING OF EVIL");
when 4 do write("PROPHET");
}
}
when 2 {
select phrase {
when 0 {
write("BEGUILING ME");
verseChunks = 2;
}
when 1 {
write("THRILLED ME");
}
when 2 {
write("STILL SITTING…");
maybeAddComma = false;
}
when 3 {
write("NEVER FLITTING");
verseChunks = 2;
}
when 4 {
write("BURNED");
}
}
}
when 3 {
select phrase {
when 0 {
write("AND MY SOUL");
}
when 1 {
write("DARKNESS THERE");
}
when 2 {
write("SHALL BE LIFTED");
}
when 3 {
write("QUOTH THE RAVEN");
}
when 4 {
if verseChunks != 0 {
write("SIGN OF PARTING");
}
}
}
}
when 4 {
select phrase {
when 0 do write("NOTHING MORE");
when 1 do write("YET AGAIN");
when 2 do write("SLOWLY CREEPING");
when 3 do write("…EVERMORE");
when 4 do write("NEVERMORE");
}
}
when 5 {
action = 0;
writeln("");
if phrasesAndVerses > MAX_PHRASES_AND_VERSES {
writeln("");
verseChunks = 0;
phrasesAndVerses = 0;
action = 2;
continue verse;
}
else {
manageTheVerseContinuation = false;
}
}
}
if manageTheVerseContinuation {
Time.sleep(0.250);
if maybeAddComma && !(verseChunks == 0 || rsReal.next() > 0.19) {
write(",");
verseChunks = 2;
}
if rsReal.next() > 0.65 {
writeln("");
verseChunks = 0;
}
else {
write(" ");
verseChunks += 1;
}
}
action += 1;
phrase = rsInt.next(0, 4);
phrasesAndVerses += 1;
if !(verseChunks > 0 || isEven(action)) {
write(" ");
}
} // verse loop
}
proc main() {
clearScreen();
printCredits();
acceptString("\nPress the Enter key to start. ");
clearScreen();
play();
}
Russian Roulette
// Russian Roulette
// Original version in BASIC:
// Creative Computing (Morristown, New Jersey, USA), ca. 1980.
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2249+0200.
// Terminal {{{1
// =============================================================================
import IO;
import Random;
const normalStyle = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(normalStyle);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// User input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc pressEnterToStart() {
acceptString("Press Enter to start. ");
}
// Credits and instructions {{{1
// =============================================================================
proc printCredits() {
clearScreen();
writeln("Russian Roulette\n");
writeln("Original version in BASIC:");
writeln(" Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
pressEnterToStart();
}
proc printInstructions() {
clearScreen();
writeln("Here is a revolver.");
writeln("Type 'f' to spin chamber and pull trigger.");
writeln("Type 'g' to give up, and play again.");
writeln("Type 'q' to quit.\n");
}
// Main {{{1
// =============================================================================
proc play() {
var rsInt = new Random.randomStream(int);
while true { // game loop
printInstructions();
var times: int = 0;
label playLoop while true {
var command: string = acceptString("> ");
select command {
when "f" { // fire
if rsInt.next(0, 99) > 83 {
writeln("Bang! You're dead!");
writeln("Condolences will be sent to your relatives.");
break playLoop;
} else {
times += 1;
if times > 10 {
writeln("You win!");
writeln("Let someone else blow his brains out.");
break playLoop;
} else {
writeln("Click.");
}
}
}
when "g" { // give up
writeln("Chicken!");
break playLoop;
}
when "q" { // quit
return;
}
}
} // play loop
pressEnterToStart();
} // game loop
}
proc bye() {
writeln("Bye!");
}
proc main() {
printCredits();
play();
bye();
}
Seance
// Seance
// Original version in BASIC:
// By Chris Oxlade, 1983.
// https://archive.org/details/seance.qb64
// https://github.com/chaosotter/basic-games
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-09.
//
// Last modified: 20250731T1954+0200.
// Modules {{{1
// =============================================================================
import IO;
import Random;
import Time;
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setCursorPosition(y: int, x: int) {
writef("\x1B[%i;%iH", y, x);
}
proc hideCursor() {
write("\x1B[?25l");
}
proc showCursor() {
write("\x1B[?25h");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseLineToEnd() {
write("\x1B[K");
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Config {{{1
// =============================================================================
const TITLE = "Seance";
const MAX_SCORE = 50;
const MAX_MESSAGE_LENGTH = 6;
const MIN_MESSAGE_LENGTH = 3;
const BASE_CHARACTER = '@'.toCodepoint();
const PLANCHETTE = '*'.toCodepoint();
const SPACE = ' '.toCodepoint();
const FIRST_LETTER_NUMBER = 1;
const LAST_LETTER_NUMBER = 26;
const BOARD_INK = BRIGHT + CYAN + FOREGROUND;
const DEFAULT_INK = WHITE + FOREGROUND;
const INPUT_INK = BRIGHT + GREEN + FOREGROUND;
const INSTRUCTIONS_INK = YELLOW + FOREGROUND;
const MISTAKE_EFFECT_INK = BRIGHT + RED + FOREGROUND;
const PLANCHETTE_INK = YELLOW + FOREGROUND;
const TITLE_INK = BRIGHT + RED + FOREGROUND;
const BOARD_X = 29; // screen column
const BOARD_Y = 5; // screen line
const BOARD_HEIGHT = 5; // characters displayed on the left and right borders
const BOARD_WIDTH = 8; // characters displayed on the top and bottom borders
const BOARD_PAD = 1; // blank characters separating the board from its left and right borders
const BOARD_ACTUAL_WIDTH = BOARD_WIDTH + 2 * BOARD_PAD; // screen columns
const BOARD_BOTTOM_Y = BOARD_HEIGHT + 1; // relative to the board
const INPUT_X = BOARD_X;
const INPUT_Y = BOARD_Y + BOARD_BOTTOM_Y + 4;
const MESSAGES_Y = INPUT_Y;
const MISTAKE_EFFECT_PAUSE = 3.0; // seconds
// User input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc pressEnter(prompt: string) {
acceptString(prompt);
}
// Credits and instructions {{{1
// =============================================================================
proc printTitle() {
setStyle(TITLE_INK);
writef("%s\n", TITLE);
setStyle(DEFAULT_INK);
}
proc printCredits() {
printTitle();
writeln("\nOriginal version in BASIC:");
writeln(" Written by Chris Oxlade, 1983.");
writeln(" https://archive.org/details/seance.qb64");
writeln(" https://github.com/chaosotter/basic-games");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
}
proc printInstructions() {
printTitle();
setStyle(INSTRUCTIONS_INK);
writeln("\nMessages from the Spirits are coming through, letter by letter. They want you");
writeln("to remember the letters and type them into the computer in the correct order.");
writeln("If you make mistakes, they will be angry -- very angry...");
writeln("");
writeln("Watch for stars on your screen -- they show the letters in the Spirits'");
writeln("messages.");
setStyle(DEFAULT_INK);
}
// Game {{{1
// =============================================================================
var rsInt = new Random.randomStream(int);
// Return the x coordinate to print the given text centered on the board.
//
proc boardCenteredX(text: string): int {
return (BOARD_X + (BOARD_ACTUAL_WIDTH - text.size) / 2): int;
}
// Print the given text on the given row, centered on the board.
//
proc printCentered(text: string, y: int) {
setCursorPosition(y, boardCenteredX(text));
writef("%s\n", text);
}
// Print the title on the given row, centered on the board.
//
proc printCenteredTitle(y: int) {
setStyle(TITLE_INK);
printCentered(TITLE, y);
setStyle(DEFAULT_INK);
}
// Print the given letter at the given board coordinates.
//
proc printCharacter(y: int, x: int, charCode: int) {
setCursorPosition(y + BOARD_Y, x + BOARD_X);
write(codepointToString(charCode: int(32)));
}
proc printBoard() {
setStyle(BOARD_INK);
for i in 1 .. BOARD_WIDTH {
printCharacter(0, i + 1, BASE_CHARACTER + i); // top border
printCharacter(BOARD_BOTTOM_Y, i + 1, BASE_CHARACTER + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1); // bottom border
}
for i in 1 .. BOARD_HEIGHT {
printCharacter(i , 0, BASE_CHARACTER + LAST_LETTER_NUMBER - i + 1); // left border
printCharacter(i , 3 + BOARD_WIDTH, BASE_CHARACTER + BOARD_WIDTH + i); // right border
}
writeln("");
setStyle(DEFAULT_INK);
}
proc eraseLineFrom(line: int, column: int) {
setCursorPosition(line, column);
eraseLineToEnd();
}
// Print the given mistake effect, do a pause and erase it.
//
proc printMistakeEffect(effect: string) {
var x: int = boardCenteredX(effect);
hideCursor();
setCursorPosition(MESSAGES_Y, x);
setStyle(MISTAKE_EFFECT_INK);
writeln(effect);
setStyle(DEFAULT_INK);
Time.sleep(MISTAKE_EFFECT_PAUSE);
eraseLineFrom(MESSAGES_Y, x);
showCursor();
}
// Return a new message of the given length, after marking its letters on the
// board.
//
proc message(length: int): string {
const LETTER_PAUSE: real = 1.0; // seconds
var y: int = 0;
var x: int = 0;
var letters: string;
hideCursor();
for i in 0 .. length {
var letterNumber: int = rsInt.next(
FIRST_LETTER_NUMBER,
LAST_LETTER_NUMBER
);
letters += codepointToString((BASE_CHARACTER + letterNumber): int(32));
if letterNumber <= BOARD_WIDTH {
// top border
y = 1;
x = letterNumber + 1;
} else if letterNumber <= BOARD_WIDTH + BOARD_HEIGHT {
// right border
y = letterNumber - BOARD_WIDTH;
x = 2 + BOARD_WIDTH;
} else if letterNumber <= BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH {
// bottom border
y = BOARD_BOTTOM_Y - 1;
x = 2 + BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH - letterNumber;
} else {
// left border
y = 1 + LAST_LETTER_NUMBER - letterNumber;
x = 1;
}
setStyle(PLANCHETTE_INK);
printCharacter(y, x, PLANCHETTE);
writeln();
Time.sleep(LETTER_PAUSE);
setStyle(DEFAULT_INK);
printCharacter(y, x, SPACE);
}
showCursor();
return letters;
}
proc acceptMessage(): string {
setStyle(INPUT_INK);
setCursorPosition(INPUT_Y, INPUT_X);
var result: string = acceptString("? ").toUpper();
setStyle(DEFAULT_INK);
eraseLineFrom(INPUT_Y, INPUT_X);
return result;
}
proc play() {
var score: int = 0;
var mistakes: int = 0;
printCenteredTitle(1);
printBoard();
while true {
var messageLength: int = rsInt.next(
MIN_MESSAGE_LENGTH,
MAX_MESSAGE_LENGTH
);
var messageReceived: string = message(messageLength);
var messageUnderstood: string = acceptMessage();
if messageReceived != messageUnderstood {
mistakes += 1;
select mistakes {
when 1 {
printMistakeEffect("The table begins to shake!");
}
when 2 {
printMistakeEffect("The light bulb shatters!");
}
when 3 {
printMistakeEffect("Oh, no! A pair of clammy hands grasps your neck!");
return;
}
}
} else {
score += messageLength;
if score >= MAX_SCORE {
printCentered("Whew! The spirits have gone!", MESSAGES_Y);
printCentered("You live to face another day!", MESSAGES_Y + 1);
return;
}
}
}
}
// Main {{{1
// =============================================================================
proc main() {
setStyle(DEFAULT_INK);
clearScreen();
printCredits();
pressEnter("\nPress the Enter key to read the instructions. ");
clearScreen();
printInstructions();
pressEnter("\nPress the Enter key to start. ");
clearScreen();
play();
writeln("");
}
Sine Wave
/*
Sine Wave
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in Chapel:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2023-03.
Updated to Chapel 2.3.0 on 2025-01-19.
Last modified 20250405T2002+0200.
*/
import IO;
import Math;
var word: [0 .. 1] string;
// `clear` clears the screen and moves the cursor to its home position,
// by printing ANSI codes.
proc clear() {
write("\x1B[2J\x1B[H");
}
proc printCredits() {
clear();
writeln("Sine Wave\n");
writeln("Original version in BASIC:");
writeln(" Creative computing (Morristown, New Jersey, USA), ca. 1980.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2023, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
writeln("Press Enter to start the program.");
IO.readLine();
}
proc getWords() {
const order = ("first", "second");
clear();
for w in 0 .. 1 {
write("Enter the ", order[w], " word ");
IO.readLine(word[w]);
// N.B. The input includes the carriage return character.
}
}
proc draw() {
clear();
var even = false;
var angle = 0.0;
while angle <= 40.0 {
// N.B. `write` is used because the word has a trailing carriage return
// character.
write((26.0 + 25.0 * Math.sin(angle)):int * " ", word[even:int]);
even = !even;
angle += 0.25;
}
}
printCredits();
getWords();
draw();
Slots
// Slots
// A slot machine simulation.
// Original version in BASIC:
// Creative Computing (Morristown, New Jersey, USA).
// Produced by Fred Mirabelle and Bob Harper on 1973-01-29.
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-07.
//
// Last modified: 20250407T1916+0200.
// Modules {{{1
// =============================================================================
import IO;
import Random;
import Time;
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc hideCursor() {
write("\x1B[?25l");
}
proc showCursor() {
write("\x1B[?25h");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Data {{{1
// =============================================================================
param REELS = 3;
param IMAGES = 6;
var images: range = 0 ..< IMAGES;
const image: [images] string = [
" BAR ",
" BELL ",
"ORANGE",
"LEMON ",
" PLUM ",
"CHERRY"
];
const BAR = 0; // position of "BAR" in `image`.
const color: [0 .. 5] int = [
FOREGROUND + WHITE,
FOREGROUND + CYAN,
FOREGROUND + YELLOW,
FOREGROUND + BRIGHT + YELLOW,
FOREGROUND + BRIGHT + WHITE,
FOREGROUND + BRIGHT + RED
];
param MAX_BET = 100;
param MIN_BET = 1;
// User input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
// Print the given prompt, accept a string from the user. If the typed string
// is a valid integer return it; otherwise return 0.
//
proc acceptInteger(prompt: string): int {
var result: int;
var s: string = acceptString(prompt);
try {
result = (s): int;
}
catch exc {
result = 0;
}
return result;
}
// Credits and instructions {{{1
// =============================================================================
proc printCredits() {
clearScreen();
writeln("Slots");
writeln("A slot machine simulation.\n");
writeln("Original version in BASIC:");
writeln(" Creative computing (Morristown, New Jersey, USA).");
writeln(" Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n");
writeln("This version in Chapel:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair\n");
acceptString("Press Enter for instructions. ");
}
proc printInstructions() {
clearScreen();
writeln("You are in the H&M casino, in front of one of our");
writef("one-arm bandits. Bet from %i to %i USD (or 0 to quit).\n\n",
MIN_BET, MAX_BET);
acceptString("Press Enter to start. ");
}
// Game {{{1
// =============================================================================
proc won(prize: int, bet: int): int {
select prize {
when 2 do writeln("DOUBLE!");
when 5 do writeln("*DOUBLE BAR*");
when 10 do writeln("**TOP DOLLAR**");
when 100 do writeln("***JACKPOT***");
}
writeln("You won!");
return (prize + 1) * bet;
}
proc showStandings(usd: int) {
writef("Your standings are %i USD.\n", usd);
}
proc printReels(reel: [] int) {
moveCursorHome();
for r in reel {
setStyle(color[r]);
writef("[%s] ", image[r]);
}
setStyle(NORMAL_STYLE);
writeln("");
}
var rsInt = new Random.randomStream(int);
proc initReels(ref reel: [] int) {
var images: int = (image.size): int;
for i in 0 ..< reel.size {
reel[i] = rsInt.next(0, images - 1);
}
}
proc spinReels(ref reel: [] int) {
const SECONDS = 2;
hideCursor();
var startSecond: int = Time.timeSinceEpoch().seconds;
var currentSecond: int;
do {
initReels(reel);
printReels(reel);
currentSecond = Time.timeSinceEpoch().seconds;
} while currentSecond - startSecond <= SECONDS;
showCursor();
}
proc play() {
var standings: int = 0;
var bet: int = 0;
var reel: [0 ..< REELS] int;
initReels(reel);
label playLoop while true {
label betLoop while true {
clearScreen();
printReels(reel);
bet = acceptInteger("Your bet (or 0 to quit): ");
if bet > MAX_BET {
writef("House limits are %i USD.\n", MAX_BET);
acceptString("Press Enter to try again. ");
} else if bet < MIN_BET {
var confirmation: string =
acceptString("Type \"q\" to confirm you want to quit. ");
if confirmation == "q" || confirmation == "Q" {
break playLoop;
}
} else {
break betLoop;
} // bet check
} // bet loop
clearScreen();
spinReels(reel);
var bars: int = reel.count(BAR);
var equals: int;
for image in images {
equals = max(equals, reel.count(image));
}
select equals {
when 3 {
if bars == 3 {
standings += won(100, bet);
} else {
standings += won(10, bet);
}
}
when 2 {
if bars == 2 {
standings += won(5, bet);
} else {
standings += won(2, bet);
}
}
otherwise {
writeln("You lost.");
standings -= bet;
}
} // prize check
showStandings(standings);
acceptString("Press Enter to continue. ");
} // play loop
showStandings(standings);
if standings < 0 {
writeln("Pay up! Please leave your money on the terminal.");
} else if standings > 0 {
writeln("Collect your winnings from the H&M cashier.");
} else {
writeln("Hey, you broke even.");
}
}
// Main {{{1
// =============================================================================
proc main() {
printCredits();
printInstructions();
play();
}
Stars
// Stars
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-05.
//
// Last modified: 20250405T2255+0200.
import IO;
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc acceptValidInteger(prompt: string): int {
var result: int;
while true {
var s: string = acceptString(prompt);
try {
result = s: int;
break;
}
catch exc {
writeln("Integer expected.");
}
}
return result;
}
proc isYes(s: string): bool {
select(s.toLower()) {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
proc main() {
var name: string = acceptString("What is your name? ");
writef("Hello, %s.\n", name);
do {
var number: int = acceptValidInteger("How many stars do you want? ");
writeln("*" * number);
} while isYes(acceptString("Do you want more stars? "));
}
Strings
// Strings
// Original version in BASIC:
// Example included in Vintage BASIC 1.0.3.
// http://www.vintage-basic.net
// This version in Chapel:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-06.
//
// Last modified: 20250407T1346+0200.
import IO;
proc acceptString(prompt: string): string {
write(prompt);
IO.stdout.flush();
return IO.readLine().strip();
}
proc acceptValidInteger(prompt: string): int {
var result: int;
while true {
var s: string = acceptString(prompt);
try {
result = (s): int;
break;
}
catch exc {
writeln("Integer expected.");
}
}
return result;
}
proc main() {
var s: string = acceptString("Enter a string: ");
var n: int = acceptValidInteger("Enter an integer: ");
writef(
'ASC("%s") --> "%s"[0].toCodepoint() --> %i\n',
s, s, s[0].toCodepoint()
);
writef(
'CHR$(%i) --> codepointToString(%i: int(32)) --> "%s"\n',
n, n, codepointToString(n: int(32))
);
writef(
'LEFT$("%s", %i) --> "%s"[0 .. min(%i - 1, "%s".size - 1)] --> "%s"\n',
s, n, s, n, s, s[0 .. min(n - 1, s.size - 1)]
);
writef(
'MID$("%s", %i) --> "%s"[%i - 1 ..] --> "%s"\n',
s, n, s, n, s[n - 1 ..]
);
writef(
'MID$("%s", %i, 3) --> "%s"[%i - 1 .. min(%i - 1 + 3 - 1, "%s".size - 1)] --> "%s"\n',
s, n, s, n, n, s, s[n - 1 .. min(n - 1 + 3 - 1, s.size - 1)]
);
writef(
'RIGHT$("%s", %i) --> "%s"[max(0, "%s".size - %i) .. "%s".size - 1] --> "%s"\n',
s, n, s, s, n, s, s[max(0, s.size - n) .. s.size - 1]
);
writef(
'LEN("%s") --> "%s".size --> %i\n',
s, s, s.size
);
var sToReal: real;
try {
sToReal = s: real;
writef(
'VAL("%s") --> "%s": real --> %r\n',
s, s, sToReal
);
}
catch ext {
writef(
'VAL("%s") --> "%s": real /* error catched by `catch` */ --> %r\n',
s, s, sToReal
);
}
writef(
'STR$(%i) --> %i: string --> "%s"\n',
n,n, n: string
);
writef(
'SPC(%i) --> " " * %i --> "%s"',
n, n, " " * n
);
}
Xchange
// Xchange
// Original version in BASIC:
// Written by Thomas C. McIntire, 1979.
// Published in "The A to Z Book of Computer Games", 1979.
// https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up
// https://github.com/chaosotter/basic-games
// This improved remake in Chapel:
// Copyright (c) 2025, 2026, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-04-09 and in 2026-02-07/08.
//
// Last modified: 20260208T2107+0100.
// Modules {{{1
// =============================================================================
import Random;
use IO;
use IO.FormattedIO; // format
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setCursorPosition(line: int, column: int) {
writef("\x1B[%i;%iH", line, column);
}
proc setCursorCoord(coord: (int, int)) {
setCursorPosition(coord[0], coord[1]);
}
proc hideCursor() {
write("\x1B[?25l");
}
proc showCursor() {
write("\x1B[?25h");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseLineToEnd() {
write("\x1B[K");
}
proc eraseScreenToEnd() {
write("\x1B[J");
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Data {{{1
// =============================================================
const BOARD_INK = FOREGROUND + BRIGHT + CYAN;
const DEFAULT_INK = FOREGROUND + WHITE;
const INPUT_INK = FOREGROUND + BRIGHT + GREEN;
const INSTRUCTIONS_INK = FOREGROUND + YELLOW;
const TITLE_INK = FOREGROUND + BRIGHT + RED;
const BLANK = "*";
const GRID_HEIGHT = 3; // cell rows
const GRID_WIDTH = 3; // cell columns
const CELLS = GRID_WIDTH * GRID_HEIGHT;
const gridRange = 0 ..< CELLS;
var pristineGrid: [gridRange] string;
const GRIDS_ROW = 3; // screen row where the grids are printed
const GRIDS_COLUMN = 5; // screen column where the left grid is printed
const CELLS_GAP = 2; // distance between the grid cells, in screen rows or columns
const GRIDS_GAP = 16; // screen columns between equivalent cells of the grids
const FIRST_PLAYER = 0;
const MAX_PLAYERS = 4;
const playerRange = FIRST_PLAYER ..< MAX_PLAYERS;
var grid: [playerRange, gridRange] string;
var isPlaying: [0 ..< MAX_PLAYERS] bool;
var players: int = 0;
const QUIT_COMMAND = "X";
// used with coord arrays instead of 0 and 1, for clarity
const Y = 0;
const X = 1;
// User input {{{1
// =============================================================
proc acceptString(prompt: string): string {
write(prompt);
stdout.flush();
return readLine().strip();
}
// Print the given prompt, accept a string from the user. If the typed string
// is a valid integer return it; otherwise return 0.
//
proc getInteger(prompt: string = ""): int {
var result: int;
var s: string = acceptString(prompt);
try {
result = (s): int;
}
catch exc {
result = 0;
}
return result;
}
proc getString(prompt: string = ""): string {
setStyle(INPUT_INK);
var s: string = acceptString(prompt);
setStyle(DEFAULT_INK);
return s;
}
proc pressEnter(prompt: string) {
acceptString(prompt);
}
proc isYes(s: string): bool {
select s.toLower() {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
proc isNo(s: string): bool {
select s.toLower() {
when "n", "no", "nope" do return true;
otherwise do 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".
//
proc yes(prompt: string): bool {
var result: bool;
while true {
var answer: string = getString(prompt);
if isYes(answer) {
result = true;
break;
}
if isNo(answer) {
result = false;
break;
}
}
return result;
}
// Title, instructions and credits {{{1
// =============================================================
proc printTitle() {
setStyle(TITLE_INK);
writeln("Xchange");
setStyle(DEFAULT_INK);
}
proc printCredits() {
printTitle();
writeln("\nOriginal version in BASIC:");
writeln(" Written by Thomas C. McIntire, 1979.");
writeln(" Published in \"The A to Z Book of Computer Games\", 1979.");
writeln(" https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up");
writeln(" https://github.com/chaosotter/basic-games");
writeln("This improved remake in Chapel:");
writeln(" Copyright (c) 2025, 2026, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
}
proc printInstructions() {
printTitle();
setStyle(INSTRUCTIONS_INK);
writeln("\nOne or two may play. If two, you take turns. A grid looks like this:\n");
setStyle(BOARD_INK);
writeln(" F G D");
writef(" A H %s\n", BLANK);
writeln(" E B C\n");
setStyle(INSTRUCTIONS_INK);
writeln("But it should look like this:\n");
setStyle(BOARD_INK);
writeln(" A B C");
writeln(" D E F");
writef(" G H %s\n\n", BLANK);
setStyle(INSTRUCTIONS_INK);
writef("You may exchange any one letter with the '%s', but only one that's adjacent:\n", BLANK);
writeln("above, below, left, or right. Not all puzzles are possible, and you may enter");
writef("'%s' to give up.\n\n", QUIT_COMMAND);
writeln("Here we go...");
setStyle(DEFAULT_INK);
}
// Grids {{{1
// =============================================================
// Print the given player's grid title.
//
proc printGridTitle(player: int) {
setCursorPosition(GRIDS_ROW, GRIDS_COLUMN + (player * GRIDS_GAP));
writef("Player %i", player + 1);
}
// Return the cursor position of the given player's grid cell.
//
proc cellPosition(player: int, cell: int): (int, int) {
var gridRow: int = cell / GRID_HEIGHT;
var gridColumn: int = cell % GRID_WIDTH;
var titleMargin: int = if players > 1 then 2 else 0;
var row: int = GRIDS_ROW + titleMargin + gridRow;
var column: int = GRIDS_COLUMN +
(gridColumn * CELLS_GAP) +
(player * GRIDS_GAP);
return (row, column);
}
// Return the cursor position of the given player's grid prompt.
//
proc gridPromptPositionOf(player: int): (int, int) {
var coord: (int, int) = cellPosition(player, CELLS);
var row: int = coord[0];
var column: int = coord[1];
return (row + 1, column);
}
// Print the given player's grid, in the given or default color.
//
proc printGrid(player: int, color: int = BOARD_INK) {
if players > 1 {
printGridTitle(player);
}
setStyle(color);
for cell in gridRange {
setCursorCoord(cellPosition(player, cell));
write(grid[player, cell]);
}
setStyle(DEFAULT_INK);
}
// Print the current players' grids.
//
proc printGrids() {
for player in 0 ..< players {
if isPlaying[player] {
printGrid(player);
}
}
writeln("");
eraseScreenToEnd();
}
// Init the grids.
//
proc initGrids() {
Random.shuffle(pristineGrid);
for player in 0 ..< players {
for cell in gridRange {
grid[player, cell] = pristineGrid[cell];
}
}
}
// Messages {{{1
// =============================================================
// Return a message prefix for the given player.
//
proc playerPrefix(player: int): string {
return if players > 1 then "Player %i: ".format(player + 1) else "";
}
// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.
//
proc messagePosition(player: int, rowInc: int = 0 ): (int, int) {
var promptCoord: (int, int) = gridPromptPositionOf(player);
return (promptCoord[Y] + 2 + rowInc, 1);
}
// Print the given message about the given player, adding the given row
// increment, which defaults to zero, to the default cursor coordinates.
//
proc printMessage(message: string, player: int, rowInc: int = 0) {
setCursorCoord(messagePosition(player, rowInc));
writef("%s%s", playerPrefix(player), message);
eraseLineToEnd();
writeln("");
}
// Erase the last message about the given player.
//
proc eraseMessage(player: int) {
setCursorCoord(messagePosition(player));
eraseLineToEnd();
}
// Game loop {{{1
// =============================================================
// Return a message with the players range.
//
proc playersRangeMessage(): string {
if MAX_PLAYERS == 2 {
return "1 or 2";
} else {
return "from 1 to %i".format(MAX_PLAYERS);
}
}
// Return the number of players, asking the user if needed.
//
proc numberOfPlayers(): int {
var players: int = 0;
printTitle();
writeln("");
if MAX_PLAYERS == 1 {
players = 1;
} else {
while players < 1 || players > MAX_PLAYERS {
var prompt: string = "Number of players (%s): ".format(playersRangeMessage());
players = getInteger(prompt);
}
}
return players;
}
// Is the given cell the first one on a grid row?
//
proc isFirstCellOfGridRow(cell: int): bool {
return cell % GRID_WIDTH == 0;
}
// Is the given cell the last one on a grid row?
//
proc isLastCellOfGridRow(cell: int): bool {
return (cell + 1) % GRID_WIDTH == 0;
}
// Are the given cells adjacent?
//
proc areCellsAdjacent(cell1: int, cell2: int): bool {
return (cell2 == cell1 + 1 && !isFirstCellOfGridRow(cell2)) ||
(cell2 == cell1 + GRID_WIDTH) ||
(cell2 == cell1 - 1 && !isLastCellOfGridRow(cell2)) ||
(cell2 == cell1 - 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 -1.
//
proc positionToCell(player: int, charCell: int): int {
for cell in gridRange {
if grid[player, cell] == BLANK {
if areCellsAdjacent(charCell, cell) {
return cell;
} else {
break;
}
}
}
printMessage("Illegal move \"%s\".".format(grid[player, charCell]), player);
return -1;
}
// If the given player's command is valid, i.e. a grid character, return its
// position; otherwise return -1.
//
proc commandToPosition(player: int, command: string): int {
if command != BLANK {
for position in gridRange {
if command == grid[player, position] {
return position;
}
}
}
printMessage("Invalid character \"%s\".".format(command), player);
return -1;
}
// Forget the given player, who quitted.
//
proc forgetPlayer(player: int) {
isPlaying[player] = false;
printGrid(player, DEFAULT_INK);
}
// Play the turn of the given player.
//
proc playTurn(player: int) {
var blankPosition: int = 0;
var characterPosition: int = 0;
if isPlaying[player] {
while true {
while true {
var coord: (int, int) = gridPromptPositionOf(player);
setCursorCoord(coord);
eraseLineToEnd();
setCursorCoord(coord);
var command: string = getString("Move: ").toUpper();
if command == QUIT_COMMAND {
forgetPlayer(player);
return;
}
var position: int = commandToPosition(player, command);
if position >= 0 {
characterPosition = position;
break;
}
}
var position: int = positionToCell(player, characterPosition);
if position >= 0 {
blankPosition = position;
break;
}
}
eraseMessage(player);
grid[player, blankPosition] = grid[player, characterPosition];
grid[player, characterPosition] = BLANK;
}
}
// Play the turns of all players.
//
proc playTurns() {
for player in 0 ..< players {
playTurn(player);
}
}
// Is someone playing?
//
proc isSomeonePlaying(): bool {
for player in 0 ..< players {
if isPlaying[player] {
return true;
}
}
return false;
}
proc hasAnEmptyGrid(player: int): bool {
for i in gridRange {
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`.
//
proc hasSomeoneWon(): bool {
var winners: int = 0;
for player in 0 ..< players {
if isPlaying[player] {
if hasAnEmptyGrid(player) {
winners += 1;
if winners > 0 {
printMessage(
"You're the winner%s!".format(if winners > 1 then ", too" else ""),
player,
winners - 1);
}
}
}
}
return winners > 0;
}
// Init the game.
//
proc initGame() {
clearScreen();
players = numberOfPlayers();
for player in 0 ..< players {
isPlaying[player] = true;
}
clearScreen();
printTitle();
initGrids();
printGrids();
}
// Play the game.
//
proc play() {
initGame();
while isSomeonePlaying() {
playTurns();
printGrids();
if hasSomeoneWon() {
break;
}
}
}
// Main {{{1
// =============================================================
proc initOnce() {
// Init the pristine grid.
const FIRST_CHAR_CODE = 'A'.toCodepoint();
for cell in gridRange {
pristineGrid[cell] = codepointToString((FIRST_CHAR_CODE + cell): int(32));
}
pristineGrid[CELLS - 1] = BLANK;
}
// Return `true` if the player does not want to play another game; otherwise
// return `false`.
//
proc enough(): bool {
setCursorCoord(gridPromptPositionOf(FIRST_PLAYER));
return !yes("Another game? ");
}
proc main() {
initOnce();
clearScreen();
printCredits();
pressEnter("\nPress the Enter key to read the instructions. ");
clearScreen();
printInstructions();
pressEnter("\nPress the Enter key to start. ");
while true {
play();
if enough() {
break;
}
}
writeln("So long…");
}
Z-End
// Z-End
// Original version in BASIC:
// A to Z Book of Computer Games, by Thomas C. McIntire, 1979.
// - https://archive.org/details/A_to_Z_Book_of_Computer_Games_1979_Thomas_C_McIntire/page/n293/mode/2up
// - https://github.com/chaosotter/basic-games/tree/master/games/A%20to%20Z%20Book%20of%20Computer%20Games/Z-End
// This version in Chapel:
// Copyright (c) 2026, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2026-02-09.
//
// Last modified: 20260209T1321+0100.
use IO;
use Random;
// Terminal {{{1
// =============================================================================
const BLACK = 0;
const RED = 1;
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const DEFAULT = 9;
const STYLE_OFF = 20;
const FOREGROUND = 30;
const BACKGROUND = 40;
const BRIGHT = 60;
const NORMAL_STYLE = 0;
proc moveCursorHome() {
write("\x1B[H");
}
proc setStyle(style: int) {
writef("\x1B[%im", style);
}
proc color(n: int) {
setStyle(n + FOREGROUND);
}
proc resetAttributes() {
setStyle(NORMAL_STYLE);
}
proc eraseScreen() {
write("\x1B[2J");
}
proc clearScreen() {
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Input {{{1
// =============================================================================
proc acceptString(prompt: string): string {
color(GREEN);
defer {
color(WHITE);
}
write(prompt);
IO.stdout.flush();
return readLine().strip();
}
proc acceptValidInteger(prompt: string): int {
var result: int;
while true {
var s: string = acceptString(prompt);
try {
result = (s): int;
break;
}
catch exc {
writeln("Integer expected.");
}
}
return result;
}
proc isYes(answer: string): bool {
select answer.toLower() {
when "ok", "y", "yeah", "yes" do return true;
otherwise do return false;
}
}
// Main {{{1
// =============================================================================
enum Player {
computer,
human,
}
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
proc printRules() {
clearScreen();
color(RED);
writeln("Z-End\n");
var answer: string = acceptString("Skip the rules? (Y/N) ");
if !isYes(answer) {
color(YELLOW);
writeln();
writeln("I'll print the alphabet, and you're first. You type the number of letters");
writeln("that I should omit next time. We take turns, and the limit per turn is five.");
writeln("The one that gets the 'Z' is the loser, and that's Z-End!");
writeln();
writeln("Good luck, cuz I'm clever...");
color(WHITE);
}
writeln();
}
var rsInt = new randomStream(int);
var firstLetter: int;
proc computerPick(): int {
var pick: int;
var remainingLetters: int = (alphabet.size - firstLetter): int;
if remainingLetters < 6 {
pick = remainingLetters - 1;
} else if remainingLetters > 10 {
pick = rsInt.next(1, 5);
} else {
pick = 1;
}
writeln("My pick is ", pick, ".");
return pick;
}
proc humanPick(): int {
var pick: int;
while true {
pick = acceptValidInteger("Your turn (1-5) ");
color(WHITE);
if pick < 1 || pick > 5 {
writeln("Illegal entry -- must be in range 1 to 5!");
} else {
break;
}
}
return pick;
}
proc gameOver(): bool {
return firstLetter == alphabet.size - 1;
}
proc printAlphabet(omittedLetters: int = 0) {
firstLetter += omittedLetters;
color(MAGENTA);
writeln(alphabet[firstLetter ..]);
writeln();
color(WHITE);
}
proc printResult(player: Player) {
write("Z-End -- ");
select player {
when Player.computer do writeln("Ha ha!");
when Player.human do writeln("Oops!");
}
}
proc pick(player: Player): int {
select player {
when Player.computer do return computerPick();
otherwise do return humanPick();
}
}
proc playing(player: Player): bool {
var picked: int = pick(player);
printAlphabet(omittedLetters = picked);
if gameOver() {
printResult(player);
}
return !gameOver();
}
proc play() {
firstLetter = 0;
printAlphabet();
while playing(Player.human) && playing(Player.computer) { }
}
proc again(): bool {
writeln();
var answer: string = acceptString("Do it again (Y/N) ");
return isYes(answer);
}
proc main() {
printRules();
do {
play();
} while again();
writeln("\nGoodbye.");
}
