国际象棋引擎。将棋盘对象引用传递给棋子是否可行?

3

我正在创建一个棋局引擎。我有一个Piece接口,Rook、Bishop等实现此接口的类。我的棋盘是Piece[][]数组。假设白方玩家想要移动他的Bishop。我将目标坐标和棋盘的引用传递给Bishop。Bishop会检查目标是否在同一条对角线上,然后询问棋盘是否在他的位置和目标方格之间没有任何棋子。从面向对象编程的角度来看这样做是否正确?谢谢


1
从面向对象的角度来看,这是可以接受的,但可能会让一些各种MVC方案的倡导者感到不安。 - Hot Licks
好的,谢谢。我有一个名为GameController的类,它寻找检查、将军和保存历史记录。如果主教只在同一对角线上检查,而GameController检查之间的方块,这样会更好吗? - user1872329
通常最好将类似的逻辑集中在一个地方。例如,主教会“询问”棋盘对象是否有任何它需要了解的棋子,提供描述“有趣”的参数,其他棋子也会这样做。 - Hot Licks
3个回答

3

这有点微妙。

OOP的观点

从面向对象编程的角度来看,人们可以质疑棋盘是否应该首先是一个Piece[][]数组。在这里,一个好的、干净的、面向对象的设计可能涉及到类似于一个

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

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

然后,一个关键的问题是:主教是否会将“自己”放在给定棋盘的目标位置上?或者,从面向对象编程的角度来看:给定给棋子的棋盘是可变的还是“只读”的?一个人可以想象一个恶意主教类,当它被赋予Board时,通过将自己放在国王的位置上暗杀对手的国王。

从非常高层次的抽象面向对象的角度来看,一个Piece是否应该具有任何智能。一个Piece是一块愚蠢的塑料。它不知道任何关于国际象棋规则的事情。您(玩家)可以将该棋子放置在任何地方,无论是遵守还是忽略国际象棋规则。因此,棋子绝对不应该遵守或甚至检查任何规则。可以说,遵守规则是从玩家那里期望的,而强制执行规则的遵守是一个高级智能的工作(可能是一些“ChessGameManager”类)。

一个简单的方法,似乎适合于面向对象的国际象棋实现,是拥有像下面这样的类:

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

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

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

// + other classes extending "Piece"

但需要注意的是,这可能并不总是最好的方法,也可能不足够。特别是,您应该非常清楚地了解您的EngineBoardPiecePlayer类如何相互作用以及它们的责任。 (经过一段时间的思考,您可能会得出结论,您还需要一个Move类...)。总的来说,检查移动是否有效比起初看起来要复杂得多。您提到,对于主教的移动,您会检查目标位置是否有效以及是否有其他棋子在中间。 但是,如果移动导致自己的国王处于被将军的状态下,则移动仍然无效。 这只能由“引擎”检查,而几乎无法由棋子本身检查。人们往往会忘记的其他事情(涉及整个游戏状态的信息,因此几乎无法由单个棋子处理)是CastlingEn passant移动。

从棋类引擎的角度看

对于一个棋类引擎来说,有几个要求使得采用良好的面向对象方法特别困难。如果你的目的是编写一个高效的棋类引擎,那么你的棋盘很可能会是一个由位运算操作的long值数组....

(顺便提一句:如果您将您的Board类设计为一个接口,如上所建议,那么您仍然可以在引擎本身使用高级的、面向对象的视图来保持优化表示。我的经验法则是:一开始将所有东西都建模为一个接口。后来再将其具体化是很容易的)


所以,取决于您是想编写一个适用于两个人的象棋游戏(具有规则检查),还是一个象棋引擎,您可能需要以不同的方式解决游戏设计中的某些部分。

  • 一个适用于两个人的象棋游戏需要面向对象编程,并进行规则检查。
  • 一个象棋引擎需要实现搜索算法和评估函数。

编辑:当你在寻找有关国际象棋(引擎)编程的信息时,你一定会遇到这个问题,但我想指出https://www.chessprogramming.org/Main_Page提供了很多背景信息。再次强调:这不是真正关于OO设计,而更多地涉及国际象棋引擎的细节。


