Basics of Kotlin

Descrition del contenete del págine

Conversion de old BASIC-programas a Kotlin por aprender lu elementari de ti-ci lingue.

Etiquettes:

3D Plot

/*
3D Plot

Original version in BASIC:
    Creative Computing (Morristown, New Jersey, USA), ca. 1980.

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-08, 2023-09, 2023-10.

Last modified: 20250419T2224+0200.
*/

fun home() {
    print("\u001B\u005B\u0048")
}

fun clear() {
    print("\u001B\u005B\u0032\u004A")
    home()
}

fun showCredits() {
    println("3D Plot\n")
    println("Original version in BASIC:")
    println("    Creative computing (Morristown, New Jersey, USA), ca. 1980.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    println("Press Enter to start the program.")
    readln()
}


fun a(z: Double): Double {
    return 30 * kotlin.math.exp(-z * z / 100)
}

fun draw() {

    val width = 56
    val space = ' '
    val dot = '*'
    var line = CharArray(width)

    var l: Int
    var y1: Int
    var z: Int

    var x = -30.0
    while (x <= 30) {
        line.fill(space)
        l = 0
        y1 = 5 * (kotlin.math.sqrt(900 - x * x) / 5).toInt()
        for (y in y1 downTo -y1 step 5) {
            z = (25 + a(kotlin.math.sqrt(x * x + (y * y)))-0.7 * y).toInt()
            if (z > l) {
                l = z
                line[z] = dot
            }
        }
        for (pos in 0 ..< width) {
            print(line[pos])
        }
        println()
        x += 1.5
    }
}

fun main() {
    clear()
    showCredits()
    clear()
    draw()
}

Bagels

/*
Bagels

Original version in BASIC:
    D. Resek, P. Rowe, 1978.
    Creative Computing (Morristown, New Jersey, USA), 1978.

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-10.

Last modified: 20250420T0003+0200.
*/


// Move the cursor to the home position.
fun home() {
    print("\u001B[H")
}

// Clear the screen and move the cursor to the home position.
fun clear() {
    print("\u001B[2J")
    home()
}

// Wait until the user presses the Enter key.
// Clear the screen, display the credits and wait for a keypress.
fun printCredits() {
    clear()
    println("Bagels")
    println("Number guessing game\n")
    println("Original source unknown but suspected to be:")
    println("    Lawrence Hall of Science, U.C. Berkely.\n")
    println("Original version in BASIC:")
    println("    D. Resek, P. Rowe, 1978.")
    println("    Creative computing (Morristown, New Jersey, USA), 1978.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    print("Press Enter to read the instructions. ")
    readln()
}

// Clear the screen, print the instructions and wait for a keypress.
fun printInstructions() {
    clear()
    println("Bagels")
    println("Number guessing game\n")
    println("I am thinking of a three-digit number that has no two digits the same.")
    println("Try to guess it and I will give you clues as follows:\n")
    println("   PICO   - one digit correct but in the wrong position")
    println("   FERMI  - one digit correct and in the right position")
    println("   BAGELS - no digits correct")
    print("\nPress Enter to start. ")
    readln()
}

const val DIGITS = 3

// Return three random digits.
fun random(): IntArray {
    var randomDigit = IntArray(DIGITS)
    for (i in 0..<DIGITS) {
        digitLoop@ while (true) {
            randomDigit[i] = (0..9).random()
            for (j in 0..<i) {
                if (i != j && randomDigit[i] == randomDigit[j]) { continue@digitLoop }
            }
            break
        }
    }
    return randomDigit
}

const val ASCII_0 = 48

// Print the given prompt and get a three-digit number from the user.
fun input(prompt: String): IntArray {
    var userDigit = IntArray(DIGITS)
    getLoop@ while (true) {
        print(prompt)
        var input = readln()
        if (input.length != DIGITS) {
            println("Remember it's a $DIGITS-digit number.")
            continue@getLoop
        }
        for ((pos, digit) in input.withIndex()) {
            if (digit.isDigit()) {
                userDigit[pos] = digit.code - ASCII_0
            } else {
                println("What?")
                continue@getLoop
            }
        }
        if (userDigit.distinct().count() != DIGITS) {
            println("Remember my number has no two digits the same.")
            continue@getLoop
        }
        break
    }
    return userDigit
}

// Return `true` if the given String is "yes" or a synonym.
fun isYes(answer: String): Boolean {
    when (answer.lowercase()) {
        "ok", "yeah", "yes", "y" -> return true
        else -> return false
    }
}

// Return `true` if the given String is "no" or a synonym.
fun isNo(answer: String): Boolean {
    when (answer.lowercase()) {
        "no", "nope", "n" -> return true
        else -> return false
    }
}

// Print the given prompt, wait until the user enters a valid yes/no
// String, and return `true` for "yes" or `false` for "no".
fun yes(prompt: String): Boolean {
    var answer = ""
    while (!(isYes(answer) || isNo(answer))) {
        print(prompt)
        answer = readln()
    }
    return isYes(answer)
}

const val TRIES = 20

// Init and run the game loop.
fun play() {
    var score = 0
    while (true) {
        clear()
        var computerNumber = random()
        var fermi = 0
        println("O.K. I have a number in mind.")
        for (guess in 1..TRIES) {
            var userNumber = input("Guess #${guess.toString().padStart(2, '0')}: ")
            fermi = 0
            var 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
                        }
                    }
                }
            }
            print("PICO ".repeat(pico))
            print("FERMI ".repeat(fermi))
            if (pico + fermi == 0) { print("BAGELS") }
            println()
            if (fermi == DIGITS) { break }
        }
        if (fermi == DIGITS) {
            println("You got it!!!")
            score += 1
        } else {
            println("Oh well.")
            print("That's $TRIES guesses.  My number was ")
            for (i in 0..<DIGITS) { print(computerNumber[i]) }
            println(".")
        }
        if (!yes("Play again? ")) { break }
    }
    if (score != 0) {
        println("A $score-point bagels, buff!!")
    }
    println("Hope you had fun.  Bye.")
}

fun main() {
    printCredits()
    printInstructions()
    play()
}

Bug

/*
Bug

Original version in BASIC:
    Brian Leibowitz, 1978.
    Creative Computing (Morristown, New Jersey, USA), 1978.

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2023-10-26.

Last modified: 20250518T2359+0200.
*/


class Bug (
    var body: Boolean,
    var neck: Boolean,
    var head: Boolean,
    var feelers: Int,
    val feelerType: Char,
    var tail: Boolean,
    var legs: Int,
)

class Player (
    val pronoun: String,
    val possessive: String,
    val bug: Bug,
)

val computer = Player("I", "My", Bug(false, false, false, 0, 'F', false, 0))

val human = Player("you", "Your", Bug(false, false, false, 0, 'A', false, 0))

enum class Part {Body, Neck, Head, Feeler, Tail, Leg}

// Bug body attributes.
const val BODY_HEIGHT = 2
const val FEELER_LENGTH = 4
const val LEG_LENGTH = 2
const val MAX_FEELERS = 2
const val MAX_LEGS = 6
const val NECK_LENGTH = 2

// Move the cursor to the home position.
fun home() {
    print("\u001B[H")
}

// Clear the screen and move the cursor to the home position.
fun clear() {
    print("\u001B[2J")
    home()
}

// Move the cursor up by the given number of rows (defaults to 1), without
// changing the column position.
fun cursorUp(rows: Short = 1) {
    print("\u001B[${rows}A")
}

// Erase the current line, without moving the cursor position.
fun eraseLine() {
    print("\u001B[2K")
}

// Move the cursor to the previous row, without changing the column position,
// and erase its line.
fun erasePreviousLine() {
    cursorUp()
    eraseLine()
}

// Print the given prompt and wait until the user presses the Enter key.
fun pressEnter(prompt: String) {
    print(prompt)
    readln()
}

