成语接龙游戏代码分析

成语接龙游戏代码分析

以下是该成语接龙游戏项目的目录结构(基于提供的代码文件路径整理):

com.idiomgame/
├── main/
│   └── IdiomGameMain.java  // 程序入口类,负责启动GUI
├── ui/
│   ├── MainFrame.java      // 主游戏界面,包含游戏交互逻辑
│   └── RankingFrame.java   // 排行榜界面,展示玩家成绩排名
├── model/
│   └── PlayerRecord.java   // 数据模型类,存储玩家记录信息(姓名、成语数量、用时)
└── service/
    ├── IdiomService.java   // 成语业务逻辑类,处理成语校验、接龙规则、机器响应等核心功能
    └── RankingService.java // 排行榜服务类,负责记录的增删、排序及文件持久化

该结构遵循了功能模块化的设计思想,将不同职责的代码分门别类:

  • main 包:程序启动入口
  • ui 包:图形用户界面相关类
  • model 包:数据模型类,存储业务数据
  • service 包:业务逻辑处理类,实现核心功能

功能概述

该代码实现了一个基于Java Swing的成语接龙游戏,主要功能包括:

  • 人机对战的成语接龙核心逻辑
  • 游戏记录(成语数量、用时)的本地存储与排行榜展示
  • 图形化用户界面,支持游戏开始/结束、成绩提交、排行榜查看等操作

重点代码讲解

1. 核心游戏逻辑(IdiomService.java)

// 校验用户输入是否为有效成语
public boolean isValidIdiom(String userIdiom) {
    if (userIdiom == null || userIdiom.trim().length() != 4) {
        return false; // 非四字文本直接排除
    }
    String idiom = userIdiom.trim();
    // 检查是否在本地词库中
    boolean existsInLibrary = false;
    for (String localIdiom : LOCAL_IDIOMS) {
        if (localIdiom.equals(idiom)) {
            existsInLibrary = true;
            break;
        }
    }
    // 检查是否已被使用
    return existsInLibrary && !usedIdioms.contains(idiom);
}

// 校验接龙是否正确
public boolean checkIdiomConnection(String lastIdiom, String currentIdiom) {
    if (lastIdiom == null || currentIdiom == null || lastIdiom.length() != 4 || currentIdiom.length() != 4) {
        return false;
    }
    // 取尾字和首字进行匹配
    char lastChar = lastIdiom.charAt(lastIdiom.length() - 1);
    char firstChar = currentIdiom.charAt(0);
    return lastChar == firstChar;
}

这部分代码实现了游戏的核心规则:

  • 验证成语是否为四字且存在于词库中
  • 验证接龙是否符合"前一个成语尾字与后一个成语首字相同"的规则
  • 管理已使用成语集合,避免重复使用

2. 排行榜功能(RankingService.java)

// 添加新记录并保持前10名
public void addRecord(PlayerRecord record) {
    records.add(record);
    sortRecords(); // 排序
    if (records.size() > MAX_RECORDS) {
        records = records.subList(0, MAX_RECORDS); // 只保留前10
    }
    saveToFile(); // 持久化到文件
}

// 排序规则:按成语数量降序,数量相同按时间升序
private void sortRecords() {
    Collections.sort(records, new Comparator<PlayerRecord>() {
        @Override
        public int compare(PlayerRecord r1, PlayerRecord r2) {
            if (r1.getIdiomCount() != r2.getIdiomCount()) {
                return Integer.compare(r2.getIdiomCount(), r1.getIdiomCount());
            } else {
                return Long.compare(r1.getTimeSpent(), r2.getTimeSpent());
            }
        }
    });
}

排行榜功能特点:

  • 采用文件存储(CSV格式),支持数据持久化
  • 只保留排名前10的记录
  • 排序规则合理:先按成语数量(多者优先),再按用时(少者优先)

3. 用户界面(MainFrame.java)

// 游戏流程控制
private void startGame() {
    idiomService.resetGame();
    historyArea.setText("");
    inputField.setText("");
    isPlaying = true;

    // 更新按钮状态
    startButton.setEnabled(false);
    endButton.setEnabled(true);
    submitButton.setEnabled(true);
    
    // 机器先出第一个成语
    lastIdiom = idiomService.getMachineResponse(null);
    idiomService.recordUsedIdiom(lastIdiom);
    historyArea.append("机器: " + lastIdiom + "\n");
    historyArea.append("请接下一个成语(以" + lastIdiom.charAt(lastIdiom.length() - 1) + "开头):\n");

    gameStartTime = new Date();
}

界面实现了完整的游戏流程控制:

  • 状态管理(游戏中/非游戏中)
  • 按钮状态联动
  • 游戏历史记录展示
  • 时间统计与成绩提交

MVC架构现状

  1. 模型(Model)

    • PlayerRecord:存储玩家记录数据
    • LOCAL_IDIOMS:成语数据
  2. 视图(View)

    • MainFrame:主游戏界面
    • RankingFrame:排行榜界面
  3. 控制器(Controller)

    • IdiomService:游戏逻辑控制
    • RankingService:排行榜数据管理

