sponsored links

JAVA實現經典《中國象棋》遊戲

前言

中國象棋是起源於中國的一種棋,屬於二人對抗性遊戲的一種,在中國有著悠久的歷史。由於用具簡單,趣味性強,成為流行極為廣泛的棋藝活動。

中國象棋使用方形格狀棋盤,圓形棋子共有32個,紅黑二色各有16個棋子,擺放和活動在交叉點上。雙方交替行棋,先把對方的將(帥)“將死”的一方獲勝。

中國象棋是一款具有濃郁中國特色的益智遊戲,新增的聯網對戰,趣味多多,聚會可以約小朋友一起來挑戰。精彩的對弈讓你感受中國象棋的博大精深。

《中國象棋》遊戲是用java語言實現,採用了swing技術進行了介面化處理,設計思路用了面向物件思想。, 人機對弈基於極大極小值搜尋演算法。

主要需求

按照中國象棋的規則,實現紅黑棋對戰,要有AI對手,可以玩家跟AI的對弈,也可以兩個玩家自己玩。

主要設計

1、尋找棋盤介面和對應的棋子圖片,程式設計棋盤介面和功能選單

2、設計不同的棋子的移動邏輯

3、棋子移動時,要有音效

4、設計對手AI的邏輯演算法,這裡運用了極大極小值搜尋演算法,設定不同的搜尋深度AI(智慧不同)

5、對局開始前,雙方棋子在棋盤上的擺法。 6、對局時,由執紅棋的一方先走,雙方輪流走一步。 7、輪到走棋的一方,將某個棋子從一個交叉點走到另一個交叉點,或者吃掉對方的棋子而佔領其交叉點,都算走了一著。 8、雙方各走一著,稱為一個回合。 9、走一著棋時,如果己方棋子能夠走到的位置有對方棋子存在,就可以把對方棋子吃掉而佔領那個位置。 10、一方的棋子攻擊對方的帥(將),並在下一著要把它吃掉,稱為“照將”,或簡稱“將”。“照將”不必宣告。被“照將”的一方必須立即“應將”,即用自己的著法去化解被“將”的狀態。如果被“照將”而無法“應將”,就算被“將死”。

11、特別設計了人機對弈,人人對弈,還有AI對AI對弈

功能截圖

遊戲開始

JAVA實現經典《中國象棋》遊戲

遊戲選單設定

JAVA實現經典《中國象棋》遊戲

移動效果

JAVA實現經典《中國象棋》遊戲

程式碼實現

棋盤面板設計


@Slf4j
public class BoardPanel extends JPanel implements LambdaMouseListener {

    /**
     * 用於標記棋盤走棋痕跡
     */
    private final transient TraceMarker traceMarker;
    /**
     * 當前走棋開始座標位置對應棋子
     */
    private transient ChessPiece curFromPiece;
    /**
     * 場景
     */
    private transient Situation situation;

    /**
     * Create the panel.
     */
    public BoardPanel() {
        setBorder(new EmptyBorder(5, 5, 5, 5));
        setLayout(null);
        // 初始化標記符
        traceMarker = new TraceMarker(BoardPanel.this);
        // 新增滑鼠事件
        addMouseListener(this);
    }

    /**
     * 更新標記
     */
    public void updateMark(Place from, Place to) {
        // 更新標記
        curFromPiece = null;
        // 更改標記
        traceMarker.endedStep(from, to);
    }

    /**
     * 初始化所有標記
     */
    public void initMark() {
        traceMarker.initMarker();
    }

    /**
     * 新增棋子
     */
    public void init(Situation situation) {
        this.situation = situation;
        // 移除所有元件
        this.removeAll();
        // 新增棋子
        situation.getPieceList().forEach(it -> add(it.getComp()));
        situation.getSituationRecord().getEatenPieceList().forEach(it -> add(it.getComp()));
        // 初始化標記符
        traceMarker.initMarker();
        repaint();
    }

