Files

511 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7.增大距离
*一个典型的冒险包含许多谜题。众所周知,[Infocom](https://en.wikipedia.org/wiki/Infocom)的冒险很难完成。解决每个难题可能需要数周甚至数月的反复试验。*
*当玩家操纵角色失败后,如果只是返回“你不能这么操作”来回应玩家,会很 nt很没意思。*
*它忽略了电脑游戏的一个重要方面,而这也是生活本身的一部分:玩家必须从错误中吸取教训。*
当你的游戏反复输入东西都是,你不能这样做的时候,会显得很无聊的。
*冒险游戏至少应该做的是解释为什么玩家的命令无法完成:“你不能这样做,因为......”这有助于使虚拟世界更具说服力,故事更可信,游戏更有趣。*
我们已经付出了相当大的努力让游戏解释**为什么**某些命令是无效的。只需看看*名词.cinventory.clocation.c**move.c*中的许多*printf*调用。但随着游戏变得越来越复杂,这正成为一个相当大的负担。我们需要一种更结构化的方法来检测和处理错误情况。这就是我们在本章中将要讨论的内容。
大多数命令对一个或多个对象进行操作,例如:
- 玩家拿起一件物品,然后把它交给另一个 NPC。
- 玩家沿着一条通道到另一个位置。
首先要检查的(在[解析器](http://en.wikipedia.org/wiki/Parsing)捕获检测是否会有明显[拼写错误](http://en.wikipedia.org/wiki/Typographical_error)之后)是这些对象**是否存在**;
失败应该导致类似“这里没有...“或”你看不到任何东西...”等文字出现。在本章中,我们将构建一个通用函数,每个命令都可以使用它来找出玩家是否在可及的范围内。
你可能认为我们只需要区分两种情况:对象在这里,或者它不在这里。
但是许多命令需要更多的渐变,而不仅仅是“这里”和“不在这里”。例子:
- 要使用武器或工具,玩家必须持有它;仅仅在现场存在是不够的。
- 你不能放下一个你没拿起来的道具,也不能拿起一个已经有的东西
- 如果你跟商人买东西的时候,他店里东西你不能随便拿
| 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”*不应该*返回“这里没有你自己”。
我试图遵循一个逻辑顺序:附近的事物最高优先级,随后优先级会变低。
| distNotHere | 对象不在这里(或看起来)不在这里 | |
| ----------------- | -------------------------------- | -------------- |
| distUnknownObject | 解析器无法识别输入的名词 | object == NULL |
请注意,我们有七种不同的“这里”案例,但只有一种是“不在这里”。这是因为通常,游戏只需要提供有关玩家可以感知的事物的信息。如果它不在这里,那么就没什么可说的了。
在最左边的列中,我们为每个案例提出了一个符号名称。我们将在名为 **DISTANCE** 的[枚举](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*.c*中有以下代码:
```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 🤔 尝试理解一下这样做的意义
:::
这只是一个例子,让你对这个概念有所了解;您将在下面找到*noun.c*的实际实现,看起来略有不同。
是时候把事情落实到位了。枚举 *DISTANCE* 和函数 *getDistance* 的定义被添加到 *misc.h**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 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);
```
在函数 *executeGo* 中,我们可以用检查距离来替换大多数 *if* 条件。
## 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 来完善判断条件?
:::
函数 *executeGet* 也是如此。
## **inventory.h**
```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);
```
## **inventory.c**
```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*.c*中的约束。我们正在向函数*getObject*添加两个参数,以便找到特定名词的匹配项,同时忽略任何被认为不存在的对象。这将在下一章中得到真正的回报,我们将在下一章中介绍具有相同标签的不同对象。
## noun.h
```c
extern OBJECT *getVisibleconst char *intention const char *noun;
extern OBJECT *getPossessionOBJECT *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什么时候不用吗
:::
在本章中,*距离*的概念主要用于在游戏可以给用户的不同响应之间进行选择。但是,距离的好处并不局限于**输出**端;它可以同样很好地用于在**输入**端进行改进。在下一章中,我们将使用距离来提高对用户输入的名词的识别。
输出样例
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!