Java Swing写的Scrabble拼字游戏源码,带完整界面、拖拽磁贴和实时计分

该文章已生成可运行项目,

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个Scrabble拼字游戏用纯Java Swing实现,不依赖第三方库,开箱即用。运行后能看到标准15×15棋盘、可拖拽的字母磁贴、玩家手牌托盘和实时更新的得分面板。每步落子都会自动检查是否连成有效单词、是否贴合已有字母、是否覆盖合法格子(比如双倍字分、三倍字母分),并即时计算得分,状态栏同步提示成功或错误原因(比如‘单词不在词典中’或‘放置位置不合法’)。内置基础词典验证框架(dict.txt),支持常见英文单词校验;资源文件包含所有图标、字体(scrabble.ttf)、棋盘背景图和错误提示图。源码按功能拆分为Board、Tile、Tray、Bag、GameState等清晰模块,每个类职责单一,事件响应逻辑集中在Swing ActionListeners里,适合边运行边调试。附带可直接双击运行的jar包、MANIFEST配置和Ant构建脚本(build.xml),导入IDE后无需额外配置就能编译启动。适合练手GUI布局、鼠标拖放交互、集合管理(如手牌增删)、简单回溯式单词合法性判断,以及理解回合制状态流转。

1. 项目概述:一个“能跑、能玩、能学”的Scrabble教学级实现

你有没有试过想教新手理解GUI事件流,却卡在“按钮点了没反应”上?或者想讲清楚MVC在桌面端怎么落地,结果学生盯着一堆ActionListener匿名类发懵?这个Java Swing版Scrabble不是玩具Demo,而是一个我反复打磨、带学生实操过三轮的“教学锚点”。它用最朴素的Swing组件——JPanel堆棋盘、JLabel当磁贴、JLayeredPane管拖拽层级、GridLayout布托盘——把拼字游戏里所有关键机制都拆解成可触摸、可打断、可单步调试的代码块。核心关键词就三个:Scrabble游戏、Java Swing GUI、拼字游戏源码,但背后是整整一套面向初学者的GUI工程实践闭环:从鼠标按下(mousePressed)到释放(mouseReleased)的完整拖放状态机;从ArrayList<Tile>手牌集合到HashMap<Character, Integer>词频统计的自然过渡;从BoardTile.isDoubleWord()布尔判断到ScoreCalculator.computeScore()里嵌套的格子倍率乘法逻辑链。它不炫技,没有JavaFX动画、不接网络对战、不搞JSON存档,但你双击scrabble.jar就能立刻进入一个15×15标准棋盘世界——拖动字母磁贴时有阴影跟随,放下瞬间自动高亮合法单词路径,得分面板数字跳变,状态栏弹出“BINGO! +50 bonus”或“‘XQZ’ not in dictionary”这样的真实反馈。这不是教科书里的伪代码,而是你能在IDE里设断点、看GameState.currentPlayer如何流转、跟踪Bag.drawTiles(7)如何从剩余字母池里抽牌的活体样本。尤其适合那些刚写完“Hello World”、正对着JFrame.setVisible(true)发呆的同学——它告诉你,GUI编程的本质,就是把用户的一次点击、一次拖动、一次键盘输入,翻译成内存里对象状态的精确变更。

2. 整体架构与设计思路拆解:为什么用Swing?为什么这样分层?

2.1 拒绝“技术正确”,拥抱“教学友好”:Swing的选择逻辑

有人会问:都2024年了,为什么不用JavaFX?甚至用Web前端做?答案很实在:Swing的“笨重感”恰恰是教学优势。JavaFX的DragEventDropTarget封装太深,学生容易陷入“为什么拖拽监听器没触发”的玄学排查;而Swing的MouseListener+MouseMotionListener组合,每个回调方法(mouseDragged, mouseReleased)都像手术刀一样精准暴露交互链条。我试过让学生在TrayTile.mousePressed()里加一行System.out.println("drag start"),再在Board.mouseReleased()里打日志,他们立刻就懂了“拖拽不是魔法,是坐标计算+组件重绘+状态同步”三件事的协作。更关键的是,Swing零外部依赖——你不需要配Maven仓库、不用处理JavaFX模块路径,javac *.java && java -jar scrabble.jar两行命令就能跑起来。这对初学者建立“代码→可执行程序”的完整心智模型至关重要。至于性能?Scrabble棋盘只有225个格子,每步最多校验3个方向(横向、纵向、交叉),Swing的渲染延迟远低于人类反应时间,完全够用。