现状分析:

  • 基本实现了MVC分离,业务逻辑与界面展示分离
  • 服务类(Service)承担了控制器角色,处理核心逻辑
  • 视图层不直接操作数据,通过服务类间接交互

需要改进的地方

  1. 数据存储

    • 目前使用CSV文件存储,可考虑使用数据库提高性能和可靠性
    • 文件操作未处理并发问题,多用户场景下可能出现数据不一致
  2. 代码结构

    • 成语数据硬编码在代码中,应改为外部文件加载
    • MainFrame类代码量较大(300行),可拆分出事件处理类
  3. 功能扩展

    • 缺少难度选择功能
    • 没有玩家信息管理(如多用户、头像等)
    • 缺少游戏帮助和规则说明
  4. 异常处理

    • 文件操作异常仅打印堆栈,未给用户友好提示
    • 输入验证可更严格(如空格、特殊字符处理)
  5. 性能优化

    • 成语校验使用线性查找(O(n)),可改为HashSet存储提高效率(O(1))
    // 建议改进:初始化时将成语存入HashSet
    private Set<String> idiomSet;
    
    public IdiomService() {
        idiomSet = new HashSet<>(Arrays.asList(LOCAL_IDIOMS));
    }
    
    // 优化后验证方法
    public boolean isValidIdiom(String userIdiom) {
        // ... 其他校验
        return idiomSet.contains(idiom) && !usedIdioms.contains(idiom);
    }
    
  6. 数据库:
    成语数据过少,只能判断在本地中的成语,未在数组中的成语会被识别为无效或者已经使用。

重要代码用法详解

1. 成语接龙核心算法

// 机器接龙实现
public String getMachineResponse(String lastIdiom) {
    // 机器先出第一个成语
    if (lastIdiom == null) {
        List<String> unusedIdioms = new ArrayList<>();
        for (String idiom : LOCAL_IDIOMS) {
            if (!usedIdioms.contains(idiom)) {
                unusedIdioms.add(idiom);
            }
        }
        if (unusedIdioms.isEmpty()) return null;
        String selected = unusedIdioms.get(random.nextInt(unusedIdioms.size()));
        usedIdioms.add(selected);
        return selected;
    }

    // 机器接用户的成语
    char targetFirstChar = lastIdiom.charAt(lastIdiom.length() - 1);
    List<String> candidateIdioms = new ArrayList<>();
    for (String idiom : LOCAL_IDIOMS) {
        if (!usedIdioms.contains(idiom) && idiom.charAt(0) == targetFirstChar) {
            candidateIdioms.add(idiom);
        }
    }

    if (candidateIdioms.isEmpty()) return null;
    String selected = candidateIdioms.get(random.nextInt(candidateIdioms.size()));
    usedIdioms.add(selected);
    return selected;
}

工作原理:

  • 首次调用(lastIdiom为null)时随机选择一个未使用的成语
  • 后续调用时,根据上一个成语的尾字筛选符合条件的候选成语
  • 从候选成语中随机选择一个并标记为已使用
  • 若无候选成语则返回null,表示机器接不上来(用户获胜)

2. 排行榜文件操作

// 从CSV文件加载排行榜
private void loadFromFile() {
    File file = new File(RANKING_FILE);
    if (!file.exists()) return;

    try (BufferedReader br = new BufferedReader(
            new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
        String line;
        br.readLine(); // 跳过标题行

        while ((line = br.readLine()) != null) {
            String[] parts = line.split(",");
            if (parts.length == 3) {
                try {
                    String name = parts[0];
                    int count = Integer.parseInt(parts[1]);
                    long time = Long.parseLong(parts[2]);
                    records.add(new PlayerRecord(name, count, time));
                } catch (NumberFormatException e) {
                    // 忽略格式错误的行
                }
            }
        }
        sortRecords();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

文件格式说明:

  • 使用UTF-8编码的CSV文件
  • 第一行为标题行:"姓名,成语数量,用时(毫秒)"
  • 后续每行一条记录,格式为"姓名,数量,时间"
  • 加载时进行格式验证,忽略错误行

3. 界面事件处理

// 提交按钮事件处理
submitButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        processUserInput();
    }
});

// 输入框回车提交
inputField.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        if (isPlaying) {
            processUserInput();
        }
    }
});

事件处理特点:

  • 支持按钮点击和回车键两种提交方式
  • 通过isPlaying状态控制输入是否有效
  • 统一由processUserInput()方法处理输入逻辑,避免代码重复

运行示范

  1. 主界面
    image
  2. 点击开始挑战后,分别为答对和误答的两种情况
    image
    image
    image
  3. 答题结束后,计入成绩
    image
    image

总结

该代码实现了一个功能完整的成语接龙游戏,架构清晰,核心逻辑正确。通过MVC模式分离了数据、视图和控制,便于维护和扩展。主要可改进点在于数据存储方式、代码结构优化和功能扩展,特别是将硬编码的成语数据改为外部文件加载,以及使用更高效的数据结构提升性能。

posted @ 2025-11-01 02:32  才知风自由  阅读(3)  评论(0)    收藏  举报