    /**
     * @param e 滑鼠按壓事件物件
     */
    @Override
    public void mouseReleased(MouseEvent e) {
        // 位置
        Place pointerPlace = ChessDefined.convertLocationToPlace(e.getPoint());
        if (pointerPlace == null) {
            return;
        }
        if (situation.winner() != null) {
            log.warn("已經存在勝利者: {}, 無法走棋", situation.winner());
            return;
        }
        // 當前走棋方
        @NonNull Part pointerPart = situation.getNextPart();
        // 當前焦點棋子
        ChessPiece pointerPiece = situation.getChessPiece(pointerPlace);
        // 通過當前方和當前位置判斷是否可以走棋
        // step: form
        if (curFromPiece == null) {
            // 當前焦點位置有棋子且是本方棋子
            if (pointerPiece != null && pointerPiece.piece.part == pointerPart) {
                // 本方棋子, 同時是from指向
                curFromPiece = pointerPiece;
                traceMarker.setMarkFromPlace(pointerPlace);
                // 獲取toList
                MyList<Place> list = curFromPiece.piece.role.find(new AnalysisBean(situation.generatePieces()), pointerPart, pointerPlace);
                traceMarker.showMarkPlace(list);
                ChessAudio.CLICK_FROM.play();
                log.info("true -> 當前焦點位置有棋子且是本方棋子");
                final ListPool listPool = ListPool.localPool();
                listPool.addListToPool(list);
                return;
            }
            log.warn("warning -> from 焦點指示錯誤");
            return;
        }
        if (pointerPlace.equals(curFromPiece.getPlace())) {
            log.warn("false -> from == to");
            return;
        }
        // 當前焦點位置有棋子且是本方棋子
        if (pointerPiece != null && pointerPiece.piece.part == pointerPart) {
            assert curFromPiece.piece.part == pointerPart : "當前焦點位置有棋子且是本方棋子 之前指向了對方棋子";
            // 更新 curFromPiece
            curFromPiece = pointerPiece;
            traceMarker.setMarkFromPlace(pointerPlace);
            MyList<Place> list = curFromPiece.piece.role.find(new AnalysisBean(situation.generatePieces()), pointerPart, pointerPlace);
            traceMarker.showMarkPlace(list);
            ChessAudio.CLICK_FROM.play();
            log.info("true -> 更新 curFromPiece");
            ListPool.localPool().addListToPool(list);
            return;
        }
        final StepBean stepBean = StepBean.of(curFromPiece.getPlace(), pointerPlace);
        // 如果不符合規則則直接返回
        final Piece[][] pieces = situation.generatePieces();
        if (!curFromPiece.piece.role.rule.check(pieces, pointerPart, stepBean.from, stepBean.to)) {
            // 如果當前指向棋子是本方棋子
            log.warn("不符合走棋規則");
            return;
        }
        // 如果達成長攔或者長捉, 則返回
        final StepBean forbidStepBean = situation.getForbidStepBean();
        if (forbidStepBean != null && forbidStepBean.from == stepBean.from && forbidStepBean.to == stepBean.to) {
            ChessAudio.MAN_MOV_ERROR.play();
            log.warn("長攔或長捉");
            return;
        }
        AnalysisBean analysisBean = new AnalysisBean(pieces);
        // 如果走棋後, 導致兩個 BOSS 對面, 則返回
        if (!analysisBean.isBossF2FAfterStep(curFromPiece.piece, stepBean.from, stepBean.to)) {
            ChessAudio.MAN_MOV_ERROR.play();
            log.warn("BOSS面對面");
            return;
        }
        /* 模擬走一步棋, 之後再計算對方再走一步是否能夠吃掉本方的 boss */
        if (analysisBean.simulateOneStep(stepBean, bean -> bean.canEatBossAfterOneAiStep(Part.getOpposite(pointerPart)))) {
            ChessAudio.MAN_MOV_ERROR.play();
            log.warn("BOSS 危險");
            if (!Application.config().isActiveWhenBeCheck()) {
                return;
            }
        }
        // 當前棋子無棋子或者為對方棋子, 且符合規則, 可以走棋
        Object[] objects = new Object[]{stepBean.from, stepBean.to, PlayerType.PEOPLE};
        final boolean sendSuccess = Application.context().getCommandExecutor().sendCommandWhenNotRun(CommandExecutor.CommandType.LocationPiece, objects);
        if (!sendSuccess) {
            log.warn("命令未傳送成功: {} ==> {}", CommandExecutor.CommandType.LocationPiece, Arrays.toString(objects));
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Image img = ChessImage.CHESS_BOARD.getImage();
        int imgWidth = img.getWidth(this);
        int imgHeight = img.getHeight(this);// 獲得圖片的寬度與高度
        int fWidth = getWidth();
        int fHeight = getHeight();// 獲得視窗的寬度與高度
        int x = (fWidth - imgWidth) / 2;
        int y = (fHeight - imgHeight) / 2;
        // 520 576 514 567
        log.debug(String.format("%s,%s,%s,%s,%s,%s", imgWidth, imgHeight, fWidth, fHeight, x, y));
        g.drawImage(img, 0, 0, null);
    }

}

複製程式碼

命令執行器, 用於處理走棋中的命令


@Slf4j
public class CommandExecutor {

    /**
     * 非同步呼叫執行緒, 來處理走棋命令
     */
    private final CtrlLoopThreadComp ctrlLoopThreadComp;
    private final BoardPanel boardPanel;
    /**
     * 是否持續執行標記
     */
    private volatile boolean sustain;

