11. 设置条件
到目前为止,所有对象的属性都是数据:文本、数字。但属性也可以是代码。
在上一章中,我们通过关闭洞穴入口(进入洞穴的通道)来限制玩家的行动自由。这已经让游戏变得更有挑战性了,但这并没有带来多少谜题,除非我们为玩家提供一个微小的可能性来打开通道。真正的挑战应该是让玩家找到通道打开的条件。
让我们举一个简单的例子。为了越过守卫进入山洞,玩家必须杀死或贿赂守卫(或两者兼而有之,这很有价值)。换句话说:
- 当警卫死亡时(HP=0),入口开放
- 当警卫拿着银币 (贿赂警卫) 时,入口开放
- 两者都不是,入口关闭
打开一个封闭的通道(在这里是进入洞穴)涉及到改变一些属性值:
- 目的地从 NULL (空地点) 变为洞穴
- textGo 从 "警卫阻止你......" 改为 "你走进山洞"
- 在一些特殊情况下,描述和细节不需要改变。但对于一个门洞或栅栏,其中之一(或两者)通常会包含一些从 "开放" 到 "关闭" 的文字。
有许多方法来实现这一目标。在这里,我们将讨论一种简单、可维护和通用的方法。
首先,我们定义了两个独立的通道:一个代表开放通道,另一个代表封闭通道。除了上面列出的那些,这两条通道在每一个属性上都是相同的。(在你下面看到的生成的地图中,注意有两个箭头通向洞穴;一个是实线,一个是虚线)。
接下来,我们引入一个名为条件的新属性,它决定了某个对象是否存在。这两个通道将被赋予互斥的条件,因此在任何时候都只能有一个存在。
每个条件将被实现为一个布尔函数:TRUE 意味着该对象存在,FALSE 意味着它不存在。
bool intoCaveIsOpen(void)
{
return guard->health == 0 || silver->location == guard;
}
bool intoCaveIsClosed(void)
{
return guard->health > 0 && silver->location != guard;
}
2
3
4
5
6
7
8
9
🤔 思考题:你能仿照上面例子自己写一些条件函数吗?
新的属性条件是一个指向这样一个函数的指针。
bool (*condition)(void);
接下来,我们可以立即开始为 object.txt 中的新属性分配函数。
object.txt
- intoCave
condition intoCaveIsOpen
description "a cave entrance to the east"
tags "east", "entrance"
location field
destination cave
detail "The entrance is just a narrow opening in a small outcrop.\n"
textGo "You walk into the cave.\n"
- intoCaveBlocked
condition intoCaveIsClosed
description "a cave entrance to the east"
tags "east", "entrance"
location field
prospect cave
detail "The entrance is just a narrow opening in a small outcrop.\n"
textGo "The guard stops you from walking into the cave.\n"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
🤔 思考题:尝试自己实现上面的伪代码
这两个 "条件" 函数是如此具体,每一个条件函数都只用这一次。现在,我们可以在我们需要的地方定义这些函数。许多编程语言都支持匿名函数,像这样:
- intoCave
condition { return guard->health == 0 || silver->location == guard; }
...
- intoCaveBlocked
condition { return guard->health > 0 && silver->location != guard; }
...
2
3
4
5
6
7
所以现在我们可以把额外的段落和条件添加到 object.txt 中,就像前面解释的那样。
new object.txt
#include <stdbool.h>
#include <stdio.h>
#include "object.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;
} 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 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 "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."
- 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."
- 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."
- 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", "south", "rock"
location cave
details "Carved in stone is a secret password 'abccb'."
textGo "Solid rock is blocking the way."
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
🤔 思考题:尝试自己实现这些功能,并看看与你之前设计的有何不同
为了使这些条件发挥作用,我们需要调整函数 isHolding 和 getDistance。
misc.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;
}
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 :
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 &&
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))
{
if (count++ == 0)
{
printf("%s:\n", location->contents);
}
printf("%s\n", obj->description);
}
}
return count;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
🤔 思考题:想想我们调整了什么
注意:
- 警卫不可能会死,所以可以说我们的条件函数中的 HP 是很无用的。当然,这很容易通过添加一个 kill 命令来解决,见第 20 章。
- 这两个条件函数是互补的;它们有资格成为重复的代码。为了消除这一点,我们可能决定让一个函数调用另一个函数(用 '!' 操作符来否定结果)。一个匿名函数没有(稳定的)名字,但我们可以用它的对象来指代它。我们可以用 intoCaveBlocked 的条件函数代替。
- 为了简单起见,条件函数没有参数。实际上,传递一个参数 OBJECT *obj 可能更好;这使得编写更多的通用条件函数成为可能,可以在多个对象中重复使用。
- 在理论上,任何对象都可以成为 "条件"。在下一章,你可以看到一个类似的技术被应用于此。
🤔 思考题:想一想上面第二点要怎么用 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 forest all around
--> go entrance The guard stops you from walking into the cave.
--> get coin You pick up a silver coin.
--> give coin You give a silver coin to a burly guard.
--> go entrance You walk into the cave.
You are in a little cave. You see: a gold coin an exit to the west solid rock all around
--> quit
Bye!