Commit Diff


commit - /dev/null
commit + cc9bbf6125fec1aaa9a7a1a06574a537fc3c3b29
blob - /dev/null
blob + 29cd95d90f161b219410c6181daa459ca15e440f (mode 644)
--- /dev/null
+++ .gitignore
@@ -0,0 +1,5 @@
+*.o
+*.core
+object.h
+object.c
+adventure
blob - /dev/null
blob + 326004787dedf08975694a156caae88ed67e0459 (mode 644)
--- /dev/null
+++ 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
blob - /dev/null
blob + ff8b428ff5b02713dd5ed192c3c6ce852891f83e (mode 644)
--- /dev/null
+++ 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
+
+> ...
blob - /dev/null
blob + dd9c4e796cfe63fa782d616578cb3033971b34cd (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + a90e0d0ed0c4c829142d3bd19c00b6e035f6dfee (mode 644)
--- /dev/null
+++ 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
blob - /dev/null
blob + 37001a87d2710754ab4376a189c2b3ae7e52d575 (mode 644)
--- /dev/null
+++ 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++;
+	}
+}
blob - /dev/null
blob + 77a11f7e7236ff90b70630acebcebb3bb20193bb (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + f66b92f00ef5d3a6a19907bf40e89aa58af648d4 (mode 644)
--- /dev/null
+++ 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;
+}
+
blob - /dev/null
blob + 9a68b70c497797bb7dfabab1f996de78484735d2 (mode 644)
--- /dev/null
+++ 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]";
+}
blob - /dev/null
blob + 0da6cc66ddcfc9ad148e6596be1aea80aef3b4d7 (mode 644)
--- /dev/null
+++ 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';
+}
blob - /dev/null
blob + e00c37883127490644c8326a178aacfdc73c2646 (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + 31f20e809ccc1ae773db23c1c049a64bfffb3908 (mode 644)
--- /dev/null
+++ 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
blob - /dev/null
blob + a85a81d1d2f614c61f3e32515811ffecd7c5ef12 (mode 644)
--- /dev/null
+++ 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)();
+}
blob - /dev/null
blob + b058a863c858125593a005c6c08423a24f67b742 (mode 644)
--- /dev/null
+++ 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.";
+	}
+}