    public CommandExecutor(BoardPanel boardPanel) {
        this.boardPanel = boardPanel;
        this.ctrlLoopThreadComp = CtrlLoopThreadComp.ofRunnable(this::loop)
                .setName("CommandExecutor")
                .catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE);
    }

    /**
     * 下一步驟命令
     */
    private CommandType nextCommand;
    /**
     * 下一步驟命令的引數
     */
    private Object nextParamObj;

    private volatile boolean isRun;

    /**
     * @param commandType 命令型別
     */
    public void sendCommand(@NonNull CommandType commandType) {
        sendCommand(commandType, null);
    }

    /**
     * @param commandType 命令型別
     * @param paramObj    命令引數
     */
    public synchronized void sendCommand(@NonNull CommandType commandType, Object paramObj) {
        this.nextCommand = commandType;
        this.nextParamObj = paramObj;
        sustain = false;
        this.ctrlLoopThreadComp.startOrWake();
    }

    /**
     * 只有在 執行緒沒有執行的情況下, 才能新增成功
     *
     * @param commandType 命令型別
     * @param paramObj    命令引數
     * @return 是否新增成功
     */
    public synchronized boolean sendCommandWhenNotRun(@NonNull CommandType commandType, Object paramObj) {
        if (isRun) {
            return false;
        }
        sendCommand(commandType, paramObj);
        return true;
    }

    private void loop() {
        final CommandType command;
        final Object paramObj;
        synchronized (this) {
            command = this.nextCommand;
            paramObj = this.nextParamObj;
            this.nextCommand = null;
            this.nextParamObj = null;
        }
        if (command != null) {
            isRun = true;
            try {
                log.debug("處理事件[{}] start", command.getLabel());
                consumerCommand(command, paramObj);
                log.debug("處理事件[{}] end ", command.getLabel());
            } catch (Exception e) {
                log.error("執行命令[{}]發生異常", command.getLabel(), e);
                new Thread(() -> JOptionPane.showMessageDialog(boardPanel, e.getMessage(), e.toString(), JOptionPane.ERROR_MESSAGE)).start();
            }
        } else {
            this.ctrlLoopThreadComp.pause();
            isRun = false;
        }
    }

    /**
     * 執行
     */
    private void consumerCommand(final CommandType commandType, Object paramObj) {
        switch (commandType) {
            case SuspendCallBackOrAiRun:
                break;
            case CallBackOneTime:
                Application.context().rollbackOneStep();
                break;
            case AiRunOneTime:
                if (Application.context().aiRunOneTime() != null) {
                    log.debug("已經決出勝方!");
                }
                break;
            case SustainCallBack:
                sustain = true;
                while (sustain) {
                    if (!Application.context().rollbackOneStep()) {
                        sustain = false;
                        break;
                    }
                    Throws.con(Application.config().getComIntervalTime(), Thread::sleep).logThrowable();
                }
                break;
            case SustainAiRun:
                sustain = true;
                while (sustain) {
                    if (Application.context().aiRunOneTime() != null) {
                        log.debug("已經決出勝方, AI執行暫停!");
                        sustain = false;
                        break;
                    }
                    Throws.con(Application.config().getComIntervalTime(), Thread::sleep).logThrowable();
                }
                break;
            case SustainAiRunIfNextIsAi:
                sustain = true;
                while (sustain) {
                    // 如果下一步棋手不是 AI, 則暫停
                    if (!PlayerType.COM.equals(Application.config().getPlayerType(Application.context().getSituation().getNextPart()))) {
                        sustain = false;
                        log.debug("下一步棋手不是 AI, 暫停!");
                    } else if (Application.context().aiRunOneTime() != null) {
                        log.debug("已經決出勝方, AI執行暫停!");
                        sustain = false;
                    } else {
                        Throws.con(Application.config().getComIntervalTime(), Thread::sleep).logThrowable();
                    }
                }
                break;
            case LocationPiece:
                final Object[] params = (Object[]) paramObj;
                Place from = (Place) params[0];
                Place to = (Place) params[1];
                PlayerType type = (PlayerType) params[2];
                Application.context().locatePiece(from, to, type);
                sendCommand(CommandExecutor.CommandType.SustainAiRunIfNextIsAi);
                break;
            default:
                throw new ShouldNotHappenException("未處理的命令: " + commandType);
        }
    }

    /**
     * 命令支援列舉(以下命令應當使用同一個執行緒執行, 一個事件結束之後, 另一個事件才能開始執行.)
     */
    @SuppressWarnings("java:S115")
    public enum CommandType {
        SuspendCallBackOrAiRun("停止撤銷|AI計算"),
        CallBackOneTime("撤銷一步"),
        SustainCallBack("持續撤銷"),
        AiRunOneTime("AI計算一步"),
        SustainAiRun("AI持續執行"),
        SustainAiRunIfNextIsAi("COM角色執行"),
        LocationPiece("ui落子命令");

