亚马逊棋java程序设计及其智能AI实现

游戏规则介绍 :

亚马逊棋是一种棋盘游戏,使用一个10x10的方格棋盘。游戏中有两名玩家,每个玩家控制4个棋子。每个棋子可以在水平、垂直或对角线方向上移动。

游戏开始时,每个玩家的4个棋子放置在棋盘上的任意位置。玩家轮流行动,每次行动可以选择一个棋子进行移动或进行射箭。移动规则如下:

1. 每个棋子可以在水平、垂直或对角线方向上移动,但不能跳过其他棋子。
2. 移动时,棋子可以沿着一条直线移动,直到遇到其他棋子或棋盘的边界。
3. 移动过程中,不能穿越或跳过其他棋子。

射箭规则如下:

1. 玩家可以选择一个棋子进行射箭。
2. 射箭时,棋子沿着水平、垂直或对角线方向发射箭矢,直到遇到其他棋子、棋盘的边界或无法继续前进。
3. 箭矢停在最后一个遇到的棋子上,将该位置标记为射箭的位置。

游戏终止条件:

1. 当一名玩家的所有棋子无法移动或射箭时,游戏结束。
2. 游戏结束后,根据剩余棋子的数量和位置来判断胜负。

亚马逊棋的目标是通过合理的移动和射箭策略,阻止对手的棋子移动,并占据有利的位置,最终获得胜利。

 

JAVA程序设计思路: 

亚马逊棋系统图示:d7a2d4f0bd25443c99956ec2c8f7e63a.png

 

本文将对于引擎模块展开介绍与分析

首先简单介绍下亚马逊棋设计思路:

基本前提假设:

1.创建一个10x10的二维数组表示棋盘状态

2.白方的四个棋子分别用1,2,3,4来表示

3.黑方的四个棋子分别用5,6,7,8来表示

4.射箭区域用数字9来表示

下棋行走思路及流程:

7be790ede5664cd0a08131a52b97b4eb.png 

人行走方法:

1.行走时由鼠标点击或者手动传入自己选定的棋子坐标x1,y1,和想要行走到的目标地点坐标x2,y2和此时的状态数组board。由点x1,y1分别向八个方向搜索通路,如果有任何一条通路可到达则执行行走步骤,否则要求重新选择。

2.执行行走步骤,将初始坐标x1,y1在状态数组上修改为0,目标坐标在二维数组上的值修改为棋子所代表的数值。例如选中2号棋子,假设棋初始位置在点(2,3)处,即x1,y1=2,3.

想要行走至(4,5)处,即x2,y2=4,5.则执行完步骤后x1,y1处坐标在状态数组上数值修改为0,x2,y2在状态数组上的数值自改为2。

3.射箭步骤,传入需要射箭的位置x3,y3,进行判断后可射箭则将该点在状态数组上的值改为9。

 AI随机行走方法:

1.AI需要随机出需要行动的棋子,才能执行下一步的步骤,这也是我们为什么给每个棋子用不同数字编号的原因,便是易于选择棋子。

2.随机出棋子后,获得该棋子在棋盘上的坐标,此坐标即为x1y1。过后再随机出可执行的x2,y2与x3,y3进行行走与射箭步骤。

悔棋功能实现 :

悔棋功能是一种在游戏或者路径搜索等场景中常见的功能,它允许用户回退到上一个状态或者上一步操作,以便重新选择或者重新执行。

悔棋功能的核心原理是保存操作历史或者状态的记录,并在需要时回退到之前的状态。通常情况下,我们可以使用(stack)数据结构来实现悔棋功能。栈是一种先进后出的数据结构,允许在栈顶进行插入删除操作。

在悔棋功能中,我们可以使用一个栈来存储每一步的状态或者操作。每当用户进行一步操作时,将该操作记录并压入栈顶。当用户触发悔棋操作时,从栈顶弹出最近的一步操作,回退到上一个状态。

具体实现悔棋功能的步骤如下:

1. 创建一个栈用于存储操作历史或者状态。可以使用语言中提供的栈数据结构或者手动实现一个栈。

2. 每当用户进行一步操作时,将该操作记录并压入栈顶。操作可以是用户的移动、选择、操作等。

3. 当用户触发悔棋操作时,从栈顶弹出最近的一步操作,回退到上一个状态。可以使用栈的弹出操作来实现。