// Clear the screen, display the credits and wait for a keypress.
fun printCredits() {
    clear()
    println("Bug\n")
    println("Original version in BASIC:")
    println("    Brian Leibowitz, 1978.")
    println("    Creative computing (Morristown, New Jersey, USA), 1978.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    pressEnter("Press Enter to read the instructions. ")
}

const val 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.
fun printPartsTable() {

    val COLUMNS = 3
    val COLUMN_WIDTH = 8
    val COLUMN_SEPARATION = 2

    // Headers
    val header = arrayOf<String>("Number", "Part", "Quantity")
    for (i in 0..<COLUMNS) {
        print(header[i].padEnd(COLUMN_WIDTH + COLUMN_SEPARATION))
    }
    println()

    // Rulers
    for (i in 0..<COLUMNS) {
        print("-".repeat(COLUMN_WIDTH))
        print(if (i == COLUMNS - 1) "" else " ".repeat(COLUMN_SEPARATION))
    }
    println()

    // Data
    val partQuantity = arrayOf(1, 1, 1, 2, 1, 6)
    for (part in enumValues<Part>()) {
        print((part.ordinal + 1).toString().padEnd(COLUMN_WIDTH + COLUMN_SEPARATION))
        print(part.toString().padEnd(COLUMN_WIDTH + COLUMN_SEPARATION))
        println(partQuantity[part.ordinal])
    }

}

// Clear the screen, print the instructions and wait for a keypress.
fun printInstructions() {
    clear()
    println("Bug")
    println(instructions)
    printPartsTable()
    pressEnter("\nPress Enter to start. ")
}

// Print a bug head.
fun printHead() {
    println("        HHHHHHH")
    println("        H     H")
    println("        H O O H")
    println("        H     H")
    println("        H  V  H")
    println("        HHHHHHH")
}

// Print the given bug.
fun printBug(bug: Bug) {
    if (bug.feelers > 0) {
        for (i in 0..<FEELER_LENGTH) {
            print("        ")
            for (j in 0..<bug.feelers) {
                print(" ${bug.feelerType}")
            }
            println()
        }
    }
    if (bug.head) {
        printHead()
    }
    if (bug.neck) {
        for (i in 0..<NECK_LENGTH) {
            println("          N N")
        }
    }
    if (bug.body) {
        println("     BBBBBBBBBBBB")
        for (i in 0..<BODY_HEIGHT) {
            println("     B          B")
        }
        if (bug.tail) {
            println("TTTTTB          B")
        }
        println("     BBBBBBBBBBBB")
    }
    if (bug.legs > 0) {
        for (i in 0..<LEG_LENGTH) {
            print("    ")
            for (j in 0..<bug.legs) {
                print(" L")
            }
            println()
        }
    }
}

// Return `true` if the given bug is finished; otherwise return `false`.
fun finished(bug: Bug): Boolean {
    return bug.feelers == MAX_FEELERS && bug.tail && bug.legs == MAX_LEGS
}

// Array to convert a number to its equilavent text.
val asText = arrayOf<String>(
    "no",
    "a",
    "two",
    "three",
    "four",
    "five",
    "six") // MAX_LEGS

// Return a string containing the given number and noun in their proper form.
fun plural(number: Int, noun: String): String {
    return "${asText[number]} $noun${if (number > 1) "s" else ""}"
}

// Add the given part to the given player's bug, if needed; return `true` if the
// bug was changed, otherwise return `false`.
fun addPart(part: Part, player: Player): Boolean {
    when (part) {
        Part.Body ->
            if (player.bug.body) {
                println(", but ${player.pronoun} already have a body.")
            } else {
                player.bug.body = true
                println("; ${player.pronoun} now have a body:")
                return true
            }
        Part.Neck ->
            if (player.bug.neck) {
                println(", but ${player.pronoun} you already have a neck.")
            } else if (!player.bug.body) {
                println(", but ${player.pronoun} need a body first.")
            } else {
                player.bug.neck = true
                println("; ${player.pronoun} now have a neck:")
                return true
            }
        Part.Head ->
            if (player.bug.head) {
                println(", but ${player.pronoun} already have a head.")
            } else if (!player.bug.neck) {
                println(", but ${player.pronoun} need a a neck first.")
            } else {
                player.bug.head = true
                println("; ${player.pronoun} now have a head:")
                return true
            }
        Part.Feeler ->
            if (player.bug.feelers == MAX_FEELERS) {
                println(", but ${player.pronoun} have two feelers already.")
            } else if (!player.bug.head) {
                println(", but ${player.pronoun} need a head first.")
            } else {
                player.bug.feelers += 1
                println("; ${player.pronoun} now have ${plural(player.bug.feelers, "feeler")}:")
                return true
            }
        Part.Tail ->
            if (player.bug.tail) {
                println(", but ${player.pronoun} already have a tail.")
            } else if (!player.bug.body) {
                println(", but ${player.pronoun} need a body first.")
            } else {
                player.bug.tail = true
                println("; ${player.pronoun} now have a tail:")
                return true
            }
        Part.Leg ->
            if (player.bug.legs == MAX_LEGS) {
                println(", but ${player.pronoun} have ${asText[MAX_LEGS]} feet already.")
            } else if (!player.bug.body) {
                println(", but ${player.pronoun} need a body first.")
            } else {
                player.bug.legs += 1
                println("; ${player.pronoun} now have ${plural(player.bug.legs, "leg")}:")
                return true
            }
    }
    return false
}

// Ask the user to press the Enter key, wait for the input, then erase the
// prompt text.
fun prompt() {
    pressEnter("Press Enter to roll the dice. ")
    erasePreviousLine()
}

// Play one turn for the given player, rolling the dice and updating his bug.
fun turn(player: Player) {
    prompt()
    val number: Int = (0..<enumValues<Part>().size).random()
    val part : Part = enumValues<Part>()[number]
    print(player.pronoun.lowercase().replaceFirstChar{it.uppercase()})
    print(" rolled a ${number + 1} (${part.toString().lowercase()})")
    if (addPart(part, player)) {
        println()
        printBug(player.bug)
    }
    println()
}

// Print a message about the winner.
fun printWinner() {
    if (finished(human.bug) && finished(computer.bug)) {
        println("Both of our bugs are finished in the same number of turns!")
    } else if (finished(human.bug)) {
        println("${human.possessive} bug is finished.")
    } else if (finished(computer.bug)) {
        println("${computer.possessive} bug is finished.")
    }
}

// Return `true` if either bug is finished, i.e. the game ending condition.
fun gameOver(): Boolean {
    return finished(human.bug) || finished(computer.bug)
}

// Execute the game loop.
fun play() {
    clear()
    while (!gameOver()) {
        turn(human)
        turn(computer)
    }
    printWinner()
}

fun main() {
    printCredits()
    printInstructions()
    play()
    println("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 Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-08, 2023-10.

Last modified: 20250518T2359+0200.
*/

// Move the cursor to the top left position of the terminal.
fun home() {
    print("\u001B\u005B\u0048")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B\u005B\u0032\u004A")
    home()
}

// Clear the screen, print the credits and wait for the Enter key.
fun printCredits() {
    println("Bunny\n")
    println("Original version in BASIC:")
    println("    Creative Computing (Morristown, New Jersey, USA), 1978.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    println("Press Enter to start the program.")
    readln()
}

const val WIDTH = 53
var line = IntArray(WIDTH) // buffer

// Clear the line buffer with spaces.
fun clearLine() {
    for (column in 0..<WIDTH) {
        line[column] = ' '.code
    }
}

val letter = arrayOf<Char>('B', 'U', 'N', 'N', 'Y')
var letters = letter.size

const val EOL: Int = -1 // end of line identifier
var data = arrayOf<Int>(
    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)

fun printLine() {
    for (i in line) {
        print(i.toChar())
    }
    println()
}

// Draw the graphic out of `data` and `letter`.
fun draw() {
    clearLine()
    var dataIndex = 0
    while (dataIndex < data.size) {
        val firstColumn = data[dataIndex++]
        if (firstColumn == EOL) {
            printLine()
            clearLine()
        } else {
            val lastColumn = data[dataIndex++]
            for (column in firstColumn..lastColumn) {
                line[column] = letter[column % letters].code
            }
        }
    }
}

fun main() {
    clearScreen()
    printCredits()
    clearScreen()
    draw()
}

Chase

/*
Chase

Original version in BASIC:
    Anonymous.
    Published in 1977 in "The Best of Creative Computing", Volume 2.
    https://www.atariarchives.org/bcc2/showpage.php?page=253

This version in Kotlin:
    Copyright (c) 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2025-05-18.

Last modified 20250519T0024+0200.
*/

import kotlin.random.Random

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BACKGROUND = +40
const val BRIGHT     = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the cursor position to the given coordinates (the top left position is 1, 1)
fun setCursorPosition(line: Int, column: Int) {
    print("\u001B[${line};${column}H")
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Erase from the current cursor position to the end of the current line.
fun eraseLineRight() {
    print("\u001B[K")
}

// Globals {{{1
// =============================================================

// Colors

const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val INSTRUCTIONS_INK = YELLOW + FOREGROUND
const val TITLE_INK = BRIGHT + RED + FOREGROUND

// Arena

const val ARENA_WIDTH = 20
const val ARENA_HEIGHT = 10
const val ARENA_LAST_X = ARENA_WIDTH - 1
const val ARENA_LAST_Y = ARENA_HEIGHT - 1
const val ARENA_ROW = 3

var arena = Array(ARENA_HEIGHT) { Array(ARENA_WIDTH) { "" } }

const val EMPTY = " "
const val FENCE = "X"
const val MACHINE = "m"
const val HUMAN = "@"

const val FENCES = 15 // inner obstacles, not the border

// The end

enum class End {
    Not_Yet,
    Quit,
    Electrified,
    Killed,
    Victory,
}

var theEnd: End = End.Not_Yet

// Machines

const val MACHINES = 5
const val MACHINES_DRAG = 2 // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.

class Machine (
    var x: Int = 0,
    var y: Int = 0,
    var operative: Boolean = true,
)

var machine: Array<Machine> = Array(MACHINES) { Machine() }

var destroyedMachines: Int = 0 // counter

// Human

var humanX: Int = 0
var humanY: Int = 0

// User input {{{1
// =============================================================

// Print the given prompt and wait until the user enters a string.
//
fun inputString(prompt: String = ""): String {
    setColor(INPUT_INK)
    print(prompt)
    val result = readln()
    setColor(DEFAULT_INK)
    return result
}

// Print the given prompt and wait until the user presses Enter.
//
fun pressEnter(prompt: String) {
    inputString(prompt)
}

// Return `true` if the given string is "yes" or a synonym.
//
fun isYes(s: String): Boolean {
    return s.lowercase() in arrayOf("ok", "y", "yeah", "yes")
}

// Return `true` if the given string is "no" or a synonym.
//
fun isNo(s: String): Boolean {
    return s.lowercase() in arrayOf("n", "no", "nope")
}

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fun yes(prompt: String): Boolean {
    while (true) {
        val answer = inputString(prompt)
        if (isYes(answer)) return true
        if (isNo(answer)) return false
    }
}

// Title, credits and instructions {{{1
// =============================================================

const val TITLE = "Chase"

// Print the title at the current cursor position.
//
fun printTitle() {
    setColor(TITLE_INK)
    println(TITLE)
    setColor(DEFAULT_INK)
}

fun printCredits() {
    printTitle()
    println("\nOriginal version in BASIC:")
    println("    Anonymous.")
    println("    Published in \"The Best of Creative Computing\", Volume 2, 1977.")
    println("    https://www.atariarchives.org/bcc2/showpage.php?page=253")
    println("This version in Kotlin:")
    println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair")
}

// Print the game instructions and wait for a key.
//
fun printInstructions() {
    printTitle()
    setColor(INSTRUCTIONS_INK)
    println("\nYou ($HUMAN) are in a high voltage maze with $MACHINES")
    println("security machines ($MACHINE) trying to kill you.")
    println("You must maneuver them into the maze ($FENCE) to survive.\n")
    println("Good luck!\n")
    println("The movement commands are the following:\n")
    println("    ↖  ↑  ↗")
    println("    NW N NE")
    println("  ←  W   E  →")
    println("    SW S SE")
    println("    ↙  ↓  ↘")
    println("\nPlus 'Q' to end the game.")
    setColor(DEFAULT_INK)
}

// Arena {{{1
// =============================================================

// Display the arena at the top left corner of the screen.
//
fun printArena() {
    setCursorPosition(ARENA_ROW, 1)
    for (y in 0 .. ARENA_LAST_Y) {
        for (x in 0 .. ARENA_LAST_X) {
            print(arena[y][x])
        }
        println()
    }
}

// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
fun isBorder(y: Int, x: Int): Boolean {
    return (y == 0) || (x == 0) || (y == ARENA_LAST_Y) || (x == ARENA_LAST_X)
}

// Place the given string at a random empty position of the arena and return
// the coordinates.
//
fun place(s: String): Pair<Int, Int> {
    var y: Int = 0
    var x: Int = 0
    while (true) {
        y = Random.nextInt(1, ARENA_LAST_Y)
        x = Random.nextInt(1, ARENA_LAST_X)
        if (arena[y][x] == EMPTY) break
    }
    arena[y][x] = s
    return Pair(y, x)
}

// Inhabit the arena with the machines, the inner fences and the human.
//
fun inhabitArena() {
    for (m in 0 .. MACHINES - 1) {
        var (y, x) = place(MACHINE)
        machine[m].y = y
        machine[m].x = x
        machine[m].operative = true
    }
    for (i in 0 .. FENCES - 1) {
        place(FENCE)
    }
    var (y, x) = place(HUMAN)
    humanY = y
    humanX = x
}

// Clean the arena, i.e. empty it and add a surrounding fence.
//
fun cleanArena() {
    for (y in 0 .. ARENA_LAST_Y) {
        for (x in 0 .. ARENA_LAST_X) {
            arena[y][x] = if (isBorder(y, x)) FENCE else EMPTY
        }
    }
}

// Game {{{1
// =============================================================

// Init the game.
//
fun initGame() {
    cleanArena()
    inhabitArena()
    destroyedMachines = 0
    theEnd = End.Not_Yet
}

// Move the given machine.
//
fun moveMachine(m: Int) {

    var maybe: Int = 0

    arena[machine[m].y][machine[m].x] = EMPTY

    maybe = Random.nextInt(2) // 0 .. 1
    if (machine[m].y > humanY) {
        machine[m].y -= maybe
    } else if (machine[m].y < humanY) {
        machine[m].y += maybe
    }

    maybe = Random.nextInt(2) // 0 .. 1
    if (machine[m].x > humanX) {
        machine[m].x -= maybe
    } else if (machine[m].x < humanX) {
        machine[m].x += maybe
    }

    if (arena[machine[m].y][machine[m].x] == EMPTY) {
        arena[machine[m].y][machine[m].x] = MACHINE
    } else if (arena[machine[m].y][machine[m].x] == FENCE) {
        machine[m].operative = false
        destroyedMachines += 1
        if (destroyedMachines == MACHINES) {
            theEnd = End.Victory
        }
    } else if (arena[machine[m].y][machine[m].x] == HUMAN) {
        theEnd = End.Killed
    }
}

// Maybe move the given operative machine.
//
fun maybeMoveMachine(m: Int) {
    if (Random.nextInt(0, MACHINES_DRAG) == 0) moveMachine(m)
}

// Move the operative machines.
//
fun moveMachines() {
    for (m in 0 .. MACHINES - 1) {
        if (machine[m].operative) {
            maybeMoveMachine(m)
        }
    }
}

// Read a user command; update `theEnd` accordingly and return the direction
// increments.
//
fun getMove(): Pair<Int, Int> {
    var yInc: Int = 0
    var xInc: Int = 0
    println()
    eraseLineRight()
    when (inputString("Command: ").lowercase()) {
        "q" ->
            theEnd = End.Quit
        "sw" -> {
            yInc = +1
            xInc = -1
        }
        "s" ->
            yInc = +1
        "se" -> {
            yInc = +1
            xInc = +1
        }
        "w" ->
            xInc = -1
        "e" ->
            xInc = +1
        "nw" -> {
            yInc = -1
            xInc = -1
        }
        "n" ->
            yInc = -1
        "ne" -> {
            yInc = -1
            xInc = +1
        }
    }
    return Pair(yInc, xInc)
}

fun play() {

    while (true) { // game loop

        clearScreen()
        printTitle()
        initGame()

        while (true) { // action loop
            printArena()
            var (yInc, xInc) = getMove()
            if (theEnd == End.Not_Yet) {
                if (yInc != 0 || xInc != 0) {
                    arena[humanY][humanX] = EMPTY
                    if (arena[humanY + yInc][humanX + xInc] == FENCE) {
                        theEnd = End.Electrified
                    } else if (arena[humanY + yInc][humanX + xInc] == MACHINE) {
                        theEnd = End.Killed
                    } else {
                        arena[humanY][humanX] = EMPTY
                        humanY = humanY + yInc
                        humanX = humanX + xInc
                        arena[humanY][humanX] = HUMAN
                        printArena()
                        moveMachines()
                    }
                }
            }
            if (theEnd != End.Not_Yet) break
        }// action loop

        when (theEnd) {
            End.Quit ->
                println("\nSorry to see you quit.")
            End.Electrified ->
                println("\nZap! You touched the fence!")
            End.Killed ->
                println("\nYou have been killed by a lucky machine.")
            End.Victory ->
                println("\nYou are lucky, you destroyed all machines.")
            else ->
                print("")
        }

        if (! yes("\nDo you want to play again? ") ) break

    } // game loop

    println("\nHope you don't feel fenced in.")
    println("Try again sometime.")
}

// Main {{{1
// =============================================================

fun main() {
    setColor(DEFAULT_INK)
    clearScreen()
    printCredits()
    pressEnter("\nPress the Enter key to read the instructions. ")
    clearScreen()
    printInstructions()
    pressEnter("\nPress the Enter key to start. ")
    play()
}

Diamond

/*
Diamond

Original version in BASIC:
    Example included in Vintage BASIC 1.0.3.
    http://www.vintage-basic.net

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-08, 2023-10.

Last modified: 20231104T0030+0100.
*/

fun main() {

    val lines = 17
    var i = 1
    var j: Int

    while (i <= lines / 2 + 1) {
        j = 1
        while (j <= (lines + 1) / 2 - i + 1) {
            print(" ")
            j += 1
        }
        j = 1
        while (j <= i * 2 - 1) {
            print("*")
            j += 1
        }
        println()
        i += 1
    }
    i = 1
    while (i <= lines / 2) {
        j = 1
        while (j <= i + 1) {
            print(" ")
            j += 1
        }
        j = 1
        while (j <= ((lines + 1) / 2 - i) * 2 - 1) {
            print("*")
            j += 1
        }
        println()
        i += 1
    }
}

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
    Written in 2023-09, 2023-10.

This conversion from Odin to Kotlin:
    Copyright (c) 2023, 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair
    Written in 2023-11-01/02, 2025-05-18.
    Last modified: 20260205T1346+0100.

*/

import kotlin.random.Random
import kotlin.system.exitProcess

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BRIGHT = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Global variables and constants {{{1
// =============================================================

const val ACRES_A_BUSHEL_CAN_SEED = 2 // yearly
const val ACRES_A_PERSON_CAN_SEED = 10 // yearly
const val ACRES_PER_PERSON = 10 // to calculate the initial acres of the city
const val BUSHELS_TO_FEED_A_PERSON = 20 // yearly
const val MAX_IRRITATION = 16
const val IRRITATION_LEVELS = 5 // after the switch in `showIrritation`
const val IRRITATION_STEP: Int = MAX_IRRITATION / IRRITATION_LEVELS
const val RANGE_OF_HARVESTED_BUSHELS_PER_ACRE = 10
const val MIN_HARVESTED_BUSHELS_PER_ACRE = 17
const val MAX_HARVESTED_BUSHELS_PER_ACRE = MIN_HARVESTED_BUSHELS_PER_ACRE + RANGE_OF_HARVESTED_BUSHELS_PER_ACRE - 1
const val PLAGUE_CHANCE = 0.15 // 15% yearly
const val YEARS = 10 // goverment period

const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val INSTRUCTIONS_INK = BRIGHT + YELLOW + FOREGROUND
const val RESULT_INK = BRIGHT + CYAN + FOREGROUND
const val SPEECH_INK = BRIGHT + MAGENTA + FOREGROUND
const val TITLE_INK = BRIGHT + WHITE + FOREGROUND
const val WARNING_INK = BRIGHT + RED + FOREGROUND

enum class Result {VeryGood, NotTooBad, Bad, VeryBad}

var acres: Int = 0
var bushelsEatenByRats: Int = 0
var bushelsHarvested: Int = 0
var bushelsHarvestedPerAcre: Int = 0
var bushelsInStore: Int = 0
var bushelsToFeedWith: Int = 0
var dead: Int = 0
var infants: Int = 0
var irritation: Int = 0 // counter (0..99)
var population: Int = 0
var starvedPeoplePercentage: Int = 0
var totalDead: Int = 0

// Instructions {{{1
// =============================================================

fun instructions(): String {

    return """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 $MIN_HARVESTED_BUSHELS_PER_ACRE and $MAX_HARVESTED_BUSHELS_PER_ACRE 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."""

}

fun printInstructions() {
    setColor(INSTRUCTIONS_INK)
    println(instructions())
    setColor(DEFAULT_INK)
}

// User input {{{1
// =============================================================

fun pause(prompt: String = "> ") {
    setColor(INPUT_INK)
    print(prompt)
    readln()
    setColor(DEFAULT_INK)
}

// Print the given prompt in the correspondent color, wait until the user
// enters a string and return it parsed as an `int`; return also an ok flag
// which is `false` if no appropriate value could be found, or if the input
// string contained more than just the number. Restore the default color.
fun input(prompt: String): Pair<Int, Boolean> {
    setColor(INPUT_INK)
    print(prompt)
    val input = readln()
    var number : Int = 0
    var ok : Boolean
    try {
        number = input.toInt()
        ok = true
    }
    catch (e: NumberFormatException) {
        ok = false
    }
    setColor(DEFAULT_INK)
    return Pair(number, ok)
}

// Credits {{{1
// =============================================================

const val 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 Kotlin:
  Copyright (c) 2023, Marcos Cruz (programandala.net)
  SPDX-License-Identifier: Fair"""

fun printCredits() {
    setColor(TITLE_INK)
    println(credits)
    setColor(DEFAULT_INK)
}

// Main (2) {{{1
// =============================================================

fun ordinalSuffix(n: Int): String {
    when (n) {
        1 -> return "st"
        2 -> return "nd"
        3 -> return "rd"
        else -> return "th"
    }
}

// Return the description of the given year as the previous one.
fun previous(year: Int): String {
    if (year == 0) {
        return "the previous year"
    } else {
        return "your $year${ordinalSuffix(year)} year"
    }
}

// Return the proper wording for `n` persons, using the given or default words
// for singular and plural forms.
fun persons(n: Int, singular: String = "person", plural: String = "people"): String {
    when (n) {
        0 -> return "nobody"
        1 -> return "one $singular"
        else -> return "$n $plural"
    }
}

fun printAnnualReport(year: Int) {

    clearScreen()
    setColor(SPEECH_INK)
    println("Hammurabi, I beg to report to you.")
    setColor(DEFAULT_INK)

    println("\nIn ${previous(year)}, ${persons(dead)} starved and ${persons(infants, "infant", "infants")}, ${if (infants > 1) "were" else "was"} born.")

    population += infants

    var plagueChance = Random.nextDouble()
    if (if (year > 0) plagueChance <= PLAGUE_CHANCE else false) {
        population = (population / 2)
        setColor(WARNING_INK)
        println("A horrible plague struck!  Half the people died.")
        setColor(DEFAULT_INK)
    }

    println("The population is $population.")
    println("The city owns $acres acres.")
    println("You harvested $bushelsHarvested bushels ($bushelsHarvestedPerAcre per acre).")
    if (bushelsEatenByRats > 0) {
        println("The rats ate $bushelsEatenByRats bushels.")
    }
    println("You have $bushelsInStore bushels in store.")
    bushelsHarvestedPerAcre =
        (RANGE_OF_HARVESTED_BUSHELS_PER_ACRE * Random.nextDouble()).toInt() +
        MIN_HARVESTED_BUSHELS_PER_ACRE
    println("Land is trading at $bushelsHarvestedPerAcre bushels per acre.\n")

}

fun sayBye() {
    setColor(DEFAULT_INK)
    println("\nSo long for now.\n")
}

fun quitGame() {
    sayBye()
    exitProcess(0)
}

fun relinquish() {
    setColor(SPEECH_INK)
    println("\nHammurabi, I am deeply irritated and cannot serve you anymore.")
    println("Please, get yourself another steward!")
    setColor(DEFAULT_INK)
    quitGame()
}

fun increaseIrritation() {
    irritation += (1..IRRITATION_STEP).random()
    if (irritation >= MAX_IRRITATION) relinquish() // this never returns
}

fun printIrritated(adverb: String) {
    println("The steward seems $adverb irritated.")
}

fun showIrritation() {
    when {
        irritation < IRRITATION_STEP -> {}
        irritation < IRRITATION_STEP * 2 -> printIrritated("slightly")
        irritation < IRRITATION_STEP * 3 -> printIrritated("quite")
        irritation < IRRITATION_STEP * 4 -> printIrritated("very")
        else -> printIrritated("profoundly")
    }
}

// Print a message begging to repeat an ununderstandable input.
fun begRepeat() {
    increaseIrritation() // this may never return
    setColor(SPEECH_INK)
    println("I beg your pardon?  I did not understand your order.")
    setColor(DEFAULT_INK)
    showIrritation()
}

// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
fun begThinkAgain(n: Int, name: String) {
    increaseIrritation() // this may never return
    setColor(SPEECH_INK)
    println("I beg your pardon?  You have only $n $name.  Now then…")
    setColor(DEFAULT_INK)
    showIrritation()
}

// Buy or sell land.
fun trade() {

    var acresToBuy: Int
    var acresToSell: Int

    while (true) {
        var (number, ok) = input("How many acres do you wish to buy? (0 to sell): ")
        if (! ok || number < 0) {
            begRepeat() // this may never return
            continue
        }
        acresToBuy = number
        if (bushelsHarvestedPerAcre * acresToBuy <= bushelsInStore) break
        begThinkAgain(bushelsInStore, "bushels of grain")
    }

    if (acresToBuy != 0) {

        println("You buy $acresToBuy acres.")
        acres += acresToBuy
        bushelsInStore -= bushelsHarvestedPerAcre * acresToBuy
        println("You now have $acres acres and $bushelsInStore bushels.")

    } else {

        while (true) {
            var (number, ok) = input("How many acres do you wish to sell?: ")
            if (! ok || number < 0) {
                begRepeat() // this may never return
                continue
            }
            acresToSell = number
            if (acresToSell < acres) break
            begThinkAgain(acres, "acres")
        }

        if (acresToSell > 0) {
            println("You sell $acresToSell acres.")
            acres -= acresToSell
            bushelsInStore += bushelsHarvestedPerAcre * acresToSell
            println("You now have $acres acres and $bushelsInStore bushels.")
        }

    }

}

// Feed the people.
fun feed() {

    while (true) {
        var (number, ok) = input("How many bushels do you wish to feed your people with?: ")
        if (! ok || number < 0) {
            begRepeat() // this may never return
            continue
        }
        bushelsToFeedWith = number
        // Trying to use more grain than is in silos?
        if (bushelsToFeedWith <= bushelsInStore) break
        begThinkAgain(bushelsInStore, "bushels of grain")
    }

    println("You feed your people with $bushelsToFeedWith bushels.")
    bushelsInStore -= bushelsToFeedWith
    println("You now have $bushelsInStore bushels.")

}

// Seed the land.
fun seed() {

    var acresToSeed: Int

    while (true) {

        var (number, ok) = input("How many acres do you wish to seed?: ")
        if (! ok || number < 0) {
            begRepeat() // this may never return
            continue
        }
        acresToSeed = number
        if (acresToSeed == 0) break

        // Trying to seed more acres than you own?
        if (acresToSeed > acres) {
            begThinkAgain(acres, "acres")
            continue
        }

        // Enough grain for seed?
        if ((acresToSeed / ACRES_A_BUSHEL_CAN_SEED) > bushelsInStore) {
            begThinkAgain(
                bushelsInStore,
                "bushels of grain,\nand one bushel can seed $ACRES_A_BUSHEL_CAN_SEED acres"
            )
            continue
        }

        // Enough people to tend the crops?
        if (acresToSeed <= ACRES_A_PERSON_CAN_SEED * population) break

        begThinkAgain(
            population,
            "people to tend the fields,\nand one person can seed $ACRES_A_PERSON_CAN_SEED acres"
        )

    }

    var bushelsUsedForSeeding = (acresToSeed / ACRES_A_BUSHEL_CAN_SEED)
    println("You seed $acresToSeed acres using $bushelsUsedForSeeding bushels.")
    bushelsInStore -= bushelsUsedForSeeding
    println("You now have $bushelsInStore bushels.")

    // A bountiful harvest!
    bushelsHarvestedPerAcre = (1..5).random()
    bushelsHarvested = acresToSeed * bushelsHarvestedPerAcre
    bushelsInStore += bushelsHarvested

}

fun isEven(n: Int): Boolean {
    return n % 2 == 0
}

fun checkRats() {

    var ratChance = (1..5).random()
    bushelsEatenByRats = if (isEven(ratChance)) (bushelsInStore / ratChance) else 0
    bushelsInStore -= bushelsEatenByRats

}

// Set the variables to their values in the first year.
fun init() {

    dead = 0
    totalDead = 0
    starvedPeoplePercentage = 0
    population = 95
    infants = 5
    acres = ACRES_PER_PERSON * (population + infants)
    bushelsHarvestedPerAcre = 3
    bushelsHarvested = acres * bushelsHarvestedPerAcre
    bushelsEatenByRats = 200
    bushelsInStore = bushelsHarvested - bushelsEatenByRats
    irritation = 0

}

// Result {{{1
// =============================================================

fun printResult(result: Result) {

    setColor(RESULT_INK)

    when (result) {
        Result.VeryGood -> {
            println("A fantastic performance!  Charlemagne, Disraeli and Jefferson combined could")
            println("not have done better!")
        }
        Result.NotTooBad -> {
            println("Your performance could have been somewat better, but really wasn't too bad at")
            println("all. ${(population.toDouble() * .8 * Random.nextDouble()).toInt()} people would dearly like to see you assassinated, but we all have our")
            println("trivial problems.")
        }
        Result.Bad -> {
            println("Your heavy-handed performance smacks of Nero and Ivan IV.  The people")
            println("(remaining) find you an unpleasant ruler and, frankly, hate your guts!")
        }
        Result.VeryBad -> {
            println("Due to this extreme mismanagement you have not only been impeached and thrown")
            println("out of office but you have also been declared national fink!!!")
        }
    }

    setColor(DEFAULT_INK)

}

fun printFinalReport() {

    clearScreen()

    if (starvedPeoplePercentage > 0) {
        println("In your $YEARS-year term of office, $starvedPeoplePercentage percent of the")
        println("population starved per year on the average, i.e., a total of $totalDead people died!\n")
    }

    var acresPerPerson = acres / population
    println("You started with $ACRES_PER_PERSON acres per person and ended with $acresPerPerson.\n")

    when {
        starvedPeoplePercentage > 33 || acresPerPerson <  7 -> printResult(Result.VeryBad)
        starvedPeoplePercentage > 10 || acresPerPerson <  9 -> printResult(Result.Bad)
        starvedPeoplePercentage >  3 || acresPerPerson < 10 -> printResult(Result.NotTooBad)
        else -> printResult(Result.VeryGood)
    }

}

// Main (1) {{{1
// =============================================================

fun checkStarvation(year: Int) {

        // How many people has been fed?
        var fedPeople = (bushelsToFeedWith / BUSHELS_TO_FEED_A_PERSON)

        if (population > fedPeople) {

            dead = population - fedPeople
            starvedPeoplePercentage = ((year - 1) * starvedPeoplePercentage + dead * 100 / population) / year
            population -= dead
            totalDead += dead

            // Starve enough for impeachment?
            if (dead > (.45 * population.toDouble()).toInt()) {

                setColor(WARNING_INK)
                println("\nYou starved $dead people in one year!!!\n")
                setColor(DEFAULT_INK)
                printResult(Result.VeryBad)
                quitGame()

            }

        }

}

fun govern() {

    init()

    printAnnualReport(0)

    for (year in 1 .. YEARS) {

        trade()
        feed()
        seed()
        checkRats()

        // Let's have some babies
        infants = ((1..5).random() * (20 * acres + bushelsInStore) / population / 100 + 1)

        checkStarvation(year)

        pause("\nPress the Enter key to read the annual report. ")
        printAnnualReport(year)

    }

}

fun main() {

    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
    Written in 2023-11-24/25.

This conversion from Odin to Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair
    Written in 2023-11-26/27.
    Last modified: 20260205T1349+0100.

*/

import kotlin.random.Random

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BRIGHT = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Global variables and constants {{{1
// =============================================================

const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = GREEN + BRIGHT + FOREGROUND
const val INSTRUCTIONS_INK = YELLOW + FOREGROUND
const val TITLE_INK = RED + BRIGHT + FOREGROUND

const val INITIAL_DISTANCE = 100
const val INITIAL_BULLETS = 4
const val MAX_WATERING_TROUGHS = 3

var distance = 0// distance between both gunners, in paces
var strategy = "" // player's strategy

var playerBullets = 0
var opponentBullets = 0

// User input {{{1
// =============================================================

// Print the given prompt in the correspondent color, wait until the user
// enters a string and return it parsed as an `int`; if no appropriate value
// could be found, or if the input string contained more than just the number,
// return 0. Restore the default color.
fun inputInt(prompt: String): Int {
    setColor(INPUT_INK)
    print(prompt)
    val input = readln()
    var number : Int
    try {
        number = input.toInt()
    }
    catch (e: NumberFormatException) {
        number = 0
    }
    setColor(DEFAULT_INK)
    return number
}

// Print the given prompt and wait until the user enters a string.
fun inputString(prompt: String = ""): String {
    setColor(INPUT_INK)
    print(prompt)
    setColor(DEFAULT_INK)
    return readln()
}

// Print the given prompt and wait until the user presses Enter.
fun pressEnter(prompt: String) {
    inputString(prompt)
}

// Return `true` if the given string is "yes" or a synonym.
fun isYes(answer: String): Boolean {
    when (answer.lowercase()) {
        "ok", "yeah", "yes", "y" -> return true
        else -> return false
    }
}

// Return `true` if the given string is "no" or a synonym.
fun isNo(answer: String): Boolean {
    when (answer.lowercase()) {
        "no", "nope", "n" -> return true
        else -> return false
    }
}

// Print the given prompt, wait until the user enters a valid yes/no
// string, and return `true` for "yes" or `false` for "no".
fun yes(prompt: String): Boolean {
    while (true) {
        var answer = inputString(prompt)
        if (isYes(answer) ) return true
        if (isNo(answer) ) return false
    }
}

// Title, instructions and credits {{{1
// =============================================================

const val TITLE = "High Noon"

// Print the title at the current cursor position.
fun printTitle() {
    setColor(TITLE_INK)
    println(TITLE)
    setColor(DEFAULT_INK)
}

fun printCredits() {
    printTitle()
    println("\nOriginal version in BASIC:")
    println("    Designed and programmend by Chris Gaylo, 1970.")
    println("    http://mybitbox.com/highnoon-1970/")
    println("    http://mybitbox.com/highnoon/")
    println("Transcriptions:")
    println("    https://github.com/MrMethor/Highnoon-BASIC/")
    println("    https://github.com/mad4j/basic-highnoon/")
    println("Version modified for QB64:")
    println("    By Daniele Olmisani, 2014.")
    println("    https://github.com/mad4j/basic-highnoon/")
    println("This improved remake in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair")
}

fun printInstructions() {
    printTitle()
    setColor(INSTRUCTIONS_INK)
    println("\nYou have been challenged to a showdown by Black Bart, one of")
    println("the meanest desperadoes west of the Allegheny mountains.")
    println("\nWhile you are walking down a dusty, deserted side street,")
    println("Black Bart emerges from a saloon one hundred paces away.")
    println("\nBy agreement, you each have $INITIAL_BULLETS bullets in your six-guns.")
    println("\nYour marksmanship equals his. At the start of the walk nei-")
    println("ther of you can possibly hit the other, and at the end of")
    println("the walk, neither can miss. the closer you get, the better")
    println("your chances of hitting black Bart, but he also has beter")
    println("chances of hitting you.")
    setColor(DEFAULT_INK)
}

// Game loop {{{1
// =============================================================

fun pluralSuffix(n: Int): String {
    when (n) {
        1 -> return ""
        else -> return "s"
    }
}

fun printShellsLeft() {
    if (playerBullets == opponentBullets) {
        println("Both of you have $playerBullets bullets.")
    } else {
        println(
            "You now have $playerBullets bullet${pluralSuffix(playerBullets)} to Black Bart's $opponentBullets bullet${pluralSuffix(opponentBullets)}."
        )
    }
}

fun printCheck() {
    val check = (0..999).random().toString().padStart(4, '0')
    val day = 10 + (0..9).random()
    println("******************************************************")
    println("*                                                    *")
    println("*                 BANK OF DODGE CITY                 *")
    println("*                  CASHIER'S RECEIT                  *")
    println("*                                                    *")
    println("* CHECK NO. $check                   AUGUST ${day}TH, 1889 *")
    println("*                                                    *")
    println("*                                                    *")
    println("*       PAY TO THE BEARER ON DEMAND THE SUM OF       *")
    println("*                                                    *")
    println("* TWENTY THOUSAND DOLLARS-------------------$20,000  *")
    println("*                                                    *")
    println("******************************************************")
}

fun getReward() {
    println("As mayor of Dodge City, and on behalf of its citizens,")
    println("I extend to you our thanks, and present you with this")
    println("reward, a check for $20,000, for killing Black Bart.\n\n")
    printCheck()
    println("\n\nDon't spend it all in one place.")
}

fun moveTheOpponent() {
    var paces = 2 + (0..7).random()
    println("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.
fun maybeMoveTheOpponent(silent : Boolean = false): Boolean {
    if ((0..1).random() == 0) { // 50% chances
        moveTheOpponent()
        return true
    } else {
        if (! silent) {
            println("Black Bart stands still.")
        }
        return false
    }
}

fun missedShot(): Boolean {
    return Random.nextDouble() * 10 <= distance / 10.toDouble()
}

// Handle the opponent's shot and return a flag with the result: if the
// opponent kills the player, return `true`; otherwise return `false`.
fun theOpponentFiresAndKills(): Boolean {
    println("Black Bart fires…")
    opponentBullets -= 1
    if (missedShot()) {
        println("A miss…")
        when (opponentBullets) {
            3 -> println("Whew, were you lucky. That bullet just missed your head.")
            2 -> println("But Black Bart got you in the right shin.")
            1 -> println("Though Black Bart got you on the left side of your jaw.")
            0 -> println("Black Bart must have jerked the trigger.")
        }
    } else {
        if (strategy == "j") {
            println("That trick just saved yout life. Black Bart's bullet")
            println("was stopped by the wood sides of the trough.")
        } else {
            println("Black Bart shot you right through the heart that time.")
            println("You went kickin' with your boots on.")
            return true
        }
    }
    return false
}

// Handle the opponent's strategy and return a flag with the result: if the
// opponent runs or kills the player, return `true`; otherwise return `false`.
fun theOpponentKillsOrRuns(): Boolean {

    if (distance >= 10 || playerBullets == 0) {
        if (maybeMoveTheOpponent(silent = true) ) return false
    }
    if (opponentBullets > 0) {
        return theOpponentFiresAndKills()
    } else {
        if (playerBullets > 0) {
            if ((0..1).random() == 0) { // 50% chances
                println("Now is your chance, Black Bart is out of bullets.")
            } else {
                println("Black Bart just hi-tailed it out of town rather than face you")
                println("without a loaded gun. You can rest assured that Black Bart")
                println("won't ever show his face around this town again.")
                return true
            }
        }
    }
    return false

}

fun play() {

    distance = INITIAL_DISTANCE
    var wateringTroughs = 0
    playerBullets = INITIAL_BULLETS
    opponentBullets = INITIAL_BULLETS

    showdown@ while (true) {

        println("You are now $distance paces apart from Black Bart.")
        printShellsLeft()
        setColor(INSTRUCTIONS_INK)
        println("\nStrategies:")
        println("  [A]dvance")
        println("  [S]tand still")
        println("  [F]ire")
        println("  [J]ump behind the watering trough")
        println("  [G]ive up")
        println("  [T]urn tail and run")
        setColor(DEFAULT_INK)

        strategy = inputString("What is your strategy? ").lowercase()

        when (strategy) {

            "a" -> { // advance
                while (true) {
                    var paces = inputInt("How many paces do you advance? ")
                    if (paces < 0) {
                        println("None of this negative stuff, partner, only positive numbers.")
                    } else if (paces > 10) {
                        println("Nobody can walk that fast.")
                    } else {
                        distance -= paces
                        break
                    }
                }
            }

            "s" -> { // stand still
                println("That move made you a perfect stationary target.")
            }

            "f" -> { // fire
                if (playerBullets == 0) {

                    println("You don't have any bullets left.")
                } else {
                    playerBullets -= 1
                    if (missedShot()) {
                        when (playerBullets) {
                            2 ->
                                println("Grazed Black Bart in the right arm.")
                            1 -> {
                                println("He's hit in the left shoulder, forcing him to use his right")
                                println("hand to shoot with.")
                            }
                        }
                        println("What a lousy shot.")
                        if (playerBullets == 0) {
                            println("Nice going, ace, you've run out of bullets.")
                            if (opponentBullets != 0) {
                                println("Now Black Bart won't shoot until you touch noses.")
                                println("You better think of something fast (like run).")
                            }
                        }
                    } else {
                        println("What a shot, you got Black Bart right between the eyes.")
                        pressEnter("\nPress the Enter key to get your reward. ")
                        clearScreen()
                        getReward()
                        break@showdown
                    }
                }
            }

            "j" -> { // jump
                if (wateringTroughs == MAX_WATERING_TROUGHS) {
                    println("How many watering troughs do you think are on this street?")
                    strategy = ""
                } else {
                    wateringTroughs += 1
                    println("You jump behind the watering trough.")
                    println("Not a bad maneuver to threw Black Bart's strategy off.")
                }
            }

            "g" -> { // give up
                println("Black Bart accepts. The conditions are that he won't shoot you")
                println("if you take the first stage out of town and never come back.")
                if (yes("Agreed? ")) {
                    println("A very wise decision.")
                    break@showdown
                } else {
                    println("Oh well, back to the showdown.")
                }
            }

            "t" -> { // turn tail and run
                // The more bullets of the opponent, the less chances to escape.
                if (((0..(opponentBullets + 1)).random()) == 0) {
                    println("Man, you ran so fast even dogs couldn't catch you.")
                } else {
                    when (opponentBullets) {
                        0 -> {
                            println("You were lucky, Black Bart can only throw his gun at you, he")
                            println("doesn't have any bullets left. You should really be dead.")
                        }
                        1 -> {
                            println("Black Bart fires his last bullet…",)
                            println("He got you right in the back. That's what you deserve, for running.")
                        }
                        2 -> {
                            println("Black Bart fires and got you twice: in your back")
                            println("and your ass. Now you can't even rest in peace.")
                        }
                        3 -> {
                            println("Black Bart unloads his gun, once in your back")
                            println("and twice in your ass. Now you can't even rest in peace.")
                        }
                        4 -> {
                            println("Black Bart unloads his gun, once in your back")
                            println("and three times in your ass. Now you can't even rest in peace.")
                        }
                    }
                    opponentBullets = 0
                }
                break@showdown
            }

            else -> println("You sure aren't going to live very long if you can't even follow directions.")

        } // strategy switch

        if (theOpponentKillsOrRuns() ) break

        if (playerBullets + opponentBullets == 0) {
            println("The showdown must end, because nobody has bullets left.")
            break
        }

        println()

    } // showdown loop

}

// Main {{{1
// =============================================================

fun main() {

    clearScreen()
    printCredits()

    pressEnter("\nPress the Enter key to read the instructions. ")
    clearScreen()
    printInstructions()

    pressEnter("\nPress the Enter key to start. ")
    clearScreen()
    play()

}

Math

/*
Math

Original version in BASIC:
    Example included in Vintage BASIC 1.0.3.
    http://www.vintage-basic.net

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-08, 2023-10, 2024-03, 2025-05.

Last modified: 20250519T0016+0200.
*/

fun main() {

    print("Enter a number: ")
    var input = readln()
    var n: Double = try { input.toDouble() } catch (e: NumberFormatException) { 0.0 }

    println("ABS($n) --> kotlin.math.abs($n) --> ${kotlin.math.abs(n)}")
    println("ATN($n) --> kotlin.math.atan($n) --> ${kotlin.math.atan(n)}")
    println("COS($n) --> kotlin.math.cos($n) --> ${kotlin.math.cos(n)}")
    println("EXP($n) --> kotlin.math.exp($n) --> ${kotlin.math.exp(n)}")
    println("INT($n) --> $n.toInt() --> ${n.toInt()}")
    println("LOG($n) --> kotlin.math.ln($n) --> ${kotlin.math.ln(n)}")
    println("SGN($n) --> kotlin.math.sign($n) --> ${kotlin.math.sign(n)}")
    println("SQR($n) --> kotlin.math.sqrt($n) --> ${kotlin.math.sqrt(n)}")
    println("TAN($n) --> kotlin.math.tan($n) --> ${kotlin.math.tan(n)}")

}

Mugwump

/*
Mugwump

Original version in BASIC:
    Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).
    Slightly modified by Bob Albrecht of People's Computer Company.
    Published by Creative Computing (Morristown, New Jersey, USA), 1978.
    - https://www.atariarchives.org/basicgames/showpage.php?page=114
    - http://vintage-basic.net/games.html

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-10.

Last modified: 20250518T2359+0200.
*/

import kotlin.math.pow
import kotlin.math.sqrt

const val GRID_SIZE = 10
const val TURNS = 10
const val MUGWUMPS = 4

class Mugwump {
    var x: Int = 0
    var y: Int = 0
    var hidden: Boolean = false
}

var mugwump: MutableList<Mugwump> = mutableListOf()

var found: Int = 0 // counter

// Move the cursor to the top left position of the terminal.
fun home() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clear() {
    print("\u001B[2J")
    home()
}

// Print the given prompt, wait until the user enters a valid integer
// and return it.
fun getNumber(prompt: String): Int {
    var number: Int
    while (true) {
        try {
            print(prompt)
            number = readln().toInt()
            break
        }
        catch (e: Exception) { }
    }
    return number
}

// Return `true` if the given string is "yes" or a synonym.
fun isYes(answer: String): Boolean {
    when (answer.lowercase()) {
        "ok", "yeah", "yes", "y" -> return true
        else -> return false
    }
}

// Return `true` if the given string is "no" or a synonym.
fun isNo(answer: String): Boolean {
    when (answer.lowercase()) {
        "no", "nope", "n" -> return true
        else -> return false
    }
}

// Print the given prompt, wait until the user enters a valid yes/no
// String, and return `true` for "yes" or `false` for "no".
fun yes(prompt: String): Boolean {
    var answer = ""
    while (!(isYes(answer) || isNo(answer))) {
        println(prompt)
        answer = readln()
    }
    return isYes(answer)
}


// Clear the screen, print the credits and ask the user to press enter.
fun printCredits() {
    clear()
    println("Mugwump\n")
    println("Original version in BASIC:")
    println("    Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).")
    println("    Slightly modified by Bob Albrecht of People's Computer Company.")
    println("    Published by Creative Computing (Morristown, New Jersey, USA), 1978.")
    println("    - https://www.atariarchives.org/basicgames/showpage.php?page=114")
    println("    - http://vintage-basic.net/games.html\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    print("Press Enter to read the instructions. ")
    readln()
}

// Clear the screen, print the instructions and ask the user to press enter.
fun printInstructions() {
    clear()
    println("Mugwump\n")
    println("The object of this game is to find four mugwumps")
    println("hidden on a 10 by 10 grid.  Homebase is position 0,0.")
    println("Any guess you make must be two numbers with each")
    println("number between 0 and 9, inclusive.  First number")
    println("is distance to right of homebase and second number")
    println("is distance above homebase.\n")
    println("You get $TURNS tries.  After each try, you will see")
    println("how far you are from each mugwump.\n")
    print("Press Enter to start. ")
    readln()
}

// Init the mugwumps' positions, `hidden` flags and count.
fun hideMugwumps() {
    for (m in mugwump.indices) {
        mugwump[m].x = (0..<GRID_SIZE).random()
        mugwump[m].y = (0..<GRID_SIZE).random()
        mugwump[m].hidden = true
    }
    found = 0 // counter
}

// Create the mugwumps.
fun createMugwumps() {
    for (m in 0..<MUGWUMPS) {
        mugwump.add(Mugwump())
    }
}

// Print the given prompt, wait until the user enters a valid coord
// and return it.
fun getCoord(prompt: String): Int {
    var coord: Int
    while (true) {
        coord = getNumber(prompt)
        if (coord < 0 || coord >= GRID_SIZE) {
            println("Invalid value $coord: not in range [0, ${GRID_SIZE - 1}].")
        } else {
            break
        }
    }
    return coord
}

// Return `true` if the given mugwump is hidden in the given coords.
fun isHere(m: Int, x: Int, y: Int): Boolean {
    return mugwump[m].hidden && mugwump[m].x == x && mugwump[m].y == y
}

// Return the distance between the given mugwump and the given coords.
fun distance(m: Int, xCoord: Int, yCoord: Int): Int {
    return sqrt((mugwump[m].x.toDouble() - xCoord.toDouble()).pow(2.0) + (mugwump[m].y.toDouble() - yCoord.toDouble()).pow(2.0)).toInt()
}

// Return a plural ending (default: "s") if the given number is greater than 1;
// otherwise return a singular ending (default: an empty string).
fun plural(n: Int, pluralEnding: String = "s", singularEnding: String = ""): String {
    return if (n > 1) pluralEnding else singularEnding
}

// Run the game.
fun play() {
    var x: Int
    var y: Int
    createMugwumps()
    while (true) { // game
        clear()
        hideMugwumps()
        var turn: Int = 1
        turnsLoop@ while (turn <= TURNS) {
            println("Turn number $turn\n")
            println("What is your guess (in range 0..${GRID_SIZE - 1})?")
            x = getCoord("Distance right of homebase (x-axis): ")
            y = getCoord("Distance above homebase (y-axis): ")
            println("\nYour guess is ($x, $y).")
            for (m in mugwump.indices) {
                if (isHere(m, x, y)) {
                    mugwump[m].hidden = false
                    found += 1
                    println("You have found mugwump $m!")
                    if (found == mugwump.size) { break@turnsLoop }
                }
            }
            for (m in mugwump.indices) {
                if (mugwump[m].hidden) {
                    println("You are ${distance(m, x, y)} units from mugwump $m.")
                }
            }
            println()
            turn += 1
        } // turns
        if (found == mugwump.size) {
            println("\nYou got them all in $turn ${plural(turn)}!")
            println("That was fun! let's play again…")
            println("Four more mugwumps are now in hiding.")
        } else {
            println("Sorry, that's $TURNS tr${plural(TURNS, "ies", "y")}.\n")
            println("Here is where they're hiding:")
            for (m in mugwump.indices) {
                if (mugwump[m].hidden) {
                    println("Mugwump $m is at (${mugwump[m].x}, ${mugwump[m].y}).")
                }
            }
        }
        println()
        if (!yes("Do you want to play again? ")) { break }
    } // game
}

fun main() {
    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 Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2023-10-25.

Last modified: 20250420T0003+0200.
*/


// Print the given prompt, wait until the user enters a valid integer
// and return it.
fun getNumber(prompt: String): Int {
    var number: Int
    while (true) {
        try {
            print(prompt)
            number = readln().toInt()
            break
        }
        catch (e: Exception) { }
    }
    return number
}

fun main() {
    print("What is your name? ")
    val name = readln()
    var number = getNumber("Enter a number: ")
    print("Hello, $name!\n".repeat(number))
}

Poetry

/*
Poetry

Original version in BASIC:
    Unknown author.
    Modified and reworked by Jim Bailey, Peggy Ewing, and Dave Ahl at DEC.
    Published in "BASIC Computer Games", Creative Computing (Morristown, New Jersey, USA), 1978.
    https://archive.org/details/Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
    https://github.com/chaosotter/basic-games/tree/master/games/BASIC%20Computer%20Games/Poetry
    http://vintage-basic.net/games.html

This improved remake in Kotlin:
    Copyright (c) 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2025-05-17/18.

Last modified: 20250731T1954+0200.
*/

import kotlin.random.Random
import kotlin.time.*

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BACKGROUND = +40
const val BRIGHT     = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Globals {{{1
// =============================================================

const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val TITLE_INK = BRIGHT + RED + FOREGROUND

// User input {{{1
// =============================================================

fun pressEnter(prompt: String) {
    setColor(INPUT_INK)
    print(prompt)
    readln()
    setColor(DEFAULT_INK)
}

// Title and credits {{{1
// =============================================================

fun printTitle() {
    setColor(TITLE_INK)
    println("Poetry")
    setColor(DEFAULT_INK)
}

fun printCredits() {
    printTitle()
    println("\nOriginal version in BASIC:")
    println("    Unknown author.")
    println("    Published in \"BASIC Computer Games\",")
    println("    Creative Computing (Morristown, New Jersey, USA), 1978.\n")
    println("This improved remake in Kotlin:")
    println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair")
}

// Time {{{1
// =============================================================

// Wait the given number of seconds.
//
// Note `Thread.sleep(1000 * seconds)` (provided by `import
// kotlin.concurrent.thread`) would be a simpler alternative, but it's not
// supported by kotlinc-native.
//
fun waitSeconds(seconds: Double) {
    val duration: Duration = seconds.toDuration(DurationUnit.SECONDS)
    val timeSource = TimeSource.Monotonic
    val startTime = timeSource.markNow()
    do {
        var currentTime = timeSource.markNow()
    } while ((currentTime - startTime) < duration)
}

// Main {{{1
// =============================================================

fun isEven(n: Int): Boolean {
    return n.rem(2) == 0
}

fun play() {

    val MAX_PHRASES_AND_VERSES = 20

    // counters:
    var action = 0
    var phrase = 0
    var phrasesAndVerses = 0
    var verseChunks = 0

    verse@ while (true) {

        var manageTheVerseContinuation = true
        var maybeAddComma = true

        when (action) {
            0, 1 ->
                when (phrase) {
                    0 ->  print("MIDNIGHT DREARY")
                    1 ->  print("FIERY EYES")
                    2 ->  print("BIRD OR FIEND")
                    3 ->  print("THING OF EVIL")
                    4 ->  print("PROPHET")
                }
            2 ->
                when (phrase) {
                    0 ->  { print("BEGUILING ME"); verseChunks = 2 }
                    1 ->  print("THRILLED ME")
                    2 ->  { print("STILL SITTING…"); maybeAddComma = false }
                    3 ->  { print("NEVER FLITTING"); verseChunks = 2 }
                    4 ->  print("BURNED")
                }
            3 ->
                when (phrase) {
                    0 ->  print("AND MY SOUL")
                    1 ->  print("DARKNESS THERE")
                    2 ->  print("SHALL BE LIFTED")
                    3 ->  print("QUOTH THE RAVEN")
                    4 ->  if (verseChunks != 0) print("SIGN OF PARTING")
                }
            4 ->
                when (phrase) {
                    0 ->  print("NOTHING MORE")
                    1 ->  print("YET AGAIN")
                    2 ->  print("SLOWLY CREEPING")
                    3 ->  print("…EVERMORE")
                    4 ->  print("NEVERMORE")
                }
            5 -> {
                action = 0
                println()
                if (phrasesAndVerses > MAX_PHRASES_AND_VERSES) {
                    println()
                    verseChunks = 0
                    phrasesAndVerses = 0
                    action = 2
                    continue@verse
                } else {
                    manageTheVerseContinuation = false
                }
            }
        }

        if (manageTheVerseContinuation) {

            waitSeconds(0.25)

            if (maybeAddComma && ! (verseChunks == 0 || Random.nextDouble() > 0.19)) {
                print(",")
                verseChunks = 2
            }

            if (Random.nextDouble() > 0.65) {
                println()
                verseChunks = 0
            } else {
                print(" ")
                verseChunks += 1
            }

        }

        action += 1
        phrase = Random.nextInt(0, 4)
        phrasesAndVerses += 1

        if (! (verseChunks > 0 || isEven(action))) {
            print("     ")
        }

    } // verse loop

}

fun main() {
    clearScreen()
    printCredits()
    pressEnter("\nPress the Enter key to start. ")
    clearScreen()
    play()
}

Russian Roulette

/*
Russian Roulette

Original version in BASIC:
    Creative Computing (Morristown, New Jersey, USA), ca. 1980.

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-10.

Last modified: 20250420T0003+0200.
*/

// Move the cursor to the top left position of the terminal.
fun home() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clear() {
    print("\u001B[2J")
    home()
}

// Prompt the user to enter a command and return it.
fun command(prompt: String = "> "): String {
    print(prompt)
    return readln()
}

fun pressEnterToStart() {
    print("Press Enter to start. ")
    readln()
}

fun printCredits() {
    clear()
    println("Russian Roulette\n")
    println("Original version in BASIC:")
    println("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    pressEnterToStart()
}

fun printInstructions() {
    clear()
    println("Here is a revolver.")
    println("Type 'f' to spin chamber and pull trigger.")
    println("Type 'g' to give up, and play again.")
    println("Type 'q' to quit.\n")
}

fun play() {
    gameLoop@ while (true) {
        printInstructions()
        var times = 0
        playLoop@ while (true) {
            when (command()) {
                "f" -> // fire
                    if ((0..100).random() > 83) {
                        println("Bang! You're dead!")
                        println("Condolences will be sent to your relatives.")
                        break@playLoop
                    } else {
                        times += 1
                        if (times > 10) {
                            println("You win!")
                            println("Let someone else blow his brains out.")
                            break@playLoop
                        } else {
                            println("Click.")
                        }
                    }
                "g" -> // give up
                    {
                        println("Chicken!")
                        break@playLoop
                    }
                "q" -> // quit
                    {
                        break@gameLoop
                    }
            }
        } // play loop
        pressEnterToStart()
    } // game loop
}

fun main() {
    printCredits()
    play()
    println("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 Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2023-11-26.

Last modified: 20250519T0008+0200.
*/

import kotlin.time.*

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BRIGHT = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Set the cursor position to the given coordinates (the top left position is 1, 1)
fun setCursorPosition(line: Int, column: Int) {
    print("\u001B[${line};${column}H")
}

// Make the cursor invisible.
fun hideCursor() {
    print("\u001B[?25l")
}

// Make the cursor visible.
fun showCursor() {
    print("\u001B[?25h")
}

// Erase from the current cursor position to the end of the current line.
fun eraseLineRight() {
    print("\u001B[K")
}

// Erase the given line to the right of the given column.
fun eraseLineRightFrom(line: Int, column: Int) {
    setCursorPosition(line, column)
    eraseLineRight()
}

// Global variables and constants {{{1
// =============================================================

const val MAX_SCORE = 50

const val MAX_MESSAGE_LENGTH = 6
const val MIN_MESSAGE_LENGTH = 3

const val BASE_CHARACTER = '@'.code
const val PLANCHETTE = '*'

const val FIRST_LETTER_NUMBER = 1
const val LAST_LETTER_NUMBER = 26

const val BOARD_INK = BRIGHT + CYAN + FOREGROUND
const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val INSTRUCTIONS_INK = YELLOW + FOREGROUND
const val MISTAKE_EFFECT_INK = BRIGHT + RED + FOREGROUND
const val PLANCHETTE_INK = YELLOW + FOREGROUND
const val TITLE_INK = BRIGHT + RED + FOREGROUND

const val MISTAKE_EFFECT_PAUSE: Long = 3 // seconds

const val BOARD_HEIGHT = 5 // characters displayed on the left and right borders
const val BOARD_BOTTOM_Y = BOARD_HEIGHT + 1 // relative to the board
const val BOARD_PAD = 1 // blank characters separating the board from its left and right borders
const val BOARD_WIDTH = 8 // characters displayed on the top and bottom borders
const val BOARD_ACTUAL_WIDTH = BOARD_WIDTH + 2 * BOARD_PAD // screen columns
const val BOARD_X = 29 // screen column
const val BOARD_Y = 5 // screen line

const val INPUT_X = BOARD_X
const val INPUT_Y = BOARD_Y + BOARD_BOTTOM_Y + 4

const val MESSAGES_Y = INPUT_Y

const val TITLE = "Seance"

// User input {{{1
// =============================================================

// Print the given prompt and wait until the user enters a string.
fun input(prompt: String = ""): String {
    setColor(INPUT_INK)
    print(prompt)
    val result: String = readln() ?: ""
    setColor(DEFAULT_INK)
    return result
}

// Print the given prompt and wait until the user presses Enter.
fun pressEnter(prompt: String) {
    input(prompt)
}

// Accept a string from the user, erase it from the screen and return it.
fun messageUnderstood(): String {
    setCursorPosition(INPUT_Y, INPUT_X)
    val result = input("? ").uppercase()
    eraseLineRightFrom(INPUT_Y, INPUT_X)
    return result
}

// Title, instructions and credits {{{1
// =============================================================

// Print the title at the current cursor position.
fun printTitle() {
    setColor(TITLE_INK)
    println(TITLE)
    setColor(DEFAULT_INK)
}

// Print the title on the given row, centered on the board.
fun printBoardCenteredTitle(y: Int) {
    setColor(TITLE_INK)
    printBoardCentered(TITLE, y)
    setColor(DEFAULT_INK)
}

fun printCredits() {
    printTitle()
    println("\nOriginal version in BASIC:")
    println("    Written by Chris Oxlade, 1983.")
    println("    https://archive.org/details/seance.qb64")
    println("    https://github.com/chaosotter/basic-games")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
}

fun printInstructions() {
    printTitle()
    setColor(INSTRUCTIONS_INK)
    println("\nMessages from the Spirits are coming through, letter by letter.  They want you")
    println("to remember the letters and type them into the computer in the correct order.")
    println("If you make mistakes, they will be angry -- very angry...")
    println()
    println("Watch for stars on your screen -- they show the letters in the Spirits'")
    println("messages.")
    setColor(DEFAULT_INK)
}

// Time {{{1
// =============================================================

// Wait the given number of seconds.
//
// Note `Thread.sleep(1000 * seconds)` (provided by `import
// kotlin.concurrent.thread`) would be a simpler alternative, but it's not
// supported by kotlinc-native.
//
fun waitSeconds(seconds: Long) {
    val duration: Duration = seconds.toDuration(DurationUnit.SECONDS)
    val timeSource = TimeSource.Monotonic
    val startTime = timeSource.markNow()
    do {
        var currentTime = timeSource.markNow()
    } while ((currentTime - startTime) < duration)
}

// Board {{{1
// =============================================================

// Return the x coordinate to print the given text centered on the board.
fun boardCenteredX(text: String): Int {
    return BOARD_X + (BOARD_ACTUAL_WIDTH - text.length) / 2
}

// Print the given text on the given row, centered on the board.
fun printBoardCentered(text: String, y: Int) {
    setCursorPosition(y, boardCenteredX(text))
    println(text)
}

// Print the given letter at the given board coordinates.
fun printCharacter(y: Int, x: Int, a: Char) {
    setCursorPosition(y + BOARD_Y, x + BOARD_X)
    print(a)
}

// Print the board.
fun printBoard() {
    setColor(BOARD_INK)
    for (i in 1..BOARD_WIDTH) {
        printCharacter(0, i + 1, (BASE_CHARACTER + i).toChar()) // top border
        printCharacter(BOARD_BOTTOM_Y, i + 1, (BASE_CHARACTER + LAST_LETTER_NUMBER - BOARD_HEIGHT - i + 1).toChar()) // bottom border
    }
    for (i in 1..BOARD_HEIGHT) {
        printCharacter(i , 0, (BASE_CHARACTER + LAST_LETTER_NUMBER - i + 1).toChar()) // left border
        printCharacter(i , 3 + BOARD_WIDTH, (BASE_CHARACTER + BOARD_WIDTH + i).toChar()) // right border
    }
    println()
    setColor(DEFAULT_INK)
}

// Print the given mistake effect, wait a configured number of seconds and finally erase it.
fun printMistakeEffect(effect: String) {
    var x = boardCenteredX(effect)
    hideCursor()
    setCursorPosition(MESSAGES_Y, x)
    setColor(MISTAKE_EFFECT_INK)
    println(effect)
    setColor(DEFAULT_INK)
    waitSeconds(MISTAKE_EFFECT_PAUSE)
    eraseLineRightFrom(MESSAGES_Y, x)
    showCursor()
}

// Return a new message of the given length, after marking its letters on the board.
fun message(length: Int): String {
    var y: Int
    var x: Int
    var message = ""
    hideCursor()
    for (i in 1..length) {
        var letterNumber = (FIRST_LETTER_NUMBER..LAST_LETTER_NUMBER).random()
        var letter: Char = (BASE_CHARACTER + letterNumber).toChar()
        message = "$message$letter"
        when {
            letterNumber <= BOARD_WIDTH  -> {
                // top border
                y = 1
                x = letterNumber + 1
            }
            letterNumber <= BOARD_WIDTH + BOARD_HEIGHT  -> {
                // right border
                y = letterNumber - BOARD_WIDTH
                x = 2 + BOARD_WIDTH
            }
            letterNumber <= BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH  -> {
                // bottom border
                y = BOARD_BOTTOM_Y - 1
                x = 2 + BOARD_WIDTH + BOARD_HEIGHT + BOARD_WIDTH - letterNumber
            }
            else -> {
                // left border
                y = 1 + LAST_LETTER_NUMBER - letterNumber
                x = 1
            }
        }
        setColor(PLANCHETTE_INK)
        printCharacter(y, x, PLANCHETTE)
        setColor(DEFAULT_INK)
        waitSeconds(1)
        printCharacter(y, x, ' ')
    }
    showCursor()
    return message
}

// Game loop {{{1
// =============================================================

fun play() {

    var score = 0
    var mistakes = 0
    printBoardCenteredTitle(1)
    printBoard()

    while (true) {
        var messageLength = (MIN_MESSAGE_LENGTH..MAX_MESSAGE_LENGTH).random()
        if (message(messageLength) != messageUnderstood()) {
            mistakes += 1
            when (mistakes) {
                1  ->
                    printMistakeEffect("The table begins to shake!")
                2  ->
                    printMistakeEffect("The light bulb shatters!")
                3  -> {
                    printMistakeEffect("Oh, no!  A pair of clammy hands grasps your neck!")
                    return
                }
            }
        } else {
            score += messageLength
            if (score >= MAX_SCORE) {
                printBoardCentered("Whew!  The spirits have gone!", MESSAGES_Y)
                printBoardCentered("You live to face another day!", MESSAGES_Y + 1)
                return
            }
        }
    }

}

// Main {{{1
// =============================================================

fun main() {

    setColor(DEFAULT_INK)
    clearScreen()
    printCredits()

    pressEnter("\nPress the Enter key to read the instructions. ")
    clearScreen()
    printInstructions()

    pressEnter("\nPress the Enter key to start. ")
    clearScreen()
    play()
    println("\n")

}

Sine Wave

/*
Sine Wave

Original version in BASIC:
    Creative Computing (Morristown, New Jersey, USA), ca. 1980.

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-08, 2023-09, 2023-10.

Last modified: 20250419T2224+0200.
*/

fun home() {
    print("\u001B\u005B\u0048")
}

fun clear() {
    print("\u001B\u005B\u0032\u004A")
    home()
}

val order = arrayOf("first", "second")

var word = arrayOfNulls<String>(2)

fun getWords() {
    for (w in word.indices) {
        print("Enter the ${order[w]} word: ")
        word[w] = readln()
    }
}

fun showCredits() {
    println("Sine Wave\n")
    println("Original version in BASIC:")
    println("    Creative Computing (Morristown, New Jersey, USA), ca. 1980.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    println("Press Enter to start the program.")
    readln()
}

fun draw() {
    var even = true
    var angle = 0.0
    while (angle <= 40) {
        print(" ".repeat((26 + 25 * kotlin.math.sin(angle)).toInt()))
        println(word[if (even) 0 else 1])
        even = ! even
        angle += 0.25
    }
}

fun main() {
    clear()
    showCredits()
    clear()
    getWords()
    clear()
    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 Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-10.

Last modified: 20250519T0009+0200.
*/

import kotlin.time.*

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BRIGHT = +60

const val NORMAL = 0

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clear() {
    print("\u001B[2J")
    moveCursorHome()
}

// Make the cursor invisible.
fun hideCursor() {
    print("\u001B[?25l")
}

// Make the cursor visible.
fun showCursor() {
    print("\u001B[?25h")
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Set the attribute.
fun setAttribute(attr: Int) {
    print("\u001B[0;${attr}m")
}

// Global variables and constants {{{1
// =============================================================

var image = arrayOf<String>(" BAR  ", " BELL ", "ORANGE", "LEMON ", " PLUM ", "CHERRY")
const val BAR = 0 // position of "BAR" in `image`.
var color = arrayOf<Int>(
    WHITE + FOREGROUND,
    CYAN + FOREGROUND,
    YELLOW + FOREGROUND,
    BRIGHT + YELLOW + FOREGROUND,
    BRIGHT + WHITE + FOREGROUND,
    BRIGHT + RED + FOREGROUND )
const val MAX_BET = 100
const val MIN_BET = 1

var reel = mutableListOf<Int>(0, 0, 0)

// User input {{{1
// =============================================================

fun pressEnter(prompt: String) {
    print(prompt)
    readln()
}

// Accept and return a valid integer, or zero.
fun getNumber(prompt: String): Int {
    try {
        print(prompt)
        return readln().toInt()
    }
    catch (e: Exception) { return 0 }
}

// Credits, instructions {{{1
// =============================================================

fun printCredits() {
    clear()
    println("Slots")
    println("A slot machine simulation.\n")
    println("Original version in BASIC:")
    println("    Creative computing (Morristown, New Jersey, USA).")
    println("    Produced by Fred Mirabelle and Bob Harper on 1973-01-29.\n")
    println("This version in Kotlin:")
    println("    Copyright (c) 2023, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair\n")
    pressEnter("Press Enter for instructions.")
}

fun printInstructions() {
    clear()
    println("You are in the H&M casino, in front of one of our")
    println("one-arm bandits. Bet from $MIN_BET to $MAX_BET USD (or 0 to quit).\n")
    pressEnter("Press Enter to start.")
}

// Main {{{1
// =============================================================

fun won(prize: Int, bet: Int): Int {
    when (prize) {
        2 -> println("DOUBLE!")
        5 -> println("*DOUBLE BAR*")
        10 -> println("**TOP DOLLAR**")
        100 -> println("***JACKPOT***")
    }
    println("You won!")
    return (prize + 1) * bet
}

fun showStandings(dollars: Int) {
    println("Your standings are $dollars USD.")
}

fun printReels() {
    moveCursorHome()
    for (r in reel) {
        setColor(color[r])
        print("[${image[r]}]")
    }
    setAttribute(NORMAL)
    println("")
}

fun initReels() {
    for (i in reel.indices) {
        reel[i] = image.indices.random()
    }
}

fun spinReels() {
    val duration: Duration = 2.toDuration(DurationUnit.SECONDS)
    val timeSource = TimeSource.Monotonic
    val startTime = timeSource.markNow()
    hideCursor()
    do {
        initReels()
        printReels()
        var currentTime = timeSource.markNow()
    } while ((currentTime - startTime) < duration)
    showCursor()
}

fun play() {
    var bet: Int
    var standings = 0
    initReels()
    playLoop@ while (true) {
        betLoop@ while (true) {
            clear()
            printReels()
            bet = getNumber("Your bet (or 0 to quit): ")
            when (true) {
                (bet > MAX_BET) -> {
                    println("House limits are $MAX_BET USD.")
                    pressEnter("Press Enter to try again.")
                }
                (bet < MIN_BET) -> {
                    println("Type \"q\" to confirm you want to quit.")
                    var confirm = readln()
                    if (confirm.lowercase() == "q") {
                        break@playLoop
                    }
                }
                else -> break@betLoop
            } // bet check
        } // bet loop
        clear()
        spinReels()
        var bars = 0 // counter
        for (i in reel.indices) {
            bars += if (reel[i] == BAR) 1 else 0
        }
        when (reel.distinct().count()) {
            1 ->
                if (bars == 3) {
                    standings += won(100, bet)
                } else {
                    standings += won(10, bet)
                }
            2 ->
                if (bars == 2) {
                    standings += won(5, bet)
                } else {
                    standings += won(2, bet)
                }
            else -> {
                println("You lost.")
                standings -= bet
            }
        } // prize check
        showStandings(standings)
        pressEnter("Press Enter to continue.")
    } // play loop
    showStandings(standings)
    when {
        (standings < 0) ->
            println("Pay up!  Please leave your money on the terminal.")
        (standings == 0) ->
            println("Hey, you broke even.")
        (standings > 0) ->
            println("Collect your winnings from the H&M cashier.")
    }
}

fun main() {
    printCredits()
    printInstructions()
    play()
}

Stars

/*
Stars

Original version in BASIC:
    Example included in Vintage BASIC 1.0.3.
    http://www.vintage-basic.net

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-10.

Last modified: 20250420T0003+0200.
*/


// Print the given prompt, wait until the user enters a valid integer
// and return it.
fun getNumber(prompt: String): Int {
    var number: Int
    while (true) {
        try {
            print(prompt)
            number = readln().toInt()
            break
        }
        catch (e: Exception) { }
    }
    return number
}

fun main() {
    print("What is your name? ")
    val name = readln()
    println("Hello, $name.")
    do {
        var number = getNumber("How many stars do you want? ")
        println("*".repeat(number))
        print("Do you want more stars? ")
        val answer = readln()
    } while (
        when (answer.lowercase()) {
            "ok", "yeah", "yes", "y"  -> true
            else -> false
        }
    )
}

Strings

/*
Strings

Original version in BASIC:
    Example included in Vintage BASIC 1.0.3.
    http://www.vintage-basic.net

This version in Kotlin:
    Copyright (c) 2023, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written in 2023-09, 2023-10.

Last modified: 20260205T1349+0100.
*/

fun main() {

    print("Enter a string: ")
    val s = readln()
    print("Enter a number: ")
    val input = readln()
    val n = input.toInt()

    print("$s and $n\n")

    print("ASC(\"$s\") --> ")
    print("\"$s\"[0].code --> ")
    println("${s[0].code}")

    print("CHR$($n) --> ")
    print("$n.toChar() --> ")
    println("'${n.toChar()}'")

    print("LEFT$(\"$s\", $n) --> ")
    print("\"$s\".substring(0, minOf(\"$s\".length $n)) --> ")
    println(s.substring(0, minOf(s.length, n)))

    print("MID$(\"$s\", $n) --> ")
    print("\"$s\".substring(minOf(\"$s\".length, $n - 1)) --> ")
    println(s.substring(minOf(s.length, n - 1)))

    print("MID$(\"$s\", $n, 3) --> ")
    print("\"$s\".substring(minOf(\"$s\".length, $n - 1), minOf(\"$s\".length, $n - 1 + 3)) --> ")
    println(s.substring(minOf(s.length, n - 1), minOf(s.length, n - 1 + 3)))

    print("RIGHT$(\"$s\", $n) --> ")
    print("\"$s\".substring(maxOf(0, \"$s\".length - $n)) --> ")
    println(s.substring(maxOf(0, s.length - n)))

    print("LEN(\"$s\") --> ")
    print("\"$s\".lenght --> ")
    println(s.length)

    print("VAL(\"$s\") --> ")
    print("\"$s\".toFloat() --> ")
    println("${try { s.toFloat() } catch(e: Exception){ 0.0 }}")

    print("STR$($n) --> ")
    print("$n.toString() --> ")
    println("\"${n.toString()}\"")

    print("SPC($n) --> ")
    print("\" \".repeat($n) --> ")
    println("\"${" ".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 Kotlin:
    Copyright (c) 2025, Marcos Cruz (programandala.net)
    SPDX-License-Identifier: Fair

Written on 2025-05-18, 2025-08-06.

Last modified: 20260205T1353+0100.
*/

import kotlin.random.Random

// Terminal {{{1
// =============================================================

const val BLACK = 0
const val RED = 1
const val GREEN = 2
const val YELLOW = 3
const val BLUE = 4
const val MAGENTA = 5
const val CYAN = 6
const val WHITE = 7
const val DEFAULT = 9

const val FOREGROUND = +30
const val BACKGROUND = +40
const val BRIGHT     = +60

// Move the cursor to the top left position of the terminal.
fun moveCursorHome() {
    print("\u001B[H")
}

// Clear the terminal and move the cursor to the top left position.
fun clearScreen() {
    print("\u001B[2J")
    moveCursorHome()
}

// Set the cursor position to the given coordinates (the top left position is 1, 1)
fun setCursorPosition(line: Int, column: Int) {
    print("\u001B[${line};${column}H")
}

// Set the color.
fun setColor(color: Int) {
    print("\u001B[${color}m")
}

// Erase from the current cursor position to the end of the current line.
fun eraseLineRight() {
    print("\u001B[K")
}

// Erase the screen from the current line down to the bottom of the screen.
//
fun eraseScreenDown() {
    print("\u001B[J")
}

// Globals {{{1
// =============================================================

const val BOARD_INK = BRIGHT + CYAN + FOREGROUND
const val DEFAULT_INK = WHITE + FOREGROUND
const val INPUT_INK = BRIGHT + GREEN + FOREGROUND
const val INSTRUCTIONS_INK = YELLOW + FOREGROUND
const val TITLE_INK = BRIGHT + RED + FOREGROUND

const val BLANK = "*"

const val GRID_HEIGHT = 3 // cell rows
const val GRID_WIDTH = 3 // cell columns

const val CELLS = GRID_WIDTH * GRID_HEIGHT

var pristineGrid = Array(CELLS) { "" }

const val GRIDS_ROW = 3 // screen row where the grids are printed
const val GRIDS_COLUMN = 5 // screen column where the left grid is printed
const val CELLS_GAP = 2 // distance between the grid cells, in screen rows or columns
const val GRIDS_GAP = 16 // screen columns between equivalent cells of the grids

const val MAX_PLAYERS = 4

var grid = Array(MAX_PLAYERS) { Array(CELLS) { "" } }

var isPlaying = BooleanArray(MAX_PLAYERS)

var players: Int = 0

const val QUIT_COMMAND = "X"

// User input {{{1
// =============================================================

// Print the given prompt and wait until the user enters an integer.
//
fun inputInt(prompt: String = ""): Int {
    setColor(INPUT_INK)
    var number: Int = 0
    while (true) {
        try {
            print(prompt)
            number = readln().toInt()
            break
        }
        catch (e: Exception) { }
    }
    setColor(DEFAULT_INK)
    return number
}

// Print the given prompt and wait until the user enters a string.
//
fun inputString(prompt: String = ""): String {
    setColor(INPUT_INK)
    print(prompt)
    val result: String = readln()
    setColor(DEFAULT_INK)
    return result
}

// Print the given prompt and wait until the user presses Enter.
//
fun pressEnter(prompt: String) {
    inputString(prompt)
}

// Return `true` if the given string is "yes" or a synonym.
//
fun isYes(s: String): Boolean {
    return s.lowercase() in arrayOf("ok", "y", "yeah", "yes")
}

// Return `true` if the given string is "no" or a synonym.
//
fun isNo(s: String): Boolean {
    return s.lowercase() in arrayOf("n", "no", "nope")
}

// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fun yes(prompt: String): Boolean {
    while (true) {
        val answer = inputString(prompt)
        if (isYes(answer)) return true
        if (isNo(answer)) return false
    }
}

// Title, instructions and credits {{{1
// =============================================================

// Print the title at the current cursor position.
//
fun printTitle() {
    setColor(TITLE_INK)
    println("Xchange")
    setColor(DEFAULT_INK)
}

// Print the credits at the current cursor position.
//
fun printCredits() {
    printTitle()
    println("\nOriginal version in BASIC:")
    println("    Written by Thomas C. McIntire, 1979.")
    println("    Published in \"The A to Z Book of Computer Games\", 1979.")
    println("    https://archive.org/details/The_A_to_Z_BookOf_Computer_Games/page/n269/mode/2up")
    println("    https://github.com/chaosotter/basic-games")
    println("This improved remake in Kotlin:")
    println("    Copyright (c) 2025, Marcos Cruz (programandala.net)")
    println("    SPDX-License-Identifier: Fair")
}

// Print the instructions at the current cursor position.
//
fun printInstructions() {
    printTitle()
    setColor(INSTRUCTIONS_INK)
    println("\nOne or two may play.  If two, you take turns.  A grid looks like this:\n")
    setColor(BOARD_INK)
    println("    F G D")
    println("    A H $BLANK")
    println("    E B C\n")
    setColor(INSTRUCTIONS_INK)
    println("But it should look like this:\n")
    setColor(BOARD_INK)
    println("    A B C")
    println("    D E F")
    println("    G H $BLANK\n")
    setColor(INSTRUCTIONS_INK)
    println("You may exchange any one letter with the '$BLANK', but only one that's adjacent:")
    println("above, below, left, or right.  Not all puzzles are possible, and you may enter")
    println("'$QUIT_COMMAND' to give up.\n")
    println("Here we go...")
    setColor(DEFAULT_INK)
}

// Grids {{{1
// =============================================================

// Print the given player's grid title.
//
fun printGridTitle(player: Int) {
    setCursorPosition(GRIDS_ROW, GRIDS_COLUMN + (player * GRIDS_GAP))
    print("Player ${player + 1 }")
}

// Return the cursor position of the given player's grid cell.
//
fun cellPosition(player: Int, cell: Int): Pair<Int, Int> {
    val gridRow: Int = cell / GRID_HEIGHT
    val gridColumn: Int = cell % GRID_WIDTH
    val titleMargin = if (players > 1) 2 else 0
    return Pair(
        GRIDS_ROW + titleMargin + gridRow,
        GRIDS_COLUMN + (gridColumn * CELLS_GAP) + (player * GRIDS_GAP)
    )
}

// Return the cursor position of the given player's grid prompt.
//
fun gridPromptPosition(player: Int): Pair<Int, Int> {
    val gridRow: Int = CELLS / GRID_HEIGHT
    val gridColumn: Int = CELLS % GRID_WIDTH
    val titleMargin = if (players > 1) 2 else 0
    return Pair(
        GRIDS_ROW + titleMargin + gridRow + 1,
        GRIDS_COLUMN + (gridColumn * CELLS_GAP) + (player * GRIDS_GAP)
    )
}

// Print the given player's grid, in the given or default color.
//
fun printGrid(player: Int, color: Int = BOARD_INK) {
    if (players > 1) printGridTitle(player)
    setColor(color)
    for (cell in 0 .. CELLS - 1) {
        val (row, column) = cellPosition(player, cell)
        setCursorPosition(row, column)
        print(grid[player][cell])
    }
    setColor(DEFAULT_INK)
}

// Print the current players' grids.
//
fun printGrids() {
    for (player in 0 .. players - 1) {
        if (isPlaying[player]) printGrid(player)
    }
    println()
    eraseScreenDown()
}

// Scramble the grid of the given player.
//
fun scrambleGrid(player: Int) {
    for (cell in 0 .. CELLS - 1) {
        val randomCell = Random.nextInt(CELLS)
        // Exchange the contents of the current cell with that of the random one.
        grid[player][cell] = grid[player][randomCell].also {
            grid[player][randomCell] = grid[player][cell]
        }
    }
}

fun initGrids() {
    grid[0] = pristineGrid.copyOf()
    scrambleGrid(0)
    for (player in 0 + 1 .. players - 1) {
        grid[player] = grid[0].copyOf()
    }
}

// Messages {{{1
// =============================================================

// Return a message prefix for the given player.
//
fun playerPrefix(player: Int): String {
    return if (players > 1) "Player " + player + 1 + ": " else ""
}

// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.
//
fun messagePosition(player: Int, rowInc: Int = 0): Pair<Int, Int> {
    val (promptRow, prompColumn) = gridPromptPosition(player)
    return Pair(promptRow + 2 + rowInc, 1)
}

// Print the given message about the given player, adding the given row
// increment, which defaults to zero, to the default cursor coordinates.
//
fun printMessage(message: String, player: Int, rowInc: Int  = 0) {
    val (row, column) = messagePosition(player, rowInc)
    setCursorPosition(row, column)
    print("${playerPrefix(player)}${message}")
    eraseLineRight()
    println()
}

// Erase the last message about the given player.
//
fun eraseMessage(player: Int) {
    val (row, column) = messagePosition(player)
    setCursorPosition(row, column)
    eraseLineRight()
}

// Game loop {{{1
// =============================================================

// Return a message with the players range.
//
fun playersRangeMessage(): String {
    if (MAX_PLAYERS == 2) {
        return "1 or 2"
    } else {
        return "from 1 to " + MAX_PLAYERS
    }
}

// Return the number of players, asking the user if needed.
//
fun numberOfPlayers(): Int {
    var players: Int = 0
    printTitle()
    println()
    if (MAX_PLAYERS == 1) {
        players = 1
    } else {
        while (!(players in 1 .. MAX_PLAYERS)) {
            players = inputInt("Number of players (" + playersRangeMessage() + "): ")
        }
    }
    return players
}

fun isFirstCellOfGridRow(cell: Int): Boolean {
    return cell % GRID_WIDTH == 0
}

fun isLastCellOfGridRow(cell: Int): Boolean {
    return (cell + 1) % GRID_WIDTH == 0
}

fun areCellsAdjacent(cell_1: Int, cell_2: Int): Boolean {
    when {
        cell_2 == cell_1 + 1 && !isFirstCellOfGridRow(cell_2) ->
            return true
        cell_2 == cell_1 + GRID_WIDTH ->
            return true
        cell_2 == cell_1 - 1 && !isLastCellOfGridRow(cell_2) ->
            return true
        cell_2 == cell_1 - GRID_WIDTH ->
            return true
    }
    return false
}

const val NOWHERE = -1

// If the given player's character cell is a valid move, i.e. is it adjacent to
// the blank cell, return the blank cell position of the grid; otherwise return
// `null`.
//
fun targetPosition(player: Int, charCell: Int): Int? {
    val blankCell = grid[player].indexOf(BLANK)
    if (blankCell != NOWHERE && areCellsAdjacent(charCell, blankCell)) {
        return blankCell
    } else {
        printMessage("Illegal move \"" + grid[player][charCell] + "\".", player)
        return null
    }
}

// If the given player's command is valid, i.e. a grid character, return its
// position in the grid; otherwise print an error message and return `null`.
//
fun commandToCellPosition(player: Int, command: String): Int? {
    val position = grid[player].indexOf(command)
    if (position != NOWHERE) {
        return position
    } else {
        printMessage("Invalid character \"" + command + "\".", player)
        return null
    }
}

fun forgetPlayer(player: Int) {
    isPlaying[player] = false
    printGrid(player, DEFAULT_INK)
}

fun playTurn(player: Int) {

    var blankPosition: Int? = null
    var characterPosition: Int? = null

    if (isPlaying[player]) {

        while (true) {
            while (true) {
                val (row, column) = gridPromptPosition(player)
                setCursorPosition(row, column)
                eraseLineRight()
                setCursorPosition(row, column)
                val command: String = inputString("Move: ").trim().uppercase()
                if (command == QUIT_COMMAND) {
                    forgetPlayer(player)
                    return
                }
                characterPosition = commandToCellPosition(player, command)
                if (characterPosition is Int) {
                    break
                }
            }
            blankPosition = targetPosition(player, characterPosition)
            if (blankPosition is Int) {
                break
            }
        }
        eraseMessage(player)
        grid[player][blankPosition] = grid[player][characterPosition]
        grid[player][characterPosition] = BLANK

    }

}

fun playTurns() {
    for (player in 0 .. players - 1) playTurn(player)
}

fun isSomeonePlaying(): Boolean {
    for (player in 0 .. players - 1) {
        if (isPlaying[player]) return true
    }
    return false
}

// Has someone won? If so, print a message for every winner and return `true`;
// otherwise just return `false`.
//
fun hasSomeoneWon(): Boolean {
    var winners = 0
    for (player in 0 .. players - 1) {
        if (isPlaying[player]) {
            if (grid[player] contentEquals pristineGrid) {
                winners += 1
                if (winners > 0) {
                    printMessage(
                        "You're the winner" + (if (winners > 1) ", too" else "") + "!",
                        player,
                        rowInc = winners - 1)
                }
            }
        }
    }
    return winners > 0
}

fun initGame() {
    clearScreen()
    players = numberOfPlayers()
    for (player in 0 .. players - 1) {
        isPlaying[player] = true
    }
    clearScreen()
    printTitle()
    initGrids()
    printGrids()
}

fun play() {
    initGame()
    while (isSomeonePlaying()) {
        playTurns()
        printGrids()
        if (hasSomeoneWon()) break
    }
}

// Main {{{1
// =============================================================

// Init the program, i.e. just once before the first game.
//
fun initOnce() {
    // Init the pristine grid.
    val FIRST_CHAR_CODE = 'A'.code
    for (cell in 0 .. CELLS - 2) {
        pristineGrid[cell] = (FIRST_CHAR_CODE + cell).toChar().toString()
    }
    pristineGrid[CELLS - 1] = BLANK
}

// Return `true` if the player does not want to play another game; otherwise
// return `false`.
//
fun enough(): Boolean {
    val (row, column) = gridPromptPosition(player = 0)
    setCursorPosition(row, column)
    return !yes("Another game? ")
}

fun main() {
    initOnce()
    clearScreen()
    printCredits()
    pressEnter("\nPress the Enter key to read the instructions. ")
    clearScreen()
    printInstructions()
    pressEnter("\nPress the Enter key to start. ")
    while (true) {
        play()
        if (enough()) break
    }
    println("So long…")
}

Págines relatet

Basics off
Metaprojecte pri li projectes «Basics of…».
Basics of 8th
Conversion de old BASIC-programas a 8th por aprender lu elementari de ti-ci lingue.
Basics of Ada
Conversion de old BASIC-programas a Ada por aprender lu elementari de ti-ci lingue.
Basics of Arturo
Conversion de old BASIC-programas a Arturo por aprender lu elementari de ti-ci lingue.
Basics of C#
Conversion de old BASIC-programas a C# por aprender lu elementari de ti-ci lingue.
Basics of C3
Conversion de old BASIC-programas a C3 por aprender lu elementari de ti-ci lingue.
Basics of Chapel
Conversion de old BASIC-programas a Chapel por aprender lu elementari de ti-ci lingue.
Basics of Clojure
Conversion de old BASIC-programas a Clojure por aprender lu elementari de ti-ci lingue.
Basics of Crystal
Conversion de old BASIC-programas a Crystal por aprender lu elementari de ti-ci lingue.
Basics of D
Conversion de old BASIC-programas a D por aprender lu elementari de ti-ci lingue.
Basics of Elixir
Conversion de old BASIC-programas a Elixir por aprender lu elementari de ti-ci lingue.
Basics of F#
Conversion de old BASIC-programas a F# por aprender lu elementari de ti-ci lingue.
Basics of Factor
Conversion de old BASIC-programas a Factor por aprender lu elementari de ti-ci lingue.
Basics of FreeBASIC
Conversion de old BASIC-programas a FreeBASIC por aprender lu elementari de ti-ci lingue.
Basics of Gleam
Conversion de old BASIC-programas a Gleam por aprender lu elementari de ti-ci lingue.
Basics of Go
Conversion de old BASIC-programas a Go por aprender lu elementari de ti-ci lingue.
Basics of Hare
Conversion de old BASIC-programas a Hare por aprender lu elementari de ti-ci lingue.
Basics of Haxe
Conversion de old BASIC-programas a Haxe por aprender lu elementari de ti-ci lingue.
Basics of Icon
Conversion de old BASIC-programas a Icon por aprender lu elementari de ti-ci lingue.
Basics of Io
Conversion de old BASIC-programas a Io por aprender lu elementari de ti-ci lingue.
Basics of Janet
Conversion de old BASIC-programas a Janet por aprender lu elementari de ti-ci lingue.
Basics of Julia
Conversion de old BASIC-programas a Julia por aprender lu elementari de ti-ci lingue.
Basics of Lobster
Conversion de old BASIC-programas a Lobster por aprender lu elementari de ti-ci lingue.
Basics of Lua
Conversion de old BASIC-programas a Lua por aprender lu elementari de ti-ci lingue.
Basics of Nature
Conversion de old BASIC-programas a Nature por aprender lu elementari de ti-ci lingue.
Basics of Neat
Conversion de old BASIC-programas a Neat por aprender lu elementari de ti-ci lingue.
Basics of Neko
Conversion de old BASIC-programas a Neko por aprender lu elementari de ti-ci lingue.
Basics of Nelua
Conversion de old BASIC-programas a Nelua por aprender lu elementari de ti-ci lingue.
Basics of Nim
Conversion de old BASIC-programas a Nim por aprender lu elementari de ti-ci lingue.
Basics of Nit
Conversion de old BASIC-programas a Nit por aprender lu elementari de ti-ci lingue.
Basics of Oberon-07
Conversion de old BASIC-programas a Oberon-07 por aprender lu elementari de ti-ci lingue.
Basics of OCaml
Conversion de old BASIC-programas a OCaml por aprender lu elementari de ti-ci lingue.
Basics of Odin
Conversion de old BASIC-programas a Odin por aprender lu elementari de ti-ci lingue.
Basics of Pike
Conversion de old BASIC-programas a Pike por aprender lu elementari de ti-ci lingue.
Basics of Pony
Conversion de old BASIC-programas a Pony por aprender lu elementari de ti-ci lingue.
Basics of Python
Conversion de old BASIC-programas a Python por aprender lu elementari de ti-ci lingue.
Basics of Racket
Conversion de old BASIC-programas a Racket por aprender lu elementari de ti-ci lingue.
Basics of Raku
Conversion de old BASIC-programas a Raku por aprender lu elementari de ti-ci lingue.
Basics of Retro
Conversion de old BASIC-programas a Retro por aprender lu elementari de ti-ci lingue.
Basics of Rexx
Conversion de old BASIC-programas a Rexx por aprender lu elementari de ti-ci lingue.
Basics of Ring
Conversion de old BASIC-programas a Ring por aprender lu elementari de ti-ci lingue.
Basics of Rust
Conversion de old BASIC-programas a Rust por aprender lu elementari de ti-ci lingue.
Basics of Scala
Conversion de old BASIC-programas a Scala por aprender lu elementari de ti-ci lingue.
Basics of Scheme
Conversion de old BASIC-programas a Scheme por aprender lu elementari de ti-ci lingue.
Basics of Styx
Conversion de old BASIC-programas a Styx por aprender lu elementari de ti-ci lingue.
Basics of Swift
Conversion de old BASIC-programas a Swift por aprender lu elementari de ti-ci lingue.
Basics of V
Conversion de old BASIC-programas a V por aprender lu elementari de ti-ci lingue.
Basics of Vala
Conversion de old BASIC-programas a Vala por aprender lu elementari de ti-ci lingue.
Basics of Zig
Conversion de old BASIC-programas a Zig por aprender lu elementari de ti-ci lingue.

Extern ligamentes relatet