        @Getter
        private final String label;

        CommandType(String label) {
            this.label = label;
        }
    }

}
複製程式碼

核心演算法



@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class AlphaBeta {

    private static final int MAX = 100_000_000;
    /**
     * 這裡要保證 Min + Max = 0, 哪怕是微不足道的差距都可能導致發生錯誤
     */
    private static final int MIN = -MAX;

    /**
     * 根據棋子數量, 動態調整搜尋深度
     *
     * @param pieceNum 棋子數量
     * @return 調整搜尋深度差值
     */
    public static int searchDeepSuit(final int pieceNum) {
        // 根據棋子數量, 動態調整搜尋深度
        if (pieceNum > 20) {
            return -2;
        } else if (pieceNum <= 4) {
            return 4;
        } else if (pieceNum <= 8) {
            return 2;
        }
        return 0;
    }

    /**
     * 生成待選的列表,就是可以下子的空位, 如果 deep > 2 則對搜尋結果進行排序.
     *
     * @param analysisBean 棋盤分析物件
     * @param curPart      當前走棋方
     * @param deep         搜尋深度
     * @return 可以下子的空位集合
     */
    private static MyList<StepBean> geneNestStepPlaces(final AnalysisBean analysisBean, final Part curPart, final int deep) {
        final Piece[][] pieces = analysisBean.pieces;
        // 是否殺棋
        MyList<StepBean> stepBeanList = ListPool.localPool().getAStepBeanList();
        for (int x = 0; x < ChessDefined.RANGE_X; x++) {
            for (int y = 0; y < ChessDefined.RANGE_Y; y++) {
                final Piece fromPiece = pieces[x][y];
                if (fromPiece != null && fromPiece.part == curPart) {
                    final Place from = Place.of(x, y);
                    // TO DO 考慮下此處新增至集合的做法 在計算時 是否有最佳化空間.
                    final MyList<Place> list = fromPiece.role.find(analysisBean, curPart, from);
                    if (list.isEmpty()) {
                        ListPool.localPool().addListToPool(list);
                        continue;
                    }
                    final Object[] elementData = list.eleTemplateDate();
                    for (int i = 0, len = list.size(); i < len; i++) {
                        stepBeanList.add(StepBean.of(from, (Place) elementData[i]));
                    }
                    ListPool.localPool().addListToPool(list);
                }
            }
        }
        // 是否排序, 如果搜尋深度大於2, 則對結果進行排序
        // 排序後的結果, 進入極大極小值搜尋演算法時, 容易被剪枝.
        if (deep > 2) {
            orderStep(analysisBean, stepBeanList, curPart);
        }

        return stepBeanList;
    }

    /**
     * 對 空位列表 進行排序, 排序後的空位列表, 進入極大極小值搜尋演算法時, 容易被剪枝.
     *
     * @param analysisBean 棋盤分析物件
     * @param stepBeanList 可以下子的空位列表
     * @param curPart      當前走棋方
     */
    private static void orderStep(final AnalysisBean analysisBean, final MyList<StepBean> stepBeanList, final Part curPart) {
        final Piece[][] srcPieces = analysisBean.pieces;
        // 進入迴圈之前計算好迴圈內使用常量
        MyList<DoubleBean<Integer, StepBean>> bestPlace = ListPool.localPool().getADoubleBeanList();
        // 對方棋手
        final Part oppositeCurPart = Part.getOpposite(curPart);
        int best = MIN;

        final Object[] objects = stepBeanList.eleTemplateDate();
        for (int i = 0; i < stepBeanList.size(); i++) {
            final StepBean item = (StepBean) objects[i];
            final Place to = item.to;
            // 備份
            final Piece eatenPiece = srcPieces[to.x][to.y];
            int score;
            // 判斷是否勝利
            if (eatenPiece != null && eatenPiece.role == Role.BOSS) {
                score = MAX;
            } else {
                // 走棋
                final int invScr = analysisBean.goForward(item.from, to, eatenPiece);
                DebugInfo.incrementAlphaBetaOrderTime();
                // 評分
                score = negativeMaximumWithNoCut(analysisBean, oppositeCurPart, -best);
                // 退回上一步
                analysisBean.backStep(item.from, to, eatenPiece, invScr);
            }
            // 這裡新增進所有的分數
            bestPlace.add(new DoubleBean<>(score, item));
            if (score > best) { // 找到一個更好的分,就把以前存的位子全部清除
                best = score;
            }
        }
        /* 排序後返回 */
        // 這樣排序是正確的, 可以有效消減數量
        bestPlace.sort((o1, o2) -> o2.getO1() - o1.getO1());

        stepBeanList.clear();
        bestPlace.forEach(dou -> stepBeanList.add(dou.getO2()));

        ListPool.localPool().addListToDoubleBeanListPool(bestPlace);
    }

    /**
     * 負極大值搜尋演算法(不帶剪枝演算法)
     *
     * @param analysisBean 局勢分析物件
     * @param curPart      當前走棋方
     * @return 負極大值搜尋演算法計算分值
     */
    private static int negativeMaximumWithNoCut(AnalysisBean analysisBean, Part curPart, int alphaBeta) {
        // 1. 初始化各個變數
        final Piece[][] pieces = analysisBean.pieces;
        int best = MIN;
        // 2. 生成待選的列表,就是可以下子的列表
        MyList<StepBean> stepBeanList = geneNestStepPlaces(analysisBean, curPart, 1);

        final Object[] objects = stepBeanList.eleTemplateDate();
        for (int i = 0, len = stepBeanList.size(); i < len; i++) {
            final StepBean item = (StepBean) objects[i];
            Place from = item.from;
            Place to = item.to;
            // 備份
            Piece eatenPiece = pieces[to.x][to.y];
            int score;
            // 判斷是否勝利
            if (eatenPiece != null && eatenPiece.role == Role.BOSS) {
                score = MAX;
            } else {
                // 走棋
                final int invScr = analysisBean.goForward(from, to, eatenPiece);
                DebugInfo.incrementAlphaBetaOrderTime();
                score = analysisBean.getCurPartEvaluateScore(curPart);
                // 退回上一步
                analysisBean.backStep(from, to, eatenPiece, invScr);
            }
            if (score > best) { // 找到一個更好的分,就更新分數
                best = score;
            }
            if (score > alphaBeta) { // alpha剪枝
                break;
            }
        }
        ListPool.localPool().addListToStepBeanListPool(stepBeanList);
        return -best;
    }

    /**
     * 奇數層是電腦(max層)thisSide, 偶數層是human(min層)otherSide
     *
     * @param srcPieces 棋盤
     * @param curPart   當前走棋方
     * @param deep      搜尋深度
     * @param forbidStep 禁止的步驟(長捉或長攔)
     * @return 下一步的位置
     */
    public static Set<StepBean> getEvaluatedPlace(final Piece[][] srcPieces, final Part curPart, final int deep, final StepBean forbidStep) {
        // 1. 初始化各個變數
        final AnalysisBean analysisBean = new AnalysisBean(srcPieces);
        // 2. 獲取可以下子的空位列表
        MyList<StepBean> stepBeanList = geneNestStepPlaces(analysisBean, curPart, deep);
        // 3. 移除不該下的子
        stepBeanList.remove(forbidStep);
        // 進入迴圈之前計算好迴圈內使用常量
        Set<StepBean> bestPlace = new HashSet<>();
        int best = MIN;
        // 對方棋手
        final Part oppositeCurPart = Part.getOpposite(curPart);
        // 下一深度
        final int nextDeep = deep - 1;
        log.debug("size : {}, content: {}", stepBeanList.size(), stepBeanList);
        final Object[] objects = stepBeanList.eleTemplateDate();
        for (int i = 0, len = stepBeanList.size(); i < len; i++) {
            StepBean item = (StepBean) objects[i];
            final Place to = item.to;
            // 備份
            final Piece eatenPiece = srcPieces[to.x][to.y];
            int score;
            // 判斷是否勝利
            if (eatenPiece != null && eatenPiece.role == Role.BOSS) {
                // 步數越少, 分值越大
                score = MAX + deep;
            } else {
                // 走棋
                final int invScr = analysisBean.goForward(item.from, to, eatenPiece);
                // 評分
                if (deep <= 1) {
                    score = analysisBean.getCurPartEvaluateScore(curPart);
                } else {
                    score = negativeMaximum(analysisBean, oppositeCurPart, nextDeep, -best);
                }
                // 退回上一步
                analysisBean.backStep(item.from, to, eatenPiece, invScr);
            }
            if (score == best) { // 找到相同的分數, 就新增這一步
                bestPlace.add(item);
            }
            if (score > best) { // 找到一個更好的分,就把以前存的位子全部清除
                best = score;
                bestPlace.clear();
                bestPlace.add(item);
            }
        }
        ListPool.end();
        ListPool.localPool().addListToStepBeanListPool(stepBeanList);
        return bestPlace;
    }

    /**
     * 奇數層是電腦(max層)thisSide, 偶數層是human(min層)otherSide
     *
     * @param srcPieces 棋盤
     * @param curPart   當前走棋方
     * @param deep      搜尋深度
     * @param forbidStep 禁止的步驟(長捉或長攔)
     * @return 下一步的位置
     */
    public static Set<StepBean> getEvaluatedPlaceWithParallel(final Piece[][] srcPieces, final Part curPart, final int deep, final StepBean forbidStep) {
        // 1. 初始化各個變數
        final AnalysisBean srcAnalysisBean = new AnalysisBean(srcPieces);
        // 2. 獲取可以下子的空位列表
        MyList<StepBean> stepBeanList = geneNestStepPlaces(srcAnalysisBean, curPart, deep);
        // 3. 移除不該下的子
        stepBeanList.remove(forbidStep);
        // 進入迴圈之前計算好迴圈內使用常量
        final Set<StepBean> bestPlace = new HashSet<>();
        final AtomicInteger best = new AtomicInteger(MIN);
        // 對方棋手
        final Part oppositeCurPart = Part.getOpposite(curPart);
        // 下一深度
        final int nextDeep = deep - 1;
        log.debug("size : {}, content: {}", stepBeanList.size(), stepBeanList);

        Arrays.stream(stepBeanList.toArray()).parallel().filter(Objects::nonNull).map(StepBean.class::cast).forEach(item -> {
            log.debug("並行流 ==> Thread : {}", Thread.currentThread().getId());
            final Piece[][] pieces = ArrayUtils.deepClone(srcPieces);
            final AnalysisBean analysisBean = new AnalysisBean(pieces);

            final Place to = item.to;
            // 備份
            final Piece eatenPiece = pieces[to.x][to.y];
            int score;
            // 判斷是否勝利
            if (eatenPiece != null && eatenPiece.role == Role.BOSS) {
                // 步數越少, 分值越大
                score = MAX + deep;
            } else {
                // 走棋
                final int invScr = analysisBean.goForward(item.from, to, eatenPiece);
                // 評分
                if (deep <= 1) {
                    score = analysisBean.getCurPartEvaluateScore(curPart);
                } else {
                    score = negativeMaximum(analysisBean, oppositeCurPart, nextDeep, -best.get());
                }
                // 退回上一步
                analysisBean.backStep(item.from, to, eatenPiece, invScr);
            }
            if (score == best.get()) { // 找到相同的分數, 就新增這一步
                synchronized (bestPlace) {
                    bestPlace.add(item);
                }
            }
            if (score > best.get()) { // 找到一個更好的分,就把以前存的位子全部清除
                best.set(score);
                synchronized (bestPlace) {
                    bestPlace.clear();
                    bestPlace.add(item);
                }
            }
            ListPool.end();
        });
        ListPool.localPool().addListToStepBeanListPool(stepBeanList);
        ListPool.end();
        return bestPlace;
    }

    /**
     * 負極大值搜尋演算法
     *
     * @param analysisBean 局勢分析物件
     * @param curPart      當前走棋方
     * @param deep         搜尋深度
     * @param alphaBeta    alphaBeta 剪枝分值
     * @return 負極大值搜尋演算法計算分值
     */
    private static int negativeMaximum(AnalysisBean analysisBean, Part curPart, int deep, int alphaBeta) {
        // 1. 初始化各個變數
        final Piece[][] pieces = analysisBean.pieces;
        int best = MIN;
        // 對方棋手
        final Part oppositeCurPart = Part.getOpposite(curPart);
        // 下一深度
        final int nextDeep = deep - 1;
        // 2. 生成待選的列表,就是可以下子的列表
        final MyList<StepBean> stepBeanList = geneNestStepPlaces(analysisBean, curPart, deep);

        final Object[] objects = stepBeanList.eleTemplateDate();
        for (int i = 0, len = stepBeanList.size(); i < len; i++) {
            final StepBean item = (StepBean) objects[i];
            Place from = item.from;
            Place to = item.to;
            // 備份
            Piece eatenPiece = pieces[to.x][to.y];
            int score;
            // 判斷是否勝利
            if (eatenPiece != null && eatenPiece.role == Role.BOSS) {
                // 步數越少, 分值越大
                score = MAX + deep;
            } else {
                // 走棋
                final int invScr = analysisBean.goForward(from, to, eatenPiece);
                // 評估
                if (deep <= 1) {
                    score = analysisBean.getCurPartEvaluateScore(curPart);
                } else {
                    score = negativeMaximum(analysisBean, oppositeCurPart, nextDeep, -best);
                }
                // 退回上一步
                analysisBean.backStep(from, to, eatenPiece, invScr);
            }
            if (score > best) { // 找到一個更好的分,就更新分數
                best = score;
            }
            if (score > alphaBeta) { // alpha剪枝
                break;
            }
        }
        ListPool.localPool().addListToStepBeanListPool(stepBeanList);
        return -best;
    }

}

