Chase
Descrition del contenete del págine
Conversion de Chase a pluri lingues de programation.
Ti-ci programa esset convertet a 11 lingues de programation.
Originale
Origin of chase.bas:
The original file was found in a CP/M EBASIC disk image.
The original code was published in 1977 in "The Best of Creative Computing",
Volume 2, page 253:
https://www.atariarchives.org/bcc2/showpage.php?page=253
10 PRINT "THIS IS THE GAME OF CHASE"
60 PRINT "WANT INSTRUCTIONS";
70 INPUT C$
75 RANDOMIZE
80 IF LEFT$(C$,1)="N" THEN 200
90 IF LEFT$(C$,1)<>"Y" THEN 60
100 PRINT "YOU ARE '*' IN A HIGH VOLTAGE MAZE WITH 5"
110 PRINT "SECURITY MACHINES '+' TRYING TO DESTROY YOU"
120 PRINT "YOU MUST MANEUVER THE SECURITY MACHINES INTO"
130 PRINT "THE MAZE 'X' TO SURVIVE. GOOD LUCK !!!"
140 PRINT "MOVES ARE 7,8,9"
150 PRINT " 4,5,6"
160 PRINT " 1,2,3 0 TO END THE GAME"
170 PRINT
200 DIM A(10,20),E(21),F(21)
210 LET G=0
220 FOR B=1 TO 10
230 FOR C=1 TO 20
240 LET A(B,C)=0
250 IF B=1 THEN 285
260 IF B=10 THEN 285
270 IF C=1 THEN 285
280 IF C=20 THEN 285
283 GOTO 290
285 LET A(B,C)=1
290 NEXT C
300 NEXT B
310 FOR D=1 TO 21
320 LET B=INT(RND*8)+2
330 LET C=INT(RND*18)+2
340 IF A(B,C)<>0 THEN 320
350 LET A(B,C)=1
360 IF D<6 THEN 365
363 GOTO 370
365 LET A(B,C)=2
370 IF D=6 THEN 375
373 GOTO 380
375 LET A(B,C)=3
380 LET E(D)=B
390 LET F(D)=C
400 NEXT D
500 FOR B=1 TO 10
510 FOR C=1 TO 20
520 IF A(B,C)<>0 THEN 530
523 PRINT " ";
525 GOTO 560
530 IF A(B,C)<>1 THEN 540
533 PRINT "X";
535 GOTO 560
540 IF A(B,C)<>2 THEN 550
543 PRINT "+";
545 GOTO 560
550 IF A(B,C)<>3 THEN 560
555 PRINT "*";
560 NEXT C
570 PRINT
580 NEXT B
600 LET B=E(6)
610 LET C=F(6)
620 LET A(B,C)=0
630 INPUT Y
640 ON Y+1 GOTO 1400,680,680,680,690,800,690,660,660,660
660 LET B=B-1
670 GOTO 690
680 LET B=B+1
690 ON Y GOTO 700,800,720,700,800,720,700,800,720
700 LET C=C-1
710 GOTO 800
720 LET C=C+1
800 IF A(B,C)=1 THEN 1500
810 IF A(B,C)=2 THEN 1600
820 LET A(B,C)=3
830 LET E(6)=B
840 LET F(6)=C
850 FOR D=1 TO 5
860 IF A(E(D),F(D))<>2 THEN 960
870 LET A(E(D),F(D))=0
880 IF E(D)>=B THEN 890
883 LET E(D)=E(D)+1
885 GOTO 900
890 IF E(D)=B THEN 900
895 LET E(D)=E(D)-1
900 IF F(D)>=C THEN 910
903 LET F(D)=F(D)+1
905 GOTO 920
910 IF F(D)=C THEN 920
915 LET F(D)=F(D)-1
920 IF A(E(D),F(D))=3 THEN 1600
930 IF A(E(D),F(D))=0 THEN 940
933 LET G=G+1
935 GOTO 950
940 LET A(E(D),F(D))=2
950 IF G=5 THEN 1700
960 NEXT D
970 GOTO 500
1400 PRINT "SORRY TO SEE YOU QUIT"
1410 GOTO 1710
1500 PRINT "ZAP!!! YOU TOUCHED THE FENCE !!!!!"
1510 GOTO 1710
1600 PRINT "** YOU HAVE BEEN DESTROYED BY A LUCKY COMPUTER **"
1610 GOTO 1710
1700 PRINT "YOU ARE LUCKY **YOU DESTROYED ALL THE ENEMY**"
1710 PRINT "WANT TO PLAY AGAIN";
1720 INPUT C$
1730 IF LEFT$(C$,1)="Y" THEN 210
1735 IF LEFT$(C$,1)<>"N" THEN 1710
1740 PRINT "HOPE YOU DON'T FEEL FENCED IN."
1750 PRINT "TRY AGAIN SOMETIME"
2000 END
In C#
// 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 C#:
// Copyright (c) 2024, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-01-01.
//
// Last modified 20251205T1534+0100.
using System;
using System.Linq;
class Chase
{
// Globals {{{1
// =============================================================
// Colors
const ConsoleColor DEFAULT_INK = ConsoleColor.White;
const ConsoleColor INPUT_INK = ConsoleColor.Green;
const ConsoleColor INSTRUCTIONS_INK = ConsoleColor.DarkYellow;
const ConsoleColor TITLE_INK = ConsoleColor.Red;
// Arena
const int ARENA_WIDTH = 20;
const int ARENA_HEIGHT = 10;
const int ARENA_LAST_X = ARENA_WIDTH - 1;
const int ARENA_LAST_Y = ARENA_HEIGHT - 1;
const int ARENA_ROW = 2;
static string[,] arena = new string[ARENA_HEIGHT, ARENA_WIDTH];
const string EMPTY = " ";
const string FENCE = "X";
const string MACHINE = "m";
const string HUMAN = "@";
const int FENCES = 15; // inner obstacles, not the border
// The end
enum End
{
NotYet,
Quit,
Electrified,
Killed,
Victory,
}
static End theEnd;
// Machines
const int MACHINES = 5;
const int MACHINES_DRAG = 2; // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
static int[] machineX = new int[MACHINES];
static int[] machineY = new int[MACHINES];
static bool[] operative = new bool[MACHINES];
static int destroyedMachines; // counter
// Human
static int humanX;
static int humanY;
// Random numbers generator
static Random rand = new Random();
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
static string InputString(string prompt = "")
{
Console.ForegroundColor = INPUT_INK;
Console.Write(prompt);
string result = Console.ReadLine();
Console.ForegroundColor = DEFAULT_INK;
return result;
}
// Print the given prompt and wait until the user presses Enter.
//
static void PressEnter(string prompt)
{
InputString(prompt);
}
// Return `true` if the given string is "yes" or a synonym.
//
static bool IsYes(string s)
{
string[] validOptions = {"ok", "y", "yeah", "yes"};
return validOptions.Contains(s.ToLower());
}
// Return `true` if the given string is "no" or a synonym.
//
static bool IsNo(string s)
{
string[] validOptions = {"n", "no", "nope"};
return validOptions.Contains(s);
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
static bool Yes(string prompt)
{
while (true)
{
string answer = InputString(prompt);
if (IsYes(answer))
{
return true;
}
if (IsNo(answer))
{
return false;
}
}
}
// Title, credits and instructions {{{1
// =============================================================
const string TITLE = "Chase";
// Print the title at the current cursor position.
//
static void PrintTitle()
{
Console.ForegroundColor = TITLE_INK;
Console.WriteLine(TITLE);
Console.ForegroundColor = DEFAULT_INK;
}
static void PrintCredits()
{
PrintTitle();
Console.WriteLine("\nOriginal version in BASIC:");
Console.WriteLine(" Anonymous.");
Console.WriteLine(" Published in \"The Best of Creative Computing\", Volume 2, 1977.");
Console.WriteLine(" https://www.atariarchives.org/bcc2/showpage.php?page=253");
Console.WriteLine("\nThis version in C#:");
Console.WriteLine(" Copyright (c) 2024, Marcos Cruz (programandala.net)");
Console.WriteLine(" SPDX-License-Identifier: Fair");
}
// Print the game instructions and wait for a key.
//
static void PrintInstructions()
{
PrintTitle();
Console.ForegroundColor = INSTRUCTIONS_INK;
Console.WriteLine($"\nYou ({HUMAN}) are in a high voltage maze with {MACHINES}");
Console.WriteLine($"security machines ({MACHINE}) trying to kill you.");
Console.WriteLine($"You must maneuver them into the maze ({FENCE}) to survive.\n");
Console.WriteLine("Good luck!\n");
Console.WriteLine("The movement commands are the following:\n");
Console.WriteLine(" ↖ ↑ ↗");
Console.WriteLine(" NW N NE");
Console.WriteLine(" ← W E →");
Console.WriteLine(" SW S SE");
Console.WriteLine(" ↙ ↓ ↘");
Console.WriteLine("\nPlus 'Q' to end the game.");
Console.ForegroundColor = DEFAULT_INK;
}
// Arena {{{1
// =============================================================
// Display the arena at the top left corner of the screen.
//
static void PrintArena()
{
Console.SetCursorPosition(0, ARENA_ROW);
for (int y = 0; y <= ARENA_LAST_Y; y++)
{
for (int x = 0; x <= ARENA_LAST_X; x++)
{
Console.Write(arena[y, x]);
}
Console.WriteLine();
}
}
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
static bool IsBorder(int y, int x)
{
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}
// Return a random integer in the given inclusive range.
//
static int RandomIntInInclusiveRange(int min, int max)
{
return rand.Next(max - min + 1) + min;
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
static (int y, int x) Place(string s)
{
int y;
int x;
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.
//
static void InhabitArena()
{
for (int m = 0; m < MACHINES; m++)
{
(machineY[m], machineX[m]) = Place(MACHINE);
operative[m] = true;
}
for (int _ = 0; _ < FENCES; _++) Place(FENCE);
(humanY, humanX) = Place(HUMAN);
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
static void CleanArena()
{
for (int y = 0; y <= ARENA_LAST_Y; y++)
{
for (int x = 0; x <= ARENA_LAST_X; x++)
{
arena[y, x] = IsBorder(y, x) ? FENCE : EMPTY;
}
}
}
// Game {{{1
// =============================================================
// Init the game.
//
static void InitGame()
{
CleanArena();
InhabitArena();
destroyedMachines = 0;
theEnd = End.NotYet;
}
// Move the given machine.
//
static void MoveMachine(int m)
{
int maybe;
arena[machineY[m], machineX[m]] = EMPTY;
maybe = rand.Next(2);
if (machineY[m] > humanY)
{
machineY[m] -= maybe;
}
else if (machineY[m] < humanY)
{
machineY[m] += maybe;
}
maybe = rand.Next(2);
if ((machineX[m] > humanX))
{
machineX[m] -= maybe;
}
else if ((machineX[m] < humanX))
{
machineX[m] += maybe;
}
if (arena[machineY[m], machineX[m]] == EMPTY)
{
arena[machineY[m], machineX[m]] = MACHINE;
}
else if (arena[machineY[m], machineX[m]] == FENCE)
{
operative[m] = false;
destroyedMachines += 1;
if (destroyedMachines == MACHINES)
{
theEnd = End.Victory;
}
}
else if (arena[machineY[m], machineX[m]] == HUMAN)
{
theEnd = End.Killed;
}
}
// Maybe move the given operative machine.
//
static void MaybeMoveMachine(int m)
{
if (rand.Next(MACHINES_DRAG) == 0)
{
MoveMachine(m);
}
}
// Move the operative machines.
//
static void MoveMachines()
{
for (int m = 0; m < MACHINES; m++)
{
if (operative[m])
{
MaybeMoveMachine(m);
}
}
}
// Erase the current line to the right from the position of the cursor.
//
static void EraseLineRight()
{
int y = Console.CursorTop;
int x = Console.CursorLeft;
string blank = new String(' ', Console.WindowWidth - x - 1);
Console.Write(blank);
Console.SetCursorPosition(x, y);
}
// Read a user command; update `theEnd` accordingly and return the direction
// increments.
//
static (int yInc, int xInc) GetMove()
{
int yInc = 0;
int xInc = 0;
Console.WriteLine();
EraseLineRight();
string command = InputString("Command: ").ToLower();
switch (command)
{
case "q" : theEnd = End.Quit; break;
case "sw" : yInc = +1; xInc = -1; break;
case "s" : yInc = +1; break;
case "se" : yInc = +1; xInc = +1; break;
case "w" : xInc = -1; break;
case "e" : xInc = +1; break;
case "nw" : yInc = -1; xInc = -1; break;
case "n" : yInc = -1; break;
case "ne" : yInc = -1; xInc = +1; break;
}
return (yInc, xInc);
}
static void Play()
{
int yInc;
int xInc;
while (true) { // game loop
Console.Clear();
PrintTitle();
InitGame();
while (true) { // action loop
PrintArena();
(yInc, xInc) = GetMove();
if (theEnd == End.NotYet)
{
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();
}
}
}
if (theEnd != End.NotYet)
{
break;
}
}// action loop;
switch (theEnd)
{
case End.Quit :
Console.WriteLine("\nSorry to see you quit.");
break;
case End.Electrified :
Console.WriteLine("\nZap! You touched the fence!");
break;
case End.Killed :
Console.WriteLine("\nYou have been killed by a lucky machine.");
break;
case End.Victory :
Console.WriteLine("\nYou are lucky, you destroyed all machines.");
break;
}
if (! Yes("\nDo you want to play again? "))
{
break;
}
}; // game loop
Console.WriteLine("\nHope you don't feel fenced in.");
Console.WriteLine("Try again sometime.");
}
// Main {{{1
// =============================================================
static void Main()
{
Console.ForegroundColor = DEFAULT_INK;
Console.Clear();
PrintCredits();
PressEnter("\nPress the Enter key to read the instructions. ");
Console.Clear();
PrintInstructions();
PressEnter("\nPress the Enter key to start. ");
Play();
}
}
In Chapel
// 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();
}
In Crystal
# 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 Crystal:
#
# Copyright (c) 2024, Marcos Cruz (programandala.net)
# SPDX-License-Identifier: Fair
#
# Written in 2024-11-27/29.
#
# Last modified: 20250511T2305+0200.
# Terminal {{{1
# ==============================================================================
# Screen colors
BLACK = 0
RED = 1
GREEN = 2
YELLOW = 3
BLUE = 4
MAGENTA = 5
CYAN = 6
WHITE = 7
DEFAULT = 9
# Screen attributes
NORMAL = 0
# Screen color offsets
FOREGROUND = +30
BRIGHT = +60
# Moves the cursor to the home position.
def home
print "\e[H"
end
# Clears the screen and moves the cursor to the home position.
def clear_screen
print "\e[2J"
home
end
def set_color(color : Int)
print "\e[#{color}m"
end
def set_attribute(attr : Int)
print "\e[0;#{attr}m"
end
# Sets the cursor position to the given coordinates (the top left position is 1, 1).
def set_cursor_position(line, column : Int)
print "\e[#{line};#{column}H"
end
def erase_line_right
print "\e[K"
end
def hide_cursor
print "\e[?25l"
end
def show_cursor
print "\e[?25h"
end
# Globals {{{1
# =============================================================
# Colors
DEFAULT_INK = FOREGROUND + WHITE
INPUT_INK = FOREGROUND + BRIGHT + GREEN
INSTRUCTIONS_INK = FOREGROUND + YELLOW
TITLE_INK = FOREGROUND + BRIGHT + RED
# Arena
ARENA_WIDTH = 20
ARENA_HEIGHT = 10
ARENA_LAST_X = ARENA_WIDTH - 1
ARENA_LAST_Y = ARENA_HEIGHT - 1
ARENA_ROW = 3
EMPTY = " "
FENCE = "X"
MACHINE = "m"
HUMAN = "@"
FENCES = 15 # inner obstacles, not the border
# The end
enum End
Not_Yet
Quit
Electrified
Killed
Victory
end
the_end : End
# Machines
# XXX TODO
# class Machine
# property x : Int32
# property y : Int32
# property operative : Bool
#
# def initialize
# @x = 0
# @y = 0
# @operative = true
# end
# end
MACHINES = 5
MACHINES_DRAG = 2 # probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
class Global
class_property arena = Array(Array(String)).new(ARENA_HEIGHT) { Array(String).new(ARENA_WIDTH, "") }
class_property machine_x : Array(Int32) = Array.new(MACHINES, 0)
class_property machine_y : Array(Int32) = Array.new(MACHINES, 0)
class_property operative : Array(Bool) = Array.new(MACHINES, true)
class_property destroyed_machines = 0 # counter
class_property human_x = 0
class_property human_y = 0
end
# User input {{{1
# =============================================================
# Print the given prompt and wait until the user enters a String.
def input_string(prompt : String = "") : String
s = nil
set_color(INPUT_INK)
while s.is_a?(Nil)
print prompt
s = gets
end
set_color(DEFAULT_INK)
s
end
# Print the given prompt and wait until the user presses Enter.
def press_enter(prompt : String)
input_string(prompt)
end
# Return `true` if the given String is "yes" or a synonym.
def is_yes?(answer : String) : Bool
["ok", "yeah", "yes", "y"].any? { |yes| answer.downcase == yes }
end
# Return `true` if the given String is "no" or a synonym.
def is_no?(answer : String) : Bool
["no", "nope", "n"].any? { |no| answer.downcase == no }
end
# Print the given prompt, wait until the user enters a valid yes/no
# String, and return `true` for "yes" or `false` for "no".
def yes?(prompt : String) : Bool
while true
answer = input_string(prompt)
if is_yes?(answer)
return true
end
if is_no?(answer)
return false
end
end
end
# Title, credits and instructions {{{1
# =============================================================
TITLE = "Chase"
# Print the title at the current cursor position.
def print_title
set_color(TITLE_INK)
puts TITLE
set_color(DEFAULT_INK)
end
def print_credits
print_title
puts
puts "Original version in BASIC:"
puts " Anonymous."
puts " Published in \"The Best of Creative Computing\", Volume 2, 1977."
puts " https://www.atariarchives.org/bcc2/showpage.php?page=253"
puts
puts "This version in Crystal:"
puts " Copyright (c) 2024, Marcos Cruz (programandala.net)"
puts " SPDX-License-Identifier: Fair"
end
# Print the game instructions and wait for a key.
def print_instructions
print_title
set_color(INSTRUCTIONS_INK)
puts "\nYou (#{HUMAN}) are in a high voltage maze with #{MACHINES}"
puts "security machines (#{MACHINE}) trying to kill you."
puts "You must maneuver them into the maze (#{FENCE}) to survive."
puts
puts "Good luck!"
puts
puts "The movement commands are the following:"
puts
puts " ↖ ↑ ↗"
puts " NW N NE"
puts " ← W E →"
puts " SW S SE"
puts " ↙ ↓ ↘"
puts "\nPlus 'Q' to end the game."
set_color(DEFAULT_INK)
end
# Arena {{{1
# =============================================================
# Display the arena at the top left corner of the screen.
def print_arena
set_cursor_position(ARENA_ROW, 1)
(0..ARENA_LAST_Y).each do |y|
(0..ARENA_LAST_X).each do |x|
print Global.arena[y][x]
end
puts
end
end
# If the given arena coordinates `y` and `x` are part of the border arena (i.e.
# its surrounding fence), return `true`, otherwise return `false`.
def is_border?(y : Int, x : Int) : Bool
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X)
end
# Return a random integer in the given inclusive range.
def random_int_in_inclusive_range(min, max : Int) : Int
return rand(max - min + 1) + min
end
# Place the given String at a random empty position of the arena and return
# the coordinates.
def place(s : String) : Tuple
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 Global.arena[y][x] == EMPTY
break
end
end
Global.arena[y][x] = s
{y, x}
end
# Inhabit the arena with the machines, the inner fences and the human.
def inhabit_arena
(0...MACHINES).each do |m|
Global.machine_y[m], Global.machine_x[m] = place(MACHINE)
Global.operative[m] = true
end
(0...FENCES).each do
place(FENCE)
end
Global.human_y, Global.human_x = place(HUMAN)
end
# Clean the arena, i.e. empty it and add a surrounding fence.
def clean_arena
(0..ARENA_LAST_Y).each do |y|
(0..ARENA_LAST_X).each do |x|
Global.arena[y][x] = is_border?(y, x) ? FENCE : EMPTY
end
end
end
# Game {{{1
# =============================================================
# Move the given machine.
def move_machine(m : Int)
maybe : Int32
Global.arena[Global.machine_y[m]][Global.machine_x[m]] = EMPTY
maybe = rand(2)
if Global.machine_y[m] > Global.human_y
Global.machine_y[m] -= maybe
elsif Global.machine_y[m] < Global.human_y
Global.machine_y[m] += maybe
end
maybe = rand(2)
if (Global.machine_x[m] > Global.human_x)
Global.machine_x[m] -= maybe
elsif (Global.machine_x[m] < Global.human_x)
Global.machine_x[m] += maybe
end
if Global.arena[Global.machine_y[m]][Global.machine_x[m]] == EMPTY
Global.arena[Global.machine_y[m]][Global.machine_x[m]] = MACHINE
elsif Global.arena[Global.machine_y[m]][Global.machine_x[m]] == FENCE
Global.operative[m] = false
Global.destroyed_machines += 1
if Global.destroyed_machines == MACHINES
the_end = End::Victory
end
elsif Global.arena[Global.machine_y[m]][Global.machine_x[m]] == HUMAN
the_end = End::Killed
end
end
# Maybe move the given operative machine.
def maybe_move_machine(m : Int)
if rand(MACHINES_DRAG) == 0
move_machine(m)
end
end
# Move the operative machines.
def move_machines
(0...MACHINES).each do |m|
if Global.operative[m]
maybe_move_machine(m)
end
end
end
# Read a user command; update `the_end` accordingly and return the direction
# increments.
def get_move : Tuple
y_inc = 0
x_inc = 0
puts
erase_line_right
command = input_string("Command: ").downcase
case command
when "q" ; the_end = End::Quit
when "sw"; y_inc = +1; x_inc = -1
when "s" ; y_inc = +1
when "se"; y_inc = +1; x_inc = +1
when "w" ; x_inc = -1
when "e" ; x_inc = +1
when "nw"; y_inc = -1; x_inc = -1
when "n" ; y_inc = -1
when "ne"; y_inc = -1; x_inc = +1
end
return y_inc, x_inc
end
def play
y_inc : Int32
x_inc : Int32
moving : Bool
while true # game loop
clear_screen
print_title
# init game
clean_arena
inhabit_arena
Global.destroyed_machines = 0
the_end = End::Not_Yet
while true # action loop
print_arena
y_inc, x_inc = get_move
if the_end == End::Not_Yet
if y_inc != 0 || x_inc != 0
Global.arena[Global.human_y][Global.human_x] = EMPTY
if Global.arena[Global.human_y + y_inc][Global.human_x + x_inc] == FENCE
the_end = End::Electrified
elsif Global.arena[Global.human_y + y_inc][Global.human_x + x_inc] == MACHINE
the_end = End::Killed
else
Global.arena[Global.human_y][Global.human_x] = EMPTY
Global.human_y = Global.human_y + y_inc
Global.human_x = Global.human_x + x_inc
Global.arena[Global.human_y][Global.human_x] = HUMAN
print_arena
move_machines
end
end
end
if the_end != End::Not_Yet
break
end
end # action loop
puts
case the_end
when End::Quit
puts "Sorry to see you quit."
when End::Electrified
puts "Zap! You touched the fence!"
when End::Killed
puts "You have been killed by a lucky machine."
when End::Victory
puts "You are lucky, you destroyed all machines."
end
if !yes?("\nDo you want to play again? ")
break
end
end # game loop
puts "\nHope you don't feel fenced in."
puts "Try again sometime."
end
# Main {{{1
# =============================================================
set_color(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
In D
// 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 D:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-03-23.
// Last modified: 20251220T0637+0100.
module chase;
// Terminal {{{1
// =============================================================================
enum BLACK = 0;
enum RED = 1;
enum GREEN = 2;
enum YELLOW = 3;
enum BLUE = 4;
enum MAGENTA = 5;
enum CYAN = 6;
enum WHITE = 7;
enum DEFAULT = 9;
enum STYLE_OFF = 20;
enum FOREGROUND = 30;
enum BACKGROUND = 40;
enum BRIGHT = 60;
enum NORMAL_STYLE = 0;
void moveCursorHome()
{
import std.stdio : write;
write("\x1B[H");
}
void setCursorPosition(int line, int col)
{
import std.stdio : writef;
writef("\x1B[%d;%dH", line, col);
}
void hideCursor()
{
import std.stdio : write;
write("\x1B[?25l");
}
void showCursor()
{
import std.stdio : write;
write("\x1B[?25h");
}
void setStyle(int style)
{
import std.stdio : writef;
writef("\x1B[%dm", style);
}
void resetAttributes()
{
setStyle(NORMAL_STYLE);
}
void eraseLineToEnd()
{
import std.stdio : write;
write("\x1B[K");
}
void eraseScreen()
{
import std.stdio : write;
write("\x1B[2J");
}
void clearScreen()
{
eraseScreen();
resetAttributes();
moveCursorHome();
}
// Config {{{1
// =============================================================
enum DEFAULT_INK = WHITE + FOREGROUND;
enum INPUT_INK = BRIGHT + GREEN + FOREGROUND;
enum INSTRUCTIONS_INK = YELLOW + FOREGROUND;
enum TITLE_INK = BRIGHT + RED + FOREGROUND;
// Data {{{1
// =============================================================
// Arena
enum ARENA_WIDTH = 20;
enum ARENA_HEIGHT = 10;
enum ARENA_LAST_X = ARENA_WIDTH - 1;
enum ARENA_LAST_Y = ARENA_HEIGHT - 1;
enum ARENA_ROW = 3;
//string[ARENA_HEIGHT][ARENA_WIDTH] arena;
string[ARENA_WIDTH][ARENA_HEIGHT] arena;
enum EMPTY = " ";
enum FENCE = "X";
enum MACHINE = "m";
enum HUMAN = "@";
enum FENCES = 15; // inner obstacles, not the border
// The end
enum End
{
NOT_YET,
QUIT,
ELECTRIFIED,
KILLED,
VICTORY
}
End theEnd = End.NOT_YET;
// Machines
enum MACHINES = 5;
enum MACHINES_DRAG = 2; // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
int[MACHINES] machineX;
int[MACHINES] machineY;
bool[MACHINES] operative;
int destroyedMachines = 0; // counter
// Human
int humanX = 0;
int humanY = 0;
// User input {{{1
// =============================================================
string acceptString(string prompt)
{
import std.stdio : readln;
import std.stdio : write;
import std.string : strip;
write(prompt);
return strip(readln());
}
string getString(string prompt)
{
setStyle(INPUT_INK);
string s = acceptString(prompt);
setStyle(DEFAULT_INK);
return s;
}
void pressEnter(string prompt)
{
acceptString(prompt);
}
bool isYes(string s)
{
import std.uni : toLower;
switch (toLower(s))
{
case "ok":
case "y":
case "yeah":
case "yes":
return true;
default:
return false;
}
}
bool isNo(string s)
{
import std.uni : toLower;
switch (toLower(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 = getString(prompt);
if (isYes(answer))
{
return true;
}
if (isNo(answer))
{
return false;
}
}
}
// Title, credits and instructions {{{1
// =============================================================
enum TITLE = "Chase";
void printTitle()
{
import std.stdio : writef;
setStyle(TITLE_INK);
writef("%s\n", TITLE);
setStyle(DEFAULT_INK);
}
void printCredits()
{
import std.stdio : writeln;
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 D:");
writeln(" Copyright (c) 2025, Marcos Cruz (programandala.net)");
writeln(" SPDX-License-Identifier: Fair");
}
void printInstructions()
{
import std.stdio : writef;
import std.stdio : writeln;
printTitle();
setStyle(INSTRUCTIONS_INK);
writef("\nYou (%s) are in a high voltage maze with %d\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
// =============================================================
void printArena()
{
import std.stdio : write;
setCursorPosition(ARENA_ROW, 1);
foreach (int y; 0 .. ARENA_LAST_Y + 1)
{
foreach (int x; 0 .. ARENA_LAST_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 isBorder(int y, int x)
{
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
}
int randomIntInInclusiveRange(int min, int max)
{
import std.random : uniform;
return uniform(min, min + max);
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
int[] place(string s)
{
int y = 0;
int x = 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.
void inhabitArena()
{
int[] coords;
foreach (int m; 0 .. MACHINES)
{
coords = place(MACHINE);
machineY[m] = coords[0];
machineX[m] = coords[1];
operative[m] = true;
}
foreach (int i; 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.
void cleanArena()
{
foreach (int y; 0 .. ARENA_LAST_Y + 1)
{
foreach (int x; 0 .. ARENA_LAST_X + 1)
{
arena[y][x] = isBorder(y, x) ? FENCE : EMPTY;
}
}
}
// Game {{{1
// =============================================================
void initGame()
{
cleanArena();
inhabitArena();
destroyedMachines = 0;
theEnd = End.NOT_YET;
}
void moveMachine(int m)
{
import std.random : uniform;
int maybe = 0;
arena[machineY[m]][machineX[m]] = EMPTY;
maybe = uniform(0, 2);
if (machineY[m] > humanY)
{
machineY[m] -= maybe;
}
else if (machineY[m] < humanY)
{
machineY[m] += maybe;
}
maybe = uniform(0, 2);
if (machineX[m] > humanX)
{
machineX[m] -= maybe;
}
else if (machineX[m] < humanX)
{
machineX[m] += maybe;
}
if (arena[machineY[m]][machineX[m]] == EMPTY)
{
arena[machineY[m]][machineX[m]] = MACHINE;
}
else if (arena[machineY[m]][machineX[m]] == FENCE)
{
operative[m] = false;
destroyedMachines += 1;
if (destroyedMachines == MACHINES)
{
theEnd = End.VICTORY;
}
}
else if (arena[machineY[m]][machineX[m]] == HUMAN)
{
theEnd = End.KILLED;
}
}
void maybeMoveMachine(int m)
{
import std.random : uniform;
if (uniform(0, MACHINES_DRAG) == 0)
{
moveMachine(m);
}
}
void moveMachines()
{
foreach (int m; 0 .. MACHINES)
{
if (operative[m])
{
maybeMoveMachine(m);
}
}
}
// Read a user command; update `theEnd` accordingly and return the direction
// increments.
int[] getMove()
{
import std.stdio : write;
import std.uni : toLower;
int yInc = 0;
int xInc = 0;
write("\n");
eraseLineToEnd();
string command = toLower(getString("Command: "));
switch (command)
{
case "q":
theEnd = End.QUIT;
break;
case "sw":
yInc = 1;
xInc = -1;
break;
case "s":
yInc = 1;
break;
case "se":
yInc = 1;
xInc = 1;
break;
case "w":
xInc = -1;
break;
case "e":
xInc = 1;
break;
case "nw":
yInc = -1;
xInc = -1;
break;
case "n":
yInc = -1;
break;
case "ne":
yInc = -1;
xInc = 1;
break;
default:
break;
}
return [yInc, xInc];
}
void play()
{
import std.stdio : writeln;
int yInc = 0;
int xInc = 0;
while (true) { // game loop
clearScreen();
printTitle();
initGame();
actionLoop:
while (true) {
printArena();
int[] coords = 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();
}
}
}
final switch (theEnd)
{
case End.NOT_YET:
break;
case End.QUIT:
writeln("\nSorry to see you quit.");
break actionLoop;
case End.ELECTRIFIED:
writeln("\nZap! You touched the fence!");
break actionLoop;
case End.KILLED:
writeln("\nYou have been killed by a lucky machine.");
break actionLoop;
case 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
// =============================================================
void 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();
}
In Hare
// 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 Hare:
// Copyright (c) 2025, Marcos Cruz (programandala.net)
// SPDX-License-Identifier: Fair
//
// Written on 2025-02-17.
// Last modified: 20260213T1645+0100.
// Modules {{{1
// =============================================================================
use ascii;
use bufio;
use fmt;
use math::random;
use os;
use strings;
use time;
// Terminal {{{1
// =============================================================================
def BLACK = 0;
def RED = 1;
def GREEN = 2;
def YELLOW = 3;
def BLUE = 4;
def MAGENTA = 5;
def CYAN = 6;
def WHITE = 7;
def DEFAULT = 9;
def STYLE_OFF = 20;
def FOREGROUND = 30;
def BACKGROUND = 40;
def BRIGHT = 60;
def NORMAL_STYLE = 0;
fn move_cursor_home() void = {
fmt::print("\x1B[H")!;
};
fn set_cursor_position(line: int, column: int) void = {
fmt::printf("\x1B[{};{}H", line, column)!;
};
fn hide_cursor() void = {
fmt::print("\x1B[?25l")!;
};
fn show_cursor() void = {
fmt::print("\x1B[?25h")!;
};
fn set_style(style: int) void = {
fmt::printf("\x1B[{}m", style)!;
};
fn reset_attributes() void = {
set_style(NORMAL_STYLE);
};
fn erase_line_to_end() void = {
fmt::print("\x1B[K")!;
};
fn erase_screen() void = {
fmt::print("\x1B[2J")!;
};
fn clear_screen() void = {
erase_screen();
reset_attributes();
move_cursor_home();
};
// Config {{{1
// =============================================================
def DEFAULT_INK = FOREGROUND + WHITE;
def INPUT_INK = FOREGROUND + BRIGHT + GREEN;
def INSTRUCTIONS_INK = FOREGROUND + YELLOW;
def TITLE_INK = FOREGROUND + BRIGHT + RED;
// Data {{{1
// =============================================================
// Arena
def ARENA_WIDTH = 20;
def ARENA_HEIGHT = 10;
def ARENA_LAST_X = ARENA_WIDTH - 1;
def ARENA_LAST_Y = ARENA_HEIGHT - 1;
def ARENA_ROW = 3;
let arena: [ARENA_HEIGHT][ARENA_WIDTH]str = [[""...]...];
def EMPTY = " ";
def FENCE = "X";
def MACHINE = "m";
def HUMAN = "@";
def FENCES = 15; // inner obstacles, not the border
// The end
type end = enum {
NOT_YET,
QUIT,
ELECTRIFIED,
KILLED,
VICTORY,
};
let the_end: end = end::NOT_YET;
// Machines
def MACHINES = 5;
def MACHINES_DRAG = 2; // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
let machine_x: [MACHINES]int = [0...];
let machine_y: [MACHINES]int = [0...];
let operative: [MACHINES]bool = [false...];
let destroyed_machines: int = 0; // counter
// Human
let human_x: int = 0;
let human_y: int = 0;
// User input {{{1
// =============================================================
fn print_prompt(prompt: str = "") void = {
fmt::print(prompt)!;
bufio::flush(os::stdout)!;
};
fn accept_string(prompt: str = "") str = {
print_prompt(prompt);
const buffer = match (bufio::read_line(os::stdin)!) {
case let buffer: []u8 =>
yield buffer;
case =>
return "";
};
defer free(buffer);
return strings::dup(strings::fromutf8(buffer)!)!;
};
fn get_string(prompt: str = "") str = {
set_style(INPUT_INK);
defer set_style(DEFAULT_INK);
return accept_string(prompt);
};
fn press_enter(prompt: str) void = {
free(accept_string(prompt));
};
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s: str) bool = {
const lowercase_s = ascii::strlower(s)!;
defer free(lowercase_s);
return switch(lowercase_s) {
case "ok", "y", "yeah", "yes" =>
yield true;
case =>
yield false;
};
};
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s: str) bool = {
const lowercase_s = ascii::strlower(s)!;
defer free(lowercase_s);
return switch(lowercase_s) {
case "n", "no", "nope" =>
yield true;
case =>
yield false;
};
};
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt: str) bool = {
for (true) {
let answer = get_string(prompt);
defer free(answer);
if (is_yes(answer)) {
return true;
};
if (is_no(answer)) {
return false;
};
};
};
// Title, credits and instructions {{{1
// =============================================================
def TITLE = "Chase";
// Print the title at the current cursor position.
//
fn print_title() void = {
set_style(TITLE_INK);
fmt::println(TITLE)!;
set_style(DEFAULT_INK);
};
fn print_credits() void = {
print_title();
fmt::println("\nOriginal version in BASIC:")!;
fmt::println(" Anonymous.")!;
fmt::println(" Published in \"The Best of Creative Computing\", Volume 2, 1977.")!;
fmt::println(" https://www.atariarchives.org/bcc2/showpage.php?page=253")!;
fmt::println("This version in Hare:")!;
fmt::println(" Copyright (c) 2025, Marcos Cruz (programandala.net)")!;
fmt::println(" SPDX-License-Identifier: Fair")!;
};
// Print the game instructions and wait for a key.
//
fn print_instructions() void = {
print_title();
set_style(INSTRUCTIONS_INK);
defer set_style(DEFAULT_INK);
fmt::printfln("\nYou ({}) are in a high voltage maze with {}", HUMAN, MACHINES)!;
fmt::printfln("security machines ({}) trying to kill you.", MACHINE)!;
fmt::printfln("You must maneuver them into the maze ({}) to survive.\n", FENCE)!;
fmt::println("Good luck!\n")!;
fmt::println("The movement commands are the following:\n")!;
fmt::println(" ↖ ↑ ↗")!;
fmt::println(" NW N NE")!;
fmt::println(" ← W E →")!;
fmt::println(" SW S SE")!;
fmt::println(" ↙ ↓ ↘")!;
fmt::println("\nPlus 'Q' to end the game.")!;
};
// Random numbers {{{1
// =============================================================================
let rand: random::random = 0;
fn randomize() void = {
rand = random::init(time::now(time::clock::MONOTONIC).sec: u64);
};
fn random_int_in_inclusive_range(min: int, max: int) int = {
return random::u32n(&rand, (max - min):u32): int + min;
};
// Arena {{{1
// =============================================================
// Display the arena at the top left corner of the screen.
//
fn print_arena() void = {
set_cursor_position(ARENA_ROW, 1);
for (let y = 0; y <= ARENA_LAST_Y; y += 1) {
for (let x = 0; x <= ARENA_LAST_X; x += 1) {
fmt::print(arena[y][x])!;
};
fmt::println()!;
};
};
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
fn is_border(y: int, x: int) bool = {
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X);
};
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
fn place(s: str) (int, int) = {
let y: int = 0;
let x: int = 0;
for (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.
//
fn inhabit_arena() void = {
for (let m = 0; m < MACHINES; m += 1) {
let coords = place(MACHINE);
machine_y[m] = coords.0;
machine_x[m] = coords.1;
operative[m] = true;
};
for (let i = 0; i < FENCES; i += 1) {
place(FENCE);
};
let coords = place(HUMAN);
human_y = coords.0;
human_x = coords.1;
};
// Clean the arena, i.e. empty it and add a surrounding fence.
//
fn clean_arena() void = {
for (let y = 0; y <= ARENA_LAST_Y; y += 1) {
for (let x = 0; x <= ARENA_LAST_X; x += 1) {
arena[y][x] = if (is_border(y, x)) FENCE else EMPTY;
};
};
};
// Game {{{1
// =============================================================
// Init the game.
//
fn init_game() void = {
clean_arena();
inhabit_arena();
destroyed_machines = 0;
the_end = end::NOT_YET;
};
// Move the given machine.
//
fn move_machine(m: int) void = {
let maybe: int = 0;
arena[machine_y[m]][machine_x[m]] = EMPTY;
maybe = random::u32n(&rand, 2): int;
if (machine_y[m] > human_y) {
machine_y[m] -= maybe;
} else if (machine_y[m] < human_y) {
machine_y[m] += maybe;
};
maybe = random::u32n(&rand, 2): int;
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 = end::VICTORY;
};
} else if (arena[machine_y[m]][machine_x[m]] == HUMAN) {
the_end = end::KILLED;
};
};
// Maybe move the given operative machine.
//
fn maybe_move_machine(m: int) void = {
if (random::u32n(&rand, MACHINES_DRAG) == 0) {
move_machine(m);
};
};
// Move the operative machines.
//
fn move_machines() void = {
for (let 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.
//
fn get_move() (int, int) = {
let y_inc: int = 0;
let x_inc: int = 0;
fmt::println()!;
erase_line_to_end();
let command = ascii::strlower(get_string("Command: "))!;
defer free(command);
switch (command) {
case "q" =>
the_end = end::QUIT;
case "sw" =>
y_inc = 1;
x_inc = -1;
case "s" =>
y_inc = 1;
case "se" =>
y_inc = 1;
x_inc = 1;
case "w" =>
x_inc = -1;
case "e" =>
x_inc = 1;
case "nw" =>
y_inc = -1;
x_inc = -1;
case "n" =>
y_inc = -1;
case "ne" =>
y_inc = -1;
x_inc = 1;
case =>
void;
};
return (y_inc, x_inc);
};
fn play() void = {
let y_inc: int = 0;
let x_inc: int = 0;
for (true) { // game loop
clear_screen();
print_title();
init_game();
for (true) { // action loop
print_arena();
let coords = get_move();
y_inc = coords.0;
x_inc = coords.1;
if (the_end == end::NOT_YET) {
if (y_inc != 0 || x_inc != 0) {
arena[human_y][human_x] = EMPTY;
if (arena[human_y + y_inc][human_x + x_inc] == FENCE) {
the_end = end::ELECTRIFIED;
} else if (arena[human_y + y_inc][human_x + x_inc] == MACHINE) {
the_end = end::KILLED;
} else {
arena[human_y][human_x] = EMPTY;
human_y = human_y + y_inc;
human_x = human_x + x_inc;
arena[human_y][human_x] = HUMAN;
print_arena();
move_machines();
};
};
};
if (the_end != end::NOT_YET) {
break;
};
}; // action loop;
switch (the_end) {
case end::QUIT =>
fmt::println("\nSorry to see you quit.")!;
case end::ELECTRIFIED =>
fmt::println("\nZap! You touched the fence!")!;
case end::KILLED =>
fmt::println("\nYou have been killed by a lucky machine.")!;
case end::VICTORY =>
fmt::println("\nYou are lucky, you destroyed all machines.")!;
case =>
void;
};
if (!yes("\nDo you want to play again? ")) {
break;
};
}; // game loop
fmt::println("\nHope you don't feel fenced in.")!;
fmt::println("Try again sometime.")!;
};
// Main {{{1
// =============================================================
export fn main() void = {
randomize();
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();
};
In Kotlin
/*
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 Kotlin:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-05-18.
Last modified 20250519T0024+0200.
*/
import kotlin.random.Random
// Terminal {{{1
// =============================================================
const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9
const val FOREGROUND = +30
const val BACKGROUND = +40
const val BRIGHT = +60
// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
print("\u001B[H")
}
// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
print("\u001B[2J")
moveCursorHome()
}
// Set the cursor position to the given coordinates (the top left position is 1, 1)
fun setCursorPosition(line: Int, column: Int) {
print("\u001B[${line};${column}H")
}
// Set the color.
fun setColor(color: Int) {
print("\u001B[${color}m")
}
// Erase from the current cursor position to the end of the current line.
fun eraseLineRight() {
print("\u001B[K")
}
// Globals {{{1
// =============================================================
// Colors
const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val INSTRUCTIONS_INK = YELLOW + FOREGROUND
const val TITLE_INK = BRIGHT + RED + FOREGROUND
// Arena
const val ARENA_WIDTH = 20
const val ARENA_HEIGHT = 10
const val ARENA_LAST_X = ARENA_WIDTH - 1
const val ARENA_LAST_Y = ARENA_HEIGHT - 1
const val ARENA_ROW = 3
var arena = Array(ARENA_HEIGHT) { Array(ARENA_WIDTH) { "" } }
const val EMPTY = " "
const val FENCE = "X"
const val MACHINE = "m"
const val HUMAN = "@"
const val FENCES = 15 // inner obstacles, not the border
// The end
enum class End {
Not_Yet,
Quit,
Electrified,
Killed,
Victory,
}
var theEnd: End = End.Not_Yet
// Machines
const val MACHINES = 5
const val MACHINES_DRAG = 2 // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
class Machine (
var x: Int = 0,
var y: Int = 0,
var operative: Boolean = true,
)
var machine: Array<Machine> = Array(MACHINES) { Machine() }
var destroyedMachines: Int = 0 // counter
// Human
var humanX: Int = 0
var humanY: Int = 0
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fun inputString(prompt: String = ""): String {
setColor(INPUT_INK)
print(prompt)
val result = readln()
setColor(DEFAULT_INK)
return result
}
// Print the given prompt and wait until the user presses Enter.
//
fun pressEnter(prompt: String) {
inputString(prompt)
}
// Return `true` if the given string is "yes" or a synonym.
//
fun isYes(s: String): Boolean {
return s.lowercase() in arrayOf("ok", "y", "yeah", "yes")
}
// Return `true` if the given string is "no" or a synonym.
//
fun isNo(s: String): Boolean {
return s.lowercase() in arrayOf("n", "no", "nope")
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fun yes(prompt: String): Boolean {
while (true) {
val answer = inputString(prompt)
if (isYes(answer)) return true
if (isNo(answer)) return false
}
}
// Title, credits and instructions {{{1
// =============================================================
const val TITLE = "Chase"
// Print the title at the current cursor position.
//
fun printTitle() {
setColor(TITLE_INK)
println(TITLE)
setColor(DEFAULT_INK)
}
fun printCredits() {
printTitle()
println("\nOriginal version in BASIC:")
println(" Anonymous.")
println(" Published in \"The Best of Creative Computing\", Volume 2, 1977.")
println(" https://www.atariarchives.org/bcc2/showpage.php?page=253")
println("This version in Kotlin:")
println(" Copyright (c) 2025, Marcos Cruz (programandala.net)")
println(" SPDX-License-Identifier: Fair")
}
// Print the game instructions and wait for a key.
//
fun printInstructions() {
printTitle()
setColor(INSTRUCTIONS_INK)
println("\nYou ($HUMAN) are in a high voltage maze with $MACHINES")
println("security machines ($MACHINE) trying to kill you.")
println("You must maneuver them into the maze ($FENCE) to survive.\n")
println("Good luck!\n")
println("The movement commands are the following:\n")
println(" ↖ ↑ ↗")
println(" NW N NE")
println(" ← W E →")
println(" SW S SE")
println(" ↙ ↓ ↘")
println("\nPlus 'Q' to end the game.")
setColor(DEFAULT_INK)
}
// Arena {{{1
// =============================================================
// Display the arena at the top left corner of the screen.
//
fun printArena() {
setCursorPosition(ARENA_ROW, 1)
for (y in 0 .. ARENA_LAST_Y) {
for (x in 0 .. ARENA_LAST_X) {
print(arena[y][x])
}
println()
}
}
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
fun isBorder(y: Int, x: Int): Boolean {
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X)
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
fun place(s: String): Pair<Int, Int> {
var y: Int = 0
var x: Int = 0
while (true) {
y = Random.nextInt(1, ARENA_LAST_Y)
x = Random.nextInt(1, ARENA_LAST_X)
if (arena[y][x] == EMPTY) break
}
arena[y][x] = s
return Pair(y, x)
}
// Inhabit the arena with the machines, the inner fences and the human.
//
fun inhabitArena() {
for (m in 0 .. MACHINES - 1) {
var (y, x) = place(MACHINE)
machine[m].y = y
machine[m].x = x
machine[m].operative = true
}
for (i in 0 .. FENCES - 1) {
place(FENCE)
}
var (y, x) = place(HUMAN)
humanY = y
humanX = x
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
fun cleanArena() {
for (y in 0 .. ARENA_LAST_Y) {
for (x in 0 .. ARENA_LAST_X) {
arena[y][x] = if (isBorder(y, x)) FENCE else EMPTY
}
}
}
// Game {{{1
// =============================================================
// Init the game.
//
fun initGame() {
cleanArena()
inhabitArena()
destroyedMachines = 0
theEnd = End.Not_Yet
}
// Move the given machine.
//
fun moveMachine(m: Int) {
var maybe: Int = 0
arena[machine[m].y][machine[m].x] = EMPTY
maybe = Random.nextInt(2) // 0 .. 1
if (machine[m].y > humanY) {
machine[m].y -= maybe
} else if (machine[m].y < humanY) {
machine[m].y += maybe
}
maybe = Random.nextInt(2) // 0 .. 1
if (machine[m].x > humanX) {
machine[m].x -= maybe
} else if (machine[m].x < humanX) {
machine[m].x += maybe
}
if (arena[machine[m].y][machine[m].x] == EMPTY) {
arena[machine[m].y][machine[m].x] = MACHINE
} else if (arena[machine[m].y][machine[m].x] == FENCE) {
machine[m].operative = false
destroyedMachines += 1
if (destroyedMachines == MACHINES) {
theEnd = End.Victory
}
} else if (arena[machine[m].y][machine[m].x] == HUMAN) {
theEnd = End.Killed
}
}
// Maybe move the given operative machine.
//
fun maybeMoveMachine(m: Int) {
if (Random.nextInt(0, MACHINES_DRAG) == 0) moveMachine(m)
}
// Move the operative machines.
//
fun moveMachines() {
for (m in 0 .. MACHINES - 1) {
if (machine[m].operative) {
maybeMoveMachine(m)
}
}
}
// Read a user command; update `theEnd` accordingly and return the direction
// increments.
//
fun getMove(): Pair<Int, Int> {
var yInc: Int = 0
var xInc: Int = 0
println()
eraseLineRight()
when (inputString("Command: ").lowercase()) {
"q" ->
theEnd = End.Quit
"sw" -> {
yInc = +1
xInc = -1
}
"s" ->
yInc = +1
"se" -> {
yInc = +1
xInc = +1
}
"w" ->
xInc = -1
"e" ->
xInc = +1
"nw" -> {
yInc = -1
xInc = -1
}
"n" ->
yInc = -1
"ne" -> {
yInc = -1
xInc = +1
}
}
return Pair(yInc, xInc)
}
fun play() {
while (true) { // game loop
clearScreen()
printTitle()
initGame()
while (true) { // action loop
printArena()
var (yInc, xInc) = getMove()
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()
}
}
}
if (theEnd != End.Not_Yet) break
}// action loop
when (theEnd) {
End.Quit ->
println("\nSorry to see you quit.")
End.Electrified ->
println("\nZap! You touched the fence!")
End.Killed ->
println("\nYou have been killed by a lucky machine.")
End.Victory ->
println("\nYou are lucky, you destroyed all machines.")
else ->
print("")
}
if (! yes("\nDo you want to play again? ") ) break
} // game loop
println("\nHope you don't feel fenced in.")
println("Try again sometime.")
}
// Main {{{1
// =============================================================
fun main() {
setColor(DEFAULT_INK)
clearScreen()
printCredits()
pressEnter("\nPress the Enter key to read the instructions. ")
clearScreen()
printInstructions()
pressEnter("\nPress the Enter key to start. ")
play()
}
In Nim
#[
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
Version in Oberon-07:
Copyright (c) 2022, 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Version in Odin:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-22.
Last modified: 20250405T0304+0200.
]#
import std/strformat
import std/random
import std/strutils
import std/terminal
# Terminal {{{1
# =============================================================
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
proc eraseLineRight() =
write(stdout, "\e[K")
proc eraseDown() =
# Erases the screen from the current line down to the bottom of the screen.
#
# This is defined in std/terminal but deactivated, because it does not have
# equivalent in Windows.
write(stdout, "\e[J")
proc setCursorPosYX(y: int, x: int) =
setCursorPos(x, y)
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 foreground = +30 # color offset
const background = +40 # color offset
const bright = +60 # foreground and background offset
const normal = 0
const bold = 1 # bold text (or bright on terminals not supporting)
const dim = 2 # dim text
const italic = 3 # italic (or reverse on terminals not supporting)
const underlined = 4
const blinking = 5 # blinking/bold text
const rapidBlinking = 6 # rapid blinking/bold text (not widely supported)
const reversed = 7
const hidden = 8
const crossedout = 9 # strikethrough
const styleOff = +20 # style offset to turn it off
const notBold = 21 # or double underlined on some terminals
const notDim = 22
const notItalic = 23
const notUnderlined = 24
const notBlinking = 25
const notReversed = 27
const notHidden = 28
const notCrossedout = 29
proc ansiStyleCode(fg: int = default, bg: int = default, style: int = normal): string =
result = &"\e[0;{style};{fg + foreground};{bg + background}m"
proc setStyle(style: string) =
write(stdout, style)
proc setStyles(fg: int = default, bg: int = default, style: int = normal) =
setStyle(ansiStyleCode(fg, bg, style))
# Globals {{{1
# =============================================================
# Colors
const defaultInk = ansiStyleCode(white)
const inputInk = ansiStyleCode(bright + green)
const instructionsInk = ansiStyleCode(bright + yellow)
const titleInk = ansiStyleCode(bright + red)
# Arena
const arenaWidth = 20
const arenaHeight = 10
const arenaLastX = arenaWidth - 1
const arenaLastY = arenaHeight - 1
const arenaRow = 3
type Arena = array[arenaHeight, array[arenaWidth, string]]
var arena: Arena
const empty = " "
const fence = "X"
const machine = "m"
const human = "@"
const fences = 15 # inner obstacles, not the border
# The end
type End = enum
notYet,
surrender,
electrified,
killed,
victory,
var theEnd: End
# Machines
const machines = 5
const machinesDrag = 2 # probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
var machineX: array[machines, int]
var machineY: array[machines, int]
var operative: array[machines, bool]
var destroyedMachines: int # counter
# Human
var humanX: int
var humanY: int
# User input {{{1
# =============================================================
# Print the given prompt and wait until the user enters a string.
#
proc inputString(prompt: string = ""): string =
setStyle(inputInk)
defer: setStyle(defaultInk)
write(stdout, prompt)
result = readLine(stdin) # XXX TODO orElse ""
# Print the given prompt and wait until the user presses Enter.
#
proc pressEnter(prompt: string) =
discard inputString(prompt)
# Return `true` if the given string is "yes" or a synonym.
#
proc isYes(s: string): bool =
return toLower(s) in ["ok", "y", "yeah", "yes"]
# Return `true` if the given string is "no" or a synonym.
#
proc isNo(s: string): bool =
return toLower(s) in ["n", "no", "nope"]
# Print the given prompt, wait until the user enters a valid yes/no string,
# and return `true` for "yes" or `false` for "no".
#
proc yes(prompt: string): bool =
while true:
var answer = inputString(prompt)
if isYes(answer):
return true
if isNo(answer):
return false
# Title, credits and instructions {{{1
# =============================================================
const title = "Chase"
# Print the title at the current cursor position.
#
proc printTitle() =
setStyle(titleInk)
writeLine(stdout, title)
setStyle(defaultInk)
proc printCredits() =
printTitle()
writeLine(stdout, "\nOriginal version in BASIC:")
writeLine(stdout, " Anonymous.")
writeLine(stdout, " Published in \"The Best of Creative Computing\", Volume 2, 1977.")
writeLine(stdout, " https://www.atariarchives.org/bcc2/showpage.php?page=253")
writeLine(stdout, "\nThis version in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair")
# Print the game instructions and wait for a key.
#
proc printInstructions() =
printTitle()
setStyle(instructionsInk)
defer: setStyle(defaultInk)
writeLine(stdout, "\nYou (", human, ") are in a high voltage maze with ", machines, "")
writeLine(stdout, "security machines (", machine, ") trying to kill you.")
writeLine(stdout, "You must maneuver them into the maze (", fence, ") to survive.\n")
writeLine(stdout, "Good luck!\n")
writeLine(stdout, "The movement commands are the following:\n")
writeLine(stdout, " ↖ ↑ ↗")
writeLine(stdout, " NW N NE")
writeLine(stdout, " ← W E →")
writeLine(stdout, " SW S SE")
writeLine(stdout, " ↙ ↓ ↘")
writeLine(stdout, "\nPlus 'Q' to end the game.")
# Arena {{{1
# =============================================================
# Display the arena at the top left corner of the screen.
#
proc printArena() =
setCursorPosYX(arenaRow, 0)
for y in 0 .. arenaLastY:
for x in 0 .. arenaLastX:
write(stdout, arena[y][x])
writeLine(stdout, "")
# 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, x: int): bool =
result = (y == 0) or (x == 0) or (y == arenaLastY) or (x == arenaLastX)
# Place the given string at a random empty position of the arena and return
# the coordinates.
#
proc place(s: string): (int, int) =
var y: int
var x: int
while true:
y = rand(1 .. arenaLastY - 1)
x = rand(1 .. arenaLastX - 1)
if arena[y][x] == empty:
break
arena[y][x] = s
result = (y, x)
# Inhabit the arena with the machines, the inner fences and the human.
#
proc inhabitArena() =
for m in 0 ..< machines:
(machineY[m], machineX[m]) = place(machine)
operative[m] = true
for _ in 0 ..< fences:
discard place(fence)
(humanY, humanX) = place(human)
# Clean the arena, i.e. empty it and add a surrounding fence.
#
proc cleanArena() =
for y in 0 .. arenaLastY:
for x in 0 .. arenaLastX:
arena[y][x] = if isBorder(y, x): fence else: empty
# Game {{{1
# =============================================================
# Init the game.
#
proc initGame() =
cleanArena()
inhabitArena()
destroyedMachines = 0
theEnd = notYet
# Move the given machine.
#
proc moveMachine(m: int) =
var maybe: int
arena[machineY[m]][machineX[m]] = empty
maybe = rand(0 .. 1)
if machineY[m] > humanY:
machineY[m] -= maybe
elif machineY[m] < humanY:
machineY[m] += maybe
maybe = rand(0 .. 1)
if machineX[m] > humanX:
machineX[m] -= maybe
elif machineX[m] < humanX:
machineX[m] += maybe
if arena[machineY[m]][machineX[m]] == empty:
arena[machineY[m]][machineX[m]] = machine
elif arena[machineY[m]][machineX[m]] == fence:
operative[m] = false
destroyedMachines += 1
if destroyedMachines == machines:
theEnd = victory
elif arena[machineY[m]][machineX[m]] == human:
theEnd = killed
# Maybe move the given operative machine.
#
proc maybeMoveMachine(m: int) =
if rand(machinesDrag) == 0:
moveMachine(m)
# Move the operative machines.
#
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, int) =
var yInc = 0
var xInc = 0
writeLine(stdout, "")
eraseLineRight()
var command = toLower(inputString("Command: "))
case command:
of "q":
theEnd = surrender
of "sw":
yInc = +1
xInc = -1
of "s":
yInc = +1
of "se":
yInc = +1
xInc = +1
of "w":
xInc = -1
of "e":
xInc = +1
of "nw":
yInc = -1
xInc = -1
of "n":
yInc = -1
of "ne":
yInc = -1
xInc = +1
else:
discard
result = (yInc, xInc)
proc play() =
var yInc: int
var xInc: int
while true: # game loop
clearScreen()
printTitle()
initGame()
while true: # action loop
printArena()
(yInc, xInc) = getMove()
if theEnd == notYet:
if yInc != 0 or xInc != 0:
arena[humanY][humanX] = empty
if arena[humanY + yInc][humanX + xInc] == fence:
theEnd = electrified
elif arena[humanY + yInc][humanX + xInc] == machine:
theEnd = killed
else:
arena[humanY][humanX] = empty
humanY = humanY + yInc
humanX = humanX + xInc
arena[humanY][humanX] = human
printArena()
moveMachines()
if theEnd != notYet:
break
# action loop
case theEnd:
of surrender:
writeLine(stdout, "\nSorry to see you quit.")
of electrified:
writeLine(stdout, "\nZap! You touched the fence!")
of killed:
writeLine(stdout, "\nYou have been killed by a lucky machine.")
of victory:
writeLine(stdout, "\nYou are lucky, you destroyed all machines.")
else:
discard
if not yes("\nDo you want to play again? "):
break
# game loop
writeLine(stdout, "\nHope you don't feel fenced in.")
writeLine(stdout, "Try again sometime.")
# Main {{{1
# =============================================================
randomize()
setStyle(defaultInk)
clearScreen()
printCredits()
pressEnter("\nPress the Enter key to read the instructions. ")
clearScreen()
printInstructions()
pressEnter("\nPress the Enter key to start. ")
play()
In Oberon-07
(*
Chase
Original version in BASIC:
Anonymous. Published in 1977 in The Best of Creative Computing
Volumen 2, page 253:
https://www.atariarchives.org/bcc2/showpage.php?page=253
This version in Oberon-07:
Copyright (c) 2022, 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Last modified 20231215T1548+0100.
*)
MODULE chase;
IMPORT
Input0,
Out,
obqChar,
obqKey,
obqRandom,
obqScreen;
CONST
(* Map size: *)
xSize = 20;
lastX = xSize-1;
ySize = 10;
lastY = ySize-1;
(* Values of `theEnd` beside the default 0: *)
quit = 1;
electrified = 2;
killed = 3;
victory = 4;
machines = 5;
lastMachine = machines-1;
machinesDrag = 2; (* probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc. *)
(* Map content: *)
empty = " ";
fence = "X";
machine = "m";
human = "@";
fences = 15; (* inner obstacles, not the border *)
VAR
map : ARRAY ySize,xSize OF CHAR;
machineX : ARRAY machines OF INTEGER;
machineY : ARRAY machines OF INTEGER;
operative : ARRAY machines OF BOOLEAN;
destroyedMachines : INTEGER; (* counter *)
humanX : INTEGER;
humanY : INTEGER;
theEnd : INTEGER;
moving : BOOLEAN;
xInc : INTEGER;
yInc : INTEGER;
PROCEDURE Instructions;
(*
Display the game instructions and wait for a key.
*)
BEGIN
obqScreen.Clear;
Out.String("You (");
Out.Char(human);
Out.String(") are in a high voltage maze with ");
Out.Int(machines,0);Out.Ln;
Out.String("security machines (");
Out.Char(machine); Out.String(") trying to kill you.");Out.Ln;
Out.String("You must maneuver them into the maze (");
Out.Char(fence);Out.Char(")");Out.Ln;
Out.String("to survive.");Out.Ln;
Out.Ln;
Out.String("Good luck!");Out.Ln;
Out.Ln;
Out.String("The movement keys are the following 8 digits:");Out.Ln;
Out.Ln;
obqScreen.Tab(10);Out.String(" \ ^ / ");Out.Ln;
obqScreen.Tab(10);Out.String(" \ | / ");Out.Ln;
obqScreen.Tab(10);Out.String(" 7 8 9 ");Out.Ln;
obqScreen.Tab(10);Out.String(" <--4 6-->");Out.Ln;
obqScreen.Tab(10);Out.String(" 1 2 3 ");Out.Ln;
obqScreen.Tab(10);Out.String(" / | \ ");Out.Ln;
obqScreen.Tab(10);Out.String(" / V \ ");Out.Ln;
Out.Ln;
Out.String("Plus '0' to end the game.");Out.Ln;
Out.Ln;
Out.String("Press any key to start.");
WHILE Input0.Available() = 0 DO
END;
END Instructions;
PROCEDURE Yes(question: ARRAY OF CHAR): BOOLEAN;
(*
Display the given string `question` followed by the valid answer
keys and wait for a valid keypress ("Y" or "N", case-insensitive).
If the key is "Y" return `TRUE`, otherwise return `FALSE`.
*)
VAR key: CHAR;
BEGIN
Out.String(question);
Out.String(" (Y/N)");
Out.Ln;
REPEAT
key := obqChar.Upper(obqKey.Wait());
UNTIL (key = "Y") OR (key = "N");
RETURN key = "Y"
END Yes;
PROCEDURE PrintMap;
(*
Display the map at the top left corner of the screen.
*)
VAR x,y: INTEGER;
BEGIN
obqScreen.Home;
FOR y := 0 TO lastY DO
FOR x := 0 TO lastX DO
Out.Char(map[y,x])
END;
Out.Ln;
END;
END PrintMap;
PROCEDURE IsBorder(y,x: INTEGER): BOOLEAN;
(*
If the given map coordinates `y` and `x` are part of the border map
(i.e. its surrounding fence), return `TRUE`, otherwise return
`FALSE`.
*)
BEGIN
RETURN (y = 0) OR (x = 0) OR (y = lastY) OR (x = lastX)
END IsBorder;
PROCEDURE Place(VAR y,x: INTEGER; c: CHAR);
(*
Place the given character `c` at a random empty position of the map
and update the given variables `y` and `x` with that position.
*)
VAR tmpY,tmpX: INTEGER;
BEGIN
REPEAT
tmpY := obqRandom.IntRange(1,lastY-1);
tmpX := obqRandom.IntRange(1,lastX-1);
UNTIL map[tmpY,tmpX] = empty;
y := tmpY;
x := tmpX;
map[y,x] := c
END Place;
PROCEDURE InhabitMap;
(*
Occupy the map with the machines, the inner fences and the human.
*)
VAR n,x,y: INTEGER;
BEGIN
FOR n := 0 TO lastMachine DO
Place(machineY[n],machineX[n],machine);
operative[n] := TRUE
END;
FOR n := 1 TO fences DO
Place(y,x,fence); (* XXX TODO *)
END;
Place(humanY,humanX,human);
END InhabitMap;
PROCEDURE MakeMap;
(*
Make an empty map with a surrounding fence.
*)
VAR x,y: INTEGER;
BEGIN
FOR y := 0 TO lastY DO
FOR x := 0 TO lastX DO
IF IsBorder(y,x) THEN
map[y,x] := fence
ELSE
map[y,x] := empty
END;
END;
END;
END MakeMap;
PROCEDURE Init;
(*
Init the game by making a new map and resetting the variables.
*)
BEGIN
MakeMap;
InhabitMap;
destroyedMachines := 0;
theEnd := 0;
END Init;
PROCEDURE MoveMachine(m: INTEGER);
(*
Move the given machine `m`.
*)
VAR
maybe: INTEGER;
BEGIN
map[machineY[m],machineX[m]] := empty;
maybe := obqRandom.IntRange(0,1);
IF machineY[m]>humanY THEN
DEC(machineY[m],maybe)
ELSIF machineY[m]<humanY THEN
INC(machineY[m],maybe)
END;
maybe := obqRandom.IntRange(0,1);
IF (machineX[m]>humanX) THEN
DEC(machineX[m],maybe)
ELSIF (machineX[m]<humanX) THEN
INC(machineX[m],maybe)
END;
IF map[machineY[m],machineX[m]] = empty THEN
map[machineY[m],machineX[m]] := machine
ELSIF map[machineY[m],machineX[m]] = fence THEN
operative[m] := FALSE;
INC(destroyedMachines);
IF destroyedMachines = machines THEN
theEnd := victory
END
ELSIF map[machineY[m],machineX[m]] = human THEN
theEnd := killed
END;
END MoveMachine;
PROCEDURE MoveMachines;
(*
Move all of the operative machines.
*)
VAR machine: INTEGER;
BEGIN
FOR machine := 0 TO lastMachine DO
IF operative[machine] & (obqRandom.IntMax(machinesDrag) = 0) THEN
MoveMachine(machine)
END;
END;
END MoveMachines;
PROCEDURE GetMove(VAR yInc,xInc: INTEGER): BOOLEAN;
(*
Read a keypress and update the given variables `yInc` and `xInc`
according to the direction, with values -1..1.
If the key is "0", update the `theEnd`.
Return `TRUE` if a movement key was pressed, otherwise `FALSE`.
*)
VAR key: CHAR;
BEGIN
yInc := 0;
xInc := 0;
Input0.Read(key);
CASE ORD(key) OF
48: theEnd := quit | (* "0" = quit *)
49: yInc := +1; xInc := -1 | (* "1" = SW *)
50: yInc := +1; | (* "2" = S *)
51: yInc := +1; xInc := +1 | (* "3" = SE *)
52: xInc := -1 | (* "4" = W *)
54: xInc := +1 | (* "6" = E *)
55: yInc := -1; xInc := -1 | (* "7" = NW *)
56: yInc := -1; | (* "8" = N *)
57: yInc := -1; xInc := +1 | (* "9" = NE *)
0..47,53,58..255: (* other = nothing *)
END;
RETURN (yInc # 0) OR (xInc # 0)
END GetMove;
BEGIN
obqRandom.Randomize;
obqScreen.Clear;
Out.String("CHASE");
Out.Ln;
IF Yes("Do you want instructions?") THEN
Instructions;
END;
REPEAT (* game loop *)
obqScreen.Clear;
Init;
REPEAT (* action loop *)
PrintMap;
moving := GetMove(yInc,xInc);
IF theEnd = 0 THEN
IF moving THEN
map[humanY,humanX] := empty;
IF map[humanY+yInc,humanX+xInc] = fence THEN
theEnd := electrified
ELSIF map[humanY+yInc,humanX+xInc] = machine THEN
theEnd := killed
ELSE
map[humanY,humanX] := empty;
humanY := humanY + yInc;
humanX := humanX + xInc;
map[humanY,humanX] := human;
PrintMap;
MoveMachines
END;
END;
END;
UNTIL theEnd # 0; (* action loop *)
CASE theEnd OF
quit:
Out.String("Sorry to see you quit.") |
electrified:
Out.String("Zap! You touched the fence!") |
killed:
Out.String("You have been killed by a lucky machine.") |
victory:
Out.String("You are lucky, you destroyed all machines.");
END;
Out.Ln;
UNTIL ~ Yes("Do you want to play again?"); (* game loop *)
Out.String("Hope you don't feel fenced in.");
Out.Ln;
Out.String("Try again sometime.");
Out.Ln;
END chase.
In Odin
/*
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
Version in Oberon-07:
Copyright (c) 2022, 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
This version in Odin:
Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2023-12-15, 2025-02-27.
Last modified 20250227T1852+0100.
*/
package chase
import "../lib/anodino/src/basic"
import "../lib/anodino/src/read"
import "../lib/anodino/src/term"
import "core:fmt"
import "core:math/rand"
import "core:slice"
import "core:strings"
// Globals {{{1
// =============================================================
// Colors
DEFAULT_INK :: basic.WHITE
INPUT_INK :: basic.BRIGHT_GREEN
INSTRUCTIONS_INK :: basic.YELLOW
TITLE_INK :: basic.BRIGHT_RED
// Arena
ARENA_WIDTH :: 20
ARENA_HEIGHT :: 10
ARENA_LAST_X :: ARENA_WIDTH - 1
ARENA_LAST_Y :: ARENA_HEIGHT - 1
ARENA_ROW :: 3
arena : [ARENA_HEIGHT][ARENA_WIDTH]string
EMPTY :: " "
FENCE :: "X"
MACHINE :: "m"
HUMAN :: "@"
FENCES :: 15 // inner obstacles, not the border
// The end
End :: enum {
Not_Yet,
Quit,
Electrified,
Killed,
Victory,
}
the_end : End
// Machines
MACHINES :: 5
MACHINES_DRAG :: 2 // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
machine_x : [MACHINES]int
machine_y : [MACHINES]int
operative : [MACHINES]bool
destroyed_machines : int // counter
// Human
human_x : int
human_y : int
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
input_string :: proc(prompt : string = "") -> (string, bool) {
basic.color(INPUT_INK)
defer basic.color(DEFAULT_INK)
fmt.print(prompt)
return read.a_string()
}
// Print the given prompt and wait until the user presses Enter.
//
press_enter :: proc(prompt : string) {
s, _ := input_string(prompt)
delete(s)
}
// Return `true` if the given string is "yes" or a synonym.
//
is_yes :: proc(s : string) -> bool {
lowercase_s := strings.to_lower(s)
defer delete(lowercase_s)
return slice.any_of([]string{"ok", "y", "yeah", "yes"}, lowercase_s)
}
// Return `true` if the given string is "no" or a synonym.
//
is_no :: proc(s : string) -> bool {
lowercase_s := strings.to_lower(s)
defer delete(lowercase_s)
return slice.any_of([]string{"n", "no", "nope"}, lowercase_s)
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
yes :: proc(prompt : string) -> bool {
for {
answer, _ := input_string(prompt)
defer delete(answer)
if is_yes(answer) do return true
if is_no(answer) do return false
}
}
// Title, credits and instructions {{{1
// =============================================================
TITLE :: "Chase"
// Print the title at the current cursor position.
//
print_title :: proc() {
basic.color(TITLE_INK)
fmt.println(TITLE)
basic.color(DEFAULT_INK)
}
print_credits :: proc() {
print_title()
fmt.println("\nOriginal version in BASIC:")
fmt.println(" Anonymous.")
fmt.println(" Published in \"The Best of Creative Computing\", Volume 2, 1977.")
fmt.println(" https://www.atariarchives.org/bcc2/showpage.php?page=253")
fmt.println("This version in Odin:")
fmt.println(" Copyright (c) 2023, Marcos Cruz (programandala.net)")
fmt.println(" SPDX-License-Identifier: Fair")
}
// Print the game instructions and wait for a key.
//
print_instructions :: proc() {
print_title()
basic.color(INSTRUCTIONS_INK)
defer basic.color(DEFAULT_INK)
fmt.printfln("\nYou (%v) are in a high voltage maze with %v", HUMAN, MACHINES)
fmt.printfln("security machines (%v) trying to kill you.", MACHINE)
fmt.printfln("You must maneuver them into the maze (%v) to survive.\n", FENCE)
fmt.println("Good luck!\n")
fmt.println("The movement commands are the following:\n")
fmt.println(" ↖ ↑ ↗")
fmt.println(" NW N NE")
fmt.println(" ← W E →")
fmt.println(" SW S SE")
fmt.println(" ↙ ↓ ↘")
fmt.println("\nPlus 'Q' to end the game.")
}
// Arena {{{1
// =============================================================
// Display the arena at the top left corner of the screen.
//
print_arena :: proc() {
term.set_cursor_position(ARENA_ROW, 1)
for y in 0 ..= ARENA_LAST_Y {
for x in 0 ..= ARENA_LAST_X {
fmt.print(arena[y][x])
}
fmt.println()
}
}
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
is_border :: proc(y, x: int) -> bool {
return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X)
}
// Return a random integer in the given inclusive range.
//
random_int_in_inclusive_range :: proc(min, max : int) -> int {
return rand.int_max(max - min + 1) + min
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
place :: proc(s: string) -> (y, x : int) {
for {
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 do break
}
arena[y][x] = s
return y, x
}
// Inhabit the arena with the machines, the inner fences and the human.
//
inhabit_arena :: proc() {
for m in 0 ..< MACHINES {
machine_y[m], machine_x[m] = place(MACHINE)
operative[m] = true
}
for _ in 0 ..< FENCES do place(FENCE)
human_y, human_x = place(HUMAN)
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
clean_arena :: proc() {
for y in 0 ..= ARENA_LAST_Y {
for x in 0 ..= ARENA_LAST_X {
arena[y][x] = is_border(y, x) ? FENCE : EMPTY
}
}
}
// Game {{{1
// =============================================================
// Init the game.
//
init_game :: proc() {
clean_arena()
inhabit_arena()
destroyed_machines = 0
the_end = .Not_Yet
}
// Move the given machine.
//
move_machine :: proc(m: int) {
maybe : int
arena[machine_y[m]][machine_x[m]] = EMPTY
maybe = rand.int_max(2)
if machine_y[m] > human_y {
machine_y[m] -= maybe
} else if machine_y[m] < human_y {
machine_y[m] += maybe
}
maybe = rand.int_max(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
}
}
// Maybe move the given operative machine.
//
maybe_move_machine :: proc(m: int) {
if rand.int_max(MACHINES_DRAG) == 0 do move_machine(m)
}
// Move the operative machines.
//
move_machines :: proc() {
for m in 0 ..< MACHINES {
if operative[m] do maybe_move_machine(m)
}
}
// Read a user command; update `the_end` accordingly and return the direction
// increments.
//
get_move :: proc() -> (y_inc, x_inc : int) {
y_inc = 0
x_inc = 0
fmt.println()
term.erase_line_right()
raw_command, _ := input_string("Command: ")
defer delete(raw_command)
command := strings.to_lower(raw_command)
defer delete(command)
switch command {
case "q" : the_end = .Quit
case "sw" : y_inc = +1; x_inc = -1
case "s" : y_inc = +1;
case "se" : y_inc = +1; x_inc = +1
case "w" : x_inc = -1
case "e" : x_inc = +1
case "nw" : y_inc = -1; x_inc = -1
case "n" : y_inc = -1;
case "ne" : y_inc = -1; x_inc = +1
}
return y_inc, x_inc
}
play :: proc() {
y_inc, x_inc : int
for { // game loop
term.clear_screen()
print_title()
init_game()
for { // action loop
print_arena()
y_inc, x_inc = get_move()
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 do break
}// action loop
#partial switch the_end {
case .Quit :
fmt.println("\nSorry to see you quit.")
case .Electrified :
fmt.println("\nZap! You touched the fence!")
case .Killed :
fmt.println("\nYou have been killed by a lucky machine.")
case .Victory :
fmt.println("\nYou are lucky, you destroyed all machines.")
}
if ! yes("\nDo you want to play again? ") do break
} // game loop
fmt.println("\nHope you don't feel fenced in.")
fmt.println("Try again sometime.")
}
// Main {{{1
// =============================================================
main :: proc() {
basic.randomize()
basic.color(DEFAULT_INK)
term.clear_screen()
print_credits()
press_enter("\nPress the Enter key to read the instructions. ")
term.clear_screen()
print_instructions()
press_enter("\nPress the Enter key to start. ")
play()
}
In Pike
#!/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();
}
In V
/*
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
Version in Oberon-07:
Copyright (c) 2022, 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Version in Odin:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-11.
Last modified 20250122T0344+0100.
*/
import os
import rand
import term
// Console {{{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_color = 9
const foreground = 30
const background = 40
const bright = 60
// Set the given color.
//
fn set_color(color int) {
print('\x1B[0;${color}m')
}
// Erase from the current cursor position to the end of the current line.
//
fn erase_line_right() {
print('\x1B[K')
}
// Constants {{{1
// =============================================================
// Colors
const default_ink = foreground + default_color
const input_ink = foreground + bright + green
const instructions_ink = foreground + yellow
const title_ink = foreground + bright + red
// 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 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
}
// Machines
const machines = 5
const machines_drag = 2 // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
// Context structure {{{1
// =============================================================
struct Machine {
mut:
x int
y int
operative bool
}
struct Human {
mut:
x int
y int
}
struct Context {
mut:
arena [arena_height][arena_width]string
machine [machines]Machine
human Human
destroyed_machines int
the_end End
}
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
set_color(input_ink)
defer { set_color(default_ink) }
return os.input(prompt)
}
// Print the given prompt and wait until the user presses Enter.
//
fn press_enter(prompt string) {
input_string(prompt)
}
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s string) bool {
return ['ok', 'y', 'yeah', 'yes'].any(it == s.to_lower())
}
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s string) bool {
return ['n', 'no', 'nope'].any(it == s.to_lower())
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt string) bool {
mut result := false
for {
answer := input_string(prompt)
if is_yes(answer) {
result = true
break
}
if is_no(answer) {
result = false
break
}
}
return result
}
// Title, credits and instructions {{{1
// =============================================================
const title = 'Chase'
// Print the title at the current cursor position.
//
fn print_title() {
set_color(title_ink)
println(title)
set_color(default_ink)
}
fn print_credits() {
print_title()
println('\nOriginal version in BASIC:')
println(' Anonymous.')
println(' Published in "The Best of Creative Computing", Volume 2, 1977.')
println(' https://www.atariarchives.org/bcc2/showpage.php?page=253')
println('This version in V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
// Print the game instructions and wait for a key.
//
fn print_instructions() {
print_title()
set_color(instructions_ink)
defer { set_color(default_ink) }
println('\nYou (${human}) are in a high voltage maze with ${machines}')
println('security machines (${machine}) trying to kill you.')
println('You must maneuver them into the maze (${fence}) to survive.\n')
println('Good luck!\n')
println('The movement commands are the following:\n')
println(' ↖ ↑ ↗')
println(' NW N NE')
println(' ← W E →')
println(' SW S SE')
println(' ↙ ↓ ↘')
println("\nPlus 'Q' to end the game.")
}
// Arena {{{1
// =============================================================
// Display the arena at the top left corner of the screen.
//
fn print_arena(context Context) {
term.set_cursor_position(y: arena_row, x: 1)
for y in 0 .. arena_last_y + 1 {
for x in 0 .. arena_last_x + 1 {
print(context.arena[y][x])
}
println('')
}
}
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
fn is_border(y int, x int) bool {
return y == 0 || x == 0 || y == arena_last_y || x == arena_last_x
}
// Return a random integer in the given inclusive range.
//
fn random_int_in_inclusive_range(min int, max int) int {
return rand.intn(max - min + 1) or { 0 } + min
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
fn place(s string, mut context Context) (int, int) {
mut x := 0
mut y := 0
for {
y = random_int_in_inclusive_range(1, arena_last_y - 1)
x = random_int_in_inclusive_range(1, arena_last_x - 1)
if context.arena[y][x] == empty {
break
}
}
context.arena[y][x] = s
return y, x
}
// Inhabit the arena with the machines, the inner fences and the human.
//
fn inhabit_arena(mut context Context) {
for m in 0 .. machines {
context.machine[m].y, context.machine[m].x = place(machine, mut context)
context.machine[m].operative = true
}
for _ in 0 .. fences {
place(fence, mut context)
}
context.human.y, context.human.x = place(human, mut context)
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
fn clean_arena(mut context Context) {
for y in 0 .. arena_last_y + 1 {
for x in 0 .. arena_last_x + 1 {
context.arena[y][x] = if is_border(y, x) { fence } else { empty }
}
}
}
// Game {{{1
// =============================================================
// Init the game.
//
fn init_game(mut context Context) {
clean_arena(mut context)
inhabit_arena(mut context)
context.destroyed_machines = 0
context.the_end = .not_yet
}
// Move the given machine.
//
fn move_machine(m int, mut context Context) {
context.arena[context.machine[m].y][context.machine[m].x] = empty
mut maybe := rand.intn(2) or { 0 }
if context.machine[m].y > context.human.y {
context.machine[m].y -= maybe
} else if context.machine[m].y < context.human.y {
context.machine[m].y += maybe
}
maybe = rand.intn(2) or { 0 }
if context.machine[m].x > context.human.x {
context.machine[m].x -= maybe
} else if context.machine[m].x < context.human.x {
context.machine[m].x += maybe
}
if context.arena[context.machine[m].y][context.machine[m].x] == empty {
context.arena[context.machine[m].y][context.machine[m].x] = machine
} else if context.arena[context.machine[m].y][context.machine[m].x] == fence {
context.machine[m].operative = false
context.destroyed_machines += 1
if context.destroyed_machines == machines {
context.the_end = .victory
}
} else if context.arena[context.machine[m].y][context.machine[m].x] == human {
context.the_end = .killed
}
}
// Maybe move the given operative machine.
//
fn maybe_move_machine(m int, mut context Context) {
if rand.intn(machines_drag) or { 0 } == 0 {
move_machine(m, mut context)
}
}
// Move the operative machines.
//
fn move_machines(mut context Context) {
for m in 0 .. machines {
if context.machine[m].operative {
maybe_move_machine(m, mut context)
}
}
}
// Read a user command; update `context.the_end` accordingly and return the direction
// increments.
//
fn get_move(mut context Context) (int, int) {
mut y_inc := 0
mut x_inc := 0
println('')
erase_line_right()
command := input_string('Command: ').to_lower()
match command {
'q' {
context.the_end = .quit
}
'sw' {
y_inc = 1
x_inc = -1
}
's' {
y_inc = 1
}
'se' {
y_inc = 1
x_inc = 1
}
'w' {
x_inc = -1
}
'e' {
x_inc = 1
}
'nw' {
y_inc = -1
x_inc = -1
}
'n' {
y_inc = -1
}
'ne' {
y_inc = -1
x_inc = 1
}
else {}
}
return y_inc, x_inc
}
fn play() {
mut context := Context{}
mut y_inc := 0
mut x_inc := 0
for { // game loop
term.clear()
print_title()
init_game(mut context)
for { // action loop
print_arena(context)
y_inc, x_inc = get_move(mut context)
if context.the_end == .not_yet {
if y_inc != 0 || x_inc != 0 {
context.arena[context.human.y][context.human.x] = empty
if context.arena[context.human.y + y_inc][context.human.x + x_inc] == fence {
context.the_end = .electrified
} else if context.arena[context.human.y + y_inc][context.human.x + x_inc] == machine {
context.the_end = .killed
} else {
context.arena[context.human.y][context.human.x] = empty
context.human.y = context.human.y + y_inc
context.human.x = context.human.x + x_inc
context.arena[context.human.y][context.human.x] = human
print_arena(context)
move_machines(mut context)
}
}
}
if context.the_end != .not_yet {
break
}
} // action loop
match context.the_end {
.quit {
println('\nSorry to see you quit.')
}
.electrified {
println('\nZap! You touched the fence!')
}
.killed {
println('\nYou have been killed by a lucky machine.')
}
.victory {
println('\nYou are lucky, you destroyed all machines.')
}
else {}
}
if !yes('\nDo you want to play again? ') {
break
}
} // game loop
println("\nHope you don't feel fenced in.")
println('Try again sometime.')
}
// Main {{{1
// =============================================================
fn main() {
set_color(default_ink)
term.clear()
print_credits()
press_enter('\nPress the Enter key to read the instructions. ')
term.clear()
print_instructions()
press_enter('\nPress the Enter key to start. ')
play()
}
