在基于文本的冒险游戏中,如何防止长期混淆的条件代码?

时间:2016-09-24 18:56:01

标签: c++ algorithm

我正在创建一个基于文本的,用C ++选择你自己的冒险游戏。

在这个游戏中,您可以选择去哪里,选择做什么等等。

我的问题是,如何防止这种情况变得非常混乱。

示例:

让我们说,在比赛的某个时刻,你可以被问到是去森林还是沙漠。如果你选择沙漠,这就是森林里完全不同的故事情节。

那么我如何阻止我的代码看起来像这样。

if (player goes to the desert)advice? { 
    /*Whole story line of the desert*/
else if (player goes to the forest) {
    /*Whole story line of the forest */

在这些故事情节中会有更多这样的条件,以及更精细的故事情节,所以有什么方法可以在一个单独的文件中编写一个故事线的代码,然后只为该条件运行该文件?无论如何我可以单独做,而不是在条件内写出一切?如果我这样做,代码会很快变得很长,并且难以查看/编辑。

我正在考虑做标题并在写出故事线的标题内部创建函数,所以我只需输入函数,但如果我这样做,那么我就无法访问游戏中的全局变量例如playerNameplayerRace

赞赏任何和所有建议。我是C ++的新手所以请原谅我,如果我错过了一些非常明显的东西。

4 个答案:

答案 0 :(得分:2)

我将对 Trevor Hickey 状态机命题进行一些扩展,因为这是一个好主意。

首先,您需要意识到您的故事情节可以使用好的旧图表进行建模

  1. 独立故事是您认为完整且不可分离的游戏元素。例如,有DesertStory,ForestStory。它们是节点,顶点。您应该通过其名称(例如
  2. )唯一标识故事
  3. 故事之间的关系是边缘。这些边缘需要是可序列化的,这意味着能够既可以表示为对象又可以表示为某种持久格式,并且可以在它们之间加载和保存。因为您想要自定义您的游戏,您可能希望允许持久格式基于文本,以便可以手动编辑它们并在游戏开始时加载。
  4. 现在状态机来自于故事到另一个故事之间的过渡是有条件的。
  5. 在编程术语中,它可能意味着:虚拟故事类

    struct Story 
    {
         virtual std::string name() = 0;
         virtual int play() = 0;
    };
    

    故事弧,它连接故事。它需要一个触发条件,这可能是最后一个故事返回的原因

    struct StoryConnection
    {
        std::string nameStorySource;
        std::string nameStoryDestination;
        int condition;
    };
    

    有了这个,你可以在一边写个别故事,然后分别写故事弧。您还可以通过修改故事弧来调整和修改游戏的逻辑。您可以进行多个游戏,每个游戏只是一组StoryConnections。

    逻辑很简单:

    Story* s = new InitStateStory; 
    while(!endOfGame(s))
    {
          int decision = s.play();
          StoryConnection conn = getConnection(s.name(), decision);
          Story* nextstory = creatNextStory(conn.nameStoryDestination);
          delete s;
          s = nextstory;
    }
    

答案 1 :(得分:1)

可能是基于类的解决方案。这个问题引用广泛,所以不太确定哪种设计模式适合。但是,示例类可能是CrossroadsDesicision,它会导出选项["Go to desert", "Go to city", ...]并且有一个方法apply,它应该从数组中接收选项并返回相关的决策类以供下一个使用步骤

修改 基类应包含:

  • possibleDecisions - 一系列可能的决定(你可以在这里使用一个选项类,由一个名字组成(字符串或枚举 - 你应该在这里使用模板)和描述)

  • 申请 - 接受决定,对其采取行动并返回下一个决定的职能

答案 2 :(得分:1)

您需要构建代码。所以你有一个类Player,一个类Place,然后你需要一个数组来存储这些地方,在那个地方发生的任何事情都将由一个虚函数来处理:

编辑:

我已经更改了代码来处理目的地,如果你想在列表中维护目的地以便于添加/删除,你只需要另一个类:

#include <iostream>
#include <vector>
#include <climits>

class Place;

const int PLACE_TAVERN = 0;
const int PLACE_FOREST = 1;
const int PLACE_DESERT = 2;

const int NUMPLACES = 3;
std::vector<Place *>vPlaces;
Place * Destination[UCHAR_MAX];

class Place {
private:
    bool connections[NUMPLACES]; // This is a simple and inefficient way of doing it: you can also use a linked list with nodes for more flexibility/efficiency
protected:
    int id;  
    void listConnections()
    {
    int n = 0;
        for (int i=0; i<NUMPLACES; i++) {
            if (connections[i]) {
                if (n>0) {
                    std::cout << ", ";
                }
                else {
                    n++;
                }
                std::cout<< vPlaces[i]->name;
            }
        }
        std::cout << std::endl;
    }
public:
    std::string name;  

  virtual void describe() 
  {
    std::cout << "You are in " << name << std::endl;
    std::cout << "From there you can go to: " ;
    this->listConnections();
  }
  Place(int p, std::string n, char l) 
  {
    id = p;
    name = n;
    Destination[(int)l] = this;
    for (int i=0; i<NUMPLACES; i++) {
        connections[i] = false;
    }
  }

  void setConnection(int placeId) {
    connections[placeId] = true;
  }

  bool canGoTo(Place *destination) {  
    return (NULL != destination) && connections[destination->id];
  }
};


class Tavern : public Place {
public:
 Tavern() : Place(PLACE_TAVERN, "the (T)avern", 'T') {} // the move letters should be unique
};

class Forest : public Place {
public:
 Forest() : Place(PLACE_FOREST, "the (F)orest", 'F') {}
};

class Desert : public Place {
public:
    Desert() : Place(PLACE_DESERT, "the (D)esert", 'D') {}
  };

int main(void)
{
    for (int i = 0; i<UCHAR_MAX; i++) {
        Destination[i] = NULL;
    }
  Tavern* tavern = new Tavern();
  Forest* forest = new Forest();
  Desert* desert = new Desert();

  tavern->setConnection(PLACE_FOREST) ; // you can do this manually or maintain an array of bool
  forest->setConnection(PLACE_TAVERN) ;
  forest->setConnection(PLACE_DESERT) ;
  desert->setConnection(PLACE_FOREST) ;

  vPlaces = {tavern, forest, desert};

  Place* currentPlace; 
  Place* newPlace; 

  currentPlace = tavern;
  newPlace = NULL;

  char key = 0;

  do {
    currentPlace->describe();
    std::cout << "Choose a destination by their letter or (q)uit?";
    std::cin >> key;
    do {} while (std::cin.get() != '\n'); // flush keyboard

    newPlace = Destination[(int)key];
    if (currentPlace->canGoTo(newPlace)) {
        currentPlace = newPlace;
    }
    else if (key != 'q') {
        if (NULL == newPlace) {
            std::cout << "You cannot go into the void like that!" << std::endl;
        }
        else {
            std::cout << "You cannot go to " << newPlace->name << " from " << currentPlace->name << "!" << std::endl;            
        }
        std::cout << "Press Enter to continue...";
        do {} while (std::cin.get() != '\n');
    }
  } while (key != 'q');
  std::cout << "bye" << std::endl;
  return 0;
}

编译:

g++ -o file file.cc -Wall -std=c++11

答案 3 :(得分:0)

您可以使用enumswitch

class Player
{
    public:
    enum class CurrentLocation {Forest, Desert, Undefined};
    CurrentLocation currentLocation;
    //...
}

//and in checking
switch(player.currentLocation)
{
    case Player::CurrentLocation::Forest:
        //player is in forest
        break;
    case Player::CurrentLocation::Desert:
        //player is in desert
        break;
    default:
        //player is dead or sth
        break;
}

您还可以将所有内容包装在类或函数中,并将其地址存储在player对象中,这样您甚至不必检查状态player,您只需编写:< / p>

class Player
{
    Location* currentLocation;
    public:

    void setCurrentLocation(Location* loc) {currentLocation = loc;}
    Location* getCurrentLocation(void) {return currentLocation;}
    //...
}

//and use it, of course you have to implement Location class/struct
player.currentLocation()->showMap();

但选择好的游戏设计是一个复杂的主题,并不意味着“简单回答”