chore: add 3.4.6.2 chore: add 3.4.6.3 chore: add 3.4.6.4 chore: add 3.4.6.5 chore: add 3.4.6.6 chore: add 3.4.6.7 chore: add 3.4.6.8 chore: add 3.4.6.9 chore: add 3.4.6.10 docs: add 3.6.5.1
511 lines
16 KiB
Markdown
511 lines
16 KiB
Markdown
# 7.增大距离
|
||
|
||
<em>一个典型的冒险包含许多谜题。</em><em>众所周知,[Infocom](https://en.wikipedia.org/wiki/Infocom)</em><em>的冒险很难完成。解决每个难题可能需要数周甚至数月的反复试验。</em>
|
||
|
||
<em>当玩家操纵角色失败后,如果只是返回“你</em><em>不能这么操作</em><em>”来回应玩家</em><em>,会很 nt,很没意思</em><em>。</em>
|
||
|
||
<em>它忽略了</em><em>电脑</em><em>游戏的一个重要方面,而这也是生活本身的一部分:玩家必须从错误中吸取教训。</em>
|
||
|
||
当你的游戏反复输入东西都是,你不能这样做的时候,会显得很无聊的。
|
||
|
||
<em>冒险游戏至少应该做的是解释为什么玩家的命令无法完成:“你不能这样做,因为......”这有助于使虚拟世界更具说服力,故事更可信,游戏更有趣。</em>
|
||
|
||
我们已经付出了相当大的努力让游戏解释<strong>为什么</strong>某些命令是无效的。只需看看<em>名词.c,inventory.c,location.c</em>,<em>move.c</em>中的许多<em>printf</em>调用。但随着游戏变得越来越复杂,这正成为一个相当大的负担。我们需要一种更结构化的方法来检测和处理错误情况。这就是我们在本章中将要讨论的内容。
|
||
|
||
大多数命令对一个或多个对象进行操作,例如:
|
||
|
||
- 玩家拿起一件物品,然后把它交给另一个 NPC。
|
||
- 玩家沿着一条通道到另一个位置。
|
||
|
||
首先要检查的(在[解析器](http://en.wikipedia.org/wiki/Parsing)捕获检测是否会有明显[拼写错误](http://en.wikipedia.org/wiki/Typographical_error)之后)是这些对象<strong>是否存在</strong>;
|
||
|
||
失败应该导致类似“这里没有...“或”你看不到任何东西...”等文字出现。在本章中,我们将构建一个通用函数,每个命令都可以使用它来找出玩家是否在可及的范围内。
|
||
|
||
你可能认为我们只需要区分两种情况:对象在这里,或者它不在这里。
|
||
|
||
但是许多命令需要更多的渐变,而不仅仅是“这里”和“不在这里”。例子:
|
||
|
||
- 要使用武器或工具,玩家必须持有它;仅仅在现场存在是不够的。
|
||
- 你不能放下一个你没拿起来的道具,也不能拿起一个已经有的东西
|
||
- 如果你跟商人买东西的时候,他店里东西你不能随便拿
|
||
|
||
| distSelf | 对象是玩家 | object == player |
|
||
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||
| distHeld | 玩家持有物体 | object->location == player |
|
||
| distHeldContained | 玩家拿着另一个包含该物体的物体(例如袋子) | object->location != NULL &&<br/>object->location->location == player |
|
||
| distLocation | 对象是玩家的位置 | object == player->location |
|
||
| distHere | 对象位于玩家的位置 | object->location == player->location |
|
||
| distHereContained | 一个物体(NPC 或“容器”)存在于玩家的位置,正在拿着另一个物体 | object->location != NULL &&<br/>object->location->location == player->location |
|
||
| distOverthere | 对象是附近的位置 | getPassage(player->location, object) != NULL |
|
||
|
||
第一种情况(对象是玩家)可能看起来微不足道,但它仍然很重要。例如,命令“examine yourself”<em>不应该</em>返回“这里没有你自己”。
|
||
|
||
我试图遵循一个逻辑顺序:附近的事物最高优先级,随后优先级会变低。
|
||
|
||
| distNotHere | 对象不在这里(或看起来)不在这里 | |
|
||
| ----------------- | -------------------------------- | -------------- |
|
||
| distUnknownObject | 解析器无法识别输入的名词 | object == NULL |
|
||
|
||
请注意,我们有七种不同的“这里”案例,但只有一种是“不在这里”。这是因为通常,游戏只需要提供有关玩家可以感知的事物的信息。如果它不在这里,那么就没什么可说的了。
|
||
|
||
在最左边的列中,我们为每个案例提出了一个符号名称。我们将在名为 <strong>DISTANCE</strong> 的[枚举](http://en.wikipedia.org/wiki/Enumerated_type)中收集这些名称。
|
||
|
||
```c
|
||
typedef enum {
|
||
distSelf,
|
||
distHeld,
|
||
distHeldContained,
|
||
distLocation,
|
||
distHere,
|
||
distHereContained,
|
||
distOverthere,
|
||
distNotHere,
|
||
distUnknownObject
|
||
} DISTANCE;
|
||
```
|
||
|
||
::: warning 💡 typedef 以及枚举类 enum 之前有接触过吗?没有接触过的话就去学习一下吧。
|
||
:::
|
||
|
||
在最右边的列中,我们为每个情况提出了一个满足条件。通过一些重新洗牌,我们可以很容易地将其转换为计算对象“距离”的函数(从玩家的角度来看):
|
||
|
||
```c
|
||
DISTANCE getDistance(OBJECT *from, OBJECT *to)
|
||
{
|
||
return to == NULL ? distUnknownObject :
|
||
to == from ? distSelf :
|
||
to->location == from ? distHeld :
|
||
to == from->location ? distLocation :
|
||
to->location == from->location ? distHere :
|
||
getPassage(from->location, to) != NULL ? distOverthere :
|
||
to->location == NULL ? distNotHere :
|
||
to->location->location == from ? distHeldContained :
|
||
to->location->location == from->location ? distHereContained :
|
||
distNotHere;
|
||
}
|
||
```
|
||
|
||
::: warning 🤔 思考题:
|
||
你是否有其他方法实现这个功能?
|
||
|
||
注:自行实验即可
|
||
:::
|
||
|
||
就这样!我们可以调用此函数并对其返回值进行比较。例如,我们在 noun<em>.c</em>中有以下代码:
|
||
|
||
```c
|
||
else if (!(obj == player ||
|
||
obj == player->location ||
|
||
obj->location == player ||
|
||
obj->location == player->location ||
|
||
getPassage(player->location, obj) != NULL ||
|
||
(obj->location != NULL &&
|
||
(obj->location->location == player ||
|
||
obj->location->location == player->location))))
|
||
```
|
||
|
||
现在,我们可以用适当的距离检查替换每个子条件:
|
||
|
||
```c
|
||
else if (!(getDistance(player, obj) == distSelf ||
|
||
getDistance(player, obj) == distLocation ||
|
||
getDistance(player, obj) == distHeld ||
|
||
getDistance(player, obj) == distHere ||
|
||
getDistance(player, obj) == distOverthere ||
|
||
getDistance(player, obj) == distHeldContained ||
|
||
getDistance(player, obj) == distHereContained))
|
||
```
|
||
|
||
这可以简化为:
|
||
|
||
```c
|
||
else if (getDistance(player, obj) >= distNotHere)
|
||
```
|
||
|
||
::: warning 🤔 尝试理解一下这样做的意义
|
||
:::
|
||
|
||
这只是一个例子,让你对这个概念有所了解;您将在下面找到<em>noun.c</em>的实际实现,看起来略有不同。
|
||
|
||
是时候把事情落实到位了。枚举 <em>DISTANCE</em> 和函数 <em>getDistance</em> 的定义被添加到 <em>misc.h</em> 和 <em>misc.c</em> 中,因为我们将在多个模块中使用它们。
|
||
|
||
## misc.h
|
||
|
||
```c
|
||
typedef enum {
|
||
distSelf,
|
||
distHeld,
|
||
distHeldContained,
|
||
distLocation,
|
||
distHere,
|
||
distHereContained,
|
||
distOverthere,
|
||
distNotHere,
|
||
distUnknownObject
|
||
} DISTANCE;
|
||
|
||
extern bool isHolding(OBJECT *container, OBJECT *obj);
|
||
//是否持有物体
|
||
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 obj != NULL && obj->location == container;
|
||
}
|
||
|
||
OBJECT *getPassage(OBJECT *from, OBJECT *to)
|
||
{
|
||
if (from != NULL && to != NULL)
|
||
{
|
||
OBJECT *obj;
|
||
for (obj = objs; obj < endOfObjs; obj++)
|
||
{
|
||
if (isHolding(from, obj) && obj->destination == to)
|
||
{
|
||
return obj;
|
||
}
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
DISTANCE getDistance(OBJECT *from, OBJECT *to)
|
||
{
|
||
return to == NULL ? distUnknownObject :
|
||
to == from ? distSelf :
|
||
isHolding(from, to) ? distHeld :
|
||
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 == guard)
|
||
{
|
||
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))
|
||
{
|
||
if (count++ == 0)
|
||
{
|
||
printf("You see:\n");
|
||
}
|
||
printf("%s\n", obj->description);
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
```
|
||
|
||
注意:isHolding 这个函数之后我们将在各个地方使用
|
||
|
||
## location.h
|
||
|
||
```c
|
||
extern void executeLook(const char *noun);
|
||
extern void executeGo(const char *noun);
|
||
```
|
||
|
||
在函数 <em>executeGo</em> 中,我们可以用检查距离来替换大多数 <em>if</em> 条件。
|
||
|
||
## location.c
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
#include "noun.h"
|
||
|
||
void executeLook(const char *noun)
|
||
{
|
||
if (noun != NULL && strcmp(noun, "around") == 0)
|
||
{
|
||
printf("You are in %s.\n", player->location->description);
|
||
listObjectsAtLocation(player->location);
|
||
}
|
||
else
|
||
{
|
||
printf("I don't understand what you want to see.\n");
|
||
}
|
||
}
|
||
|
||
void executeGo(const char *noun)
|
||
{
|
||
OBJECT *obj = getVisible("where you want to go", noun);
|
||
switch (getDistance(player, obj))
|
||
{
|
||
case distOverthere:
|
||
printf("OK.\n");
|
||
player->location = obj;
|
||
executeLook("around");
|
||
break;
|
||
case distNotHere:
|
||
printf("You don't see any %s here.\n", noun);
|
||
break;
|
||
case distUnknownObject:
|
||
// already handled by getVisible
|
||
break;
|
||
default:
|
||
//上述情况均为出现
|
||
if (obj->destination != NULL)
|
||
{
|
||
printf("OK.\n");
|
||
player->location = obj->destination;
|
||
executeLook("around");
|
||
}
|
||
else
|
||
{
|
||
printf("You can't get much closer than this.\n");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
::: warning 🤔 思考题:你能否为 switch 函数增加更多 case 来完善判断条件?
|
||
:::
|
||
|
||
函数 <em>executeGet</em> 也是如此。
|
||
|
||
## <strong>inventory.h</strong>
|
||
|
||
```c
|
||
extern void executeGet(const char *noun);
|
||
extern void executeDrop(const char *noun);
|
||
extern void executeAsk(const char *noun);
|
||
extern void executeGive(const char *noun);
|
||
extern void executeInventory(void);
|
||
```
|
||
|
||
## <strong>inventory.c</strong>
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
#include "misc.h"
|
||
#include "noun.h"
|
||
#include "move.h"
|
||
|
||
void executeGet(const char *noun)
|
||
{
|
||
OBJECT *obj = getVisible("what you want to get", noun);
|
||
switch (getDistance(player, obj))
|
||
{
|
||
case distSelf:
|
||
printf("You should not be doing that to yourself.\n");
|
||
break;
|
||
case distHeld:
|
||
printf("You already have %s.\n", obj->description);
|
||
break;
|
||
case distOverthere:
|
||
printf("Too far away, move closer please.\n");
|
||
break;
|
||
case distUnknownObject:
|
||
// already handled by getVisible
|
||
break;
|
||
default:
|
||
if (obj->location == guard)
|
||
{
|
||
printf("You should ask %s nicely.\n", obj->location->description);
|
||
}
|
||
else
|
||
{
|
||
moveObject(obj, player);
|
||
}
|
||
}
|
||
}
|
||
|
||
void executeDrop(const char *noun)
|
||
{
|
||
moveObject(getPossession(player, "drop", noun), player->location);
|
||
}
|
||
|
||
void executeAsk(const char *noun)
|
||
{
|
||
moveObject(getPossession(actorHere(), "ask", noun), player);
|
||
}
|
||
|
||
void executeGive(const char *noun)
|
||
{
|
||
moveObject(getPossession(player, "give", noun), actorHere());
|
||
}
|
||
|
||
void executeInventory(void)
|
||
{
|
||
if (listObjectsAtLocation(player) == 0)
|
||
{
|
||
printf("You are empty-handed.\n");
|
||
}
|
||
}
|
||
```
|
||
|
||
最后,我们将调整 noun<em>.c</em>中的约束。我们正在向函数<em>getObject</em>添加两个参数,以便找到特定名词的匹配项,同时忽略任何被认为不存在的对象。这将在下一章中得到真正的回报,我们将在下一章中介绍具有相同标签的不同对象。
|
||
|
||
## noun.h
|
||
|
||
```c
|
||
extern OBJECT *getVisible(const char *intention, const char *noun);
|
||
extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun);
|
||
```
|
||
|
||
## 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)
|
||
{
|
||
return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0;
|
||
}
|
||
|
||
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 = obj;
|
||
}
|
||
}
|
||
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
|
||
{
|
||
printf("You don't see any %s here.\n", noun);
|
||
}
|
||
}
|
||
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 == from)
|
||
{
|
||
printf("You should not be doing that to %s.\n", obj->description);
|
||
obj = NULL;
|
||
}
|
||
return obj;
|
||
}
|
||
```
|
||
|
||
::: warning 🤔 思考题:你能理解什么时候加 const,什么时候不用吗?
|
||
:::
|
||
|
||
在本章中,<em>距离</em>的概念主要用于在游戏可以给用户的不同响应之间进行选择。但是,距离的好处并不局限于<strong>输出</strong>端;它可以同样很好地用于在<strong>输入</strong>端进行改进。在下一章中,我们将使用距离来提高对用户输入的名词的识别。
|
||
|
||
输出样例
|
||
|
||
Welcome to Little Cave Adventure.
|
||
You are in an open field.
|
||
You see:
|
||
a silver coin
|
||
a burly guard
|
||
a cave entrance
|
||
|
||
--> go guard
|
||
You can't get much closer than this.
|
||
|
||
--> give silver
|
||
You are not holding any silver.
|
||
|
||
--> ask silver
|
||
There appears to be no silver you can get from a burly guard.
|
||
|
||
--> get silver
|
||
You pick up a silver coin.
|
||
|
||
--> get gold
|
||
You don't see any gold here.
|
||
|
||
--> give silver
|
||
You give a silver coin to a burly guard.
|
||
|
||
--> go cave
|
||
OK.
|
||
You are in a little cave.
|
||
You see:
|
||
a gold coin
|
||
an exit
|
||
|
||
--> get gold
|
||
You pick up a gold coin.
|
||
|
||
--> give gold
|
||
There is nobody here to give that to.
|
||
|
||
--> quit
|
||
|
||
Bye!
|