Basics of Nim

Priskribo de la ĉi-paĝa enhavo

Konverto de malnovaj BASIC-programoj al Nim por lerni la fundamentojn de ĉi-tiu lingvo.

Etikedoj:

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…")

Rilataj paĝoj

Basics off
Metaprojekto pri la projektoj «Basics of…».
Basics of 8th
Konverto de malnovaj BASIC-programoj al 8th por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Ada
Konverto de malnovaj BASIC-programoj al Ada por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Arturo
Konverto de malnovaj BASIC-programoj al Arturo por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of C#
Konverto de malnovaj BASIC-programoj al C# por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of C3
Konverto de malnovaj BASIC-programoj al C3 por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Chapel
Konverto de malnovaj BASIC-programoj al Chapel por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Clojure
Konverto de malnovaj BASIC-programoj al Clojure por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Crystal
Konverto de malnovaj BASIC-programoj al Crystal por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of D
Konverto de malnovaj BASIC-programoj al D por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Elixir
Konverto de malnovaj BASIC-programoj al Elixir por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of F#
Konverto de malnovaj BASIC-programoj al F# por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Factor
Konverto de malnovaj BASIC-programoj al Factor por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of FreeBASIC
Konverto de malnovaj BASIC-programoj al FreeBASIC por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Gleam
Konverto de malnovaj BASIC-programoj al Gleam por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Go
Konverto de malnovaj BASIC-programoj al Go por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Hare
Konverto de malnovaj BASIC-programoj al Hare por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Haxe
Konverto de malnovaj BASIC-programoj al Haxe por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Icon
Konverto de malnovaj BASIC-programoj al Icon por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Io
Konverto de malnovaj BASIC-programoj al Io por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Janet
Konverto de malnovaj BASIC-programoj al Janet por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Julia
Konverto de malnovaj BASIC-programoj al Julia por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Kotlin
Konverto de malnovaj BASIC-programoj al Kotlin por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Lobster
Konverto de malnovaj BASIC-programoj al Lobster por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Lua
Konverto de malnovaj BASIC-programoj al Lua por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nature
Konverto de malnovaj BASIC-programoj al Nature por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Neat
Konverto de malnovaj BASIC-programoj al Neat por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Neko
Konverto de malnovaj BASIC-programoj al Neko por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nelua
Konverto de malnovaj BASIC-programoj al Nelua por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Nit
Konverto de malnovaj BASIC-programoj al Nit por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Oberon-07
Konverto de malnovaj BASIC-programoj al Oberon-07 por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of OCaml
Konverto de malnovaj BASIC-programoj al OCaml por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Odin
Konverto de malnovaj BASIC-programoj al Odin por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Pike
Konverto de malnovaj BASIC-programoj al Pike por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Pony
Konverto de malnovaj BASIC-programoj al Pony por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Python
Konverto de malnovaj BASIC-programoj al Python por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Racket
Konverto de malnovaj BASIC-programoj al Racket por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Raku
Konverto de malnovaj BASIC-programoj al Raku por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Retro
Konverto de malnovaj BASIC-programoj al Retro por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Rexx
Konverto de malnovaj BASIC-programoj al Rexx por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Ring
Konverto de malnovaj BASIC-programoj al Ring por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Rust
Konverto de malnovaj BASIC-programoj al Rust por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Scala
Konverto de malnovaj BASIC-programoj al Scala por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Scheme
Konverto de malnovaj BASIC-programoj al Scheme por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Styx
Konverto de malnovaj BASIC-programoj al Styx por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Swift
Konverto de malnovaj BASIC-programoj al Swift por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of V
Konverto de malnovaj BASIC-programoj al V por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Vala
Konverto de malnovaj BASIC-programoj al Vala por lerni la fundamentojn de ĉi-tiu lingvo.
Basics of Zig
Konverto de malnovaj BASIC-programoj al Zig por lerni la fundamentojn de ĉi-tiu lingvo.

Eksteraj rilataj ligiloj