Basics of V
Descripción del contenido de la página
Conversión de antiguos programas de BASIC a V para aprender los rudimentos de este lenguaje.
Etiquetas:
3D Plot
/*
3D Plot
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in V:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2023-02-04/05.
Last modified 20250108T2315+0100.
*/
import math
import os
import term
const space = u8(` `)
const dot = u8(`*`)
fn credits() {
term.clear()
println('3D Plot\n')
println('Original version in BASIC:')
println(' Creative computing (Morristown, New Jersey, USA), ca. 1980.\n')
println('This version in V:')
println(' Copyright (c) 2023, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
os.input('\nPress the Enter key to start. ')
}
fn a(z f64) f64 {
return 30 * math.exp(-z * z / 100)
}
fn draw() {
mut l := 0
mut z := 0
mut y1 := 0
width := 56
mut line := []u8{len: width}
term.clear()
for x := -30.0; x <= 30.0; x += 1.5 {
line = []u8{len: width, init: space}
l = 0
y1 = 5 * int(math.sqrt(900 - x * x) / 5)
for y := y1; y >= -y1; y += -5 {
z = int(25 + a(math.sqrt(x * x + y * y)) - 0.7 * f64(y))
if z > l {
l = z
line[z] = dot
}
} // y loop
println(line.bytestr())
} // x loop
}
fn main() {
credits()
draw()
}
Bagels
/*
Bagels
Original version in BASIC:
D. Resek, P. Rowe, 1978.
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03, 2025-01-18.
Last modified: 20250731T1954+0200.
*/
import arrays
import os
import rand
import strings
import term
fn print_credits() {
term.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 V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
os.input('Press Enter to read the instructions. ')
}
fn print_instructions() {
term.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')
os.input('\nPress Enter to start. ')
}
const digits = 3
// Print the given prompt and update the array whose address is given with a
// three-digit number from the user.
//
fn get_input(prompt string, mut user_digit []int) {
asking: for {
input := os.input(prompt)
if input.len != digits {
println("Remember it's a ${digits}-digit number.")
continue asking
}
for i, digit in input {
if u8(digit).is_digit() {
user_digit[i] = int(digit) - int(`0`)
} else {
println('What?')
continue asking
}
}
if arrays.distinct[int](*user_digit).len < digits {
println('Remember my number has no two digits the same.')
continue asking
}
break
}
}
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s string) bool {
return ['ok', 'y', 'yeah', 'yes'].any(it == s.to_lower())
}
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s string) bool {
return ['n', 'no', 'nope'].any(it == s.to_lower())
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt string) bool {
mut result := false
for {
answer := os.input(prompt)
if is_yes(answer) {
result = true
break
}
if is_no(answer) {
result = false
break
}
}
return result
}
const tries = 20
const all_digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// Init and run the game loop.
//
fn play() {
mut score := 0
mut fermi := 0 // counter
mut pico := 0 // counter
mut user_number := []int{len: digits}
for {
term.clear()
mut computer_number := rand.choose(all_digits, 3) or { [1, 2, 3] }
println('O.K. I have a number in mind.')
for guess := 1; guess <= tries; guess++ {
get_input('Guess #${guess:2}: ', mut user_number)
fermi = 0
pico = 0
for i in 0 .. digits {
for j in 0 .. digits {
if user_number[i] == computer_number[j] {
if i == j {
fermi += 1
} else {
pico += 1
}
}
}
}
print(strings.repeat_string('PICO ', pico))
print(strings.repeat_string('FERMI ', 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(computer_number[i])
}
println('.')
}
if !yes('Play again? ') {
break
}
}
if score != 0 {
println('A ${score}-point bagels, buff!!')
}
println('Hope you had fun. Bye.')
}
fn main() {
print_credits()
print_instructions()
play()
}
Bug
/*
Bug
Original version in BASIC:
Brian Leibowitz, 1978.
Creative Computing (Morristown, New Jersey, USA), 1978.
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-04.
Last modified: 20250504T0045+0200.
*/
import os
import rand
import strings
import term
struct Bug {
mut:
body bool
neck bool
head bool
feelers int
feeler_type rune
tail bool
legs int
}
struct Player {
mut:
pronoun string
possessive string
bug Bug
}
enum Part {
body
neck
head
feeler
tail
leg
}
// Bug body attributes.
//
const body_height = 2
const feeler_length = 4
const leg_length = 2
const max_feelers = 2
const max_legs = 6
const neck_length = 2
// Clear the screen, display the credits and wait for a keypress.
//
fn print_credits() {
term.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 V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
os.input('Press Enter to read the instructions. ')
}
const columns = 3
const column_width = 8
const column_separation = 2
// Return the given string padded with spaces at the right up to
// the given total length.
//
fn left_justified(s string, l int) string {
return s + strings.repeat(` `, l - s.len)
}
// Print a table with the bug parts' description.
//
fn print_parts_table() {
// Headers
header := ['Number', 'Part', 'Quantity']
for i in 0 .. columns {
print(left_justified(header[i], column_width + column_separation))
}
println('')
// Rulers
ruler := strings.repeat(`-`, column_width)
for i in 0 .. columns {
print(ruler)
if i != columns - 1 {
print(strings.repeat(` `, column_separation))
}
}
println('')
// Data
part_quantity := [1, 1, 1, 2, 1, 6]
mut i := 1
$for part in Part.values {
print(left_justified('${i}', column_width + column_separation))
print(left_justified('${part.name.capitalize()}', column_width + column_separation))
println(part_quantity[part.value])
i++
}
}
// Clear the screen, print the instructions and wait for a keypress.
//
fn print_instructions() {
term.clear()
println('Bug')
println('\nThe object is to finish your bug before I finish mine. Each number')
println('stands for a part of the bug body.')
println('\nI will roll the die for you, tell you what I rolled for you, what the')
println('number stands for, and if you can get the part. If you can get the')
println('part I will give it to you. The same will happen on my turn.')
println('\nIf there is a change in either bug I will give you the option of')
println('seeing the pictures of the bugs. The numbers stand for parts as')
println('follows:\n')
print_parts_table()
os.input('\nPress Enter to start. ')
}
// Print a bug head.
//
fn print_head() {
println(' HHHHHHH')
println(' H H')
println(' H O O H')
println(' H H')
println(' H V H')
println(' HHHHHHH')
}
// Print the given bug.
//
fn print_bug(bug Bug) {
if bug.feelers > 0 {
for _ in 0 .. feeler_length {
print(' ')
for _ in 0 .. bug.feelers {
print(' ${bug.feeler_type}')
}
println('')
}
}
if bug.head {
print_head()
}
if bug.neck {
for _ in 0 .. neck_length {
println(' N N')
}
}
if bug.body {
println(' BBBBBBBBBBBB')
for _ in 0 .. body_height {
println(' B B')
}
if bug.tail {
println('TTTTTB B')
}
println(' BBBBBBBBBBBB')
}
if bug.legs > 0 {
for _ in 0 .. leg_length {
print(' ')
for _ in 0 .. bug.legs {
print(' L')
}
println('')
}
}
}
// Return `true` if the given bug is finished; otherwise return `false`.
//
fn finished(bug Bug) bool {
return bug.feelers == max_feelers && bug.tail && bug.legs == max_legs
}
// Return a random number from 1 to 6 (inclusive).
//
fn dice() int {
return rand.intn(6) or { 0 } + 1
}
// Array to convert a number to its equilavent text.
//
const as_text = ['no', 'a', 'two', 'three', 'four', 'five', 'six']
// Return a string containing the given number and noun in their proper form.
//
fn plural(number int, noun string) string {
return as_text[number] + ' ' + noun + (if number > 1 {
's'
} else {
''
})
}
// Add the given part to the given player's bug.
//
fn add_part(part Part, mut player Player) bool {
mut changed := false
match part {
.body {
if player.bug.body {
println(', but ${player.pronoun} already have a body.')
} else {
println('; ${player.pronoun} now have a body:')
player.bug.body = true
changed = true
}
}
.neck {
if player.bug.neck {
println(', but ${player.pronoun} already have a neck.')
} else if !player.bug.body {
println(', but ${player.pronoun} need a body first.')
} else {
println('; ${player.pronoun} now have a neck:')
player.bug.neck = true
changed = true
}
}
.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 {
println('; ${player.pronoun} now have a head:')
player.bug.head = true
changed = true
}
}
.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
print('; ${player.pronoun} now have ${plural(player.bug.feelers, 'feeler')}')
println(':')
changed = true
}
}
.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 {
println('; ${player.pronoun} now have a tail:')
player.bug.tail = true
changed = true
}
}
.leg {
if player.bug.legs == max_legs {
println(', but ${player.pronoun} have ${as_text[max_legs]} feet already.')
} else if !player.bug.body {
println(', but ${player.pronoun} need a body first.')
} else {
player.bug.legs += 1
print('; ${player.pronoun} now have ${plural(player.bug.legs, 'leg')}')
println(':')
changed = true
}
}
}
return changed
}
// Ask the user to press the Enter key, wait for the input, then erase the
// prompt text.
//
fn prompt() {
os.input('Press Enter to roll the dice. ')
term.clear_previous_line()
}
// Play one turn for the given player, rolling the dice and updating his bug.
//
fn turn(mut player Player) {
prompt()
number := dice()
unsafe {
part := Part(number - 1)
print('${player.pronoun.capitalize()} rolled a ${number} (${part})')
if add_part(part, mut player) {
println('')
print_bug(player.bug)
}
}
println('')
}
// Print a message about the winner.
//
fn print_winner(human Player, computer Player) {
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.
//
fn game_over(human Player, computer Player) bool {
return finished(human.bug) || finished(computer.bug)
}
// Execute the game loop.
//
fn play() {
term.clear()
mut computer := Player{
pronoun: 'you'
possessive: 'Your'
bug: Bug{
feeler_type: `A`
}
}
mut human := Player{
pronoun: 'I'
possessive: 'My'
bug: Bug{
feeler_type: `F`
}
}
for !game_over(human, computer) {
turn(mut human)
turn(mut computer)
}
print_winner(human, computer)
}
fn main() {
print_credits()
print_instructions()
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 V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03.
Last modified: 20250406T0130+0200.
*/
import os
import term
fn print_credits() {
println('Bunny\n')
println('Original version in BASIC:')
println(' Creative Computing (Morristown, New Jersey, USA), 1978.\n')
println('This version in V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
print('Press Enter to start the program. ')
os.input('')
}
const width = 53
const letter = [`B`, `U`, `N`, `N`, `Y`]
const eol = -1 // end of line identifier
// vfmt off
const data = [
1, 2, eol, 0, 2, 45, 50, eol, 0, 5, 43, 52, eol, 0, 7, 41, 52, eol, 1, 9,
37, 50, eol, 2, 11, 36, 50, eol, 3, 13, 34, 49, eol, 4, 14, 32, 48, eol, 5,
15, 31, 47, eol, 6, 16, 30, 45, eol, 7, 17, 29, 44, eol, 8, 19, 28, 43,
eol, 9, 20, 27, 41, eol, 10, 21, 26, 40, eol, 11, 22, 25, 38, eol, 12, 22,
24, 36, eol, 13, 34, eol, 14, 33, eol, 15, 31, eol, 17, 29, eol, 18, 27,
eol, 19, 26, eol, 16, 28, eol, 13, 30, eol, 11, 31, eol, 10, 32, eol, 8,
33, eol, 7, 34, eol, 6, 13, 16, 34, eol, 5, 12, 16, 35, eol, 4, 12, 16, 35,
eol, 3, 12, 15, 35, eol, 2, 35, eol, 1, 35, eol, 2, 34, eol, 3, 34, eol, 4,
33, eol, 6, 33, eol, 10, 32, 34, 34, eol, 14, 17, 19, 25, 28, 31, 35, 35,
eol, 15, 19, 23, 30, 36, 36, eol, 14, 18, 21, 21, 24, 30, 37, 37, eol, 13,
18, 23, 29, 33, 38, eol, 12, 29, 31, 33, eol, 11, 13, 17, 17, 19, 19, 22,
22, 24, 31, eol, 10, 11, 17, 18, 22, 22, 24, 24, 29, 29, eol, 22, 23, 26,
29, eol, 27, 29, eol, 28, 29, eol, ]
// vfmt on
fn clear_line(mut line []int) {
for column in 0 .. line.len {
line[column] = int(` `)
}
}
fn draw() {
mut data_index := 0
mut line := []int{len: width}
clear_line(mut line)
for data_index < data.len {
first_column := data[data_index]
data_index += 1
if first_column == eol {
for i in line {
print(rune(i))
}
println('')
clear_line(mut line)
} else {
last_column := data[data_index]
data_index += 1
for column := first_column; column <= last_column; column++ {
line[column] = int(letter[column % letter.len])
}
}
}
}
fn main() {
term.clear()
print_credits()
term.clear()
draw()
}
Chase
/*
Chase
Original version in BASIC:
Anonymous.
Published in 1977 in "The Best of Creative Computing", Volume 2.
https://www.atariarchives.org/bcc2/showpage.php?page=253
Version in Oberon-07:
Copyright (c) 2022, 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Version in Odin:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-11.
Last modified 20250122T0344+0100.
*/
import os
import rand
import term
// Console {{{1
// =============================================================
const black = 0
const red = 1
const green = 2
const yellow = 3
const blue = 4
const magenta = 5
const cyan = 6
const white = 7
const default_color = 9
const foreground = 30
const background = 40
const bright = 60
// Set the given color.
//
fn set_color(color int) {
print('\x1B[0;${color}m')
}
// Erase from the current cursor position to the end of the current line.
//
fn erase_line_right() {
print('\x1B[K')
}
// Constants {{{1
// =============================================================
// Colors
const default_ink = foreground + default_color
const input_ink = foreground + bright + green
const instructions_ink = foreground + yellow
const title_ink = foreground + bright + red
// Arena
const arena_width = 20
const arena_height = 10
const arena_last_x = arena_width - 1
const arena_last_y = arena_height - 1
const arena_row = 3
const empty = ' '
const fence = 'X'
const machine = 'm'
const human = '@'
const fences = 15 // inner obstacles, not the border
// The end
enum End {
not_yet
quit
electrified
killed
victory
}
// Machines
const machines = 5
const machines_drag = 2 // probability not moving: 0=0%, 1=50%, 2=66%, 3=75%, etc.
// Context structure {{{1
// =============================================================
struct Machine {
mut:
x int
y int
operative bool
}
struct Human {
mut:
x int
y int
}
struct Context {
mut:
arena [arena_height][arena_width]string
machine [machines]Machine
human Human
destroyed_machines int
the_end End
}
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
set_color(input_ink)
defer { set_color(default_ink) }
return os.input(prompt)
}
// Print the given prompt and wait until the user presses Enter.
//
fn press_enter(prompt string) {
input_string(prompt)
}
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s string) bool {
return ['ok', 'y', 'yeah', 'yes'].any(it == s.to_lower())
}
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s string) bool {
return ['n', 'no', 'nope'].any(it == s.to_lower())
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt string) bool {
mut result := false
for {
answer := input_string(prompt)
if is_yes(answer) {
result = true
break
}
if is_no(answer) {
result = false
break
}
}
return result
}
// Title, credits and instructions {{{1
// =============================================================
const title = 'Chase'
// Print the title at the current cursor position.
//
fn print_title() {
set_color(title_ink)
println(title)
set_color(default_ink)
}
fn print_credits() {
print_title()
println('\nOriginal version in BASIC:')
println(' Anonymous.')
println(' Published in "The Best of Creative Computing", Volume 2, 1977.')
println(' https://www.atariarchives.org/bcc2/showpage.php?page=253')
println('This version in V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
// Print the game instructions and wait for a key.
//
fn print_instructions() {
print_title()
set_color(instructions_ink)
defer { set_color(default_ink) }
println('\nYou (${human}) are in a high voltage maze with ${machines}')
println('security machines (${machine}) trying to kill you.')
println('You must maneuver them into the maze (${fence}) to survive.\n')
println('Good luck!\n')
println('The movement commands are the following:\n')
println(' ↖ ↑ ↗')
println(' NW N NE')
println(' ← W E →')
println(' SW S SE')
println(' ↙ ↓ ↘')
println("\nPlus 'Q' to end the game.")
}
// Arena {{{1
// =============================================================
// Display the arena at the top left corner of the screen.
//
fn print_arena(context Context) {
term.set_cursor_position(y: arena_row, x: 1)
for y in 0 .. arena_last_y + 1 {
for x in 0 .. arena_last_x + 1 {
print(context.arena[y][x])
}
println('')
}
}
// If the given arena coordinates `y` and `x` are part of the border arena
// (i.e. its surrounding fence), return `true`, otherwise return `false`.
//
fn is_border(y int, x int) bool {
return y == 0 || x == 0 || y == arena_last_y || x == arena_last_x
}
// Return a random integer in the given inclusive range.
//
fn random_int_in_inclusive_range(min int, max int) int {
return rand.intn(max - min + 1) or { 0 } + min
}
// Place the given string at a random empty position of the arena and return
// the coordinates.
//
fn place(s string, mut context Context) (int, int) {
mut x := 0
mut y := 0
for {
y = random_int_in_inclusive_range(1, arena_last_y - 1)
x = random_int_in_inclusive_range(1, arena_last_x - 1)
if context.arena[y][x] == empty {
break
}
}
context.arena[y][x] = s
return y, x
}
// Inhabit the arena with the machines, the inner fences and the human.
//
fn inhabit_arena(mut context Context) {
for m in 0 .. machines {
context.machine[m].y, context.machine[m].x = place(machine, mut context)
context.machine[m].operative = true
}
for _ in 0 .. fences {
place(fence, mut context)
}
context.human.y, context.human.x = place(human, mut context)
}
// Clean the arena, i.e. empty it and add a surrounding fence.
//
fn clean_arena(mut context Context) {
for y in 0 .. arena_last_y + 1 {
for x in 0 .. arena_last_x + 1 {
context.arena[y][x] = if is_border(y, x) { fence } else { empty }
}
}
}
// Game {{{1
// =============================================================
// Init the game.
//
fn init_game(mut context Context) {
clean_arena(mut context)
inhabit_arena(mut context)
context.destroyed_machines = 0
context.the_end = .not_yet
}
// Move the given machine.
//
fn move_machine(m int, mut context Context) {
context.arena[context.machine[m].y][context.machine[m].x] = empty
mut maybe := rand.intn(2) or { 0 }
if context.machine[m].y > context.human.y {
context.machine[m].y -= maybe
} else if context.machine[m].y < context.human.y {
context.machine[m].y += maybe
}
maybe = rand.intn(2) or { 0 }
if context.machine[m].x > context.human.x {
context.machine[m].x -= maybe
} else if context.machine[m].x < context.human.x {
context.machine[m].x += maybe
}
if context.arena[context.machine[m].y][context.machine[m].x] == empty {
context.arena[context.machine[m].y][context.machine[m].x] = machine
} else if context.arena[context.machine[m].y][context.machine[m].x] == fence {
context.machine[m].operative = false
context.destroyed_machines += 1
if context.destroyed_machines == machines {
context.the_end = .victory
}
} else if context.arena[context.machine[m].y][context.machine[m].x] == human {
context.the_end = .killed
}
}
// Maybe move the given operative machine.
//
fn maybe_move_machine(m int, mut context Context) {
if rand.intn(machines_drag) or { 0 } == 0 {
move_machine(m, mut context)
}
}
// Move the operative machines.
//
fn move_machines(mut context Context) {
for m in 0 .. machines {
if context.machine[m].operative {
maybe_move_machine(m, mut context)
}
}
}
// Read a user command; update `context.the_end` accordingly and return the direction
// increments.
//
fn get_move(mut context Context) (int, int) {
mut y_inc := 0
mut x_inc := 0
println('')
erase_line_right()
command := input_string('Command: ').to_lower()
match command {
'q' {
context.the_end = .quit
}
'sw' {
y_inc = 1
x_inc = -1
}
's' {
y_inc = 1
}
'se' {
y_inc = 1
x_inc = 1
}
'w' {
x_inc = -1
}
'e' {
x_inc = 1
}
'nw' {
y_inc = -1
x_inc = -1
}
'n' {
y_inc = -1
}
'ne' {
y_inc = -1
x_inc = 1
}
else {}
}
return y_inc, x_inc
}
fn play() {
mut context := Context{}
mut y_inc := 0
mut x_inc := 0
for { // game loop
term.clear()
print_title()
init_game(mut context)
for { // action loop
print_arena(context)
y_inc, x_inc = get_move(mut context)
if context.the_end == .not_yet {
if y_inc != 0 || x_inc != 0 {
context.arena[context.human.y][context.human.x] = empty
if context.arena[context.human.y + y_inc][context.human.x + x_inc] == fence {
context.the_end = .electrified
} else if context.arena[context.human.y + y_inc][context.human.x + x_inc] == machine {
context.the_end = .killed
} else {
context.arena[context.human.y][context.human.x] = empty
context.human.y = context.human.y + y_inc
context.human.x = context.human.x + x_inc
context.arena[context.human.y][context.human.x] = human
print_arena(context)
move_machines(mut context)
}
}
}
if context.the_end != .not_yet {
break
}
} // action loop
match context.the_end {
.quit {
println('\nSorry to see you quit.')
}
.electrified {
println('\nZap! You touched the fence!')
}
.killed {
println('\nYou have been killed by a lucky machine.')
}
.victory {
println('\nYou are lucky, you destroyed all machines.')
}
else {}
}
if !yes('\nDo you want to play again? ') {
break
}
} // game loop
println("\nHope you don't feel fenced in.")
println('Try again sometime.')
}
// Main {{{1
// =============================================================
fn main() {
set_color(default_ink)
term.clear()
print_credits()
press_enter('\nPress the Enter key to read the instructions. ')
term.clear()
print_instructions()
press_enter('\nPress the Enter key to start. ')
play()
}
Diamond
/*
Diamond
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03.
Last modified: 20250103T0147+0100.
*/
const lines = 17
fn main() {
for i in 1 .. lines / 2 + 2 {
for _ in 0 .. (lines + 1) / 2 - i + 1 {
print(' ')
}
for _ in 1 .. i * 2 {
print('*')
}
println('')
}
for i in 1 .. lines / 2 + 1 {
for _ in 0 .. i + 1 {
print(' ')
}
for _ in 1 .. ((lines + 1) / 2 - i) * 2 {
print('*')
}
println('')
}
}
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/
This improved remake in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-10.
Last modified: 20250122T0345+0100.
Acknowledgment:
The following Python port was used as a reference of the original
variables: <https://github.com/jquast/hamurabi.py>.
*/
import os
import rand
import strconv
import term
// Console {{{1
// =============================================================
const black = 0
const red = 1
const green = 2
const yellow = 3
const blue = 4
const magenta = 5
const cyan = 6
const white = 7
const default_color = 9
const foreground = 30
const background = 40
const bright = 60
// Set the given color.
//
fn set_color(color int) {
print('\x1B[0;${color}m')
}
// Globals {{{1
// =============================================================
const acres_a_bushel_can_seed = 2 // yearly
const acres_a_person_can_seed = 10 // yearly
const initial_acres_per_person = 10
const bushels_to_feed_a_person = 20 // yearly
const irritation_levels = 5 // after the switch in `show_irritation`
const irritation_step = max_irritation / irritation_levels
const max_harvested_bushels_per_acre = min_harvested_bushels_per_acre +
range_of_harvested_bushels_per_acre - 1
const min_harvested_bushels_per_acre = 17
const max_irritation = 16
const plague_chance = 0.15 // 15% yearly
const range_of_harvested_bushels_per_acre = 10
const years = 10 // goverment period
const default_ink = foreground + default_color
const input_ink = foreground + bright + green
const instructions_ink = foreground + yellow
const result_ink = foreground + bright + cyan
const speech_ink = foreground + bright + magenta
const title_ink = foreground + red
const warning_ink = foreground + bright + red
enum Result {
very_good
not_too_bad
bad
very_bad
}
struct Status {
mut:
acres int
bushels_eaten_by_rats int
bushels_harvested int
bushels_harvested_per_acre int
bushels_in_store int
bushels_to_feed_with int
dead int
infants int
irritation int // counter (0 ... 99)
population int
starved_people_percentage int
total_dead int
}
// Return the proper wording for `n` persons, using the given or default words
// for singular and plural forms.
//
fn persons(n int, singular string, plural string) string {
match n {
0 {
return 'nobody'
}
1 {
return unsafe { strconv.v_sprintf('one ${singular}') }
}
else {
return unsafe { strconv.v_sprintf('${n} ${plural}') }
}
}
}
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
set_color(input_ink)
defer { set_color(default_ink) }
return os.input(prompt)
}
fn pause(prompt string) {
input_string(prompt)
}
// Print the given prompt, 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.
//
fn input_number(prompt string) (int, bool) {
set_color(input_ink)
defer { set_color(default_ink) }
mut n := 0
for {
n = strconv.atoi(input_string(prompt)) or { return n, false }
break
}
return n, true
}
// Title, credits and instructions {{{1
// =============================================================
fn print_title() {
set_color(title_ink)
defer { set_color(default_ink) }
println('Hammurabi')
}
fn print_credits() {
print_title()
println('\nOriginal program:')
println(' Written in FOCAL on a DEP PDP-8 by Rick Merrill, 1969.')
println('\nBASIC port:')
println(' Ported from FOCAL and modified for Edusystem 70 by David Ahl, c. 1973.')
println(' Modified for 8K Microsoft BASIC by Peter Turnbull, c. 1978.')
println('\nThis improved remake in V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
fn print_instructions() {
set_color(instructions_ink)
defer { set_color(default_ink) }
println('Hammurabi is a simulation game in which you, as the ruler of the ancient')
println('kingdom of Sumeria, Hammurabi, manage the resources.')
println('\nYou may buy and sell land with your neighboring city-states for bushels of')
println('grain ― the price will vary between ${min_harvested_bushels_per_acre} and ${max_harvested_bushels_per_acre} bushels per acre. You also must')
println("use grain to feed your people and as seed to plant the next year's crop.")
println('\nYou will quickly find that a certain number of people can only tend a certain')
println('amount of land and that people starve if they are not fed enough. You also')
println('have the unexpected to contend with such as a plague, rats destroying stored')
println('grain, and variable harvests.')
println('\nYou will also find that managing just the few resources in this game is not a')
println('trivial job. The crisis of population density rears its head very rapidly.')
println('\nTry your hand at governing ancient Sumeria for a ${years}-year term of office.')
}
// Stock functions {{{1
// =============================================================
// Return a random number from 1 to 5 (inclusive).
//
fn random_1_to_5() int {
return rand.intn(5) or { 0 } + 1
}
fn ordinal_suffix(n int) string {
match n {
1 {
return 'st'
}
2 {
return 'nd'
}
3 {
return 'rd'
}
else {
return 'th'
}
}
}
fn is_even(n int) bool {
return n % 2 == 0
}
// Game {{{1
// =============================================================
// Return the description of the given year as the previous one.
//
fn previous(year int) string {
if year == 0 {
return 'the previous year'
} else {
return unsafe { strconv.v_sprintf('your ${year}${ordinal_suffix(year)} year') }
}
}
fn print_annual_report(year int, mut status Status) {
term.clear()
set_color(speech_ink)
println('Hammurabi, I beg to report to you.')
set_color(default_ink)
print('\nIn ${previous(year)}, ${persons(status.dead, 'person', 'people')} starved and ${persons(status.infants,
'infant', 'infants')} ${if status.infants > 1 { 'were' } else { 'was' }} born.\n')
status.population += status.infants
if year > 0 && rand.f64() <= plague_chance {
status.population = int(status.population / 2)
set_color(warning_ink)
println('A horrible plague struck! Half the people died.')
set_color(default_ink)
}
println('The population is ${status.population}.')
println('The city owns ${status.acres} acres.')
print('You harvested ${status.bushels_harvested} bushels (${status.bushels_harvested_per_acre} per acre).\n')
if status.bushels_eaten_by_rats > 0 {
println('The rats ate ${status.bushels_eaten_by_rats} bushels.')
}
println('You have ${status.bushels_in_store} bushels in store.')
status.bushels_harvested_per_acre = int(range_of_harvested_bushels_per_acre * rand.f64()) +
min_harvested_bushels_per_acre
println('Land is trading at ${status.bushels_harvested_per_acre} bushels per acre.\n')
}
fn say_bye() {
set_color(default_ink)
println('\nSo long for now.\n')
}
fn quit_game() {
say_bye()
exit(0)
}
fn relinquish() {
set_color(speech_ink)
println('\nHammurabi, I am deeply irritated and cannot serve you anymore.')
println('Please, get yourself another steward!')
set_color(default_ink)
quit_game()
}
fn increase_irritation(mut status Status) {
status.irritation += 1 + rand.intn(irritation_step) or { 0 }
if status.irritation >= max_irritation {
relinquish()
}
// this never returns
}
fn print_irritated(adverb string) {
println('The steward seems ${adverb} irritated.')
}
fn show_irritation(status Status) {
match true {
status.irritation < irritation_step {}
status.irritation < irritation_step * 2 {
print_irritated('slightly')
}
status.irritation < irritation_step * 3 {
print_irritated('quite')
}
status.irritation < irritation_step * 4 {
print_irritated('very')
}
else {
print_irritated('profoundly')
}
}
}
// Print a message begging to repeat an ununderstandable input.
//
fn beg_repeat(mut status Status) {
increase_irritation(mut status) // this may never return
set_color(speech_ink)
println('I beg your pardon? I did not understand your order.')
set_color(default_ink)
show_irritation(status)
}
// Print a message begging to repeat a wrong input, because there's only `n`
// items of `name`.
//
fn beg_think_again(n int, name string, mut status Status) {
increase_irritation(mut status) // this may never return
set_color(speech_ink)
println('I beg your pardon? You have only ${n} ${name}. Now then…')
set_color(default_ink)
show_irritation(status)
}
// Buy or sell land.
//
fn trade(mut status Status) {
mut acres_to_buy := 0
mut acres_to_sell := 0
mut ok := false
for {
acres_to_buy, ok = input_number('How many acres do you wish to buy? (0 to sell): ')
if !ok || acres_to_buy < 0 {
beg_repeat(mut status) // this may never return
continue
}
if status.bushels_harvested_per_acre * acres_to_buy <= status.bushels_in_store {
break
}
beg_think_again(status.bushels_in_store, 'bushels of grain', mut status)
}
if acres_to_buy != 0 {
println('You buy ${acres_to_buy} acres.')
status.acres += acres_to_buy
status.bushels_in_store -= status.bushels_harvested_per_acre * acres_to_buy
println('You now have ${status.acres} acres and ${status.bushels_in_store} bushels.')
} else {
for {
acres_to_sell, ok = input_number('How many acres do you wish to sell?: ')
if !ok || acres_to_sell < 0 {
beg_repeat(mut status) // this may never return
continue
}
if acres_to_sell < status.acres {
break
}
beg_think_again(status.acres, 'acres', mut status)
}
if acres_to_sell > 0 {
println('You sell ${acres_to_sell} acres.')
status.acres -= acres_to_sell
status.bushels_in_store += status.bushels_harvested_per_acre * acres_to_sell
println('You now have ${status.acres} acres and ${status.bushels_in_store} bushels.')
}
}
}
// Feed the people.
//
fn feed(mut status Status) {
mut ok := false
for {
status.bushels_to_feed_with, ok = input_number('How many bushels do you wish to feed your people with?: ')
if !ok || status.bushels_to_feed_with < 0 {
beg_repeat(mut status) // this may never return
continue
}
// Trying to use more grain than is in silos?
if status.bushels_to_feed_with <= status.bushels_in_store {
break
}
beg_think_again(status.bushels_in_store, 'bushels of grain', mut status)
}
println('You feed your people with ${status.bushels_to_feed_with} bushels.')
status.bushels_in_store -= status.bushels_to_feed_with
println('You now have ${status.bushels_in_store} bushels.')
}
// Seed the land.
//
fn seed(mut status Status) {
mut acres_to_seed := 0
mut ok := false
for {
acres_to_seed, ok = input_number('How many acres do you wish to seed?: ')
if !ok || acres_to_seed < 0 {
beg_repeat(mut status) // this may never return
continue
}
if acres_to_seed == 0 {
break
}
// Trying to seed more acres than you own?
if acres_to_seed > status.acres {
beg_think_again(status.acres, 'acres', mut status)
continue
}
// Enough grain for seed?
if int(acres_to_seed / acres_a_bushel_can_seed) > status.bushels_in_store {
beg_think_again(status.bushels_in_store, unsafe { strconv.v_sprintf('bushels of grain,\nand one bushel can seed ${acres_a_bushel_can_seed} acres') }, mut
status)
continue
}
// Enough people to tend the crops?
if acres_to_seed <= acres_a_person_can_seed * status.population {
break
}
beg_think_again(status.population, unsafe { strconv.v_sprintf('people to tend the fields,\nand one person can seed ${acres_a_person_can_seed} acres') }, mut
status)
}
bushels_used_for_seeding := int(acres_to_seed / acres_a_bushel_can_seed)
println('You seed ${acres_to_seed} acres using ${bushels_used_for_seeding} bushels.')
status.bushels_in_store -= bushels_used_for_seeding
println('You now have ${status.bushels_in_store} bushels.')
// A bountiful harvest!
status.bushels_harvested_per_acre = random_1_to_5()
status.bushels_harvested = acres_to_seed * status.bushels_harvested_per_acre
status.bushels_in_store += status.bushels_harvested
}
fn check_rats(mut status Status) {
rat_chance := random_1_to_5()
status.bushels_eaten_by_rats = if is_even(rat_chance) {
int(status.bushels_in_store / rat_chance)
} else {
0
}
status.bushels_in_store -= status.bushels_eaten_by_rats
}
fn print_result(result Result, status Status) {
set_color(result_ink)
match result {
.very_good {
println('A fantastic performance! Charlemagne, Disraeli and Jefferson combined could')
println('not have done better!')
}
.not_too_bad {
println("Your performance could have been somewat better, but really wasn't too bad at")
print('all. ${int(f64(status.population) * .8 * rand.f64())} people would dearly like to see you assassinated, but we all have our\n')
println('trivial problems.')
}
.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!')
}
.very_bad {
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!!!')
}
}
set_color(default_ink)
}
fn print_final_report(status Status) {
term.clear()
if status.starved_people_percentage > 0 {
print('In your ${years}-year term of office, ${status.starved_people_percentage} percent of the\n')
print('population starved per year on the average, i.e., a total of ${status.total_dead} people died!\n\n')
}
acres_per_person := status.acres / status.population
print('You started with ${acres_per_person} acres per person and ended with ${acres_per_person}.\n\n')
match true {
status.starved_people_percentage > 33, acres_per_person < 7 {
print_result(.very_bad, status)
}
status.starved_people_percentage > 10, acres_per_person < 9 {
print_result(.bad, status)
}
status.starved_people_percentage > 3, acres_per_person < 10 {
print_result(.not_too_bad, status)
}
else {
print_result(.very_good, status)
}
}
}
fn check_starvation(year int, mut status Status) {
// How many people has been fed?
fed_people := int(status.bushels_to_feed_with / bushels_to_feed_a_person)
if status.population > fed_people {
status.dead = status.population - fed_people
status.starved_people_percentage = ((year - 1) * status.starved_people_percentage +
status.dead * 100 / status.population) / year
status.population -= status.dead
status.total_dead += status.dead
// Starve enough for impeachment?
if status.dead > int(.45 * f64(status.population)) {
set_color(warning_ink)
println('\nYou starved ${status.dead} people in one year!!!\n')
set_color(default_ink)
print_result(.very_bad, status)
quit_game()
}
}
}
fn govern() {
mut status := Status{
dead: 0
total_dead: 0
starved_people_percentage: 0
population: 95
infants: 5
bushels_harvested_per_acre: 3
bushels_eaten_by_rats: 200
irritation: 0
}
status.acres = initial_acres_per_person * (status.population + status.infants)
status.bushels_harvested = status.acres * status.bushels_harvested_per_acre
status.bushels_in_store = status.bushels_harvested - status.bushels_eaten_by_rats
print_annual_report(0, mut status)
for year in 1 .. years + 1 {
trade(mut status)
feed(mut status)
seed(mut status)
check_rats(mut status)
// Let's have some babies
status.infants = int(random_1_to_5() * (20 * status.acres +
status.bushels_in_store) / status.population / 100 + 1)
check_starvation(year, mut status)
pause('\nPress the Enter key to read the annual report. ')
print_annual_report(year, mut status)
}
pause('Press the Enter key to read the final report. ')
print_final_report(status)
}
// Main {{{1
// =============================================================
fn main() {
term.clear()
print_credits()
pause('\nPress the Enter key to read the instructions. ')
term.clear()
print_instructions()
pause('\nPress the Enter key to start. ')
govern()
say_bye()
}
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/
This improved remake in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2025-01-05/07.
Last modified: 20250714T1332+0200.
*/
import os
import rand
import strconv
import term
// Constants {{{1
// =============================================================
const default_ink = term.white
const input_ink = term.bright_green
const instructions_ink = term.yellow
const title_ink = term.bright_red
const initial_distance = 100
const initial_bullets = 4
const max_watering_troughs = 3
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
return os.input(term.colorize(input_ink, prompt))
}
// Print the given prompt and wait until the user enters an integer.
//
fn input_int(prompt string) int {
mut n := 0
for {
n = strconv.atoi(input_string(prompt)) or { continue }
break
}
return n
}
// Print the given prompt and wait until the user presses Enter.
//
fn press_enter(prompt string) {
input_string(prompt)
}
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s string) bool {
return ['ok', 'y', 'yeah', 'yes'].any(it == s.to_lower())
}
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s string) bool {
return ['n', 'no', 'nope'].any(it == s.to_lower())
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt string) bool {
mut result := false
for {
answer := input_string(prompt)
if is_yes(answer) {
result = true
break
}
if is_no(answer) {
result = false
break
}
}
return result
}
// Title, instructions and credits {{{1
// =============================================================
// Print the title at the current cursor position.
//
fn print_title() {
println(term.colorize(title_ink, 'High Noon'))
}
fn print_credits() {
print_title()
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 V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
fn print_instructions() {
print_title()
instructions := '\nYou have been challenged to a showdown by Black Bart, one of\n' +
'the meanest desperadoes west of the Allegheny mountains.\n' +
'\nWhile you are walking down a dusty, deserted side street,\n' +
'Black Bart emerges from a saloon one hundred paces away.\n' +
'\nBy agreement, you each have ${initial_bullets} bullets in your six-guns.\n' +
'\nYour marksmanship equals his. At the start of the walk nei-\n' +
'ther of you can possibly hit the other, and at the end of\n' +
'the walk, neither can miss. the closer you get, the better\n' +
'your chances of hitting black Bart, but he also has beter\n' + 'chances of hitting you.\n'
println(term.colorize(instructions_ink, instructions))
}
// Game {{{1
// =============================================================
struct Showdown {
mut:
player_bullets int
opponent_bullets int
distance int // distance between both gunners, in paces
strategy string
}
fn plural_suffix(n int) string {
return if n == 1 { '' } else { 's' }
}
fn print_shells_left(showdown Showdown) {
if showdown.player_bullets == showdown.opponent_bullets {
println('Both of you have ${showdown.player_bullets} bullets.')
} else {
print("You now have ${showdown.player_bullets} bullet${plural_suffix(showdown.player_bullets)} to Black Bart's ${showdown.opponent_bullets} bullet${plural_suffix(showdown.opponent_bullets)}.\n")
}
}
fn print_check() {
check := rand.intn(1000) or { 0 }
day := 10 + rand.intn(10) or { 0 }
println('******************************************************')
println('* *')
println('* BANK OF DODGE CITY *')
println("* CASHIER'S RECEIT *")
println('* *')
println('* CHECK NO. ${check:04} 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('******************************************************')
}
fn get_reward() {
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')
print_check()
println("\n\nDon't spend it all in one place.")
}
fn move_the_opponent(mut showdown Showdown) {
paces := 2 + rand.intn(8) or { 0 }
println('Black Bart moves ${paces} paces.')
showdown.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.
//
fn the_opponent_moves(silent bool, mut showdown Showdown) bool {
if rand.intn(2) or { 0 } == 0 { // 50% chances
move_the_opponent(mut showdown)
return true
} else {
if !silent {
println('Black Bart stands still.')
}
return false
}
}
fn missed_shot(showdown Showdown) bool {
return rand.f64() * 10 <= f64(showdown.distance / 10)
}
// Handle the opponent's shot and return a flag with the result: if the
// opponent kills the player, return `true`; otherwise return `false`.
//
fn the_opponent_fires_and_kills(mut showdown Showdown) bool {
println('Black Bart fires…')
showdown.opponent_bullets -= 1
if missed_shot(showdown) {
println('A miss…')
match showdown.opponent_bullets {
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 {}
}
} else {
if showdown.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`.
//
fn the_opponent_kills_or_runs(mut showdown Showdown) bool {
if showdown.distance >= 10 || showdown.player_bullets == 0 {
if the_opponent_moves(true, mut showdown) {
return false
}
}
if showdown.opponent_bullets > 0 {
return the_opponent_fires_and_kills(mut showdown)
} else {
if showdown.player_bullets > 0 {
if rand.intn(2) or { 0 } == 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
}
fn play() {
mut showdown := Showdown{
distance: initial_distance
player_bullets: initial_bullets
opponent_bullets: initial_bullets
}
mut watering_troughs := 0
showdown: for {
println('You are now ${showdown.distance} paces apart from Black Bart.')
print_shells_left(showdown)
println(term.colorize(instructions_ink, '\nStrategies:'))
menu := ['[A]dvance', '[S]tand still', '[F]ire', '[J]ump behind the watering trough',
'[G]ive up', '[T]urn tail and run']
for item in menu {
println(term.colorize(instructions_ink, ' ' + item))
}
showdown.strategy = input_string('What is your strategy? ').to_lower()
match showdown.strategy {
'a' { // advance
for {
paces := input_int('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 {
showdown.distance -= paces
break
}
}
}
's' { // stand still
println('That move made you a perfect stationary target.')
}
'f' { // fire
if showdown.player_bullets == 0 {
println("You don't have any bullets left.")
} else {
showdown.player_bullets -= 1
if missed_shot(showdown) {
match showdown.player_bullets {
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.')
}
else {}
}
println('What a lousy shot.')
if showdown.player_bullets == 0 {
println("Nice going, ace, you've run out of bullets.")
if showdown.opponent_bullets != 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.')
press_enter('\nPress the Enter key to get your reward. ')
term.clear()
get_reward()
break showdown
}
}
}
'j' { // jump
if watering_troughs == max_watering_troughs {
println('How many watering troughs do you think are on this street?')
showdown.strategy = ''
} else {
watering_troughs += 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 rand.intn(showdown.opponent_bullets + 2) or { 0 } == 0 {
println("Man, you ran so fast even dogs couldn't catch you.")
} else {
match showdown.opponent_bullets {
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.")
}
else {}
}
showdown.opponent_bullets = 0
}
break showdown
}
else {
println("You sure aren't going to live very long if you can't even follow directions.")
}
} // strategy switch
if the_opponent_kills_or_runs(mut showdown) {
break
}
if showdown.player_bullets + showdown.opponent_bullets == 0 {
println('The showdown must end, because nobody has bullets left.')
break
}
println('')
} // showdown loop
}
// Main {{{1
// =============================================================
fn main() {
term.clear()
print_credits()
press_enter('\nPress the Enter key to read the instructions. ')
term.clear()
print_instructions()
press_enter('\nPress the Enter key to start. ')
term.clear()
play()
}
Math
/*
Math
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03.
Last modified: 20250103T1730+0100.
*/
import math
import os
import strconv
fn input_float(prompt string) f64 {
mut n := 0.0
for {
n = strconv.atof64(os.input(prompt)) or { continue }
break
}
return n
}
fn main() {
n := input_float('Enter a number: ')
println('ABS(${n}) -> math.abs(${n}) -> ${math.abs(n)}')
println('ATN(${n}) -> math.atan(${n}) -> ${math.atan(n)}')
println('COS(${n}) -> math.cos(${n}) -> ${math.cos(n)}')
println('EXP(${n}) -> math.exp(${n}) -> ${math.exp(n)}')
println('INT(${n}) -> int(${n}) -> ${int(n)}')
println('LOG(${n}) -> math.log(${n}) -> ${math.log(n)}')
println('SGN(${n}) -> math.sign(${n}) -> ${math.sign(n)}')
println('SQR(${n}) -> math.sqrt(${n}) -> ${math.sqrt(n)}')
println('TAN(${n}) -> math.tan(${n}) -> ${math.tan(n)}')
}
Mugwump
/*
Mugwump
Original version in BASIC:
Written by Bud Valenti's students of Project SOLO (Pittsburg, Pennsylvania, USA).
Slightly modified by Bob Albrecht of People's Computer Company.
Published by Creative Computing (Morristown, New Jersey, USA), 1978.
- https://www.atariarchives.org/basicgames/showpage.php?page=114
- http://vintage-basic.net/games.html
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-07
Last modified: 20250406T0126+0200.
*/
import math
import os
import rand
import strconv
import term
const grid_size = 10
const turns = 10
const mugwumps = 4
struct Mugwump {
mut:
x int
y int
hidden bool
}
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
return os.input(prompt)
}
// Print the given prompt and wait until the user enters an integer.
//
fn input_int(prompt string) int {
mut n := 0
for {
n = strconv.atoi(input_string(prompt)) or { continue }
break
}
return n
}
// Print the given prompt and wait until the user enters an empty string.
//
fn press_enter(prompt string) {
for input_string(prompt).len != 0 {}
}
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s string) bool {
return ['ok', 'y', 'yeah', 'yes'].any(it == s.to_lower())
}
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s string) bool {
return ['n', 'no', 'nope'].any(it == s.to_lower())
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt string) bool {
mut result := false
for {
answer := input_string(prompt)
if is_yes(answer) {
result = true
break
}
if is_no(answer) {
result = false
break
}
}
return result
}
// Clear the screen, print the credits and ask the user to press enter.
//
fn print_credits() {
term.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 V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
press_enter('Press Enter to read the instructions. ')
}
// Clear the screen, print the instructions and ask the user to press enter.
//
fn print_instructions() {
term.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')
press_enter('Press Enter to start. ')
}
// Init the mugwumps' positions, `hidden` flags and count.
//
fn hide_mugwumps(mut mugwump [mugwumps]Mugwump) {
for m in 0 .. mugwumps {
mugwump[m].x = rand.intn(grid_size) or { 0 }
mugwump[m].y = rand.intn(grid_size) or { 0 }
mugwump[m].hidden = true
}
}
// Print the given prompt, wait until the user enters a valid coord and return
// it.
//
fn get_coord(prompt string) int {
mut coord := 0
for {
coord = input_int(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.
//
fn is_here(m Mugwump, x int, y int) bool {
return m.hidden && m.x == x && m.y == y
}
// Return the distance between the given mugwump and the given coords.
//
fn distance(m Mugwump, x int, y int) int {
return int(math.sqrt(math.pow(f64(m.x - x), 2) + math.pow(f64(m.y - y), 2)))
}
// Return a plural ending (default: "s") if the given number is greater than 1;
// otherwise return a singular ending (default: an empty string).
//
fn plural_(n int, plural string, singular string) string {
return if n > 1 { plural } else { singular }
}
// Return a plural "s" ending if the given number is greater than 1;
// otherwise return an empty string.
//
fn plural(n int) string {
return plural_(n, 's', '')
}
// Run the game.
//
fn play() {
mut mugwump := [mugwumps]Mugwump{}
mut found := 0 // counter
mut x := 0
mut y := 0
mut turn := 0 // counter
for { // game
term.clear()
hide_mugwumps(mut mugwump) // XXX FIXME
found = 0
turns_loop: for turn = 1; turn <= turns; turn += 1 {
println('Turn number ${turn}\n')
println('What is your guess (in range [0, ${grid_size - 1}])?')
x = get_coord('Distance right of homebase (x-axis): ')
y = get_coord('Distance above homebase (y-axis): ')
println('\nYour guess is (${x}, ${y}).')
for m in 0 .. mugwumps {
if is_here(mugwump[m], x, y) {
mugwump[m].hidden = false
found += 1
println('You have found mugwump ${m}!')
if found == mugwumps {
break turns_loop
}
}
}
for m in 0 .. mugwumps {
if mugwump[m].hidden {
println('You are ${distance(mugwump[m], x, y)} units from mugwump ${m}.')
}
}
println('')
} // turns
if found == mugwumps {
println('\nYou got them all in ${turn} turn${plural(turn)}!\n')
println("That was fun! let's play again…")
println('Four more mugwumps are now in hiding.')
} else {
println("\nSorry, that's ${turns} tr${plural_(turns, 'ies', 'y')}.\n")
println("Here is where they're hiding:")
for m in 0 .. mugwumps {
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
}
fn main() {
print_credits()
print_instructions()
play()
}
Name
/*
Name
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03.
Last modified: 20250103T2059+0100.
*/
import os
import strconv
fn input_int(prompt string) int {
mut n := 0
for {
n = strconv.atoi(os.input(prompt)) or { continue }
break
}
return n
}
fn main() {
name := os.input('What is your name? ')
number := input_int('Enter a number: ')
for _ in 0 .. number {
println('Hello, ${name}!')
}
}
Poetry
/*
Poetry
Original version in BASIC:
Unknown author.
Modified and reworked by Jim Bailey, Peggy Ewing, and Dave Ahl at DEC.
Published in "BASIC Computer Games", Creative Computing (Morristown, New Jersey, USA), 1978.
https://archive.org/details/Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
https://github.com/chaosotter/basic-games/tree/master/games/BASIC%20Computer%20Games/Poetry
http://vintage-basic.net/games.html
This improved remake in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-04.
Last modified: 20250104T0103+0100.
*/
import os
import rand
import term
import time
// Globals {{{1
// =============================================================
const max_phrases_and_verses = 20
const input_ink = term.bright_green
const title_ink = term.bright_red
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
return os.input(term.colorize(input_ink, prompt))
}
// Print the given prompt and wait until the user presses Enter.
//
fn press_enter(prompt string) {
input_string(prompt)
}
// Title and credits {{{1
// =============================================================
// Print the credits at the current cursor position.
//
fn print_credits() {
println(term.colorize(title_ink, 'Poetry'))
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 V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
// Main {{{1
// =============================================================
// Is the given integer even?
//
fn is_even(n int) bool {
return n % 2 == 0
}
fn play() {
// counters:
mut action := 0
mut phrase := 0
mut phrases_and_verses := 0
mut verse_chunks := 0
verse: for {
mut manage_the_verse_continuation := true
mut maybe_add_comma := true
match action {
0, 1 {
match phrase {
0 { print('MIDNIGHT DREARY') }
1 { print('FIERY EYES') }
2 { print('BIRD OR FIEND') }
3 { print('THING OF EVIL') }
4 { print('PROPHET') }
else {}
}
}
2 {
match phrase {
0 {
print('BEGUILING ME')
verse_chunks = 2
}
1 {
print('THRILLED ME')
}
2 {
print('STILL SITTING…')
maybe_add_comma = false
}
3 {
print('NEVER FLITTING')
verse_chunks = 2
}
4 {
print('BURNED')
}
else {}
}
}
3 {
match phrase {
0 {
print('AND MY SOUL')
}
1 {
print('DARKNESS THERE')
}
2 {
print('SHALL BE LIFTED')
}
3 {
print('QUOTH THE RAVEN')
}
4 {
if verse_chunks != 0 {
print('SIGN OF PARTING')
}
}
else {}
}
}
4 {
match phrase {
0 { print('NOTHING MORE') }
1 { print('YET AGAIN') }
2 { print('SLOWLY CREEPING') }
3 { print('…EVERMORE') }
4 { print('NEVERMORE') }
else {}
}
}
5 {
action = 0
println('')
if phrases_and_verses > max_phrases_and_verses {
println('')
verse_chunks = 0
phrases_and_verses = 0
action = 2
continue verse
} else {
manage_the_verse_continuation = false
}
}
else {}
}
if manage_the_verse_continuation {
time.sleep(250_000_000) // 250 ms
if maybe_add_comma && !(verse_chunks == 0 || rand.f64() > 0.19) {
print(',')
verse_chunks = 2
}
if rand.f64() > 0.65 {
println('')
verse_chunks = 0
} else {
print(' ')
verse_chunks += 1
}
}
action += 1
phrase = rand.intn(5) or { 0 }
phrases_and_verses += 1
if !(verse_chunks > 0 || is_even(action)) {
print(' ')
}
} // verse loop
}
fn main() {
term.clear()
print_credits()
press_enter('\nPress the Enter key to start. ')
term.clear()
play()
}
Russian Roulette
/*
Russian Roulette
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in V:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Last modified 20231103T2127+0100.
*/
import rand
import readline
import term
fn any_key_to_start() {
println('\nPress any key to start.')
term.utf8_getchar() or { ` ` }
}
fn credits() {
term.clear()
println('Russian Roulette\n')
println('Original version in BASIC:')
println(' Creative computing (Morristown, New Jersey, USA), ca. 1980.\n')
println('This version in V:')
println(' Copyright (c) 2023, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
any_key_to_start()
}
fn instructions() {
term.clear()
println('Here is a revolver.')
println("Press 'f' to spin chamber and pull trigger.")
println("Press 'g' to give up, and play again.")
println("Press 'q' to quit.\n")
}
fn play() bool {
mut times := 0
for {
instructions()
times = 0
for {
match term.utf8_getchar() or { ` ` } {
`f` {
// fire
if (rand.intn(100) or { 0 }) > 83 {
println("Bang! You're dead!")
println('Condolences will be sent to your relatives.')
break
} else {
times += 1
if times > 10 {
println('You win!')
println('Let someone else blow his brains out.')
break
} else {
println('Click.')
}
}
}
`g` {
// give up
println('Chicken!')
break
}
`q` {
// quit
return false
}
else {} // any other option: do nothing
} // match options
} // turn loop
any_key_to_start()
} // game loop
return true // play again, do not quit
}
fn main() {
mut rl := readline.Readline{}
rl.enable_raw_mode_nosig()
credits()
for play() {}
println('Bye!')
rl.disable_raw_mode()
}
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 V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-08.
Last modified: 20250122T0343+0100.
*/
import os
import rand
import strconv
import term
import time
// Console {{{1
// =============================================================
const black = 0
const red = 1
const green = 2
const yellow = 3
const blue = 4
const magenta = 5
const cyan = 6
const white = 7
const default_color = 9
const foreground = 30
const background = 40
const bright = 60
// Set the given color.
//
fn set_color(color int) {
print('\x1B[0;${color}m')
}
// Erase the given line to the right of the given column.
//
fn erase_line_toend_from(y int, x int) {
term.set_cursor_position(y: y, x: x)
term.erase_line_toend()
}
// Global constants {{{1
// =============================================================
const title = 'Seance'
const max_score = 50
const max_message_length = 6
const min_message_length = 3
const base_character = int(`@`)
const planchette = `*`
const first_letter_number = 1
const last_letter_number = 26
const board_ink = bright + cyan + foreground
const default_ink = default_color + foreground
const input_ink = bright + green + foreground
const instructions_ink = yellow + foreground
const mistake_effect_ink = bright + red + foreground
const planchette_ink = yellow + foreground
const title_ink = bright + red + foreground
const input_x = board_x
const input_y = board_y + board_bottom_y + 4
const messages_y = input_y
const mistake_effect_pause = 3 // seconds
const board_actual_width = board_width + 2 * board_pad // screen columns
const board_bottom_y = board_height + 1 // relative to the board
const board_height = 5 // characters displayed on the left and right borders
const board_pad = 1 // blank characters separating the board from its left and right borders
const board_width = 8 // characters displayed on the top and bottom borders
const board_x = 29 // screen column
const board_y = 5 // screen line
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input(prompt string) string {
set_color(input_ink)
defer { set_color(default_ink) }
return os.input(prompt)
}
// Print the given prompt and wait until the user presses Enter.
//
fn press_enter(prompt string) {
input(prompt)
}
// Return the x coordinate to print the given text centered on the board.
//
fn board_centered_x(text string) int {
return board_x + (board_actual_width - text.len) / 2
}
// Print the given text on the given row, centered on the board.
//
fn print_board_centered(text string, y int) {
term.set_cursor_position(y: y, x: board_centered_x(text))
println(text)
}
// Title, credits and instructions {{{1
// =============================================================
// Print the title at the current cursor position.
//
fn print_title() {
set_color(title_ink)
println(title)
set_color(default_ink)
}
fn print_credits() {
print_title()
println('\nOriginal version in BASIC:')
println(' Written by Chris Oxlade, 1983.')
println(' https://archive.org/details/seance.qb64')
println(' https://github.com/chaosotter/basic-games')
println('\nThis version in V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
fn print_instructions() {
print_title()
set_color(instructions_ink)
defer { set_color(default_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.')
}
// Game {{{1
// =============================================================
// Print the given letter at the given board coordinates.
//
fn print_char(y int, x int, a rune) {
term.set_cursor_position(y: y + board_y, x: x + board_x)
print(a)
}
fn print_board() {
set_color(board_ink)
defer { set_color(default_ink) }
for i := 1; i <= board_width; i++ {
print_char(0, i + 1, rune(base_character + i)) // top border
print_char(board_bottom_y, i + 1, rune(base_character +
last_letter_number - board_height - i + 1)) // bottom border
}
for i := 1; i <= board_height; i++ {
print_char(i, 0, rune(base_character + last_letter_number - i + 1)) // left border
print_char(i, 3 + board_width, rune(base_character + board_width + i)) // right border
}
println('')
}
// Print the title on the given row, centered on the board.
//
fn print_board_centered_title(y int) {
set_color(title_ink)
print_board_centered(title, y)
set_color(default_ink)
}
// Wait the given number of seconds.
//
fn wait_seconds(seconds int) {
time.sleep(seconds * time.second)
}
// Print the given mistake effect, wait a configured number of seconds and
// finally erase it.
//
fn print_mistake_effect(effect string) {
x := board_centered_x(effect)
term.hide_cursor()
term.set_cursor_position(y: messages_y, x: x)
set_color(mistake_effect_ink)
println(effect)
set_color(default_ink)
wait_seconds(mistake_effect_pause)
erase_line_toend_from(messages_y, x)
term.show_cursor()
}
// Return a new message of the given length, after marking its letters on the
// board.
//
fn message(length int) string {
mut y := 0
mut x := 0
mut message := ''
term.hide_cursor()
for i := 1; i <= length; i++ {
letter_number := rand.i32_in_range(first_letter_number, last_letter_number + 1) or {
first_letter_number
}
letter := rune(base_character + letter_number)
unsafe {
message = strconv.v_sprintf('${message}${letter}') // add letter to message
}
match true {
letter_number <= board_width {
// top border
y = 1
x = letter_number + 1
}
letter_number <= board_width + board_height {
// right border
y = letter_number - board_width
x = 2 + board_width
}
letter_number <= board_width + board_height + board_width {
// bottom border
y = board_bottom_y - 1
x = 2 + board_width + board_height + board_width - letter_number
}
else {
// left border
y = 1 + last_letter_number - letter_number
x = 1
}
}
set_color(planchette_ink)
defer { set_color(default_ink) }
print_char(y, x, planchette)
println('')
wait_seconds(1)
print_char(y, x, ` `)
}
term.show_cursor()
return message
}
// Accept a string from the user, erase it from the screen and return it.
//
fn message_understood() string {
term.set_cursor_position(y: input_y, x: input_x)
defer { erase_line_toend_from(input_y, input_x) }
return input('? ').to_upper()
}
fn play() {
mut score := 0
mut mistakes := 0
print_board_centered_title(1)
print_board()
for {
message_length := rand.i32_in_range(min_message_length, max_message_length + 1) or {
first_letter_number
}
if message(message_length) != message_understood() {
mistakes += 1
match mistakes {
1 {
print_mistake_effect('The table begins to shake!')
}
2 {
print_mistake_effect('The light bulb shatters!')
}
3 {
print_mistake_effect('Oh, no! A pair of clammy hands grasps your neck!')
return
}
else {}
}
} else {
score += message_length
if score >= max_score {
print_board_centered('Whew! The spirits have gone!', messages_y)
print_board_centered('You live to face another day!', messages_y + 1)
return
}
}
}
}
// Main {{{1
// =============================================================
fn main() {
set_color(default_ink)
term.clear()
print_credits()
press_enter('\nPress the Enter key to read the instructions. ')
term.clear()
print_instructions()
press_enter('\nPress the Enter key to start. ')
term.clear()
play()
println('\n')
}
Sine Wave
/*
Sine Wave
Original version in BASIC:
Creative Computing (Morristown, New Jersey, USA), ca. 1980.
This version in V:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Last modified 20231103T2127+0100.
*/
import math
import os
import term
fn main() {
term.clear()
println('Sine Wave\n')
println('Original version in BASIC:')
println(' Creative computing (Morristown, New Jersey, USA), ca. 1980.\n')
println('This version in V:')
println(' Copyright (c) 2023, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
println('Press Enter to start the program.')
os.input('')
term.clear()
mut word := ['', '']
word[0] = os.input('Enter the first word: ')
word[1] = os.input('Enter the second word: ')
term.clear()
mut even := false
for angle := 0.0; angle <= 40.0; angle += 0.25 {
print(' '.repeat(int(26 + 25 * math.sin(angle))))
println(word[int(even)])
even = !even
}
}
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 V:
Copyright (c) 2023, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Last modified 20250406T0123+0200.
*/
import arrays
import os
import rand
import readline
import strconv
import term
const image = [' BAR ', ' BELL ', 'ORANGE', 'LEMON ', ' PLUM ', 'CHERRY']
const bar = 0 // position of 'BAR' in `image`.
const images = image.len
const max_bet = 100
const min_bet = 1
fn utf8_keypress() rune {
mut rl := readline.Readline{}
rl.enable_raw_mode_nosig()
defer {
rl.disable_raw_mode()
}
return term.utf8_getchar() or { ` ` }
}
fn press(this string) rune {
print('Press ${this}')
return utf8_keypress()
}
fn credits() {
term.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 V:')
println(' Copyright (c) 2023, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair\n')
_ = press('any key for instructions. ')
}
fn instructions() {
term.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')
_ = press('any key to start. ')
}
fn won(prize int, bet int) int {
match prize {
2 { println('DOUBLE!') }
5 { println('*DOUBLE BAR*') }
10 { println('**TOP DOLLAR**') }
100 { println('***JACKPOT***') }
else {}
}
println('You won!')
return (prize + 1) * bet
}
fn show_standings(usd int) {
println('Your standings are ${usd} USD.')
}
fn home() {
term.set_cursor_position(x: 0, y: 0)
}
fn display_reels(reel []int) {
color := [
term.white,
term.cyan,
term.yellow,
term.bright_yellow,
term.bright_white,
term.bright_red,
]
home()
for i in reel {
print('[${color[i](image[i])}] ')
}
println('')
}
fn reel_image(default int) int {
return rand.intn(images) or { default }
}
fn init_reels(mut reel []int) {
for i, _ in reel {
reel[i] = reel_image(i)
}
}
fn spin_reels(mut reel []int) {
term.hide_cursor()
for spin := 0; spin < 24000; spin += 1 {
for i, _ in reel {
reel[i] = reel_image(i)
}
display_reels(reel)
}
term.show_cursor()
}
fn play() {
mut standings := 0
mut bet := 0
mut reel := [0, 0, 0]
init_reels(mut reel)
play: for {
for {
term.clear()
display_reels(reel)
bet = strconv.atoi(os.input('Your bet (or 0 to quit): ')) or { 0 }
match true {
bet > max_bet {
println('House limits are ${max_bet} USD.')
_ = press('any key to try again. ')
}
bet < min_bet {
if press('"q" to confirm you want to quit. ') == `q` {
break play
}
}
else {
break
}
} // bet match
} // bet loop
term.clear()
spin_reels(mut reel)
equals := arrays.uniq_all_repeated(reel.sorted()).len
bars := reel.count(it == bar)
match true {
equals == 3 {
if bars == 3 {
standings += won(100, bet)
} else {
standings += won(10, bet)
}
}
equals == 2 {
if bars == 2 {
standings += won(5, bet)
} else {
standings += won(2, bet)
}
}
else {
println('You lost.')
standings -= bet
}
} // prize match
show_standings(standings)
_ = press('any key to continue.')
} // play loop
show_standings(standings)
match true {
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.')
}
else {}
}
}
fn main() {
credits()
instructions()
play()
}
Stars
/*
Stars
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03.
Last modified: 20250406T0124+0200.
*/
import os
import strconv
import strings
fn input_int(prompt string) int {
mut n := 0
for {
n = strconv.atoi(os.input(prompt)) or { continue }
break
}
return n
}
fn main() {
name := os.input('What is your name? ')
println('Hello, ${name}.')
for {
stars := input_int('How many stars do you want? ')
println(strings.repeat(`*`, stars))
answer := os.input('Do you want more stars? ').trim_space()
if answer.to_lower() !in ['ok', 'yeah', 'yes', 'y'] {
break
}
}
}
Strings
/*
Strings
Original version in BASIC:
Example included in Vintage BASIC 1.0.3.
http://www.vintage-basic.net
This version in V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written on 2025-01-03/04.
Last modified: 20250714T1332+0200.
*/
import encoding.utf8
import math
import os
import strconv
import strings
fn input_int(prompt string) int {
mut n := 0
for {
n = strconv.atoi(os.input(prompt)) or { continue }
break
}
return n
}
fn main() {
s := os.input('Enter a string: ')
n := input_int('Enter an integer: ')
println('')
print("ASC('${s}') --> ")
println("int(utf8.get_rune('${s}', 0))) --> ${int(utf8.get_rune(s, 0))}")
print('CHR$(${n}) --> ')
println("rune(${n}) --> '${rune(n)}'")
print("LEFT$('${s}', ${n}) --> ")
println("'${s}'.runes()[0..math.min(${n}, '${s}'.len)].string() --> '${s.runes()[0..math.min(n,
s.len)].string()}'")
// XXX FIXME the program fails when n is out of bounds; BASIC returns an empty string instead
print("MID$('${s}', ${n}) --> ")
println("'${s}'.runes()[${n}-1..].string() --> '${s.runes()[n - 1..].string()}'")
// XXX FIXME the program fails when n is out of bounds; BASIC returns an empty string instead
print("MID$('${s}', ${n}, 3) --> ")
println("'${s}'.runes()[${n}-1..${n}-1+3].string() --> '${s.runes()[n - 1..n - 1 + 3].string()}'")
print("RIGHT$('${s}', ${n}) --> ")
println("'${s}'.runes()#[-${n}..].string() --> '${s.runes()#[-n..].string()}'")
print("LEN('${s}') --> ")
println("'${s}'.len --> ${s.len}")
print("VAL('${s}') --> ")
println("strconv.atoi('${s}') or { 0 } --> ${strconv.atoi(s) or { 0 }}")
print('STR$(${n}) --> ')
println("${n}.str() --> '${n.str()}'")
print('SPC(${n}) --> ')
println("strings.repeat(` `, ${n}) --> '${strings.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 V:
Copyright (c) 2025, Marcos Cruz (programandala.net)
SPDX-License-Identifier: Fair
Written in 2025-01-11/12.
Last modified: 20250714T1333+0200.
*/
import os
import rand
import strconv
import term
// Console {{{1
// =============================================================
const black = 0
const red = 1
const green = 2
const yellow = 3
const blue = 4
const magenta = 5
const cyan = 6
const white = 7
const default_color = 9
const foreground = 30
const background = 40
const bright = 60
fn set_color(color int) {
print('\x1B[0;${color}m')
}
// Constants {{{1
// =============================================================
const board_ink = foreground + bright + cyan
const default_ink = foreground + default_color
const input_ink = foreground + bright + green
const instructions_ink = foreground + yellow
const title_ink = foreground + bright + red
const blank = '*'
const grid_height = 3 // cell rows
const grid_width = 3 // cell columns
const cells = grid_width * grid_height
const grids_row = 3 // screen row where the grids are printed
const grids_column = 5 // screen column where the left grid is printed
const cells_gap = 2 // distance between the grid cells, in screen rows or columns
const grids_gap = 16 // screen columns between equivalent cells of the grids
const max_players = 4
const quit_command = 'X'
const nowhere = -1
const first_char_code = int(`A`)
// State structure {{{1
// =============================================================
// The `State` structure the game state, i.e. the global data.
//
struct State {
mut:
pristine_grid [cells]string
grid [max_players][cells]string
is_playing [max_players]bool
players int
}
// User input {{{1
// =============================================================
// Print the given prompt and wait until the user enters a string.
//
fn input_string(prompt string) string {
set_color(input_ink)
defer { set_color(default_ink) }
return os.input(prompt)
}
// Print the given prompt and wait until the user enters an integer.
//
fn input_int(prompt string) int {
mut n := 0
for {
n = strconv.atoi(input_string(prompt)) or { continue }
break
}
return n
}
// Print the given prompt and wait until the user presses Enter.
//
fn press_enter(prompt string) {
input_string(prompt)
}
// Return `true` if the given string is "yes" or a synonym.
//
fn is_yes(s string) bool {
return ['ok', 'y', 'yeah', 'yes'].any(it == s.to_lower())
}
// Return `true` if the given string is "no" or a synonym.
//
fn is_no(s string) bool {
return ['n', 'no', 'nope'].any(it == s.to_lower())
}
// Print the given prompt, wait until the user enters a valid yes/no string,
// and return `true` for "yes" or `false` for "no".
//
fn yes(prompt string) bool {
mut result := false
for {
answer := input_string(prompt)
if is_yes(answer) {
result = true
break
}
if is_no(answer) {
result = false
break
}
}
return result
}
// Title, instructions and credits {{{1
// =============================================================
// Print the title at the current cursor position.
//
fn print_title() {
set_color(title_ink)
println('Xchange')
set_color(default_ink)
}
// Print the credits at the current cursor position.
//
fn print_credits() {
print_title()
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_Book_of_Computer_Games/page/n269/mode/2up')
println(' https://github.com/chaosotter/basic-games')
println('This improved remake in V:')
println(' Copyright (c) 2025, Marcos Cruz (programandala.net)')
println(' SPDX-License-Identifier: Fair')
}
// Print the instructions at the current cursor position.
//
fn print_instructions() {
print_title()
set_color(instructions_ink)
defer { set_color(default_ink) }
println('\nOne or two may play. If two, you take turns. A grid looks like this:\n')
set_color(board_ink)
println(' F G D')
println(' A H ${blank}')
println(' E B C\n')
set_color(instructions_ink)
println('But it should look like this:\n')
set_color(board_ink)
println(' A B C')
println(' D E F')
println(' G H ${blank}\n')
set_color(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…')
}
// Grids {{{1
// =============================================================
// Print the given player's grid title.
//
fn print_grid_title(player int) {
term.set_cursor_position(y: grids_row, x: grids_column + (player * grids_gap))
print('Player ${player + 1}')
}
// Return the cursor position of the given player's grid cell.
//
fn cell_position(player int, cell int, state State) term.Coord {
grid_row := cell / grid_height
grid_column := cell % grid_width
title_margin := if state.players > 1 { 2 } else { 0 }
return term.Coord{
y: grids_row + title_margin + grid_row
x: grids_column + (grid_column * cells_gap) + (player * grids_gap)
}
}
// Return the cursor position of the given player's grid prompt.
//
fn grid_prompt_position(player int, state State) term.Coord {
grid_row := cells / grid_height
grid_column := cells % grid_width
title_margin := if state.players > 1 { 2 } else { 0 }
return term.Coord{
y: grids_row + title_margin + grid_row + 1
x: grids_column + (grid_column * cells_gap) + (player * grids_gap)
}
}
// Print the given player's grid, in the given or default color.
//
fn print_grid(player int, color int, state State) {
if state.players > 1 {
print_grid_title(player)
}
set_color(color)
defer { set_color(default_ink) }
for cell in 0 .. cells {
term.set_cursor_position(cell_position(player, cell, state))
print(state.grid[player][cell])
}
}
// Print the current players' grids.
//
fn print_grids(state State) {
for player in 0 .. state.players {
if state.is_playing[player] {
print_grid(player, board_ink, state)
}
}
println('')
term.erase_toend()
}
// Scramble the grid of the given player.
//
fn scramble_grid(player int, mut state State) {
for cell in 0 .. cells {
random_cell := rand.intn(cells) or { 0 }
// Exchange the contents of the current cell with that of the random one.
state.grid[player][cell], state.grid[player][random_cell] = state.grid[player][random_cell], state.grid[player][cell]
}
}
// Init the grids.
//
fn init_grids(mut state State) {
state.grid[0] = state.pristine_grid
scramble_grid(0, mut state)
for player in 0 + 1 .. state.players {
state.grid[player] = state.grid[0]
}
}
// Messages {{{1
// =============================================================
// Return a message prefix for the given player.
//
fn player_prefix(player int, state State) string {
return if state.players > 1 {
unsafe { strconv.v_sprintf('Player ${player + 1}: ') }
} else {
''
}
}
// Return the cursor position of the given player's messages, adding the given
// row increment, which defaults to zero.
//
fn message_position(player int, row_inc int, state State) term.Coord {
prompt_row := grid_prompt_position(player, state).y
return term.Coord{
y: prompt_row + 2 + row_inc
x: 1
}
}
// Print the given message about the given player, adding the given row
// increment, which defaults to zero, to the default cursor coordinates.
//
fn print_message(message string, player int, row_inc int, state State) {
term.set_cursor_position(message_position(player, row_inc, state))
print('${player_prefix(player, state)}${message}')
term.erase_line_toend()
println('')
}
// Erase the last message about the given player.
//
fn erase_message(player int, state State) {
term.set_cursor_position(message_position(player, 0, state))
term.erase_line_toend()
}
// Game loop {{{1
// =============================================================
// Return a message with the players range.
//
fn players_range_message() string {
if max_players == 2 {
return '1 or 2'
} else {
return unsafe { strconv.v_sprintf('from 1 to ${max_players}') }
}
}
// Return the number of players, asking the user if needed.
//
fn number_of_players() int {
mut players := 0
print_title()
println('')
if max_players == 1 {
players = 1
} else {
for players < 1 || players > max_players {
players = input_int(unsafe { strconv.v_sprintf('Number of players (${players_range_message()}): ') })
}
}
return players
}
// Is the given cell the first one on a grid row?
//
fn is_first_cell_of_a_grid_row(cell int) bool {
return cell % grid_width == 0
}
// Is the given cell the last one on a grid row?
//
fn is_last_cell_of_a_grid_row(cell int) bool {
return (cell + 1) % grid_width == 0
}
// Are the given cells adjacent?
//
fn are_cells_adjacent(cell_1 int, cell_2 int) bool {
match true {
cell_2 == cell_1 + 1 && !is_first_cell_of_a_grid_row(cell_2) {
return true
}
cell_2 == cell_1 + grid_width {
return true
}
cell_2 == cell_1 - 1 && !is_last_cell_of_a_grid_row(cell_2) {
return true
}
cell_2 == cell_1 - grid_width {
return true
}
else {}
}
return false
}
// Is the given player's character cell a valid move, i.e. is it adjacent to
// the blank cell? If so, return the blank cell of the grid and `true`;
// otherwise return a fake cell and `false`.
//
fn is_legal_move(player int, char_cell int, state State) (int, bool) {
for blank_cell, cell_content in state.grid[player] {
if cell_content == blank {
if are_cells_adjacent(char_cell, blank_cell) {
return blank_cell, true
}
break
}
}
print_message(unsafe { strconv.v_sprintf('Illegal move "${state.grid[player][char_cell]}".') },
player, 0, state)
return nowhere, false
}
// Is the given player's command valid, i.e. a grid character? If so, return
// its position in the grid and `true`; otherwise print an error message and
// return a fake position and `false`.
//
fn is_valid_command(player int, command string, state State) (int, bool) {
if command != blank {
for position, cell_content in state.grid[player] {
if cell_content == command {
return position, true
}
}
}
print_message(unsafe { strconv.v_sprintf('Invalid character "${command}".') }, player,
0, state)
return nowhere, false
}
// Forget the given player, who quitted.
//
fn forget_player(player int, mut state State) {
state.is_playing[player] = false
print_grid(player, default_ink, state)
}
// Play the turn of the given player.
//
fn play_turn(player int, mut state State) {
mut blank_position := 0
mut character_position := 0
if state.is_playing[player] {
for {
mut ok := false
mut command := ''
for ok = false; !ok; character_position, ok = is_valid_command(player, command,
state) {
coord := grid_prompt_position(player, state)
row, column := coord.y, coord.x
term.set_cursor_position(y: row, x: column)
term.erase_line_toend()
term.set_cursor_position(y: row, x: column)
command = input_string('Move: ').trim_space().to_upper()
if command == quit_command {
forget_player(player, mut state)
return
}
}
blank_position, ok = is_legal_move(player, character_position, state)
if ok {
break
}
}
erase_message(player, state)
state.grid[player][blank_position] = state.grid[player][character_position]
state.grid[player][character_position] = blank
}
}
// Play the turns of all players.
//
fn play_turns(mut state State) {
for player in 0 .. state.players {
play_turn(player, mut state)
}
}
// Is someone playing?
//
fn is_someone_playing(state State) bool {
for player in 0 .. state.players {
if state.is_playing[player] {
return true
}
}
return false
}
// Has someone won? If so, print a message for every winner and return `true`;
// otherwise just return `false`.
//
fn has_someone_won(state State) bool {
mut winners := 0
for player in 0 .. state.players {
if state.is_playing[player] {
if state.grid[player] == state.pristine_grid {
winners += 1
if winners > 0 {
winner := 'winner' + if winners > 1 { ', too' } else { '' }
print_message(unsafe { strconv.v_sprintf("You're the ${winner}!") },
player, winners - 1, state)
}
}
}
}
return winners > 0
}
// Init the game.
//
fn init_game(mut state State) {
term.clear()
state.players = number_of_players()
for player in 0 .. state.players {
state.is_playing[player] = true
}
term.clear()
print_title()
init_grids(mut state)
print_grids(state)
}
// Play the game.
//
fn play(mut state State) {
init_game(mut state)
for is_someone_playing(state) {
play_turns(mut state)
print_grids(state)
if has_someone_won(state) {
break
}
}
}
// Main {{{1
// =============================================================
// Init the program, i.e. just once before the first game.
//
fn init_once(mut state State) {
// Init the pristine grid.
for cell in 0 .. cells - 1 {
state.pristine_grid[cell] = rune(first_char_code + cell).str()
}
state.pristine_grid[cells - 1] = blank
}
// Return `true` if the player does not want to play another game; otherwise
// return `false`.
//
fn enough(state State) bool {
term.set_cursor_position(grid_prompt_position(0, state))
return !yes('Another game? ')
}
fn main() {
mut state := State{}
init_once(mut state)
term.clear()
print_credits()
press_enter('\nPress the Enter key to read the instructions. ')
term.clear()
print_instructions()
press_enter('\nPress the Enter key to start. ')
for {
play(mut state)
if enough(state) {
break
}
}
println('So long…')
}
