import{_ as s,c as i,o as a,a4 as n}from"./chunks/framework.DtvhUNIn.js";const F=JSON.parse('{"title":"7.增大距离","description":"","frontmatter":{},"headers":[],"relativePath":"技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.6.7.增大距离.md","filePath":"技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.6.7.增大距离.md"}'),h={name:"技术资源汇总(杭电支持版)/3.编程思维体系构建/3.4.6.7.增大距离.md"},k=n(`

7.增大距离

一个典型的冒险包含许多谜题。众所周知,Infocom的冒险很难完成。解决每个难题可能需要数周甚至数月的反复试验。

当玩家操纵角色失败后,如果只是返回“你不能这么操作”来回应玩家,会很 nt,很没意思。

它忽略了电脑游戏的一个重要方面,而这也是生活本身的一部分:玩家必须从错误中吸取教训。

当你的游戏反复输入东西都是,你不能这样做的时候,会显得很无聊的。

冒险游戏至少应该做的是解释为什么玩家的命令无法完成:“你不能这样做,因为......”这有助于使虚拟世界更具说服力,故事更可信,游戏更有趣。

我们已经付出了相当大的努力让游戏解释为什么某些命令是无效的。只需看看名词.c,inventory.c,location.cmove.c中的许多printf调用。但随着游戏变得越来越复杂,这正成为一个相当大的负担。我们需要一种更结构化的方法来检测和处理错误情况。这就是我们在本章中将要讨论的内容。

大多数命令对一个或多个对象进行操作,例如:

首先要检查的(在解析器捕获检测是否会有明显拼写错误之后)是这些对象是否存在;

失败应该导致类似“这里没有...“或”你看不到任何东西...”等文字出现。在本章中,我们将构建一个通用函数,每个命令都可以使用它来找出玩家是否在可及的范围内。

你可能认为我们只需要区分两种情况:对象在这里,或者它不在这里。

但是许多命令需要更多的渐变,而不仅仅是“这里”和“不在这里”。例子:

distSelf对象是玩家object == player
distHeld玩家持有物体object->location == player
distHeldContained玩家拿着另一个包含该物体的物体(例如袋子)object->location != NULL &&
object->location->location == player
distLocation对象是玩家的位置object == player->location
distHere对象位于玩家的位置object->location == player->location
distHereContained一个物体(NPC 或“容器”)存在于玩家的位置,正在拿着另一个物体object->location != NULL &&
object->location->location == player->location
distOverthere对象是附近的位置getPassage(player->location, object) != NULL

第一种情况(对象是玩家)可能看起来微不足道,但它仍然很重要。例如,命令“examine yourself”不应该返回“这里没有你自己”。

我试图遵循一个逻辑顺序:附近的事物最高优先级,随后优先级会变低。

distNotHere对象不在这里(或看起来)不在这里
distUnknownObject解析器无法识别输入的名词object == NULL

请注意,我们有七种不同的“这里”案例,但只有一种是“不在这里”。这是因为通常,游戏只需要提供有关玩家可以感知的事物的信息。如果它不在这里,那么就没什么可说的了。

在最左边的列中,我们为每个案例提出了一个符号名称。我们将在名为 DISTANCE枚举中收集这些名称。

c
typedef enum {
   distSelf,
   distHeld,
   distHeldContained,
   distLocation,
   distHere,
   distHereContained,
   distOverthere,
   distNotHere,
   distUnknownObject
} DISTANCE;

💡 typedef 以及枚举类 enum 之前有接触过吗?没有接触过的话就去学习一下吧。

在最右边的列中,我们为每个情况提出了一个满足条件。通过一些重新洗牌,我们可以很容易地将其转换为计算对象“距离”的函数(从玩家的角度来看):

c
DISTANCE getDistance(OBJECT *from, OBJECT *to)
{
   return to == NULL                               ? distUnknownObject :
          to == from                               ? distSelf :
          to->location == from                     ? distHeld :
          to == from->location                     ? distLocation :
          to->location == from->location           ? distHere :
          getPassage(from->location, to) != NULL   ? distOverthere :
          to->location == NULL                     ? distNotHere :
          to->location->location == from           ? distHeldContained :
          to->location->location == from->location ? distHereContained :
                                                     distNotHere;
}

🤔 思考题:

你是否有其他方法实现这个功能?

注:自行实验即可

就这样!我们可以调用此函数并对其返回值进行比较。例如,我们在 noun*.c*中有以下代码:

c
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))))

现在,我们可以用适当的距离检查替换每个子条件:

c
else if (!(getDistance(player, obj) == distSelf ||
           getDistance(player, obj) == distLocation ||
           getDistance(player, obj) == distHeld ||
           getDistance(player, obj) == distHere ||
           getDistance(player, obj) == distOverthere ||
           getDistance(player, obj) == distHeldContained ||
           getDistance(player, obj) == distHereContained))

这可以简化为:

c
else if (getDistance(player, obj) >= distNotHere)

🤔 尝试理解一下这样做的意义

这只是一个例子,让你对这个概念有所了解;您将在下面找到noun.c的实际实现,看起来略有不同。

是时候把事情落实到位了。枚举 DISTANCE 和函数 getDistance 的定义被添加到 misc.hmisc.c 中,因为我们将在多个模块中使用它们。

misc.h

c
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);

misc.c

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->destination == 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 == guard)
      {
         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("You see:\\n");
         }
         printf("%s\\n", obj->description);
      }
   }
   return count;
}

注意:isHolding 这个函数之后我们将在各个地方使用

location.h

c
extern void executeLook(const char *noun);
extern void executeGo(const char *noun);

在函数 executeGo 中,我们可以用检查距离来替换大多数 if 条件。

location.c

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
   {
      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);
   switch (getDistance(player, obj))
   {
   case distOverthere:
      printf("OK.\\n");
      player->location = obj;
      executeLook("around");
      break;
   case distNotHere:
      printf("You don't see any %s here.\\n", noun);
      break;
   case distUnknownObject:
      // already handled by getVisible
      break;
   default:
      //上述情况均为出现
      if (obj->destination != NULL)
      {
         printf("OK.\\n");
         player->location = obj->destination;
         executeLook("around");
      }
      else
      {
         printf("You can't get much closer than this.\\n");
      }
   }
}

🤔 思考题:你能否为 switch 函数增加更多 case 来完善判断条件?

函数 executeGet 也是如此。

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 <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 == 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");
   }
}

最后,我们将调整 noun*.c中的约束。我们正在向函数getObject*添加两个参数,以便找到特定名词的匹配项,同时忽略任何被认为不存在的对象。这将在下一章中得到真正的回报,我们将在下一章中介绍具有相同标签的不同对象。

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"
#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 *from, DISTANCE maxDistance)
{
   OBJECT *obj, *res = NULL;
   for (obj = objs; obj < endOfObjs; obj++)
   {
      if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance)
          //只考虑与对象距离小于或等于最大距离的物体
      {
         res = obj;
      }
   }
   return res;
}

OBJECT *getVisible(const char *intention, const char *noun)
{
   OBJECT *obj = getObject(noun, player, distOverthere);
   //玩家看不到的自动忽略
   if (obj == NULL)
   {
      if (getObject(noun, player, distNotHere) == NULL)
      {
         printf("I don't understand %s.\\n", intention);
      }
      else
      {
         printf("You don't see any %s here.\\n", noun);
      }
   }
   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, from, distHeldContained)) == NULL)
       //限制范围
   {
      if (getObject(noun, player, distNotHere) == NULL)
      {
         printf("I don't understand what you want to %s.\\n", verb);
      }
      else 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);
      }
   }
   else if (obj == from)
   {
      printf("You should not be doing that to %s.\\n", obj->description);
      obj = NULL;
   }
   return obj;
}

🤔 思考题:你能理解什么时候加 const,什么时候不用吗?

在本章中,距离的概念主要用于在游戏可以给用户的不同响应之间进行选择。但是,距离的好处并不局限于输出端;它可以同样很好地用于在输入端进行改进。在下一章中,我们将使用距离来提高对用户输入的名词的识别。

输出样例

Welcome to Little Cave Adventure. You are in an open field. You see: a silver coin a burly guard a cave entrance

--> go guard You can't get much closer than this.

--> give silver You are not holding any silver.

--> ask silver There appears to be no silver you can get from a burly guard.

--> get silver You pick up a silver coin.

--> get gold You don't see any gold here.

--> give silver You give a silver coin to a burly guard.

--> go cave OK. You are in a little cave. You see: a gold coin an exit

--> get gold You pick up a gold coin.

--> give gold There is nobody here to give that to.

--> quit

Bye!

`,70),l=[k];function t(p,e,E,r,d,g){return a(),i("div",null,l)}const o=s(h,[["render",t]]);export{F as __pageData,o as default};