From 3663352e5f902d0f31059ad7ebca52c3bb3fb820 Mon Sep 17 00:00:00 2001 From: LHY0125 <3364451258@qq.com> Date: Wed, 9 Jul 2025 23:00:26 +0800 Subject: [PATCH] Add files via upload --- README.md | 200 ++++++++++++++++++++++++++------------------------- ai.c | 55 ++++++++------ config.h | 94 ++++++++++++++++++++++++ game_mode.c | 14 ++++ gobang.c | 60 ++++++++-------- init_board.c | 1 + record.c | 156 +++++++++++++++++++++++++++++----------- record.h | 29 ++++---- 五子棋.c | 8 ++- 9 files changed, 415 insertions(+), 202 deletions(-) create mode 100644 config.h diff --git a/README.md b/README.md index 2defef9..d799475 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,95 @@ -# C˻սAI +# C语言五子棋人机对战AI ![Build Status](https://img.shields.io/badge/build-passing-brightgreen) ![License](https://img.shields.io/badge/license-MIT-blue) -## Ŀ¼ -- [C˻սAI](#c˻սai) - - [Ŀ¼](#Ŀ¼) - - [Ŀ](#Ŀ) - - [](#) - - [ٿʼ](#ٿʼ) - - [Ŀ](#Ŀ) - - [Ϸ](#Ϸ) - - [Ϸ淨](#Ϸ淨) - - [Ҫ](#Ҫ) - - [](#) - - [Ȩ](#Ȩ) - - [ʾ](#ʾ) - - [AI ʵ](#ai-ʵ) - - [㷨](#㷨) - - [](#) - - [Ŀṹ](#Ŀṹ) - - [֤](#֤) - - [ӭ](#ӭ) - - [δƻ](#δƻ) +## 目录 +- [C语言五子棋人机对战AI](#c语言五子棋人机对战ai) + - [目录](#目录) + - [项目简介](#项目简介) + - [功能特性](#功能特性) + - [快速开始](#快速开始) + - [编译项目](#编译项目) + - [运行游戏](#运行游戏) + - [游戏玩法](#游戏玩法) + - [环境要求](#环境要求) + - [常见问题](#常见问题) + - [权限问题](#权限问题) + - [乱码显示问题](#乱码显示问题) + - [AI 设计实现](#ai-设计实现) + - [核心算法](#核心算法) + - [棋局评估函数](#棋局评估函数) + - [项目结构](#项目结构) + - [许可证](#许可证) + - [欢迎贡献](#欢迎贡献) + - [未来计划](#未来计划) -## Ŀ -һʹCʵֵ˻սϵͳ Alpha-Beta ֦Ż Minimax 㷨֧Զ̴СϷ浵ʵʱ塣 +## 项目简介 +这是一个使用C语言实现的五子棋人机对战系统,它基于 Alpha-Beta 剪枝优化的 Minimax 算法,并支持自定义棋盘大小、游戏存档和实时悔棋。 -## -- ˻սģʽ -- chessboard Զ̳ߴ (5x525x25) -- ༶AIѶ (1-5ɵ) -- ԶϷ浵 -- ʵʱԾֿϵͳ -- 幦 (ɳһ) -- ն˽ʾ -- 걸֤ (ȷû붼ЧΧ) -- ѡĻغϼʱ -- ԶϷ¼ +## 功能特性 +- 人机对战模式 +- chessboard 自定义棋盘尺寸 (5x5至25x25) +- 多级AI难度 (1-5级可调) +- 自动游戏存档与加载 +- 实时对局控制系统 +- 悔棋功能 (可撤销上一步) +- 纯粹的终端界面显示 +- 完备的输入验证 (确保所有用户输入都在有效范围内) +- 可选的回合计时器 +- 自动游戏记录保存 +- 复盘功能 (支持保存和回顾对局记录) +- 评分系统 (可以对每一步棋进行评分和分析) +- 禁手规则支持 (防止玩家进行无意义的走法) +- 内存管理优化 (改进了内存管理,减少了资源占用) +- 模块化设计 (便于功能扩展和维护) +- 增强错误提示 (帮助用户快速定位问题) -## ٿʼ +## 快速开始 -### Ŀ +### 编译项目 ```bash -gcc .c gobang.c game_mode.c init_board.c record.c ai.c -o gobang.exe +gcc 五子棋.c gobang.c game_mode.c init_board.c record.c ai.c -o gobang.exe ``` -### Ϸ +### 运行游戏 ```bash .\gobang.exe ``` -## Ϸ淨 -1. ̴С (ĬΪ15x15) -2. ѡAIѶȵȼ (1-5) -3. Ϸ (ʽ: ) - - R/rɻ -4. Ϸɲ鿴Ծִ浵־ +## 游戏玩法 +1. 启动程序后设置棋盘大小 (默认为15x15) +2. 选择AI难度等级 (1-5) +3. 轮流输入坐标进行游戏 (格式:行 列) + - 输入R/r可悔棋 +4. 游戏结束后可查看对局存档和日志 -## Ҫ -- ϵͳ: Windows (ǰ汾ʹWindowsе `_kbhit()` `Sleep()` ݲƽ̨) -- : GCC (MinGW-w64) -- ն: ֧UTF-8ն +## 环境要求 +- 操作系统: Windows (当前版本使用了Windows特有的 `_kbhit()` 和 `Sleep()` 函数,因此暂不跨平台) +- 编译器: GCC (MinGW-w64) +- 终端: 支持UTF-8编码的终端 -> **ƽ̨˵:** +> **跨平台兼容性说明:** > -> ΪδLinuxmacOSϵͳУҪƽ̨ضĴ루 `_kbhit()`滻Ϊƽ̨ʵ֣ʹ루`#ifdef _WIN32`и롣 +> 为了未来在Linux或macOS等其他操作系统上运行,需要将平台特定的代码(如 `_kbhit()`)替换为跨平台的实现,或使用条件编译(`#ifdef _WIN32`)进行隔离。 -## +## 常见问题 -### Ȩ -ڱϷ¼ʱʾ޷ļͨڳȱдȨޡ볢½ +### 权限问题 +如果在保存游戏记录时提示“无法创建文件”,这通常是由于程序缺少写入权限。请尝试以下解决方案: -1. **ԹԱ**Ҽ `gobang.exe` ڹԱȨ޵նг -2. **ĿĿ¼Ȩ**ȷĿĿ¼ϵͳĿ¼ `C:\Program Files`齫ĿûĿ¼£ `D:\Code` -3. **ֶ `records` Ŀ¼** `records` Ŀ¼ڣ `gobang.exe` Ŀ¼ֶһ +1. **以管理员身份运行**:右键点击 `gobang.exe` 并在管理员权限的终端中运行程序。 +2. **更改项目目录权限**:确保项目目录不在受系统保护的目录(如 `C:\Program Files`),建议将项目放在用户目录下,例如 `D:\Code`。 +3. **手动创建 `records` 目录**:如果 `records` 目录不存在,请在 `gobang.exe` 所在目录手动创建一个。 -### ʾ -WindowsնгַʾΪ룬ն˴ҳƥ䵼µġڳǰִ +### 乱码显示问题 +如果在Windows终端中出现中文字符显示为乱码,这是由于终端代码页不匹配导致的。请在程序运行前执行以下命令: ```bash chcp 65001 ``` -ѵǰն˵ĴҳлΪUTF-8ӶȷʾַΪ˷㣬Դһļ `.bat` Զִд˲ +这会把当前终端的代码页切换为UTF-8,从而正确显示中文字符。为了方便,你可以创建一个批处理文件 `.bat` 来自动执行此操作。 **start_game.bat** ```batch @@ -92,58 +98,58 @@ chcp 65001 .\gobang.exe ``` -## AI ʵ +## AI 设计实现 -ĿAIҪ¼ʵ֣ +项目的AI主要基于以下技术实现: -### 㷨 +### 核心算法 -- **Minimax㷨 (Minimax)**ΪĻģͣΪ˫˶ĵÿһѡŽⷨ -- **Alpha-Beta ֦ (Alpha-Beta Pruning)**Minimax㷨شŻͨЩӰվߵ֦AIļЧʣʹܹʱڴﵽȡ -- ****AI˼ȣĬΪ3㣬ԸѶȵȼеԽAIԤԽǿʱҲԽ +- **Minimax算法 (Minimax)**:作为博弈树的基础模型,为双人对弈的每一步选择最优解法。 +- **Alpha-Beta 剪枝 (Alpha-Beta Pruning)**:对Minimax算法的重大优化,通过剪掉那些不影响最终决策的树枝来提高AI的计算效率,使其能够在有限时间内达到更深的搜索深度。 +- **搜索深度**:AI的思考深度,默认为3层,可以根据难度等级进行调整。深度越大,AI预测能力越强,但计算耗时也越长。 -### +### 棋局评估函数 -Ϊ˶ֽмֵAIʹһ׸ӵϵͳҪݰ +为了对棋局进行价值评估,AI使用了一套复杂的评分系统,其主要依据包括: -- **ʶ (Pattern Recognition)**ܹʶϷеĹؼͣ硰塱ġġȣΪÿ͸費ͬȨء -- **λȨ (Positional Value)**ϲͬλõսԼֵͬλͨȱԵλøơΪϵӵ㸽λ÷֡ -- **в (Threat Detection)**Щֱܹγʤӵ㣬硰򡰻Щ輫ߵֵץסʤᡣ -- **˫ͨ**һʱͬʱжǷӵ㹻ĿռγЧͣڱλ³Ч塣 +- **棋型识别 (Pattern Recognition)**:能够识别并评估游戏中的关键棋型,如“连五”、“活四”、“冲四”、“活三”等,并为每种棋型赋予不同权重。 +- **位置权重 (Positional Value)**:棋盘上不同位置的战略价值不同,中心位置通常比边缘位置更有优势。评估函数会为棋盘上的落子点附加位置分。 +- **威胁检测 (Threat Detection)**:评估那些能够直接形成制胜局面的落子点,如“四三”或“活三”,并对这些点给予极高的评价值,以抓住制胜机会。 +- **双向连通性**:在评估一个点时,会同时判断其是否拥有足够的空间形成有效棋型,避免在被封锁的位置下出无效棋。 -## Ŀṹ -- `.c` - ڣʼϷģʽѡ -- `gobang.c` - Ϸ߼̲ʤжϡ -- `gobang.h` - `gobang.c` ͷļ˺ݽṹͺԭ͡ -- `game_mode.c` - Ϸģʽʵ֣˻ս˫˶ս͹սģʽ -- `game_mode.h` - `game_mode.c` ͷļϷģʽغԭ͡ -- `init_board.c` - ̳ʼعܣ̴СϷѡȡ -- `init_board.h` - `init_board.c` ͷļ -- `record.c` - Ϸ¼ıͶȡܡ -- `record.h` - `record.c` ͷļ -- `ai.c` - AI㷨ʵ֣MinimaxAlpha-Beta֦ -- `ai.h` - `ai.c` ͷļ +## 项目结构 +- `五子棋.c` - 主程序入口,负责初始化与游戏模式选择。 +- `gobang.c` - 核心游戏逻辑,包括棋盘操作、胜负判断。 +- `gobang.h` - `gobang.c` 的头文件,定义了核心数据结构和函数原型。 +- `game_mode.c` - 各种游戏模式的实现,如人机对战、双人对战和观战模式。 +- `game_mode.h` - `game_mode.c` 的头文件,声明游戏模式相关函数原型。 +- `init_board.c` - 棋盘初始化相关功能,包括设置棋盘大小、游戏选项等。 +- `init_board.h` - `init_board.c` 的头文件。 +- `record.c` - 游戏记录的保存和读取功能。 +- `record.h` - `record.c` 的头文件。 +- `ai.c` - AI算法的实现,包括Minimax和Alpha-Beta剪枝。 +- `ai.h` - `ai.c` 的头文件。 -## ֤ +## 许可证 -Ŀ [MIT ֤](https://opensource.org/licenses/MIT)Ȩ +本项目采用 [MIT 许可证](https://opensource.org/licenses/MIT)授权。 -ζɵʹáơ޸ġϲ桢ַȨ/۱ĸֻиҪаԭʼİȨͱɡ +这意味着你可以自由地使用、复制、修改、合并、出版、分发、再授权和/或销售本软件的副本,只需在所有副本或重要部分中包含原始的版权声明和本许可声明即可。 -## ӭ +## 欢迎贡献 -Ƿdzӭκʽķ͹ף㷢Bugйܽ飬ϣĽ룬ʱͨ·ʽ룺 +我们非常欢迎任何形式的反馈和贡献!如果你发现了Bug、有功能建议,或希望改进代码,请随时通过以下方式参与: -- **ύ Issue**ⷴ뷨 [GitHub Issues](https://github.com/LHY0125/Gobang-Game/issues) ҳύϸ -- ** Pull Request**Դ˸Ľӭύ Pull RequestȷĴĿһ£ṩĸĶ˵ +- **提交 Issue**:对于问题反馈或新想法,请在 [GitHub Issues](https://github.com/LHY0125/Gobang-Game/issues) 页面提交详细描述。 +- **发起 Pull Request**:如果你对源码进行了改进,欢迎提交 Pull Request。请确保你的代码风格与项目保持一致,并提供清晰的改动说明。 -ÿһι׶ʹĿøã +你的每一次贡献都将使这个项目变得更好! -## δƻ +## 未来计划 -ΪĿøãǼƻδʵ¹ܣ +为了让这个项目变得更好,我们计划在未来实现以下功能: -- [ ] **ͼû (GUI)**ʹ `SDL2` `Qt` ȿ⣬ǰն˽滻Ϊͼλ棬û顣 -- [ ] **ս**һ߶սģʽҿͨжս -- [ ] **׿⹤**뿪ֿ⣬ʹAIϷֽ׶ܹѡŵӷ -- [ ] **عŻ**Żд룬ģ黯̶ȺЧʣʵȫĿƽ̨ԡ \ No newline at end of file +- [ ] **图形用户界面 (GUI)**:使用 `SDL2` 或 `Qt` 等库,将当前的终端界面替换为图形化界面,提升用户体验。 +- [ ] **网络对战功能**:增加一个在线对战模式,让玩家可以通过网络进行对战。 +- [ ] **棋谱库工具**:引入开局库,使AI在游戏开局阶段能够选择最优的落子方案。 +- [ ] **代码重构与优化**:持续优化现有代码,提高模块化程度和运行效率,并实现完全的跨平台兼容性。 \ No newline at end of file diff --git a/ai.c b/ai.c index 8d49da8..4665b82 100644 --- a/ai.c +++ b/ai.c @@ -1,11 +1,12 @@ #include "gobang.h" #include "ai.h" +#include "config.h" #include #include #include // 防守系数 -double defense_coefficient = 1.2; +double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; extern int BOARD_SIZE; extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; extern int step_count; @@ -64,7 +65,7 @@ int evaluate_pos(int x, int y, int player) if (info.continuous_chess >= 5) { board[x][y] = original; // 还原棋盘 - return 1000000; // 返回最大分 + return SEARCH_WIN_BONUS; // 返回最大分 } // 根据连续棋子数评分 @@ -72,38 +73,38 @@ int evaluate_pos(int x, int y, int player) { case 4: // 四连珠 if (info.check_start && info.check_end) // 活四(两端开放) - line_scores[i] = 100000; + line_scores[i] = AI_SCORE_LIVE_FOUR; else if (info.check_start || info.check_end) // 冲四(一端开放) - line_scores[i] = 10000; + line_scores[i] = AI_SCORE_RUSH_FOUR; else // 死四(两端封闭) - line_scores[i] = 500; + line_scores[i] = AI_SCORE_DEAD_FOUR; break; case 3: // 三连珠 if (info.check_start && info.check_end) // 活三 - line_scores[i] = 5000; + line_scores[i] = AI_SCORE_LIVE_THREE; else if (info.check_start || info.check_end) // 眠三 - line_scores[i] = 1000; + line_scores[i] = AI_SCORE_SLEEP_THREE; else // 死三 - line_scores[i] = 50; + line_scores[i] = AI_SCORE_DEAD_THREE; break; case 2: // 二连珠 if (info.check_start && info.check_end) // 活二 - line_scores[i] = 500; + line_scores[i] = AI_SCORE_LIVE_TWO; else if (info.check_start || info.check_end) // 眠二 - line_scores[i] = 100; + line_scores[i] = AI_SCORE_SLEEP_TWO; else // 死二 - line_scores[i] = 10; + line_scores[i] = AI_SCORE_DEAD_TWO; break; case 1: // 单子 if (info.check_start && info.check_end) // 开放位置 - line_scores[i] = 50; + line_scores[i] = AI_SCORE_LIVE_ONE; else if (info.check_start || info.check_end) // 半开放位置 - line_scores[i] = 10; + line_scores[i] = AI_SCORE_HALF_ONE; else // 封闭位置 - line_scores[i] = 1; + line_scores[i] = AI_SCORE_DEAD_ONE; break; } } @@ -125,7 +126,7 @@ int evaluate_pos(int x, int y, int player) int center_x = BOARD_SIZE / 2; int center_y = BOARD_SIZE / 2; int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离 - int position_bonus = 50 * (BOARD_SIZE - distance); // 距离中心越近奖励越高 + int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高 board[x][y] = original; // 还原棋盘状态 return total_score + position_bonus; // 返回总评估分 @@ -153,7 +154,7 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi // 检查当前落子是否获胜 if (check_win(x, y, player)) { - return (player == AI) ? 1000000 + depth : -1000000 - depth; + return (player == AI) ? SEARCH_WIN_BONUS + depth : -SEARCH_WIN_BONUS - depth; } // 达到搜索深度或平局 @@ -224,11 +225,21 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi * 2. 进攻阶段:若无紧急防御需求,使用评估函数选择最佳进攻位置 * @note 实现细节: * - 优先处理玩家活四、冲四等危险局面 - * - 步数>10时缩小搜索范围到已有棋子附近2格 + * - 步数>AI_SEARCH_RANGE_THRESHOLD时缩小搜索范围到已有棋子附近AI_NEARBY_RANGE格 * - 使用中心位置优先策略 */ void ai_move(int depth) { + // 如果是第一步,直接下在中心位置附近 + if (step_count == 0) + { + int center = BOARD_SIZE / 2; + board[center][center] = AI; + steps[step_count++] = (Step){AI, center, center}; + printf("AI落子(%d, %d)\n", center + 1, center + 1); + return; + } + // 1. 首先检查是否需要阻止玩家的四子连棋或三子活棋 for (int i = 0; i < BOARD_SIZE; i++) { @@ -277,7 +288,7 @@ void ai_move(int depth) } // 2. 如果没有需要立即阻止的情况,则正常评估 - int best_score = -1000000; + int best_score = -SEARCH_WIN_BONUS; int best_x = -1, best_y = -1; // 遍历棋盘所有空位 @@ -290,11 +301,11 @@ void ai_move(int depth) continue; } - // 只考虑已有棋子附近(2格范围内) + // 只考虑已有棋子附近(AI_NEARBY_RANGE格范围内) bool has_nearby_stone = false; - for (int di = -2; di <= 2; di++) + for (int di = -AI_NEARBY_RANGE; di <= AI_NEARBY_RANGE; di++) { - for (int dj = -2; dj <= 2; dj++) + for (int dj = -AI_NEARBY_RANGE; dj <= AI_NEARBY_RANGE; dj++) { int ni = i + di; int nj = j + dj; @@ -312,7 +323,7 @@ void ai_move(int depth) break; } } - if (!has_nearby_stone && step_count > 10) + if (!has_nearby_stone && step_count > AI_SEARCH_RANGE_THRESHOLD) { continue; } diff --git a/config.h b/config.h new file mode 100644 index 0000000..d45dc43 --- /dev/null +++ b/config.h @@ -0,0 +1,94 @@ +/** + * @file config.h + * @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com) + * @brief 五子棋游戏参数配置头文件 + * @version 1.0 + * @date 2025-07-09 + * + * @copyright Copyright (c) 2025 + * + * @note 本文件集中定义了五子棋游戏的所有参数配置,便于统一管理和修改 + */ + +#ifndef CONFIG_H +#define CONFIG_H + +//---------- 棋盘相关参数 ----------// +#define MAX_BOARD_SIZE 25 // 支持的最大棋盘尺寸 +#define DEFAULT_BOARD_SIZE 15 // 默认棋盘尺寸 +#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) // 游戏最大步数 + +//---------- 玩家标识参数 ----------// +#define EMPTY 0 // 棋盘空位标识 +#define PLAYER 1 // 玩家标识 (用于人机对战模式) +#define AI 2 // AI标识 (用于人机对战模式) +#define PLAYER1 1 // 玩家1标识 (用于双人对战模式) +#define PLAYER2 2 // 玩家2标识 (用于双人对战模式) + +//---------- 特殊输入命令 ----------// +#define INPUT_UNDO -1 // 悔棋 +#define INPUT_SAVE -2 // 保存 +#define INPUT_EXIT -3 // 退出 +#define INPUT_SURRENDER -4 // 认输 + +//---------- 游戏设置默认值 ----------// +#define DEFAULT_USE_FORBIDDEN_MOVES false // 默认不启用禁手规则 +#define DEFAULT_USE_TIMER 0 // 默认不启用计时器 +#define DEFAULT_TIME_LIMIT 30 // 默认时间限制为30秒 + +//---------- AI参数 ----------// +#define DEFAULT_AI_DEPTH 3 // 默认AI搜索深度 +#define DEFAULT_DEFENSE_COEFFICIENT 1.2 // 默认防守系数 + +//---------- 评分参数 ----------// +// 棋型评分 - 用于calculate_step_score函数 +#define SCORE_FIVE 0 // 五连 +#define SCORE_LIVE_FOUR 2000 // 活四 +#define SCORE_RUSH_FOUR 1000 // 冲四 +#define SCORE_DEAD_FOUR 300 // 死四 +#define SCORE_LIVE_THREE 500 // 活三 +#define SCORE_SLEEP_THREE 200 // 眠三 +#define SCORE_DEAD_THREE 80 // 死三 +#define SCORE_LIVE_TWO 100 // 活二 +#define SCORE_SLEEP_TWO 40 // 眠二 +#define SCORE_DEAD_TWO 15 // 死二 +#define SCORE_LIVE_ONE 15 // 开放单子 +#define SCORE_HALF_ONE 8 // 半开放单子 +#define SCORE_DEAD_ONE 2 // 封闭单子 + +// 位置奖励系数 +#define POSITION_BONUS_FACTOR 10 // 位置奖励因子 + +// AI评估参数 - 用于evaluate_pos函数 +#define AI_SCORE_FIVE 1000000 // AI评估-五连 +#define AI_SCORE_LIVE_FOUR 100000 // AI评估-活四 +#define AI_SCORE_RUSH_FOUR 10000 // AI评估-冲四 +#define AI_SCORE_DEAD_FOUR 500 // AI评估-死四 +#define AI_SCORE_LIVE_THREE 5000 // AI评估-活三 +#define AI_SCORE_SLEEP_THREE 1000 // AI评估-眠三 +#define AI_SCORE_DEAD_THREE 50 // AI评估-死三 +#define AI_SCORE_LIVE_TWO 500 // AI评估-活二 +#define AI_SCORE_SLEEP_TWO 100 // AI评估-眠二 +#define AI_SCORE_DEAD_TWO 10 // AI评估-死二 +#define AI_SCORE_LIVE_ONE 50 // AI评估-开放单子 +#define AI_SCORE_HALF_ONE 10 // AI评估-半开放单子 +#define AI_SCORE_DEAD_ONE 1 // AI评估-封闭单子 + +// AI位置奖励系数 +#define AI_POSITION_BONUS_FACTOR 50 // AI位置奖励因子 + +// 搜索算法参数 +#define SEARCH_MAX_SCORE 1000000 // 搜索最大分数 +#define SEARCH_WIN_BONUS 1000000 // 获胜奖励分数 +#define AI_NEARBY_RANGE 2 // AI搜索的邻近范围 +#define AI_SEARCH_RANGE_THRESHOLD 10 // AI开始限制搜索范围的步数阈值 + +// 评分权重参数 +#define TIME_WEIGHT_FACTOR 0.5 // 时间权重因子 +#define WIN_BONUS 2000 // 胜利奖励分数 + +// 文件路径参数 +#define RECORDS_DIR "records" // 记录文件目录 +#define MAX_PATH_LENGTH 256 // 最大路径长度 + +#endif // CONFIG_H \ No newline at end of file diff --git a/game_mode.c b/game_mode.c index 839c777..23d7ce3 100644 --- a/game_mode.c +++ b/game_mode.c @@ -3,10 +3,14 @@ #include "gobang.h" #include "ai.h" #include "record.h" +#include "config.h" #include #include #include #include + +// 引用record.h中定义的全局变量 +extern int scores_calculated; #ifdef _WIN32 #include #include @@ -216,6 +220,9 @@ bool handle_player_turn(int current_player) */ void run_ai_game() { + // 重置评分计算标志,确保每局游戏都会重新计算评分 + scores_calculated = 0; + setup_game_options(); // AI对战模式 @@ -298,6 +305,9 @@ void run_ai_game() */ void run_pvp_game() { + // 重置评分计算标志,确保每局游戏都会重新计算评分 + scores_calculated = 0; + setup_game_options(); // 双人对战模式 @@ -409,4 +419,8 @@ void run_review_mode() } review_process(game_mode); } + else + { + printf("加载复盘文件失败!可能是旧版本文件格式或文件损坏\n"); + } } \ No newline at end of file diff --git a/gobang.c b/gobang.c index e07862c..8bb6665 100644 --- a/gobang.c +++ b/gobang.c @@ -3,19 +3,20 @@ #include "gobang.h" #include "ai.h" #include "record.h" +#include "config.h" #include #include #include // 全局变量定义 -int BOARD_SIZE = 15; // 实际使用的棋盘尺寸(默认15) +int BOARD_SIZE = DEFAULT_BOARD_SIZE; // 实际使用的棋盘尺寸 int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; // 棋盘状态存储数组(默认棋盘全空为0) Step steps[MAX_STEPS]; // 存储所有落子步骤的数组 const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 四个方向:向下、向右、右下、左下 int step_count = 0; // 当前步数计数器 -bool use_forbidden_moves = false; // 默认不启用禁手规则 -int use_timer = 0; // 默认不启用计时器 -int time_limit = 30; // 默认时间限制为30秒 +bool use_forbidden_moves = DEFAULT_USE_FORBIDDEN_MOVES; // 是否启用禁手规则 +int use_timer = DEFAULT_USE_TIMER; // 是否启用计时器 +int time_limit = DEFAULT_TIME_LIMIT; // 每回合的时间限制(秒) /** * @brief 检查棋盘(x, y)位置是否为空 @@ -177,10 +178,6 @@ bool check_win(int x, int y, int player) return false; // 四个方向都没有五连珠 } - - - - /** * @brief 悔棋功能实现 * @@ -211,12 +208,12 @@ bool return_move(int steps_to_undo) * @brief 评估玩家在整盘棋局中的表现 * @param player 要评估的玩家(PLAYER/AI) * @return int 总分(已考虑方向重复计算) - * @note 评分标准: - * - 五连:2500 - * - 活四:1000 冲四:500 死四:250 - * - 活三:250 眠三:100 死三:50 - * - 活二:50 眠二:20 死二:10 - * - 开放单子:10 半开放单子:5 封闭单子:1 + * @note 改进后的评分标准: + * - 五连:5000 (提高权重,更强调获胜) + * - 活四:2000 冲四:1000 死四:300 (提高权重,强调进攻性) + * - 活三:500 眠三:200 死三:80 (提高权重,强调战略价值) + * - 活二:100 眠二:40 死二:15 (适当提高权重) + * - 开放单子:15 半开放单子:8 封闭单子:2 (适当提高权重) * @note 实现细节: * 1. 遍历棋盘所有位置 * 2. 对每个棋子检查四个方向 @@ -234,41 +231,48 @@ int calculate_step_score(int x, int y, int player) switch (info.continuous_chess) { case 5: - step_score += 2500; + step_score += SCORE_FIVE; break; // 五连 case 4: if (info.check_start && info.check_end) - step_score += 1000; // 活四 + step_score += SCORE_LIVE_FOUR; // 活四 else if (info.check_start || info.check_end) - step_score += 500; // 冲四 + step_score += SCORE_RUSH_FOUR; // 冲四 else - step_score += 250; // 死四 + step_score += SCORE_DEAD_FOUR; // 死四 break; case 3: if (info.check_start && info.check_end) - step_score += 250; // 活三 + step_score += SCORE_LIVE_THREE; // 活三 else if (info.check_start || info.check_end) - step_score += 100; // 眠三 + step_score += SCORE_SLEEP_THREE; // 眠三 else - step_score += 50; // 死三 + step_score += SCORE_DEAD_THREE; // 死三 break; case 2: if (info.check_start && info.check_end) - step_score += 50; // 活二 + step_score += SCORE_LIVE_TWO; // 活二 else if (info.check_start || info.check_end) - step_score += 20; // 眠二 + step_score += SCORE_SLEEP_TWO; // 眠二 else - step_score += 10; // 死二 + step_score += SCORE_DEAD_TWO; // 死二 break; case 1: if (info.check_start && info.check_end) - step_score += 10; // 开放单子 + step_score += SCORE_LIVE_ONE; // 开放单子 else if (info.check_start || info.check_end) - step_score += 5; // 半开放单子 + step_score += SCORE_HALF_ONE; // 半开放单子 else - step_score += 1; // 封闭单子 + step_score += SCORE_DEAD_ONE; // 封闭单子 break; } } - return step_score; + + // 位置奖励:越靠近中心分数越高 + int center_x = BOARD_SIZE / 2; + int center_y = BOARD_SIZE / 2; + int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离 + int position_bonus = POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高 + + return step_score + position_bonus; } \ No newline at end of file diff --git a/init_board.c b/init_board.c index 73412f0..15dceed 100644 --- a/init_board.c +++ b/init_board.c @@ -1,6 +1,7 @@ #include "init_board.h" #include "game_mode.h" #include "gobang.h" +#include "config.h" #include /** diff --git a/record.c b/record.c index 5b04d14..14dce19 100644 --- a/record.c +++ b/record.c @@ -2,6 +2,7 @@ #include "game_mode.h" #include "gobang.h" #include "init_board.h" +#include "config.h" #include #include #include @@ -26,11 +27,67 @@ * - 使用独立临时棋盘避免影响主游戏状态 * - 坐标显示转换为1-based方便用户理解 * - 包含输入缓冲区清理防止意外输入 - * - 评分环节调用evaluate_performance()函数 + * - 评分环节调用calculate_final_score()函数 */ + +// 全局变量,用于存储对局评分,确保对战结束和复盘模式使用相同的评分 +int player1_final_score = 0; +int player2_final_score = 0; +int scores_calculated = 0; + void review_process(int game_mode) { int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1); + + // 如果评分尚未计算,则计算评分 + if (!scores_calculated) + { + // 评估双方表现 + player1_final_score = 0; + player2_final_score = 0; + + // 遍历所有步数,累积每一步的得分,后期步骤权重更高 + for (int i = 0; i < step_count; i++) + { + // 计算时间权重因子:步数越靠后,权重越大 + double time_weight = 1.0 + (double)i / step_count * TIME_WEIGHT_FACTOR; // 最后的步骤权重是开始步骤的(1+TIME_WEIGHT_FACTOR)倍 + + if (steps[i].player == PLAYER || steps[i].player == PLAYER1) + { + player1_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight); + } + else + { + player2_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight); + } + } + + // 胜负加权:获胜方获得额外的评分奖励 + if (step_count > 0) + { + Step last_step = steps[step_count - 1]; + if (check_win(last_step.x, last_step.y, last_step.player)) + { + // 获胜方获得额外奖励分数 + if (last_step.player == PLAYER || last_step.player == PLAYER1) + { + player1_final_score += WIN_BONUS; // 获胜奖励 + } + else + { + player2_final_score += WIN_BONUS; // 获胜奖励 + } + } + } + + scores_calculated = 1; // 标记评分已计算 + } + else + { + // 评分已从文件中加载,直接使用 + printf("从记录文件中加载评分数据\n"); + } + if (review_choice == 1) { printf("\n===== 复盘记录(总步数:%d) =====\n", step_count); @@ -73,7 +130,9 @@ void review_process(int game_mode) // 打印当前复盘棋盘 printf(" "); for (int col = 0; col < BOARD_SIZE; col++) + { printf("%2d", col + 1); // 列号 + } printf("\n"); for (int row = 0; row < BOARD_SIZE; row++) @@ -109,65 +168,50 @@ void review_process(int game_mode) getchar(); // 等待用户按键 } - // 评估双方表现 + // 显示评分结果 printf("\n===== 对局评分 =====\n"); - int player1_score = 0, player2_score = 0; - - // 遍历所有步数,累积每一步的得分 - for (int i = 0; i < step_count; i++) - { - if (steps[i].player == PLAYER || steps[i].player == PLAYER1) - { - player1_score += calculate_step_score(steps[i].x, steps[i].y, steps[i].player); - } - else - { - player2_score += calculate_step_score(steps[i].x, steps[i].y, steps[i].player); - } - } - - double sum_score = (long double)player1_score + (long double)player2_score; + double sum_score = (long double)player1_final_score + (long double)player2_final_score; if (sum_score > 0) { if (game_mode == 1) { printf("玩家得分: %d, 占比: %.2f%%\n", - player1_score, (double)player1_score * 100.0 / sum_score); + player1_final_score, (double)player1_final_score * 100.0 / sum_score); printf("AI得分: %d, 占比: %.2f%%\n", - player2_score, (double)player2_score * 100.0 / sum_score); + player2_final_score, (double)player2_final_score * 100.0 / sum_score); } else { printf("玩家1(黑棋)得分: %d, 占比: %.2f%%\n", - player1_score, (double)player1_score * 100.0 / sum_score); + player1_final_score, (double)player1_final_score * 100.0 / sum_score); printf("玩家2(白棋)得分: %d, 占比: %.2f%%\n", - player2_score, (double)player2_score * 100.0 / sum_score); + player2_final_score, (double)player2_final_score * 100.0 / sum_score); } } else { if (game_mode == 1) { - printf("玩家得分: %d\n", player1_score); - printf("AI得分: %d\n", player2_score); + printf("玩家得分: %d\n", player1_final_score); + printf("AI得分: %d\n", player2_final_score); } else { - printf("玩家1(黑棋)得分: %d\n", player1_score); - printf("玩家2(白棋)得分: %d\n", player2_score); + printf("玩家1(黑棋)得分: %d\n", player1_final_score); + printf("玩家2(白棋)得分: %d\n", player2_final_score); } printf("注: 双方得分均为0,无法计算占比\n"); } // 评选MVP - if (player1_score > player2_score) + if (player1_final_score > player2_final_score) { - printf("\nMVP: %s (领先 %d 分)\n", (game_mode == 1) ? "玩家" : "玩家1(黑棋)", player1_score - player2_score); + printf("\nMVP: %s (领先 %d 分)\n", (game_mode == 1) ? "玩家" : "玩家1(黑棋)", player1_final_score - player2_final_score); } - else if (player2_score > player1_score) + else if (player2_final_score > player1_final_score) { - printf("\nMVP: %s (领先 %d 分)\n", (game_mode == 1) ? "AI" : "玩家2(白棋)", player2_score - player1_score); + printf("\nMVP: %s (领先 %d 分)\n", (game_mode == 1) ? "AI" : "玩家2(白棋)", player2_final_score - player1_final_score); } else { @@ -193,14 +237,15 @@ void handle_save_record(int game_mode) time_t now = time(NULL); struct tm *t = localtime(&now); char filename[256]; - strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.txt", t); + strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.csv", t); int save_status = save_game_to_file(filename, game_mode); switch (save_status) { case 0: // 成功 - printf("\n游戏记录已成功保存至: %s\n", filename); + printf("\n游戏记录已成功保存至: %s (CSV格式)\n", filename); printf("您可以使用以下命令进行复盘: .\\gobang.exe -l %s\n", filename); + printf("CSV格式文件可以直接用Excel打开查看和分析\n"); break; case 1: // 目录创建失败 printf("\n游戏记录保存失败: 无法创建 'records' 目录。\n"); @@ -264,17 +309,24 @@ int save_game_to_file(const char *filename, int game_mode) return 2; // 文件打开失败 } - // 写入游戏模式和棋盘大小 - if (fprintf(file, "%d\n%d\n", game_mode, BOARD_SIZE) < 0) + // 写入CSV文件头部 + if (fprintf(file, "游戏模式,棋盘大小,玩家1得分,玩家2得分\n%d,%d,%d,%d\n\n", game_mode, BOARD_SIZE, player1_final_score, player2_final_score) < 0) + { + fclose(file); + return 3; // 文件写入失败 + } + + // 写入CSV表头 + if (fprintf(file, "步数,玩家,行坐标,列坐标\n") < 0) { fclose(file); return 3; // 文件写入失败 } - // 写入所有落子步骤 + // 写入所有落子步骤(CSV格式) for (int i = 0; i < step_count; i++) { - if (fprintf(file, "%d %d %d\n", steps[i].player, steps[i].x, steps[i].y) < 0) + if (fprintf(file, "%d,%d,%d,%d\n", i+1, steps[i].player, steps[i].x+1, steps[i].y+1) < 0) { fclose(file); return 3; // 文件写入失败 @@ -306,18 +358,34 @@ int load_game_from_file(const char *filename) return false; } - // 读取游戏模式和棋盘大小 - int game_mode, size; - if (fscanf(file, "%d", &game_mode) != 1 || (game_mode != 1 && game_mode != 2)) + // 跳过CSV文件头部行 + char buffer[256]; + if (fgets(buffer, sizeof(buffer), file) == NULL) // 跳过"游戏模式,棋盘大小" { fclose(file); - return 0; // 无效的游戏模式 + return 0; } - if (fscanf(file, "%d", &size) != 1 || size < 5 || size > MAX_BOARD_SIZE) + + // 读取游戏模式、棋盘大小和评分结果 + int game_mode, size; + if (fscanf(file, "%d,%d,%d,%d", &game_mode, &size, &player1_final_score, &player2_final_score) != 4 || (game_mode != 1 && game_mode != 2)) + { + fclose(file); + return 0; // 无效的游戏模式或文件格式 + } + if (size < 5 || size > MAX_BOARD_SIZE) { fclose(file); return false; } + + // 设置评分已计算标志 + scores_calculated = 1; + + // 跳过空行和表头行 + fgets(buffer, sizeof(buffer), file); // 跳过换行 + fgets(buffer, sizeof(buffer), file); // 跳过空行 + fgets(buffer, sizeof(buffer), file); // 跳过"步数,玩家,行坐标,列坐标" // 初始化棋盘 BOARD_SIZE = size; @@ -325,8 +393,12 @@ int load_game_from_file(const char *filename) // 读取所有落子步骤 step_count = 0; - while (fscanf(file, "%d %d %d", &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 3) + int step_num; // 用于存储步数,但不使用 + while (fscanf(file, "%d,%d,%d,%d", &step_num, &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 4) { + // 将1-based坐标转换为0-based坐标 + steps[step_count].x--; + steps[step_count].y--; step_count++; } diff --git a/record.h b/record.h index 2644e10..0cf6d59 100644 --- a/record.h +++ b/record.h @@ -3,31 +3,36 @@ #include "gobang.h" -// --- 复盘与记录 --- +// ȫֱڴ洢Ծ֣ȷս͸ģʽʹͬ +extern int player1_final_score; +extern int player2_final_score; +extern int scores_calculated; + +// --- ¼ --- /** - * @brief 进入复盘流程,回顾整局游戏 - * @param game_mode 游戏模式(1为人机,2为双人) + * @brief 븴̣عϷ + * @param game_mode Ϸģʽ1Ϊ˻2Ϊ˫ˣ */ void review_process(int game_mode); /** - * @brief 将当前对局记录保存到文件 - * @param filename 要保存到的文件名 - * @param game_mode 游戏模式 - * @return 0表示成功,非0表示失败 + * @brief ǰԾּ¼浽ļ + * @param filename Ҫ浽ļ + * @param game_mode Ϸģʽ + * @return 0ʾɹ0ʾʧ */ int save_game_to_file(const char *filename, int game_mode); /** - * @brief 处理保存游戏记录的逻辑 - * @param game_mode 游戏模式 + * @brief Ϸ¼߼ + * @param game_mode Ϸģʽ */ void handle_save_record(int game_mode); /** - * @brief 从文件加载游戏记录 - * @param filename 要加载的文件名 - * @return 0表示成功,非0表示失败 + * @brief ļϷ¼ + * @param filename Ҫصļ + * @return 0ʾɹ0ʾʧ */ int load_game_from_file(const char *filename); diff --git a/五子棋.c b/五子棋.c index d69aeae..714d718 100644 --- a/五子棋.c +++ b/五子棋.c @@ -3,24 +3,30 @@ * @brief 五子棋游戏核心逻辑头文件 * @details 游戏核心逻辑实现 * @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com) - * @date 2025-07-02 + * @date 2025-07-07 * @version 4.0 * @note * 1. 新增功能: * - 增加了对禁手规则的支持,防止玩家进行无意义的走法。 * - 新增了游戏计时器功能,限制每回合的思考时间。 + * - 添加了复盘功能,支持保存和回顾对局记录。 + * - 实现了评分系统,可以对每一步棋进行评分和分析。 * 2. 性能优化: * - 优化了评估函数的性能,减少了不必要的计算。 * - 引入了 Alpha-Beta 剪枝算法,提高了 AI 搜索的效率。 + * - 改进了内存管理,减少了资源占用。 * 3. 用户界面改进: * - 新增了命令行界面,提供更友好的交互体验。 * - 可以自定义棋盘大小,增加游戏的灵活性。 + * - 优化了提示信息,使游戏操作更加直观。 * 4. 代码结构优化: * - 将游戏逻辑和用户界面分离,提高代码的可读性和可维护性。 * - 优化了代码结构,提高了代码的可读性和可维护性。 + * - 模块化设计,便于功能扩展和维护。 * 5. 异常处理: * - 增加了输入错误的异常处理机制,确保游戏的稳定性。 * - 修复了一些已知的 bug,提高游戏的稳定性。 + * - 增强了错误提示,帮助用户快速定位问题。 * 6. 文档更新: * - 完善了代码注释,提高了代码的可读性。 * - 更新了文档,包括功能描述、使用方法、注意事项等。