Chapter 12: Game Commands
Let's implement command parsing and the main game loop.
Command Parser Module
Create adventure/commands.sgl:
(define-library (adventure commands)
(import (adventure world)
(sigil string))
(export
parse-command
execute-command
command-look
command-go
command-take
command-drop
command-inventory
command-examine
command-use
command-help
command-quit)
(begin
;;; ========== Command Parsing ==========
;; Parse input into (verb . args) or #f
(define (parse-command input)
(let ((trimmed (string-trim input)))
(if (string=? trimmed "")
#f
(let ((words (string-split trimmed " ")))
(cons (string-downcase (car words))
(cdr words))))))
;;; ========== Direction Shortcuts ==========
(define (expand-direction word)
(cond
((string=? word "n") 'north)
((string=? word "s") 'south)
((string=? word "e") 'east)
((string=? word "w") 'west)
((string=? word "north") 'north)
((string=? word "south") 'south)
((string=? word "east") 'east)
((string=? word "west") 'west)
(else #f)))
;;; ========== Command Execution ==========
(define (execute-command cmd state)
(if (not cmd)
state
(let ((verb (car cmd))
(args (cdr cmd)))
(cond
;; Movement
((or (string=? verb "go")
(expand-direction verb))
(let ((dir (if (string=? verb "go")
(and (pair? args)
(expand-direction (car args)))
(expand-direction verb))))
(if dir
(command-go dir state)
(begin
(display "Go where?")
(newline)
state))))
;; Looking
((string=? verb "look")
(command-look state))
;; Taking items
((string=? verb "take")
(if (pair? args)
(command-take (string->symbol (car args)) state)
(begin
(display "Take what?")
(newline)
state)))
;; Dropping items
((string=? verb "drop")
(if (pair? args)
(command-drop (string->symbol (car args)) state)
(begin
(display "Drop what?")
(newline)
state)))
;; Inventory
((or (string=? verb "inventory")
(string=? verb "i"))
(command-inventory state))
;; Examine
((or (string=? verb "examine")
(string=? verb "x")
(string=? verb "look"))
(if (pair? args)
(command-examine (string->symbol (car args)) state)
(command-look state)))
;; Use
((string=? verb "use")
(if (pair? args)
(command-use (string->symbol (car args)) state)
(begin
(display "Use what?")
(newline)
state)))
;; Help
((string=? verb "help")
(command-help state))
;; Quit
((string=? verb "quit")
(command-quit state))
;; Unknown
(else
(display "I don't understand '")
(display verb)
(display "'.")
(newline)
state)))))
;;; ========== Command Implementations ==========
(define (command-look state)
(let* ((room-id (game-state-current-room state))
(rm (lookup-room state room-id)))
(newline)
(display "=== ")
(display (room-name rm))
(display " ===")
(newline)
(newline)
(display (room-description rm))
(newline)
;; Show items in room
(let ((items (room-items rm)))
(unless (null? items)
(newline)
(display "You can see:")
(newline)
(for-each
(lambda (item-id)
(let ((it (lookup-item state item-id)))
(display " - ")
(display (item-name it))
(newline)))
items)))
(newline)
state))
(define (command-go direction state)
(let* ((room-id (game-state-current-room state))
(next-room (get-exit-room state room-id direction)))
(if next-room
(let ((new-state (game-state state current-room: next-room)))
(command-look new-state))
(begin
(display "You can't go that way.")
(newline)
state))))
(define (command-take item-id state)
(let* ((room-id (game-state-current-room state))
(rm (lookup-room state room-id))
(room-items (room-items rm))
(item (lookup-item state item-id)))
(cond
((not item)
(display "There's no such thing.")
(newline)
state)
((not (memq item-id room-items))
(display "You don't see that here.")
(newline)
state)
((not (item-takeable item))
(display "You can't take that.")
(newline)
state)
(else
;; Remove from room, add to inventory
(let ((new-room (room rm
items: (filter (lambda (x)
(not (eq? x item-id)))
room-items))))
(display "Taken.")
(newline)
(game-state state
inventory: (cons item-id (game-state-inventory state))
rooms: (cons (cons room-id new-room)
(filter (lambda (p)
(not (eq? (car p) room-id)))
(game-state-rooms state)))))))))
(define (command-drop item-id state)
(let ((inv (game-state-inventory state)))
(if (not (memq item-id inv))
(begin
(display "You're not carrying that.")
(newline)
state)
(let* ((room-id (game-state-current-room state))
(rm (lookup-room state room-id))
(new-room (room rm items: (cons item-id (room-items rm)))))
(display "Dropped.")
(newline)
(game-state state
inventory: (filter (lambda (x) (not (eq? x item-id))) inv)
rooms: (cons (cons room-id new-room)
(filter (lambda (p)
(not (eq? (car p) room-id)))
(game-state-rooms state))))))))
(define (command-inventory state)
(let ((inv (game-state-inventory state)))
(if (null? inv)
(display "You're not carrying anything.")
(begin
(display "You are carrying:")
(newline)
(for-each
(lambda (item-id)
(let ((it (lookup-item state item-id)))
(display " - ")
(display (item-name it))
(newline)))
inv)))
(newline)
state))
(define (command-examine item-id state)
(let ((inv (game-state-inventory state))
(room-items (get-room-items state (game-state-current-room state)))
(item (lookup-item state item-id)))
(cond
((not item)
(display "There's no such thing.")
(newline))
((or (memq item-id inv)
(memq item-id room-items))
(display (item-description item))
(newline))
(else
(display "You don't see that here.")
(newline)))
state))
(define (command-use item-id state)
;; Special handling for the key at the front door
(cond
((and (eq? item-id 'key)
(memq 'key (game-state-inventory state))
(eq? (game-state-current-room state) 'hallway))
(newline)
(display "You insert the golden key into the front door's lock.")
(newline)
(display "With a satisfying *click*, the door swings open!")
(newline)
(display "Sunlight floods in. You step outside, finally free.")
(newline)
(newline)
(display "*** CONGRATULATIONS! You escaped! ***")
(newline)
(game-state state won: #t))
((not (memq item-id (game-state-inventory state)))
(display "You're not carrying that.")
(newline)
state)
(else
(display "You can't use that here.")
(newline)
state)))
(define (command-help state)
(display "Commands:")
(newline)
(display " look - Describe your surroundings")
(newline)
(display " go <dir> - Move in a direction (n/s/e/w)")
(newline)
(display " take <item> - Pick up an item")
(newline)
(display " drop <item> - Drop an item")
(newline)
(display " inventory (i) - List what you're carrying")
(newline)
(display " examine <item>- Look at something closely")
(newline)
(display " use <item> - Use an item")
(newline)
(display " help - Show this help")
(newline)
(display " quit - Exit the game")
(newline)
(newline)
state)
(define (command-quit state)
(display "Thanks for playing!")
(newline)
(game-state state won: 'quit))))Main Game Module
Create adventure/game.sgl:
(define-library (adventure game)
(import (adventure world)
(adventure commands))
(export run-game)
(begin
(define (show-intro)
(display "========================================")
(newline)
(display " THE LOST KEY")
(newline)
(display " A Text Adventure")
(newline)
(display "========================================")
(newline)
(newline)
(display "You wake up in an unfamiliar house.")
(newline)
(display "The front door is locked, but there")
(newline)
(display "must be a key somewhere...")
(newline)
(newline)
(display "(Type 'help' for commands)")
(newline))
(define (game-loop state)
;; Check for game over
(cond
((eq? (game-state-won state) #t)
state) ; Player won, exit loop
((eq? (game-state-won state) 'quit)
state) ; Player quit
(else
;; Get input
(display "> ")
(let ((input (read-line)))
(if (eof-object? input)
state
(let* ((cmd (parse-command input))
(new-state (execute-command cmd state)))
(game-loop new-state)))))))
(define (run-game)
(show-intro)
(let ((state (create-world)))
(command-look state)
(game-loop state)))))Running the Game
Run from your project directory (use -L . to find the adventure modules):
sigil eval -L . -f adventure/main.sglYou should see the intro, then be able to explore:
========================================
THE LOST KEY
A Text Adventure
========================================
You wake up in an unfamiliar house.
The front door is locked, but there
must be a key somewhere...
(Type 'help' for commands)
=== The Kitchen ===
A cozy kitchen with copper pots hanging from the ceiling.
The morning light streams through a window over the sink.
A door leads east to the hallway.
You can see:
- a kitchen knife
- a red apple
> e
=== The Hallway ===
...Walkthrough
To win:
e— Go to hallways— Go to gardentake key— Pick up the keyn— Back to hallwayuse key— Escape!
Practice Exercises
- Add a
readcommand for the note - Add more items and rooms
- Create a puzzle that requires multiple items
- Add synonyms (e.g., "grab" = "take")
What's Next
Let's polish the game with save/load and refinements.