# 6.绘制地图 作为一个 RPG 游戏怎么能没有地图呢,*是时候绘制地图了!* 绘制地图的最佳工具始终是:一支铅笔和一张纸。基本地图由**位置**(矩形)组成,由道路(箭头)连接。我们已经在第 3 章中创建了位置,现在我们将开始添加道路。 在虚拟世界中,“道路”可能是连接两个位置的任何东西:一条路,一扇门,沙漠中。基本上,一段经文具有以下属性: - 起点(位置)。 - 目标(位置)。 - 叙述性描述,例如“森林小径”。 - 在 *go* 命令中往哪里走的描述性标记 考虑到这些属性,第 4 章中定义的结构对象就非常适合存储道路了。事实上,一个道路与一个项目或 NPC 并没有太大的不同,它作为“可见出口”存在于某个位置(该位置是起点)。它只是与某些命令的行为不同,特别是命令“go”:应用于道路,*go*将改变玩家的位置。 ```c struct object { const char *description; const char *tag; struct object *location; struct object *destination; }; ``` 注意: - 显然,*目的地*在大多数其他对象(物品,NPC)中都没有使用 - 通道总是朝一个方向运行;要双向连接两个位置,我们总是必须创建两个单独的通道。乍一看,这似乎很笨拙,但它确实给了我们很大的灵活性来完善命令“go”的行为 - 在大地图上,你可能会发现手动创建所有通道很乏味。所以,我强烈建议你使用自定义工具*生成*地图中重复性更强的部分。这里不会介绍这一点,但您可能会在第 9 章中找到一些灵感,我们将在其中讨论自动胜场。 ::: warning 🤔 思考题:为什么创建两个通道可以使我们的程序更加灵活? ::: 接下来我们将展开对象数组 ## object.h ```c typedef struct object { const char *description; const char *tag; struct object *location; struct object *destination; } OBJECT; extern OBJECT objs[]; #define field (objs + 0) //是不是觉得这个很熟悉 #define cave (objs + 1) #define silver (objs + 2) #define gold (objs + 3) #define guard (objs + 4) #define player (objs + 5) #define intoCave (objs + 6) #define exitCave (objs + 7) #define endOfObjs (objs + 8) ``` ## object.c ```c #include #include "object.h" OBJECT objs[] = { {"an open field" , "field" , NULL , NULL }, {"a little cave" , "cave" , NULL , NULL }, {"a silver coin" , "silver" , field, NULL }, {"a gold coin" , "gold" , cave , NULL }, {"a burly guard" , "guard" , field, NULL }, {"yourself" , "yourself", field, NULL }, {"a cave entrance", "entrance", field, cave }, {"an exit" , "exit" , cave , field } }; ``` 我们将在 *misc.c* 中添加一个小的帮助函数,以确定两个给定位置之间是否存在通道。 ## misc.h ```c extern OBJECT *getPassage(OBJECT *from, OBJECT *to); extern OBJECT *actorHere(void); extern int listObjectsAtLocation(OBJECT *location); ``` ## misc.c ```c #include #include "object.h" OBJECT *getPassage(OBJECT *from, OBJECT *to) { if (from != NULL && to != NULL) { OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) //寻找物品之间是否具有通道 { if (obj->location == from && obj->destination == to) { return obj; //找到了 } } } return NULL;//找不到 } OBJECT *actorHere(void) { OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj->location == player->location && obj == guard) { return obj; } } return NULL; } int listObjectsAtLocation(OBJECT *location) { int count = 0; OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj != player && obj->location == location) { if (count++ == 0) { printf("You see:\n"); } printf("%s\n", obj->description); } } return count; } ``` 我们将在命令“go”的实现中使用新功能*getPassage*来确定是否存在可以将玩家带到所需位置的通道。 ## location.h ```c extern void executeLook(const char *noun); extern void executeGo(const char *noun); ``` ## location.c ```c #include #include #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); if (obj == NULL) { // already handled by getVisible } else if (getPassage(player->location, obj) != NULL) //go 只会在有地方的时候才会运行起来 { printf("OK.\n"); player->location = obj; executeLook("around"); } else if (obj->location != player->location) { printf("You don't see any %s here.\n", noun); } else if (obj->destination != NULL) { printf("OK.\n"); player->location = obj->destination; executeLook("around"); } else { printf("You can't get much closer than this.\n"); } } ``` 我们还将使用新功能*getPassage*来确定从玩家站立的位置是否可以看到某个位置。未通过通道连接到当前位置的位置不被视为可见。 ## 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 #include #include #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 *obj, *res = NULL; for (obj = objs; obj < endOfObjs; obj++) { if (objectHasTag(obj, noun)) { res = obj; } } return res; } OBJECT *getVisible(const char *intention, const char *noun) { OBJECT *obj = getObject(noun); if (obj == NULL) { printf("I don't understand %s.\n", intention); } 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)))) { printf("You don't see any %s here.\n", noun); obj = NULL; } 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)) == NULL) { printf("I don't understand what you want to %s.\n", verb); } else if (obj == from) { printf("You should not be doing that to %s.\n", obj->description); obj = NULL; } else if (obj->location != from) { 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); } obj = NULL; } return obj; } ``` 显然,此示例中的地图是微不足道的:只有两个位置,并且它们在两个方向上都连接在一起。第 12 章将增加第三个地点。 ::: warning 🤔 思考题: 你能否绘制一张更精细的地图,并将其变成对象列表(位置和通道) 注:不用当成任务,自行实验即可 ::: 输出样例 Welcome to Little Cave Adventure. You are in an open field. You see: a silver coin a burly guard a cave entrance --> go entrance OK. You are in a little cave. You see: a gold coin an exit --> go exit OK. You are in an open field. You see: a silver coin a burly guard a cave entrance --> go cave OK. You are in a little cave. You see: a gold coin an exit --> quit Bye!