通过使用栈数据结构,我们可以简单高效地实现悔棋功能。栈记录了操作历史或者状态的顺序,允许我们在需要时回退到之前的状态,实现悔棋的效果。

 

 

思路完毕,棋子部分代码如下 :

import java.util.Stack;

public class Quenn {
    private String colour;
    private Stack<int[]> moveHistory; // 保存移动历史的栈

    /**
     * 构造函数,初始化棋子颜色和移动历史栈
     *
     * @param colour 棋子颜色
     */
    public Quenn(String colour) {
        this.colour = colour;
        this.moveHistory = new Stack<>();
    }

    /**
     * 判断皇后是否可以从一个位置移动到另一个位置
     *
     * @param matrix 表示棋盘的二维数组
     * @param x1     起始位置的横坐标
     * @param y1     起始位置的纵坐标
     * @param x2     目标位置的横坐标
     * @param y2     目标位置的纵坐标
     * @return 是否可行的布尔值
     */
    public boolean whetherWalk(int[][] matrix, int x1, int y1, int x2, int y2) {
        if (x1 == x2 && y1 == y2) {
            return false;
        }

        if (searchDirection(matrix, x1, y1, x2, y2, 1, 0)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, -1, 0)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, 0, 1)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, 0, -1)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, 1, 1)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, -1, -1)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, 1, -1)) return true;
        if (searchDirection(matrix, x1, y1, x2, y2, -1, 1)) return true;

        return false;
    }

    /**
     * 在指定的方向上搜索路径,判断是否可达目标位置
     *
     * @param matrix 表示棋盘的二维数组
     * @param x1     起始位置的横坐标
     * @param y1     起始位置的纵坐标
     * @param x2     目标位置的横坐标
     * @param y2     目标位置的纵坐标
     * @param dx     横向搜索的步长
     * @param dy     纵向搜索的步长
     * @return 是否可达目标位置的布尔值
     */
    private boolean searchDirection(int[][] matrix, int x1, int y1, int x2, int y2, int dx, int dy) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        int x = x1;
        int y = y1;

        while (x >= 0 && x < rows && y >= 0 && y < cols) {
            if (x == x2 && y == y2) {
                return true;
            }

            if (matrix[x][y] >= 1 && matrix[x][y] <= 9) {
                break;
            }

            x += dx;
            y += dy;
        }

        return false;
    }

    /**
     * 执行亚马逊棋中的射箭动作
     *
     * @param x1 射箭起始位置的横坐标
     * @param y1 射箭起始位置的纵坐标
     * @param x2 射箭目标位置的横坐标
     * @param y2 射箭目标位置的纵坐标
     * @param arr 表示棋盘的二维数组
     * @return 是否射箭成功的布尔值
     */
    public boolean archery(int x1, int y1, int x2, int y2, int[][] arr) {
        if (whetherWalk(arr, x1, y1, x2, y2)) {
            moveHistory.push(new int[]{x2, y2}); // 将移动记录压入栈中
            arr[y2][x2] = 9; // 更新目标位置为射箭结果
            return true;
        }

        return false;
    }

    /**
     * 悔棋操作,将棋子移回上一步的位置
     *
     * @param arr 表示棋盘的二维数组
     */
    public void undoMove(int[][] arr) {
        if (!moveHistory.isEmpty()) {
            int[] lastMove = moveHistory.pop();
            int x = lastMove[0];
            int y = lastMove[1];
            arr[y][x] = 0; // 将目标位置恢复为空
        }
    }

    /**
     * 判断一个点周围是否满的
     *
     * @param x     指定点的横坐标
     * @param y     指定点的纵坐标
     * @param array 二维数组
     * @return 周围是否满的布尔值
     */
    public boolean judgeFull(int x, int y, int[][] array) {
        int[] dx = {-1, -1, -1, 0, 0, 1, 1, 1};
        int[] dy = {-1, 0, 1, -1, 1, -1, 0, 1};

        for (int i = 0; i < dx.length; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];

            if (nx < 0 || nx >= array.length || ny < 0 || ny >= array[0].length) {
                continue;
            }

            int value = array[nx][ny];
            if (value < 1 || value > 9) {
                return false;
            }
        }

        return true;
    }

    /**
     * 返回棋子在棋盘上的位置
     *
     * @param chessNumbering 棋子编号
     * @param checkBoard    表示棋盘的二维数组
     * @return 棋子在棋盘上的坐标数组
     */
    public int[] chessLocation(int chessNumbering, int[][] checkBoard) {
        int[] coordinate = new int[2];

        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                if (checkBoard[i][j] == chessNumbering) {
                    coordinate[0] = j;
                    coordinate[1] = i;
                    return coordinate;
                }
            }
        }

        return coordinate;
    }
}

 