2.2 MVC的轻量级落地:三层职责的物理隔离

这个项目的MVC不是概念空谈,而是通过包结构和类命名强制约束的物理隔离:

  • Model层(src/model/)Bag(字母袋)、Tile(单个磁贴)、Board(棋盘状态)、GameState(全局回合状态)。它们只管数据:Bag维护26个字母的剩余数量(Map<Character, Integer>),Board用二维数组BoardTile[15][15]存格子状态,GameState记录当前玩家、分数、是否游戏结束。关键设计:所有Model类都是POJO,无Swing引用,可脱离GUI单独单元测试。比如Bag.testDraw()能验证抽牌概率,Board.testPlaceWord()能离线校验单词放置合法性。

  • View层(src/view/)Scrabble.java(主窗口)、BoardPanel.java(棋盘视图)、TrayPanel.java(手牌托盘)、ScorePanel.java(得分面板)。它们只负责“画什么”:BoardPanelGridLayout铺15×15个BoardTile组件,每个BoardTile重写paintComponent()绘制背景图(scrabble.png)和倍率标识(双倍字分用红色D,三倍字母用蓝色T);TrayPanelFlowLayout水平排列7个TrayTile标签。关键设计:View层绝不处理业务逻辑!BoardPanel.mouseReleased()只做一件事:把鼠标坐标转成棋盘行列索引,然后调用Controller.placeTile(row, col),自己不碰GameStateScoreCalculator

  • Controller层(src/controller/)GameController.java(核心协调者)。它像交响乐指挥,接收View的事件(如“用户在(3,4)放下磁贴”),调用Model的方法(Board.placeTile(tile, 3, 4)),再通知View更新(scorePanel.updateScore()statusBar.showMessage("Valid word!"))。为什么不分得更细? 因为初学者容易迷失在“Controller该不该调用Validator?”的哲学讨论里。这里直接把校验逻辑内聚在GameController里:validateAndScore()方法内部串联WordValidator.isValid()BoardValidator.isAdjacent()ScoreCalculator.computeScore(),形成一条清晰的“输入→处理→输出”流水线。

提示:这种分层不是银弹。当你需要添加“撤销功能”时,GameController会膨胀。但对教学而言,它让“哪里改代码影响哪里”变得一目了然——想改计分规则?只动ScoreCalculator;想换词典校验算法?只碰WordValidator;想调整棋盘UI?只改BoardPanel。这种确定性,比追求架构完美更重要。

2.3 拖拽交互的底层实现:不是API调用,而是坐标运算

Swing没有开箱即用的“拖拽磁贴”组件,所有效果都是手动计算出来的。核心在于TrayTile(手牌磁贴)和BoardTile(棋盘格子)的协同:

  • 拖拽起点TrayTile.mousePressed()记录初始坐标startX/startY,并调用getTopLevelAncestor().setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR))切换鼠标样式。
  • 拖拽过程TrayTile.mouseDragged()计算鼠标相对初始点的偏移量(dx, dy),然后调用TrayTile.setLocation(x + dx, y + dy)实现视觉跟随。这里的关键技巧是:TrayTile必须设置为setOpaque(false)且父容器TrayPanel启用setOpaque(true),否则拖拽时会出现重影。
  • 拖拽终点Board.mouseReleased()获取鼠标在棋盘组件内的坐标,用SwingUtilities.convertPoint()转换为棋盘网格索引(row, col)难点在于吸附精度:用户鼠标可能落在格子边缘,直接取整会导致错位。解决方案是计算鼠标到最近格子中心的距离,若小于阈值(如15像素),则吸附到该格子——这行代码int row = Math.round((y - boardY) / TILE_HEIGHT)背后,是三次调试才确定的TILE_HEIGHT=48像素(棋盘图scrabble.png实际尺寸)。

这种“手动造轮子”的过程,恰恰让学生看清了GUI交互的本质:一切都是坐标系转换和状态同步。比起调用DragSource.startDrag(),亲手写mouseDragged()让他们真正理解“为什么拖拽要禁用组件默认行为”、“为什么释放时要重新计算坐标”。

3. 核心细节解析与实操要点:从磁贴拖放到词典校验的硬核细节

3.1 磁贴(Tile)的设计哲学:一个对象承载三重身份

