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 test/*.expect
dist/nmm

0
dist/.gitkeep vendored 100644
View File

View File

@ -38,7 +38,8 @@ Here's what it looks like:
## BACKGROUND ## 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: 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 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 also look at these round cows
https://en.wikipedia.org/wiki/Spherical_cow 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 I'm still interested in the Result / Either monad option
but I think I'm doing to first try the conventional lua way 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. 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 .pl \n[nl]u

View File

@ -2,10 +2,15 @@
default: default:
just --list --unsorted just --list --unsorted
build:
fennel --compile-binary main.fnl dist/nmm \
/usr/local/lib/liblua.a \
/usr/local/include/lua5.4
# run tests # run tests
test: test:
#!/bin/zsh #!/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 # build expect scripts
expects: 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`. moves are recorded in `<file>.dat`.
then you can `awk -f test.awk file.dat > file.expect`. 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` then you can `expect file.expect`
to have expect play the game for you up to a certain point. to have expect play the game for you up to a certain point.
== REQUIREMENTS == ## REQUIREMENTS
- awk - awk
- expect - expect
== FUTURE PLANS == ## FUTURE PLANS
have actual integration tests? have actual integration tests?