Java国际象棋应用getMove()方法与GUI交互及忙循环替代方案咨询
问题根因
- Swing的所有UI事件、渲染逻辑都运行在*事件调度线程(EDT)*上,你写的空死循环直接占住了EDT资源,导致
actionPerformed点击回调永远没有机会被执行,点击坐标自然不会更新。 - 你没有给坐标变量加
volatile修饰,多线程内存可见性无法保证,EDT更新的变量值不能被循环线程即时读取。添加System.out.println能临时生效是因为该方法内部有同步逻辑,会强制触发线程内存同步,所以能读到最新的坐标值,但这只是偶然生效的不稳定方案。
替代方案(事件驱动实现)
完全抛弃轮询死循环的逻辑,把走棋逻辑改成点击事件触发的回调模式,这也是GUI应用的标准实现思路,步骤如下:
- 首先删除
getMove方法里的空死循环逻辑,不需要主动阻塞等待点击。 - 调整主流程逻辑:每次轮到玩家行棋时,仅调用
resetClicks()重置坐标,不需要主动调用getMove等待返回。 - 修改
actionPerformed方法,在用户完成第二次点击(也就是ultimateFile和ultimateRank赋值完成后),主动触发走棋校验逻辑:
public void actionPerformed(ActionEvent event) { LocalizedButton button = (LocalizedButton) event.getSource(); if(penultimateFile == -1) { penultimateFile = button.getFile(); penultimateRank = button.getRank(); // 可选扩展:给用户反馈选中的棋子,比如高亮对应格子 } else { ultimateFile = button.getFile(); ultimateRank = button.getRank(); // 两次点击完成,直接构造走棋请求触发校验 MoveAttempt attempt = new MoveAttempt(penultimateFile, penultimateRank, ultimateFile, ultimateRank, currentPlayerIsWhite()); if (isMoveValid(attempt)) { // 合法走棋:执行移动、切换玩家 executeMove(attempt); switchPlayer(); resetClicks(); } else { // 非法走棋:重置点击,可选提示用户 resetClicks(); } } }
- 如果需要保留
HumanPlayer的getMove接口抽象,可以配合CountDownLatch实现非忙等的等待逻辑,同时避免阻塞EDT:- 先给所有坐标变量添加
volatile修饰保证跨线程可见性 - 把
getMove方法放在独立业务线程执行,修改为:
public MoveAttempt getMove(Interface interaction) throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); // 给interaction注册点击完成回调,两次点击完成后调用latch.countDown() interaction.setClickCompleteListener(latch::countDown); latch.await(); // 线程阻塞等待但不占用CPU资源 byte pieceFile = interaction.getPenultimateFile(); byte pieceRank = interaction.getPenultimateRank(); byte toFile = interaction.getUltimateFile(); byte toRank = interaction.getUltimateRank(); return new MoveAttempt(pieceFile, pieceRank, toFile, toRank, getIsWhite()); } - 先给所有坐标变量添加
额外优化建议
- 吃过路兵的bug可以在执行走棋逻辑时,判断走棋类型为过路兵后,手动删除对应位置的对方兵即可
- 第一次点击选中棋子后添加高亮反馈,能大幅提升用户体验
内容的提问来源于stack exchange,提问作者user17255798
相关产品推荐
相关产品推荐

