1009 lines
26 KiB
Markdown
1009 lines
26 KiB
Markdown
# 15.赋予明暗
|
||
|
||
在许多冒险中,灯是一个重要的物品。没有它,你就无法穿越前方的黑暗洞穴。
|
||
|
||
在黑暗中的效果因游戏而异。通常情况下,它使命令 "look "没有效果。在一些游戏中(如 Zork),黑暗是致命的。在其他游戏中,只要你画出了黑暗区域的详细地图,在没有光源的情况下,你仍然可以取得进展。
|
||
|
||
我们的游戏将保持在这两者之间;在黑暗中不会让你被杀,但你也不能进入任何通道 (具有光亮的通道将是一个例外)。对我们来说,让玩家跑进一个黑暗的区域,而没有不让他机会回到他来的地方,似乎是不公平的。
|
||
|
||
好吧,所以首先,我们来设计在黑暗中玩家无法看到周围环境。
|
||
|
||
## location.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
#include "match.h"
|
||
#include "noun.h"
|
||
|
||
bool executeLookAround(void)
|
||
{
|
||
if (isLit(player->location))
|
||
{
|
||
printf("You are in %s.\n", player->location->description);
|
||
}
|
||
else
|
||
{
|
||
printf("It is very dark in here.\n");
|
||
}
|
||
listObjectsAtLocation(player->location);
|
||
return true;
|
||
}
|
||
|
||
bool executeLook(void)
|
||
{
|
||
OBJECT *obj = getVisible("what you want to look at", params[0]);
|
||
switch (getDistance(player, obj))
|
||
{
|
||
case distHereContained:
|
||
printf("Hard to see, try to get it first.\n");
|
||
break;
|
||
case distOverthere:
|
||
printf("Too far away, move closer please.\n");
|
||
break;
|
||
case distNotHere:
|
||
printf("You don't see any %s here.\n", params[0]);
|
||
break;
|
||
case distUnknownObject:
|
||
// already handled by getVisible
|
||
break;
|
||
default:
|
||
printf("%s\n", obj->details);
|
||
listObjectsAtLocation(obj);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static void movePlayer(OBJECT *passage)
|
||
{
|
||
printf("%s\n", passage->textGo);
|
||
if (passage->destination != NULL)
|
||
{
|
||
player->location = passage->destination;
|
||
printf("\n");
|
||
executeLookAround();
|
||
}
|
||
}
|
||
|
||
bool executeGo(void)
|
||
{
|
||
OBJECT *obj = getVisible("where you want to go", params[0]);
|
||
switch (getDistance(player, obj))
|
||
{
|
||
case distOverthere:
|
||
movePlayer(getPassage(player->location, obj));
|
||
break;
|
||
case distNotHere:
|
||
printf("You don't see any %s here.\n", params[0]);
|
||
break;
|
||
case distUnknownObject:
|
||
// already handled by getVisible
|
||
break;
|
||
default:
|
||
movePlayer(obj);
|
||
}
|
||
return true;
|
||
}
|
||
```
|
||
|
||
其次,在黑暗中玩家无法看到或使用附近的物体。
|
||
|
||
## noun.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
|
||
static bool objectHasTag(OBJECT *obj, const char *noun)
|
||
{
|
||
if (noun != NULL && *noun != '\0')
|
||
{
|
||
const char **tag;
|
||
for (tag = obj->tags; *tag != NULL; tag++)
|
||
{
|
||
if (strcmp(*tag, noun) == 0) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static OBJECT ambiguousNoun;
|
||
|
||
static OBJECT *getObject(const char *noun, OBJECT *from, DISTANCE maxDistance)
|
||
{
|
||
OBJECT *obj, *res = NULL;
|
||
for (obj = objs; obj < endOfObjs; obj++)
|
||
{
|
||
if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance)
|
||
{
|
||
res = res == NULL ? obj : &ambiguousNoun;
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
OBJECT *getVisible(const char *intention, const char *noun)
|
||
{
|
||
OBJECT *obj = getObject(noun, player, distOverthere);
|
||
if (obj == NULL)
|
||
{
|
||
if (getObject(noun, player, distNotHere) == NULL)
|
||
{
|
||
printf("I don't understand %s.\n", intention);
|
||
}
|
||
else if (isLit(player->location))
|
||
{
|
||
printf("You don't see any %s here.\n", noun);
|
||
}
|
||
else
|
||
{
|
||
printf("It's too dark.\n");
|
||
}
|
||
}
|
||
else if (obj == &ambiguousNoun)
|
||
{
|
||
printf("Please be specific about which %s you mean.\n", noun);
|
||
obj = NULL;
|
||
}
|
||
return obj;
|
||
}
|
||
|
||
OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
|
||
{
|
||
OBJECT *obj = NULL;
|
||
if (from == NULL)
|
||
{
|
||
printf("I don't understand who you want to %s.\n", verb);
|
||
}
|
||
else if ((obj = getObject(noun, from, distHeldContained)) == NULL)
|
||
{
|
||
if (getObject(noun, player, distNotHere) == NULL)
|
||
{
|
||
printf("I don't understand what you want to %s.\n", verb);
|
||
}
|
||
else if (from == player)
|
||
{
|
||
printf("You are not holding any %s.\n", noun);
|
||
}
|
||
else
|
||
{
|
||
printf("There appears to be no %s you can get from %s.\n",
|
||
noun, from->description);
|
||
}
|
||
}
|
||
else if (obj == &ambiguousNoun)
|
||
{
|
||
printf("Please be specific about which %s you want to %s.\n",
|
||
noun, verb);
|
||
obj = NULL;
|
||
}
|
||
else if (obj == from)
|
||
{
|
||
printf("You should not be doing that to %s.\n", obj->description);
|
||
obj = NULL;
|
||
}
|
||
return obj;
|
||
}
|
||
```
|
||
|
||
在这两种情况下,我们都使用了一个函数 isLit。它在 misc.c 中被定义(并且使用量增多了)。
|
||
|
||
## misc.h
|
||
|
||
```c
|
||
typedef enum {
|
||
distSelf,
|
||
distHeld,
|
||
distHeldContained,
|
||
distLocation,
|
||
distHere,
|
||
distHereContained,
|
||
distOverthere,
|
||
distNotHere,
|
||
distUnknownObject
|
||
} DISTANCE;
|
||
|
||
extern bool isHolding(OBJECT *container, OBJECT *obj);
|
||
extern bool isLit(OBJECT *location);
|
||
extern OBJECT *getPassage(OBJECT *from, OBJECT *to);
|
||
extern DISTANCE getDistance(OBJECT *from, OBJECT *to);
|
||
extern OBJECT *actorHere(void);
|
||
extern int listObjectsAtLocation(OBJECT *location);
|
||
```
|
||
|
||
## misc.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
|
||
bool isHolding(OBJECT *container, OBJECT *obj)
|
||
{
|
||
return validObject(obj) && obj->location == container;
|
||
}
|
||
|
||
bool isLit(OBJECT *target)
|
||
//检测光亮
|
||
{
|
||
OBJECT *obj;
|
||
if (validObject(target))
|
||
{
|
||
if (target->light > 0)
|
||
{
|
||
return true;
|
||
}
|
||
for (obj = objs; obj < endOfObjs; obj++)
|
||
{
|
||
if (validObject(obj) && obj->light > 0 &&
|
||
(isHolding(target, obj) || isHolding(target, obj->location)))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static bool isNoticeable(OBJECT *obj)
|
||
{
|
||
return obj->location == player ||
|
||
isLit(obj) || isLit(obj->prospect) || isLit(player->location);
|
||
}
|
||
|
||
OBJECT *getPassage(OBJECT *from, OBJECT *to)
|
||
{
|
||
if (from != NULL && to != NULL)
|
||
{
|
||
OBJECT *obj;
|
||
for (obj = objs; obj < endOfObjs; obj++)
|
||
{
|
||
if (isHolding(from, obj) && obj->prospect == to)
|
||
{
|
||
return obj;
|
||
}
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
DISTANCE getDistance(OBJECT *from, OBJECT *to)
|
||
{
|
||
return to == NULL ? distUnknownObject :
|
||
!validObject(to) ? distNotHere :
|
||
to == from ? distSelf :
|
||
isHolding(from, to) ? distHeld :
|
||
!isNoticeable(to) ? distNotHere :
|
||
isHolding(to, from) ? distLocation :
|
||
isHolding(from->location, to) ? distHere :
|
||
isHolding(from, to->location) ? distHeldContained :
|
||
isHolding(from->location, to->location) ? distHereContained :
|
||
getPassage(from->location, to) != NULL ? distOverthere :
|
||
distNotHere;
|
||
}
|
||
|
||
OBJECT *actorHere(void)
|
||
{
|
||
OBJECT *obj;
|
||
for (obj = objs; obj < endOfObjs; obj++)
|
||
{
|
||
if (isHolding(player->location, obj) && obj != player &&
|
||
isNoticeable(obj) && obj->health > 0)
|
||
{
|
||
return obj;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
int listObjectsAtLocation(OBJECT *location)
|
||
{
|
||
int count = 0;
|
||
OBJECT *obj;
|
||
for (obj = objs; obj < endOfObjs; obj++)
|
||
{
|
||
if (obj != player && isHolding(location, obj) && isNoticeable(obj))
|
||
{
|
||
if (count++ == 0)
|
||
{
|
||
printf("%s:\n", location->contents);
|
||
}
|
||
printf("%s\n", obj->description);
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
```
|
||
|
||
注意:
|
||
|
||
- isLit 将被用来检查一个给定的位置是亮还是暗,但是,仅仅检查位置的属性光线是不够的,因为位置可能被一盏灯照亮。
|
||
- isNoticeable 函数相比 isLit 更进一步。它对每个物体都有效,而不仅仅是地点和灯。它还考虑到玩家库存中的物体总是可以被使用,即使是在黑暗中。
|
||
- 第 60 行:附近的物体仍然隐藏在黑暗中,被视为'不在这里'。这自然可以防止游戏泄露玩家不应该知道的物体的信息。
|
||
|
||
::: warning 🤔 思考题:你还能想到那哪些可以改进的地方吗?
|
||
:::
|
||
|
||
我们为实现 isLit 函数的功能从而使用了一个新的属性 light。
|
||
|
||
## object.awk
|
||
|
||
```awk
|
||
BEGIN {
|
||
count = 0;
|
||
obj = "";
|
||
if (pass == "c2") {
|
||
print "\nstatic bool alwaysTrue(void) { return true; }";
|
||
print "\nOBJECT objs[] = {";
|
||
}
|
||
}
|
||
|
||
/^- / {
|
||
outputRecord(",");
|
||
obj = $2;
|
||
prop["condition"] = "alwaysTrue";
|
||
prop["description"] = "NULL";
|
||
prop["tags"] = "";
|
||
prop["location"] = "NULL";
|
||
prop["destination"] = "NULL";
|
||
prop["prospect"] = "";
|
||
prop["details"] = "\"You see nothing special.\"";
|
||
prop["contents"] = "\"You see\"";
|
||
prop["textGo"] = "\"You can't get much closer than this.\"";
|
||
prop["weight"] = "99";
|
||
prop["capacity"] = "0";
|
||
prop["health"] = "0";
|
||
prop["light"] = "0";
|
||
prop["open"] = "cannotBeOpened";
|
||
prop["close"] = "cannotBeClosed";
|
||
prop["lock"] = "cannotBeLocked";
|
||
prop["unlock"] = "cannotBeUnlocked";
|
||
}
|
||
|
||
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 bool " prop[name] "(void) " $0;
|
||
}
|
||
}
|
||
else if (pass == "c2") {
|
||
print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\"";
|
||
}
|
||
}
|
||
|
||
!obj && pass == (/^#include/ ? "c1" : "h") {
|
||
print;
|
||
}
|
||
|
||
END {
|
||
outputRecord("\n};");
|
||
if (pass == "h") {
|
||
print "\n#define endOfObjs\t(objs + " count ")";
|
||
print "\n#define validObject(obj)\t" \
|
||
"((obj) != NULL && (*(obj)->condition)())";
|
||
}
|
||
}
|
||
|
||
function outputRecord(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["textGo"] ",";
|
||
print "\t\t" prop["weight"] ",";
|
||
print "\t\t" prop["capacity"] ",";
|
||
print "\t\t" prop["health"] ",";
|
||
print "\t\t" prop["light"] ",";
|
||
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++;
|
||
}
|
||
}
|
||
```
|
||
|
||
默认情况下,亮度为零意味着一个物体不发光。在大白天的每一个地点(通常是除了地下的所有地点)都会被赋予一个正 (大于 0) 的光线值。其实是什么值并不重要,只要它不是零就可以了。我们还将添加一盏灯,玩家可以携带它来穿越黑暗区域。
|
||
|
||
## object.txt
|
||
|
||
```txt
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "toggle.h"
|
||
|
||
typedef struct object {
|
||
bool (*condition)(void);
|
||
const char *description;
|
||
const char **tags;
|
||
struct object *location;
|
||
struct object *destination;
|
||
struct object *prospect;
|
||
const char *details;
|
||
const char *contents;
|
||
const char *textGo;
|
||
int weight;
|
||
int capacity;
|
||
int health;
|
||
int light;
|
||
void (*open)(void);
|
||
void (*close)(void);
|
||
void (*lock)(void);
|
||
void (*unlock)(void);
|
||
} OBJECT;
|
||
|
||
extern OBJECT objs[];
|
||
|
||
- field
|
||
description "an open field"
|
||
tags "field"
|
||
details "The field is a nice and quiet place under a clear blue sky."
|
||
capacity 9999
|
||
light 100
|
||
//到目前为止,场地是唯一有 "自然 "光线的地方。
|
||
|
||
- cave
|
||
description "a little cave"
|
||
tags "cave"
|
||
details "The cave is just a cold, damp, rocky chamber."
|
||
capacity 9999
|
||
|
||
- silver
|
||
description "a silver coin"
|
||
tags "silver", "coin", "silver 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 openBox
|
||
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 "yourself"
|
||
location field
|
||
details "You would need a mirror to look at yourself."
|
||
contents "You have"
|
||
health 100
|
||
capacity 20
|
||
|
||
- intoCave
|
||
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."
|
||
textGo "You walk into the cave."
|
||
open isAlreadyOpen
|
||
|
||
- intoCaveBlocked
|
||
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."
|
||
textGo "The guard stops you from walking into the cave."
|
||
open isAlreadyOpen
|
||
|
||
- exitCave
|
||
description "an exit to the west"
|
||
tags "west", "exit"
|
||
location cave
|
||
destination field
|
||
details "Sunlight pours in through an opening in the cave's wall."
|
||
textGo "You walk out of the cave."
|
||
open isAlreadyOpen
|
||
|
||
- wallField
|
||
description "dense forest all around"
|
||
tags "west", "north", "south", "forest"
|
||
location field
|
||
details "The field is surrounded by trees and undergrowth."
|
||
textGo "Dense forest is blocking the way."
|
||
|
||
- wallCave
|
||
description "solid rock all around"
|
||
tags "east", "north", "rock"
|
||
location cave
|
||
details "Carved in stone is a secret password 'abccb'."
|
||
textGo "Solid rock is blocking the way."
|
||
|
||
- backroom
|
||
description "a backroom"
|
||
tags "backroom"
|
||
details "The room is dusty and messy."
|
||
capacity 9999
|
||
|
||
- wallBackroom
|
||
description "solid rock all around"
|
||
tags "east", "west", "south", "rock"
|
||
location backroom
|
||
details "Trendy wallpaper covers the rock walls."
|
||
textGo "Solid rock is blocking the way."
|
||
|
||
- openDoorToBackroom
|
||
description "an open door to the south"
|
||
tags "south", "door", "doorway"
|
||
destination backroom
|
||
details "The door is open."
|
||
textGo "You walk through the door into a backroom."
|
||
open isAlreadyOpen
|
||
close toggleDoorToBackroom
|
||
|
||
- closedDoorToBackroom
|
||
description "a closed door to the south"
|
||
tags "south", "door", "doorway"
|
||
location cave
|
||
prospect backroom
|
||
details "The door is closed."
|
||
textGo "The door is closed."
|
||
open toggleDoorToBackroom
|
||
close isAlreadyClosed
|
||
|
||
- openDoorToCave
|
||
description "an open door to the north"
|
||
tags "north", "door", "doorway"
|
||
destination cave
|
||
details "The door is open."
|
||
textGo "You walk through the door into the cave."
|
||
open isAlreadyOpen
|
||
close toggleDoorToCave
|
||
|
||
- closedDoorToCave
|
||
description "a closed door to the north"
|
||
tags "north", "door", "doorway"
|
||
location backroom
|
||
prospect cave
|
||
details "The door is closed."
|
||
textGo "The door is closed."
|
||
open toggleDoorToCave
|
||
close isAlreadyClosed
|
||
|
||
- openBox
|
||
description "a wooden box"
|
||
tags "box", "wooden box"
|
||
details "The box is open."
|
||
weight 5
|
||
capacity 10
|
||
open isAlreadyOpen
|
||
close toggleBox
|
||
lock isStillOpen
|
||
unlock isAlreadyOpen
|
||
|
||
- closedBox
|
||
description "a wooden box"
|
||
tags "box", "wooden box"
|
||
details "The box is closed."
|
||
weight 5
|
||
open toggleBox
|
||
close isAlreadyClosed
|
||
lock toggleBoxLock
|
||
unlock isAlreadyUnlocked
|
||
|
||
- lockedBox
|
||
description "a wooden box"
|
||
tags "box", "wooden box"
|
||
location backroom
|
||
details "The box is closed."
|
||
weight 5
|
||
open isStillLocked
|
||
close isAlreadyClosed
|
||
lock isAlreadyLocked
|
||
unlock toggleBoxLock
|
||
|
||
- keyForBox
|
||
description "a tiny key"
|
||
tags "key", "tiny key"
|
||
location cave
|
||
details "The key is really small and shiny."
|
||
weight 1
|
||
|
||
- lampOff
|
||
description "a lamp"
|
||
tags "lamp"
|
||
location field
|
||
details "The lamp is off."
|
||
weight 5
|
||
|
||
- lampOn
|
||
description "a lamp"
|
||
tags "lamp"
|
||
details "The lamp is on."
|
||
weight 5
|
||
light 100
|
||
```
|
||
|
||
注意:对于灯,我们实际上有两个对象:打开的灯和关闭的灯,但玩家一次只能看到一个。它们一起作为一个单一的项目。
|
||
|
||
我们将添加一些命令,我们可以用来打开和关闭灯。
|
||
|
||
## parsexec.c
|
||
|
||
```c
|
||
#include <ctype.h>
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
#include "match.h"
|
||
#include "location.h"
|
||
#include "inventory.h"
|
||
#include "inventory2.h"
|
||
#include "openclose.h"
|
||
#include "onoff.h"
|
||
|
||
typedef struct
|
||
{
|
||
const char *pattern;
|
||
bool (*function)(void);
|
||
} COMMAND;
|
||
|
||
static bool executeQuit(void)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
static bool executeNoMatch(void)
|
||
{
|
||
const char *src = *params;
|
||
int len;
|
||
for (len = 0; src[len] != '\0' && !isspace(src[len]); len++);
|
||
if (len > 0) printf("I don't know how to '%.*s'.\n", len, src);
|
||
return true;
|
||
}
|
||
|
||
bool parseAndExecute(const char *input)
|
||
{
|
||
static const COMMAND commands[] =
|
||
{
|
||
{ "quit" , executeQuit },
|
||
{ "look" , executeLookAround },
|
||
{ "look around" , executeLookAround },
|
||
{ "look at A" , executeLook },
|
||
{ "look A" , executeLook },
|
||
{ "examine A" , executeLook },
|
||
{ "go to A" , executeGo },
|
||
{ "go A" , executeGo },
|
||
{ "get A from B" , executeGetFrom },
|
||
{ "get A" , executeGet },
|
||
{ "put A in B" , executePutIn },
|
||
{ "drop A in B" , executePutIn },
|
||
{ "drop A" , executeDrop },
|
||
{ "ask A from B" , executeAskFrom },
|
||
{ "ask A" , executeAsk },
|
||
{ "give A to B" , executeGiveTo },
|
||
{ "give A" , executeGive },
|
||
{ "inventory" , executeInventory },
|
||
{ "open A" , executeOpen },
|
||
{ "close A" , executeClose },
|
||
{ "lock A" , executeLock },
|
||
{ "unlock A" , executeUnlock },
|
||
{ "turn on A" , executeTurnOn },
|
||
{ "turn off A" , executeTurnOff },
|
||
{ "turn A on" , executeTurnOn },
|
||
{ "turn A off" , executeTurnOff },
|
||
{ "A" , executeNoMatch }
|
||
};
|
||
const COMMAND *cmd;
|
||
for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++);
|
||
return (*cmd->function)();
|
||
}
|
||
```
|
||
|
||
下面是这些命令的实现。
|
||
|
||
## onoff.h
|
||
|
||
```c
|
||
extern bool executeTurnOn(void);
|
||
extern bool executeTurnOff(void);
|
||
```
|
||
|
||
## onoff.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "match.h"
|
||
#include "reach.h"
|
||
#include "toggle.h"
|
||
|
||
bool executeTurnOn(void)
|
||
{
|
||
OBJECT *obj = reachableObject("what you want to turn on", params[0]);
|
||
if (obj != NULL)
|
||
{
|
||
if (obj == lampOff)
|
||
{
|
||
toggleLamp();
|
||
}
|
||
else
|
||
{
|
||
printf(obj == lampOn ? "The lamp is already on.\n"
|
||
: "You cannot turn that on.\n");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool executeTurnOff(void)
|
||
{
|
||
OBJECT *obj = reachableObject("what you want to turn off", params[0]);
|
||
if (obj != NULL)
|
||
{
|
||
if (obj == lampOn)
|
||
{
|
||
toggleLamp();
|
||
}
|
||
else
|
||
{
|
||
printf(obj == lampOff ? "The lamp is already off.\n"
|
||
: "You cannot turn that off.\n");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
```
|
||
|
||
为了打开和关闭灯,我们将使用我们用来打开和关闭门和盒子的相同技巧(见第 13 章)。
|
||
|
||
## toggle.h
|
||
|
||
```c
|
||
extern void cannotBeOpened(void);
|
||
extern void cannotBeClosed(void);
|
||
extern void cannotBeLocked(void);
|
||
extern void cannotBeUnlocked(void);
|
||
|
||
extern void isAlreadyOpen(void);
|
||
extern void isAlreadyClosed(void);
|
||
extern void isAlreadyLocked(void);
|
||
extern void isAlreadyUnlocked(void);
|
||
|
||
extern void isStillOpen(void);
|
||
extern void isStillLocked(void);
|
||
|
||
extern void toggleDoorToBackroom(void);
|
||
extern void toggleDoorToCave(void);
|
||
extern void toggleBox(void);
|
||
extern void toggleBoxLock(void);
|
||
|
||
extern void toggleLamp(void);
|
||
```
|
||
|
||
## toggle.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
#include "location.h"
|
||
|
||
static void swapLocations(const char *verb1, OBJECT *obj1,
|
||
const char *verb2, OBJECT *obj2)
|
||
{
|
||
OBJECT *tmp = obj1->location;
|
||
OBJECT *obj = tmp != NULL ? obj1 : obj2;
|
||
const char *verb = tmp != NULL ? verb1 : verb2;
|
||
obj1->location = obj2->location;
|
||
obj2->location = tmp;
|
||
if (verb != NULL) printf("You %s %s.\n", verb, obj->description);
|
||
}
|
||
|
||
void cannotBeOpened(void) { printf("That cannot be opened.\n"); }
|
||
void cannotBeClosed(void) { printf("That cannot be closed.\n"); }
|
||
void cannotBeLocked(void) { printf("That cannot be locked.\n"); }
|
||
void cannotBeUnlocked(void) { printf("That cannot be unlocked.\n"); }
|
||
|
||
void isAlreadyOpen(void) { printf("That is already open.\n"); }
|
||
void isAlreadyClosed(void) { printf("That is already closed.\n"); }
|
||
void isAlreadyLocked(void) { printf("That is already locked.\n"); }
|
||
void isAlreadyUnlocked(void) { printf("That is already unlocked.\n"); }
|
||
|
||
void isStillOpen(void) { printf("That is still open.\n"); }
|
||
void isStillLocked(void) { printf("That is locked.\n"); }
|
||
|
||
void toggleDoorToBackroom(void)
|
||
{
|
||
swapLocations(NULL, openDoorToCave, NULL, closedDoorToCave);
|
||
swapLocations("close", openDoorToBackroom, "open", closedDoorToBackroom);
|
||
}
|
||
|
||
void toggleDoorToCave(void)
|
||
{
|
||
swapLocations(NULL, openDoorToBackroom, NULL, closedDoorToBackroom);
|
||
swapLocations("close", openDoorToCave, "open", closedDoorToCave);
|
||
}
|
||
|
||
void toggleBox(void)
|
||
{
|
||
swapLocations("close", openBox, "open", closedBox);
|
||
}
|
||
|
||
void toggleBoxLock(void)
|
||
{
|
||
if (keyForBox->location == player)
|
||
{
|
||
swapLocations("lock", closedBox, "unlock", lockedBox);
|
||
}
|
||
else
|
||
{
|
||
printf("You don't have a key.\n");
|
||
}
|
||
}
|
||
|
||
void toggleLamp(void)
|
||
{
|
||
bool oldLit = isLit(player->location);
|
||
swapLocations("turn off", lampOn, "turn on", lampOff);
|
||
if (oldLit != isLit(player->location))
|
||
{
|
||
printf("\n");
|
||
executeLookAround();
|
||
}
|
||
}
|
||
```
|
||
|
||
注意:当在黑暗区域打开灯光时,我们立即让玩家看一下周围的环境。这与'go'的命令的行为是一致的:当你把目光投向一个地方时,'look'就会自动执行。
|
||
|
||
你可能注意到我们在这里做了同样的事情,当把灯关掉的时候,很明显,这只会返回 "这里很黑",但这似乎也是一个相关的观察。
|
||
|
||
那么,既然我们无论如何都在做 "look around "的工作,64~68 中"if "语句的意义何在?好吧,这可以防止在大白天(即在野外)打开或关闭灯时,或在同一房间内任何其他光源仍在工作时,进行无用的 "环顾"。
|
||
|
||
最后,我们将在我们生成的地图上标记出黑暗的位置。
|
||
|
||
## map.awk
|
||
|
||
```awk
|
||
BEGIN { print "digraph map {\n\tnode [style=filled]"; }
|
||
/^- / { outputEdges(); obj = $2; delete a; }
|
||
/^[ \t]/ { a[$1] = $2; }
|
||
END { outputEdges(); outputNodes(); print "}"; }
|
||
|
||
function outputEdges()
|
||
{
|
||
color[obj] = a["light"] ? "white" : "grey";
|
||
outputEdge(a["location"], a["destination"], "");
|
||
outputEdge(a["location"], a["prospect"], " [style=dashed]");
|
||
}
|
||
|
||
function outputEdge(from, to, style)
|
||
{
|
||
if (to)
|
||
{
|
||
nodes[to] = 1;
|
||
if (from)
|
||
{
|
||
nodes[from] = 1;
|
||
print "\t" from " -> " to style;
|
||
}
|
||
}
|
||
}
|
||
|
||
function outputNodes()
|
||
{
|
||
for (n in nodes) print "\t" n " [fillcolor=" color[n] "]";
|
||
}
|
||
```
|
||
|
||
::: warning 🤔 思考题:尝试将上面的伪代码用 C 语言实现其功能
|
||
:::
|
||
|
||
玩家们,请注意不要把灯关掉,也不要把它丢掉。如果这样做,那么在黑暗中你将永远无法找回它。换言之,你会被困在黑暗之中!幸运的是,下一章将提供一个撤销笨拙行动的方法。
|
||
|
||
输出样例:Welcome to Little Cave Adventure.
|
||
You are in an open field.
|
||
You see:
|
||
a silver coin
|
||
a burly guard
|
||
a cave entrance to the east
|
||
dense forestall around
|
||
a lamp
|
||
|
||
--> get lamp
|
||
You pick up a lamp.
|
||
|
||
--> get coin
|
||
You pick up a silver coin.
|
||
|
||
--> give coin
|
||
You give a silver coin to a burly guard.
|
||
|
||
--> go cave
|
||
You walk into the cave.
|
||
|
||
It is very dark here.
|
||
You see:
|
||
an exit to the west
|
||
|
||
--> get key
|
||
It's too dark.
|
||
|
||
--> open door
|
||
It's too dark.
|
||
|
||
--> go south
|
||
It's too dark.
|
||
|
||
--> turn lamp on
|
||
You turn on a lamp.
|
||
|
||
You are in a little cave.
|
||
You see:
|
||
an exit to the west
|
||
solid rock all around
|
||
a closed door to the south
|
||
a tiny key
|
||
|
||
--> look around
|
||
You are in a little cave.
|
||
You see:
|
||
an exit to the west
|
||
solid rock all around
|
||
a closed door to the south
|
||
a tiny key
|
||
|
||
--> get key
|
||
You pick up a tiny key.
|
||
|
||
--> open door
|
||
You open a closed door to the south.
|
||
|
||
--> go south
|
||
You walk through the door into a backroom.
|
||
|
||
You are in a backroom.
|
||
You see:
|
||
solid rock all around
|
||
an open door to the north
|
||
a wooden box
|
||
|
||
--> quit
|
||
|
||
Bye
|