aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmar Polo <op@xglobe.in>2020-04-01 22:16:27 +0200
committerOmar Polo <op@xglobe.in>2020-04-01 22:16:27 +0200
commitcc9bbf6125fec1aaa9a7a1a06574a537fc3c3b29 (patch)
tree8962634f78ba97ab24482b1c4ada2b2bc357c68d
downloadtext-adventure-cc9bbf6125fec1aaa9a7a1a06574a537fc3c3b29.tar.gz
text-adventure-cc9bbf6125fec1aaa9a7a1a06574a537fc3c3b29.tar.bz2
initial commit
-rw-r--r--.gitignore5
-rw-r--r--Makefile33
-rw-r--r--README.md44
-rw-r--r--adventure.c19
-rw-r--r--adventure.h76
-rw-r--r--data_to_c.awk104
-rw-r--r--inventory.c64
-rw-r--r--io.c19
-rw-r--r--map.awk13
-rw-r--r--match.c103
-rw-r--r--misc.c109
-rw-r--r--object.data203
-rw-r--r--parseexec.c194
-rw-r--r--toggle.c102
14 files changed, 1088 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..29cd95d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.core
+object.h
+object.c
+adventure
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3260047
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+CC = cc
+CFLAGS = -Wall -g -O0
+LDFLAGS = -lreadline
+
+.PHONY: all clean
+
+all: adventure
+
+OBJ = match.o parseexec.o toggle.c inventory.o \
+ misc.o object.o io.o adventure.o
+
+adventure: object.h ${OBJ}
+ ${CC} -o adventure ${OBJ} ${LDFLAGS}
+
+object.c: object.data data_to_c.awk
+ awk -v pass=c1 -f data_to_c.awk object.data > object.c
+ awk -v pass=c2 -f data_to_c.awk object.data >> object.c
+
+object.h: object.data data_to_c.awk
+ awk -v pass=h -f data_to_c.awk object.data > object.h
+
+.SUFFIXES: .c .o
+.c.o:
+ ${CC} ${CFLAGS} -c $< -o $@
+
+clean:
+ rm -f *.o adventure object.c object.h map.gv map.png
+
+map.gv: map.awk object.data
+ awk -f map.awk object.data > $@
+
+map.png: map.gv
+ dot -Tpng -o $@ map.gv
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ff8b428
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+a text adventure
+=================
+
+"a text adventure" is a text adventure based on the (unfortunately)
+unfinished tutorial:
+http://home.hccnet.nl/r.helderman/adventures/htpataic01.html
+
+The code is more or less the same that you'll find on the tutorial,
+except for the stile and some minor difference in the structure.
+
+
+building
+--------
+
+ make
+
+It requires awk(1), a C compiler and the GNU readline library.
+
+It builds on OpenBSD without external packages installed btw.
+
+
+grammar
+-------
+
+While you can look for the grammar in `parseexec.c:/^parseexec`, I
+would ask you not to do so: you may read spoilers.
+
+Instead, try to express what you want to accomplish in plain english,
+it should work. (note: no punctuation or conjunctions are supported
+as of now)
+
+Some examples
+
+> quit
+
+> look around
+
+> look guard
+
+> go to east
+
+> pick up the coin
+
+> ...
diff --git a/adventure.c b/adventure.c
new file mode 100644
index 0000000..dd9c4e7
--- /dev/null
+++ b/adventure.c
@@ -0,0 +1,19 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "adventure.h"
+
+int
+main()
+{
+ printf("Welcome to Little Cave Adventure.\n");
+
+ exec_look_around();
+
+ while (getinput() && parseexec(line))
+ ;
+
+ printf("\nBye!\n");
+ return 0;
+}
diff --git a/adventure.h b/adventure.h
new file mode 100644
index 0000000..a90e0d0
--- /dev/null
+++ b/adventure.h
@@ -0,0 +1,76 @@
+#ifndef ADVENTURE_H
+#define ADVENTURE_H
+
+#include <stddef.h>
+
+enum distance {
+ dist_player,
+ dist_held,
+ dist_held_contained,
+ dist_location,
+ dist_here,
+ dist_here_contained,
+ dist_overthere,
+ dist_not_here,
+ dist_unknown_obj,
+ dist_no_obj_specified,
+};
+
+/* object.c */
+#include "object.h"
+
+/* match.c */
+#define MAX_PARAMS 26
+struct param {
+ const char *tag;
+ struct object *object;
+ enum distance distance;
+ size_t count;
+};
+extern struct param params[MAX_PARAMS];
+#define param_by_letter(l) (params + (l) - 'A')
+int match_command(const char*, const char*);
+
+/* parseexec.h */
+struct command {
+ int (*fn)(void);
+ const char *pattern;
+};
+int exec_look_around(void);
+int parseexec(const char*);
+
+/* misc.c */
+size_t list_objs_at_loc(struct object*);
+struct object *person_here(void);
+struct object *get_passage_to(struct object*);
+enum distance distance_to(struct object*);
+void move_player(struct object*);
+int weight_of_contents(struct object*);
+int object_within_reach(const char*, struct param*);
+
+/* io.c */
+extern char *line;
+int getinput();
+
+/* inventory.c */
+int move_object(struct param*, struct object *from, struct object *to);
+
+/* toggle.c */
+const char *cannot_be_opened(struct object*);
+const char *cannot_be_closed(struct object*);
+const char *cannot_be_locked(struct object*);
+const char *cannot_be_unlocked(struct object*);
+
+const char *is_already_open(struct object*);
+const char *is_already_closed(struct object*);
+const char *is_already_locked(struct object*);
+const char *is_already_unlocked(struct object*);
+
+const char *is_still_open(struct object*);
+const char *is_still_locked(struct object*);
+
+const char *toggle_backdoor(struct object*);
+const char *toggle_box(struct object*);
+const char *toggle_box_lock(struct object*);
+
+#endif
diff --git a/data_to_c.awk b/data_to_c.awk
new file mode 100644
index 0000000..37001a8
--- /dev/null
+++ b/data_to_c.awk
@@ -0,0 +1,104 @@
+BEGIN {
+ count = 0;
+ obj = "";
+ if (pass == "h") {
+ print "#ifndef OBJECT_H";
+ print "#define OBJECT_H";
+ print "";
+ }
+ if (pass == "c1") {
+ print "#include \"adventure.h\"\n";
+ print "";
+ print "static int always_true(struct object *o) { return 1; }";
+ print "";
+ }
+ if (pass == "c2") {
+ print "\nstruct object objs[] = {";
+ }
+}
+
+/^- / {
+ output_record(",");
+ obj = $2;
+ prop["condition"] = "always_true";
+ prop["description"] = "NULL";
+ prop["tags"] = "";
+ prop["location"] = "NULL";
+ prop["destination"] = "NULL";
+ prop["prospect"] = "";
+ prop["details"] = "\"You see nothing special.\"";
+ prop["contents"] = "\"You see\"";
+ prop["text_go"] = "\"You can't get any closer than this.\"";
+ prop["weight"] = "99";
+ prop["capacity"] = "9999";
+ prop["health"] = "0";
+ prop["open"] = "cannot_be_opened";
+ prop["close"] = "cannot_be_closed";
+ prop["lock"] = "cannot_be_locked";
+ prop["unlock"] = "cannot_be_unlocked";
+}
+
+obj && /^[ \t]+[a-z]/ {
+ name = $1;
+ $1 = "";
+ if (name in prop) {
+ prop[name] = $0;
+ if (/^[ \t]*\{/) {
+ prop[name] = name count;
+ if (pass == "c1")
+ print "static int " prop[name] "(struct object *obj) " $0;
+ }
+ } else if (pass == "c2") {
+ print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";
+ }
+}
+
+!obj && pass == (/^#include/ ? "c1" : "h") {
+ print;
+}
+
+END {
+ output_record("\n};");
+ if (pass == "h") {
+ print "";
+ print "#define obj_end\t(objs + " count ")";
+ print "#define valid_obj(obj)\t" \
+ "((obj) != NULL && (*(obj)->condition)((obj)))";
+ print "#define foreach_obj(obj)\t" \
+ "for (obj = objs; obj < obj_end; ++obj) if (valid_obj(obj))";
+ print "";
+ print "#endif";
+ }
+}
+
+function output_record(separator) {
+ if (obj) {
+ if (pass == "h") {
+ print "#define " obj "\t(objs + " count ")";
+ } else if (pass == "c1") {
+ print "static const char *tags" count "[] = {" prop["tags"] ", NULL};";
+ } else if (pass == "c2") {
+ print "\t{\t/* " count " = " obj " */";
+ print "\t\t" prop["condition"] ",";
+ print "\t\t" prop["description"] ",";
+ print "\t\ttags" count ",";
+ print "\t\t" prop["location"] ",";
+ print "\t\t" prop["destination"] ",";
+ print "\t\t" prop[prop["prospect"] ? "prospect" : "destination"] ",";
+ print "\t\t" prop["details"] ",";
+ print "\t\t" prop["contents"] ",";
+ print "\t\t" prop["text_go"] ",";
+ print "\t\t" prop["weight"] ",";
+ print "\t\t" prop["capacity"] ",";
+ print "\t\t" prop["health"] ",";
+ print "\t\t" prop["open"] ",";
+ print "\t\t" prop["close"] ",";
+ print "\t\t" prop["lock"] ",";
+ print "\t\t" prop["unlock"] ",";
+ print "\t}" separator;
+ delete prop;
+ }
+
+ count++;
+ }
+}
diff --git a/inventory.c b/inventory.c
new file mode 100644
index 0000000..77a11f7
--- /dev/null
+++ b/inventory.c
@@ -0,0 +1,64 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "adventure.h"
+
+int
+move_object(struct param *par, struct object *from, struct object *to)
+{
+ struct object *obj = par->object;
+ enum distance dist = par->distance;
+
+ if (obj == NULL || dist == dist_unknown_obj || dist == dist_not_here)
+ printf("I don't understand what item you mean.\n");
+ else if (to == NULL)
+ printf("There is nobody here to give that to.\n");
+ else if (obj == to)
+ printf("What's the meaning of putting a %s inside itself?\n", obj->tags[0]);
+ else if (from != obj->location) {
+ /* give the appropriate error message */
+ switch (dist) {
+ case dist_player:
+ printf("You should not be doing that to yourself.\n");
+ break;
+
+ case dist_held:
+ printf("You already have %s.\n", obj->description);
+ break;
+
+ case dist_location:
+ case dist_overthere:
+ printf("That's not an item.\n");
+ break;
+
+ case dist_here:
+ if (from == player)
+ printf("You have no %s.\n", par->tag);
+ else
+ printf("Sorry, %s has no %s.\n",
+ from->description,
+ par->tag);
+ break;
+
+ case dist_held_contained:
+ case dist_here_contained:
+ printf("Sorry, %s is holding it.\n",
+ obj->location->description);
+ break;
+
+ default:
+ /* we should have handled all other cases
+ * before this point */
+ abort();
+ }
+ } else if (obj->weight > to->capacity) {
+ printf("That is way too heavy.\n");
+ } else if (obj->weight + weight_of_contents(to) > to->capacity) {
+ printf("That would becamo too heavy.\n");
+ } else {
+ obj->location = to;
+ printf("OK.\n");
+ }
+
+ return 1;
+}
diff --git a/io.c b/io.c
new file mode 100644
index 0000000..f66b92f
--- /dev/null
+++ b/io.c
@@ -0,0 +1,19 @@
+#include "adventure.h"
+
+/* this in only needed for readline */
+#include <stdio.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+char *line = NULL;
+
+int
+getinput()
+{
+ line = readline("~> ");
+ if (line && *line)
+ add_history(line);
+ return line != NULL;
+}
+
diff --git a/map.awk b/map.awk
new file mode 100644
index 0000000..9a68b70
--- /dev/null
+++ b/map.awk
@@ -0,0 +1,13 @@
+BEGIN { print "digraph map {"; }
+/^. / { output_edge(); location = destination = ""; }
+$1 == "location" { location = $2; }
+$1 == "destination" { destination = $2; }
+$1 == "prospect" { prospect = $2; }
+END { output_edge(); print "}"; }
+
+function output_edge() {
+ if (location && destination)
+ print "\t" location " -> " destination;
+ if (location && prospect)
+ print "\t" location " -> " prospect " [style=dashed]";
+}
diff --git a/match.c b/match.c
new file mode 100644
index 0000000..0da6cc6
--- /dev/null
+++ b/match.c
@@ -0,0 +1,103 @@
+#include "adventure.h"
+
+#include <ctype.h>
+#include <string.h>
+
+struct param params[MAX_PARAMS];
+
+static const char *
+skip_spaces(const char *src)
+{
+ while (isspace(*src))
+ src++;
+ return src;
+}
+
+static const char *
+match_spaces(const char *src)
+{
+ return *src == '\0' || isspace(*src) ? skip_spaces(src) : NULL;
+}
+
+static const char *
+match_terminal(const char *src, char terminal)
+{
+ return terminal == ' '
+ ? match_spaces(src)
+ : tolower(*src) == tolower(terminal)
+ ? src + 1
+ : NULL;
+}
+
+static const char *
+match_tag(const char *src, const char *tag)
+{
+ while (src != NULL && *tag != '\0')
+ src = match_terminal(src, *tag++);
+ return src;
+}
+
+static int
+compare_with_param(const char *tag, enum distance dist, struct param *par)
+{
+ int diff = strlen(tag) - strlen(par->tag);
+ if (diff == 0)
+ diff = par->distance - dist;
+ if (diff == 0)
+ par->count++;
+ return diff;
+}
+
+static const char *
+match_param(const char *src, struct param *par, int loose)
+{
+ struct object *obj;
+ const char *rest_of_src = loose ? src + strlen(src) : NULL;
+
+ par->tag = src;
+ par->distance = *src == '\0' ? dist_no_obj_specified : dist_unknown_obj;
+
+ foreach_obj (obj) {
+ const char **tag;
+ enum distance dist = distance_to(obj);
+ for (tag = obj->tags; *tag != NULL; ++tag) {
+ const char *behind_match = match_tag(src, *tag);
+ if (behind_match != NULL &&
+ compare_with_param(*tag, dist, par) > 0 &&
+ (!loose || *skip_spaces(behind_match) == '\0')) {
+ par->tag = *tag;
+ par->object = obj;
+ par->distance = dist;
+ par->count = 1;
+ rest_of_src = behind_match;
+ }
+ }
+ }
+
+ return rest_of_src;
+}
+
+int
+match_command(const char *src, const char *pattern)
+{
+ struct param *par;
+
+ /* clean params */
+ for (par = params; par < params + MAX_PARAMS; ++par) {
+ par->object = NULL;
+ par->distance = dist_no_obj_specified;
+ par->count = 0;
+ }
+
+ /* actual parsing */
+ for (src = skip_spaces(src); src != NULL && *pattern != '\0'; ++pattern) {
+ src = isupper(*pattern)
+ ? match_param(src, param_by_letter(*pattern),
+ pattern[1] == '?')
+ : *pattern == '?'
+ ? src
+ : match_terminal(src, *pattern);
+ }
+
+ return src != NULL && *skip_spaces(src) == '\0';
+}
diff --git a/misc.c b/misc.c
new file mode 100644
index 0000000..e00c378
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,109 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "adventure.h"
+
+size_t
+list_objs_at_loc(struct object *location)
+{
+ size_t count = 0;
+ struct object *obj;
+
+ foreach_obj (obj) {
+ if (obj != player && obj->location == location) {
+ if (count++ == 0)
+ printf("%s:\n", location->contents);
+ printf("%s\n", obj->description);
+ }
+ }
+
+ return count;
+}
+
+struct object *
+person_here(void)
+{
+ struct object *obj;
+
+ foreach_obj (obj) {
+ if (distance_to(obj) == dist_here && obj->health > 0)
+ return obj;
+ }
+
+ return NULL;
+}
+
+struct object *
+get_passage_to(struct object *target)
+{
+ struct object *obj;
+
+ foreach_obj (obj) {
+ if (obj->location == player->location &&
+ obj->prospect == target)
+ return obj;
+ }
+
+ return NULL;
+}
+
+enum distance
+distance_to(struct object *obj)
+{
+ return
+ !valid_obj(obj) ? dist_unknown_obj :
+ obj == player ? dist_player :
+ obj == player->location ? dist_location :
+ obj->location == player ? dist_held :
+ obj->location == player->location ? dist_here :
+ get_passage_to(obj) != NULL ? dist_overthere :
+ !valid_obj(obj->location) ? dist_not_here :
+ obj->location->location == player ? dist_held_contained :
+ obj->location->location == player->location ? dist_here_contained :
+ dist_not_here;
+}
+
+void
+move_player(struct object *passage)
+{
+ printf("%s\n", passage->text_go);
+ if (passage->destination != NULL) {
+ player->location = passage->destination;
+ printf("\n");
+ exec_look_around();
+ }
+}
+
+int
+weight_of_contents(struct object *container)
+{
+ int sum = 0;
+ struct object *obj;
+
+ foreach_obj (obj) {
+ if (obj->location == container)
+ sum += obj->weight;
+ }
+ return sum;
+}
+
+int
+object_within_reach(const char *verb, struct param *par)
+{
+ int ok = 0;
+
+ enum distance dist = par->distance;
+
+ if (dist > dist_not_here)
+ printf("I don't understand what you want to %s.\n", verb);
+ else if (dist == dist_not_here)
+ printf("You don't see any %s here.\n", par->tag);
+ else if (dist >= dist_here_contained)
+ printf("That is out of reach.\n");
+ else if (par->count > 1)
+ printf("Multiple choices to %s; be more specific.\n", verb);
+ else
+ ok = 1;
+
+ return ok;
+}
diff --git a/object.data b/object.data
new file mode 100644
index 0000000..31f20e8
--- /dev/null
+++ b/object.data
@@ -0,0 +1,203 @@
+#include <stdio.h>
+
+struct object {
+ int (*condition)(struct object*);
+ const char *description;
+ const char **tags;
+ struct object *location;
+ struct object *destination;
+ struct object *prospect;
+ const char *details;
+ const char *contents;
+ const char *text_go;
+ int weight;
+ int capacity;
+ int health;
+ const char *(*open)(struct object*);
+ const char *(*close)(struct object*);
+ const char *(*lock)(struct object*);
+ const char *(*unlock)(struct object*);
+};
+
+extern struct object objs[];
+
+- field
+ description "an open field"
+ tags "field"
+ details "The filed is a nice and quiet place under a clear blue sky."
+
+- cave
+ description "a little cave"
+ tags "cave"
+ details "The cave is just a cold, damp, rocky chamber."
+
+- silver
+ description "a silver coin"
+ tags "silver", "coin", "sirver coin"
+ location field
+ details "The coin has an eagle on the obverse."
+ weight 1
+
+- gold
+ description "a gold coin"
+ tags "gold", "coin", "gold coin"
+ location cave
+ details "The shiny coin seems to be a rare and priceless artefact."
+ weight 1
+
+- guard
+ description "a burly guard"
+ tags "guard", "burly guard"
+ location field
+ details "The guard is a really big fellow."
+ contents "He has"
+ health 100
+ capacity 20
+
+- player
+ description "yourself"
+ tags "me"
+ location field
+ details "You would need a mirror to look at yourself."
+ contents "You have"
+ health 100
+ capacity 20
+
+- into_cave
+ condition { return guard->health == 0 || silver->location == guard; }
+ description "a cave entrance to the east"
+ tags "east", "entrance"
+ location field
+ destination cave
+ details "The entrance is just a narrow opening in a small outcrop."
+ text_go "You walk into the cave."
+ open is_already_open
+
+- into_cave_blocked
+ condition { return guard->health > 0 && silver->location != guard; }
+ description "a cave entrance to the east"
+ tags "east", "entrance"
+ location field
+ prospect cave
+ details "The entrance is just a narrow opening in a small outcrop."
+ text_go "The guard stops you from walking into the cave."
+ open is_already_open
+
+- exit_cave
+ description "a way out to the west"
+ tags "west", "out"
+ location cave
+ destination field
+ details "Sunlight pours in through an opening in the cave's wall."
+ text_go "You walk out of the cave."
+ open is_already_open
+
+- wall_field
+ description "dense forest all around"
+ tags "west", "north", "south", "forest"
+ location field
+ details "The field is surrounded by trees and undergrowth."
+ text_go "Dense forest is blocking the way."
+
+- wall_cave
+ description "solid rock all around"
+ tags "east", "north", "rock"
+ location cave
+ details "carved in stone is a secret password 'abccb'."
+ text_go "Solid rock is blocking the way."
+
+- backroom
+ description "a backroom"
+ tags "backroom"
+ details "The room is dusty and messy."
+
+- wall_backroom
+ description "solid rock all around"
+ tags "east", "west", "south", "rock"
+ location backroom
+ details "Trendy wallpaper covers the rock walls."
+ text_go "Solid rock is blocking the way."
+
+- open_door_to_backroom
+ description "an open door to the south"
+ tags "south", "door", "doorway"
+ destination backroom
+ details "The door is open."
+ text_go "You walk through the door into the backroom."
+ open is_already_open
+ close toggle_backdoor
+
+- closed_door_to_backroom
+ description "a closed door to the south"
+ tags "south", "door", "doorway"
+ location cave
+ prospect backroom
+ details "The door is closed."
+ text_go "The door is closed."
+ open toggle_backdoor
+ close is_already_closed
+
+- open_door_to_cave
+ description "an open door to the north"
+ tags "north", "door", "doorway"
+ destination cave
+ details "The door is open"
+ text_go "You walk through the door into the cave."
+ open is_already_open
+ close toggle_backdoor
+
+- closed_door_to_cave
+ description "a closed door to the north"
+ tags "north", "door", "doorway"
+ location backroom
+ prospect cave
+ details "The door is closed."
+ text_go "The door is closed."
+ open toggle_backdoor
+ close is_already_closed
+
+- open_box
+ description "a wooden box"
+ tags "box", "wooden box"
+ details "The box is open."
+ weight 5
+ capacity 10
+ open is_already_open
+ close toggle_box
+ lock is_still_open
+ unlock is_already_open
+
+- closed_box
+ description "a wooden box"
+ tags "box", "wooden box"
+ details "The box is closed."
+ weight 5
+ open toggle_box
+ close is_already_closed
+ lock toggle_box_lock
+ unlock is_already_unlocked
+
+- locked_box
+ description "a wooden box"
+ tags "box", "wooden box"
+ location backroom
+ details "The box is closed."
+ weight 5
+ open is_still_locked
+ close is_already_closed
+ lock is_already_locked
+ unlock toggle_box_lock
+
+- key_for_box
+ description "a tiny key"
+ tags "key", "tiny key"
+ location cave
+ details "The key is really small and shiny."
+ weight 1
+
+- knife
+ description "a small, rusty knife"
+ tags "knife", "rusty knife", "small knife"
+ location open_box
+ details "This knife has surely seen better times."
+ weight 1
diff --git a/parseexec.c b/parseexec.c
new file mode 100644
index 0000000..a85a81d
--- /dev/null
+++ b/parseexec.c
@@ -0,0 +1,194 @@
+#include "adventure.h"
+
+#include <stdio.h>
+
+int
+exec_quit(void)
+{
+ return 0;
+}
+
+int
+exec_no_match(void)
+{
+ struct param *par = param_by_letter('A');
+
+ if (par->distance != dist_no_obj_specified)
+ printf("I don't know how to %s.\n", par->tag);
+ return 1;
+}
+
+int
+exec_look_around(void)
+{
+ printf("You are in %s.\n", player->location->description);
+ list_objs_at_loc(player->location);
+ return 1;
+}
+
+int
+exec_look(void)
+{
+ struct param *par = param_by_letter('A');
+ struct object *obj = par->object;
+ enum distance dist = par->distance;
+
+ if (dist >= dist_unknown_obj)
+ printf("I don't understand what you want to see.\n");
+ else if (dist == dist_not_here)
+ printf("You don't see any %s here.\n", par->tag);
+ else if (dist == dist_overthere)
+ printf("You squeeze your eyes, but %s is too far away.\n", par->tag);
+ else if (dist == dist_here_contained)
+ printf("Hard to see, try to get it first.\n");
+ else {
+ printf("%s\n", obj->details);
+ list_objs_at_loc(obj);
+ }
+
+ return 1;
+}
+
+int
+exec_go(void)
+{
+ struct param *par = param_by_letter('A');
+ struct object *obj = par->object;
+ enum distance dist = par->distance;
+
+ if (dist >= dist_unknown_obj)
+ printf("I don't understand where you want to go.\n");
+ else if (dist == dist_location)
+ printf("You are already there.\n");
+ else if (dist == dist_overthere)
+ move_player(get_passage_to(obj));
+ else if (dist == dist_here)
+ move_player(obj);
+ else if (dist < dist_not_here)
+ printf("You can't get any closer than this.\n");
+ else
+ printf("You don't see any %s here.\n", "XXX");
+
+ return 1;
+}
+
+int
+exec_get_from(void)
+{
+ return move_object(param_by_letter('A'), param_by_letter('B')->object, player);
+}
+
+int
+exec_get(void)
+{
+ return move_object(param_by_letter('A'), player->location, player);
+}
+
+int
+exec_drop(void)
+{
+ return move_object(param_by_letter('A'), player, player->location);
+}
+
+int
+exec_give(void)
+{
+ return move_object(param_by_letter('A'), player, person_here());
+}
+
+int
+exec_ask(void)
+{
+ return move_object(param_by_letter('A'), person_here(), player);
+}
+
+int
+exec_put_in(void)
+{
+ return move_object(param_by_letter('A'), player, param_by_letter('B')->object);
+}
+
+int
+exec_inventory(void)
+{
+ if (list_objs_at_loc(player) == 0)
+ printf("You are empty-handed.\n");
+ return 1;
+}
+
+int
+exec_open(void)
+{
+ struct param *par = param_by_letter('A');
+ if (object_within_reach("open", par))
+ printf("%s\n", (par->object->open)(par->object));
+ return 1;
+}
+
+int
+exec_close(void)
+{
+ struct param *par = param_by_letter('A');
+ if (object_within_reach("close", par))
+ printf("%s\n", (par->object->close)(par->object));
+ return 1;
+}
+
+int
+exec_lock(void)
+{
+ struct param *par = param_by_letter('A');
+ if (object_within_reach("lock", par))
+ printf("%s\n", (par->object->lock)(par->object));
+ return 1;
+}
+
+int
+exec_unlock(void)
+{
+ struct param *par = param_by_letter('A');
+ if (object_within_reach("unlock", par))
+ printf("%s\n", (par->object->unlock)(par->object));
+ return 1;
+}
+
+int
+parseexec(const char *input)
+{
+ static const struct command commands[] = {
+ {&exec_quit, "quit"},
+ {&exec_look_around, "look"},
+ {&exec_look_around, "look around"},
+ {&exec_look, "look at A?"},
+ {&exec_look, "look A?"},
+ {&exec_go, "go to the A?"},
+ {&exec_go, "go to A?"},
+ {&exec_go, "go A?"},
+ {&exec_get_from, "get A from B?"},
+ {&exec_get, "get the A?"},
+ {&exec_get, "get A?"},
+ {&exec_get, "pick up the A?"},
+ {&exec_get, "pick up a A?"},
+ {&exec_get, "pick up A?"},
+ {&exec_get, "pick the A?"},
+ {&exec_get, "pick a A?"},
+ {&exec_get, "pick A?"},
+ {&exec_put_in, "put A in B?"},
+ {&exec_put_in, "drop A in B?"},
+ {&exec_drop, "drop A?"},
+ {&exec_give, "give A?"},
+ {&exec_ask, "ask A?"},
+ {&exec_inventory, "inventory"},
+ {&exec_open, "open A?"},
+ {&exec_close, "close A?"},
+ {&exec_lock, "lock A?"},
+ {&exec_unlock, "unlock A?"},
+ {&exec_no_match, "A?"},
+ };
+
+ const struct command *cmd;
+ for (cmd = commands; !match_command(input, cmd->pattern); ++cmd)
+ ;
+
+ return (*cmd->fn)();
+}
diff --git a/toggle.c b/toggle.c
new file mode 100644
index 0000000..b058a86
--- /dev/null
+++ b/toggle.c
@@ -0,0 +1,102 @@
+#include <assert.h>
+
+#include "adventure.h"
+
+static void
+swap_locations(struct object *a, struct object *b)
+{
+ struct object *t;
+
+ assert(a != NULL);
+ assert(b != NULL);
+
+ t = a->location;
+ a->location = b->location;
+ b->location = t;
+}
+
+const char *
+cannot_be_opened(struct object *o)
+{
+ return "That cannot be opened.";
+}
+
+const char *
+cannot_be_closed(struct object *o)
+{
+ return "That cannot be closed.";
+}
+
+const char *
+cannot_be_locked(struct object *o)
+{
+ return "That cannot be locked.";
+}
+
+const char *
+cannot_be_unlocked(struct object *o)
+{
+ return "That cannot be unlocked.";
+}
+
+const char *
+is_already_open(struct object *o)
+{
+ return "That is already open.";
+}
+
+const char *
+is_already_closed(struct object *o)
+{
+ return "That is already closed.";
+}
+
+const char *
+is_already_locked(struct object *o)
+{
+ return "That is already locked.";
+}
+
+const char *
+is_already_unlocked(struct object *o)
+{
+ return "That is already unlocked.";
+}
+
+const char *
+is_still_open(struct object *o)
+{
+ return "That is still open.";
+}
+
+const char *
+is_still_locked(struct object *o)
+{
+ return "That is still locked.";
+}
+
+const char *
+toggle_backdoor(struct object *o)
+{
+ swap_locations(open_door_to_backroom, closed_door_to_backroom);
+ swap_locations(open_door_to_cave, closed_door_to_cave);
+ return "OK.";
+}
+
+const char *
+toggle_box(struct object *o)
+{
+ swap_locations(open_box, closed_box);
+ return "OK.";
+}
+
+const char *
+toggle_box_lock(struct object *o)
+{
+ if (key_for_box->location == player) {
+ swap_locations(closed_box, locked_box);
+ return "OK.";
+ } else {
+ return "You try really hard, but the closed box won't open without a key.";
+ }
+}