M dryads-wake.w +1 -1
@@ 157,7 157,7 @@ define : load-game ;; TODO: extract to g
,(first-encounter state)
-define : read-line
+ndefine : read-line
sanitize-string
let loop : : chars '()
define char : read-char
A => tests-textfile-input/welcome +13 -0
@@ 0,0 1,13 @@
+Robert
+Arne
+
+Robert
+It would be nice to be able to turn stories into playable websites
+
+Arne
+I hope this works for you!
+
+Read again?
+welcome
+Exit?
+exit
A => +71 -0
@@ 0,0 1,71 @@
+;; are we running as webserver?
+define running-as-webserver #f
+
+define : exit state
+ Print
+ You reached the end of the story
+ that is already written.
+ Please come back with our next release.
+
+
+define : help args
+ format : current-output-port
+ . "~a [-i] [--help | --version | --test | --server [host]]\n"
+ first args
+
+define %this-module : current-module
+define : test scene
+ define state : game-state-init!
+ ;; analyze the plot
+ define : module->filename mod
+ string-append
+ string-join : map symbol->string : module-name mod
+ . "/"
+ . ".w"
+ pretty-print
+ analyze : list : module->filename %this-module
+ set-speed-extremely-fast!
+ when scene
+ (eval (string->symbol scene) %this-module ) state
+
+define : final-action? args
+ ;; abuse revealed-count for speed scaling
+ set-speed-normal!
+ if {(length args) > 1}
+ cond
+ : equal? "--help" : second args
+ help args
+ . #t
+ : equal? "--version" : second args
+ format : current-output-port
+ . "~a\n" version
+ . #t
+ : equal? "--test" : second args
+ test : if {(length args) > 2} (third args) #f
+ . #t
+ : member (second args) '("--servertest" "--server")
+ set! running-as-webserver #t
+ as-webserver
+ λ :
+ welcome : game-state-init!
+ Print
+ ""
+ Reload this website to play again.
+ ;; to set your own static site instead of websocket.static-response add
+ ;; . #:static-response "<the full text content of the static site>"
+ . #t
+ : equal? "--scene" : second args
+ apply
+ module-ref %this-module
+ string->symbol : third args
+ list : game-state-init!
+ . #t
+ else #f
+ . #f
+
+
+define : main args
+ when : not : final-action? args
+ welcome : game-state-init!
+
+;; for emacs (progn (defun test-this-file () (interactive) (save-current-buffer) (async-shell-command (concat (buffer-file-name (current-buffer)) " --test"))) (local-set-key (kbd "<f9>") 'test-this-file)(defun analyze-plot () (interactive) (save-current-buffer) (async-shell-command (concat "./analyze-plot.w " (buffer-file-name (current-buffer))))) (local-set-key (kbd "<f8>") 'analyze-plot))
A => +36 -0
@@ 0,0 1,36 @@
+#!/usr/bin/env bash
+# -*- wisp -*-
+# ensure that (language wisp) is pre-compiled
+if ! guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) --language=wisp -c '' 2>/dev/null; then
+ guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) -c '(import (language wisp spec))' >/dev/null 2>&1
+fi
+# run textfiles-game as module to ensure it is used pre-compiled
+if [[ "$1" == "--server" ]]; then
+ exec -a "$0" guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) --language=wisp -x .w -e '(textfiles-game)' -c '' "${@}"
+else
+ exec -a "$0" guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) --language=wisp -x .w -e '(textfiles-game)' -c '' "${@}" # 2>/dev/null
+fi
+; !#
+
+;; no declarative modules because we change bindings.
+eval-when (expand) : false-if-exception : user-modules-declarative? #f
+
+define-module : textfiles-game
+ . #:export : main
+ . #:declarative? #f
+
+import
+ only (srfi srfi-19) current-date date->string string->date date->time-utc time-utc->date
+ . make-time time-utc time-duration add-duration current-time
+ only (srfi srfi-9) define-record-type
+ only (srfi srfi-11) let-values
+ only (ice-9 pretty-print) pretty-print
+ only (ice-9 format) format
+ only (srfi srfi-1) first second third alist-cons assoc lset<= lset-intersection lset-difference remove drop
+ only (ice-9 ftw) scandir
+ doctests
+ enter
+ only (d6) roll check
+ only (names) first-names last-names
+ only (game-helpers) game-state game-state-scene game-state-scene-set! game-state-init! game-state-key game-state-key-set! game-state-key-equal? game-state-things game-state-things-contains? game-state-things-add-one! game-state-things-remove-one! game-state-id game-state-id-set! game-state-name game-state-name-set! name->id define-scene profile-ability-score describe-ability-score make-profile state-profile state-profile-key-set! score->description increase-ability-probabilistic challenge save-state load-state game-states uniquify-id game-state-outcomes-contains? game-state-outcomes-add! game-state-outcomes-remove! define-outcome state-profile-wounds-add! state-profile-wounds-cure! as-webserver make-secret game-state-secret game-state-secret-set! sanitize-string
+ only (analyze-plot) analyze
A => textfiles-to-game.w +248 -0
@@ 0,0 1,248 @@
+#!/usr/bin/env bash
+# -*- wisp -*-
+# This is a parser for a simplified format to enable basic usage of enter.w for more people.
+# Just write multiple files in one folder, each starting with one line per person to enter,
+# then an empty line, then blocks starting with the person and then lines of text.
+# In the last block have alternating lines with question and the next filename to switch to.
+# Switching to the filename exit ends the game.
+# Example:
+# --- welcome
+# Robert
+# Arne
+#
+# Robert
+# It would be nice to be able to turn stories into playable websites
+#
+# Arne
+# I hope this works for you!
+#
+# Read again?
+# welcome
+# Exit?
+# exit
+
+# ensure that (language wisp) is pre-compiled
+if ! guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) --language=wisp -c '' 2>/dev/null; then
+ guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) -c '(import (language wisp spec))' >/dev/null 2>&1
+fi
+# run textfiles-to-game as module to ensure it is used pre-compiled
+if [[ "$1" == "--server" ]]; then
+ exec -a "$0" guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) --language=wisp -x .w -e '(textfiles-to-game)' -c '' "${@}"
+else
+ exec -a "$0" guile -L $(dirname $(realpath "$0")) -C $(dirname $(realpath "$0")) --language=wisp -x .w -e '(textfiles-to-game)' -c '' "${@}" # 2>/dev/null
+fi
+; !#
+
+;; no declarative modules because we change bindings.
+eval-when (expand) : false-if-exception : user-modules-declarative? #f
+
+define-module : textfiles-to-game
+ . #:export : main
+ . #:declarative? #f
+
+import : only (srfi :1) first second remove drop
+ only (srfi srfi-9) define-record-type
+ ice-9 ftw
+ ice-9 match
+ only (ice-9 rdelim) read-delimited
+ doctests
+ only (ice-9 optargs) define*
+ only (ice-9 rdelim) read-line
+ only (srfi :26) cut
+
+define* : map-over-lines/port fun port #:key (terminator eof-object?)
+ define terminator?
+ if : or (procedure? terminator) (macro? terminator)
+ . terminator
+ cut equal? terminator <>
+ let loop : (lines '()) (line (read-line port))
+ if : or (terminator? line) (eof-object? line)
+ begin
+ reverse! lines
+ loop
+ cons : fun line
+ . lines
+ read-line port
+
+define just-files
+ ;; Remove the `stat' object the `file-system-tree' provides
+ ;; for each file in the tree.
+ match-lambda
+ : name stat ; flat file
+ . name
+ : name stat children ... ; directory
+ map just-files children
+ else '()
+
+define : list-files folder
+ ##
+ tests
+ test-equal '("tests-textfile-input/welcome")
+ list-files "tests-textfile-input"
+ map : λ(c) : string-join (list folder c) file-name-separator-string
+ remove list? : just-files : file-system-tree folder
+
+define-record-type <scenefile>
+ make-scenefile enter content choose
+ . scenefile?
+ enter scenefile-enter
+ content scenefile-content
+ choose scenefile-choose
+
+
+define : parse-file filename
+ ##
+ tests
+ test-equal
+ make-scenefile
+ ' "Robert" "Arne"
+ '
+ "Robert" "It would be nice to be able to turn stories into playable websites"
+ "Arne" "I hope this works for you!"
+ ' "Read again?" "welcome" "Exit?" "exit"
+ parse-file "tests-textfile-input/welcome"
+ define port : open-input-file filename
+ define enter
+ map-over-lines/port identity port #:terminator ""
+ define choices #f
+ define content
+ let loop : (blocks '())
+ define block : map-over-lines/port identity port #:terminator ""
+ cond
+ : and (null? block) (null? blocks)
+ '
+ : null? block
+ set! choices : car blocks
+ reverse! : cdr blocks
+ else : loop : cons block blocks
+ if choices ; good file
+ make-scenefile enter content choices
+ . #f
+
+define : format-enter people
+ ##
+ tests
+ test-equal " Enter
+ Robert
+ Arne
+"
+ format-enter
+ ' "Robert" "Arne"
+ format #f " Enter\n~a\n"
+ string-join
+ map : λ(name) : string-append " " name
+ . people
+ . "\n"
+
+define : format-one-paragraph paragraph
+ ##
+ tests
+ test-equal " Robert
+ It would be nice to be able to turn stories into playable websites.
+ I mean it.
+"
+ format-one-paragraph
+ list "Robert"
+ . "It would be nice to be able to turn stories into playable websites."
+ . "I mean it."
+ define person : car paragraph
+ define lines : cdr paragraph
+ format #f " ~a\n~a\n" person
+ string-join
+ map : λ(line) : string-append " " line
+ . lines
+ . "\n"
+
+define : format-choose questions-and-answers
+ ##
+ tests
+ test-equal " Choose
+ : Read again?
+ ,(welcome state)
+ : Exit?
+ ,(exit state)
+"
+ format-choose
+ ' "Read again?" "welcome" "Exit?" "exit"
+ when : not : zero? : modulo (length questions-and-answers) 2
+ error "The lines of questions and answers must be a multiple of two, but the length is ~a" : length questions-and-answers
+ format #f " Choose\n~a\n"
+ let loop : (lines '()) (qa questions-and-answers)
+ if : null? qa
+ string-join (reverse lines) "\n"
+ let
+ : question : format #f " : ~a" : first qa
+ answer : format #f " ,(~a state)" : second qa
+ loop : cons answer : cons question lines
+ drop qa 2
+
+
+define : format-scene scenename scenefile
+ ##
+ tests
+ test-equal "define-scene : welcome state
+ Enter
+ Robert
+ Arne
+
+ Robert
+ It would be nice to be able to turn stories into playable websites
+
+ Arne
+ I hope this works for you!
+
+ Choose
+ : Read again?
+ ,(welcome state)
+ : Exit?
+ ,(exit state)
+"
+ format-scene "welcome"
+ parse-file "tests-textfile-input/welcome"
+ define c
+ format #f "define-scene : ~a state\n~a\n~a\n~a"
+ . scenename
+ format-enter : scenefile-enter scenefile
+ string-join : map format-one-paragraph : scenefile-content scenefile
+ . "\n"
+ format-choose : scenefile-choose scenefile
+
+
+define : convert folder
+ define header : read-delimited "" : open-input-file "textfiles-to-game-header.w"
+ define footer : read-delimited "" : open-input-file "textfiles-to-game-footer.w"
+ define filenames
+ list-files folder
+ define contents
+ map parse-file filenames
+ define scenenames
+ map basename filenames
+ string-join
+ append : list header
+ map format-scene scenenames contents
+ list footer
+ . "\n\n"
+
+
+define : help args
+ format #t "~a <folder>\n\nthis writes into a file named textfiles-game.w" : car args
+
+define %this-module : current-module
+define : test args
+ doctests-testmod %this-module
+
+
+define : main args
+ cond
+ : null? : cdr args
+ help args
+ : member "--help" args
+ help args
+ : member "--test" args
+ test args
+ else
+ with-output-to-port : open-output-file "textfiles-game.w"
+ λ () : display : convert : second args
+ chmod "textfiles-game.w" #o700
+
+;; for emacs (progn (defun test-this-file () (interactive) (save-current-buffer) (async-shell-command (concat (buffer-file-name (current-buffer)) " --test"))) (local-set-key (kbd "<f9>") 'test-this-file)(defun analyze-plot () (interactive) (save-current-buffer) (async-shell-command (concat "./analyze-plot.w textfiles-game.w"))) (local-set-key (kbd "<f8>") 'analyze-plot))