feat: add story engine

main
Dozens B. McCuzzins 2024-06-25 21:22:35 -06:00
parent 9bf31e86ce
commit bf71791fc5
12 changed files with 375 additions and 6 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
test/*.expect
dist/nmm

0
dist/.gitkeep vendored 100644
View File

View File

@ -38,7 +38,8 @@ Here's what it looks like:
## BACKGROUND
9mm is legit a great game.
Nine Mens Morris is legit a great game,
and I like it a lot.
One time i wrote an essay about the social contract implicit to nine mens morris:
https://write.tildeverse.org/dozens/nine-mens-morris-cultural-meanings-and-social-contracts
@ -49,3 +50,12 @@ https://en.wikipedia.org/wiki/Morabaraba
also look at these round cows
https://en.wikipedia.org/wiki/Spherical_cow
## USAGE
1. If you have fennel installed,
run `fennel main.fnl`
2. If you have fennel installed,
you can compile a binary
using `fennel --compile-binary`.
(See `justfile` for an example of how I do it.)

View File

@ -310,6 +310,29 @@ for the client to interpret.
I'm still interested in the Result / Either monad option
but I think I'm doing to first try the conventional lua way
and throw an error, and then 'pcall' the function and handle the error politely.
.
.
.IP "WEEK FOUR REVIEW"
Today is the end of tilde30!
I didn't get too much done this week
on account of the surgery.
At least,
I wasn't able to keep up with the daily updates.
I did end up writing a tracery-lite
type templating thing
that you can find
in `src/story`.
I did not succeed in
actually incorporating it into the game.
But it's there.
tilde30 on the whole was a fun exercise
and a success.
I had a lot of fun working on my project,
and I especially had a lot of fun hearing about other people's projects.
Even if they didn't complete them.
I am planning to do it again in September,
and am planning to continue working on 9mm
in the meantime!
.pl \n[nl]u

View File

@ -2,10 +2,15 @@
default:
just --list --unsorted
build:
fennel --compile-binary main.fnl dist/nmm \
/usr/local/lib/liblua.a \
/usr/local/include/lua5.4
# run tests
test:
#!/bin/zsh
for f in **/*.test.fnl; do fennel $f | faucet; done
for f in lib/**/*.test.fnl; do fennel $f | faucet; done
# build expect scripts
expects:

110
src/story/README.md 100644
View File

@ -0,0 +1,110 @@
## Format
Here is a list of lists
representing a slightly augmented
deck of cards
(basically the heckadeck):
```
:: suit
spades
hearts
clubs
diamonds
acorns
clouds
swords
planets
:: face
beast
thief
jack
queen
king
:: number
zero
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
:: special
crone
joker
watcher
traveler
:: card
[[number]]
[[face]]
:: draw
[[card]] of [[suit]]
[[special]]
```
A list title appears on a line by itself,
preceded by a double colon (::)
and at least one space.
A list title can contain alphabet characters and a dash.
Following the list title are list items,
each on its own line.
A list item may be (or contain) a reference
to a list title in double brackets.
e.g. [[list-title]]
Blank lines are ignored.
## Usage
Pass the filename of a file of lists formatted in this way
to `create_corpus`
and get a "corpus" in return.
A corpus is just a deserialized fennel table
of list titles and list items.
Then pass the corpus to `flatten`
along with a string or a list to serve as the "origin".
In this case if you pass `corpus.draw`,
then `flatten` will return a random selection from `:draw`,
expanding references along the way:
- beast of spades
- five of hearts
- jack of acorns
- watcher
- three of planets
- beast of clubs
Read and run `story.test.fnl` for an example.
## Inspiration
This is inspired by [tracery][1]
and [perchance][4]
and the [List to HTML Generator][5],
and is similar to [twee][2] format.
In fact,
with just a little modification,
you can use this story file
to generate tracery output
in a twine story
using [trice][3].
[1]: https://github.com/galaxykate/tracery
[2]: https://twinery.org/cookbook/terms/terms_twee.html
[3]: https://github.com/incobalt/Trice
[4]: https://perchance.org/
[5]: https://slightadjustments.blogspot.com/p/generator.html

View File

@ -0,0 +1,46 @@
:: suit
spades
hearts
clubs
diamonds
acorns
clouds
swords
planets
:: face
beast
thief
jack
queen
king
:: number
zero
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
:: special
crone
joker
watcher
traveler
:: card
[[number]]
[[face]]
:: draw
[[card]] of [[suit]]
[[special]]

View File