複製程式碼

總結

透過此次的《中國象棋》遊戲實現,讓我對swing的相關知識有了進一步的瞭解,對java這門語言也有了比以前更深刻的認識。

java的一些基本語法,比如資料型別、運算子、程式流程控制和陣列等,理解更加透徹。java最核心的核心就是面向物件思想,對於這一個概念,終於悟到了一些。

作者:小虛竹and掘金
連結:https://juejin.cn/post/7061502423596007432
來源:稀土掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

分類: 旅遊
時間: 2022-02-07

相關文章

“奶豆”遐想
兒子同學的小寵物!取名叫奶豆! 它一進我家,我馬上就盯了上去,誰都不瞅啦!連兒子同學我都忽略不計! 兒子很小的時候因為孤獨,也曾央求過我,同意他養一隻小狗狗,因為我懂得那種傷,不想兒子情有此" ...

有錢人家的院子,愛養這4種花,高檔有品味,富貴又典雅

有錢人家的院子,愛養這4種花,高檔有品味,富貴又典雅
我國的園林藝術有著悠久的歷史,自古以來,有錢人家就愛在自家的庭院花園裡養養一些花草綠植,打造出一個雅緻,有品味的私家花園,這個習慣性一直延伸到如今,你知道嗎?有幾種花,很多人家都愛養,現在很多人家的私 ...

