# 4.创建对象 在我们继续之前,我在这里使用的是哲学意义上的“对象”一词。它与面向对象编程无关,也与JavaC#Python等编程语言中预定义的“对象”类型没有任何共同之处。下面,我将定义一个名为 object 的结构体。。 冒险游戏中的大多数谜题都围绕着物品。例子: - 必须找到一把钥匙,然后用来解锁某扇门。 - 必须杀死守卫或者诱骗守卫才能开启房间 所以,为了表示这个物品,我们可以使用如下[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language)): - description: 对物品的描述 - tag: 物品的类型 - location: 物品所在的位置。这是对应上一章中定义的物品位置的指针。 ```c struct object { const char *description; const char *tag; struct location *location; } objs[] = { {"a silver coin", "silver", &locs[0]}, {"a gold coin" , "gold" , &locs[1]}, {"a burly guard", "guard" , &locs[0]} }; ``` 我们发现这一章的物品的信息和上一章好像也差不多呀,所以我们直接把他合并好了! ```c struct object { const char *description; const char *tag; struct object *location; } objs[] = { {"an open field", "field" , NULL}, {"a little cave", "cave" , NULL}, {"a silver coin", "silver", &objs[0]}, {"a gold coin" , "gold" , &objs[1]}, {"a burly guard", "guard" , &objs[0]} }; ``` 这样子我们的代码就会看起来更加简洁! 我们发现 OBJECT 的结构体里面有一个指针和自己长得一样,不用担心,这和链表的操作类似。 思考题:链表是什么,为什么要有这么一个操作指针? 链表和数组有什么异同点,他们分别在增删改查上有什么优劣? 为了更容易地用那些所谓的物品或者是地点,我们将为每个元素定义一个名字 ```c #define field (objs + 0) #define cave (objs + 1) #define silver (objs + 2) #define gold (objs + 3) #define guard (objs + 4) ``` 如何用各个元素的指针来方便的进行操作呢? ```c printf("You are in %s.\n", field->description); ``` 然后用这样的操作可以列出一个物品里面所有的小东西 ```c struct object *obj; for (obj = objs; obj < objs + 5; obj++) { if (obj->location == cave) { printf("%s\n", obj->description); } } ``` 暂停理解一下吧 那么,我们有合并这个物品(或地点)列表有什么好处呢?答案是这会让我们的代码变得更加简单,因为许多函数(如上面的函数通过这样的列表)只需要扫描单个列表就可以实现,而不是三个列表。有人可能会说没必要,因为每个命令仅适用于一种类型的对象: - 命令 go 适用于位置对象。 - 命令 get 应用于获得物品。 - 命令 kill 适应用于杀死人物。 但这种方法不太对劲,原因有三: 1. 某些命令适用于多种类型的对象,尤其是检查。 2. 有时候会出现很没意思的交互方式,比如说你要吃掉守卫,他说不行。 3. 某些对象在游戏中可能具有多个角色。比如说队友系统,NPC 可以是你的物品也可以是对象 将所有对象放在一个大列表中,很容易添加一个名为“type”的属性来构造对象,以帮助我们区分不同类型的对象。 怎么做怎么遍历呢?先思考吧 但是,对象通常具有同样有效的其他特征: - Locations: 通过道路连接(将在后面介绍)。如果一个物体无法通过一条通道到达,那么它就不是一个位置。就是这么简单。 - Items:玩家唯一可以捡起的物品;可以给他们整一个重量的属性 - Actors:玩家唯一可以与之交谈,交易,战斗的对象;当然,前提是他们还活着!可以加一个 HP 属性 我们还要向数组中添加一个对象:玩家自己。 在上一章中,有一个单独的变量 locationOfPlayer。我们将删除它,然后换上用户的位置属性取代他! 例如,此语句会将玩家移入洞穴: ```c player->location = cave; ``` 此表达式返回玩家当前位置的描述: ```c player->location->description ``` 是时候把它们放在一起了。我们从对象数组的全新模块开始 # Object.h ```c typedef struct object { const char *description; const char *tag; struct object *location; } 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 endOfObjs (objs + 6) ``` # Object.c ```c #include #include "object.h" OBJECT objs[] = { {"an open field", "field" , NULL }, {"a little cave", "cave" , NULL }, {"a silver coin", "silver" , field }, {"a gold coin" , "gold" , cave }, {"a burly guard", "guard" , field }, {"yourself" , "yourself", field } }; ``` 注意:要编译此模块,编译器必须支持 Constant folding。这排除了一些更原始的编译器,如 [Z88DK](http://en.wikipedia.org/wiki/Z88DK)。 以下模块将帮助我们找到与指定名词匹配的对象。 # noun.h ```c extern OBJECT *getVisible(const char *intention, const char *noun); ``` # 指针?函数?希望你已经掌握这是什么了 # noun.c ```c #include #include #include #include "object.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 || //玩家当前位置的物体 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; //感受到注释有多伟大了吧 } ``` 这是另一个辅助程序的函数。它打印存在于特定位置的对象(物品,NPC)的列表。它将用于函数 executeLook,在下一章中,我们将介绍另一个需要它的命令。 # misc.h ```c extern int listObjectsAtLocation(OBJECT *location); ``` # misc.c ```c #include #include "object.h" int listObjectsAtLocation(OBJECT *location) { int count = 0; OBJECT *obj; for (obj = objs; obj < endOfObjs; obj++) { if (obj != player && obj->location == location) //排除玩家在玩家的位置这种蠢东西 { if (count++ == 0) //我们需要保证找到一个东西之前他不会打印you see { printf("You see:\n"); } printf("%s\n", obj->description); } } return count; //返回的是数目的数量,下一章对此做额外操作 } ``` 在 location.c 中,命令环顾四周的实现,并根据新的数据结构进行调整。旧的位置数组被删除,变量 locationOfPlayer 也是如此。 # 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) { //消除了函数executeGo中的循环,代码更优雅了~ OBJECT *obj = getVisible("where you want to go", noun); if (obj == NULL) { // already handled by getVisible } else if (obj->location == NULL && obj != player->location) { printf("OK.\n"); player->location = obj; executeLook("around"); } else { printf("You can't get much closer than this.\n"); } } ``` 你可以自由添加对象哦,自己设计一个游戏道具一定很有意思 现在金银宝物散落一地可是我们捡不起来,下一章我们会试着解决问题 测试样例: Welcome to Little Cave Adventure. You are in an open field. You see: a silver coin a burly guard --> go cave OK. You are in a little cave. You see: a gold coin --> go field OK. You are in an open field. You see: a silver coin a burly guard --> go field You can't get much closer than this. --> look around You are in an open field. You see: a silver coin a burly guard --> quit Bye!