云计算百科
云计算领域专业知识百科平台

Java Swing实战:双人交互性中国象棋对战系统开发全记录(一)

中国象棋是流传已久的传统益智棋类游戏,有着严谨完备的行走规则、独特的博弈逻辑。平日里时常进行象棋对弈,于是萌生了使用Java Swing GUI图形框架,复刻双人本地对战象棋程序的想法。

本次项目摒弃现成游戏引擎,依靠原生Java语法从零开发,利用二维数组存储整张棋盘的棋子排布信息,搭配鼠标监听器完成选子、预览落点、移动棋子、吃子整套交互。逐一编写车、马、炮、兵、士、象、将帅全部棋子的专属行走逻辑,同时实现别马腿、炮隔子打子、将帅不能隔空对视、红黑两方交替落子等核心对局规则,完整还原线下真人下棋的游玩体验。

撰写本篇开发手记,一方面梳理GUI界面搭建、事件监听、多层逻辑判断的知识点,复盘开发过程里遇到的落子失效、回合判定错乱、规则判断异常等各类问题;另一方面借助游戏项目实操,锻炼自身逻辑梳理与代码架构设计能力。后续计划在此基础上拓展对局存档、简易人机对战、历史对局回看等功能,进一步完善整套象棋程序。

一.项目概述:传统象棋游戏功能清单

