Up to Main Index Up to Journal for October, 2022
JOURNAL FOR MONDAY 3RD OCTOBER, 2022
______________________________________________________________________________
SUBJECT: The Cottage — Mere edition (terrible idea)
DATE: Mon 3 Oct 20:22:26 BST 2022
For myself a programming language should meet two criteria. It should allow
programmers to write meaningful programs and it should let the programmer
express themselves clearly — the code should be readable and writable. There
are other important criteria such as being self consistent and efficient, but
these are secondary concerns.
I thought I’d apply my criteria, and test out Mere’s new map implementation,
by trying to write something meaningful in Mere. I decided to write a Mere
version of “The Cottage”[1]. It did not go well…
The program runs and does what it is supposed to do. Here is a brief example:
>bin/mere theCottage
Welcome to the cottage!
For a list of commands type h or help.
[ Path outside a cottage ]
You are on a path leading north to a small, white cottage.
You see exits: north, south and west
? help
Available commands:
n or north - move north
s or south - move south
e or east - move east
w or west - move west
sneeze - makes you sneeze
x or examine - examine an item
l or look - describe location again
h or help - display this help
q or quit - exit the game
? west
[ Front lawn ]
You are standing on a small, neat lawn outside the cottage.
You see a green frog here.
You see a red ball here.
You see a red frog here.
You see exits: east and north
? examine red
You examine a red ball.
This is a small, red child's ball.
? examine ball
You examine a red ball.
This is a small, red child's ball.
? examine red frog
You examine a red frog.
This is a small, red frog.
? east
[ Path outside a cottage ]
You are on a path leading north to a small, white cottage.
You see exits: north, south and west
? north
[ Front door ]
You are standing at the open front door of a small, white cottage.
You see the front door here.
You see a doormat here.
You see exits: north and south
? quit
Bye bye!
>
I guess in a way that is success. However, the code is terrible. Writing the
code is a bit of a nightmare for three reasons: everything is global, there
are no scopes and there are no user defined functions. Having proper for loops
would help make the code cleaner and clearer — but, no scopes. All things I
need to work on implementing.
Ok, maybe I’m being too harsh on myself. Reimplementing “The Cottage” at this
stage in Mere’s development was probably a little too ambitious :(
For the morbid, I’ve added the code to the end of this post. NOTE THAT IT WILL
NOT RUN IN MERE ICE. I have not implemented the input built-in yet — and I’m
still not too sure on how I’m going to do that — and Mere/Mere ICE do not have
the work on maps included yet. Note also that everything is in one file with
the data hard-coded, just like the original implementation.
Astute readers will notice that the ‘Locs’ data structure, in the code below,
extends over multiple lines. Implementing maps while having everything crammed
onto a single line was becoming too painful. The fix was a minor tweak to the
tokenizer. Lines ending with a left parenthesis or a comma now continue to the
next line.
I hope to be updating Mere and Mere ICE with the latest map additions soon.
One more pain point I, I keep trying to gofmt my Mere source code XD
--
Diddymus
[1] Original “The Cottage”: ../../2019/5/cottage.html
/*
The Cottage - Mere edition
Copyright 2022, Andrew 'Diddymus' Rolfe. All rights reserved.
The cottage is a small, simple, single player, plain text, 'toy' adventure
game. It is intended to be used for learning, experimenting, general hacking
and having fun while doing so.
*/
// Exits lookup map for directions
Exits:[string] = [string] (
"n" "north",
"s" "south",
"e" "east",
"w" "west",
)
// Locs map for location data.
Locs:[string] = [string](
"path" [string](
"title" "Path outside a cottage",
"text" "You are on a path leading north to a small, white cottage.",
"exits" [string]("n" "frontdoor", "s" "", "w" "frontlawn"),
"inv" [string](
"path" [string](
"alias" []string("path"),
"name" "a path",
"text" "The path is made of small, neat paving stones.",
"narrative" true,
),
"paving" [string](
"alias" []string("paving", "stones"),
"name" "some paving stones",
"text" "The paving stones are a foot square and grey in colour.",
"narrative" true,
),
"cottage" [string](
"alias" []string("cottage"),
"name" "a white cottage",
"text" "From here the cottage seems to be very inviting.",
"narrative" true,
),
),
),
"frontlawn" [string](
"title" "Front lawn",
"text" "You are standing on a small, neat lawn outside the cottage.",
"exits" [string]("n" "sidepath", "e" "path"),
"inv" [string](
"greenfrog" [string](
"alias" []string("green" "frog"),
"name" "a green frog",
"text" "This is a small, green frog.",
),
"redfrog" [string](
"alias" []string("red" "frog"),
"name" "a red frog",
"text" "This is a small, red frog.",
),
"redball" [string](
"alias" []string("red" "ball"),
"name" "a red ball",
"text" "This is a small, red child's ball.",
),
),
),
"frontdoor" [string](
"title" "Front door",
"text" "You are standing at the open front door of a small, white cottage.",
"exits" [string]("n" "southhall", "s" "path"),
"inv" [string](
"door" [string](
"alias" []string("front" "door"),
"name" "the front door",
"text" "This is a sturdy, wooden front door. Someone has painted it black.",
),
"doormat" [string](
"alias" []string("door" "mat", "doormat"),
"name" "a doormat",
"text" "This is a brown, bristly doormat. It has \"welcome\" written on it.",
),
),
),
"southhall" [string](
"title" "Hallway",
"text" "You are in a hallway by the front door.",
"exits" [string]("n" "northhall", "e" "kitchen", "s" "frontdoor", "w" "study"),
),
"northhall" [string](
"title" "Hallway",
"text" "You are in a hallway. Some pictures hang on the wall.",
"exits" [string]("n" "bathroom", "s" "southhall", "w" "bedroom"),
),
"kitchen" [string](
"title" "Kitchen",
"text" "You are standing in a small, very tidy kitchen.",
"exits" [string]("n" "backlawn", "w" "southhall"),
),
"study" [string](
"title" "Study",
"text" "This is a small, cosy study with a desk and many bookcases.",
"exits" [string]("e" "southhall"),
),
"bedroom" [string](
"title" "Bedroom",
"text" "You are in a small bedroom with a bed and a large wardrobe.",
"exits" [string]("e" "northhall"),
),
"bathroom" [string](
"title" "Bathroom",
"text" "You are in a small, very clean and bright bathroom.",
"exits" [string]("s" "northhall"),
),
"backlawn" [string](
"title" "Back lawn",
"text" "You are standing on a small, neat lawn at the back of the cottage.",
"exits" [string]("s" "kitchen", "w" "sidepath"),
),
"sidepath" [string](
"title" "Side path",
"text" "You are on a narrow path running along the side of the cottage.",
"exits" [string]("n" "shed", "e" "backlawn", "s" "frontlawn"),
),
"shed" [string](
"title" "Shed",
"text" "You are standing in a small, dark, dingy garden shed.",
"exits" [string]("s" "sidepath"),
),
)
// Global variables
Cmd:string = "look" // Starting command
Here:[string] = Locs["path"] // Starting location
Words:[]string // Additional words after command
FoundItem:string // Shared with findItem subroutine
gosub Greeting
// Main game processing loop.
Main:
handled:bool = false
if Cmd == "h" || Cmd == "help"; gosub Help
if Cmd == "q" || Cmd == "quit"; gosub Quit
if Cmd == "l" || Cmd == "look"; gosub Look
if Cmd == "n" || Cmd == "north"; gosub Move
if Cmd == "s" || Cmd == "south"; gosub Move
if Cmd == "e" || Cmd == "east"; gosub Move
if Cmd == "w" || Cmd == "west"; gosub Move
if Cmd == "x" || Cmd == "examine"; gosub Examine
if Cmd == "sneeze"; gosub Sneeze
if !handled && Cmd != ""; println "Eh?"
print "? "; input Cmd
gosub Split
goto Main
// Quit exits the game.
Quit:
println
println "Bye bye!"
println
exit 0
// Sneeze makes the player sneeze. Example of a very simple command.
Sneeze:
handled = true
println "Achoo! You sneeze."
return
// Look command to describe the player's current location.
Look:
handled = true
println "[ " Here["title"] " ]"
println Here["text"]
println
if !exists Here "inv"; goto noinv
needNL:bool = false
invKeys:[]string = keys Here["inv"]
inv_loop:
item:[string] = Here["inv"][invKeys[0]]
if exists(item, "narrative"); goto isNarrative
println("You see ", item["name"], " here.")
needNL = true
isNarrative:
delete(invKeys, 0)
if len(invKeys) > 0; goto inv_loop
if needNL; println
noinv:
exitList:string = ""
dkeys:[]string = keys Here["exits"]
dirs_loop:
exitList += Exits[dkeys[0]]
if len(dkeys) == 2; exitList += " and "
if len(dkeys) > 2; exitList += ", "
delete dkeys 0
if len(dkeys) > 0; goto dirs_loop
println "You see exits: " exitList
return
// Move command to move player between locations.
Move:
handled = true
dir:string = Cmd[0]
if !exists Here["exits"] dir; goto noexit
if !exists Locs Here["exits"][dir]; goto badexit
Here = Locs[Here["exits"][dir]]
gosub Look
return
noexit: println "You can't go " Exits[dir] "."; return
badexit: println "Oops, you can't actually go " Exits[dir] "."; return
// Examine command to examine items.
Examine:
handled = true
if len(Words) == 0; goto examineWhat
gosub findItem
if FoundItem == ""; goto noExamine
item:[string] = Here["inv"][FoundItem]
println "You examine " item["name"] "."
println item["text"]
return
examineWhat: println "You goto examine... something?"; return
noExamine:
wordList:string = sprintf("%s", Words)
wordList = substr(wordList, 1, len(wordList)-2)
println "You see no '" wordList "' here to examine."
return
// findItem looks for items with aliases matching Words. Returns the index of
// the first match as a string in FoundItem. If there are no matches return an
// empty string.
findItem:
items:[string] = Here["inv"]
keep:[]string = keys items
keepers:[]string = []string()
matching:[]string = Words
FoundItem = ""
midx:int = 0
matchLoop:
kidx:int = 0
keepLoop:
match:bool = false
aliases:[]string = items[keep[kidx]]["alias"]
aidx:int = 0
aliasLoop:
if aliases[aidx] == matching[midx]; match = true
if aidx++ < len(aliases); goto aliasLoop
if match; keepers = []string keepers keep[kidx]
if kidx++ < len(keep); goto keepLoop
keep = keepers
keepers = []string()
if len(keep) == 0; return
if midx++ < len(matching); goto matchLoop
if len(keep) > 0; FoundItem = keep[0]
return
// Split player command into Words and move first word into Cmd.
Split:
Words = []string()
if len(Cmd) == 0; return
word:string = ""
Cmd += " "
pos:int = 0
split_loop:
if Cmd[pos] != " "; word += Cmd[pos]
if Cmd[pos] == " " && word != ""; Words = []string Words word
if Cmd[pos] == " " && word != ""; word = ""
if pos++ < len Cmd; goto split_loop
Cmd = Words[0]
delete Words 0
return
// Greeting welcomes the player.
Greeting:
println
println " Welcome to the cottage!"
println
println "For a list of commands type h or help."
println
return
// Help displays the available commands.
Help:
handled = true
println "Available commands:"
println
println " n or north - move north"
println " s or south - move south"
println " e or east - move east"
println " w or west - move west"
println " sneeze - makes you sneeze"
println " x or examine - examine an item"
println " l or look - describe location again"
println " h or help - display this help"
println " q or quit - exit the game"
println
return
Up to Main Index Up to Journal for October, 2022