当函数依赖于两个不同的类时,避免进行类型检查

Avoid type checking when function dependant on two distinct classes

本文关键字:检查 类型 依赖于 函数依赖 函数 两个      更新时间:2023-10-16

我正在尝试编写一个游戏中心,它基本上是棋盘游戏或可以在命令行轻松玩的游戏的集合。(Tic-Tac-Toe、Connect Four等)用户还可以选择制作两个玩家中的任何一个电脑。如果我只制作Tic-Tac-Toe,这不会是一个问题,因为我可以在Player的人机子类中实现"移动",然后以多态方式调用正确的子类,但随着其他游戏的添加,我不知道如何摆脱类型检查。例如,如果移动是玩家内部的一个功能,那么除了通过类型检查之外,该功能如何知道是根据Tic-Tac-Toe还是Connect Four的规则移动?

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

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

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

我是什么意思?你问

例如,如果移动是玩家内部的一个功能,那么除了通过类型检查之外,该功能如何知道是根据Tic-Tac-Toe还是Connect Four的规则移动?

让我问你这个问题——假设你确实像你描述的那样解决了这个问题。现在你想添加一个新游戏(比如跳棋)。你需要更改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;
}

这个怎么样:

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

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