|
|
|
#lang br
|
|
|
|
(require (for-syntax racket/string))
|
|
|
|
|
|
|
|
(provide define-verbs
|
|
|
|
define-thing
|
|
|
|
define-place
|
|
|
|
define-everywhere
|
|
|
|
|
|
|
|
show-current-place
|
|
|
|
show-inventory
|
|
|
|
|
|
|
|
have-thing?
|
|
|
|
take-thing!
|
|
|
|
drop-thing!
|
|
|
|
thing-state
|
|
|
|
set-thing-state!
|
|
|
|
|
|
|
|
(except-out (all-from-out br) #%module-begin)
|
|
|
|
(rename-out [module-begin #%module-begin]))
|
|
|
|
|
|
|
|
;; ============================================================
|
|
|
|
;; Overall module:
|
|
|
|
|
|
|
|
(define-macro (module-begin LINES ...)
|
|
|
|
#'(#%module-begin
|
|
|
|
LINES ...
|
|
|
|
|
|
|
|
(provide do-verb do-place)
|
|
|
|
(module+ main
|
|
|
|
(parameterize ([cmd-line-mode? #t])
|
|
|
|
(do-place)))))
|
|
|
|
|
|
|
|
|
|
|
|
;; ==============================================================
|
|
|
|
;; Process parse trees from the reader:
|
|
|
|
|
|
|
|
(provide txtadv-program)
|
|
|
|
(define-macro txtadv-program #'module-begin)
|
|
|
|
|
|
|
|
(provide verb-section)
|
|
|
|
(define-macro-cases verb-section
|
|
|
|
[(_ ((NAME0 . TRANSITIVE0?) (NAME . _) ... DESC) ...)
|
|
|
|
(with-shared-id
|
|
|
|
(in-verbs)
|
|
|
|
#'(define-verbs in-verbs
|
|
|
|
[(NAME0 . TRANSITIVE0?) (= NAME ...) DESC] ...))])
|
|
|
|
|
|
|
|
(provide everywhere-section)
|
|
|
|
(define-macro (everywhere-section [ID DESC] ...)
|
|
|
|
#'(define-everywhere everywhere-actions
|
|
|
|
([ID DESC] ...)))
|
|
|
|
|
|
|
|
(provide things-section)
|
|
|
|
(define-macro (things-section (THINGNAME (ACTIONNAME ACTIONDESC) ...) ...)
|
|
|
|
#'(begin (define-thing THINGNAME [ACTIONNAME ACTIONDESC] ...) ...))
|
|
|
|
|
|
|
|
(provide places-section)
|
|
|
|
(define-macro (places-section (PLACE-ID PLACE-DESC [PLACE-ITEM ...] [ACTIONNAME ACTIONDESC] ...) ...)
|
|
|
|
#'(begin (define-place PLACE-ID PLACE-DESC [PLACE-ITEM ...] ([ACTIONNAME ACTIONDESC] ...)) ...))
|
|
|
|
|
|
|
|
|
|
|
|
;; todo: consolidate the game-starters.
|
|
|
|
;; `start-game-at` works with s-exp language,
|
|
|
|
;; `start-section` works with text lang.
|
|
|
|
(provide start-game-at)
|
|
|
|
(define-macro (start-game-at WHERE)
|
|
|
|
#'(init-game WHERE
|
|
|
|
in-verbs
|
|
|
|
everywhere-actions))
|
|
|
|
|
|
|
|
(provide start-section)
|
|
|
|
(define-macro (start-section WHERE)
|
|
|
|
(with-shared-id
|
|
|
|
(in-verbs)
|
|
|
|
#'(init-game WHERE
|
|
|
|
in-verbs
|
|
|
|
everywhere-actions)))
|
|
|
|
|
|
|
|
;; ============================================================
|
|
|
|
;; Model:
|
|
|
|
|
|
|
|
;; Elements of the world:
|
|
|
|
(struct verb (aliases ; list of symbols
|
|
|
|
desc ; string
|
|
|
|
transitive?) #:transparent) ; boolean
|
|
|
|
(struct thing (name ; symbol
|
|
|
|
[state #:mutable] ; any value
|
|
|
|
actions) #:transparent) ; list of verb--thunk pairs
|
|
|
|
|
|
|
|
(struct place (desc ; string
|
|
|
|
[things #:mutable] ; list of things
|
|
|
|
actions) #:transparent) ; list of verb--thunk pairs
|
|
|
|
|
|
|
|
(define action-verb car)
|
|
|
|
(define action-response cdr)
|
|
|
|
|
|
|
|
;; Tables mapping names<->things for save and load
|
|
|
|
(define names (make-hash))
|
|
|
|
(define elements (make-hash))
|
|
|
|
|
|
|
|
(define (record-element! name val)
|
|
|
|
(hash-set! names name val)
|
|
|
|
(hash-set! elements val name))
|
|
|
|
|
|
|
|
(define (name->element name) (hash-ref names name #f))
|
|
|
|
(define (element->name obj) (hash-ref elements obj #f))
|
|
|
|
|
|
|
|
;; ============================================================
|
|
|
|
;; Macros for constructing and registering elements:
|
|
|
|
|
|
|
|
(define-macro (define-verbs ALL-ID [(ID . MAYBE-UNDERSCORE) SPEC ...] ...)
|
|
|
|
#'(begin
|
|
|
|
(define-one-verb (ID . MAYBE-UNDERSCORE) SPEC ...) ...
|
|
|
|
(record-element! 'ID ID) ...
|
|
|
|
(define ALL-ID (list ID ...))))
|
|
|
|
|
|
|
|
|
|
|
|
;; todo: the underscore arguments in cases 2 & 4 should be literal underscores, not wildcards
|
|
|
|
(define-macro (define-one-verb (ID . MAYBE-UNDERSCORE) . REST)
|
|
|
|
(with-pattern
|
|
|
|
([TRANSITIVE? (equal? '("_") (syntax->datum #'MAYBE-UNDERSCORE))]
|
|
|
|
[VERB-ARGS (syntax-case #'REST ()
|
|
|
|
[((= ALIAS ...) DESC)
|
|
|
|
#'((list 'ID 'ALIAS ...) DESC TRANSITIVE?)]
|
|
|
|
[else
|
|
|
|
#'((list 'ID) (symbol->string 'ID) TRANSITIVE?)])])
|
|
|
|
#'(define ID (verb . VERB-ARGS))))
|
|
|
|
|
|
|
|
|
|
|
|
(define-macro (define-thing ID [VERB-ARG EXPR] ...)
|
|
|
|
#'(begin
|
|
|
|
(define ID
|
|
|
|
(thing 'ID #f (list (cons VERB-ARG (λ () EXPR)) ...)))
|
|
|
|
(record-element! 'ID ID)))
|
|
|
|
|
|
|
|
|
|
|
|
(define-macro (define-place ID DESC (THING-ARG ...) ([VERB-ARG EXPR] ...))
|
|
|
|
#'(begin
|
|
|
|
(define ID (place DESC
|
|
|
|
(list THING-ARG ...)
|
|
|
|
(list (cons VERB-ARG (λ () EXPR)) ...)))
|
|
|
|
(record-element! 'ID ID)))
|
|
|
|
|
|
|
|
|
|
|
|
(define-macro (define-everywhere ID ([VERB-ARG EXPR] ...))
|
|
|
|
#'(define ID (list (cons VERB-ARG (λ () EXPR)) ...)))
|
|
|
|
|
|
|
|
;; ============================================================
|
|
|
|
;; Game state
|
|
|
|
|
|
|
|
(define cmd-line-mode? (make-parameter #f))
|
|
|
|
|
|
|
|
;; Initialized on startup:
|
|
|
|
(define game-verbs null) ; list of verbs
|
|
|
|
(define everywhere-actions null) ; list of verb--thunk pairs
|
|
|
|
|
|
|
|
;; Things carried by the player:
|
|
|
|
(define player-inventory null) ; list of things
|
|
|
|
|
|
|
|
;; Current location:
|
|
|
|
(define current-place #f) ; place (or #f until started)
|
|
|
|
|
|
|
|
;; Fuctions to be used by verb responses:
|
|
|
|
(define (have-thing? thing)
|
|
|
|
(memq thing player-inventory))
|
|
|
|
(define (take-thing! thing)
|
|
|
|
(set-place-things! current-place (remq thing (place-things current-place)))
|
|
|
|
(set! player-inventory (cons thing player-inventory)))
|
|
|
|
(define (drop-thing! thing)
|
|
|
|
(set-place-things! current-place (cons thing (place-things current-place)))
|
|
|
|
(set! player-inventory (remq thing player-inventory)))
|
|
|
|
|
|
|
|
;; ============================================================
|
|
|
|
;; Game execution
|
|
|
|
|
|
|
|
;; Show the player the current place, then get a command:
|
|
|
|
(define (do-place)
|
|
|
|
(show-current-place)
|
|
|
|
(when (cmd-line-mode?) (do-verb)))
|
|
|
|
|
|
|
|
;; Show the current place:
|
|
|
|
(define (show-current-place)
|
|
|
|
(printf "~a\n" (place-desc current-place))
|
|
|
|
(for-each
|
|
|
|
(λ (thing) (printf "There is a ~a here.\n" (thing-name thing)))
|
|
|
|
(place-things current-place)))
|
|
|
|
|
|
|
|
;; Get and handle a command:
|
|
|
|
|
|
|
|
(define (get-line)
|
|
|
|
(printf "> ")
|
|
|
|
(flush-output)
|
|
|
|
(read-line))
|
|
|
|
|
|
|
|
(define (do-verb [line (and (cmd-line-mode?) (get-line))])
|
|
|
|
(define input (if (eof-object? line)
|
|
|
|
'(quit)
|
|
|
|
(let ([port (open-input-string line)])
|
|
|
|
(for/list ([v (in-port read port)]) v))))
|
|
|
|
(if (and (list? input)
|
|
|
|
(andmap symbol? input)
|
|
|
|
(<= 1 (length input) 2))
|
|
|
|
(let* ([verb (car input)]
|
|
|
|
[response
|
|
|
|
(case (length input)
|
|
|
|
[(2) (handle-transitive-verb verb (cadr input))]
|
|
|
|
[(1) (handle-intransitive-verb verb)])]
|
|
|
|
[result (response)])
|
|
|
|
(cond
|
|
|
|
[(place? result)
|
|
|
|
(set! current-place result)
|
|
|
|
(do-place)]
|
|
|
|
[(string? result)
|
|
|
|
(printf "~a\n" result)
|
|
|
|
(when (cmd-line-mode?) (do-verb))]
|
|
|
|
[else (when (cmd-line-mode?) (do-verb))]))
|
|
|
|
(begin
|
|
|
|
(printf "I don't undertand what you mean.\n")
|
|
|
|
(when (cmd-line-mode?) (do-verb)))))
|
|
|
|
|
|
|
|
;; Handle an intransitive-verb command:
|
|
|
|
(define (handle-intransitive-verb verb)
|
|
|
|
(or
|
|
|
|
(find-verb verb (place-actions current-place))
|
|
|
|
(find-verb verb everywhere-actions)
|
|
|
|
(using-verb
|
|
|
|
verb game-verbs
|
|
|
|
(λ (verb)
|
|
|
|
(λ () (if (verb-transitive? verb)
|
|
|
|
(format "~a what?" (string-titlecase (verb-desc verb)))
|
|
|
|
(format "Can't ~a here." (verb-desc verb))))))
|
|
|
|
(λ () (format "I don't know how to ~a." verb))))
|
|
|
|
|
|
|
|
;; Handle a transitive-verb command:
|
|
|
|
(define (handle-transitive-verb verb-in obj)
|
|
|
|
(or (using-verb
|
|
|
|
verb-in game-verbs
|
|
|
|
(λ (verb)
|
|
|
|
(and
|
|
|
|
(verb-transitive? verb)
|
|
|
|
(cond
|
|
|
|
[(for/first ([thing (in-list (append (place-things current-place)
|
|
|
|
player-inventory))]
|
|
|
|
#:when (eq? (thing-name thing) obj))
|
|
|
|
thing)
|
|
|
|
=> (λ (thing)
|
|
|
|
(or (find-verb verb-in (thing-actions thing))
|
|
|
|
(λ ()
|
|
|
|
(format "Don't know how to ~a ~a."
|
|
|
|
(verb-desc verb) obj))))]
|
|
|
|
[else
|
|
|
|
(λ () (format "There's no ~a here to ~a." obj
|
|
|
|
(verb-desc verb)))]))))
|
|
|
|
(λ () (format "I don't know how to ~a ~a." verb-in obj))))
|
|
|
|
|
|
|
|
;; Show what the player is carrying:
|
|
|
|
(define (show-inventory)
|
|
|
|
(printf "You have")
|
|
|
|
(if (zero? (length player-inventory))
|
|
|
|
(printf " no items.")
|
|
|
|
(for-each (λ (thing)
|
|
|
|
(printf "\n a ~a" (thing-name thing)))
|
|
|
|
player-inventory))
|
|
|
|
(printf "\n"))
|
|
|
|
|
|
|
|
;; Look for a command match in a list of verb--response pairs,
|
|
|
|
;; and returns the response thunk if a match is found:
|
|
|
|
(define (find-verb cmd actions)
|
|
|
|
(for/first ([action (in-list actions)]
|
|
|
|
#:when (memq cmd (verb-aliases (action-verb action))))
|
|
|
|
(action-response action)))
|
|
|
|
|
|
|
|
;; Looks for a command in a list of verbs, and
|
|
|
|
;; applies `success-func' to the verb if one is found:
|
|
|
|
(define (using-verb cmd verbs success-func)
|
|
|
|
(for/first ([verb (in-list verbs)]
|
|
|
|
#:when (memq cmd (verb-aliases verb)))
|
|
|
|
(success-func verb)))
|
|
|
|
|
|
|
|
|
|
|
|
;; ============================================================
|
|
|
|
;; To go:
|
|
|
|
|
|
|
|
(define (init-game in-place
|
|
|
|
in-verbs
|
|
|
|
in-everywhere-actions)
|
|
|
|
(set! current-place in-place)
|
|
|
|
(set! game-verbs in-verbs)
|
|
|
|
(set! everywhere-actions in-everywhere-actions))
|