10. 增添属性
我们在第 4 章中介绍 object 时,它们只有三个属性。
在第 6 章中,我们增加了第四个。这他妈太少了!
为了在我们的冒险中加入更多细节,我们需要更多的属性。下面是一些示例。
- 环顾四周这个命令可以给出玩家位置的全局描述,包括在那里的物品、NPC 和其他对象的列表。很多冒险活动都需要玩家对这些对象进行查询,从而揭示出在游戏中获得进展所需要的某些线索,或者简单地提升游戏的氛围。我们将添加一个对每个对象都有详细说明的属性细节,加上一个与包含其他对象的对象一起使用的属性内容。
- 如果玩家开始一段话时,响应总是 “OK” 和新位置的描述。这似乎有点沉闷。所以,为每个段落提供自己的自定义消息会更好。我们将添加一个属性 textGo 来保存此消息。
- 有些通道可能是特殊设定的,比如说传送门,陷阱等等。
举个例子,一条林道可能隐藏着陷阱。虽然通道似乎从位置 A 通向位置 B,但实际上终点是位置 C,即掉进坑了。
假设我们的洞口被警卫挡住了。玩家就过不去,我们可以简单地将通道的目的地更改为终点位置(或 NULL),但这会导致对诸如 go cave 和 look cave 这样的命令做出不正确的回应:“你在这里看不到任何洞穴。我们需要一个将通道的实际终点和虚假终点分开的单独属性。为此,我们将引入一个属性 prospect 来表示后者。
- 在许多冒险中,玩家以及游戏中的 NPC 在携带量方面受到限制。给每件物品一个重量,角色库存中所有物品的总重量不应超过该角色所能承载的最大重量。当然,我们也可以给一个物体一个非常高的重量,使它不可移动(一棵树,一座房子,一座山)。
- RPG 式的冒险游戏需要角色的整个属性范围 (玩家与非玩家),例如 HP。HP 为零的对象要么死了,要么根本不是角色。
我们在 object.txt 中定义了七个新属性:
#include <stdio.h>
#include "object.h"
typedef struct object {
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
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
注意:textGo 不仅对通道对象有用,而且对非通道对象也有用 (在这种情况下,以后我们将介绍 “墙” 这个概念)
🤔 思考题:你能否自行实现上述伪代码?
现在,我们已经可以使用新属性 (如果你完成了上面的思考题),details 用于新识别的命令外观 <object>
,textGo 在我们的命令 go 实现中替换固定文本 *“OK*”。
location.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
{
OBJECT *obj = getVisible("what you want to look at", noun);
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", noun);
break;
case distUnknownObject:
// already handled by getVisible
break;
default:
printf("%s\n", obj->details);
listObjectsAtLocation(obj);
}
}
}
static void movePlayer(OBJECT *passage)
{
printf("%s\n", passage->textGo);
if (passage->destination != NULL)
{
player->location = passage->destination;
printf("\n");
executeLook("around");
}
}
void executeGo(const char *noun)
{
OBJECT *obj = getVisible("where you want to go", noun);
switch (getDistance(player, obj))
{
case distOverthere:
movePlayer(getPassage(player->location, obj));
break;
case distNotHere:
printf("You don't see any %s here.\n", noun);
break;
case distUnknownObject:
// already handled by getVisible
break;
default:
movePlayer(obj);
}
}
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
属性权重和容量一起成为不能将某些对象移动到周围的可能原因。而 HP 检查代替了角色的硬编码白名单。
move.c
#include <stdbool.h>
#include <stdio.h>
#include "object.h"
#include "misc.h"
static int weightOfContents(OBJECT *container)
{
int sum = 0;
OBJECT *obj;
for (obj = objs; obj < endOfObjs; obj++)
{
if (isHolding(container, obj)) sum += obj->weight;
}
return sum;
}
static void describeMove(OBJECT *obj, OBJECT *to)
{
if (to == player->location)
{
printf("You drop %s.\n", obj->description);
}
else if (to != player)
{
printf(to->health > 0 ? "You give %s to %s.\n" : "You put %s in %s.\n",
obj->description, to->description);
}
else if (obj->location == player->location)
{
printf("You pick up %s.\n", obj->description);
}
else
{
printf("You get %s from %s.\n",
obj->description, obj->location->description);
}
}
void moveObject(OBJECT *obj, OBJECT *to)
{
if (obj == NULL)
{
// already handled by getVisible or getPossession
}
else if (to == NULL)
{
printf("There is nobody here to give that to.\n");
}
else if (obj->weight > to->capacity)
{
printf("That is way too heavy.\n");
}
else if (obj->weight + weightOfContents(to) > to->capacity)
{
printf("That would become too heavy.\n");
}
else
{
describeMove(obj, to);
obj->location = to;
}
}
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
这里还有一个模块可以使用 HP 来识别角色。
inventory.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 != NULL && obj->location->health > 0)
{
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");
}
}
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
🤔 思考题:仔细观察这段代码,看看与你写的有何不同?
权重检查利用了新功能 weightOfContents,它将在 misc.c 中实现。在同一模块中,我们还对一些现有函数进行了修改,以支持最后几个属性。
属性内容将替换固定文本 *“You see”。在列出玩家的库存时,原始文本已经有点奇怪了,但是现在函数 listObjectsAtLocation 用于显示任何可能对象的内容(请参阅上面的函数 expertLook*),我们真的需要一些更灵活的东西。
在函数 getPassage 中我们将属性目标替换为 prospect,并改进对所有命令(而不仅仅是 go and look)的响应,这些命令应用于位于 “隐藏通道” 另一端的位置。
misc.h
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
misc.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->prospect == 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 != 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
🤔 思考题:
为什么上面的 getPassage 函数使用了函数指针这种语法?
函数指针和指针函数有什么区别?
为了使整个画面完整,最好扩展前面生成的地图,我们可以用虚线表示 “明显” 的通道。
BEGIN { print "digraph map {"; }
/^- / { outputEdges(); delete a; }
/^[ \t]/ { a[$1] = $2; }
END { outputEdges(); print "}"; }
function outputEdges()
{
outputEdge(a["location"], a["destination"], "");
outputEdge(a["location"], a["prospect"], " [style=dashed]");
}
function outputEdge(from, to, style)
{
if (from && to) print "\t" from " -> " to style;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意:
- 尽量不要太担心浪费仅在某些类型的对象中使用的属性上的内存空间(例如,textGo 仅用于通道),或者许多重复的字符串文本。
- 为了演示属性 prospect 的使用,我们使洞穴无法访问。当您查看新 * 地图时,* 这一点立即变得很明显。进入洞穴的箭头是虚线的,这意味着这是一个虚假的通道,但不是实际的通道。请放心,洞穴将在下一章重新开放。
- 请注意,更详细的描述往往需要一个更大的字典(更多的对象,更多的标签)。例如,命令 look silver coin 现在返回 "该硬币的正面有一只鹰"。玩家通过输入一个命令 look eagle 来查看银币,但程序并不知道鹰是什么意思 (显然这样子是不行的)。
输出样例
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
--> look guard The guard is a really big fellow.
--> get guard That is way too heavy.
--> get coin You pick up a silver coin.
--> inventory You have: a silver coin
--> give coin You give a silver coin to a burly guard.
--> look guard The guard is a really big fellow. He has: a silver coin
--> go cave The guard stops you from walking into the cave.
--> go north Dense forest is blocking the way.
--> quit
Bye!