434 lines
11 KiB
Markdown
434 lines
11 KiB
Markdown
# 5.捡起物品
|
||
|
||
在上一章中,我们制作了一个大大大数组来存储所有对象,包括玩家本人。
|
||
|
||
通过将玩家视为对象的一部分,你已经成为了游戏的重要组成部分,而不是以局外人的视角看世界。
|
||
|
||
这种方法的优势在具有玩家角色的游戏中变得最为明显,你可以换人 hhhhh。
|
||
|
||
玩家属性不再需要存储在单独的变量中;我们可以使用与任何其他对象相同的数据结构。所以玩家,作为一个对象必须具有以下特点:
|
||
|
||
- 所处位置(我在哪)
|
||
- 玩家可能持有的任何物品的位置。
|
||
|
||
这使得某些常见操作非常容易实现:
|
||
|
||
|**Action**|**Typical Command**|**Example**|
|
||
| ------------------------------ | --------- | ------------------------------------ |
|
||
| 玩家从一个位置移动到另一个位置 | go | player->location = cave; |
|
||
| 列出某个位置存在的项和参与者 | look | listObjectsAtLocation(cave); |
|
||
| 玩家获取物品 | get | silver->location = player; |
|
||
| 玩家掉落物品 | drop | silver->location = player->location; |
|
||
| 列出玩家的物品栏 | inventory | listObjectsAtLocation(player); |
|
||
| 玩家将物品赠送给演员 | give | listObjectsAtLocation(player); |
|
||
| 玩家从演员那里收到物品 | ask | silver->location = player; |
|
||
| 列出其他演员的库存 | examine | listObjectsAtLocation(guard); |
|
||
|
||
你可以尝试去使用这些命令(上面的前两个示例已经在上一章中实现了)。现在,我们将为玩家和非玩家角色介绍一些典型的**物品栏**操作(命令*获取*、*掉落*、*给予*、*询问*和*物品栏*)。
|
||
|
||
::: warning 🤔 思考题:
|
||
你能不能尝试自己实现一下上面的命令?
|
||
|
||
如果你可以在不参考下面内容的情况下就写出基本内容会有很大收获的
|
||
:::
|
||
|
||
## **parsexec.c**
|
||
|
||
```c
|
||
#include <stdbool.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "location.h"
|
||
#include "inventory.h" //这是一个新模块
|
||
|
||
bool parseAndExecute(char *input)
|
||
{
|
||
char *verb = strtok(input, " \n");
|
||
char *noun = strtok(NULL, " \n");
|
||
//第二次使用 strtok 要用 NULL 传参
|
||
if (verb != NULL)
|
||
{
|
||
if (strcmp(verb, "quit") == 0)
|
||
{
|
||
return false;
|
||
}
|
||
else if (strcmp(verb, "look") == 0) //使游戏识别上述命令
|
||
{
|
||
executeLook(noun);
|
||
}
|
||
else if (strcmp(verb, "go") == 0)
|
||
{
|
||
executeGo(noun);
|
||
}
|
||
else if (strcmp(verb, "get") == 0)
|
||
{
|
||
executeGet(noun);
|
||
}
|
||
else if (strcmp(verb, "drop") == 0)
|
||
{
|
||
executeDrop(noun);
|
||
}
|
||
else if (strcmp(verb, "give") == 0)
|
||
{
|
||
executeGive(noun);
|
||
}
|
||
else if (strcmp(verb, "ask") == 0)
|
||
{
|
||
executeAsk(noun);
|
||
}
|
||
else if (strcmp(verb, "inventory") == 0)
|
||
{
|
||
executeInventory();
|
||
}
|
||
else
|
||
{
|
||
printf("I don't know how to '%s'.\n", verb);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
```
|
||
|
||
新命令由以下模块实现。
|
||
|
||
## **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 <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);
|
||
if (obj == NULL)
|
||
{
|
||
// already handled by getVisible
|
||
}
|
||
else if (obj == player)
|
||
{
|
||
printf("You should not be doing that to yourself.\n");
|
||
}
|
||
else if (obj->location == player)
|
||
{
|
||
printf("You already have %s.\n", obj->description);
|
||
}
|
||
else 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"); //告诉用户啥也没有
|
||
}
|
||
}
|
||
```
|
||
|
||
注意:由于动词名词比较好弄,命令 *ask* 和 *give* 只有一个参数:item。
|
||
|
||
::: warning 🤔 思考题:
|
||
为什么我们要这样设计?
|
||
|
||
你能否为这些命令多加几个参数?
|
||
:::
|
||
|
||
从本质上讲,*get*, *drop*, *give* and *ask 这些命令*除了将项目从一个地方移动到另一个地方之外,什么都不做。单个函数 *move 对象*可以对所有四个命令执行该操作。
|
||
|
||
## move.h
|
||
|
||
```c
|
||
extern void moveObject(OBJECT *obj, OBJECT *to);
|
||
```
|
||
|
||
## move.c
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
|
||
static void describeMove(OBJECT *obj, OBJECT *to) //确认移动命令
|
||
{
|
||
if (to == player->location)
|
||
{
|
||
printf("You drop %s.\n", obj->description);
|
||
}
|
||
else if (to != player)
|
||
{
|
||
printf(to == guard ? "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->location == NULL) //有些物体无法拾取,具体识别条件将在后面得到改进
|
||
{
|
||
printf("That is way too heavy.\n");
|
||
}
|
||
else
|
||
{
|
||
describeMove(obj, to);
|
||
obj->location = to; //移动对象
|
||
}
|
||
}
|
||
```
|
||
|
||
::: warning 🤔 思考题:识别一些我们拿不了的物品需要考虑什么因素?
|
||
:::
|
||
|
||
命令“get”使用函数*getVisible*将名词转换为 object,就像命令“go”一样;请参阅上一章。但是对于对玩家(或其他一些参与者)已经持有的对象进行*drop*, *ask*, *give 等*命令时,我们需要稍微不同的东西。我们将在 *noun.c* 中添加一个函数 *getPossession*。
|
||
|
||
## 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 <stdbool.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
```
|
||
|
||
注意:新函数(45-75 行) *getPossession* 是 *getObject* 的装饰器(wrapper)(参见第 52 行),它要么返回匹配的对象,要么返回 NULL(如果没有合适的对象与名词匹配)。
|
||
|
||
函数 *actor 这里*用于命令 *give* 和 *ask*,但它也可能由其他命令调用。所以我们在*misc.c*中定义了它。
|
||
|
||
## misc.h
|
||
|
||
```c
|
||
extern OBJECT *actorHere(void);
|
||
extern int listObjectsAtLocation(OBJECT *location);
|
||
```
|
||
|
||
## misc.c
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
#include "object.h"
|
||
|
||
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;
|
||
}
|
||
```
|
||
|
||
::: warning 🤔 思考题:上面第四行中的函数 actorHere 返回的指针指向什么?
|
||
:::
|
||
|
||
在第 9 行中,有一个详尽的,硬编码的非玩家角色列表(到目前为止,只有一个:*守卫*)。
|
||
|
||
在第 10 章中,我们将开始使用属性作为区分角色与项目和其他非参与者对象的更优雅方式。
|
||
|
||
测试样例
|
||
|
||
Welcome to Little Cave Adventure.
|
||
You are in an open field.
|
||
You see:
|
||
a silver coin
|
||
a burly guard
|
||
|
||
--> get silver
|
||
You pick up a silver coin.
|
||
|
||
--> inventory
|
||
You see:
|
||
a silver coin
|
||
|
||
--> look around
|
||
You are in an open field.
|
||
You see:
|
||
a burly guard
|
||
|
||
--> give silver
|
||
You give a silver coin to a burly guard.
|
||
|
||
--> inventory
|
||
You are empty-handed.
|
||
|
||
--> ask silver
|
||
You get a silver coin from a burly guard.
|
||
|
||
--> inventory
|
||
You see:
|
||
a silver coin
|
||
|
||
--> go cave
|
||
OK.
|
||
You are in a little cave.
|
||
You see:
|
||
a gold coin
|
||
|
||
--> give silver
|
||
There is nobody here to give that to.
|
||
|
||
--> drop silver
|
||
You drop a silver coin.
|
||
|
||
--> look around
|
||
You are in a little cave.
|
||
You see:
|
||
a silver coin
|
||
a gold coin
|
||
|
||
--> inventory
|
||
You are empty-handed.
|
||
|
||
--> quit
|
||
|
||
Bye!
|