@ -0,0 +1,19 @@
:: origin
[[you]] [[need]] [[go]] [[search]] [[find]] [[take]] [[return]] [[change]]
:: you
[[beginning]]
:: beginning
Once upon a time
I've told you before but I'll tell you again
Once there was
One day, a long time ago
There was and there was not
East of the sun and west of the moon
In the beginning
Back in the day
I remember when
On an old day, in the old times
Back when tigers used to smoke tobacco
That time then and once again

View File

@ -0,0 +1,58 @@
(fn lines [filename callback]
(case (pcall #(with-open [file (io.open filename)] (each [line (file:lines)] (callback line))))
(false err) (print (string.format "Error: Could not open file %s\n%s" filename err))))
(fn _create-corpus [lines data]
(var current-key nil)
(var corpus {})
(lines data
#(let [key (string.match $1 "^::%s+([%a-]+)")
blank (or (= nil $1) (= "" $1))]
(when (not blank)
(if (not key)
(let [list (. corpus current-key)]
(table.insert list $1)
(tset corpus current-key list))
(do
(set current-key key)
(tset corpus current-key []))))))
corpus)
(local create-corpus (partial _create-corpus lines))
(fn one-of [t]
"returns a random element of a sequential or non-sequential table"
(let [len (accumulate [l 0 _ _ (pairs t)] (+ l 1)) ;; do it the hard way
;; because nonseq tables
;; have no length?
handle (io.popen "echo $RANDOM")
output (handle:read "*a")
random (output:gsub "[\n\r]" "")
seed (math.randomseed random) ;; SIDE EFFECT
whatever (handle:close) ;; SIDE EFFECT
idx (math.random len)
keys (accumulate [acc [] k v (pairs t)] (do (table.insert acc k) acc))
rndkey (. keys idx)
it (. t rndkey)]
it))
(fn flatten [corpus origin]
(let [str (if (= "string" (type origin))
origin
(if (= "table" (type origin))
(one-of origin)
(error "Origin must be a table or a string")))
template-pattern "%[%[[%a-]+%]%]" ; [[word]]
word-pattern "%[%[([%a-]+)%]%]" ; word
(i j) (string.find str template-pattern) ; indices
word (string.match str word-pattern)] ; the actual keyword
(if (not i)
str
(let [next-str (string.format "%s%s%s"
(string.sub str 1 (- i 1))
(one-of (. corpus word))
(string.sub str (+ j 1)))]
(flatten corpus next-str j))))) ;; this is a tail call!
{: create-corpus
: flatten
}

View File

@ -0,0 +1,80 @@
:: start
To [[do]] in the [[place]] [[preposition]] the [[color]] [[celestial]]
:: do
[[walk]]
[[feel]]
:: place
[[biome]]
[[weather]] [[biome]]
[[weather]] [[biome]]
:: feel
brood
go to pieces
wallow
percolate
ferment
pine
waste away
ponder
wonder
:: preposition
beneath
amongst
betwixt
below
between
through
around
despite
:: walk
walk
stroll
jaunt
wander
meander
amble
stalk
ambulate
:: weather
blistering
undulating
weeping
mourning
hidden
secret
wistful
taciturn
sticky
:: biome
woods
dunes
forest
plains
hills
mountains
ocean
bog
lake
:: color
chartreuse
opalescent
verdant
vermilion
aquamarine
copper
:: celestial
skies
moon
stars
planets
clouds
sun

View File

@ -0,0 +1,16 @@
(let [{
: flatten
: create-corpus
} (require :src.story.story)]
(let [corpus (create-corpus "src/story/story.test.dat")
get-story (partial flatten corpus corpus.start)]
(print "\n== POEMS ==")
(for [_ 1 10] (print (get-story))))
(let [corpus (create-corpus "src/story/cards.dat")
get-story (partial flatten corpus corpus.draw)]
(print "\n== CARDS ==")
(for [_ 1 10] (print (get-story)))))

View File

@ -1,6 +1,7 @@
== ABOUT ==
## ABOUT
these files are to help me test the ui
these files are to help me test the ui.
actually, just the game state really.
moves are recorded in `<file>.dat`.
then you can `awk -f test.awk file.dat > file.expect`.
@ -8,11 +9,11 @@ then you can `awk -f test.awk file.dat > file.expect`.
then you can `expect file.expect`
to have expect play the game for you up to a certain point.
== REQUIREMENTS ==
## REQUIREMENTS
- awk
- expect
== FUTURE PLANS ==
## FUTURE PLANS
have actual integration tests?