走,去麗江

走,去麗江
來源:雲南日報 麗江的夏天涼爽親人, 18℃的自然氣溫, 比裝滿西瓜冷飲的空調房更誘人. 走! 這個夏天,去麗江! (麗江古城 木瓊曉 攝) 去麗江看未曾見過的獨特風景 麗江的景點極多,全部玩完恐怕一 ...

這些花溫度越高,長勢越好,除了不能遮光以外,還要做好“三勤”

這些花溫度越高,長勢越好,除了不能遮光以外,還要做好“三勤”
溫度高的時候,很多花草都會停止生長,或者在陰涼處可以生長,在太陽下就會曬焦曬蔫.但也有一些花草是比較喜歡高溫的,夏天溫度高光照強的時候,一樣可以旺盛生長.下面這些花溫度越高,長勢越好,除了不能遮光以外 ...

一眼心動!這個秋天,我要去這7個“最”中國的古村,享受慢時光

一眼心動!這個秋天,我要去這7個“最”中國的古村,享受慢時光
如果說, 每個季節都有它專屬的顏色, 那秋天,一定是黃色. 低頭看自己行在有落葉的路上, 晚風吹來了陣陣涼意, 忽然就秋天了~ 一陣秋風吹過, 染黃了樹葉,染黃了草原, 染黃了山坡,染黃了胡楊- 當大 ...

