Files
fzu-product/3.编程思维体系构建/3.4.6.14.丰富命令.md
2023-04-14 15:41:04 +08:00

9.3 KiB
Raw Blame History

14.丰富命令

是时候证明新的解析器确实有能力解释更复杂的命令了。

到目前为止,我们的解析器只接受简单的动词-名词命令。让我们试着解析一些更复杂的命令,比如:

  • get coin from box
  • put coin in box
  • ask coin from guard
  • give coin to guard

在 parsexec.c 中添加模式似乎很简单。

  • get A from B
  • put A in B
  • ask A from B
  • give A to B

思考题:你能否自行实现这些命令

但是正如前一章所解释的,类似的命令(比如 "从 B 中获取 A "和已经存在的 "获取 A")必须以正确的顺序排列。如果'get A'首先出现,那么它将消耗任何以'get'开头的命令,包括所有应该被新模式匹配的命令。简而言之,"从 B 获取 A "必须出现在 "获取 A "之前。

有些命令(例如 put没有单名词的变体。然而我们将添加一个带有单非词的模式'put A')。我们需要这个模式来正确捕捉一个拼写错误的名词。完整的模式 "把 A 放进 B "不会匹配 "把 Koin 放进盒子 "这样的命令。正如前一章所解释的,只有模式末尾的非终端才能够捕捉到拼写错误的名词或其他不被识别的名词。

这也适用于有两个以上的名词的命令。有 n 个名词,你需要 n 个模式来处理所有可能的不匹配。一个有三个参数的例子:

  1. “paint A on B with C”
  2. “paint A on B”
  3. “paint A”

同样,模式的顺序是至关重要的:如果 "涂抹 A "在最上面(意味着它将被首先尝试),那么它将消耗所有的涂抹命令,包括那些本应由 1 和 2 捕获的命令。

目前,我们不需要有两个以上非终结点的模式。

parsexec.h

extern bool parseAndExecute(const char *input);

parsexec.c

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include "object.h"
#include "misc.h"
#include "match.h"
#include "location.h"
#include "inventory.h"
#include "inventory2.h"
#include "openclose.h"

typedef struct
{
   const char *pattern;
   bool (*function)(void);
} COMMAND;

static bool executeQuit(void)
{
   return false;
}

static bool executeNoMatch(void)
{
   const char *src = *params;
   int len;
   for (len = 0; src[len] != '\0' && !isspace(src[len]); len++);
   if (len > 0) printf("I don't know how to '%.*s'.\n", len, src);
   return true;
}

bool parseAndExecute(const char *input)
{
   static const COMMAND commands[] =
   {
      { "quit"                , executeQuit       },
      { "look"                , executeLookAround },
      { "look around"         , executeLookAround },
      { "look at A"           , executeLook       },
      { "look A"              , executeLook       },
      { "examine A"           , executeLook       },
      { "go to A"             , executeGo         },
      { "go A"                , executeGo         },
      { "get A from B"        , executeGetFrom    },
      { "get A"               , executeGet        },
      { "put A in B"          , executePutIn      },
      { "drop A in B"         , executePutIn      },
      { "drop A"              , executeDrop       },
      { "ask A from B"        , executeAskFrom    },
      { "ask A"               , executeAsk        },
      { "give A to B"         , executeGiveTo     },
      { "give A"              , executeGive       },
      { "inventory"           , executeInventory  },
      { "open A"              , executeOpen       },
      { "close A"             , executeClose      },
      { "lock A"              , executeLock       },
      { "unlock A"            , executeUnlock     },
      { "A"                   , executeNoMatch    }
   };
   const COMMAND *cmd;
   for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++);
   return (*cmd->function)();
}

在一个新的模块 inventory2.c 中,我们实现了新的多名词命令 get、drop/put、ask 和 give。现在我们终于可以把金币放回盒子里了(可喜可贺,可喜可贺)。

inventory2.h

extern bool executeGetFrom(void);
extern bool executePutIn(void);
extern bool executeAskFrom(void);
extern bool executeGiveTo(void);

inventory2.c

#include <stdbool.h>
#include <stdio.h>
#include "object.h"
#include "match.h"
#include "noun.h"
#include "move.h"
#include "reach.h"

bool executeGetFrom(void)
{
   OBJECT *from = reachableObject("where to get that from", params[1]);
   if (from != NULL && getVisible("what you want to get", params[0]) != NULL)
   {
      if (from->health > 0)
      {
         printf("You should ask %s nicely.\n", from->description);
      }
      else
      {
         moveObject(getPossession(from, "get", params[0]), player);
      }
   }
   return true;
}

bool executePutIn(void)
{
   OBJECT *obj = getPossession(player, "put", params[0]);
   if (obj != NULL)
   {
      OBJECT *to = reachableObject("where to put that in", params[1]);
      if (to != NULL)
      {
         if (to->health > 0)
         {
            printf("You should offer that nicely to %s.\n", to->description);
         }
         else
         {
            moveObject(obj, to);
         }
      }
   }
   return true;
}

bool executeAskFrom(void)
{
   OBJECT *from = reachableObject("who to ask that", params[1]);
   if (from != NULL)
   {
      if (from->health > 0)
      {
         if (getVisible("what you want to ask", params[0]) != NULL)
         {
            moveObject(getPossession(from, "ask", params[0]), player);
         }
      }
      else
      {
         printf("There is no response from %s.\n", from->description);
      }
   }
   return true;
}

bool executeGiveTo(void)
{
   OBJECT *obj = getPossession(player, "give", params[0]);
   if (obj != NULL)
   {
      OBJECT *to = reachableObject("who to give that to", params[1]);
      if (to != NULL)
      {
         if (to->health > 0)
         {
            moveObject(obj, to);
         }
         else
         {
            printf("No eagerness is shown by %s.\n", to->description);
         }
      }
   }
   return true;
}

仔细观察上面的代码,你可能会注意到,像 "把硬币放进森林 "和 "把硬币放进盒子 "这样的命令(当盒子被关闭时)会得到以下奇怪的回答:"这太重了。" 这是因为大多数物体(包括封闭的盒子,以及像森林这样的场景)容纳其他物体的能力为零。这是正确的,但这个信息是很不恰当的。为了避免这种情况,我们将为那些容量为零的物体引入一个单独的信息:"这样做似乎不太适合。"

先想想有没有什么办法解决?

然而,当它作为对 "把钥匙放进盒子里 "的回应时,这个特殊的信息是完全误导的。所以我们将为这种特殊的对象组合做一个特殊的例外。

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 (to->capacity == 0)
   {
      printf(obj == keyForBox && (to == closedBox || to == lockedBox) ?
                "The key seems to fit the lock.\n" :
                "It doesn't seem to fit in.\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;
   }
}

毫无疑问,我们接受的命令越复杂,我们就需要花更多的精力来做出令人信服的答复。 输出样例

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 forest all around

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

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

--> go cave You walk into the cave.

You are in a little cave. You see: an exit to the west solid rock all around a closed door to the south a tiny key

--> get key You pick up a tiny key.

--> go south The door is closed.

--> open door You open a closed door to the south.

--> go south You walk through the door into a backroom.

You are in a backroom. You see: solid rock all around an open door to the north a wooden box

--> unlock box You unlock a wooden box.

--> open box You open a wooden box.

--> examine box The box is open. You see: a gold coin

--> get gold from box You get a gold coin from a wooden box.

--> inventory You have: a gold coin a tiny key

--> put gold in box You put a gold coin in a wooden box.

--> examine box The box is open. You see: a gold coin

--> quit

Bye!