国际象棋引擎。可以将板对象引用传递给一块吗?

Chess engine. Is it ok to pass board object reference to a piece?

我正在创建一个国际象棋引擎。我有 Piece 接口,Rook、Bishop 等实现了这个 class。我的棋盘是 Piece[][] 数组。假设白人玩家想要移动他的主教。我将目的地坐标和板参考传递给主教。主教检查,如果目的地在同一条对角线上,那么它会询问棋盘在他的位置和目的地广场之间是否没有任何棋子。从 OOP 的角度来看可以这样做吗? 谢谢

从 OOP 的角度来看,象(车等等)应该能够说出对他来说什么是合法转弯——也就是说,如果给定的场地在同一对角线上。它还可以告诉棋盘它不能 "skip" 其他棋子(IIRC 只有骑士可以这样做,所以骑士可以覆盖它)。

再一次,任何棋子都不能移动到上面有另一块相同颜色的棋子的区域,也没有棋子应该危及(检查)国王。这些约束应由您的 GameController class(或封装该逻辑的一些底层 class)检查,因为它们适用于所有片段。

如果 GameController 检查目标字段是否为空,然后询问棋子是否可以移动到那里,棋子本身就不必知道您的棋盘数组,并且公共逻辑将集中在控制器中。

抱歉我的国际象棋词汇量很差:)

从设计的角度来看,您有两个(或更多)选项可供考虑:

  1. 棋盘是一种规则管理器,它应该知道棋子如何行动 - 这是有限制的 - 棋盘必须知道每个参与者,因为国际象棋的类型数量有限,这不是问题。
  2. 棋盘只是棋子的占位符/坐标系。使用这种方法,您可以通过为 Piece 使用 abstract class (或接口,就像您写的那样,但是片段之间会有许多共同的属性,所以 abstract class 对我来说看起来更好)来节省大量代码, 每种类型的作品都会 extend/implement 它。示例:

    public abstract class Piece
    {
        private int row;
        private int column; // or other method to store position
        private boolean isBlack // or enum for type  
    
        // contructor, getters, setters etc...
    
        public abstract boolean canMove(int newX, int newY);
       /* some other abstract methods if you need */
    }
    

    以后

    public class Bishop extends Piece
    {
          @Override
          public boolean canMove(int newX, int newY)
          {
                if( /*check if new points aare on diagonal */)
                    return true;
                else
                    return false;
          }
     }
     public class Knight extends Piece
     {
          @Override
          public boolean canMove(int newX, int newY)
          {
                if( /*check if L shaped with prev pos */)
                    return true;
                else
                    return false;
          }
     }
    

我会选择第二个选项。它更面向对象 + 它允许更灵活地存储片段。在游戏 class 中,您可以使用将 Piece 作为参数并传递任何扩展 Piece class、Bishops、Knight 等的方法,多态性将为您完成这项工作。如果是第一个选项,您可能需要使用一些 switch/case.

当然你还需要其他 class 用于玩家、游戏状态等

回答你的问题——把板子传给一块是可以的。在我的命题中,它知道它的位置,所以它只需要知道什么是新提议的位置,以检查它是否没有超过棋盘大小并且对于它的类型是合法的。由于游戏控制器会检查碰撞,您实际上并不需要该板?

有点娇嫩。

OOP 的观点

从 OOP 的角度来看,有人可能会质疑 Board 是否应该首先是一个 Piece[][] 数组。一个漂亮、干净、面向对象的设计可能会涉及到类似

的东西
interface Board {
    Piece get(int r, int c);

    // Should this be here? See below...
    void set(int r, int c, Piece p);
}

然后,一个关键的问题是:主教会把 "itself" 放在给定棋盘的目标位置上吗?或者,关注 OOP 点:给棋子的棋盘是 mutable 还是 "read-only"?可以想象一个 MaliciousBishop class,当它被赋予 Board 时,通过将自己置于国王的位置来刺杀对手的国王。

从非常高层次的抽象 OOP 的角度来看,人们可能会质疑 Piece 是否应该具有任何智能。 Piece 是一块哑塑料。它不知道任何关于国际象棋规则的信息。 (玩家)可以将棋子放在任何地方,遵守或忽略国际象棋规则。因此,遵守甚至检查任何规则当然不是作品的工作。可以说遵守规则是玩家所期望的,而执行规则服从是高级智能的工作(一些 "ChessGameManager" class,也许)。

似乎 (!) 适合 OO 国际象棋实施的一种简单方法是 class 像

abstract class Piece {
    Color getColor() { ... }
    Point getPosition() { ... }

    abstract void doMove(...) { ... }
}

class Bishop extends Piece {
    void doMove(....) { ... }   
}

// + other classes extending "Piece"

但请注意,这可能并不总是最好的方法,而且可能并不总是足够的。特别是,您应该非常清楚地了解您的 EngineBoardPiecePlayer classes 交互以及他们的责任是什么。 (考虑一段时间后,您可能会得出这样的结论:您还需要 Move class...)。一般来说,检查一步棋是否有效比乍一看要复杂。您提到过,对于 Bishop 移动,您检查目标位置是否有效,并且中间没有其他棋子。但如果此棋导致己方国王被制止,此棋仍然无效。这只能通过 "engine" 来检查,而很难通过作品本身来检查。人们容易忘记的其他事情(涉及 整个游戏状态 的信息,因此很难由单个 Piece 处理)是 Castling or En passant 步。

下棋引擎观点

对于国际象棋引擎,有几个要求使得一个好的、面向对象的方法特别困难。如果您的目的是编写一个高效的国际象棋引擎,那么您的棋盘很可能是一个 long 值的数组,这些值通过按位运算进行操作....

(旁注:如果您按照上面的建议将 Board class 设计为 接口 ,那么您仍然可以保持良好的, high-level, object oriented view 在用于引擎本身的这种高度性能优化的表示上。我的经验法则:始终在开始时将所有内容建模为接口。这很容易之后使它更具体)


所以要看你要不要写

  • 适合两个人类棋手的面向对象的国际象棋,带有规则检查或
  • 国际象棋引擎

您可能想以不同的方式处理游戏设计的某些部分。


编辑:当您寻找有关国际象棋(引擎)编程的信息时,您肯定会偶然发现这一点,但我想指出 https://www.chessprogramming.org/Main_Page 提供了大量背景信息。再次强调:这并不是真正的 OO 设计,而是关于国际象棋引擎的细节。