Java编程 - 国际象棋移动(基本,无人工智能)

10
我需要在设计国际象棋游戏方面寻求帮助。我已经开始着手编写,但由于我对Java和编程都很陌生,所以进展并不顺利。
无论如何,我有一个抽象类Piece和各种棋子作为其子类。我有一个movePiece方法在我的抽象类中,我想为所有子类定义这个方法。
目前它只是将棋子从一个方格移动到另一个方格。我有一个Square类可以容纳Piece对象,棋盘由一个64x1的Square数组组成。
我知道棋子如何移动,但我该如何进行编程呢? 我想尝试应用MVC模式,但这真的是我第一次使用模式。
基本上,我打算使用Graphics2D为每个Square创建一个框。然后当玩家点击一个棋子时,移动后可用的方块将用某种颜色进行轮廓描绘。 玩家点击其中一个方块后,将运行我已经在movePiece方法中编写的代码。
我想要做的是在每个Piece子类中覆盖我的movePiece方法。问题是,其中一个这些方法的代码可能会是什么样子呢? 以Pawn子类为例。
我不是要求复制/粘贴的代码,只是要一些关于如何做到这一点的指针,最好是一些样本代码。
谢谢!
public class Game {


@SuppressWarnings("unused")
public static void main(String[] args){
    Board board = new Board();
} }

public class Board {

Square[] grid;

public Board(){
    grid = new Square[64];
}   
public Square getSquare(int i){
    return grid[i];
}   
public void setDefault(){

}   
public Boolean isMoveValid(){
    return null;    
} }

public class Square {

private Piece piece;

public void addPiece(Piece pieceType, String pieceColour, String pieceOwner) 
        throws ClassNotFoundException, InstantiationException, IllegalAccessException{

    PieceFactory factory = new PieceFactory();
    Piece piece = factory.createPiece(pieceType);

    piece.setColour(pieceColour);
    piece.setOwner(pieceOwner);

    this.piece = piece; 
}
public void addPiece(Piece pieceType){ 
    this.piece = pieceType; 
}
public void removePiece(){  
    piece = null;
}
public Piece getPiece(){
    return piece;       
}

class PieceFactory {     
     @SuppressWarnings("rawtypes")
     public Piece createPiece(Piece pieceType) 
            throws ClassNotFoundException, InstantiationException, IllegalAccessException{
         Class pieceClass = Class.forName(pieceType.toString());
         Piece piece = (Piece) pieceClass.newInstance();

         return piece;       
     } }

public void setColour(String colour){

} }

public abstract class Piece {

Board board;

public void setColour(String pieceColour) {
}

public void setOwner(String pieceOwner) {
}

public String getColour() {
    return "";
}

public String getOwner() {
    return "";      
}
public void movePiece(int oldIndex, int newIndex){
    board.getSquare(oldIndex).removePiece();
    board.getSquare(newIndex).addPiece(this);
}
public String toString(){
    return this.getClass().getSimpleName();
} }

你想看代码,我知道它非常基础。我将把[64]更改为[8][8]。

我试图让它不那么难。我可能可以将颜色和所有者组合为一个属性,并使其成为枚举(BLACK或WHITE)。

如果格式不好,很抱歉。


棋盘由一个64x1的方形数组组成。为什么?国际象棋棋盘是8x8,而不是64x1。基本上我想使用Graphics2D为每个方格创建一个框。GUI其次,不用担心。在编写GUI之前先让您的代码正常工作。您应该展示您已经编写的内容,而不是询问代码会如何。看起来您已经有了设计的基础(实现抽象类方法)。所以去做吧。 - Falmarri
为什么要区分pieceColour和pieceOwner?它们是一样的。 - Carles Barrobés
此外,stackoverflow不是一个适合问棋类问题的好地方。这里的人对如何编写棋类游戏的知识很少或者根本没有。你可以试试Talkchess.com,那是一个专业的棋类网站。 - ABCD
不知道,只是接受了我喜欢的答案之一!此外,这里的答案对我帮助很大。 - Smooth Operator
@Falmarri 一个物理棋盘是。代表棋盘的数据结构不必如此,而使用单个数组而不是矩阵意味着有很多不需要进行的数组索引。 - Thorbjørn Ravn Andersen
6个回答

