【问题标题】:Avoid type checking when function dependant on two distinct classes当函数依赖于两个不同的类时避免类型检查
【发布时间】:2015-06-30 05:50:25
【问题描述】:

我正在尝试编写一个游戏中心,它基本上是一组棋盘游戏或可以在命令行轻松玩的游戏。 (井字游戏、四连等)用户还可以选择将两个玩家中的任何一个制作为计算机。如果我只制作井字游戏,这将不是问题,因为我可以在 Player 的人类和计算机子类中实现“移动”,然后以多态方式调用正确的,但是随着其他游戏的添加,我不知道如何摆脱类型检查。例如,如果 move 是 player 内部的函数,那么除了通过类型检查之外,该函数如何知道是按照井字游戏规则还是四连线规则移动?

我正在考虑为每个场景创建一个具有不同子类的单独移动类,但我不确定这是否正确或如何实现。

顺便说一句,这是用 C++ 编写的。

【问题讨论】:

  • 移动多态是可以的。然后制作一个游戏(或几个游戏)并让游戏实例化移动对象。

标签: c++ oop design-patterns


【解决方案1】:

如果您希望所有游戏都继承自同一个 game_base 基类,那么您需要使用这些游戏的人不“知道”他们正在玩什么游戏。

我是什么意思?你问了

例如,如果 move 是 player 内部的一个函数,那么除了通过类型检查之外,该函数如何知道是按照井字游戏规则还是四连线规则移动?

让我问你这个问题 - 说你确实像你描述的那样解决了这个问题。现在你想添加一个新游戏(比如跳棋)。您是否需要更改player 才能知道如何玩跳棋?

如果是这样 - 那么你的游戏不应该使用继承(不应该全部继承自 game_base

为什么?因为基本上你是在说“我的player 课程必须内置所有游戏的所有可能性”。如果是这样,为什么有不同的游戏类?

作为解决方案,我会这样说:

  • 如果人类玩家需要根据井字游戏或四连线移动,它会如何分类? GAME 类应该告诉它!不仅如此——假设玩家根本不了解这款游戏!告诉玩家当前的合法动作是什么!

一个例子:

class game_base{
  // returns the number of players in this game. 
  // Doesn't change during play
  virtual int num_players() = 0;
  // returns the current player - the player whose turn it is now
  virtual int curr_player() = 0;
  // returns a string that describes (or draws) the current
  // game state
  virtual std::string draw_board()const = 0;
  // returns all the possible legal moves the player can 
  // make this turn
  virtual std::vector<std::string> curr_turn_possible_moves()const = 0;
  // Plays the given move. Has to be one of the moves
  // returned by curr_turn_possible_moves()
  virtual void play(std::string move) = 0;
  // returns the player who won the game, or -1 if the 
  // game is still ongoing
  virtual int won() = 0;
};

看看如何使用这个游戏类,让同一个 player 类可以玩你曾经制作的所有游戏!

您甚至可以制作一个适用于所有游戏的“检查所有选项,直至 N 级深度”!

  • 关于电脑玩家:您可以制作一个“通用”电脑玩家,尝试向前走 N 步以寻找获胜策略(您需要将选项添加到 virtual game_base *copy()const 当前游戏状态)。但实际上,您需要为每个游戏量身定制的电脑播放器(只玩该游戏)。

那么你是怎么做到的呢?更重要的是 - 你怎么知道每个游戏选择哪个电脑玩家?

所有计算机玩家都将继承自 computer_player_base 类(它可能只有一个函数 play 在给定游戏的情况下执行下一步)。诀窍是 - 如果您现在想为游戏添加新的计算机玩家(新游戏或现有游戏的另一个可能的计算机玩家),您需要一种“注册”该玩家的方法。你想要的东西看起来像:

std::vector<computer_player_base*> possible_computer_players(const game_base*game);

返回所有可能知道如何玩给定游戏的计算机玩家。最简单的方法是让计算机玩家类本身告诉你它是否可以玩给定的游戏。所以它看起来像这样:

class computer_player_base{
  // return true if this class knows how to play this game
  // implemented using dynamic_cast - something like this:
  // return dynamic_cast<connect_4*>(game) != 0;
  virtual bool can_play(game_base *game) = 0;
  // plays the next turn of the game
  virtual void play(game_base *game) = 0;
};

然后有一个可供选择的所有计算机播放器的全局列表,例如

std::vector<computer_player_base*> all_computer_players

您将填充每个计算机播放器中的一个,以及一个函数

std::vector<computer_player_base*> possible_computer_players(game_base *game)
{
  std::vector<computer_player_base*> res;
  for (auto p:all_computer_players)
    if (p->can_play(game))
      res.push_back(p);
  return res;
}

【讨论】:

  • 谢谢!我会尽快考虑实施。从来没有这样想过。
  • @Keltek 请注意,这只是一个基本想法,而不是完整的解决方案。你会有很多不同的事情需要考虑。重要的部分是这样想:您希望能够添加新游戏(game_base 的新子代)而不更改程序中的任何其他内容。与添加新的计算机播放器相同。
【解决方案2】:

这个怎么样:

enum GameType {
TicTacToe,
ConnectFour,
SnakesLadders,
Battleships,
};

然后将 GameType gt 存储在您正在使用的游戏对象中。然后在每个方法中做一个switch(gt)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 2020-12-02
    • 1970-01-01
    相关资源
    最近更新 更多