这段代码是针对亚马逊棋游戏的程序设计。亚马逊棋是一种棋盘游戏,玩家需要在棋盘上移动棋子并射箭来攻击对手的棋子。以下是这段代码的主要作用和功能:

1. `whetherWalk` 方法用于判断一个棋子是否可以从起始位置移动到目标位置。它通过搜索八个方向来判断是否存在可行的路径。如果路径中的元素是1-9之间的任意数字,则表示该路径不可行。

2. `searchDirection` 方法在指定的方向上搜索路径,判断是否可以到达目标位置。它根据起始位置、目标位置和步长(dx和dy)进行循环搜索,判断是否可以到达目标位置。如果路径中的元素是1-9之间的任意数字,则表示该路径不可行。

3. `archery` 方法执行亚马逊棋中的射箭动作。它调用 `whetherWalk` 方法来检查是否可以进行射箭,如果可以,则将目标位置设置为射箭结果,并返回射箭成功的布尔值。

4. `undoMove` 方法用于悔棋操作。它将棋子移回上一步的位置,通过弹出存储移动历史的栈中的最后一次移动来实现。

5. `judgeFull` 方法用于判断指定点周围是否被占满。它检查指定点周围八个方向的相邻位置是否都被1-9之间的数字占据,如果是,则返回 true,表示周围被占满;否则返回 false。

6. `chessLocation` 方法用于返回棋子在棋盘上的位置。它遍历棋盘二维数组,找到指定棋子编号对应的坐标,并返回该坐标。

这些方法在亚马逊棋游戏中起到关键作用,提供了移动棋子、射箭、悔棋和判断棋盘状态等功能。通过这些方法,玩家可以在程序中进行棋盘操作,实现游戏的进行和规则的判断。

Gamer类和AI类分别为这个核心类的子类,通过继承获取棋子移动的规则方法,两个类代码如下:

Gamer类,此类实现了玩家行走逻辑:

public class Gamer extends Quenn {
    private int[] chessmanGamer = {5, 6, 7, 8};

    /**
     * 构造函数,初始化棋子颜色
     *
     * @param colour 棋子颜色
     */
    public Gamer(String colour) {
        super(colour);
    }

    /**
     * 初始化棋盘
     *
     * @param board 表示棋盘的二维数组
     */
    public void initializeGamer(int[][] board) {
        // 设置棋子初始位置
        board[6][0] = 5;
        board[9][3] = 6;
        board[9][6] = 7;
        board[6][9] = 8;
    }

    /**
     * 玩家移动棋子并射箭
     *
     * @param x1    起始位置的横坐标
     * @param y1    起始位置的纵坐标
     * @param x2    目标位置的横坐标
     * @param y2    目标位置的纵坐标
     * @param x3    射箭目标位置的横坐标
     * @param y3    射箭目标位置的纵坐标
     * @param board 表示棋盘的二维数组
     * @return 是否成功移动并射箭的布尔值
     */
    public boolean gamerWalk(int x1, int y1, int x2, int y2, int x3, int y3, int[][] board) {
        // 判断是否可以移动到目标位置
        if (whetherWalk(board, x1, y1, x2, y2)) {
            int chessNum = board[y1][x1]; // 获取棋子编号
            board[y2][x2] = chessNum; // 将棋子移动到目标位置
            board[y1][x1] = 0; // 将起始位置设为0,表示棋子离开该位置

            // 射箭
            if (archery(x2, y2, x3, y3, board)) {
                return true; // 射箭成功
            }
        }
        return false; // 移动或射箭失败
    }
}

AI类 ,此类实现了AI随机算法的实现:

import java.util.Random;

public class Ai extends Quenn {
    private int[] chessmanAi = {1, 2, 3, 4};

    /**
     * 构造函数,初始化棋子颜色和移动历史栈
     *
     * @param colour 棋子颜色
     */
    public Ai(String colour) {
        super(colour);
    }

    /**
     * 初始化棋盘
     *
     * @param board 表示棋盘的二维数组
     */
    public void initializeChess(int[][] board) {
        // 设置棋子初始位置
        board[3][0] = 1;
        board[0][3] = 2;
        board[0][6] = 3;
        board[3][9] = 4;
    }