Tile类看似简单,却是整个游戏的数据基石。它同时扮演三个角色:

  • 物理实体char letter(字母)、int value(分值)、boolean isBlank(是否空白牌)。Scrabble规则中,空白牌可代表任意字母但分值为0,这直接影响计分逻辑——ScoreCalculator遇到空白牌时,需跳过其基础分值计算,但倍率仍生效。
  • 状态载体boolean isPlaced(是否已放置)、int row/col(在棋盘上的位置)。注意:isPlaced不是布尔开关,而是状态机的一部分。当用户拖动已放置的磁贴时,GameController先调用Board.removeTile(row, col)将其标记为未放置,再放入新位置。这避免了“同一磁贴被重复放置”的逻辑漏洞。
  • UI代理JLabel继承自Tile,使其天然支持Swing渲染。Tile重写toString()返回字母(如”A”),JLabelsetText()直接绑定,省去额外的getLetter()调用。实操心得:初学者常犯的错误是给TileJLabel字段导致循环引用。正确做法是让Tile本身成为JLabel子类——既符合“is-a”关系(磁贴就是一个可显示的标签),又避免了TileJLabel状态不同步的问题。

3.2 棋盘(Board)的合法性校验:三道防火墙的协同工作

每步落子后,GameController.validateAndScore()会启动三重校验,缺一不可:

  1. 基础放置校验(BoardValidator.isPlacementValid())
    - 检查目标格子是否为空(board[row][col].isEmpty()
    - 检查是否首次放置(GameState.isFirstMove())且必须覆盖中心格(row==7 && col==7
    - 检查是否相邻于已有字母(BoardValidator.hasAdjacentTile())——遍历(row±1,col)(row,col±1)四个方向,只要有一个非空即通过。注意:这个检查在首次放置时跳过,否则中心格永远无法落子。

  2. 单词连通性校验(WordValidator.isWordConnected())
    这是最易被忽略的环节。Scrabble要求新放置的磁贴必须与棋盘上已有字母形成至少一个连续单词(横向或纵向)。算法采用“种子填充”思想:
    - 以新放置磁贴为起点,沿横向扫描,收集所有连续非空格子,构成候选单词字符串;
    - 同样沿纵向扫描,构成另一候选字符串;
    - 若任一字符串长度≥2,且所有字符均来自Tile.letter(空白牌视为通配符),则视为连通。
    - 避坑技巧:学生常误以为只需检查“新磁贴是否挨着旧磁贴”,但规则要求的是“形成有效单词”。例如,在已有”CAT”下方放”T”,形成垂直”CT”不算单词,必须延伸成”CAT”纵向才合法。

  3. 词典有效性校验(WordValidator.isValidInDictionary())
    使用dict.txt(约10万英文单词)构建HashSet<String>内存词典。关键优化在于大小写归一化dict.txt全小写,而用户放置的单词可能是大写(Tile.letter默认大写),所以校验前必须candidateWord.toLowerCase()。更进一步,处理空白牌:若单词含空白牌(如”QU?T”),需生成所有可能替换(”QUAT”, “QUBT”…),但教学版简化为仅校验无空白牌的单词——因为真实Scrabble中,空白牌代表的字母由玩家声明,校验时需用户提供该字母,此处为降低复杂度暂不实现。

注意:三重校验的顺序不能颠倒!必须先过基础放置(否则连通性校验无意义),再过连通性(否则词典校验无效单词),最后词典校验。我在GameController里用if-else if-else链式判断,而非并行校验,确保错误提示精准定位问题根源。

3.3 实时计分系统:倍率叠加的数学陷阱与破解

Scrabble计分是典型的“乘法叠加”而非“加法累加”,极易因计算顺序错误导致分值偏差。以单词”QUIZ”放在双倍字分(DW)格上为例:
- 基础分:Q(10)+U(1)+I(1)+Z(10) = 22
- 字母倍率:Q在三倍字母(TL)格 → Q×3=30,其余不变 → 30+1+1+10 = 42
- 单词倍率:整个单词在DW格 → 42×2 = 84

ScoreCalculator.computeScore()的实现必须严格遵循此顺序:

// 步骤1:计算基础分(含字母倍率)
int baseScore = 0;
for (int i = 0; i < word.length(); i++) {
    char c = word.charAt(i);
    int tileValue = getTileValue(c); // 获取字母基础分
    int multiplier = getLetterMultiplier(row, col, i); // 获取该位置字母倍率
    baseScore += tileValue * multiplier;
}

// 步骤2:应用单词倍率(仅对本次放置的单词)
int wordMultiplier = getWordMultiplier(row, col, direction); // 获取单词倍率
int totalScore = baseScore * wordMultiplier;

// 步骤3:检查BINGO奖励(一次性放置7个磁贴)
if (placedTiles.size() == 7) {
    totalScore += 50;
}

致命陷阱:如果先算单词倍率再算字母倍率,结果会变成(10+1+1+10)×2×3=132(错误!)。教学时我让学生手动计算”QUIZ”案例,再对比代码输出,立刻暴露出运算优先级问题。这就是为什么getLetterMultiplier()必须在循环内调用——每个字母的倍率可能不同(如Q在TL格,U在普通格),必须逐个乘。

4. 实操过程与核心环节实现:从零编译到流畅对战的完整路径

4.1 环境准备与项目导入:告别“配置地狱”

这个项目刻意规避了现代Java开发的复杂配置,全程基于JDK 8+和Ant构建:

  • JDK版本:明确要求JDK 8或更高(MANIFEST.MFMain-Class: ScrabbleClass-Path: .表明无外部依赖)。学生用java -version确认即可,无需配置JAVA_HOME环境变量(Windows下双击jar自动调用系统默认JDK)。
  • IDE导入:以IntelliJ IDEA为例:
    1. File → Open → 选择项目根目录(含src/resources/的文件夹)
    2. IDE自动识别为Ant项目(因存在build.xml),无需手动配置SDK——它会读取build.xml中的<property name="java.home" value="${java.home}"/>使用当前运行IDE的JDK。
    3. 右键Scrabble.java → Run 'Scrabble.main()',瞬间启动游戏窗口。
  • Ant构建脚本(build.xml)详解
    xml <target name="compile"> <mkdir dir="build/classes"/> <javac srcdir="src" destdir="build/classes" includeantruntime="false"/> </target> <target name="jar" depends="compile"> <jar destfile="scrabble.jar" basedir="build/classes"> <fileset dir="resources"/> <manifest> <attribute name="Main-Class" value="Scrabble"/> </manifest> </jar> </target>
    关键点在于<fileset dir="resources"/>——它把logo1.pngscrabble.ttf等资源打包进jar,确保双击运行时图片字体不丢失。学生第一次运行ant jar成功生成scrabble.jar时,那种“我亲手造出了可执行程序”的成就感,远超任何理论讲解。

4.2 核心交互流程实录:一次标准回合的代码追踪

让我们以玩家1放置单词”HELLO”在第1行(横向)为例,追踪从鼠标点击到得分更新的完整链路:

  1. 用户操作:在TrayPanel中点击字母”H”磁贴(TrayTile实例)。
  2. View响应TrayTile.mousePressed()触发,记录起始坐标,setCursor(MOVE_CURSOR)
  3. 拖拽过程TrayTile.mouseDragged()持续更新磁贴位置,视觉上”H”跟随鼠标移动。
  4. 释放落子:鼠标在BoardPanel第1行第3列释放,BoardPanel.mouseReleased()捕获坐标(x,y),调用SwingUtilities.convertPoint()得到棋盘索引(row=0, col=2)
  5. Controller调度BoardPanel委托给GameController.placeTile(tile, 0, 2)
  6. Model变更
    - GameController调用Board.placeTile(tile, 0, 2),将tile存入board[0][2]
    - 调用Tray.removeTile(tile),从玩家手牌移除”H”;
    - 调用Bag.drawTile()补充一个新磁贴到托盘。
  7. 合法性校验
    - BoardValidator.isPlacementValid(0,2):检查board[0][2]为空 → 通过;
    - WordValidator.isWordConnected(0,2,"HORIZONTAL"):横向扫描得”HELLO”(假设L,O已存在),长度5≥2 → 通过;
    - WordValidator.isValidInDictionary("HELLO"):查dict.txt命中 → 通过。
  8. 计分计算ScoreCalculator.computeScore("HELLO", 0, 2, "HORIZONTAL")返回12分(H=4,E=1,L=1,L=1,O=1,无倍率)。
  9. View更新
    - ScorePanel.updateScore(player1, 12)刷新玩家1分数;
    - StatusBar.showMessage("HELLO scored 12 points!")
    - BoardPanel.repaint()重绘棋盘,高亮”HELLO”路径。

实操心得:建议学生在GameController.placeTile()开头加System.out.println("Placing " + tile.getLetter() + " at " + row + "," + col),再在computeScore()里打印中间变量。这种“裸眼调试”比断点更直观——看着控制台滚动的日志,就像亲眼见证数据在内存中流动。

4.3 资源文件的精准运用:不只是图片,更是规则载体

项目中的资源文件绝非装饰,而是规则的具体化:

  • scrabble.png:15×15棋盘背景图,每个格子尺寸严格为48×48像素(代码中TILE_WIDTH/TILE_HEIGHT常量由此而来)。中心格(7,7)必须是双倍字分(DW)图案,四角为三倍字分(TW),确保BoardTile.isDoubleWord()等方法能通过坐标准确判断倍率。
  • scrabble.ttf:官方Scrabble字体,用于ScorePanelStatusBar的标题文字。加载方式为Font.createFont(Font.TRUETYPE_FONT, new File("resources/scrabble.ttf")),若文件路径错误,字体回退到系统默认,但视觉上立刻暴露资源缺失。
  • illegal-tile-placement.pngillegal-word.png:状态栏错误提示的图标。StatusBarshowError(String message, String imagePath)方法根据错误类型动态切换图标——这教会学生“UI反馈要具体”,而不是笼统弹出“操作失败”。
  • dict.txt:词典文件,UTF-8编码,每行一个单词(全小写)。关键细节:文件末尾必须有空行,否则BufferedReader.readLine()在最后一行返回null导致HashSet漏加载最后一个单词。我在WordValidator.loadDictionary()里加了while ((line = reader.readLine()) != null && !line.trim().isEmpty())双重保险。

5. 常见问题与排查技巧实录:那些让我熬夜调试的坑

5.1 拖拽失效:光标变不了?磁贴不跟随?

这是新手最高频问题,90%源于坐标系混乱:

现象根本原因解决方案
鼠标按下后光标没变TrayTile未调用setCursor(),或父容器TrayPanel拦截了事件TrayTile.mousePressed()第一行加getParent().setCursor(...),确保父容器也响应
磁贴拖拽时闪烁/重影TrayTile未设置setOpaque(false),导致重绘时背景色叠加TrayTile构造函数中添加this.setOpaque(false),并确保TrayPanel背景色为纯色(setBackground(Color.WHITE)
拖拽到棋盘后释放无反应BoardPanel未启用鼠标监听,或mouseReleased()坐标转换错误检查BoardPanel是否调用addMouseListener(this);用System.out.println("Raw X:"+e.getX()+", Y:"+e.getY())打印原始坐标,对比convertPoint()后的值

独家技巧:在BoardPanel.paintComponent()里临时添加Graphics2D g2 = (Graphics2D)g; g2.setColor(Color.RED); g2.drawRect(0,0,getWidth(),getHeight());,画出棋盘组件的实际边界框。很多“拖拽不到棋盘”的问题,其实是鼠标释放时落在了BoardPanel的边框外(比如状态栏区域),画框后一目了然。

5.2 计分错误:为什么”QUIZ”只算了42分?

倍率计算错误往往藏在细节里:

  • 陷阱1:倍率数组越界
    Boardint[][] letterMultiplier存储倍率,但初始化时写成new int[15][14](少一列)。结果board[0][14](第1行第15列)的倍率始终为0。排查:在ScoreCalculator中加System.out.println("Multiplier at "+row+","+col+": "+letterMultiplier[row][col]),对比棋盘图确认坐标。

  • 陷阱2:空白牌分值误算
    getTileValue(' ')返回0,但ScoreCalculator未跳过其倍率计算,导致0×3=0参与总和。修复:在计算循环中加if (c == ' ') continue;,空白牌不计入基础分。

  • 陷阱3:BINGO奖励重复触发
    学生在GameController.endTurn()里写了if (tray.size()==0) score += 50;,但tray.size()是手牌数,BINGO要求“本回合放置7个磁贴”,应检查placedTiles.size()==7教训:变量命名要精确——traySize vs placedCount,避免语义混淆。

5.3 词典校验失败:明明”HELLO”在dict.txt里,却提示”not in dictionary”

大小写和换行符是隐形杀手:

问题表现快速验证法
dict.txt用Windows换行符(\r\nreadLine()读出的单词末尾带\r"HELLO\r".equals("HELLO")为falseloadDictionary()中加line = line.trim(),清除首尾空白
dict.txt编码不是UTF-8特殊字符(如”naïve”)乱码,HashSet存入乱码字符串用记事本打开dict.txt,另存为“UTF-8无BOM格式”
单词含多余空格dict.txt某行是" HELLO "(前后空格)同样用line.trim()解决,或在isValidInDictionary()中统一candidate.trim().toLowerCase()

终极排查表:当校验失败时,按顺序执行:
1. System.out.println("Candidate: '"+candidate+"'"); —— 看单词是否含不可见字符
2. System.out.println("Dict size: "+dictionary.size()); —— 确认词典加载成功(应≈100000)
3. System.out.println("First dict word: '"+dictionary.iterator().next()+"'"); —— 验证词典内容正常

5.4 构建失败:ant jar报错”package resources does not exist”

资源路径错误的经典症状:

  • 错误场景src/Scrabble.java中写ImageIcon icon = new ImageIcon("resources/logo1.png"),但ant jar后jar包内logo1.png在根目录,而代码试图从当前工作目录找。
  • 正确做法:全部改用ClassLoader资源定位:
    ```java
    // 错误:相对路径依赖工作目录
    ImageIcon icon = new ImageIcon(“resources/logo1.png”);

// 正确:从classpath根目录找
URL imageUrl = getClass().getClassLoader().getResource(“logo1.png”);
ImageIcon icon = new ImageIcon(imageUrl);
`` - **验证技巧**:在Scrabble.java构造函数中加System.out.println(“Resource URL: “+imageUrl);,运行jar时看输出是否为jar:file:/path/to/scrabble.jar!/logo1.png。如果不是,说明资源未打包进jar——检查build.xml `是否指向正确目录。

6. 教学延展与二次开发指南:从“能跑”到“能改”的跃迁

这个项目真正的价值,不在于它现在是什么,而在于它极低的修改门槛。我带过的三届学生,都在一周内完成了以下改造:

  • 添加音效:在GameController.placeTile()末尾插入AudioClip sound = java.awt.Toolkit.getDefaultToolkit().getAudioClip(getClass().getResource("/sound/place.wav")); sound.play();。只需准备resources/sound/place.wav,无需引入任何音频库。
  • 实现撤销功能(Undo):在GameController中增加Stack<GameStateSnapshot> undoStack,每次placeTile()前调用saveState()快照当前BoardTrayScore状态。undo()方法弹出栈顶快照并恢复。关键技巧:Board的快照不是深拷贝整个二维数组,而是只记录changedTiles(本次修改的格子列表),大幅降低内存开销。
  • 扩展词典校验:将WordValidator.isValidInDictionary()替换为调用在线API(如https://api.dictionaryapi.dev/api/v2/entries/en/{word}),需添加HttpURLConnection代码。此时dict.txt降级为离线缓存,isValidInDictionary()先查本地缓存,未命中再请求网络——教会学生混合架构设计。
  • 适配中文Scrabble:替换dict.txt为中文词库(如《现代汉语词典》词条),Tile类增加String chineseWord字段,ScoreCalculator按汉字笔画数或常用度赋分。这让学生直面Unicode处理(String.length() vs codePointCount())、中文分词等新挑战。

最后分享一个小技巧:鼓励学生修改TripleWord.java中的颜色常量——把Color.RED改成new Color(255, 105, 180)(粉红),再观察BoardPanel重绘效果。这种“改一行代码看到即时变化”的正向反馈,比一百句理论讲解更能点燃学习热情。毕竟,编程的乐趣,从来不在宏大的架构,而在指尖敲下回车后,那个小小的红色“TW”格子,真的在屏幕上亮了起来。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个Scrabble拼字游戏用纯Java Swing实现,不依赖第三方库,开箱即用。运行后能看到标准15×15棋盘、可拖拽的字母磁贴、玩家手牌托盘和实时更新的得分面板。每步落子都会自动检查是否连成有效单词、是否贴合已有字母、是否覆盖合法格子(比如双倍字分、三倍字母分),并即时计算得分,状态栏同步提示成功或错误原因(比如‘单词不在词典中’或‘放置位置不合法’)。内置基础词典验证框架(dict.txt),支持常见英文单词校验;资源文件包含所有图标、字体(scrabble.ttf)、棋盘背景图和错误提示图。源码按功能拆分为Board、Tile、Tray、Bag、GameState等清晰模块,每个类职责单一,事件响应逻辑集中在Swing ActionListeners里,适合边运行边调试。附带可直接双击运行的jar包、MANIFEST配置和Ant构建脚本(build.xml),导入IDE后无需额外配置就能编译启动。适合练手GUI布局、鼠标拖放交互、集合管理(如手牌增删)、简单回溯式单词合法性判断,以及理解回合制状态流转。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值