Basics of Nim
Descripción del contenido de la página
Conversión de antiguos programas de BASIC a Nim para aprender los rudimentos de este lenguaje.
Etiquetas:
Bagels
#[
Bagels
Original version in BASIC:
D. Resek, P. Rowe, 1978.
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2025-01-19/20.
Last modified: 20250124T0133+0100.
]#
import std/random
import std/sequtils
import std/strformat
import std/strutils
import std/terminal
import std/unicode
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
# Clear the screen, display the credits and wait for a keypress.
#
proc printCredits() =
clearScreen()
writeLine(stdout, "Bagels")
writeLine(stdout, "Number guessing game\n")
writeLine(stdout, "Original source unknown but suspected to be:")
writeLine(stdout, " Lawrence Hall of Science, U.C. Berkely.\n")
writeLine(stdout, "Original version in BASIC:")
writeLine(stdout, " D. Resek, P. Rowe, 1978.")
writeLine(stdout, " Creative computing (Morristown, New Jersey, USA), 1978.\n")
writeLine(stdout, "This version in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair\n")
write(stdout, "Press Enter to read the instructions. ")
discard readLine(stdin)
# Clear the screen, print the instructions and wait for a keypress.
#
proc printInstructions() =
clearScreen()
writeLine(stdout, "Bagels")
writeLine(stdout, "Number guessing game\n")
writeLine(stdout, "I am thinking of a three-digit number that has no two digits the same.")
writeLine(stdout, "Try to guess it and I will give you clues as follows:\n")
writeLine(stdout, " PICO - one digit correct but in the wrong position")
writeLine(stdout, " FERMI - one digit correct and in the right position")
writeLine(stdout, " BAGELS - no digits correct")
write(stdout, "\nPress Enter to start. ")
discard readLine(stdin)
const digits = 3
# Return a sequence containing as many random digits as the value of the
# `digits` constant.
#
proc random(): seq[int] =
while len(result) < digits:
var digit = rand(9)
if digit notin result:
add(result, digit)
# Print the given prompt and update the array whose address is given with a
# three-digit number from the user.
#
proc getInput(prompt: string, userDigit: var array[digits, int]) =
var ok = false
while not ok:
write(stdout, prompt)
var input = readLine(stdin)
if len(input) != digits:
writeLine(stdout, "Remember it's a ", digits, "-digit number.")
continue
ok = true
for pos, charDigit in input:
if isDigit(charDigit):
var stringDigit = newStringOfCap(1)
add(stringDigit, charDigit)
userDigit[pos] = parseInt(stringDigit)
else:
ok = false
writeLine(stdout, "What?")
break
if ok and len(deduplicate(userDigit)) < digits:
writeLine(stdout, "Remember my number has no two digits the same.")
ok = false
# 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:
write(stdout, prompt)
var answer = readLine(stdin)
if isYes(answer):
return true
if isNo(answer):
return false
# Init and run the game loop.
#
proc play() =
const tries = 20
var score = 0
var fermi: int # counter
var pico: int # counter
var userNumber: array[digits, int]
while true:
clearScreen()
var computerNumber = random()
writeLine(stdout, "O.K. I have a number in mind.")
for guess in 1 .. tries:
# writeLine(stdout, "My number: ", computerNumber) # XXX TMP
getInput(fmt"Guess #{guess:00}: ", userNumber)
fermi = 0
pico = 0
for i in 0 ..< digits:
for j in 0 ..< digits:
if userNumber[i] == computerNumber[j]:
if i == j:
fermi += 1
else:
pico += 1
write(stdout, repeat("PICO ", pico))
write(stdout, repeat("FERMI ", fermi))
if pico + fermi == 0:
write(stdout, "BAGELS")
writeLine(stdout, "")
if fermi == digits:
break
if fermi == digits:
writeLine(stdout, "You got it!!!")
score += 1
else:
writeLine(stdout, "Oh well.")
write(stdout, "That's ", tries, " guesses. My number was ")
for i in 0 ..< digits:
write(stdout, computerNumber[i])
writeLine(stdout, ".")
if not yes("Play again? "):
break
if score != 0:
writeLine(stdout, "A ", score, "-point bagels, buff!!")
writeLine(stdout, "Hope you had fun. Bye.")
printCredits()
printInstructions()
play()
Bug
#[
Bug
Original version in BASIC:
Brian Leibowitz, 1978.
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-20.
Last modified: 20250405T0304+0200.
]#
import std/random
import std/strformat
import std/strutils
import std/terminal
import std/unicode
type Bug = tuple [
body: bool,
neck: bool,
head: bool,
feelers: int,
feelerType: char,
tail: bool,
legs: int ]
type Player = tuple [
pronoun: string,
possessive: string,
bug: Bug ]
var computer: Player
var human: Player
const bugParts = 6
type Part = enum
body = 1, neck, head, feeler, tail, leg
# Bug body attributes.
#
const bodyHeight = 2
const feelerLength = 4
const legLength = 2
const maxFeelers = 2
const maxLegs = 6
const neckLength = 2
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
# Move the cursor to the previous row, without changing the column position,
# and erase its line.
#
proc erasePreviousLine() =
cursorUp()
eraseLine()
# Clear the screen, display the credits and wait for a keypress.
#
proc printCredits() =
clearScreen()
writeLine(stdout, "Bug\n")
writeLine(stdout, "Original version in BASIC:")
writeLine(stdout, " Brian Leibowitz, 1978.")
writeLine(stdout, " Creative computing (Morristown, New Jersey, USA), 1978.\n")
writeLine(stdout, "This version in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair\n")
write(stdout, "Press Enter to read the instructions. ")
discard readLine(stdin)
const instructions = """
The object is to finish your bug before I finish mine. Each number
stands for a part of the bug body.
I will roll the die for you, tell you what I rolled for you, what the
number stands for, and if you can get the part. If you can get the
part I will give it to you. The same will happen on my turn.
If there is a change in either bug I will give you the option of
seeing the pictures of the bugs. The numbers stand for parts as
follows:
"""
# Print a table with the bug parts' description.
#
proc printPartsTable() =
const columns = 3
const columnWidth = 8
const columnSeparation = 2
var padding: string
# Headers
const header: array[3, string] = ["Number", "Part", "Quantity"]
for i in 0 ..< columns:
padding = repeat(" ", columnWidth + columnSeparation - len(header[i]))
write(stdout, header[i], padding)
writeLine(stdout, "")
# Rulers
const ruler = repeat("-", columnWidth)
for i in 0 ..< columns:
padding = if i == columns - 1: "" else: repeat(" ", columnSeparation)
write(stdout, ruler, padding)
writeLine(stdout, "")
# Data
var partQuantity: array[bugParts, int] = [1, 1, 1, 2, 1, 6]
for part in Part:
padding = repeat(" ", columnWidth + columnSeparation - len($int(part)))
write(stdout, int(part), padding)
padding = repeat(" ", columnWidth + columnSeparation - len($part))
write(stdout, capitalize($part), padding)
writeLine(stdout, partQuantity[int(part) - 1])
proc printInstructions() =
clearScreen()
writeLine(stdout, "Bug\n")
writeLine(stdout, instructions)
printPartsTable()
write(stdout, "\nPress Enter to start. ")
discard readLine(stdin)
proc printHead() =
writeLine(stdout, " HHHHHHH")
writeLine(stdout, " H H")
writeLine(stdout, " H O O H")
writeLine(stdout, " H H")
writeLine(stdout, " H V H")
writeLine(stdout, " HHHHHHH")
proc printBug(bug: Bug) =
if bug.feelers > 0:
for _ in 0 ..< feelerLength:
write(stdout, " ")
for _ in 0 ..< bug.feelers:
write(stdout, " ", bug.feelerType)
writeLine(stdout, "")
if bug.head:
printHead()
if bug.neck:
for _ in 0 ..< neckLength:
writeLine(stdout, " N N")
if bug.body:
writeLine(stdout, " BBBBBBBBBBBB")
for _ in 0 ..< bodyHeight:
writeLine(stdout, " B B")
if bug.tail:
writeLine(stdout, "TTTTTB B")
writeLine(stdout, " BBBBBBBBBBBB")
if bug.legs > 0:
for _ in 0 ..< legLength:
write(stdout, " ")
for _ in 0 ..< bug.legs:
write(stdout, " L")
writeLine(stdout, "")
proc finished(bug: Bug): bool =
return bug.feelers == maxFeelers and bug.tail and bug.legs == maxLegs
proc dice(): int =
return rand(1 .. 6)
const asText: array[maxLegs + 1, string] = [
"no",
"a",
"two",
"three",
"four",
"five",
"six" ]
# Return a string containing the given number and noun in their proper form.
#
proc plural(number: int, noun: string): string =
let ending = if number > 1: "s" else: ""
return fmt" {asText[number]} {noun}{ending}"
# Add the given part to the given player's bug.
#
proc addPart(part: var Part, player: var Player): bool =
var changed: bool = false
case part:
of Part.body:
if player.bug.body:
writeLine(stdout, ", but ", player.pronoun, " already have a body.")
else:
writeLine(stdout, "; ", player.pronoun, " now have a body:")
player.bug.body = true
changed = true
of Part.neck:
if player.bug.neck:
writeLine(stdout, ", but ", player.pronoun, " already have a neck.")
elif not player.bug.body:
writeLine(stdout, ", but ", player.pronoun, " need a body first.")
else:
writeLine(stdout, "; ", player.pronoun, " now have a neck:")
player.bug.neck = true
changed = true
of Part.head:
if player.bug.head:
writeLine(stdout, ", but ", player.pronoun, " already have a head.")
elif not player.bug.neck:
writeLine(stdout, ", but ", player.pronoun, " need a a neck first.")
else:
writeLine(stdout, "; ", player.pronoun, " now have a head:")
player.bug.head = true
changed = true
of Part.feeler:
if player.bug.feelers == maxFeelers:
writeLine(stdout, ", but ", player.pronoun, " have two feelers already.")
elif not player.bug.head:
writeLine(stdout, ", but ", player.pronoun, " need a head first.")
else:
player.bug.feelers += 1
write(stdout, "; ", player.pronoun, " now have",
plural(player.bug.feelers, "feeler"))
writeLine(stdout, ":")
changed = true
of Part.tail:
if player.bug.tail:
writeLine(stdout, ", but ", player.pronoun, " already have a tail.")
elif not player.bug.body:
writeLine(stdout, ", but ", player.pronoun, " need a body first.")
else:
writeLine(stdout, "; ", player.pronoun, " now have a tail:")
player.bug.tail = true
changed = true
of Part.leg:
if player.bug.legs == maxLegs:
writeLine(stdout, ", but ", player.pronoun, " have",
asText[maxLegs], "feet already.")
elif not player.bug.body:
writeLine(stdout, ", but ", player.pronoun, " need a body first.")
else:
player.bug.legs += 1
write(stdout, "; ", player.pronoun, " now have",
plural(player.bug.legs, "leg"))
writeLine(stdout, ":")
changed = true
return changed
# Ask the user to press the Enter key, wait for the input, then erase the
# prompt text.
#
proc prompt() =
write(stdout, "Press Enter to roll the dice. ")
discard readLine(stdin)
erasePreviousLine()
# Play one turn for the given player, rolling the dice and updating his bug.
#
proc turn(player: var Player) =
prompt()
var number: int = dice()
var part = Part(number)
write(stdout, capitalize(player.pronoun), " rolled a ", number, " (", part, ")")
if addPart(part, player):
writeLine(stdout, "")
printBug(player.bug)
writeLine(stdout, "")
proc printWinner() =
if finished(human.bug) and finished(computer.bug):
writeLine(stdout, "Both of our bugs are finished in the same number of turns!")
elif finished(human.bug):
writeLine(stdout, human.possessive, " bug is finished.")
elif finished(computer.bug):
writeLine(stdout, computer.possessive, " bug is finished.")
proc gameOver(): bool =
return finished(human.bug) or finished(computer.bug)
proc play() =
clearScreen()
while not gameOver():
turn(human)
turn(computer)
printWinner()
# Init the players' data before a new game.
#
proc init() =
human.pronoun = "you"
human.possessive = "Your"
human.bug.feelerType = 'A'
computer.pronoun = "I"
computer.possessive = "My"
computer.bug.feelerType = 'F'
init()
printCredits()
printInstructions()
play()
writeLine(stdout, "I hope you enjoyed the game, play it again soon!!")
Bunny
#[
Bunny
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in Nim:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2023-08.
Last modified: 20250405T0304+0200.
]#
import std/algorithm
import std/terminal
proc cursorHome() =
setCursorPos 0, 0
proc clearScreen() =
eraseScreen()
cursorHome()
proc showCredits() =
echo "Bunny\n"
echo "Original version in BASIC:"
echo " Creative computing (Morristown, New Jersey, USA), ca. 1978.\n"
echo "This version in Nim:"
echo " Copyright (c) 2023, Marcos Cruz (programandala.net)"
echo " SPDX-License-Identifier: Fair\n"
echo "Press any key to start the program."
discard getch()
const Width = 53
const space = ' '
var line: array[Width, char] # buffer
# Print the line buffer.
proc printLine() =
for col in 0 ..< Width:
write(stdout, line[col])
echo ""
const letter: array[5, char] = ['B', 'U', 'N', 'N', 'Y']
const EOL = -1 # end of line identifier
const data = [
1, 2, EOL, 0, 2, 45, 50, EOL, 0, 5, 43, 52, EOL, 0, 7, 41, 52, EOL,
1, 9, 37, 50, EOL, 2, 11, 36, 50, EOL, 3, 13, 34, 49, EOL, 4, 14,
32, 48, EOL, 5, 15, 31, 47, EOL, 6, 16, 30, 45, EOL, 7, 17, 29, 44,
EOL, 8, 19, 28, 43, EOL, 9, 20, 27, 41, EOL, 10, 21, 26, 40, EOL,
11, 22, 25, 38, EOL, 12, 22, 24, 36, EOL, 13, 34, EOL, 14, 33, EOL,
15, 31, EOL, 17, 29, EOL, 18, 27, EOL, 19, 26, EOL, 16, 28, EOL,
13, 30, EOL, 11, 31, EOL, 10, 32, EOL, 8, 33, EOL, 7, 34, EOL, 6,
13, 16, 34, EOL, 5, 12, 16, 35, EOL, 4, 12, 16, 35, EOL, 3, 12, 15,
35, EOL, 2, 35, EOL, 1, 35, EOL, 2, 34, EOL, 3, 34, EOL, 4, 33,
EOL, 6, 33, EOL, 10, 32, 34, 34, EOL, 14, 17, 19, 25, 28, 31, 35,
35, EOL, 15, 19, 23, 30, 36, 36, EOL, 14, 18, 21, 21, 24, 30, 37, 37,
EOL, 13, 18, 23, 29, 33, 38, EOL, 12, 29, 31, 33, EOL, 11, 13, 17,
17, 19, 19, 22, 22, 24, 31, EOL, 10, 11, 17, 18, 22, 22, 24, 24, 29,
29, EOL, 22, 23, 26, 29, EOL, 27, 29, EOL, 28, 29, EOL ]
# Draw the graphic out of `data` and `letter`.
proc draw() =
var d = 0 # pointer in `data`
var col: int # col to draw from
var toCol: int # col to draw to
fill(line, space)
while d < data.len:
col = data[d]
d += 1
if col == EOL:
printLine()
fill(line, space)
else:
toCol = data[d]
d += 1
for c in col .. toCol:
line[c] = letter[c mod letter.len]
clearScreen()
showCredits()
clearScreen()
draw()
Chase
#[
Chase
Original version in BASIC:
Anonymous.
Published in 1977 in "The Best of Creative Computing", Volume 2.
https://www.atariarchives.org/bcc2/showpage.php?page=253
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()
3D Plot
#[
D3 Plot
Original version in BASIC, "3D PLOT":
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in Nim:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2023-01/02, 2023-08.
Last modified: 20250405T0304+0200.
]#
import std/algorithm
import std/math
import std/terminal
proc cursorHome() =
setCursorPos 0, 0
proc clearScreen() =
eraseScreen()
cursorHome()
proc showCredits() =
echo "D3 Plot\n"
echo "Original version in BASIC:"
echo " 3D PLOT"
echo " Creative computing (Morristown, New Jersey, USA), ca. 1980.\n"
echo "This version in Nim:"
echo " Copyright (c) 2023, Marcos Cruz (programandala.net)"
echo " SPDX-License-Identifier: Fair\n"
echo "Press any key to start the program."
discard getch()
proc a(z: float): float =
result = 30 * exp(-z * z / 100)
proc draw() =
const width = 56
const space = ' '
const dot = '*'
var line: array[width, char]
var l = 0
var y1 = 0
var z = 0
var x = -30.0
while x <= 30:
fill(line, space)
l = 0
y1 = 5 * int(sqrt(900 - x * x) / 5)
for y in countdown(y1, -y1, 5):
z = int(25 + a(sqrt(x * x + float(y * y)))-0.7 * float(y))
if z > l:
l = z
line[z] = dot
for pos in 0 ..< width:
write(stdout, line[pos])
echo ""
x += 1.5
clearScreen()
showCredits()
clearScreen()
draw()
Diamond
#[
Diamond
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in Nim:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2023-07-18/19, 2023-08-27.
Last modified: 20250405T0302+0200.
]#
const Lines = 17
for i in 1 .. int(Lines / 2 + 1):
for j in 1 .. int((Lines + 1) / 2) - i + 1:
write(stdout, " ")
for j in 1 .. i * 2 - 1:
write(stdout, "*")
writeLine(stdout, "")
for i in 1 .. int(Lines / 2):
for j in 1 .. i + 1:
write(stdout, " ")
for j in 1 .. (int((Lines + 1) / 2) - i) * 2 - 1:
write(stdout, "*")
writeLine(stdout, "")
Hammurabi
#[
Hammurabi
Description:
A simple text-based simulation game set in the ancient kingdom of Sumeria.
Original program:
Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
BASIC port:
Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
More details:
- https://en.wikipedia.org/wiki/Hamurabi_(video_game)
- https://www.mobygames.com/game/22232/hamurabi/
Improved remake in Odin:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
This improved remake in Odin:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-22.
Last modified: 20251119T0013+0100.
Acknowledgment:
The following Python port was used as a reference of the original
variables: <https://github.com/jquast/hamurabi.py>.
]#
import std/math
import std/random
import std/strformat
import std/strutils
import std/terminal
import system
# 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")
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
# =============================================================
const acresABushelCanSeed = 2 # yearly
const acresAPersonCanSeed = 10 # yearly
const initialAcresPerPerson = 10 # to calculate the initial acres of the city
const bushelsToFeedAPerson = 20 # yearly
const irritationLevels = 5 # after the switch in `showIrritation`
const maxIrritation = 16
const irritationStep = int(maxIrritation / irritationLevels)
const minHarvestedBushelsPerAcre = 17
const rangeOfHarvestedBushelsPerAcre = 10
const maxHarvestedBushelsPerAcre = minHarvestedBushelsPerAcre + rangeOfHarvestedBushelsPerAcre - 1
const plagueChance = 0.15 # 15% yearly
const years = 10 # goverment period
type Result = enum
veryGood
notTooBad
bad
veryBad
var acres: int
var bushelsEatenByRats: int
var bushelsHarvested: int
var bushelsHarvestedPerAcre: int
var bushelsInStore: int
var bushelsToFeedWith: int
var dead: int
var infants: int
var irritation: int # counter (0 .. 99)
var population: int
var starvedPeoplePercentage: int
var totalDead: int
const defaultStyle = ansiStyleCode(white)
const inputStyle = ansiStyleCode(bright + green)
const instructionsStyle = ansiStyleCode(bright + yellow)
const resultStyle = ansiStyleCode(bright + cyan)
const speechStyle = ansiStyleCode(bright + magenta)
const titleStyle = ansiStyleCode(bright + white)
const warningStyle = ansiStyleCode(bright + red)
# User input {{{1
# =============================================================
# Print the given prompt and return the integer entered by the
# user; if the user input is not a valid integer, ask again.
#
proc input(prompt: string): int =
setStyle(inputStyle)
defer: setStyle(defaultStyle)
while true:
try:
write(stdout, prompt)
result = parseInt(readLine(stdin))
break
except ValueError:
continue
proc pause(prompt: string = "> ") =
setStyle(inputStyle)
write(stdout, prompt)
discard readLine(stdin)
setStyle(defaultStyle)
# Instructions and credits {{{1
# =============================================================
proc instructions(): string =
result = fmt"""
Hammurabi is a simulation game in which you, as the ruler of the ancient
kingdom of Sumeria, Hammurabi, manage the resources.
You may buy and sell land with your neighboring city-states for bushels of
grain ― the price will vary between {minHarvestedBushelsPerAcre} and {maxHarvestedBushelsPerAcre} bushels per acre. You also must
use grain to feed your people and as seed to plant the next year's crop.
You will quickly find that a certain number of people can only tend a certain
amount of land and that people starve if they are not fed enough. You also
have the unexpected to contend with such as a plague, rats destroying stored
grain, and variable harvests.
You will also find that managing just the few resources in this game is not a
trivial job. The crisis of population density rears its head very rapidly.
Try your hand at governing ancient Sumeria for a {years}-year term of office."""
proc printInstructions() =
setStyle(instructionsStyle)
writeLine(stdout, instructions())
setStyle(defaultStyle)
const credits = """Hammurabi
Original program:
Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.
BASIC port:
Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.
Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.
This improved remake in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair"""
proc printCredits() =
setStyle(titleStyle)
writeLine(stdout, credits)
setStyle(defaultStyle)
# Game {{{1
# =============================================================
proc ordinalSuffix(n: int): string =
case n:
of 1: return "st"
of 2: return "nd"
of 3: return "rd"
else: return "th"
# Return the description of the given year as the previous one.
#
proc previous(year: int): string =
if year == 0:
return "the previous year"
else:
return fmt"your {year}{ordinalSuffix(year)} year"
# Return the proper wording for `n` persons, using the given or default words
# for singular and plural forms.
#
proc persons(n: int, singular = "person", plural = "people"): string =
case n:
of 0: return "nobody"
of 1: return fmt"one {singular}"
else: return fmt"{n} {plural}"
proc printAnnualReport(year: int) =
clearScreen()
setStyle(speechStyle)
writeLine(stdout, "Hammurabi, I beg to report to you.")
setStyle(defaultStyle)
let children = persons(infants, "infant", "infants")
let were = if infants > 1: "were" else: "was"
write(stdout, &"\nIn {previous(year)}, {persons(dead)} starved and {children} {were} born.\n")
population += infants
if year > 0 and rand(1.0) <= plagueChance:
population = int(population / 2)
setStyle(warningStyle)
writeLine(stdout, "A horrible plague struck! Half the people died.")
setStyle(defaultStyle)
writeLine(stdout, fmt"The population is {population}.")
writeLine(stdout, fmt"The city owns {acres} acres.")
write(stdout, &"You harvested {bushelsHarvested} bushels ({bushelsHarvestedPerAcre} per acre).\n")
if bushelsEatenByRats > 0:
writeLine(stdout, fmt"The rats ate {bushelsEatenByRats} bushels.")
writeLine(stdout, fmt"You have {bushelsInStore} bushels in store.")
bushelsHarvestedPerAcre =
int(rangeOfHarvestedBushelsPerAcre * rand(1.0)) +
minHarvestedBushelsPerAcre
writeLine(stdout, &"Land is trading at {bushelsHarvestedPerAcre} bushels per acre.\n")
proc sayBye() =
setStyle(defaultStyle)
writeLine(stdout, "\nSo long for now.\n")
proc quitGame() =
sayBye()
quit(0)
proc relinquish() =
setStyle(speechStyle)
writeLine(stdout, "\nHammurabi, I am deeply irritated and cannot serve you anymore.")
writeLine(stdout, "Please, get yourself another steward!")
setStyle(defaultStyle)
quitGame()
proc increaseIrritation() =
irritation += 1 + rand(irritationStep - 1)
if irritation >= maxIrritation:
relinquish() # this never returns
proc printIrritated(adverb: string) =
writeLine(stdout, fmt"The steward seems {adverb} irritated.")
proc showIrritation() =
if irritation < irritationStep:
discard
elif irritation < irritationStep * 2:
printIrritated("slightly")
elif irritation < irritationStep * 3:
printIrritated("quite")
elif irritation < irritationStep * 4:
printIrritated("very")
else:
printIrritated("profoundly")
# Print a message begging to repeat an ununderstandable input.
#
proc begRepeat() =
increaseIrritation() # this may never return
setStyle(speechStyle)
writeLine(stdout, "I beg your pardon? I did not understand your order.")
setStyle(defaultStyle)
showIrritation()
# Print a message begging to repeat a wrong input, because there's only `n`
# items of `name`.
#
proc begThinkAgain(n: int, name: string) =
increaseIrritation() # this may never return
setStyle(speechStyle)
writeLine(stdout, fmt"I beg your pardon? You have only {n} {name}. Now then…")
setStyle(defaultStyle)
showIrritation()
# Buy or sell land.
#
proc trade() =
var acresToBuy: int
var acresToSell: int
var ok: bool
while true:
acresToBuy = input("How many acres do you wish to buy? (0 to sell): ")
if acresToBuy < 0:
begRepeat() # this may never return
continue
if bushelsHarvestedPerAcre * acresToBuy <= bushelsInStore:
break
begThinkAgain(bushelsInStore, "bushels of grain")
if acresToBuy != 0:
writeLine(stdout, fmt"You buy {acresToBuy} acres.")
acres += acresToBuy
bushelsInStore -= bushelsHarvestedPerAcre * acresToBuy
writeLine(stdout, fmt"You now have {acres} acres and {bushelsInStore} bushels.")
else:
while true:
acresToSell = input("How many acres do you wish to sell?: ")
if acresToSell < 0:
begRepeat() # this may never return
continue
if acresToSell < acres:
break
begThinkAgain(acres, "acres")
if acresToSell > 0:
writeLine(stdout, fmt"You sell {acresToSell} acres.")
acres -= acresToSell
bushelsInStore += bushelsHarvestedPerAcre * acresToSell
writeLine(stdout, fmt"You now have {acres} acres and {bushelsInStore} bushels.")
# Feed the people.
#
proc feed() =
var ok: bool
while true:
bushelsToFeedWith = input("How many bushels do you wish to feed your people with?: ")
if bushelsToFeedWith < 0:
begRepeat() # this may never return
continue
# Trying to use more grain than is in silos?
if bushelsToFeedWith <= bushelsInStore:
break
begThinkAgain(bushelsInStore, "bushels of grain")
writeLine(stdout, fmt"You feed your people with {bushelsToFeedWith} bushels.")
bushelsInStore -= bushelsToFeedWith
writeLine(stdout, fmt"You now have {bushelsInStore} bushels.")
# Seed the land.
#
proc seed() =
var acresToSeed: int
var ok: bool
while true:
acresToSeed = input("How many acres do you wish to seed?: ")
if acresToSeed < 0:
begRepeat() # this may never return
continue
if acresToSeed == 0:
break
# Trying to seed more acres than you own?
if acresToSeed > acres:
begThinkAgain(acres, "acres")
continue
# Enough grain for seed?
if int(acresToSeed / acresABushelCanSeed) > bushelsInStore:
begThinkAgain(
bushelsInStore,
fmt"bushels of grain,\nand one bushel can seed {acresABushelCanSeed} acres")
continue
# Enough people to tend the crops?
if acresToSeed <= acresAPersonCanSeed * population:
break
begThinkAgain(
population,
fmt"people to tend the fields,\nand one person can seed {acresAPersonCanSeed} acres")
var bushelsUsedForSeeding = int(acresToSeed / acresABushelCanSeed)
writeLine(stdout, fmt"You seed {acresToSeed} acres using {bushelsUsedForSeeding} bushels.")
bushelsInStore -= bushelsUsedForSeeding
writeLine(stdout, fmt"You now have {bushelsInStore} bushels.")
# A bountiful harvest!
bushelsHarvestedPerAcre = rand(1 .. 5)
bushelsHarvested = acresToSeed * bushelsHarvestedPerAcre
bushelsInStore += bushelsHarvested
proc isEven(n: int): bool =
result = n %% 2 == 0
proc checkRats() =
var ratChance = rand(1 .. 5)
bushelsEatenByRats = if isEven(ratChance): int(bushelsInStore / ratChance) else: 0
bushelsInStore -= bushelsEatenByRats
# Set the variables to their values in the first year.
#
proc init() =
dead = 0
totalDead = 0
starvedPeoplePercentage = 0
population = 95
infants = 5
acres = initialAcresPerPerson * (population + infants)
bushelsHarvestedPerAcre = 3
bushelsHarvested = acres * bushelsHarvestedPerAcre
bushelsEatenByRats = 200
bushelsInStore = bushelsHarvested - bushelsEatenByRats
irritation = 0
proc printResult(result: Result) =
setStyle(resultStyle)
case result:
of veryGood:
writeLine(stdout, "A fantastic performance! Charlemagne, Disraeli and Jefferson combined could")
writeLine(stdout, "not have done better!")
of notTooBad:
writeLine(stdout, "Your performance could have been somewat better, but really wasn't too bad at")
write(stdout, &"all. {int(float(population) * 0.8 * rand(1.0))} people would dearly like to see you assassinated, but we all have our\n")
writeLine(stdout, "trivial problems.")
of bad:
writeLine(stdout, "Your heavy-handed performance smacks of Nero and Ivan IV. The people")
writeLine(stdout, "(remaining) find you an unpleasant ruler and, frankly, hate your guts!")
of veryBad:
writeLine(stdout, "Due to this extreme mismanagement you have not only been impeached and thrown")
writeLine(stdout, "out of office but you have also been declared national fink!!!")
setStyle(defaultStyle)
proc printFinalReport() =
clearScreen()
if starvedPeoplePercentage > 0:
write(stdout, &"In your {years}-year term of office, {starvedPeoplePercentage} percent of the\n")
write(stdout, &"population starved per year on the average, i.e., a total of {totalDead} people died!\n\n")
var acresPerPerson = acres / population
write(stdout, &"You started with {initialAcresPerPerson} acres per person and ended with {acresPerPerson}.\n\n")
if starvedPeoplePercentage > 33 or acresPerPerson < 7:
printResult(veryBad)
elif starvedPeoplePercentage > 10 or acresPerPerson < 9:
printResult(bad)
elif starvedPeoplePercentage > 3 or acresPerPerson < 10:
printResult(notTooBad)
else:
printResult(veryGood)
proc checkStarvation(year: int) =
# How many people has been fed?
var fedPeople = int(bushelsToFeedWith / bushelsToFeedAPerson)
if population > fedPeople:
dead = population - fedPeople
starvedPeoplePercentage = int((float64((year - 1) * starvedPeoplePercentage) + float64(dead) * 100.0 / float64(population)) / float64(year))
population -= dead
totalDead += dead
# Starve enough for impeachment?
if dead > int(0.45 * float(population)):
setStyle(warningStyle)
writeLine(stdout, &"\nYou starved {dead} people in one year!!!\n")
setStyle(defaultStyle)
printResult(veryBad)
quitGame()
proc govern() =
init()
printAnnualReport(0)
for year in 1 .. years:
trade()
feed()
seed()
checkRats()
# Let's have some babies
infants = int(rand(1 .. 5) * (20 * acres + bushelsInStore) / population / 100 + 1)
checkStarvation(year)
pause("\nPress the Enter key to read the annual report. ")
printAnnualReport(year)
# Main {{{1
# =============================================================
randomize()
clearScreen()
printCredits()
pause("\nPress the Enter key to read the instructions. ")
clearScreen()
printInstructions()
pause("\nPress the Enter key to start. ")
govern()
pause("Press the Enter key to read the final report. ")
printFinalReport()
sayBye()
High Noon
#[
High Noon
Original version in BASIC:
Designed and programmed by Chris Gaylo, Syosset High School, New York, 1970-09-12.
http://mybitbox.com/highnoon-1970/
http://mybitbox.com/highnoon/
Transcriptions:
https://github.com/MrMethor/Highnoon-BASIC/
https://github.com/mad4j/basic-highnoon/
Version modified for QB64:
By Daniele Olmisani, 2014.
https://github.com/mad4j/basic-highnoon/
Improved remake in Odin:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
This improved remake in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-21.
Last modified: 20250404T0159+0200.
]#
import std/math
import std/random
import std/strformat
import std/strutils
import std/terminal
import std/unicode
# Terminal {{{1
# =============================================================
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
# Global variables and constants {{{1
# =============================================================
type Color = tuple [
foreground: ForegroundColor,
bright: bool,
]
const defaultInk: Color = (fgWhite, false)
const inputInk: Color = (fgGreen, true)
const instructionsInk: Color = (fgYellow, false)
const titleInk: Color = (fgRed, false)
const initialDistance = 100
const initialBullets = 4
const maxWateringTroughs = 3
var distance: int # distance between both gunners, in paces
var strategy: string # player's strategy
var playerBullets: int
var opponentBullets: int
# User input {{{1
# =============================================================
# Print the given prompt and wait until the user enters an integer.
#
proc inputInt(prompt = ""): int =
write(stdout, ansiForegroundColorCode(inputInk.foreground, inputInk.bright))
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
while true:
try:
write(stdout, prompt)
result = parseInt(readLine(stdin))
break
except ValueError:
result = 0
# Print the given prompt and wait until the user enters a string.
#
proc inputString(prompt = ""): string =
write(stdout, ansiForegroundColorCode(inputInk.foreground, inputInk.bright))
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
write(stdout, prompt)
result = readLine(stdin)
# Print the given prompt and wait until the user presses Enter.
#
proc pressAnyKey(prompt: string) =
write(stdout, prompt)
discard getch()
# Return `true` if the given string is "yes" or a synonym.
#
proc isYes(s: string): bool =
result = toLower(s) in ["ok", "y", "yeah", "yes"]
# Return `true` if the given string is "no" or a synonym.
#
proc isNo(s: string): bool =
result = 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, instructions and credits {{{1
# =============================================================
# Print the title at the current cursor position.
#
proc printTitle() =
write(stdout, ansiForegroundColorCode(titleInk.foreground, titleInk.bright))
writeLine(stdout, "High Noon")
write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
proc printCredits() =
printTitle()
writeLine(stdout, "\nOriginal version in BASIC:")
writeLine(stdout, " Designed and programmend by Chris Gaylo, 1970.")
writeLine(stdout, " http://mybitbox.com/highnoon-1970/")
writeLine(stdout, " http://mybitbox.com/highnoon/")
writeLine(stdout, "Transcriptions:")
writeLine(stdout, " https://github.com/MrMethor/Highnoon-BASIC/")
writeLine(stdout, " https://github.com/mad4j/basic-highnoon/")
writeLine(stdout, "Version modified for QB64:")
writeLine(stdout, " By Daniele Olmisani, 2014.")
writeLine(stdout, " https://github.com/mad4j/basic-highnoon/")
writeLine(stdout, "This improved remake in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair")
proc printInstructions() =
printTitle()
write(stdout, ansiForegroundColorCode(instructionsInk.foreground, instructionsInk.bright))
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
writeLine(stdout, "\nYou have been challenged to a showdown by Black Bart, one of")
writeLine(stdout, "the meanest desperadoes west of the Allegheny mountains.")
writeLine(stdout, "\nWhile you are walking down a dusty, deserted side street,")
writeLine(stdout, "Black Bart emerges from a saloon one hundred paces away.")
write(stdout, "\nBy agreement, you each have ", initialBullets, " bullets in your six-guns.")
writeLine(stdout, "\nYour marksmanship equals his. At the start of the walk nei-")
writeLine(stdout, "ther of you can possibly hit the other, and at the end of")
writeLine(stdout, "the walk, neither can miss. the closer you get, the better")
writeLine(stdout, "your chances of hitting black Bart, but he also has beter")
writeLine(stdout, "chances of hitting you.")
# Game loop {{{1
# =============================================================
proc pluralSuffix(n: int): string =
case n:
of 1: return ""
else: return "s"
proc printShellsLeft() =
if playerBullets == opponentBullets:
writeLine(stdout, "Both of you have ", playerBullets, " bullets.")
else:
writeLine(stdout, "You now have ", playerBullets, " bullet", pluralSuffix(playerBullets), " to Black Bart's ", opponentBullets, " bullet", pluralSuffix(opponentBullets), ".")
proc printCheck() =
writeLine(stdout, "******************************************************")
writeLine(stdout, "* *")
writeLine(stdout, "* BANK OF DODGE CITY *")
writeLine(stdout, "* CASHIER'S RECEIT *")
writeLine(stdout, "* *")
writeLine(stdout, fmt"* CHECK NO. {rand(1000 - 1):04} AUGUST ", 10 + rand(10 - 1), "TH, 1889 *")
writeLine(stdout, "* *")
writeLine(stdout, "* *")
writeLine(stdout, "* PAY TO THE BEARER ON DEMAND THE SUM OF *")
writeLine(stdout, "* *")
writeLine(stdout, "* TWENTY THOUSAND DOLLARS-------------------$20,000 *")
writeLine(stdout, "* *")
writeLine(stdout, "******************************************************")
proc getReward() =
writeLine(stdout, "As mayor of Dodge City, and on behalf of its citizens,")
writeLine(stdout, "I extend to you our thanks, and present you with this")
writeLine(stdout, "reward, a check for $20,000, for killing Black Bart.\n\n")
printCheck()
writeLine(stdout, "\n\nDon't spend it all in one place.")
proc moveTheOpponent() =
var paces = 2 + rand(8 - 1)
writeLine(stdout, "Black Bart moves ", paces, " paces.")
distance -= paces
# Maybe move the opponent; if so, return `true`, otherwise return `false`. A
# true `silent` flag allows to omit the message when the opponent doesn't
# move.
#
proc maybeMoveTheOpponent(silent = false): bool =
if rand(1) == 0: # 50% chances
moveTheOpponent()
return true
else:
if not silent:
writeLine(stdout, "Black Bart stands still.")
return false
proc missedShot(): bool =
result = rand(1.0) * 10 <= float(distance / 10)
# Handle the opponent's shot and return a flag with the result: if the
# opponent kills the player, return `true`; otherwise return `false`.
#
proc theOpponentFiresAndKills(): bool =
writeLine(stdout, "Black Bart fires…")
opponentBullets -= 1
if missedShot():
writeLine(stdout, "A miss…")
case opponentBullets:
of 3:
writeLine(stdout, "Whew, were you lucky. That bullet just missed your head.")
of 2:
writeLine(stdout, "But Black Bart got you in the right shin.")
of 1:
writeLine(stdout, "Though Black Bart got you on the left side of your jaw.")
of 0:
writeLine(stdout, "Black Bart must have jerked the trigger.")
else:
discard
else:
if strategy == "j":
writeLine(stdout, "That trick just saved yout life. Black Bart's bullet")
writeLine(stdout, "was stopped by the wood sides of the trough.")
else:
writeLine(stdout, "Black Bart shot you right through the heart that time.")
writeLine(stdout, "You went kickin' with your boots on.")
return true
result = false
# Handle the opponent's strategy and return a flag with the result: if the
# opponent runs or kills the player, return `true`; otherwise return `false`.
#
proc theOpponentKillsOrRuns(): bool =
if distance >= 10 or playerBullets == 0:
if maybeMoveTheOpponent(silent = true):
return false
if opponentBullets > 0:
return theOpponentFiresAndKills()
else:
if playerBullets > 0:
if rand(1) == 0: # 50% chances
writeLine(stdout, "Now is your chance, Black Bart is out of bullets.")
else:
writeLine(stdout, "Black Bart just hi-tailed it out of town rather than face you")
writeLine(stdout, "without a loaded gun. You can rest assured that Black Bart")
writeLine(stdout, "won't ever show his face around this town again.")
return true
result = false
proc play() =
distance = initialDistance
var wateringTroughs = 0
playerBullets = initialBullets
opponentBullets = initialBullets
block showdown:
while true:
writeLine(stdout, "You are now ", distance, " paces apart from Black Bart.")
printShellsLeft()
write(stdout, ansiForegroundColorCode(instructionsInk.foreground, instructionsInk.bright))
writeLine(stdout, "\nStrategies:")
writeLine(stdout, " [A]dvance")
writeLine(stdout, " [S]tand still")
writeLine(stdout, " [F]ire")
writeLine(stdout, " [J]ump behind the watering trough")
writeLine(stdout, " [G]ive up")
writeLine(stdout, " [T]urn tail and run")
write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
strategy = toLower(inputString("What is your strategy? "))
case strategy:
of "a": # advance
while true:
var paces = inputInt("How many paces do you advance? ")
if paces < 0:
writeLine(stdout, "None of this negative stuff, partner, only positive numbers.")
elif paces > 10:
writeLine(stdout, "Nobody can walk that fast.")
else:
distance -= paces
break
of "s": # stand still
writeLine(stdout, "That move made you a perfect stationary target.")
of "f": # fire
if playerBullets == 0:
writeLine(stdout, "You don't have any bullets left.")
else:
playerBullets -= 1
if missedShot():
case playerBullets:
of 2:
writeLine(stdout, "Grazed Black Bart in the right arm.")
of 1:
writeLine(stdout, "He's hit in the left shoulder, forcing him to use his right")
writeLine(stdout, "hand to shoot with.")
else:
discard
writeLine(stdout, "What a lousy shot.")
if playerBullets == 0:
writeLine(stdout, "Nice going, ace, you've run out of bullets.")
if opponentBullets != 0:
writeLine(stdout, "Now Black Bart won't shoot until you touch noses.")
writeLine(stdout, "You better think of something fast (like run).")
else:
writeLine(stdout, "What a shot, you got Black Bart right between the eyes.")
pressAnyKey("\nPress any key to get your reward. ")
clearScreen()
getReward()
break showdown
of "j": # jump
if wateringTroughs == maxWateringTroughs:
writeLine(stdout, "How many watering troughs do you think are on this street?")
strategy = ""
else:
wateringTroughs += 1
writeLine(stdout, "You jump behind the watering trough.")
writeLine(stdout, "Not a bad maneuver to threw Black Bart's strategy off.")
of "g": # give up
writeLine(stdout, "Black Bart accepts. The conditions are that he won't shoot you")
writeLine(stdout, "if you take the first stage out of town and never come back.")
if yes("Agreed? "):
writeLine(stdout, "A very wise decision.")
break showdown
else:
writeLine(stdout, "Oh well, back to the showdown.")
of "t": # turn tail and run
# The more bullets of the opponent, the less chances to escape.
if rand(opponentBullets + 2 - 1) == 0:
writeLine(stdout, "Man, you ran so fast even dogs couldn't catch you.")
else:
case opponentBullets:
of 0:
writeLine(stdout, "You were lucky, Black Bart can only throw his gun at you, he")
writeLine(stdout, "doesn't have any bullets left. You should really be dead.")
of 1:
writeLine(stdout, "Black Bart fires his last bullet…")
writeLine(stdout, "He got you right in the back. That's what you deserve, for running.")
of 2:
writeLine(stdout, "Black Bart fires and got you twice: in your back")
writeLine(stdout, "and your ass. Now you can't even rest in peace.")
of 3:
writeLine(stdout, "Black Bart unloads his gun, once in your back")
writeLine(stdout, "and twice in your ass. Now you can't even rest in peace.")
of 4:
writeLine(stdout, "Black Bart unloads his gun, once in your back")
writeLine(stdout, "and three times in your ass. Now you can't even rest in peace.")
else:
discard
opponentBullets = 0
break showdown
else:
writeLine(stdout, "You sure aren't going to live very long if you can't even follow directions.")
# end of strategy case
if theOpponentKillsOrRuns():
break
if playerBullets + opponentBullets == 0:
writeLine(stdout, "The showdown must end, because nobody has bullets left.")
break
writeLine(stdout, "")
# end of showdown block
# Main {{{1
# =============================================================
randomize()
clearScreen()
printCredits()
pressAnyKey("\nPress any key to read the instructions. ")
clearScreen()
printInstructions()
pressAnyKey("\nPress any key to start. ")
clearScreen()
play()
Math
#[
Math
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in Nim:
Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2023-08-26, 2025-01-20.
Last modified: 20250731T1954+0200.
]#
import std/math
import std/strutils
var n: float
while true:
try:
write(stdout, "Enter a number: ")
n = parseFloat(readLine(stdin))
break
except ValueError:
writeLine(stdout, "Real number expected.")
echo "ABS(", n, ") = abs(", n, ") = ", abs(n)
echo "ATN(", n, ") = arctan(", n, ") = ", arctan(n)
echo "COS(", n, ") = cos(", n, ") = ", cos(n)
echo "EXP(", n, ") = exp(", n, ") = ", exp(n)
echo "INT(", n, ") = int(", n, ") = ", int(n)
echo "LOG(", n, ") = log(", n, ", E) = ", log(n, E)
echo "SGN(", n, ") = sgn(", n, ") = ", sgn(n)
echo "SQR(", n, ") = sqrt(", n, ") = ", sqrt(n)
echo "TAN(", n, ") = tan(", n, ") = ", tan(n)
Mugwump
#[
Mugwump
Original version in BASIC:
Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).
Slightly modified by Bob Albrecht of People's Computer Company.
Published by Creative Computing (Morristown, New Jersey, USA), 1978.
- https://www.atariarchives.org/basicgames/showpage.php?page=114
- http://vintage-basic.net/games.html
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-20.
Last modified: 20250404T0159+0200.
]#
import std/math
import std/random
import std/strutils
import std/terminal
import std/unicode
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
const gridSize = 10
const turns = 10
const mugwumps = 4
type Mugwump = tuple[
x: int,
y: int,
hidden: bool,
]
var mugwump: array[mugwumps, Mugwump]
var found: int # counter
# Wait until the user enters a string and return it.
#
proc getString(prompt: string): string =
write(stdout, prompt)
result = readLine(stdin)
# Print the given prompt, wait until the user enters a valid integer and
# return it.
#
proc getNumber(prompt: string): int =
var n: int
while true:
try:
write(stdout, prompt)
n = parseInt(readLine(stdin))
break
except ValueError:
discard
result = n
# Return `true` if the given string is "yes" or a synonym.
#
proc isYes(s: string): bool =
result = toLower(s) in ["ok", "y", "yeah", "yes"]
# Return `true` if the given string is "no" or a synonym.
#
proc isNo(s: string): bool =
result = 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 = getString(prompt)
if isYes(answer):
return true
if isNo(answer):
return false
# Return a plural ending (default: "s") if the given number is greater than 1;
# otherwise return a singular ending (default: an empty string).
#
proc plural(n: int, plural = "s", singular = ""): string =
result = if n > 1: plural else: singular
# Clear the screen, print the credits and ask the user to press enter.
#
proc printCredits() =
clearScreen()
writeLine(stdout, "Mugwump\n")
writeLine(stdout, "Original version in BASIC:")
writeLine(stdout, " Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).")
writeLine(stdout, " Slightly modified by Bob Albrecht of People's Computer Company.")
writeLine(stdout, " Published by Creative Computing (Morristown, New Jersey, USA), 1978.")
writeLine(stdout, " - https://www.atariarchives.org/basicgames/showpage.php?page=114")
writeLine(stdout, " - http://vintage-basic.net/games.html\n")
writeLine(stdout, "This version in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair")
write(stdout, "\nPress Enter to read the instructions. ")
discard readLine(stdin)
# Clear the screen, print the instructions and ask the user to press enter.
#
proc printInstructions() =
clearScreen()
writeLine(stdout, "Mugwump\n")
writeLine(stdout, "The object of this game is to find four mugwumps")
writeLine(stdout, "hidden on a 10 by 10 grid. Homebase is position 0,0.")
writeLine(stdout, "Any guess you make must be two numbers with each")
writeLine(stdout, "number between 0 and 9, inclusive. First number")
writeLine(stdout, "is distance to right of homebase and second number")
writeLine(stdout, "is distance above homebase.\n")
var tries: string = plural(turns, "tries", "try")
writeLine(stdout, "You get ", turns, " ", tries, ". After each try, you will see")
writeLine(stdout, "how far you are from each mugwump.")
write(stdout, "\nPress Enter to start. ")
discard readLine(stdin)
# Init the mugwumps' positions, `hidden` flags and count.
#
proc hideMugwumps() =
for m in 0 ..< mugwumps:
mugwump[m].x = rand(gridSize - 1)
mugwump[m].y = rand(gridSize - 1)
mugwump[m].hidden = true
found = 0 # counter
# Print the given prompt, wait until the user enters a valid coord and return
# it.
#
proc getCoord(prompt: string): int =
while true:
result = getNumber(prompt)
if result < 0 or result >= gridSize:
writeLine(stdout, "Invalid value ", result, ": not in range [0, ", gridSize - 1, "].")
else:
break
# Return `true` if the given mugwump is hidden in the given coords.
#
proc isHere(m: int, x: int, y: int): bool =
result = mugwump[m].hidden and mugwump[m].x == x and mugwump[m].y == y
# Return the distance between the given mugwump and the given coords.
#
proc distance(m: int, x: int, y: int): int =
result = int(sqrt(
pow(float(mugwump[m].x - x), 2) +
pow(float(mugwump[m].y - y), 2)))
# Run the game.
#
proc play() =
var x: int
var y: int
var turn: int # counter
while true: # game
clearScreen()
hideMugwumps()
block playing:
for turn in 1 ..< turns:
writeLine(stdout, "Turn number ", turn, "\n")
writeLine(stdout, "What is your guess (in range [0, ", gridSize - 1, "])?")
x = getCoord("Distance right of homebase (x-axis): ")
y = getCoord("Distance above homebase (y-axis): ")
writeLine(stdout, "\nYour guess is (", x, ", ", y, ").")
for m in 0 ..< mugwumps:
if isHere(m, x, y):
mugwump[m].hidden = false
found += 1
writeLine(stdout, "You have found mugwump ", m, "!")
if found == mugwumps:
break playing
for m in 0 ..< mugwumps:
if mugwump[m].hidden:
writeLine(stdout, "You are ", distance(m, x, y), " units from mugwump ", m, ".")
writeLine(stdout, "")
if found == mugwumps:
writeLine(stdout, "\nYou got them all in ", turn, " turn", plural(turn), "!")
writeLine(stdout, "\nThat was fun! let's play again…")
writeLine(stdout, "Four more mugwumps are now in hiding.")
else:
var tries: string = plural(turns, "tries", "try")
writeLine(stdout, "\nSorry, that's ", turns, " ", tries, ".")
writeLine(stdout, "\nHere is where they're hiding:")
for m in 0 ..< mugwumps:
if mugwump[m].hidden:
writeLine(stdout, "Mugwump ", m, " is at (", mugwump[m].x, ", ", mugwump[m].y, ").")
writeLine(stdout, "")
if not yes("Do you want to play again? "):
break
printCredits()
printInstructions()
play()
Name
#[
Name
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2025-01-16.
Last modified: 20250404T0158+0200.
]#
import std/strutils
proc main() =
write(stdout, "What is your name? ")
let name = readLine(stdin)
var number: int
while true:
write(stdout, "Enter a number: ")
try:
number = parseInt(readLine(stdin))
if number >= 0:
break
writeLine(stdout, "Positive integer expected.")
except:
writeLine(stdout, "Integer expected.")
for _ in 1 .. number:
writeLine(stdout, "Hello, ", name)
main()
Poetry
#[
Poetry
Original version in BASIC:
Unknown author.
Modified and reworked by Jim Bailey, Peggy Ewing, and Dave Ahl at DEC.
Published in "BASIC Computer Games", Creative Computing (Morristown, New Jersey, USA), 1978.
https://archive.org/details/Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
https://github.com/chaosotter/basic-games/tree/master/games/BASIC%20Computer%20Games/Poetry
http://vintage-basic.net/games.html
This remake in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-16.
Last modified: 20250121T0253+0100.
]#
import os
import std/random
import std/terminal
const inputInk = fgGreen
const titleInk = fgRed
proc cursorHome() =
setCursorPos 0, 0
proc clearScreen() =
eraseScreen()
cursorHome()
proc pressAnyKey() =
setForegroundColor(inputInk)
write(stdout, "\nPress any Enter key to start. ")
setForegroundColor(fgDefault)
discard getch()
proc printCredits() =
setForegroundColor(titleInk)
writeLine(stdout, "Poetry")
setForegroundColor(fgDefault)
writeLine(stdout, "\nOriginal version in BASIC:")
writeLine(stdout, " Unknown author.")
writeLine(stdout, " Published in \"BASIC Computer Games\",")
writeLine(stdout, " Creative Computing (Morristown, New Jersey, USA), 1978.\n")
writeLine(stdout, "This improved remake in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair")
proc isEven(x: int): bool =
return x mod 2 == 0
const MAXPHRASESANDVERSES = 20
# counters:
var action = 0
var phrase = 0
var phrasesAndVerses = 0
var verseChunks = 0
proc verse() =
var manageTheVerseContinuation = true
var maybeAddComma = true
case action
of 0, 1:
case phrase:
of 0: write(stdout, "MIDNIGHT DREARY")
of 1: write(stdout, "FIERY EYES")
of 2: write(stdout, "BIRD OR FIEND")
of 3: write(stdout, "THING OF EVIL")
of 4: write(stdout, "PROPHET")
else: discard
of 2:
case phrase:
of 0:
write(stdout, "BEGUILING ME")
verseChunks = 2
of 1:
write(stdout, "THRILLED ME")
of 2:
write(stdout, "STILL SITTING…")
maybeAddComma = false
of 3:
write(stdout, "NEVER FLITTING")
verseChunks = 2
of 4:
write(stdout, "BURNED")
else:
discard
of 3:
case phrase:
of 0:
write(stdout, "AND MY SOUL")
of 1:
write(stdout, "DARKNESS THERE")
of 2:
write(stdout, "SHALL BE LIFTED")
of 3:
write(stdout, "QUOTH THE RAVEN")
of 4:
if verseChunks != 0:
write(stdout, "SIGN OF PARTING")
else:
discard
of 4:
case phrase:
of 0: write(stdout, "NOTHING MORE")
of 1: write(stdout, "YET AGAIN")
of 2: write(stdout, "SLOWLY CREEPING")
of 3: write(stdout, "…EVERMORE")
of 4: write(stdout, "NEVERMORE")
else: discard
of 5:
action = 0
writeLine(stdout, "")
if phrasesAndVerses > MAXPHRASESANDVERSES:
writeLine(stdout, "")
verseChunks = 0
phrasesAndVerses = 0
action = 2
return
else:
manageTheVerseContinuation = false
else: discard
if manageTheVerseContinuation:
sleep(250) # ms
if maybeAddComma and not (verseChunks == 0 or rand(1.0) > 0.19):
write(stdout, ",")
verseChunks = 2
if rand(1.0) > 0.65:
writeLine(stdout, "")
verseChunks = 0
else:
write(stdout, " ")
verseChunks += 1
action += 1
phrase = rand(5)
phrasesAndVerses += 1
if not (verseChunks > 0 or isEven(action)):
write(stdout, " ")
proc play() =
while true:
verse()
clearScreen()
printCredits()
pressAnyKey()
clearScreen()
play()
Russian Roulette
# Russian Roulette
#
# Original version in BASIC:
# Creative Computing (Morristown, New Jersey, USA), ca. 1980.
#
# This version in Nim:
# Copyright (c) 2023, Marcos Cruz (programandala.net)
# SPDX-License-Identifier: Fair
#
# Written in 2023-09-14/17.
#
# Last modified: 20250405T0304+0200.
import std/random
import std/terminal
proc cursorHome() =
setCursorPos 0, 0
proc clearScreen() =
eraseScreen()
cursorHome()
# Prompt the user to enter a command and return it.
proc command(prompt: string = "> "): char =
write stdout, prompt
return getch()
proc pressAnyKeyToStart() =
write stdout, "Press any key to start. "
discard getch()
proc printCredits =
echo "Russian Roulette\n"
echo "Original version in BASIC:"
echo " Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n"
echo "This version in Nim:"
echo " Copyright (c) 2023, Marcos Cruz (programandala.net)"
echo " SPDX-License-Identifier: Fair\n"
pressAnyKeyToStart()
proc printInstructions =
echo "Here is a revolver."
echo "Press 'f' to spin chamber and pull trigger."
echo "Press 'g' to give up, and play again."
echo "Press 'q' to quit.\n"
proc play: bool =
random.randomize()
while true: # game loop
clearScreen()
printInstructions()
var times = 0
while true: # play loop
case command():
of 'f': # fire
if random.rand(100) > 83:
echo "Bang! You're dead!"
echo "Condolences will be sent to your relatives."
break # next game
else:
times += 1
if times > 10:
echo "You win!"
echo "Let someone else blow his brains out."
break # next game
else:
echo "Click."
of 'g': # give up
echo "Chicken!"
break # next game
of 'q': # quit
return false
else:
echo "Invalid key."
printInstructions()
continue
pressAnyKeyToStart()
clearScreen()
printCredits()
discard play()
echo "Bye!"
Seance
#[
Seance
Original version in BASIC:
By Chris Oxlade, 1983.
https://archive.org/details/seance.qb64
https://github.com/chaosotter/basic-games
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-21.
Last modified: 20250121T1531+0100.
]#
import os
import std/math
import std/random
import std/strutils
import std/terminal
import std/unicode
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
const maxScore = 50
const maxMessageLength = 6
const minMessageLength = 3
const baseCharacter = int('@')
const planchette = '*'
const firstLetterNumber = 1
const lastLetterNumber = 26
type Ink = tuple [
foreground: ForegroundColor,
bright: bool,
]
const boardInk: Ink = (fgCyan, true)
const defaultInk: Ink = (fgWhite, false)
const inputInk: Ink = (fgGreen, true)
const instructionsInk: Ink = (fgYellow, false)
const mistakeEffectInk: Ink = (fgRed, false)
const planchetteInk: Ink = (fgYellow, false)
const titleInk: Ink = (fgRed, false)
const mistakeEffectPause = 3 # seconds
const boardX = 29 # screen column
const boardY = 5 # screen line
const boardWidth = 8 # characters displayed on the top and bottom borders
const boardHeight = 5 # characters displayed on the left and right borders
const boardPad = 1 # blank characters separating the board from its left and right borders
const boardBottomY = boardHeight + 1 # relative to the board
const boardActualWidth = boardWidth + 2 * boardPad # screen columns
const inputX = boardX
const inputY = boardY + boardBottomY + 4
const messagesY = inputY
# Print the given prompt and wait until the user enters a string.
#
proc input(prompt: string = ""): string =
write(stdout, ansiForegroundColorCode(inputInk.foreground, inputInk.bright))
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
write(stdout, prompt)
result = readLine(stdin)
# Print the given prompt and wait until the user presses Enter.
#
proc pressEnter(prompt: string) =
discard input(prompt)
# Return the x coordinate to print the given text centered on the board.
#
proc boardCenteredX(text: string): int =
result = boardX + int((boardActualWidth - len(text)) / 2)
# Print the given text on the given row, centered on the board.
#
proc printBoardCentered(text: string, y: int) =
setCursorPos(boardCenteredX(text), y)
writeLine(stdout, text)
const title = "Seance"
# Print the title at the current cursor position.
#
proc printTitle() =
write(stdout, ansiForegroundColorCode(titleInk.foreground, titleInk.bright))
writeLine(stdout, title)
write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
# Print the title on the given row, centered on the board.
#
proc printBoardCenteredTitle(y: int) =
write(stdout, ansiForegroundColorCode(titleInk.foreground, titleInk.bright))
printBoardCentered(title, y)
write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
proc printCredits() =
printTitle()
writeLine(stdout, "\nOriginal version in BASIC:")
writeLine(stdout, " Written by Chris Oxlade, 1983.")
writeLine(stdout, " https://archive.org/details/seance.qb64")
writeLine(stdout, " https://github.com/chaosotter/basic-games")
writeLine(stdout, "This version in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair")
proc printInstructions() =
printTitle()
write(stdout, ansiForegroundColorCode(instructionsInk.foreground, instructionsInk.bright))
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
writeLine(stdout, "\nMessages from the Spirits are coming through, letter by letter. They want you")
writeLine(stdout, "to remember the letters and type them into the computer in the correct order.")
writeLine(stdout, "If you make mistakes, they will be angry -- very angry...")
writeLine(stdout, "")
writeLine(stdout, "Watch for stars on your screen -- they show the letters in the Spirits'")
writeLine(stdout, "messages.")
# Print the given letter at the given board coordinates.
#
proc printCharacter(y, x: int, a: char) =
setCursorPos(x + boardX, y + boardY)
writeLine(stdout, a)
proc printBoard() =
write(stdout, ansiForegroundColorCode(boardInk.foreground, boardInk.bright))
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
for i in 1 .. boardWidth:
printCharacter(0, i + 1, char(baseCharacter + i)) # top border
printCharacter(boardBottomY, i + 1, char(baseCharacter + lastLetterNumber - boardHeight - i + 1)) # bottom border
for i in 1 .. boardHeight:
printCharacter(i , 0, char(baseCharacter + lastLetterNumber - i + 1)) # left border
printCharacter(i , 3 + boardWidth, char(baseCharacter + boardWidth + i)) # right border
writeLine(stdout, "")
proc eraseLineRight() =
write(stdout, "\e[K")
# Erase the given line to the right of the given column.
#
proc eraseLineRightFrom(line, column: int) =
setCursorPos(column, line)
eraseLineRight()
# Wait the given number of seconds.
#
proc waitSeconds(seconds: int) =
sleep(seconds * 1000)
# Print the given mistake effect, wait a configured number of seconds and
# finally erase it.
#
proc printMistakeEffect(effect: string) =
var x = boardCenteredX(effect)
hideCursor()
setCursorPos(x, messagesY)
write(stdout, ansiForegroundColorCode(mistakeEffectInk.foreground, mistakeEffectInk.bright))
writeLine(stdout, effect)
defer: write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
waitSeconds(mistakeEffectPause)
eraseLineRightFrom(messagesY, x)
showCursor()
# Return a new message of the given length, after marking its letters on the
# board.
#
proc message(length: int): string =
var y: int
var x: int
var message = ""
hideCursor()
for _ in 1 .. length:
var letterNumber = rand(firstLetterNumber .. lastLetterNumber)
var letter = char(baseCharacter + letterNumber)
message = message & letter
if letterNumber <= boardWidth:
# top border
y = 1
x = letterNumber + 1
elif letterNumber <= boardWidth + boardHeight:
# right border
y = letterNumber - boardWidth
x = 2 + boardWidth
elif letterNumber <= boardWidth + boardHeight + boardWidth:
# bottom border
y = boardBottomY - 1
x = 2 + boardWidth + boardHeight + boardWidth - letterNumber
else:
# left border
y = 1 + lastLetterNumber - letterNumber
x = 1
write(stdout, ansiForegroundColorCode(planchetteInk.foreground, planchetteInk.bright))
printCharacter(y, x, planchette)
write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
waitSeconds(1)
printCharacter(y, x, ' ')
showCursor()
result = message
# Accept a string from the user, erase it from the screen and return it.
#
proc messageUnderstood(): string =
setCursorPos(inputX, inputY)
defer: eraseLineRightFrom(inputY, inputX)
result = toUpper(input("? "))
proc play() =
var score = 0
var mistakes = 0
printBoardCenteredTitle(1)
printBoard()
while true:
var messageLength = rand(minMessageLength .. maxMessageLength)
if message(messageLength) != messageUnderstood():
mistakes += 1
case mistakes:
of 1:
printMistakeEffect("The table begins to shake!")
of 2:
printMistakeEffect("The light bulb shatters!")
of 3:
printMistakeEffect("Oh, no! A pair of clammy hands grasps your neck!")
return
else:
discard
else:
score += messageLength
if score >= maxScore:
printBoardCentered("Whew! The spirits have gone!", messagesY)
printBoardCentered("You live to face another day!", messagesY + 1)
return
randomize()
write(stdout, ansiForegroundColorCode(defaultInk.foreground, defaultInk.bright))
clearScreen()
printCredits()
pressEnter("\nPress the Enter key to read the instructions. ")
clearScreen()
printInstructions()
pressEnter("\nPress the Enter key to start. ")
clearScreen()
play()
writeLine(stdout, "\n")
Sine Wave
#[
Sine Wave
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in Nim:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2023-01, 2023-08, 2023-09.
Last modified: 20250405T0304+0200.
]#
import std/math
import std/strutils
import std/terminal
proc cursorHome() =
setCursorPos 0, 0
proc clearScreen() =
eraseScreen()
cursorHome()
proc showCredits() =
echo "Sine Wave\n"
echo "Original version in BASIC:"
echo " Creative computing (Morristown, New Jersey, USA), ca. 1980.\n"
echo "This version in Nim:"
echo " Copyright (c) 2023, Marcos Cruz (programandala.net)"
echo " SPDX-License-Identifier: Fair\n"
echo "Press any key to start the program."
discard getch()
var word: array[2, string] = ["", ""]
proc getWords() =
for i in 0 .. 1:
while word[i] == "":
write(stdout, "Enter the ", if (i == 0 ): "first" else: "second", " word: ")
word[i] = strip(readLine(stdin))
proc draw() =
var even = false
var angle = 0.0
while angle <= 40.0:
write(stdout, repeat(' ', int(26 + 25 * sin(angle))))
echo word[ord(even)]
even = not even
angle += 0.25
clearScreen()
showCredits()
clearScreen()
getWords()
clearScreen()
draw()
Slots
#[
Slots
A slot machine simulation.
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA).
Produced by Fred Mirabelle and Bob Harper on 1973-01-29.
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-21.
Last modified: 20250404T0159+0200.
]#
import std/monotimes
import std/random
import std/sequtils
import std/strutils
import std/terminal
import std/times
import std/unicode
const reels = 3
const images = 6
const image: array[images, string] = [" BAR ", " BELL ", "ORANGE", "LEMON ", " PLUM ", "CHERRY"]
const bar = 0 # position of "BAR" in `image`.
const colors = images
type ImageColor = tuple [
foreground: ForegroundColor,
bright: bool,
]
var color: array[colors, ImageColor] = [
(fgWhite, false),
(fgCyan, false),
(fgYellow, false),
(fgYellow, true),
(fgWhite, true),
(fgRed, true),
]
const maxBet = 100
const minBet = 1
proc cursorHome() =
setCursorPos(0, 0)
proc clearScreen() =
eraseScreen()
cursorHome()
proc pressEnter(prompt: string) =
write(stdout, prompt)
discard readLine(stdin)
proc printCredits() =
clearScreen()
writeLine(stdout, "Slots")
writeLine(stdout, "A slot machine simulation.\n")
writeLine(stdout, "Original version in BASIC:")
writeLine(stdout, " Creative computing (Morristown, New Jersey, USA).")
writeLine(stdout, " Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n")
writeLine(stdout, "This version in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair\n")
pressEnter("Press Enter for instructions.")
proc printInstructions() =
clearScreen()
writeLine(stdout, "You are in the H&M casino, in front of one of our")
write(stdout, "one-arm bandits. Bet from ", minBet, " to ", maxBet, " USD (or 0 to quit).\n\n")
pressEnter("Press Enter to start.")
proc won(prize: int, bet: int): int =
case prize:
of 2: writeLine(stdout, "DOUBLE!")
of 5: writeLine(stdout, "*DOUBLE BAR*")
of 10: writeLine(stdout, "**TOP DOLLAR**")
of 100: writeLine(stdout, "***JACKPOT***")
else: discard
writeLine(stdout, "You won!")
result = (prize + 1) * bet
proc showStandings(usd: int) =
writeLine(stdout, "Your standings are ", usd, " USD.")
proc printReels(reel: array[reels, int]) =
cursorHome()
for r in reel:
write(stdout, ansiForegroundColorCode(color[r].foreground, color[r].bright))
write(stdout, "[", image[r], "] ")
resetAttributes(stdout)
writeLine(stdout, "")
proc initReels(reel: var array[reels, int]) =
for i in 0 ..< reels:
reel[i] = rand(0 ..< images)
proc spinReels(reel: var array[reels, int]) =
const milliseconds = 1500
hideCursor()
var start = getMonoTime()
while inMilliseconds(getMonoTime() - start) < milliseconds:
initReels(reel)
printReels(reel)
showCursor()
# Return the number of equals and bars in the given `reel` array.
#
proc prize(reel: array[reels, int]): (int, int) =
var equals = reels - len(deduplicate(reel, isSorted = false))
equals += int(equals > 0)
var bars = count(reel, bar)
result = (equals, bars)
proc readIntOrZero(prompt: string): int =
while true:
try:
write(stdout, prompt)
result = parseInt(readLine(stdin))
break
except ValueError:
result = 0
proc play() =
var standings = 0
var bet = 0
var reel: array[reels, int] = [1, 2, 3]
var equals = 0
var bars = 0
initReels(reel)
block playing:
while true: # play loop
while true: # bet loop
clearScreen()
printReels(reel)
bet = readIntOrZero("Your bet (or 0 to quit): ")
if bet > maxBet:
writeLine(stdout, "House limits are ", maxBet, " USD.")
pressEnter("Press Enter to try again.")
elif bet < minBet:
writeLine(stdout, "Type \"q\" to confirm you want to quit.")
if toLower(readLine(stdin)) == "q":
break playing
else:
break # bet loop
# end of bet check
clearScreen()
spinReels(reel)
(equals, bars) = prize(reel)
case equals:
of 3:
if bars == 3:
standings += won(100, bet)
else:
standings += won(10, bet)
of 2:
if bars == 2:
standings += won(5, bet)
else:
standings += won(2, bet)
else:
writeLine(stdout, "You lost.")
standings -= bet
showStandings(standings)
pressEnter("Press Enter to continue.")
# end of bet loop
# end of play loop
# end of play block
showStandings(standings)
if standings < 0:
writeLine(stdout, "Pay up! Please leave your money on the terminal.")
elif standings == 0:
writeLine(stdout, "Hey, you broke even.")
else:
writeLine(stdout, "Collect your winnings from the H&M cashier.")
printCredits()
printInstructions()
play()
Stars
#[
Stars
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-16.
Last modified: 20250404T0159+0200.
]#
import std/strutils
import std/unicode
proc main() =
write(stdout, "What is your name? ")
let name = readLine(stdin)
writeLine(stdout, "Hello, ", name, ".")
while true:
var number: int
while true:
write(stdout, "How many stars do you want? ")
try:
number = parseInt(readLine(stdin))
if number >= 0:
break
writeLine(stdout, "Positive integer expected.")
except:
writeLine(stdout, "Integer expected.")
writeLine(stdout, repeat('*', number))
write(stdout, "Do you want more stars? ")
case toLower(readLine(stdin)):
of "ok", "yeah", "yes", "y":
discard
else:
break
main()
Strings
#[
Strings
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in Nim:
Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2023-08-27, 2025-01-20.
Last modified: 20250405T0304+0200.
]#
import std/strutils
write(stdout, "Enter a string: ")
let s = readLine(stdin)
var n: int
while true:
try:
write(stdout, "Enter a number: ")
n = parseInt(readLine(stdin))
break
except ValueError:
writeLine(stdout, "Integer expected.")
echo ""
echo "ASC(\"", s, "\") --> int(char(", s, "[0])) --> ", int(char(s[0]))
echo "CHR$(", n, ") --> char(", n, ") --> '", char(n), "'"
echo "LEFT$(\"", s, "\", ", n, ") --> \"", s, "\"[0 ..< ", n, "] --> ", s[0 ..< n]
echo "MID$(\"", s, "\", ", n, ") --> \"", s, "\"[", n, "-1 .. ^1] --> ", s[n-1 .. ^1]
echo "MID$(\"", s, "\", ", n, ", 3) --> \"", s, "\"[", n, "-1 ..< ", n, "+3-1] --> ", s[n-1 ..< n+3-1]
echo "RIGHT$(\"", s, "\", ", n, ") --> \"", s, "\"[^", n, " .. ^1] --> ", s[^n .. ^1]
echo "LEN(\"", s, "\") --> \"", s, "\".len --> ",s.len
echo "VAL(\"", s, "\") --> (try: parseInt(\"", s, "\") except: 0) --> ", (try: parseInt(s) except: 0)
echo "STR$(", n, ") --> $", n, " --> \"", $n, "\""
echo "SPC(", n, ") --> strutils.repeat(' ', ", n, ") --> \"", strutils.repeat(' ',n), "\""
Xchange
#[
Xchange
Original version in BASIC:
Written by Thomas C. McIntire, 1979.
Published in "The A to Z Book of Computer Games", 1979.
https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up
https://github.com/chaosotter/basic-games
This improved remake in Nim:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2025-01-21/22.
Last modified: 20250405T0304+0200.
]#
import std/strformat
import std/random
import std/strutils
import std/terminal
import std/unicode
# 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")
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
# =============================================================
const boardStyle = ansiStyleCode(cyan + bright)
const defaultStyle = ansiStyleCode(default, default, normal)
const inputStyle = ansiStyleCode(green + bright)
const instructionsStyle = ansiStyleCode(yellow)
const titleStyle = ansiStyleCode(red)
const blank = "*"
const gridHeight = 3 # cell rows
const gridWidth = 3 # cell columns
const cells = gridWidth * gridHeight
var pristineGrid: array[cells, string]
const gridsRow = 3 # screen row where the grids are printed
const gridsColumn = 5 # screen column where the left grid is printed
const cellsGap = 2 # distance between the grid cells, in screen rows or columns
const gridsGap = 16 # screen columns between equivalent cells of the grids
const maxPlayers = 4
var grid: array[maxPlayers, array[cells, string]]
var isPlaying: array[maxPlayers, bool]
var players: int
const quitCommand = "X"
# User input {{{1
# =============================================================
# Print the given prompt and return the integer entered by the
# user; if the user input is not a valid integer, return a zero
# instead.
#
proc inputInt(prompt: string): int =
setStyle(inputStyle)
defer: setStyle(defaultStyle)
while true:
try:
write(stdout, prompt)
result = parseInt(readLine(stdin))
break
except ValueError:
result = 0
# Print the given prompt and wait until the user enters a string.
#
proc inputString(prompt: string = ""): string =
setStyle(inputStyle)
defer: setStyle(defaultStyle)
write(stdout, prompt)
result = readLine(stdin)
# 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:
write(stdout, prompt)
var answer = readLine(stdin)
if isYes(answer):
return true
if isNo(answer):
return false
# Title, instructions and credits {{{1
# =============================================================
# Print the title at the current cursor position.
#
proc printTitle() =
setStyle(titleStyle)
writeLine(stdout, "Xchange")
setStyle(defaultStyle)
# Print the credits at the current cursor position.
#
proc printCredits() =
printTitle()
writeLine(stdout, "\nOriginal version in BASIC:")
writeLine(stdout, " Written by Thomas C. McIntire, 1979.")
writeLine(stdout, " Published in \"The A to Z Book of Computer Games\", 1979.")
writeLine(stdout, " https://archive.org/details/The_A_to_Z_Book_of_Computer_Games/page/n269/mode/2up")
writeLine(stdout, " https://github.com/chaosotter/basic-games")
writeLine(stdout, "This improved remake in Nim:")
writeLine(stdout, " Copyright (c) 2025, Marcos Cruz (programandala.net)")
writeLine(stdout, " SPDX-License-Identifier: Fair")
# Print the instructions at the current cursor position.
#
proc printInstructions() =
printTitle()
setStyle(instructionsStyle)
defer: setStyle(defaultStyle)
writeLine(stdout, "\nOne or two may play. If two, you take turns. A grid looks like this:\n")
setStyle(boardStyle)
writeLine(stdout, " F G D")
writeLine(stdout, " A H ", blank)
writeLine(stdout, " E B C\n")
setStyle(instructionsStyle)
writeLine(stdout, "But it should look like this:\n")
setStyle(boardStyle)
writeLine(stdout, " A B C")
writeLine(stdout, " D E F")
writeLine(stdout, " G H ", blank, "\n")
setStyle(instructionsStyle)
writeLine(stdout, "You may exchange any one letter with the '", blank, "', but only one that's adjacent:")
writeLine(stdout, "above, below, left, or right. Not all puzzles are possible, and you may enter")
writeLine(stdout, "'", quitCommand, "' to give up.\n")
writeLine(stdout, "Here we go...")
# Grids {{{1
# =============================================================
# Print the given player's grid title.
#
proc printGridTitle(player: int) =
setCursorPos(gridsColumn + (player * gridsGap), gridsRow)
write(stdout, "Player ", player + 1)
# Return the cursor position of the given player's grid cell.
#
proc cellPosition(player, cell: int): (int, int) =
let gridRow = int(cell / gridHeight)
let gridColumn: int = cell mod gridWidth
let titleMargin = if players > 1: 2 else: 0
let row = gridsRow + titleMargin + gridRow
let column = gridsColumn + (gridColumn * cellsGap) + (player * gridsGap)
result = (column, row)
# Return the cursor position of the given player's grid prompt.
#
proc gridPromptPosition(player: int): (int, int) =
let gridRow = int(cells / gridHeight)
let gridColumn = cells mod gridWidth
let titleMargin = if players > 1: 2 else: 0
let row = gridsRow + titleMargin + gridRow + 1
let column = gridsColumn + (gridColumn * cellsGap) + (player * gridsGap)
result = (column, row)
# Print the given player's grid, in the given or default color.
#
proc printGrid(player: int, color = boardStyle) =
if players > 1:
printGridTitle(player)
setStyle(color)
defer: setStyle(defaultStyle)
for cell in 0 ..< cells:
var (column, row) = cellPosition(player, cell)
setCursorPos(column, row)
write(stdout, grid[player][cell])
# Print the current players' grids.
#
proc printGrids() =
for player in 0 ..< players:
if isPlaying[player]:
printGrid(player)
writeLine(stdout, "")
eraseDown()
# Scramble the grid of the given player.
#
proc scrambleGrid(player: int) =
for cell in 0 ..< cells:
var randomCell = rand(cells - 1)
var tempCell = grid[player][cell]
grid[player][cell] = grid[player][randomCell]
grid[player][randomCell] = tempCell
# Init the grids.
#
proc initGrids() =
grid[0] = pristineGrid
scrambleGrid(0)
for player in 0 + 1 ..< players:
grid[player] = grid[0]
# Messages {{{1
# =============================================================
# Return a message prefix for the given player.
#
proc playerPrefix(player: int): string =
result = if players > 1: fmt"Player {player + 1}: " else: ""
# Return the cursor position of the given player's messages, adding the given
# row increment, which defaults to zero.
#
proc messagePosition(player: int, rowInc = 0): (int, int) =
var (_, promptRow) = gridPromptPosition(player)
result = (0, promptRow + 2 + rowInc)
# Print the given message about the given player, adding the given row
# increment, which defaults to zero, to the default cursor coordinates.
#
proc printMessage(message: string, player: int, rowInc = 0) =
var (column, row) = messagePosition(player, rowInc)
setCursorPos(column, row)
write(stdout, playerPrefix(player), message)
eraseLineRight()
writeLine(stdout, "")
# Erase the last message about the given player.
#
proc eraseMessage(player: int) =
var (column, row) = messagePosition(player)
setCursorPos(column, row)
eraseLineRight()
# Game loop {{{1
# =============================================================
# Return a message with the players range.
#
proc playersRangeMessage(): string =
if maxPlayers == 2:
return "1 or 2"
else:
return fmt"from 1 to {maxPlayers}"
# Return the number of players, asking the user if needed.
#
proc numberOfPlayers(): int =
var players = 0
printTitle()
writeLine(stdout, "")
if maxPlayers == 1:
players = 1
else:
while players < 1 or players > maxPlayers:
players = inputInt(fmt"Number of players ({playersRangeMessage()}): ")
result = players
# Is the given cell the first one on a grid row?
#
proc isFirstCellOfAGridRow(cell: int): bool =
result = cell mod gridWidth == 0
# Is the given cell the last one on a grid row?
#
proc isLastCellOfAGridRow(cell: int): bool =
result = (cell + 1) mod gridWidth == 0
# Are the given cells adjacent?
#
proc areCellsAdjacent(cell_1, cell_2: int): bool =
if cell_2 == cell_1 + 1 and not isFirstCellOfAGridRow(cell_2):
return true
elif cell_2 == cell_1 + gridWidth:
return true
elif cell_2 == cell_1 - 1 and not isLastCellOfAGridRow(cell_2):
return true
elif cell_2 == cell_1 - gridWidth:
return true
result = false
# Is the given player's character cell a valid move, i.e. is it adjacent to
# the blank cell? If so, return the blank cell of the grid and `true`;
# otherwise return a fake cell and `false`.
#
proc isLegalMove(player, charCell: int): (int, bool) =
const nowhere = -1
for blankCell, cellContent in grid[player]:
if cellContent == blank:
if areCellsAdjacent(charCell, blankCell):
return (blankCell, true)
break
printMessage(&"Illegal move \"{grid[player][charCell]}\".", player)
result = (nowhere, false)
# Is the given player's command valid, i.e. a grid character? If so, return
# its position in the grid and `true`; otherwise print an error message and
# return a fake position and `false`.
#
proc isValidCommand(player: int, command: string): (int, bool) =
const nowhere = -1
if command != blank:
for position, cellContent in grid[player]:
if cellContent == command:
return (position, true)
printMessage(&"Invalid character \"{command}\".", player)
result = (nowhere, false)
# Forget the given player, who quitted.
#
proc forgetPlayer(player: int) =
isPlaying[player] = false
printGrid(player, defaultStyle)
# Play the turn of the given player.
#
proc playTurn(player: int) =
var blankPosition: int
var characterPosition: int
if isPlaying[player]:
while true:
var command: string
var ok = false
while not ok:
var (column, row) = gridPromptPosition(player)
setCursorPos(column, row)
eraseLineRight()
setCursorPos(column, row)
command = toUpper(strip(inputString("Move: ")))
if command == quitCommand:
forgetPlayer(player)
return
(characterPosition, ok) = isValidCommand(player, command)
(blankPosition, ok) = isLegalMove(player, characterPosition)
if ok:
break
eraseMessage(player)
grid[player][blankPosition] = grid[player][characterPosition]
grid[player][characterPosition] = blank
# Play the turns of all players.
#
proc playTurns() =
for player in 0 ..< players:
playTurn(player)
# Is someone playing?
#
proc isSomeonePlaying(): bool =
for player in 0 ..< players:
if isPlaying[player]:
return true
result = false
# Has someone won? If so, print a message for every winner and return `true`;
# otherwise just return `false`.
#
proc hasSomeoneWon(): bool =
var winners = 0
for player in 0 ..< players:
if isPlaying[player]:
if grid[player] == pristineGrid:
winners += 1
if winners > 0:
var too = if winners > 1: ", too" else: ""
printMessage(
fmt"You're the winner{too}!",
player,
rowInc = winners - 1)
result = winners > 0
# Init the game.
#
proc initGame() =
clearScreen()
players = numberOfPlayers()
for player in 0 ..< players:
isPlaying[player] = true
clearScreen()
printTitle()
initGrids()
printGrids()
# Play the game.
#
proc play() =
initGame()
while isSomeonePlaying():
playTurns()
printGrids()
if hasSomeoneWon():
break
# Main {{{1
# =============================================================
# Init the program, i.e. just once before the first game.
#
proc initOnce() =
randomize()
# Init the pristine grid.
const firstCharCode = int('A')
for cell in 0 ..< cells - 1:
pristineGrid[cell] = $char(firstCharCode + cell)
pristineGrid[cells - 1] = blank
# Return `true` if the player does not want to play another game; otherwise
# return `false`.
#
proc enough(): bool =
var (column, row) = gridPromptPosition(player = 0)
setCursorPos(column, row)
result = not yes("Another game? ")
initOnce()
clearScreen()
printCredits()
pressEnter("\nPress the Enter key to read the instructions. ")
clearScreen()
printInstructions()
pressEnter("\nPress the Enter key to start. ")
while true:
play()
if enough():
break
writeLine(stdout, "So long…")
