669 lines
19 KiB
Markdown
669 lines
19 KiB
Markdown
# 12.开启关闭
|
||
|
||
在上一章中,我们使用 "条件 "函数来使对象消失。当然,还有一个更简单的方法来实现同样的目的:只要清除对象的位置属性就可以了!
|
||
|
||
洞口是一个典型的例子,条件函数在那里工作得特别好。这是因为入口受到其他对象(守卫和银币)中的属性的影响;我们可以使用函数使得所有的逻辑都能保持一致。
|
||
|
||
让我们举一个更直接的例子。假设山洞有一扇门通向一个密室。只是一个简单的门洞,玩家可以打开和关闭。就像前一章一样,我们将使用两个对象来表示这个通道;一个表示打开的门,另一个表示门关闭时。
|
||
|
||
```
|
||
- backroom
|
||
description "a backroom"
|
||
tags "backroom"
|
||
details "The room is dusty and messy.\n"
|
||
|
||
- openDoorToBackroom
|
||
description "an open door to the south"
|
||
tags "south", "door", "doorway"
|
||
destination backroom
|
||
details "The door is open.\n"
|
||
textGo "You walk through the door into the backroom.\n"
|
||
|
||
- closedDoorToBackroom
|
||
description "a closed door to the south"
|
||
tags "south", "door", "doorway"
|
||
location cave
|
||
prospect backroom
|
||
details "The door is closed.\n"
|
||
textGo "The door is closed.\n"
|
||
```
|
||
|
||
思考题:尝试自己用 C 语言实现
|
||
|
||
自然,门也应该能从另一侧进入。
|
||
|
||
```
|
||
- openDoorToCave
|
||
description "an open door to the north"
|
||
tags "north", "door", "doorway"
|
||
destination cave
|
||
details "The door is open.\n"
|
||
textGo "You walk through the door into the cave.\n"
|
||
|
||
- closedDoorToCave
|
||
description "a closed door to the north"
|
||
tags "north", "door", "doorway"
|
||
location backroom
|
||
prospect cave
|
||
details "The door is closed.\n"
|
||
textGo "The door is closed.\n"
|
||
```
|
||
|
||
注意我们只给封闭的门洞一个位置,开放的门洞没有。所以一开始,门是关闭的(因此你在右边看到的生成的地图中,山洞和密室之间有虚线箭头)。要打开门,我们所要做的就是交换位置。
|
||
|
||
```c
|
||
openDoorToBackroom->location = cave;
|
||
closedDoorToBackroom->location = NULL;
|
||
openDoorToCave->location = backroom;
|
||
closedDoorToCave->location = NULL;
|
||
```
|
||
|
||
接下来,让我们创建一个辅助函数来适应这种情况。
|
||
|
||
```c
|
||
void swapLocations(OBJECT *obj1, OBJECT *obj2)
|
||
{
|
||
OBJECT *tmp = obj1->location;
|
||
obj1->location = obj2->location;
|
||
obj2->location = tmp;
|
||
}
|
||
```
|
||
|
||
现在可以用下面的语句来打开这扇门;一旦它被打开,同样的语句将再次关闭它。
|
||
|
||
```c
|
||
swapLocations(openDoorToBackroom, closedDoorToBackroom);
|
||
swapLocations(openDoorToCave, closedDoorToCave);
|
||
```
|
||
|
||
函数 swapLocations 不依赖于固定的位置,因为它在两个对象之间来回传递当前位置。
|
||
|
||
当相关对象是可移动的时候,辅助功能就特别方便。例如,一个盒子可以被打开和关闭,但它也是一个可以被拿起并移动到其他地方的物品。换句话说,它的位置是不固定的。
|
||
|
||
当然,一个盒子不是一个通道:玩家总是在盒子外面。所以一对物体就够了,也就是说,我们调用一次 swapLocations 函数就够了。
|
||
|
||
```c
|
||
swapLocations(openBox, closedBox);
|
||
```
|
||
|
||
这或多或少是我们实现一些新命令 open 和 close 所需要的。下面是 open 的一个简单实现;close 的实现也类似。
|
||
|
||
```c
|
||
OBJECT *obj = parseObject(noun);
|
||
if (obj == closedDoorToBackRoom || obj == closedDoorToCave)
|
||
{
|
||
swapLocations(openDoorToBackroom, closedDoorToBackroom);
|
||
swapLocations(openDoorToCave, closedDoorToCave);
|
||
printf("OK.\n");
|
||
}
|
||
else if (obj == closedBox)
|
||
{
|
||
swapLocations(openBox, closedBox);
|
||
printf("OK.\n");
|
||
}
|
||
else if (obj == openDoorToBackRoom || obj == openDoorToCave || obj == openBox)
|
||
{
|
||
printf("That is already open.\n");
|
||
}
|
||
else
|
||
{
|
||
printf("That cannot be opened.\n");
|
||
}
|
||
```
|
||
|
||
思考题:你能不能仿照上面的代码实现 close 功能?
|
||
|
||
为了使事情稍微复杂一些,我们可以在门上或盒子上加一把锁。这需要(至少)三个相互排斥的对象;每个可能的状态都有一个:打开、关闭和锁定。但是我们仍然可以使用同一个函数来交换对象的位置。例如,这里是如何解锁一个上锁的盒子;反之亦然。
|
||
|
||
```c
|
||
swapLocations(closedBox, lockedBox);
|
||
```
|
||
|
||
但这仅仅是不够的,我们对命令 open 的实现必须进行扩展,以处理新的对象 lockedBox。
|
||
|
||
```c
|
||
...
|
||
else if (obj == lockedBox)
|
||
{
|
||
printf("You can't, it is locked.\n");
|
||
}
|
||
...
|
||
```
|
||
|
||
显然,代码的行数与游戏中的门(以及盒子和其他可以打开的物体)的数量成正比。因此,如果你的游戏有不止几扇门,那么选择一个更通用的解决方案是个好主意。顺便说一下,这对每一个命令都是适用的:当它涉及到许多物体时,尽量写通用代码;但当你处理一两个特殊情况时,就坚持使用直接的、专门的代码。
|
||
|
||
思考题:我们可以使用什么方法来解决这个问题?
|
||
|
||
提示:C++ 中的模板功能(这只是一种选择)
|
||
|
||
下面我们将揭晓答案
|
||
|
||
通用代码通常带有数据驱动的方法。换句话说,我们需要向我们的对象结构添加一个或多个属性。在这种特殊情况下,我们将为我们希望支持的每个命令添加一个函数指针:打开、关闭、锁定和解锁。
|
||
|
||
# object.txt
|
||
|
||
```c
|
||
#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;
|
||
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
|
||
|
||
- 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
|
||
```
|
||
|
||
注意:
|
||
|
||
- 第 89 行:乍一看,isAlreadyOpen 在这里似乎不合适;从技术上讲,intoCaveBlocked 是一个封闭的通道。但从故事上讲,它是一个不错的开场白。
|
||
- 第 169、180、191 行。如果你喜欢沉重的宝箱而不是盒子,那么你所要做的就是增加重量(并相应调整相关文字和标签)。
|
||
|
||
为了避免重复的代码,我们这次特意没有使用匿名函数。相反,我们将在一个单独的模块中实现必要的逻辑。函数 swapLocations 也在其中,这不过是一个稍微扩展的版本,它也会向用户输出反馈。
|
||
|
||
# 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);
|
||
```
|
||
|
||
# toggle.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.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");
|
||
}
|
||
}
|
||
```
|
||
|
||
正如前面所宣布的,打开、关闭、锁定和解锁这四个命令的实现是完全通用的。
|
||
|
||
# openclose.h
|
||
|
||
```c
|
||
extern void executeOpen(const char *noun);
|
||
extern void executeClose(const char *noun);
|
||
extern void executeLock(const char *noun);
|
||
extern void executeUnlock(const char *noun);
|
||
```
|
||
|
||
# openclose.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "reach.h"
|
||
|
||
void executeOpen(const char *noun)
|
||
{
|
||
OBJECT *obj = reachableObject("what you want to open", noun);
|
||
if (obj != NULL) (*obj->open)();
|
||
}
|
||
|
||
void executeClose(const char *noun)
|
||
{
|
||
OBJECT *obj = reachableObject("what you want to close", noun);
|
||
if (obj != NULL) (*obj->close)();
|
||
}
|
||
|
||
void executeLock(const char *noun)
|
||
{
|
||
OBJECT *obj = reachableObject("what you want to lock", noun);
|
||
if (obj != NULL) (*obj->lock)();
|
||
}
|
||
|
||
void executeUnlock(const char *noun)
|
||
{
|
||
OBJECT *obj = reachableObject("what you want to unlock", noun);
|
||
if (obj != NULL) (*obj->unlock)();
|
||
```
|
||
|
||
上面,我们使用了一个通用函数 reachableObject 来处理不在这里的对象;其实现见下文。这样,我们就不必把同样的代码写四遍(每个执行函数写一遍)。更多的命令将在第 15 章中加入;这些命令将受益于同样的函数。
|
||
|
||
# reach.h
|
||
|
||
```c
|
||
extern OBJECT *reachableObject(const char *intention, const char *noun);
|
||
```
|
||
|
||
# reach.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
#include "noun.h"
|
||
|
||
OBJECT *reachableObject(const char *intention, const char *noun)
|
||
{
|
||
OBJECT *obj = getVisible(intention, noun);
|
||
switch (getDistance(player, obj))
|
||
{
|
||
case distSelf:
|
||
printf("You should not be doing that to yourself.\n");
|
||
break;
|
||
case distHeldContained:
|
||
case distHereContained:
|
||
printf("You would have to get it from %s first.\n",
|
||
obj->location->description);
|
||
break;
|
||
case distOverthere:
|
||
printf("Too far away, move closer please.\n");
|
||
break;
|
||
case distNotHere:
|
||
case distUnknownObject:
|
||
// already handled by getVisible
|
||
break;
|
||
default:
|
||
return obj;
|
||
}
|
||
return NULL;
|
||
}
|
||
```
|
||
|
||
同样,我们也要对 parsexec.c 进行补充
|
||
|
||
# parsexec.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "location.h"
|
||
#include "inventory.h"
|
||
#include "openclose.h"
|
||
|
||
bool parseAndExecute(char *input)
|
||
{
|
||
char *verb = strtok(input, " \n");
|
||
char *noun = strtok(NULL, "\n");
|
||
if (verb != NULL)
|
||
{
|
||
if (strcmp(verb, "quit") == 0)
|
||
{
|
||
return false;
|
||
}
|
||
else if (strcmp(verb, "look") == 0)
|
||
{
|
||
executeLook(noun);
|
||
}
|
||
else if (strcmp(verb, "go") == 0)
|
||
{
|
||
executeGo(noun);
|
||
}
|
||
else if (strcmp(verb, "get") == 0)
|
||
{
|
||
executeGet(noun);
|
||
}
|
||
else if (strcmp(verb, "drop") == 0)
|
||
{
|
||
executeDrop(noun);
|
||
}
|
||
else if (strcmp(verb, "give") == 0)
|
||
{
|
||
executeGive(noun);
|
||
}
|
||
else if (strcmp(verb, "ask") == 0)
|
||
{
|
||
executeAsk(noun);
|
||
}
|
||
else if (strcmp(verb, "inventory") == 0)
|
||
{
|
||
executeInventory();
|
||
}
|
||
else if (strcmp(verb, "open") == 0)
|
||
{
|
||
executeOpen(noun);
|
||
}
|
||
else if (strcmp(verb, "close") == 0)
|
||
{
|
||
executeClose(noun);
|
||
}
|
||
else if (strcmp(verb, "lock") == 0)
|
||
{
|
||
executeLock(noun);
|
||
}
|
||
else if (strcmp(verb, "unlock") == 0)
|
||
{
|
||
executeUnlock(noun);
|
||
}
|
||
else
|
||
{
|
||
printf("I don't know how to '%s'.\n", verb);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
```
|
||
|
||
注意:
|
||
|
||
- 你可能已经注意到,object.txt 在本章中的大小几乎增加了一倍。我们已经可以向你保证,这只是一个开始。object.txt 是我们游戏数据的主要来源;一旦我们认真开始添加地点、物品和演员,行数很容易就会增加到数千行。
|
||
- 除了门和锁之外,函数 swapLocation 还可以用于许多其他事情。在第 15 章,它将再次被使用,这次是用来打开和关闭灯。
|
||
- 正如你在样本输出中所看到的,玩家可以从盒子里拿到金子,但他无法再把金子放回去!我们的解析器无法处理像把硬币放进盒子这样的 "复杂 "命令。因此,在下一章中,我们将编写一个全新的解析器:目前的双线实现急需更换!
|
||
|
||
输出样例
|
||
|
||
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 forest all around
|
||
|
||
--> 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.
|
||
|
||
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.
|
||
|
||
--> go south
|
||
The door is closed.
|
||
|
||
--> 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
|
||
|
||
--> unlock box
|
||
You unlock a wooden box.
|
||
|
||
--> open box
|
||
You open a wooden box.
|
||
|
||
--> look box
|
||
The box is open.
|
||
You see:
|
||
a gold coin
|
||
|
||
--> get gold
|
||
You get a gold coin from a wooden box.
|
||
|
||
--> quit
|
||
|
||
Bye!
|