18
在设计软件时,我发现思考如何使用一个方法,然后写下方法签名(如果你在进行测试驱动开发,则是单元测试),最后再思考如何实现它是有帮助的。
在这里这样做,我发现要满足像这样的要求:
“当玩家点击一个棋子时,移动后可用作目的地的方块将以某种颜色被勾画出来。”
是不可能通过类似这样的方法来实现的。
void move(Square destination);

因为没有办法在实际下棋之前找出可能的走法。要突出显示棋盘,最好使用以下方法:

Collection<Square> getPossibleMoves();

然后我们可以实现

void move(Square destination) {
    if (!getPossibleMoves().contains(destination) {
        throw new IllegalMoveException();
    }

    this.location.occupyingPiece = null;
    this.location = destination;
    this.location.occupyingPiece = this;
}

对于实现getPossibleMoves:

class Pawn extends Piece {
    @Override Collection<Square> getPossibleMoves() {
        List<Square> possibleMoves = new ArrayList<Square>();
        int dy = color == Color.white ? 1 : -1;
        Square ahead = location.neighbour(0, dy);
        if (ahead.occupyingPiece == null) {
            possibleMoves.add(ahead);
        }
        Square aheadLeft = location.neighbour(-1, dy);
        if (aheadLeft != null && aheadLeft.occupyingPiece != null && aheadLeft.occupyingPiece.color != color) {
            possibleMoves.add(aheadLeft);
        }
        Square aheadRight = location.neighbour(1, dy);
        if (aheadRight != null && aheadRight.occupyingPiece != null && aheadRight.occupyingPiece.color != color) {
            possibleMoves.add(aheadRight);
        }
        return possibleMoves;
    }
}

编辑

class Knight extends Piece {
    @Override Collection<Square> getPossibleMoves() {
        List<Square> possibleMoves = new ArrayList<Square>();
        int[][] offsets = {
            {-2, 1},
            {-1, 2},
            {1, 2},
            {2, 1},
            {2, -1},
            {1, -2},
            {-1, -2},
            {-2, -1}
        };
        for (int[] o : offsets) {
            Square candidate = location.neighbour(o[0], o[1]);
            if (candidate != null && (candidate.occupyingPiece == null || candidate.occupyingPiece.color != color)) {
                possibleMoves.add(candidate);
            }
        }
        return possibleMoves;
    }
}

我想我明白了,只是我以前没有真正使用过集合,但我猜用数组应该不是问题。谢谢! - Smooth Operator
我可能会尝试使用ArrayList,似乎很实用,不需要指定元素数量等。 只是想知道,为什么你可以写List<Square> possibleMoves = new ArrayList<Square>()? 我的意思是,List既是一种类型又是一个接口吗? - Smooth Operator
是的,接口是类型。我使用列表的原因是我们的调用方不关心列表的表示方式,“List”比“Collection”或“ArrayList”更短。 - meriton
非常感谢!终于我也明白邻居方法是如何工作的了。你真的帮了我很多! - Smooth Operator
感觉就像我在阅读我自己的代码实现 :D +1 - sybear
显示剩余3条评论

2
你所描述的不仅仅是一个移动棋子的方法,你需要一个getPossibleMoves方法来获取所有可以移动到的位置。或者还可以添加一个moveAllowed方法,用于判断是否允许棋子移动到给定位置。
为了避免使用原始的(x,y)坐标,你可以创建一个类来定义棋盘上的Location。这个类可以提供将位置转换成象棋文献中使用的“象棋坐标”的方法。一个Location可以通过new Location(x, y)来构造。
此外,我建议使用一个Board类来表示8x8格子棋盘,并作为棋子的容器。它可以拥有比数组更丰富的逻辑(例如可以提供一个pathClear(from, to)方法,告诉你是否有任何棋子阻挡了你从一个位置到另一个位置的通行)等。

不错的想法,关于位置,是否可以将数组[x][y]转换成类似[d4]这样的形式呢? 这样,我就不用写两个坐标来访问元素,而是直接写“棋盘坐标”。我需要创建一个表示线路的数组,然后再创建一个该数组的数组吗?或者有更简单的方法吗? - Smooth Operator
不,只需编写坐标系之间的转换函数。在内部,使用更简单/更有效的存储和检索方式。例如,您最终可以拥有不同的构造函数:Location(int x,int y)或Location(String chessCoords),以及getX()getY()和toString()等方法,以便将坐标作为“D4”等形式呈现。您可以通过Location访问board:Board.getPieceAt(Location l)...等等。您可以将游戏存储为移动列表,其中每个Move表示将Piece移动到新位置(或两个列表,一个用于白色,一个用于黑色)。 - Carles Barrobés

1
我会写一个isValidMove(Board boardState, int square)的抽象方法,每个棋子都会重写它,并在每次绘图迭代中调用它来突出显示有效的移动。相同的方法也会在movePiece(int square)中被调用以检查移动是否有效,如果有效则执行移动。实际上,movePiece方法将属于Board本身(并将调用Piece.isValidMove(...)进行检查)。
兵的isValidMove可能像这样(注意这是伪代码):
if ( boardState.getSquare(square).isFree() && square == this.square+ONE_FILE)
    return true;
else
    return false;

这种方法似乎也很有趣!我是否需要在isValidMove所在的同一类中定义boardState变量? - Smooth Operator
也许您不再需要这个答案了,但我错过了这条评论。我会在每个棋子类中实现“isValidMove”(为抽象类“Piece”实现它),并将“boardState”(属于“Board”类)作为参数传递给“isValidMove”方法。 - kaoD

1
我会创建一个名为getValidMoves()的方法,它返回给定棋子所有可能移动的列表。这样你就不必循环整个棋盘来查找要突出显示的方格。我还会将基类设为抽象类,以便像这样的方法不需要基本实现。

没错,但是基类(Piece)已经是抽象的了,所以一切都很好。 然后它会返回一个数组,我可以通过循环来遍历它? - Smooth Operator
是的,你只需要循环遍历validMoves。你甚至可以称之为possibleMoves,它不考虑碰撞,然后查看这些移动,并删除那些已经有其他棋子的位置。这样,validMoves就可以在没有关于棋盘上其他棋子的任何信息的情况下运行。 - Zeki

0

我刚刚完成了自己的国际象棋游戏。这里有一些我想分享给你的提示/要点。

记住,Java是面向对象的语言,将游戏分解为逻辑对象并与方法交互。

尽可能保持方法简单易懂(像一直以来那样)。编写易读的代码。 有时候!使用8种不同的方法来检查棋子(国王)可用的移动方式比一些神秘的for语句更易读,后者试图完成所有计算工作。

如果您想应用MVC模式,您可能需要考虑将GUI也作为控制器,它具有对实际棋盘的引用。

Piece.java protected List allowedSquares; public abstract void calculateAllowedSquares(ChessBoard chessBoard); public void move(ChessBoard chessBoard, Square fromSquare, Square toSquare)

在King类中,您可能需要重写move方法。例如,在王车易位中,您需要同时移动车。

  • 您还需要知道您从哪里移动(检查过路移动规则) 还可能需要清除您的移动“fromSquare”,因为您正在使用[][]板。
制作国际象棋游戏的两个主要问题是: 1. 如何消除暴露自己国王受到攻击的移动。有时候棋子根本无法移动。 2. 如何让一枚棋子只能在保护你的国王的情况下移动。也就是说,如果比如说象正在威胁你的国王,你可以移动某些棋子来阻止这种威胁,但不能移动到其他地方。
最后,我建议您与逻辑同时创建您的GUI。当您点击您的棋子(或棋子所在的方格)时,您可以立即在屏幕上看到可用的方格。这将为您节省数小时的调试时间。

0

你得放慢速度!

首先,你应该为每个项目创建一个类,并为每个项目添加可能的移动方式,还要实现一个检查游戏中可能移动的方法, 然后使用Netbeans GUI并添加一些JLabels并将它们的颜色更改为白色和黑色,这样你就可以很好地查看整个过程。

只要开始,不要犹豫删除旧代码并用新的更好的代码替换它们。 这就是它的工作原理,伙计。

祝你好运。


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