(一)核心基础功能(已实现)

  • GUI可视化界面:绘制标准象棋棋盘,包含楚河汉界、九宫格区域,并渲染样式各异的红方与黑方棋子。
  • 棋子存储架构:采用二维 int 数组存储棋盘所有棋子数据,通过数值区分棋子种类及所属阵营(红方数值 < 10,黑方数值 > 10)。
  • 选子与落点预览:鼠标点击选中棋子后,自动在可合法移动的位置绘制标记圆点,直观展示可行走点位。
  • 轮流对局机制:通过布尔变量 isRedTurn 管控回合顺序,实现红、黑双方严格交替落子,禁止单方连续操作。
  • 全棋子行走规则判定
    • 车:沿横竖直线行走,需判断路径上是否存在棋子阻挡。
    • 炮:无棋子阻挡时可直线行走;吃子时,必须与目标棋子间隔恰好一颗棋子(俗称“炮架”)。
    • 马:走“日”字形,并实现了精准的“蹩马腿”拦截判定。
    • 象:走“田”字,受“象眼”规则限制,且不能过河。
    • 士:仅限在九宫格内沿斜线移动。
    • 将/帅:在九宫格内移动,并实现了“王不见王”规则,即双方将帅不能在同一直线上隔空相对。
    • 兵/卒:过河前只能向前直行;过河后,可向前、向左、向右移动。
  • 吃子逻辑:若目标点位存在敌方棋子,则执行吃子操作,清空该坐标的棋子数据;禁止移动到己方棋子所在位置。
  • 基础对局判定:当一方棋子被全部吃光,或将帅被“将死”无法移动时,判定对局结束。
  • (二)后续可拓展规划功能(体现项目延展性)

  • 对局数据存档:使用本地文本文件保存每一局的下棋记录,支持随时调取与回看对局步骤。
  • 简易AI人机模式:编写基础算法实现电脑自动落子,支持单人挑战程序。
  • 界面切换功能:借助 CardLayout 布局管理器,实现开始首页、对局页面、规则说明页之间的平滑跳转。
  • 对局数据统计:添加计时器、对局步数统计与对局用时展示功能。
  • 个性化皮肤:支持更换棋盘与棋子的皮肤样式。
  • 悔棋功能:支持撤销上一步落子操作。
  • 二.项目开发环境

    本项目基于 Java 标准库开发,主要依赖以下核心包:

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;

    • javax.swing.*:用于构建图形用户界面(GUI),包括窗口、面板、按钮、标签等组件。
    • java.awt.*:提供基础的绘图、布局管理和事件处理框架。
    • java.awt.event.MouseEvent 与 java.awt.event.MouseListener:用于监听和处理鼠标点击、移动等交互事件,是实现棋子选取与移动的核心。

    同时,为了后期程序维护和新功能加入,我们将采取封装的知识,将所有的方法写在chessTools类中,在运用地到的地方直接调用

    三. 整体项目架构划分(窗体层、数据层、鼠标监听逻辑层)

    窗体层(View Layer):负责图形界面的绘制与刷新。为实现棋盘和棋子的实时渲染,我们采用继承(extends)机制重写绘制方法,动态更新画面。为保持代码结构清晰,所有与界面绘制相关的核心逻辑均封装在 chessTools 工具类中,便于统一调用和维护。

    数据层(Model Layer):管理棋盘与棋子的核心状态数据。每颗棋子均包含其位置信息(行 row、列 column)和阵营标识(chessFlag)。这些数据不仅是游戏逻辑运算的基础,也为后续功能(如悔棋、对局回放)提供了必要的数据支撑。关于悔棋功能的具体实现,可参考类似项目(如五子棋4.0)中的数据结构与算法设计。

    鼠标监听器层(Controller Layer):处理用户交互事件,将物理坐标转换为棋盘逻辑坐标。该层主要监听鼠标的按下(mousePressed)与释放(mouseReleased)事件:在 mousePressed 中调用棋子选中逻辑,在 mouseReleased 中触发棋子移动与规则判定,从而完成“点击-选中-移动”的完整交互闭环。

    四. 棋盘与棋子的数据结构设计(二维数组存储棋子信息)

    棋子编码与棋盘参数定义

    为在程序中精确表示棋盘状态,我们定义了以下核心数据:

    • 棋子编码:使用整数编码区分棋子类型与阵营。
      • 0:空位
      • 1 – 7:黑方棋子(将、士、象、马、车、炮、卒)
      • 11 – 17:红方棋子(帅、仕、相、马、车、炮、兵)
    • 棋盘布局参数:
      • 棋盘左上角坐标:X = 100, Y = 100
      • 格子尺寸:Size = 80
      • 棋盘行列数:Row = 10, Col = 9

    核心数据结构:二维数组 chessArrSrc

    棋盘的初始状态通过一个二维整型数组 chessArrSrc 来定义,它直观地映射了象棋的标准开局布局。

    int[][] chessArrSrc = {
    {5, 4, 3, 2, 1, 2, 3, 4, 5}, // 黑方底线:车、马、象、士、将、士、象、马、车
    {0, 0, 0, 0, 0, 0, 0, 0, 0}, // 空行
    {0, 6, 0, 0, 0, 0, 0, 6, 0}, // 黑方炮位
    {7, 0, 7, 0, 7, 0, 7, 0, 7}, // 黑方卒位
    {0, 0, 0, 0, 0, 0, 0, 0, 0}, // 空行(楚河汉界)
    {0, 0, 0, 0, 0, 0, 0, 0, 0}, // 空行(楚河汉界)
    {17, 0, 17, 0, 17, 0, 17, 0, 17}, // 红方兵位
    {0, 16, 0, 0, 0, 0, 0, 16, 0}, // 红方炮位
    {0, 0, 0, 0, 0, 0, 0, 0, 0}, // 空行
    {15, 14, 13, 12, 11, 12, 13, 14, 15} // 红方底线:车、马、相、仕、帅、仕、相、马、车
    };

    为了在游戏过程中独立操作棋盘状态而不影响初始布局,我们通过 getChessArrSrc() 方法复制一份初始数组作为游戏进行中的棋盘数据副本。

    int[][] chessArr; // 游戏进行中的棋盘状态副本

    public int[][] getChessArrSrc() {
    int[][] chessArr = new int[10][9];
    for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 9; j++) {
    chessArr[i][j] = chessArrSrc[i][j];
    }
    }
    return chessArr;
    }

    五. 界面GUI搭建:棋盘底色、棋子绘制、窗体布局设计

    棋盘绘制方法 drawChessBoard

    绘制棋盘时,绘制顺序至关重要:必须先绘制白色背景底板,再绘制黑色网格线,最后覆盖“楚河汉界”区域和文字。

    public void drawChessBoard(Graphics g) {
    // 1. 绘制白色背景
    g.setColor(Color.white);
    g.fillRect(X Size / 2, Y Size / 2, Col * Size + Size, Row * Size + Size);

    // 2. 绘制黑色棋盘网格线
    g.setColor(Color.BLACK);
    // 绘制横线
    for (int i = 0; i < 10; i++) {
    g.drawLine(X, Y + i * Size, X + (Col 1) * Size, Y + i * Size);
    }
    // 绘制竖线
    for (int i = 0; i < 9; i++) {
    g.drawLine(X + i * Size, Y, X + i * Size, Y + (Row 1) * Size);
    }

    // 3. 绘制“楚河汉界”区域
    g.setColor(Color.YELLOW);
    g.fillRect(X + 2, Y + Size * 4 + 1, (Col 1) * Size 4, Size 2);

    // 4. 绘制“楚河汉界”文字
    String str1 = "楚", str2 = "河", str3 = "汉", str4 = "界";
    g.setColor(Color.BLACK);
    g.setFont(new Font("微软雅黑", Font.BOLD, 36));
    g.drawString(str1, X + 20 + Size, Y + Size * 4 + 50);
    g.drawString(str2, X + 20 + Size * 2, Y + Size * 4 + 50);
    g.drawString(str3, X + 20 + Size * 5, Y + Size * 4 + 50);
    g.drawString(str4, X + 20 + Size * 6, Y + Size * 4 + 50);
    }

    棋子绘制方法 drawChess

    该方法根据棋子编码(type)在指定坐标(x, y)绘制棋子。棋子由橙色圆形背景和中央的汉字标识组成,颜色(红/黑)由编码值决定。

    public void drawChess(Graphics g, int x, int y, int type) {
    // 1. 绘制橙色圆形背景
    g.setColor(Color.ORANGE);
    g.fillOval(x, y, 80, 80);

    // 2. 根据棋子编码设置文字颜色(红方或黑方)
    if (type <= 7) {
    g.setColor(Color.red); // 黑方棋子用红色字(传统显示习惯)
    } else {
    g.setColor(Color.BLACK); // 红方棋子用黑色字
    }

    // 3. 根据编码映射到对应的汉字
    String str = "";
    switch (type) {
    case 1 -> str = "将";
    case 2 -> str = "士";
    case 3 -> str = "象";
    case 4 -> str = "马";
    case 5 -> str = "车";
    case 6 -> str = "炮";
    case 7 -> str = "卒";
    case 11 -> str = "帅";
    case 12 -> str = "仕";
    case 13 -> str = "相";
    case 14 -> str = "马";
    case 15 -> str = "车";
    case 16 -> str = "炮";
    case 17 -> str = "兵";
    }

    // 4. 绘制棋子汉字
    Font font = new Font("楷体", Font.BOLD, 36);
    g.setFont(font);
    g.drawString(str, x + 20, y + 50);
    }

    六. 对局核心机制:红黑交替落子与回合判定逻辑实现

    对局的核心机制之一是确保红黑双方严格交替落子,这通过一个简单的布尔标志 isRedTurn 来控制。其核心思想是:在每次有效的落子操作后,切换当前回合的归属方。

    回合状态变量定义

    在游戏主类或逻辑控制类中,我们定义一个布尔类型的成员变量来记录当前回合:

    // 用于标识当前是否为红方回合,true 表示红方,false 表示黑方
    private boolean isRedTurn = true; // 默认红方先手

    回合判定与切换逻辑

    回合判定逻辑主要集成在鼠标监听器的 mouseReleased 事件处理中,在确认一次合法的棋子移动或吃子操作后执行。其流程如下:

  • 移动前检查:当玩家点击一个棋子准备移动时,首先检查该棋子是否属于当前回合的阵营。例如,如果 isRedTurn 为 true(红方回合),则只能移动红方棋子(编码值 >= 11)。
  • 规则验证:对选中的棋子,根据其类型(车、马、炮等)和当前位置,计算所有合法的移动目标位置。
  • 执行移动:如果玩家释放鼠标的位置是一个合法目标点,则执行移动或吃子操作,更新棋盘数据数组 chessArr。
  • 回合切换:在一次成功的移动操作之后,立即切换 isRedTurn 的值。
  • // 在 mouseReleased 事件处理函数中的简化逻辑示例
    public void mouseReleased(MouseEvent e) {
    // … 之前的代码:计算目标行列,验证移动合法性 …

    if (moveIsLegal) {
    // 执行移动:更新棋盘数组 chessArr
    chessArr[targetRow][targetCol] = chessArr[selectedRow][selectedCol];
    chessArr[selectedRow][selectedCol] = 0;

    // 核心:切换回合
    isRedTurn = !isRedTurn;

    // 重绘界面,刷新棋盘和棋子
    repaint();

    // (可选) 可以在这里添加回合提示,例如更新状态栏文本
    // statusLabel.setText(isRedTurn ? "红方回合" : "黑方回合");
    }
    // … 后续清理(如清空选中状态)…
    }

    关键细节与边界处理

  • 初始回合:游戏开始时,isRedTurn 应初始化为 true,遵循象棋“红先黑后”的规则。
  • 无效操作不切换:只有当移动符合所有规则并被成功执行时,才切换回合。如果点击的是空位、己方棋子(非选中操作)、或移动不合法,则不应切换回合。
  • 与棋子编码联动:回合判定需与棋子编码规则(红方 >=11, 黑方 <=7)紧密结合。在 mousePressed(选中棋子)阶段即可进行初步过滤,提升用户体验。
  • 游戏结束判定:在切换回合前或后,可以加入游戏结束判定逻辑(如将帅被将死、无子可走)。一旦检测到游戏结束,应锁定 isRedTurn,不再接受任何落子操作,并弹出提示。
  • 逻辑流程图

    以下 Mermaid 流程图清晰地展示了从玩家点击到回合切换的完整决策过程:

    #mermaid-svg-K7VB9D86AB0Hjj8F{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K7VB9D86AB0Hjj8F .error-icon{fill:#552222;}#mermaid-svg-K7VB9D86AB0Hjj8F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K7VB9D86AB0Hjj8F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K7VB9D86AB0Hjj8F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K7VB9D86AB0Hjj8F .marker.cross{stroke:#333333;}#mermaid-svg-K7VB9D86AB0Hjj8F svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K7VB9D86AB0Hjj8F p{margin:0;}#mermaid-svg-K7VB9D86AB0Hjj8F .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F .cluster-label text{fill:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F .cluster-label span{color:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F .cluster-label span p{background-color:transparent;}#mermaid-svg-K7VB9D86AB0Hjj8F .label text,#mermaid-svg-K7VB9D86AB0Hjj8F span{fill:#333;color:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F .node rect,#mermaid-svg-K7VB9D86AB0Hjj8F .node circle,#mermaid-svg-K7VB9D86AB0Hjj8F .node ellipse,#mermaid-svg-K7VB9D86AB0Hjj8F .node polygon,#mermaid-svg-K7VB9D86AB0Hjj8F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K7VB9D86AB0Hjj8F .rough-node .label text,#mermaid-svg-K7VB9D86AB0Hjj8F .node .label text,#mermaid-svg-K7VB9D86AB0Hjj8F .image-shape .label,#mermaid-svg-K7VB9D86AB0Hjj8F .icon-shape .label{text-anchor:middle;}#mermaid-svg-K7VB9D86AB0Hjj8F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K7VB9D86AB0Hjj8F .rough-node .label,#mermaid-svg-K7VB9D86AB0Hjj8F .node .label,#mermaid-svg-K7VB9D86AB0Hjj8F .image-shape .label,#mermaid-svg-K7VB9D86AB0Hjj8F .icon-shape .label{text-align:center;}#mermaid-svg-K7VB9D86AB0Hjj8F .node.clickable{cursor:pointer;}#mermaid-svg-K7VB9D86AB0Hjj8F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K7VB9D86AB0Hjj8F .arrowheadPath{fill:#333333;}#mermaid-svg-K7VB9D86AB0Hjj8F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K7VB9D86AB0Hjj8F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K7VB9D86AB0Hjj8F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K7VB9D86AB0Hjj8F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K7VB9D86AB0Hjj8F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K7VB9D86AB0Hjj8F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K7VB9D86AB0Hjj8F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K7VB9D86AB0Hjj8F .cluster text{fill:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F .cluster span{color:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K7VB9D86AB0Hjj8F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K7VB9D86AB0Hjj8F rect.text{fill:none;stroke-width:0;}#mermaid-svg-K7VB9D86AB0Hjj8F .icon-shape,#mermaid-svg-K7VB9D86AB0Hjj8F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K7VB9D86AB0Hjj8F .icon-shape p,#mermaid-svg-K7VB9D86AB0Hjj8F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K7VB9D86AB0Hjj8F .icon-shape .label rect,#mermaid-svg-K7VB9D86AB0Hjj8F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K7VB9D86AB0Hjj8F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K7VB9D86AB0Hjj8F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K7VB9D86AB0Hjj8F :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    玩家鼠标按下

    点击位置有棋子?

    忽略,无操作

    棋子属于当前回合方?(isRedTurn 与棋子编码匹配)

    提示“请等待对方回合”

    记录选中棋子,显示可移动位置

    玩家鼠标释放

    释放位置是合法落点?

    取消选中,不清除回合

    执行移动/吃子,更新棋盘数据

    切换 isRedTurn = !isRedTurn

    重绘界面

    检测游戏是否结束?

    锁定回合,显示获胜信息

    等待下一次操作

    通过一个布尔变量 isRedTurn 和与之配套的判定逻辑,我们实现了象棋对局中最基础的回合制交替机制。这是所有后续复杂规则(如将军、困毙)得以正确运行的前提。将此逻辑清晰地封装在控制器(Controller)层,保证了游戏状态变更与用户界面刷新的同步,为项目后续添加悔棋、回放等高级功能奠定了良好的数据流基础。
    在接下来的博客中,我们将继续书写棋子的行走规则和其他内容,继续加油!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Java Swing实战:双人交互性中国象棋对战系统开发全记录(一)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!