5種被世俗耽誤的良心好花,完美詮釋了“我很普通,但很美”

5種被世俗耽誤的良心好花,完美詮釋了“我很普通,但很美”
養花種草,很多人都喜歡追求新品種.新品種的花草的確能給人眼前一亮的感覺,但它們未必好養,而且價格都比常見花草要貴不少.如果沒有多少時間打理花草,又想花開滿園,就種一些普通常見的花草好了,養好了不比新品 ...

誰說“花無百日紅”?紫薇就是與眾不同

誰說“花無百日紅”?紫薇就是與眾不同
立秋過後,城市裡的奼紫嫣紅紛紛退場,正是少花的時節.而紫薇花卻"錯峰出行",迎著漸涼的秋風,在公園綠地.路邊綠化帶等處開得紅豔. 在重慶市江北區鐵山坪開放的紫薇花(新華社記者周文衝 ...

9月養上4種花,開花漂亮好管理,趕上冬春季節開花

9月養上4種花,開花漂亮好管理,趕上冬春季節開花
9月養上4種花,開花漂亮好管理,趕上冬春季節開花 進入到秋天以後,很多花卉都開花越來越少,也逐漸到了花期結束的時候,很多花友不知道接下來該養什麼花,才能在冬春季節有漂亮的花兒觀賞.其實在冬春季節開花的 ...

拜拜了月季!養了四年月季,這個秋天果斷換坑,希望沒有換錯

拜拜了月季!養了四年月季,這個秋天果斷換坑,希望沒有換錯
每年秋天都是換坑.入坑的好時間,今年你換了哪些,入了哪些?有個花友說他愛好月季,在陽臺上養了四年月季,一開始滿心歡喜,後來厭煩到不行,幾十盆月季半死不活,在這個秋天果斷出坑了,一起來看看他的經歷吧. ...

遊薊州梨木颱風景區

遊薊州梨木颱風景區
2021年8月29日早上7:50,我從附近的民宿坐大巴到達梨木颱風景區,約二十分鐘車程. 梨木颱風景區 梨木颱風景區地處天津最北端,被稱為"天津北極". 小溪流 景區位於下營鎮船艙 ...

越過丘山:遇見小院生活才是最純粹的奢侈

越過丘山:遇見小院生活才是最純粹的奢侈
膠東民居特色,"垂直拔起"的挑空式結構獨具現代休閒氣質 民宿與丘山谷葡萄酒莊園連成一體,文化碰撞的獨特感受 地道農家飯,推薦蓬萊八大碗 對於都市人來說,院子是絕對的奢侈品.方正通透 ...

秋季可以種的4種“草花”,只需買棵小苗,就能持續開花一整年

秋季可以種的4種“草花”,只需買棵小苗,就能持續開花一整年
現在已經進入秋季了,我們好多花友都在開始大量的買花,但是買花要有針對性,如果你喜歡養觀葉植物,你就買一些觀葉植物,你喜歡養開花植物,而且又喜歡養開花時間長的植物,那麼建議你選擇4種草花,現在只需要買幾 ...

江蘇將打造5個大城市,8箇中等城市,推動省內均衡發展,反超廣東

江蘇將打造5個大城市,8箇中等城市,推動省內均衡發展,反超廣東
江蘇省在2020年的經濟總量突破10億元,達到102719億元,同時,省內蘇州邁入2萬億:南通邁入萬億:揚州邁入6000億:淮安邁入4000億,不得不說,在2020年江蘇的經濟發展是全面開花.然而江蘇 ...

2007年,中國南海神秘沉船時隔800年終於上岸,船身不腐價值連城

2007年,中國南海神秘沉船時隔800年終於上岸,船身不腐價值連城
中國作為四大文明古國之一,有著五千年輝煌燦爛的歷史,先輩們也為我們留下了不計其數的珍貴文化遺產,作為中華兒女,保護文物自然責無旁貸. 在我們的認知裡,文物大多都是作為陪葬品埋藏在陸地上的墓室王陵中,可 ...

沉海800年的南宋古船,歷時9個月花1.5億打撈,價值超乎你的想象

沉海800年的南宋古船,歷時9個月花1.5億打撈,價值超乎你的想象
1987年8月的一天,英國救撈公司和廣州救撈局合作,想在陽江海域尋找東印度公司的"萊茵堡號"沉船. 結果萊茵堡號沒撈到,卻撈出了一條鎏金腰帶. 這條金腰帶長1.7米,造型獨特精美, ...

“我家門前兩棵樹”,3步養棗樹,紅紅綠綠掛枝頭

“我家門前兩棵樹”,3步養棗樹,紅紅綠綠掛枝頭
兒時我們都學過魯迅的一篇<秋葉>:我的後園,可以看見牆外有兩株樹,一棵是棗樹,還有一棵也是棗樹.棗樹諧音"早樹",有著什麼都比別人早完成的美好希望,象徵著家庭美滿,夫妻 ...

山楂都能在家養了?5個養護步驟,冰糖葫蘆能管夠

山楂都能在家養了?5個養護步驟,冰糖葫蘆能管夠
在花友們的眼裡,一切植物皆可盆栽,現在連山楂樹都能養在家裡了.不只是盆栽山楂,甚至還可以做成山楂盆景. 而花花就在這裡分享一下家養山楂樹的養護步驟,學會了這5步,就能看到紅彤彤的山楂掛樹枝,一個冬天的 ...

這幾個公園“上新”了,有菊花、蘭花、南瓜、石榴、山楂…

這幾個公園“上新”了,有菊花、蘭花、南瓜、石榴、山楂…
一個壞訊息~小長假結束了~ 好訊息是這禮拜工作兩天,就又能休息了! 更重要的是,週末天氣很好,可以出門逛逛了~ 陶然亭公園 轉眼又是一秋,萬花已凋謝,卻是賞菊好時節~ 圖源:陶然亭公園官方微信 菊花是 ...

10種東北小“野”果,是不是你的童年回憶?

10種東北小“野”果,是不是你的童年回憶?
在很多人印象中,東北乃是高緯度苦寒之地,植被的豐富程度肯定不如熱帶亞熱帶,能有啥好果子吃?其實不然,有些植物更偏愛較為寒冷的地帶,在廣袤的東北黑土地上,也有關內輕易見不到的特色小"野&quo ...

帶著ID.4 X去“搵食”,會有怎樣新鮮的體驗?

帶著ID.4 X去“搵食”,會有怎樣新鮮的體驗?
跟很多廣東人一樣,每到週末,我都喜歡和親朋好友一起,到處去尋找美食.因為不同地方都會隱藏著一些只有當地老饕才會知道的特色美味,而要找到這些美食,一定要有一輛出色的座駕陪伴. 這個週末,我又出動了!不同 ...