谢谢您提供如此详细的答案。我没有提到,我有一个GameController类,它检查隐藏的检查、将军等等。现在我想重新设计并使所有棋子变得愚笨,这样它们只会控制它们能否移动到那里,而不检查棋盘(象的对角线)。然后GameController将负责“自由通道”和国王安全。 - user1872329
@user1872329 等等: 你说过“现在我想重新设计并使所有棋子变得愚笨”。不要仅仅因为一个答案就轻率地改变你的设计。考虑一下你想实现什么(一个双人游戏还是一个引擎?),你想关注什么(性能、面向对象设计?),以及对于你的目标来说最好的解决方案是什么。其他答案(包括建议使用...extends Piece方法的答案)都提出了有效的观点,你应该仔细考虑这些选项。让棋子“愚笨”也可以,但要考虑到我提到的情况(如易位等)。 - Marco13
我现在正在苦恼如何处理“吃过路兵”规则,所以把游戏状态控制传递给另一个类似乎是更好的选择。再次感谢您。 - user1872329

1

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

  1. Board is a kind of rules manager and it should know how pieces can act - there is a limitation - Board has to know every actor, since Chess has limited number of types this is not a problem.
  2. Board is only a place holder / coordinate system for pieces. With this approach you can save a lot of code by having abstract class (or interface, like you wrote, but there will be many common attributes between pieces, so abstract class looks better for me) for Piece, and every type of piece will extend/implement it. Example:

    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 */
    }
    

    And later

    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;
          }
     }
    
我会选择第二个选项。它更符合面向对象编程的思想,同时在存储棋子方面也更加灵活。在游戏类中,你可以编写接受 Piece 作为参数的方法,并传递任何继承自 Piece 类的东西,如 Bishops、Knight 等等,多态会帮你完成这项工作。如果使用第一个选项,你可能需要使用一些 switch/case。
当然,你还需要其他类来处理玩家、游戏状态等等。
回答你的问题——将棋盘传递给棋子是可以的。在我的建议中,棋子知道自己所处的位置,因此它只需要知道新提出的位置是否超出了棋盘大小并且是否合法就可以了。由于游戏控制器检查碰撞,实际上你并不需要棋盘?

谢谢。我会坚持使用接口,因为我已经有了它。但看起来抽象类可能是更好的选择。 - user1872329
对我来说,Piece将拥有Board字段有点奇怪。因为Board包含所有的棋子,每个棋子都会“知道”整个棋盘。 - user1872329
我认为需要知道的是,我不是国际象棋大师,但就我所记得的,你不能将自己的棋子移到你自己的另一个棋子上,因此在 canMove() 中,你还需要检查是否有相同玩家的其他棋子,因此你需要访问其他棋子。 - Kuba
我在想,每个棋子只会控制它是否可以合法地移动到那里,例如车的同一行等。然后GameController将“询问”棋盘是否有任何棋子位于从方格到目标方格之间,如果此移动不会打开某些隐藏的检查等。 - user1872329
那也可以,甚至更好,这样你在棋子和棋盘之间就没有双向连接。现在我看到你在问题中写道棋盘持有棋子数组,因此我会从我的答案中删除这部分内容。 - Kuba

1
从面向对象的角度来看,主教(车等...)应该能够说出对他来说什么是合法的回合——也就是,如果给定的场是在同一条对角线上。此外,它可以告诉棋盘它不能“跳过”其他棋子(如果我没记错,只有马可以这样做,所以马可以覆盖这个规则)。
然而,任何棋子都不能移动到另一个相同颜色的棋子所在的场上,也没有任何移动应该危及(将军)国王。这些限制应该由您的GameController类(或封装该逻辑的某个底层类)检查,因为它们适用于所有棋子。
如果GameController检查目标场是否为空,然后询问棋子是否可以在那里移动,那么棋子本身就不必知道您的棋盘数组,通用逻辑将集中在控制器中。
对于我的糟糕的棋盘词汇表示抱歉 :)

谢谢。我会修改我的设计。 - user1872329
主教(车等)应该能够确定对他来说什么是合法的回合” - 如我所述:这是纯面向对象编程观点的一个版本。但是为了检查移动是否有效,主教需要知道其他事情,例如移动是否使自己的国王处于被将军状态,或者移动是否是王车易位(因此,自己的国王是否已经移动)。棋盘的“良好面向对象设计”比一开始看起来要困难得多。 - Marco13
是的,我想说的是车/马或其他棋子不必担心自己离开后是否将国王置于将军之中等问题。相反,他们只需要知道适用于自己的规则 - 其他规则可以更容易地由控制器检查。不过,你的回答更加详细 :) - Teyras

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接