    /**
     * AI随机行走并射箭
     *
     * @param chessboard 表示棋盘的二维数组
     * @return 更新后的棋盘二维数组
     */
    public int[][] RanAiGo(int[][] chessboard) {
        int x1 = -1; // 初始棋子坐标
        int y1 = -1;
        Random random = new Random(2023);
        int chessNow = chessmanAi[random.nextInt(4)]; // 随机选择一个棋子
        int[] location = chessLocation(chessNow, chessboard); // 获取初始棋子的坐标
        x1 = location[0];
        y1 = location[1];

        int x2;
        int y2;

        while (true) {
            x2 = random.nextInt(10);
            y2 = random.nextInt(10);
            boolean listener = whetherWalk(chessboard, x1, y1, x2, y2); // 判断是否可走

            // x2和y2的值不能等于任何棋子或障碍
            if (chessboard[y2][x2] == 0) {
                listener = false;
            }

            if (listener) {
                chessboard[y1][x1] = 0; // 将初始位置设为0,表示棋子离开该位置
                chessboard[y2][x2] = chessNow; // 将棋子移动到目标位置

                int x3;
                int y3;
                while (true) {
                    x3 = random.nextInt(10);
                    y3 = random.nextInt(10);
                    boolean listener2 = archery(x2, y2, x3, y3, chessboard); // 判断是否可射箭

                    if (listener2) {
                        break;
                    }
                }
                return chessboard;
            }
        }
    }
}

AI算法缺陷:

由于是随机算法,所以有可能一直随机出不可执行步骤,从而拖慢执行速率,最坏情况下随机出可执行步骤的概率为2/10000。 

 

 

 

AI算法优化分析:

想要时ai执行效率更高,我们需要展开博弈树,来分析AI执行步骤。

1.博弈树:

博弈树是一种用于描述游戏决策过程的树状结构。在亚马逊棋博弈树中,树的根节点代表当前的游戏状态,每个节点表示一种可能的游戏状态,树的边表示玩家的行动。在亚马逊棋博弈树中,从根节点开始,通过递归地生成树的所有可能状态,直到达到游戏结束的状态。在每个节点上,轮流的玩家会选择一种行动,例如移动自己的棋子或射击对手的棋子。这样,博弈树会展示出所有可能的行动序列。通过搜索整个博弈树,可以找到最佳的决策路径,即在每个决策点上选择最优的行动,以达到最有利的游戏结果。博弈树搜索算法通常会应用剪枝技术,以减少搜索空间并提高搜索效率。

 a57f25e434484385aedb970333c2a8c5.png

 2.评估函数:

评估函数是在博弈树搜索算法中使用的一种函数,用于对棋局的优劣进行评估。它对当前局面进行打分,以指导算法选择最优的行动。在亚马逊棋中,评估函数的设计对博弈算法的性能和决策质量起着关键作用。评估函数的目标是尽可能准确地估计当前局面的优劣,并为算法提供一个量化的指标。

 3.传统极大极小值算法:

 8e8eceab397f4b028ded785d213100ce.png

 

在极大极小搜索算法中,偶数层节点表示轮到最大化评估值的一方走棋,而奇数层节点表示轮到最小化评估值的一方走棋。在偶数层,选择子节点中评估值最大的节点作为当前节点的最佳走法;在奇数层,选择子节点中评估值最小的节点作为当前节点的最佳走法。这样,逐层进行搜索和评估,直到达到根节点,从而得到最佳的决策路径。

在搜索过程中,根据评估函数对叶子节点进行评估,并通过回溯的方式从叶子节点向上计算,一直到根节点。通过记录每个节点的子节点是哪一个,可以得到一条从根节点到叶子节点的最佳路径,即最佳的决策序列。

4. 剪枝策略:

剪枝是在搜索算法中应用的一种策略,旨在减少搜索空间,提高搜索效率。剪枝的目标是通过提前终止对某些节点的搜索,以避免无效的计算。在极大极小搜索算法中,剪枝策略常用的有α-β剪枝。α-β剪枝利用了极大极小搜索算法中的特性,通过设置上界(α值)和下界(β值)来剪掉某些子树的搜索。具体过程如下:

1. 在极大节点(偶数层)中,维护一个α值,表示最大化评估值的上界。
2. 在极小节点(奇数层)中,维护一个β值,表示最小化评估值的下界。
3. 在搜索过程中,当发现某个节点的评估值超过了α值(对于极大节点)或者低于β值(对于极小节点),就可以提前终止对该节点以及其子树的搜索,因为当前节点不会影响最终的决策结果。
4. 在搜索过程中,更新α值和β值,以便更精确地剪枝。对于极大节点,如果找到比当前α值更大的评估值,就更新α值;对于极小节点,如果找到比当前β值更小的评估值,就更新β值。

通过α-β剪枝,可以剪掉许多无需继续搜索的节点,从而大幅减少搜索空间,提高搜索效率。剪枝的效果取决于搜索树的结构和节点的顺序,对于某些情况下,剪枝策略可以大幅减少搜索时间,而在某些情况下,剪枝的效果可能较小。 

总之,剪枝策略是一种优化搜索算法的技巧,可以帮助提高搜索效率,减少不必要的计算

 

 

算法优化——基于贪心算法的极大极小值搜索和非传统剪枝:

假设AI的奇数层表示行走路径节点,偶数层表示射箭节点。每次列举并搜索此时当前所有的可执行节点,每个节点都通过评估函数进行评估,评估函数是根据目标点周围两层领域内空格子的数量来确定的,空格子数量越多,评估值越高。在AI执行时,采用广度优先搜索(BFS)来遍历当前所有可执行的步骤。在奇数层中,AI选择最大的评估值作为最优解(此时代表行到该位置时棋子被围堵的概率最小);在偶数层中,AI选择最小的评估值作为最优解(此时代表射箭到此位置,对自己的棋子影响的概率最小)。每次搜索完一层后,除了目标节点的孩子节点外,其他支路都会被剪除,以减少搜索空间。(只考虑每步的最优解)

这种算法的目的是通过搜索和评估来找到一个最优的行走路径和射箭策略,以最大化目标点周围空格子的数量。该算法结合了广度优先搜索、极大极小值算法和剪枝策略,通过逐层搜索并根据评估函数进行节点选择,以找到最优解。通过剪枝策略可以减少无需继续搜索的节点,提高搜索效率。

如果不进行剪枝算法,博弈树十几层之后节点便会达到恐怖的几百万个,此时搜索效率极低。每次搜索只考虑当前最优解,列举当前的可执行节点,搜索完后剪除所有的非最优解节点及其孩子,可以保证每次搜索节点在100-200以内。

AI部分代码如下:

public class GameAI {
    public int evaluate(Node node) {
        // 评估函数:计算目标点的两层领域内空格子的数量
        int count = countEmptyCellsAroundTarget(node);
        return count;
    }

    public int maxMinSearch(Node node, int depth, boolean maximizingPlayer) {
        if (depth == 0 || node.isTerminalNode()) {
            return evaluate(node);
        }

        if (maximizingPlayer) {
            int bestValue = Integer.MIN_VALUE;
            for (Node child : node.getChildren()) {
                int value = maxMinSearch(child, depth - 1, false);
                bestValue = Math.max(bestValue, value);
            }
            return bestValue;
        } else {
            int bestValue = Integer.MAX_VALUE;
            for (Node child : node.getChildren()) {
                int value = maxMinSearch(child, depth - 1, true);
                bestValue = Math.min(bestValue, value);
            }
            return bestValue;
        }
    }

    public void search() {
        int currentDepth = 0;
        Node bestMove = null;

        // 执行广度优先搜索
        while (currentDepth < maxDepth) {
            // 获取当前层的所有节点
            List<Node> currentLevelNodes = getAllNodesAtCurrentLevel(currentDepth);

            if (currentDepth % 2 == 0) {
                // 偶数层,最大化玩家
                int bestValue = Integer.MIN_VALUE;
                for (Node node : currentLevelNodes) {
                    int value = maxMinSearch(node, currentDepth, true);
                    if (value > bestValue) {
                        // 更新最好的价值和移动
                        bestValue = value;
                        bestMove = node;
                    }
                }
            } else {
                // 奇数层,最小化玩家
                int bestValue = Integer.MAX_VALUE;
                for (Node node : currentLevelNodes) {
                    int value = maxMinSearch(node, currentDepth, false);
                    if (value < bestValue) {
                        // 更新最好的价值和移动
                        bestValue = value;
                        bestMove = node;
                    }
                }
            }

            currentDepth++;
        }

        // 在搜索结束后,执行最佳移动
        makeMove(bestMove);
    }
}

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值