From bb252f4a4cb578954797f780a41de99e612d33b2 Mon Sep 17 00:00:00 2001 From: LHY0125 <3364451258@qq.com> Date: Thu, 3 Jul 2025 09:31:48 +0800 Subject: [PATCH] Add files via upload --- README.md | 206 ++++++++--------- ai.c | 241 ++++++++++---------- ai.h | 40 ++-- game_mode.c | 155 +++++++------ game_mode.h | 54 ++--- gobang.c | 627 ++++++++------------------------------------------- gobang.exe | Bin 85011 -> 86400 bytes gobang.h | 282 +++++++++-------------- init_board.c | 114 ++++++++++ init_board.h | 35 +++ record.c | 335 +++++++++++++++++++++++++++ record.h | 34 +++ 五子棋.c | 3 +- 13 files changed, 1078 insertions(+), 1048 deletions(-) create mode 100644 init_board.c create mode 100644 init_board.h create mode 100644 record.c create mode 100644 record.h diff --git a/README.md b/README.md index c0e2c1f..c06ef0e 100644 --- a/README.md +++ b/README.md @@ -1,159 +1,149 @@ -# ? 五子棋人机对战AI +# C˻սAI ![Build Status](https://img.shields.io/badge/build-passing-brightgreen) ![License](https://img.shields.io/badge/license-MIT-blue) -![Code Lines](https://img.shields.io/tokei/lines/github/your-username/your-repo) -## 目录 -- [? 五子棋人机对战AI](#-五子棋人机对战ai) - - [目录](#目录) - - [项目简介](#项目简介) - - [功能特性](#功能特性) - - [快速开始](#快速开始) - - [编译程序](#编译程序) - - [运行游戏](#运行游戏) - - [游戏玩法](#游戏玩法) - - [开发环境](#开发环境) - - [常见问题](#常见问题) - - [权限问题](#权限问题) - - [中文显示问题](#中文显示问题) - - [技术实现](#技术实现) - - [核心决策算法](#核心决策算法) - - [启发式评估函数](#启发式评估函数) - - [代码结构](#代码结构) - - [许可证](#许可证) - - [反馈与贡献](#反馈与贡献) - - [未来计划](#未来计划) +## Ŀ¼ +- [C˻սAI](#c˻սai) + - [Ŀ¼](#Ŀ¼) + - [Ŀ](#Ŀ) + - [](#) + - [ٿʼ](#ٿʼ) + - [Ŀ](#Ŀ) + - [Ϸ](#Ϸ) + - [Ϸ淨](#Ϸ淨) + - [Ҫ](#Ҫ) + - [](#) + - [Ȩ](#Ȩ) + - [ʾ](#ʾ) + - [AI ʵ](#ai-ʵ) + - [㷨](#㷨) + - [](#) + - [Ŀṹ](#Ŀṹ) + - [֤](#֤) + - [ӭ](#ӭ) + - [δƻ](#δƻ) -## 项目简介 -基于C语言实现的五子棋人机对战系统,采用α-β剪枝优化的极小极大算法,支持自定义棋盘大小、游戏复盘和实时评分。 +## Ŀ +һʹCʵֵ˻սϵͳ Alpha-Beta ֦Ż Minimax 㷨֧Զ̴СϷ浵ʵʱ塣 -## 功能特性 -- 人机对战模式 -- 可调棋盘尺寸(5x5到25x25) -- 智能AI决策(1-5级难度) -- 完整游戏复盘功能 -- 实时对局评分系统 -- 悔棋功能(可撤销上一步) -- 清晰的终端界面显示 -- 健壮的输入验证(确保所有数字输入都在有效范围内) -- 可选的回合计时器 -- 自动游戏记录保存 +## +- ?? ˻սģʽ +- chessboard Զ̳ߴ (5x525x25) +- ? ༶AIѶ (1-5ɵ) +- ? ԶϷ浵 +- ? ʵʱԾֿϵͳ +- ?? 幦 (ɳһ) +- ?? ն˽ʾ +- ?? 걸֤ (ȷû붼ЧΧ) +- ?? ѡĻغϼʱ +- ? ԶϷ¼ -## 快速开始 +## ٿʼ -![游戏演示](https://your-gif-url.com/demo.gif) - -### 编译程序 +### Ŀ ```bash -gcc 五子棋.c gobang.c game_mode.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()` 函数,因此仅限Windows平台) -- 编译器: GCC (MinGW(gcc为14.2.0) on Windows) -- 终端: 支持UTF-8编码的终端 +## Ҫ +- ϵͳ: Windows (ǰ汾ʹWindowsе `_kbhit()` `Sleep()` ݲƽ̨) +- : GCC (MinGW-w64) +- ն: ֧UTF-8ն -> **跨平台兼容性说明:** +> **ƽ̨˵:** > -> 为了未来能在Linux或macOS等其他操作系统上运行,需要将平台特定的代码(如 `_kbhit()`)替换为跨平台的实现,或使用条件编译(`#ifdef _WIN32`)进行隔离。 +> ΪδLinuxmacOSϵͳУҪƽ̨ضĴ루 `_kbhit()`滻Ϊƽ̨ʵ֣ʹ루`#ifdef _WIN32`и롣 -## 常见问题 +## -### 权限问题 -如果程序在保存游戏记录时提示“无法创建文件”或类似错误,通常是由于缺少写入权限。请尝试以下解决方案: +### Ȩ +ڱϷ¼ʱʾ޷ļͨڳȱдȨޡ볢½ -1. **以管理员身份运行**:右键点击 `五子棋.exe` 或在管理员权限的终端中运行程序。 -2. **检查目录权限**:确保程序所在目录不是系统保护目录(如 `C:\Program Files`)。建议将项目放在用户目录下(如 `D:\Code`)。 -3. **手动创建 `records` 目录**:如果 `records` 目录不存在,请在 `output` 目录下手动创建一个。 +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 @echo off chcp 65001 -.\output\五子棋.exe +.\gobang.exe ``` -## 技术实现 +## AI ʵ -项目的AI主要基于以下技术实现: +ĿAIҪ¼ʵ֣ -### 核心决策算法 +### 㷨 -- **极小极大算法 (Minimax)**:作为决策的基础,模拟对弈双方的每一步,选择对我方最有利的走法。 -- **α-β 剪枝 (Alpha-Beta Pruning)**:对极小极大算法的關鍵優化,通过剪掉不可能影响最终决策的搜索分支,大幅提升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.h` - 定义核心数据结构(如棋盘、步骤记录等)和基础函数原型 -- `gobang.c` - 实现基础的棋盘操作(如落子、显示棋盘等)和胜负判断 -- `ai.h` - 定义 AI 相关的函数原型 -- `ai.c` - 实现 AI 的核心算法,包括: - - `evaluate_pos`: 启发式评估函数,为每个可能的落子位置打分 - - `dfs`: 带 α-β 剪枝的极小极大搜索算法 - - `ai_move`: AI 决策的主函数,实现防守和进攻的两阶段决策 -- `game_mode.c` - 实现各种游戏模式(如人机对战、复盘等)和用户交互 -- `game_mode.h` - 定义游戏模式相关的函数原型 +Ŀ [MIT ֤](https://opensource.org/licenses/MIT)Ȩ -这种模块化的结构使得: -1. 代码职责划分更加清晰 -2. AI 算法相关代码被独立出来,便于维护和优化 -3. 游戏逻辑和 AI 逻辑解耦,有利于后续扩展(如添加新的 AI 算法或游戏模式) +ζɵʹáơ޸ġϲ桢ַȨ/۱ĸֻиҪаԭʼİȨͱɡ -## 许可证 +## ӭ -该项目采用 [MIT 许可证](https://opensource.org/licenses/MIT)进行授权。 +Ƿdzӭκʽķ͹ף㷢Bugйܽ飬ϣĽ룬ʱͨ·ʽ룺 -简单来说,你可以自由地使用、复制、修改、合并、出版、分发、再授权和/或销售本软件的副本,只需在你的项目中包含原始的版权和许可声明即可。 +- **ύ Issue**ⷴ뷨 [GitHub Issues](https://github.com/LHY0125/Gobang-Game/issues) ҳύϸ +- ** Pull Request**Դ˸Ľӭύ Pull RequestȷĴĿһ£ṩĸĶ˵ -## 反馈与贡献 +ÿһι׶ʹĿøã -我们非常欢迎任何形式的反馈和贡献!如果你发现了Bug、有功能建议,或者希望改进代码,请随时通过以下方式参与: +## δƻ -- **提交 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 a062205..8d49da8 100644 --- a/ai.c +++ b/ai.c @@ -1,8 +1,11 @@ +#include "gobang.h" #include "ai.h" #include #include #include +// 防守系数 +double defense_coefficient = 1.2; extern int BOARD_SIZE; extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; extern int step_count; @@ -10,145 +13,150 @@ extern const int direction[4][2]; extern Step steps[MAX_STEPS]; /** - * @brief ????????????????????????? - * ?????????????????????????????????????????????????? - * ???????????????????????????????? - * @param x ????????????? (0-based)?? - * @param y ????????????? (0-based)?? - * @param player ????? (PLAYER ?? AI)?????????????????????? - * @return int ??????????????????????? - * @note ?????????? - * - ??????????????????????M??????????????????????????? - * - ?????????????????\????????????????????\????????????????????\????????? - * ????????????????????? - * - ?????? (??????????????????AI???): - * - ????: 1,000,000 (???) - * - ????: 100,000 (????????) - * - ????: 10,000 (????????????) - * - ????: 5,000 (??????) - * - ????: 1,000 - * - ???: 500 - * - ???: 100 - * - ????: ???????? - * - ?????????????????????????????????????????????????????????AI???????? + * @brief 评估一个落子位置的综合得分(结合进攻和防守) + * @param x 行坐标 + * @param y 列坐标 + * @return int 综合得分 + */ +int evaluate_move(int x, int y) +{ + // 进攻得分:评估AI在此处落子的分数 + int attack_score = evaluate_pos(x, y, AI); + + // 防守得分:评估玩家在此处落子的分数,作为防守的依据 + int defense_score = evaluate_pos(x, y, PLAYER); + + // 综合得分,防守权重由 defense_coefficient 控制 + return attack_score + (int)(defense_score * defense_coefficient); +} + +/** + * @brief 评估特定位置对当前玩家的战略价值 + * @param x 行坐标(0-base) + * @param y 列坐标(0-base) + * @param player 玩家标识(PLAYER/AI) + * @return int 综合评估分数(越高表示位置越好) + * @note 评分标准: + * - 活四:100000 冲四:10000 死四:500 + * - 活三:5000 眠三:1000 死三:50 + * - 活二:500 眠二:100 死二:10 + * - 单子:50(开放)/10(半开放)/1(封闭) + * - 中心位置有额外加成 */ int evaluate_pos(int x, int y, int player) { - // ????????????? + // 保存原始值用于还原 int original = board[x][y]; - // ?????????????? + // 模拟在该位置落子 board[x][y] = player; - int total_score = 0; // ??? - int line_scores[4] = {0}; // ??????????? + int total_score = 0; // 总分 + int line_scores[4] = {0}; // 四个方向的得分 - // ?????????????????? + // 遍历四个方向进行评估 for (int i = 0; i < 4; i++) { int dx = direction[i][0], dy = direction[i][1]; - // ???????????????????? + // 获取当前方向上的棋型信息 DirInfo info = count_specific_direction(x, y, dx, dy, player); - // ????????????????? + // 直接形成五连珠为必胜 if (info.continuous_chess >= 5) { - board[x][y] = original; // ??????? - return 1000000; // ???????? + board[x][y] = original; // 还原棋盘 + return 1000000; // 返回最大分 } - // ?????????????????? + // 根据连续棋子数评分 switch (info.continuous_chess) { - case 4: // ?????? - if (info.check_start && info.check_end) // ????(???????) + case 4: // 四连珠 + if (info.check_start && info.check_end) // 活四(两端开放) line_scores[i] = 100000; - else if (info.check_start || info.check_end) // ????(??????) + else if (info.check_start || info.check_end) // 冲四(一端开放) line_scores[i] = 10000; - else // ????(??????) + else // 死四(两端封闭) line_scores[i] = 500; break; - case 3: // ?????? - if (info.check_start && info.check_end) // ???? + case 3: // 三连珠 + if (info.check_start && info.check_end) // 活三 line_scores[i] = 5000; - else if (info.check_start || info.check_end) // ???? + else if (info.check_start || info.check_end) // 眠三 line_scores[i] = 1000; - else // ???? + else // 死三 line_scores[i] = 50; break; - case 2: // ?????? - if (info.check_start && info.check_end) // ??? + case 2: // 二连珠 + if (info.check_start && info.check_end) // 活二 line_scores[i] = 500; - else if (info.check_start || info.check_end) // ??? + else if (info.check_start || info.check_end) // 眠二 line_scores[i] = 100; - else // ???? + else // 死二 line_scores[i] = 10; break; - case 1: // ???? - if (info.check_start && info.check_end) // ???????? + case 1: // 单子 + if (info.check_start && info.check_end) // 开放位置 line_scores[i] = 50; - else if (info.check_start || info.check_end) // ???????? + else if (info.check_start || info.check_end) // 半开放位置 line_scores[i] = 10; - else // ??????? + else // 封闭位置 line_scores[i] = 1; break; } } - // ???????????????+????????????? + // 计算总分(最高方向分+其他方向分加权) int max_score = 0; int sum_score = 0; for (int i = 0; i < 4; i++) { if (line_scores[i] > max_score) + { max_score = line_scores[i]; + } sum_score += line_scores[i]; } - total_score = max_score * 10 + sum_score; // ???????????? + total_score = max_score * 10 + sum_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 = 50 * (BOARD_SIZE - distance); // ?????????????????? + int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离 + int position_bonus = 50 * (BOARD_SIZE - distance); // 距离中心越近奖励越高 - board[x][y] = original; // ????????? - return total_score + position_bonus; // ???????????? + board[x][y] = original; // 还原棋盘状态 + return total_score + position_bonus; // 返回总评估分 } /** - * @brief ??????-???????????????????Minimax????????????????? - * ?????????????????????????????????????????????????????? - * @param x ???????????????? - * @param y ???????????????? - * @param player ??????????? (PLAYER ?? AI)?? - * @param depth ??????????????????AI???????????????????? - * @param alpha ?????????????AI????????????????????????? - * @param beta ??????????????????????????????????????????? - * @param is_maximizing ???????true???????????????AI???????false???????????????????????? - * @return int ??????????????????????????? - * @note ?????????: - * 1. **??????????**: ????????????????????????????????????????????????????????????????? - * 2. **??????? (AI)**: ??????????????????????????????????????????????????????????alpha??? - * 3. **????????? (????)**: ????????????????????????????AI???????????????????????????????????beta??? - * 4. **??-????**: ?????Minimax??????????? - * - **?????**: ????????????????????????????????????????alpha????????????????????????? - * ??????????????????????????????????????????(if beta <= alpha) - * - **????**: ??????????ah?????????????????????????????beta?????????????????????????? - * ???????????????????????????????????????????????????????(if beta <= alpha) - * ????????????????????????????????????????????AI?????????? + * @brief 带α-β剪枝的深度优先搜索(极小极大算法实现) + * @param x 当前行坐标 + * @param y 当前列坐标 + * @param player 当前玩家 + * @param depth 剩余搜索深度 + * @param alpha α值(当前最大值) + * @param beta β值(当前最小值) + * @param is_maximizing 是否为极大化玩家(AI) + * @return int 最佳评估分数 + * @note 算法流程: + * 1. 检查是否获胜或达到搜索深度 + * 2. 遍历所有可能落子位置 + * 3. 递归评估每个位置的分数 + * 4. 根据is_maximizing选择最大/最小值 + * 5. 使用α-β剪枝优化搜索过程 */ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing) { - // ???n?????????? + // 检查当前落子是否获胜 if (check_win(x, y, player)) { return (player == AI) ? 1000000 + depth : -1000000 - depth; } - // ????????????? + // 达到搜索深度或平局 if (depth == 0 || step_count >= BOARD_SIZE * BOARD_SIZE) { return evaluate_pos(x, y, AI) - evaluate_pos(x, y, PLAYER); @@ -156,42 +164,44 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi int best_score = is_maximizing ? -1000000 : 1000000; - // ???????????????????? + // 遍历所有可能落子位置 for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (board[i][j] != EMPTY) + { continue; + } - // ??????????? + // 模拟当前玩家落子 board[i][j] = player; step_count++; - // ???????(???????????????) + // 递归搜索(切换玩家和搜索深度) int current_score = dfs(i, j, (player == AI) ? PLAYER : AI, depth - 1, alpha, beta, !is_maximizing); - // ???????? + // 撤销落子 board[i][j] = EMPTY; step_count--; - // ????????(AI)??? + // 极大值玩家(AI)逻辑 if (is_maximizing) { best_score = (current_score > best_score) ? current_score : best_score; alpha = (best_score > alpha) ? best_score : alpha; - // ????? + // α剪枝 if (beta <= alpha) { break; } } - // ????????(????)??? + // 极小值玩家(人类)逻辑 else { best_score = (current_score < best_score) ? current_score : best_score; beta = (best_score < beta) ? best_score : beta; - // ???? + // β剪枝 if (beta <= alpha) { break; @@ -200,7 +210,7 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi } if ((is_maximizing && best_score >= beta) || (!is_maximizing && best_score <= alpha)) { - break; // ???????????? + break; // 提前退出外层循环 } } @@ -208,42 +218,44 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi } /** - * @brief AI????????????????????????????????????????????? - * @note ?????????????????? - * 1. ???????????????????????????????????????????????? - * 2. ???????????????????????????DFS????????????????? - * @note ??????? - * - ?????????????????????????? - * - ????>10??????????????????????????2?? - * - ?????????????????? + * @brief AI决策主函数,使用评估函数和搜索算法选择最佳落子位置 + * @note 采用两阶段决策逻辑: + * 1. 防御阶段:检查并阻止玩家即将获胜的位置(活四、冲四、活三) + * 2. 进攻阶段:若无紧急防御需求,使用评估函数选择最佳进攻位置 + * @note 实现细节: + * - 优先处理玩家活四、冲四等危险局面 + * - 步数>10时缩小搜索范围到已有棋子附近2格 + * - 使用中心位置优先策略 */ void ai_move(int depth) { - // 1. ??????????????????????????????????? + // 1. 首先检查是否需要阻止玩家的四子连棋或三子活棋 for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (board[i][j] != EMPTY) + { continue; + } - // ????????????????? + // 模拟玩家在此位置落子 board[i][j] = PLAYER; bool need_block = false; - // ?????????? + // 检查四个方向 for (int k = 0; k < 4; k++) { DirInfo info = count_specific_direction(i, j, direction[k][0], direction[k][1], PLAYER); - // ???????????????????????????????? + // 如果玩家能形成四子连棋且至少一端开放 if (info.continuous_chess >= 4 && (info.check_start || info.check_end)) { need_block = true; break; } - // ???????????????????????????? + // 如果玩家能形成三子活棋且两端开放 if (info.continuous_chess == 3 && info.check_start && info.check_end) { need_block = true; @@ -251,32 +263,34 @@ void ai_move(int depth) } } - board[i][j] = EMPTY; // ??????? + board[i][j] = EMPTY; // 恢复棋盘 if (need_block) { - // ?????????????????? + // 必须在此位置落子阻止 board[i][j] = AI; steps[step_count++] = (Step){AI, i, j}; - printf("AI????(%d, %d)\n", i + 1, j + 1); + printf("AI落子(%d, %d)\n", i + 1, j + 1); return; } } } - // 2. ????????????????????????????????? + // 2. 如果没有需要立即阻止的情况,则正常评估 int best_score = -1000000; int best_x = -1, best_y = -1; - // ???????????????? + // 遍历棋盘所有空位 for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (board[i][j] != EMPTY) + { continue; + } - // ????????????????(2??????) + // 只考虑已有棋子附近(2格范围内) bool has_nearby_stone = false; for (int di = -2; di <= 2; di++) { @@ -284,8 +298,7 @@ void ai_move(int depth) { int ni = i + di; int nj = j + dj; - if (ni >= 0 && ni < BOARD_SIZE && - nj >= 0 && nj < BOARD_SIZE) + if (ni >= 0 && ni < BOARD_SIZE && nj >= 0 && nj < BOARD_SIZE) { if (board[ni][nj] != EMPTY) { @@ -295,17 +308,19 @@ void ai_move(int depth) } } if (has_nearby_stone) + { break; + } } if (!has_nearby_stone && step_count > 10) + { continue; + } - // ???AI???? - board[i][j] = AI; - int current_score = dfs(i, j, PLAYER, depth, -1000000, 1000000, false); - board[i][j] = EMPTY; + // 使用评估函数获取综合得分 + int current_score = evaluate_move(i, j); - // ??????????? + // 更新最佳位置 if (current_score > best_score) { best_score = current_score; @@ -315,11 +330,11 @@ void ai_move(int depth) } } - // ?????????? + // 执行最佳落子 if (best_x != -1 && best_y != -1) { board[best_x][best_y] = AI; steps[step_count++] = (Step){AI, best_x, best_y}; - printf("AI????(%d, %d)\n", best_x + 1, best_y + 1); + printf("AI落子(%d, %d)\n", best_x + 1, best_y + 1); } } \ No newline at end of file diff --git a/ai.h b/ai.h index 6fde53c..03340a6 100644 --- a/ai.h +++ b/ai.h @@ -3,31 +3,39 @@ #include "gobang.h" +// 防守系数 +extern double defense_coefficient; + /** - * @brief ???????????????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @param player ?????(PLAYER/AI) - * @return int ????????????(??????) + * @brief 评估一个落子位置的综合得分(结合进攻和防守) + * + * @param x 行坐标 + * @param y 列坐标 + * @return int 综合得分 + */ +int evaluate_move(int x, int y); + +/** + * @brief 评估指定位置的价值 + * + * @param x 位置x坐标 + * @param y 位置y坐标 + * @param player 玩家标识(PLAYER/AI) + * @return int 位置价值 */ int evaluate_pos(int x, int y, int player); /** - * @brief ????-?????????????????(??????????) - * @param x ????????? - * @param y ????????? - * @param player ?????? - * @param depth ??????? - * @param alpha ???(???????) - * @param beta ???(????????) - * @param is_maximizing ??????????? - * @return int ??????????? + * @brief 评估棋盘价值 + * + * @param player 玩家标识(PLAYER/AI) */ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing); /** - * @brief AI?????????? - * ????????????????????????????????? + * @brief AI下棋 + * + * @param depth */ void ai_move(int depth); diff --git a/game_mode.c b/game_mode.c index cfc1e5e..dc73c94 100644 --- a/game_mode.c +++ b/game_mode.c @@ -1,6 +1,8 @@ #include "game_mode.h" +#include "init_board.h" #include "gobang.h" #include "ai.h" +#include "record.h" #include #include #include @@ -12,12 +14,12 @@ #endif /** - * @brief ???????????????? + * @brief 从用户获取整数输入 * - * @param prompt ?????? - * @param min ????? - * @param max ???? - * @return int ???????????? + * @param prompt 提示信息 + * @param min 最小值 + * @param max 最大值 + * @return int 用户输入的整数 */ int get_integer_input(const char *prompt, int min, int max) { @@ -32,23 +34,23 @@ int get_integer_input(const char *prompt, int min, int max) if (result == 1 && value >= min && value <= max) { - // ???????????????????? + // 清除输入缓冲区中剩余的字符 while ((ch = getchar()) != '\n' && ch != EOF) ; return value; } else { - // ??????????? + // 清除无效输入 while ((ch = getchar()) != '\n' && ch != EOF) ; - printf("??????????????????????? %d ?? %d ??????????\n", min, max); + printf("输入无效,请输入一个介于 %d 和 %d 之间的整数。\n", min, max); } } } /** - * @brief ????????? + * @brief 处理玩家回合 * * @param current_player * @return true @@ -65,67 +67,77 @@ bool parse_player_input(int *x, int *y) scanf("%s", input); break; } - Sleep(100); + Sleep(100); // 短暂延迟以防止CPU占用过高 } if (sscanf(input, "%d", x) == 1) { + // 成功解析第一个数字,现在解析第二个 if (scanf("%d", y) != 1) { + // 如果第二个数字不可用,则检查特殊命令 if (*x == INPUT_UNDO) { int steps_to_undo; - printf("???????????????(??????????): "); + printf("请输入要悔棋的步数(双方各退一步): "); steps_to_undo = get_integer_input("", 1, step_count / 2); if (return_move(steps_to_undo * 2)) { - printf("??????????????? %d ????\n", steps_to_undo); + printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo); print_board(); } else { - printf("????????\n"); + printf("无法悔棋!\n"); } - return false; + return false; // 特殊命令 } else if (*x == INPUT_SAVE) { - return false; + // ... 处理保存 ... + return false; // 特殊命令 } else if (*x == INPUT_EXIT) { - return false; + // ... 处理退出 ... + return false; // 特殊命令 } - printf("??????????????????????????"); + printf("无效输入,请输入两个数字坐标。"); while (getchar() != '\n') ; - return false; + return false; // 无效输入 } } else { + // sscanf失败,检查'r'或'R' if (input[0] == 'r' || input[0] == 'R') { int steps_to_undo; - printf("???????????????(??????????): "); + printf("请输入要悔棋的步数(双方各退一步): "); steps_to_undo = get_integer_input("", 1, step_count / 2); if (return_move(steps_to_undo * 2)) { - printf("??????????????? %d ????\n", steps_to_undo); + printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo); print_board(); } else { - printf("????????\n"); + printf("无法悔棋!\n"); } - return false; + return false; // 特殊命令 } - printf("???????????????????????'r'????^"); - return false; + printf("无效输入,请输入数字坐标或'r'悔棋。"); + return false; // 无效输入 } - return true; + return true; // 有效坐标 } +/** + * @brief 处理玩家回合 + * @param current_player 当前玩家 + * @return true + */ bool handle_player_turn(int current_player) { int x, y; @@ -135,7 +147,7 @@ bool handle_player_turn(int current_player) time(&start_time); } - printf("\n???%d, ??????????????(?? ????1~%d)????????R/r????:", current_player, BOARD_SIZE); + printf("\n玩家%d, 请输入落子坐标(行 列,1~%d),或输入R/r悔棋:", current_player, BOARD_SIZE); while (1) { @@ -144,17 +156,18 @@ bool handle_player_turn(int current_player) time(&end_time); if (difftime(end_time, start_time) > time_limit) { - printf("\n???%d???, ????????\n", current_player); - return false; + printf("\n玩家%d超时, 对方获胜!\n", current_player); + return false; // Timeout } } if (parse_player_input(&x, &y)) { - break; + break; // 收到有效输入 } else { + // 已处理特殊命令或无效输入,继续循环 return true; } } @@ -164,33 +177,43 @@ bool handle_player_turn(int current_player) if (!player_move(x, y, current_player)) { - printf("????????????????????\n"); - return true; // ?????????????????? + printf("坐标无效!请重新输入。\n"); + return true; // 坐标无效,但回合继续 } print_board(); if (check_win(x, y, current_player)) { - printf("\n???%d?????\n", current_player); - return false; // ??????? + printf("\n玩家%d获胜!\n", current_player); + return false; // 游戏结束 } - return true; // ??????? + return true; // 成功落子 } /** - * @brief ????AI??? - * @note ??????????????????????????? - * @param AI_DEPTH AI????????? + * @brief 运行AI游戏 + * @note 从文件中加载历史记录并进行复盘 + * @param AI_DEPTH AI的搜索深度 */ void run_ai_game() { setup_game_options(); - // AI????? + // AI对战模式 setup_board_size(); int AI_DEPTH = 3; - AI_DEPTH = get_integer_input("?????AI???(1~5), ?????????????????????AI???????????):", 1, 5); + AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5); + + /** + * @brief AI的防守系数,系数越大越倾向于防守 + * @note 1~1.2 + * 2~1.3 + * 3~1.4 + * 4~1.5 + * 5~1.6 + */ + defense_coefficient = 1.2 + (AI_DEPTH - 1) * 0.1; empty_board(); int current_player = determine_first_player(PLAYER, AI); @@ -203,7 +226,7 @@ void run_ai_game() int old_step_count = step_count; if (!handle_player_turn(current_player)) { - break; // ?????????? + break; // 游戏结束或超时 } if (step_count > old_step_count) { @@ -212,7 +235,7 @@ void run_ai_game() } else { - printf("\nAI?????...\n"); + printf("\nAI思考中...\n"); time_t start_time, end_time; if (use_timer) { @@ -226,7 +249,7 @@ void run_ai_game() time(&end_time); if (difftime(end_time, start_time) > time_limit) { - printf("\nAI???, ???????\n"); + printf("\nAI超时, 玩家获胜!\n"); break; } } @@ -234,7 +257,7 @@ void run_ai_game() Step last_step = steps[step_count - 1]; if (check_win(last_step.x, last_step.y, AI)) { - printf("\nAI?????\n"); + printf("\nAI获胜!\n"); break; } current_player = PLAYER; @@ -242,29 +265,24 @@ void run_ai_game() if (step_count == BOARD_SIZE * BOARD_SIZE) { - printf("\n????\n"); + printf("\n平局!\n"); break; } } - printf("===== ??????? =====\n"); - int review_choice; - review_choice = get_integer_input("??????????????? (1-??, 0-??): ", 0, 1); - if (review_choice == 1) - { - review_process(1); // 1 for AI mode - } + printf("===== 游戏结束 =====\n"); + review_process(1); // 1 for AI mode handle_save_record(1); // 1 for AI mode } /** - * @brief ??????????? - * @note ??????????????????????????? + * @brief 运行双人对战模式 + * @note 从文件中加载历史记录并进行复盘 */ void run_pvp_game() { setup_game_options(); - // ??????? + // 双人对战模式 setup_board_size(); empty_board(); int current_player = determine_first_player(PLAYER1, PLAYER2); @@ -275,12 +293,12 @@ void run_pvp_game() int old_step_count = step_count; if (!handle_player_turn(current_player)) { - break; // ?????????? + break; // 游戏结束或超时 } if (step_count == BOARD_SIZE * BOARD_SIZE) { - printf("\n????\n"); + printf("\n平局!\n"); break; } @@ -289,19 +307,14 @@ void run_pvp_game() current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1; } } - printf("===== ??????? =====\n"); - int review_choice; - review_choice = get_integer_input("??????????????? (1-??, 0-??): ", 0, 1); - if (review_choice == 1) - { - review_process(2); // 2 for PvP mode - } + printf("===== 游戏结束 =====\n"); + review_process(2); // 2 for PvP mode handle_save_record(2); // 2 for PvP mode } /** - * @brief ?????????? - * @note ??????????????????????????? + * @brief 运行复盘模式 + * @note 从文件中加载历史记录并进行复盘 */ void run_review_mode() { @@ -327,14 +340,14 @@ void run_review_mode() if (file_count > 0) { - printf("??????????????:\n"); + printf("发现以下复盘文件:\n"); for (int i = 0; i < file_count; i++) { printf("%d. %s\n", i + 1, record_files[i]); } char prompt[150]; - sprintf(prompt, "??????????????(1-%d)????????0??????????????: ", file_count); + sprintf(prompt, "请输入复盘文件编号(1-%d),或输入0以手动输入文件名: ", file_count); int choice = get_integer_input(prompt, 0, file_count); if (choice > 0) @@ -343,7 +356,7 @@ void run_review_mode() } else { - printf("???????????????: "); + printf("请输入完整文件名: "); scanf("%s", filename); int c; while ((c = getchar()) != '\n' && c != EOF) @@ -358,7 +371,7 @@ void run_review_mode() } else { - printf("???????????????????????????????: "); + printf("未找到任何复盘文件,请输入复盘文件地址: "); scanf("%s", filename); int c; while ((c = getchar()) != '\n' && c != EOF) @@ -370,11 +383,11 @@ void run_review_mode() { if (game_mode == 1) { - printf("????AI?????????????????\n"); + printf("加载AI对战模式复盘文件成功!\n"); } else if (game_mode == 2) { - printf("???????????????????????\n"); + printf("加载双人对战模式复盘文件成功!\n"); } review_process(game_mode); } diff --git a/game_mode.h b/game_mode.h index fb32ba6..1f51696 100644 --- a/game_mode.h +++ b/game_mode.h @@ -1,16 +1,16 @@ /** * @file game_mode.h - * @author ??????(3364451258@qq.com??15236416560@163.com??lhy3364451258@outlook.com) - * @brief ???????????????? - * @version 3.0 - * @date 2025-06-30 + * @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com) + * @brief 五子棋游戏框架头文件 + * @version 4.0 + * @date 2025-07-02 * * @copyright Copyright (c) 2025 * - * @note ????????????????????????????????? - * 1. AI????? - * 2. ??????? - * 3. ?????? + * @note 本文件定义了五子棋游戏的三种主要模式: + * 1. AI对战模式 + * 2. 双人对战模式 + * 3. 复盘模式 */ #ifndef GAME_MODE_H @@ -18,53 +18,53 @@ #include "gobang.h" -// ???????????? +// 特殊输入命令 #define INPUT_UNDO -1 #define INPUT_SAVE -2 #define INPUT_EXIT -3 /** - * @brief ???????????????? + * @brief 从用户获取整数输入 * - * @param prompt ?????? - * @param min ????? - * @param max ???? - * @return int ????????? + * @param prompt 提示信息 + * @param min 最小值 + * @param max 最大值 + * @return int 输入的整数 */ int get_integer_input(const char *prompt, int min, int max); /** - * @brief ????????? + * @brief 处理玩家回合 * - * @param x ????????????? - * @param y ?????????????? - * @return true ???????? - * @return false ???????? + * @param x 玩家输入的横坐标 + * @param y 玩家输入的纵坐标 + * @return true 输入有效 + * @return false 输入无效 */ bool parse_player_input(int *x, int *y); /** - * @brief ????AI??? + * @brief 处理AI回合 * - * @param current_player ?????? + * @param current_player 当前玩家 */ bool handle_player_turn(int current_player); /** - * @brief AI????? - * ????????AI??????? + * @brief AI对战模式 + * 实现玩家与AI的对战逻辑 */ void run_ai_game(); /** - * @brief ??????? - * ??????????????????? + * @brief 双人对战模式 + * 实现两个玩家之间的对战逻辑 */ void run_pvp_game(); /** - * @brief ?????? - * ??????????????? + * @brief 复盘模式 + * 加载并重现历史对局 */ void run_review_mode(); diff --git a/gobang.c b/gobang.c index 3fcdb16..e07862c 100644 --- a/gobang.c +++ b/gobang.c @@ -1,138 +1,36 @@ -#include "gobang.h" #include "game_mode.h" +#include "init_board.h" +#include "gobang.h" #include "ai.h" +#include "record.h" #include #include #include -// ?????????? -int BOARD_SIZE = 15; // ?????????????(???15) -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?? +// 全局变量定义 +int BOARD_SIZE = 15; // 实际使用的棋盘尺寸(默认15) +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秒 /** - * @brief ??????????????????????????????? - * ?????????????????????????EMPTY??????step_count?????0 - */ -void empty_board() -{ - // ??????????????? - for (int i = 0; i < BOARD_SIZE; i++) - { - for (int j = 0; j < BOARD_SIZE; j++) - { - board[i][j] = EMPTY; - } - } - step_count = 0; // ?????????????? -} - -/** - * @brief ???????????? - * ???????????????????????????????? - * ???????????'x'??AI????????'??'??????????'??' - */ -void print_board() -{ - // ????????1-BOARD_SIZE????? - printf("\n "); - for (int i = 0; i < BOARD_SIZE; i++) - { - printf("%2d", i + 1); - if (i + 1 == 9) // ????????9??10+????? - printf(" "); - } - printf("\n"); - - // ??????????????? - for (int i = 0; i < BOARD_SIZE; i++) - { - printf("%2d ", i + 1); // ????????1-BOARD_SIZE?? - for (int j = 0; j < BOARD_SIZE; j++) - { - if (board[i][j] == PLAYER) - printf("x "); // ??????? - else if (board[i][j] == AI) - printf("?? "); // AI????(????????) - else - printf("?? "); // ???? - } - printf("\n"); // ??????????? - } -} - -/** - * @brief ?????????????????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @return true ????????????? - * @return false ???????????????? + * @brief 检查棋盘(x, y)位置是否为空 + * @param x 行坐标(0-base) + * @param y 列坐标(0-base) + * @return true-空, false-非空 */ bool have_space(int x, int y) { - // ???????????????????????????? - return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY); + return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY; } +// 函数定义 /** - * @brief ??????????? - * - * @param player1 ???1 - * @param player2 ???2 - */ -void setup_board_size() -{ - printf("?????????????????????(13X13)?????????(15X15)??????????(19X19)\n"); - char prompt[100]; - sprintf(prompt, "?????????????(5~%d)(???????????):\n", MAX_BOARD_SIZE); - BOARD_SIZE = get_integer_input(prompt, 5, MAX_BOARD_SIZE); -} - -/** - * @brief Set the up game options object - * ?????????????????????????????????? - */ -void setup_game_options() -{ - use_forbidden_moves = get_integer_input("????????????? (1-??, 0-??): ", 0, 1); - - use_timer = get_integer_input("???????????? (1-??, 0-??): ", 0, 1); - if (use_timer) - { - time_limit = get_integer_input("?????????????????? (1~60????): ", 1, 60) * 60; - } -} - -/** - * @brief ?????????? - * - * @param player1 - * @param player2 - * @return int player1 or player2 - */ -int determine_first_player(int player1, int player2) -{ - char prompt[100]; - sprintf(prompt, "?????????? (1 for Player %d, 2 for Player %d): ", player1, player2); - int first_player_choice = get_integer_input(prompt, 1, 2); - if (first_player_choice == 1) - { - return player1; - } - else - { - return player2; - } -} - -/** - * @brief ??????????? + * @brief 检查是否为禁手 * * @param x * @param y @@ -163,7 +61,7 @@ bool is_forbidden_move(int x, int y, int player) if (info.continuous_chess > 5) { board[x][y] = EMPTY; - return true; // ???????? + return true; // 长连禁手 } if (info.continuous_chess == 3 && info.check_start && info.check_end) { @@ -179,308 +77,116 @@ bool is_forbidden_move(int x, int y, int player) if (three_count >= 2 || four_count >= 2) { - return true; // ????????????? + return true; // 三三或四四禁手 } return false; } /** - * @brief ????????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @return true ?????? - * @return false ???????(????????) + * @brief 执行玩家落子操作 + * @param x 行坐标(0-base) + * @param y 列坐标(0-base) + * @return true 落子成功 + * @return false 落子失败(位置无效) */ bool player_move(int x, int y, int player) { - // ???????????false + // 位置无效则返回false if (!have_space(x, y)) return false; if (is_forbidden_move(x, y, player)) { - printf("????????????????????\n"); + printf("禁手!请选择其他位置。\n"); return false; } - // ?????????? + // 更新棋盘状态 board[x][y] = player; - // ??????????s??????????? + // 记录落子步骤:玩家标识和坐标 steps[step_count++] = (Step){player, x, y}; return true; } /** - * @brief ??????????????????????????? - * @param x ????????? - * @param y ????????? - * @param dx ??????????(-1,0,1) - * @param dy ??????????(-1,0,1) - * @param player ?????(PLAYER/AI) - * @return DirInfo ??????????????????????????? - * @note ???????????????????????????????????????? + * @brief 计算特定方向上连续同色棋子数量 + * @param x 起始行坐标 + * @param y 起始列坐标 + * @param dx 行方向增量(-1,0,1) + * @param dy 列方向增量(-1,0,1) + * @param player 玩家标识(PLAYER/AI) + * @return DirInfo 包含连续棋子数和方向开放状态的结构体 + * @note 检查正反两个方向,统计连续棋子数并判断端点是否开放 */ DirInfo count_specific_direction(int x, int y, int dx, int dy, int player) { DirInfo info; - info.continuous_chess = 1; // ??????????????????? - info.check_start = false; // ????????? - info.check_end = false; // ????????? + info.continuous_chess = 1; // 起始位置已经有一个棋子 + info.check_start = false; // 起点方向是否开放 + info.check_end = false; // 终点方向是否开放 - // ?????????dx, dy?? + // 检查正方向(dx, dy) int nx = x + dx, ny = y + dy; while (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] == player) { - info.continuous_chess++; // ??????????????? - nx += dx; // ??????????? + info.continuous_chess++; // 连续棋子计数增加 + nx += dx; // 沿当前方向前进 ny += dy; } - // ??????????????????????????? + // 判断正方向端点是否开放(遇到空位) if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE) + { if (board[nx][ny] == EMPTY) + { info.check_end = true; + } + } - // ????????-dx, -dy?? + // 检查反方向(-dx, -dy) nx = x - dx, ny = y - dy; while (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] == player) { - info.continuous_chess++; // ??????????????? - nx -= dx; // ??????????? + info.continuous_chess++; // 连续棋子计数增加 + nx -= dx; // 沿相反方向前进 ny -= dy; } - // ?????????????????????????? + // 判断反方向端点是否开放(遇到空位) if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE) + { if (board[nx][ny] == EMPTY) + { info.check_start = true; + } + } return info; } bool check_win(int x, int y, int player) { - // ?????????????????????? + // 检查四个方向是否存在五连珠 for (int i = 0; i < 4; i++) { DirInfo info = count_specific_direction(x, y, direction[i][0], direction[i][1], player); - if (info.continuous_chess >= 5) // ????????>=5????? + if (info.continuous_chess >= 5) // 连续棋子>=5即获胜 { return true; } } - return false; // ???????????????? + return false; // 四个方向都没有五连珠 } -/** - * @brief ??????????????????? - * @note ???????: - * 1. ???????????????? - * 2. ?????????????????????? - * 3. ??????: - * - ???????/????? - * - ?????(???/AI) - * - ????????(1-based????) - * - ????????? - * 4. ????????Enter???????????? - * 5. ???????????????????????: - * - ??????????? - * - ??????? - * - ???MVP - * @note ???????: - * - ?????????????????????????? - * - ???????????1-based??????????? - * - ??????????????????????????? - * - ??????????evaluate_performance()???? - */ -void review_process(int game_mode) -{ - printf("\n===== ??????(???????%d) =====\n", step_count); - // ??????????? - int c; - while ((c = getchar()) != '\n' && c != EOF) - ; - // ??????????????? - int temp_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; - memset(temp_board, EMPTY, sizeof(temp_board)); // ???????????? - // ????????????? - for (int i = 0; i < step_count; i++) - { - Step s = steps[i]; // ?????????? - temp_board[s.x][s.y] = s.player; // ??????????????? - // ????????????? - // ??????????????????????????? - if (game_mode == 1) - { - // ?????? - printf("\n===== ????????????(%dX%d????) =====", BOARD_SIZE, BOARD_SIZE); - printf("\n ??%d??/%d??: %s ??????(%d, %d)\n", - i + 1, step_count, - (s.player == PLAYER) ? "???" : "AI", - s.x + 1, s.y + 1); - } - else - { - // ????? - printf("\n===== ???????????(%dX%d????) =====", BOARD_SIZE, BOARD_SIZE); - printf("\n ??%d??/%d??: %s ??????(%d, %d)\n", - i + 1, step_count, - (s.player == PLAYER1) ? "???1(????)" : "???2(????)", - s.x + 1, s.y + 1); - } - - // ?????????????? - printf(" "); - for (int col = 0; col < BOARD_SIZE; col++) - printf("%2d", col + 1); // ???? - printf("\n"); - - for (int row = 0; row < BOARD_SIZE; row++) - { - printf("%2d ", row + 1); // ???? - for (int col = 0; col < BOARD_SIZE; col++) - { - if (temp_board[row][col] == PLAYER || temp_board[row][col] == PLAYER1) - printf("x "); - else if (temp_board[row][col] == AI || temp_board[row][col] == PLAYER2) - printf("?? "); - else - printf("?? "); - } - printf("\n"); // ?????????? - } - - // ????????????????????????????? - if (i < step_count - 1) - { - printf("\n??Enter?????????..."); - while (getchar() != '\n') - ; // ?????? - } - } - printf("\n???????????Enter??????..."); - 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; - - if (sum_score > 0) - { - if (game_mode == 1) - { - printf("??????: %d, ???: %.2f%%\n", - player1_score, (double)player1_score * 100.0 / sum_score); - printf("AI????: %d, ???: %.2f%%\n", - player2_score, (double)player2_score * 100.0 / sum_score); - } - else - { - printf("???1(????)????: %d, ???: %.2f%%\n", - player1_score, (double)player1_score * 100.0 / sum_score); - printf("???2(????)????: %d, ???: %.2f%%\n", - player2_score, (double)player2_score * 100.0 / sum_score); - } - } - else - { - if (game_mode == 1) - { - printf("??????: %d\n", player1_score); - printf("AI????: %d\n", player2_score); - } - else - { - printf("???1(????)????: %d\n", player1_score); - printf("???2(????)????: %d\n", player2_score); - } - printf("?: ?????????0????????????\n"); - } - - // ???MVP - if (player1_score > player2_score) - { - printf("\nMVP: %s (???? %d ??)\n", (game_mode == 1) ? "???" : "???1(????)", player1_score - player2_score); - } - else if (player2_score > player1_score) - { - printf("\nMVP: %s (???? %d ??)\n", (game_mode == 1) ? "AI" : "???2(????)", player2_score - player1_score); - } - else - { - printf("\n????????????\n"); - } - - getchar(); -} /** - * @brief ???????????????????? - * @return int ????????(0-???, 1-?????????, 2-????????, 3-??????????) - */ -void handle_save_record(int game_mode) -{ - int save_choice = 0; - printf("===== ??????? =====\n"); - printf("??????????? (1-??, 0-??): "); - scanf("%d", &save_choice); - - if (save_choice == 1) - { - 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); - - int save_status = save_game_to_file(filename, game_mode); - switch (save_status) - { - case 0: // ??? - printf("\n????????????????: %s\n", filename); - printf("????????????????????????: .\\gobang.exe -l %s\n", filename); - break; - case 1: // ????????? - printf("\n?????????????: ??????? 'records' ????\n"); - printf("????????????????????????????????????\n"); - break; - case 2: // ???????? - printf("\n?????????????: ????????? '%s' ?????????\n", filename); - printf("????????????????????????????????????\n"); - break; - case 3: // ?????????? - printf("\n?????????????: ????????????????\n"); - printf("??????????????????\n"); - break; - default: - printf("\n?????????????: ???????????\n"); - break; - } - } -} - -/** - * @brief ?????????? + * @brief 悔棋功能实现 * - * @param steps_to_undo ????????? - * @return true ?????? - * @return false ???????(????????) + * @param steps_to_undo 要悔棋的步数 + * @return true 悔棋成功 + * @return false 悔棋失败(步数不足) */ bool return_move(int steps_to_undo) { @@ -502,214 +208,67 @@ 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 ??????: - * 1. ???????????????? - * 2. ?????????????????? - * 3. ???????????????????? - * 4. ???????????4(??????????????????) + * @brief 评估玩家在整盘棋局中的表现 + * @param player 要评估的玩家(PLAYER/AI) + * @return int 总分(已考虑方向重复计算) + * @note 评分标准: + * - 五连:2500 + * - 活四:1000 冲四:500 死四:250 + * - 活三:250 眠三:100 死三:50 + * - 活二:50 眠二:20 死二:10 + * - 开放单子:10 半开放单子:5 封闭单子:1 + * @note 实现细节: + * 1. 遍历棋盘所有位置 + * 2. 对每个棋子检查四个方向 + * 3. 统计所有连子情况并评分 + * 4. 最终分数除以4(消除方向重复计算影响) */ int calculate_step_score(int x, int y, int player) { int step_score = 0; - // ?????????? + // 检查四个方向 for (int k = 0; k < 4; k++) { DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player); - // ?????????????? + // 根据连子数评分 switch (info.continuous_chess) { case 5: step_score += 2500; - break; // ???? + break; // 五连 case 4: if (info.check_start && info.check_end) - step_score += 1000; // ???? + step_score += 1000; // 活四 else if (info.check_start || info.check_end) - step_score += 500; // ???? + step_score += 500; // 冲四 else - step_score += 250; // ???? + step_score += 250; // 死四 break; case 3: if (info.check_start && info.check_end) - step_score += 250; // ???? + step_score += 250; // 活三 else if (info.check_start || info.check_end) - step_score += 100; // ???? + step_score += 100; // 眠三 else - step_score += 50; // ???? + step_score += 50; // 死三 break; case 2: if (info.check_start && info.check_end) - step_score += 50; // ??? + step_score += 50; // 活二 else if (info.check_start || info.check_end) - step_score += 20; // ??? + step_score += 20; // 眠二 else - step_score += 10; // ???? + step_score += 10; // 死二 break; case 1: if (info.check_start && info.check_end) - step_score += 10; // ??????? + step_score += 10; // 开放单子 else if (info.check_start || info.check_end) - step_score += 5; // ??????? + step_score += 5; // 半开放单子 else - step_score += 1; // ?????? + step_score += 1; // 封闭单子 break; } } return step_score; -} - -/** - * @brief ??????????????????????? - * @param player ??????????(PLAYER/AI) - * @return int ???(???????????????) - * @note ??????: - * - ????:2500 - * - ????:1000 ????:500 ????:250 - * - ????:250 ????:100 ????:50 - * - ???:50 ???:20 ????:10 - * - ???????:10 ???????:5 ??????:1 - * @note ??????: - * 1. ???????????????? - * 2. ?????????????????? - * 3. ???????????????????? - * 4. ???????????4(??????????????????) - */ -int evaluate_performance(int player) -{ - int total_score = 0; - - // ???????????????? - for (int i = 0; i < BOARD_SIZE; i++) - { - for (int j = 0; j < BOARD_SIZE; j++) - { - if (board[i][j] == player) - { - total_score += calculate_step_score(i, j, player); - } - } - } - return total_score / 4; // ????????????????????4 -} - -/** - * @brief ??????????????????? - * @param filename ??????????? - * @return int ??????: - * 0: ??? - * 1: ????????? - * 2: ???????? - * 3: ?????????? - */ -int save_game_to_file(const char *filename, int game_mode) -{ - // ????records??(?????????) - struct stat st = {0}; - if (stat("records", &st) == -1) - { - if (mkdir("records") != 0) - { - // ?????????????(????????????????????????) - if (stat("records", &st) == -1) - { -#ifdef _WIN32 - printf("???????????records??\n"); - printf("???????\n"); - printf("1. ?????????? - ??????????????????\n"); - printf("2. ????????????? - ??????????????\n"); - printf("3. ???????? - ?????????\n"); -#else - perror("?????????"); -#endif - return 1; // ????????? - } - } - } - - // ????? - char fullpath[256]; - snprintf(fullpath, sizeof(fullpath), "records/%s", filename); - FILE *file = fopen(fullpath, "w"); - if (!file) - { - return 2; // ???????? - } - - // ?????????????????? - if (fprintf(file, "%d\n%d\n", game_mode, BOARD_SIZE) < 0) - { - fclose(file); - return 3; // ?????????? - } - - // ??????????????? - 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) - { - fclose(file); - return 3; // ?????????? - } - } - - if (fclose(file) != 0) - { - return 3; // ??????/??????? - } - - return 0; // ??? -} - -/** - * @brief ??????????????? - * @param filename ??????????? - * @return true ?????? - * @return false ??????? - */ -int load_game_from_file(const char *filename) -{ - // ????? - char fullpath[256]; - snprintf(fullpath, sizeof(fullpath), "records/%s", filename); - FILE *file = fopen(fullpath, "r"); - if (!file) - { - return false; - } - - // ????????????????? - int game_mode, size; - if (fscanf(file, "%d", &game_mode) != 1 || (game_mode != 1 && game_mode != 2)) - { - fclose(file); - return 0; // ??????????? - } - if (fscanf(file, "%d", &size) != 1 || size < 5 || size > MAX_BOARD_SIZE) - { - fclose(file); - return false; - } - - // ????????? - BOARD_SIZE = size; - empty_board(); - - // ?????????????? - step_count = 0; - while (fscanf(file, "%d %d %d", &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 3) - { - step_count++; - } - - fclose(file); - return game_mode; } \ No newline at end of file diff --git a/gobang.exe b/gobang.exe index 1537c11b58cb28349628ed02cdda8450e9d40734..1b2644d72f666811578c51d5ea8ea02b24c51368 100644 GIT binary patch delta 22164 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 T#1B+k{+!AWeLFXwAIAR=a1qvR diff --git a/gobang.h b/gobang.h index e116f8a..550de06 100644 --- a/gobang.h +++ b/gobang.h @@ -1,233 +1,159 @@ /** * @file gobang.h - * @author ??????(3364451258@qq.com??15236416560@163.com??lhy3364451258@outlook.com) - * @brief ????????????? - * @version 3.0 - * @date 2025-06-30 - * - * @copyright Copyright (c) 2025 - * - * @note ??????gobang.c??????????????????????????????? - * @note ?????? - * 1. ???5x5??25x25?????????? - * 2. ????????????????AI???? - * 3. ????????????????????? - * 4. ???????????0-base???? - * 5. ????????????????? - * 6. ?????????????????????????????????? - * 7. ???????????????????????? + * @brief Ϸ߼ͷļ + * @details Ϸĺݽṹȫֱ궨ͺԭ͡ + * @author (3364451258@qq.com15236416560@163.comlhy3364451258@outlook.com) + * @date 2025-07-02 + * @version 4.0 + * @note + * 1. ܣ + * - ˶Խֹֹ֧֣ҽ߷ + * - Ϸʱܣÿغϵ˼ʱ䡣 + * 2. Ż + * - Żܣ˲Ҫļ㡣 + * - Alpha-Beta ֦㷨 AI Чʡ + * 3. ûĽ + * - н棬ṩѺõĽ顣 + * - Զ̴СϷԡ + * 4. ṹŻ + * - Ϸ߼û룬ߴĿɶԺͿάԡ + * - Ż˴ṹ˴ĿɶԺͿάԡ + * 5. 쳣 + * - 쳣ƣȷϷȶԡ + * - ޸һЩ֪ bugϷȶԡ + * 6. ĵ£ + * - ˴עͣ˴Ŀɶԡ + * - ĵʹ÷עȡ + * 7. 汾ƣ + * - ʹ Git а汾ƣŶЭʹ + * 8. ԣ + * - ȫIJԣȷϷȶԺ͹ܵȷԡ + * 9. ԴЭ飺 + * - ѡ MIT ԴЭ飬ûʹá޸ĺͷַ롣 + * 10. ߣ + * - + * 11. ϵϢ + * - Ŀҳ[https://github.com/LHY0125/Gobang-Game] + * - ϵ䣺[3364451258@qq.com][15236416560@163.com][lhy3364451258@outlook.com] */ #ifndef GO_BANG_H #define GO_BANG_H #include -#include -#include #include -#include +#include #include +#include +#include -// ???? +// 궨 +#define MAX_BOARD_SIZE 25 // ֵ֧̳ߴ +#define PLAYER 1 // ұʶ (˻սģʽ) +#define AI 2 // AIʶ (˻սģʽ) +#define PLAYER1 1 // 1ʶ (˫˶սģʽ) +#define PLAYER2 2 // 2ʶ (˫˶սģʽ) +#define EMPTY 0 // ̿λʶ +#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) // Ϸ + +// ȫֱ +extern int BOARD_SIZE; // ǰʵʹõ̳ߴ +extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 洢״̬Ķά +extern int step_count; // ǰϷܲ +extern bool use_forbidden_moves; // Ƿýֹı־ +extern int use_timer; // Ƿüʱı־ +extern int time_limit; // ÿغϵʱƣ룩 +extern const int direction[4][2]; // ĸˮƽֱбб + +// ݽṹ /** - * @brief ???????????? - * @note 25x25????????????????????????????????????AI?????? - */ -#define MAX_BOARD_SIZE 25 // ????????????(5x5??25x25) - -/** - * @brief ??????? - * @note ???1/2???????????????????????????? - */ -#define PLAYER 1 // ??????????? -#define AI 2 // AI???????? -#define PLAYER1 1 // ???1???????? -#define PLAYER2 2 // ???2???????? - -/** - * @brief ?????????? - * @note ??????PLAYER/AI?????? - */ -#define EMPTY 0 // ?????????? - -/** - * @brief ????????? - * @note ????????????????????????????? - */ -#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) // ?????????????????? - -// ?????????? -extern int BOARD_SIZE; // ?????????????(???15) -extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // ????????????? -extern int step_count; // ????????????? -extern bool use_forbidden_moves; // ????????????? -extern int use_timer; // ???????????? -extern int time_limit; // ?????????? -extern const int direction[4][2]; - -/** - * @brief ????????????? - * @note ?????????????????AI?????? - * @note ??????: - * - player: ????????(PLAYER/AI) - * - x/y: 0-based???????? + * @brief ¼һϸϢ */ typedef struct { - int player; // ???????? - int x, y; // ???????? + int player; // ִиòұʶ + int x; // ӵ (0-based) + int y; // ӵ (0-based) } Step; -extern Step steps[MAX_STEPS]; // ??????????????????? - -bool handle_player_turn(int current_player); +extern Step steps[MAX_STEPS]; // ڴ洢Ϸÿһ /** - * @brief ????????????? - * @note ???????????????????????? - * @note ??????: - * - continuous_chess: ?????????????? - * - check_start: ??????????????????(??????) - * - check_end: ??????????????????(??????) + * @brief 洢ضԵϢ + * @details Σжϻĵȹؼ̬ */ typedef struct { - int continuous_chess; // ???????????? - bool check_start; // ???????????????????? - bool check_end; // ???????????????????? + int continuous_chess; // ͬɫӵ + bool check_start; // еʼǷΪλǷ񿪷ţ + bool check_end; // еĩβǷΪλǷ񿪷ţ } DirInfo; -// ???????? -/** - * @brief ??????????????? - */ -void empty_board(); +// ԭ -/** - * @brief ???????????? - */ -void print_board(); +// --- Ϸ߼ --- /** - * @brief ?????????????????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @return true ????????????? - * @return false ???????????????? + * @brief ָǷΪЧӵ㣨Ϊգ + * @param x (0-based) + * @param y (0-based) + * @return λЧΪ򷵻true򷵻false */ bool have_space(int x, int y); /** - * @brief ??????????? - */ -void setup_board_size(); - -/** - * @brief ?????????? - * @note ???????????????????????? - */ -void setup_game_options(); - -/** - * @brief ??????????? - * @param player1 ???1???? - * @param player2 ???2???? - * @return int ?????????? - */ -int determine_first_player(int player1, int player2); - -/** - * @brief ??????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @param player ????? - * @return true ????? - * @return false ??????? + * @brief жһǷΪ + * @param x ӵ (0-based) + * @param y ӵ (0-based) + * @param player ǰҵıʶ + * @return ǽ򷵻true򷵻false */ bool is_forbidden_move(int x, int y, int player); + /** - * @brief ?????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @return true ?????? - * @return false ???????(????????) + * @brief ִһӲ + * @param x ӵ (0-based) + * @param y ӵ (0-based) + * @param player ǰҵıʶ + * @return ӳɹ򷵻trueλЧռãfalse */ bool player_move(int x, int y, int player); /** - * @brief ??????????????????????????? - * @param x ????????? - * @param y ????????? - * @param dx ??????????(-1,0,1) - * @param dy ??????????(-1,0,1) - * @param player ?????(PLAYER/AI) - * @return DirInfo ??????????????????????????? + * @brief ضϵϢ + * @param x ʼ + * @param y ʼ + * @param dx x (-1, 0, or 1) + * @param dy y (-1, 0, or 1) + * @param player ұʶ + * @return һϢ DirInfo ṹ */ DirInfo count_specific_direction(int x, int y, int dx, int dy, int player); /** - * @brief ???????????????????????????? - * @param x ??????(0-base) - * @param y ??????(0-base) - * @param player ?????(PLAYER/AI) - * @return true ?????????? - * @return false ???????????? + * @brief ijӺ󣬸Ƿʤ + * @param x ӵ (0-based) + * @param y ӵ (0-based) + * @param player ǰҵıʶ + * @return ʤ򷵻true򷵻false */ bool check_win(int x, int y, int player); /** - * @brief ?????????? - * @return true ?????? - * @return false ???????(????????) - * @note ????????AI???????????? + * @brief 幦ܣָ + * @param steps_to_undo ҪIJÿ˫һӣ + * @return ɹ򷵻true򷵻false */ bool return_move(int steps_to_undo); /** - * @brief ??????????? - * ???????????????????????? - */ -void review_process(int game_mode); - -/** - * @brief ??????????????????????? - * @param player ??????????(PLAYER/AI) - * @return int ???(???????????????) - */ -int evaluate_performance(int player); - -/** - * @brief ????????????? - * @param x ?????? - * @param y ?????? - * @param player ????? - * @return int ????????? + * @brief 㲢һĵ÷ + * @param x ӵ + * @param y ӵ + * @param player ұʶ + * @return òĵ÷ */ int calculate_step_score(int x, int y, int player); -/** - * @brief ??????????????????? - * @param filename ??????????? - * @return int ??????: - * 0: ??? - * 1: ????????? - * 2: ???????? - * 3: ?????????? - */ -int save_game_to_file(const char *filename, int game_mode); - -/** - * @brief ???????????????????? - */ -void handle_save_record(int game_mode); - -/** - * @brief ??????????????? - * @param filename ??????????? - * @return true ?????? - * @return false ??????? - */ -int load_game_from_file(const char *filename); - #endif // GO_BANG_H \ No newline at end of file diff --git a/init_board.c b/init_board.c new file mode 100644 index 0000000..73412f0 --- /dev/null +++ b/init_board.c @@ -0,0 +1,114 @@ +#include "init_board.h" +#include "game_mode.h" +#include "gobang.h" +#include + +/** + * @brief 初始化棋盘为全空状态并重置步数计数器 + * 清空棋盘数组并将所有位置设为EMPTY,同时将step_count重置为0 + */ +void empty_board() +{ + // 初始化棋盘状态为全空 + for (int i = 0; i < BOARD_SIZE; i++) + { + for (int j = 0; j < BOARD_SIZE; j++) + { + board[i][j] = EMPTY; + } + } + step_count = 0; // 重置步数计数器 +} + +/** + * @brief 打印当前棋盘状态 + * 以可读格式输出棋盘,包括行列号和棋子状态 + * 玩家棋子显示为'x',AI棋子显示为'○',空位显示为'·' + */ +void print_board() +{ + // 打印列号(1-BOARD_SIZE显示) + printf("\n "); + for (int i = 0; i < BOARD_SIZE; i++) + { + printf("%2d", i + 1); + if (i + 1 == 9) // 处理列号9和10+的对齐 + { + printf(" "); + } + } + printf("\n"); + + // 逐行打印棋盘内容 + for (int i = 0; i < BOARD_SIZE; i++) + { + printf("%2d ", i + 1); // 打印行号(1-BOARD_SIZE) + for (int j = 0; j < BOARD_SIZE; j++) + { + if (board[i][j] == PLAYER) + { + printf("x "); // 玩家棋子 + } + else if (board[i][j] == AI) + { + printf("○ "); // AI棋子(使用○显示) + } + else + { + printf("· "); // 空位 + } + } + printf("\n"); // 每行结束换行 + } +} + +/** + * @brief 配置棋盘大小 + * + * @param player1 玩家1 + * @param player2 玩家2 + */ +void setup_board_size() +{ + printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n"); + char prompt[100]; + sprintf(prompt, "请输入棋盘大小(5~%d)(默认为标准棋盘):\n", MAX_BOARD_SIZE); + BOARD_SIZE = get_integer_input(prompt, 5, MAX_BOARD_SIZE); +} + +/** + * @brief Set the up game options object + * 配置游戏选项,包括禁手规则、计时器和时间限制 + */ +void setup_game_options() +{ + use_forbidden_moves = get_integer_input("是否启用禁手规则 (1-是, 0-否): ", 0, 1); + + use_timer = get_integer_input("是否启用计时器 (1-是, 0-否): ", 0, 1); + if (use_timer) + { + time_limit = get_integer_input("请输入每回合的时间限制 (1~60分钟): ", 1, 60) * 60; + } +} + +/** + * @brief 确定先手玩家 + * + * @param player1 + * @param player2 + * @return int player1 or player2 + */ +int determine_first_player(int player1, int player2) +{ + char prompt[100]; + sprintf(prompt, "请选择先手方 (1 for Player %d, 2 for Player %d): ", player1, player2); + int first_player_choice = get_integer_input(prompt, 1, 2); + if (first_player_choice == 1) + { + return player1; + } + else + { + return player2; + } +} \ No newline at end of file diff --git a/init_board.h b/init_board.h new file mode 100644 index 0000000..e57a185 --- /dev/null +++ b/init_board.h @@ -0,0 +1,35 @@ +#ifndef INIT_BOARD_H +#define INIT_BOARD_H + +#include "gobang.h" + +// --- Ϸʼ --- +/** + * @brief ʼ̣λΪ(EMPTY) + */ +void empty_board(); + +/** + * @brief ǰ״̬ӡ̨ + */ +void print_board(); + +/** + * @brief õǰϷ̴С + */ +void setup_board_size(); + +/** + * @brief ϷѡǷý֡ʱ + */ +void setup_game_options(); + +/** + * @brief + * @param player1 1ıʶ + * @param player2 2ıʶ + * @return ҵıʶ + */ +int determine_first_player(int player1, int player2); + +#endif // INIT_H \ No newline at end of file diff --git a/record.c b/record.c new file mode 100644 index 0000000..5b04d14 --- /dev/null +++ b/record.c @@ -0,0 +1,335 @@ +#include "record.h" +#include "game_mode.h" +#include "gobang.h" +#include "init_board.h" +#include +#include +#include +#include + +/** + * @brief 复盘游戏全过程并展示评分 + * @note 实现流程: + * 1. 初始化临时复盘棋盘 + * 2. 按步数顺序逐步重现每个落子 + * 3. 每步显示: + * - 当前步数/总步数 + * - 落子方(玩家/AI) + * - 落子位置(1-based坐标) + * - 当前棋盘状态 + * 4. 通过用户按Enter键控制步骤前进 + * 5. 复盘结束后自动进入评分环节: + * - 评估双方表现 + * - 显示得分 + * - 评选MVP + * @note 技术细节: + * - 使用独立临时棋盘避免影响主游戏状态 + * - 坐标显示转换为1-based方便用户理解 + * - 包含输入缓冲区清理防止意外输入 + * - 评分环节调用evaluate_performance()函数 + */ +void review_process(int game_mode) +{ + int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1); + if (review_choice == 1) + { + printf("\n===== 复盘记录(总步数:%d) =====\n", step_count); + // 清空输入缓冲区 + int c; + while ((c = getchar()) != '\n' && c != EOF) + ; + + // 创建临时复盘棋盘 + int temp_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; + memset(temp_board, EMPTY, sizeof(temp_board)); // 初始化为空棋盘 + + // 逐步重现游戏过程 + for (int i = 0; i < step_count; i++) + { + Step s = steps[i]; // 获取当前步骤 + temp_board[s.x][s.y] = s.player; // 在临时棋盘上落子 + + // 打印当前步骤信息 + // 根据游戏模式显示不同的标题和玩家信息 + if (game_mode == 1) + { + // 人机对战 + printf("\n===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE); + printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n", + i + 1, step_count, + (s.player == PLAYER) ? "玩家" : "AI", + s.x + 1, s.y + 1); + } + else + { + // 双人对战 + printf("\n===== 五子棋双人对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE); + printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n", + i + 1, step_count, + (s.player == PLAYER1) ? "玩家1(黑棋)" : "玩家2(白棋)", + s.x + 1, s.y + 1); + } + + // 打印当前复盘棋盘 + printf(" "); + for (int col = 0; col < BOARD_SIZE; col++) + printf("%2d", col + 1); // 列号 + printf("\n"); + + for (int row = 0; row < BOARD_SIZE; row++) + { + printf("%2d ", row + 1); // 行号 + for (int col = 0; col < BOARD_SIZE; col++) + { + if (temp_board[row][col] == PLAYER || temp_board[row][col] == PLAYER1) + { + printf("x "); + } + else if (temp_board[row][col] == AI || temp_board[row][col] == PLAYER2) + { + printf("○ "); + } + else + { + printf("· "); + } + } + printf("\n"); // 行结束换行 + } + + // 如果不是最后一步,等待用户按键继续 + if (i < step_count - 1) + { + printf("\n按Enter继续下一步..."); + while (getchar() != '\n') + ; // 等待回车 + } + } + printf("\n复盘结束!按Enter查看评分..."); + 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; + + if (sum_score > 0) + { + if (game_mode == 1) + { + printf("玩家得分: %d, 占比: %.2f%%\n", + player1_score, (double)player1_score * 100.0 / sum_score); + printf("AI得分: %d, 占比: %.2f%%\n", + player2_score, (double)player2_score * 100.0 / sum_score); + } + else + { + printf("玩家1(黑棋)得分: %d, 占比: %.2f%%\n", + player1_score, (double)player1_score * 100.0 / sum_score); + printf("玩家2(白棋)得分: %d, 占比: %.2f%%\n", + player2_score, (double)player2_score * 100.0 / sum_score); + } + } + else + { + if (game_mode == 1) + { + printf("玩家得分: %d\n", player1_score); + printf("AI得分: %d\n", player2_score); + } + else + { + printf("玩家1(黑棋)得分: %d\n", player1_score); + printf("玩家2(白棋)得分: %d\n", player2_score); + } + printf("注: 双方得分均为0,无法计算占比\n"); + } + + // 评选MVP + if (player1_score > player2_score) + { + printf("\nMVP: %s (领先 %d 分)\n", (game_mode == 1) ? "玩家" : "玩家1(黑棋)", player1_score - player2_score); + } + else if (player2_score > player1_score) + { + printf("\nMVP: %s (领先 %d 分)\n", (game_mode == 1) ? "AI" : "玩家2(白棋)", player2_score - player1_score); + } + else + { + printf("\n双方势均力敌!\n"); + } + + getchar(); +} + +/** + * @brief 处理游戏结束后的记录保存 + * @return int 保存状态码(0-成功, 1-目录创建失败, 2-文件打开失败, 3-文件写入失败) + */ +void handle_save_record(int game_mode) +{ + int save_choice = 0; + printf("===== 游戏结束 =====\n"); + printf("是否保存游戏记录? (1-是, 0-否): "); + scanf("%d", &save_choice); + + if (save_choice == 1) + { + 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); + + int save_status = save_game_to_file(filename, game_mode); + switch (save_status) + { + case 0: // 成功 + printf("\n游戏记录已成功保存至: %s\n", filename); + printf("您可以使用以下命令进行复盘: .\\gobang.exe -l %s\n", filename); + break; + case 1: // 目录创建失败 + printf("\n游戏记录保存失败: 无法创建 'records' 目录。\n"); + printf("请检查程序是否具有足够的写入权限或磁盘空间是否充足。\n"); + break; + case 2: // 文件打开失败 + printf("\n游戏记录保存失败: 无法在路径 '%s' 创建文件。\n", filename); + printf("请检查路径是否有效以及程序是否具有写入权限。\n"); + break; + case 3: // 文件写入失败 + printf("\n游戏记录保存失败: 写入文件时发生错误。\n"); + printf("请检查磁盘空间是否已满。\n"); + break; + default: + printf("\n游戏记录保存失败: 发生未知错误。\n"); + break; + } + } +} + +/** + * @brief 将当前游戏记录保存到文件 + * @param filename 要保存的文件名 + * @return int 错误码: + * 0: 成功 + * 1: 目录创建失败 + * 2: 文件打开失败 + * 3: 文件写入失败 + */ +int save_game_to_file(const char *filename, int game_mode) +{ + // 创建records目录(如果不存在) + struct stat st = {0}; + if (stat("records", &st) == -1) + { + if (mkdir("records") != 0) + { + // 检查是否目录已存在(多线程情况下可能被其他线程创建) + if (stat("records", &st) == -1) + { +#ifdef _WIN32 + printf("错误:无法创建records目录\n"); + printf("可能原因:\n"); + printf("1. 没有写入权限 - 请尝试以管理员身份运行\n"); + printf("2. 防病毒软件阻止 - 请检查安全软件设置\n"); + printf("3. 路径无效 - 请检查工作目录\n"); +#else + perror("创建目录失败"); +#endif + return 1; // 目录创建失败 + } + } + } + + // 打开文件 + char fullpath[256]; + snprintf(fullpath, sizeof(fullpath), "records/%s", filename); + FILE *file = fopen(fullpath, "w"); + if (!file) + { + return 2; // 文件打开失败 + } + + // 写入游戏模式和棋盘大小 + if (fprintf(file, "%d\n%d\n", game_mode, BOARD_SIZE) < 0) + { + fclose(file); + return 3; // 文件写入失败 + } + + // 写入所有落子步骤 + 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) + { + fclose(file); + return 3; // 文件写入失败 + } + } + + if (fclose(file) != 0) + { + return 3; // 文件关闭/写入失败 + } + + return 0; // 成功 +} + +/** + * @brief 从文件加载游戏记录 + * @param filename 要加载的文件名 + * @return true 加载成功 + * @return false 加载失败 + */ +int load_game_from_file(const char *filename) +{ + // 打开文件 + char fullpath[256]; + snprintf(fullpath, sizeof(fullpath), "records/%s", filename); + FILE *file = fopen(fullpath, "r"); + if (!file) + { + return false; + } + + // 读取游戏模式和棋盘大小 + int game_mode, size; + if (fscanf(file, "%d", &game_mode) != 1 || (game_mode != 1 && game_mode != 2)) + { + fclose(file); + return 0; // 无效的游戏模式 + } + if (fscanf(file, "%d", &size) != 1 || size < 5 || size > MAX_BOARD_SIZE) + { + fclose(file); + return false; + } + + // 初始化棋盘 + BOARD_SIZE = size; + empty_board(); + + // 读取所有落子步骤 + step_count = 0; + while (fscanf(file, "%d %d %d", &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 3) + { + step_count++; + } + + fclose(file); + return game_mode; +} \ No newline at end of file diff --git a/record.h b/record.h new file mode 100644 index 0000000..2644e10 --- /dev/null +++ b/record.h @@ -0,0 +1,34 @@ +#ifndef RECORD_H +#define RECORD_H + +#include "gobang.h" + +// --- 复盘与记录 --- +/** + * @brief 进入复盘流程,回顾整局游戏 + * @param game_mode 游戏模式(1为人机,2为双人) + */ +void review_process(int game_mode); + +/** + * @brief 将当前对局记录保存到文件 + * @param filename 要保存到的文件名 + * @param game_mode 游戏模式 + * @return 0表示成功,非0表示失败 + */ +int save_game_to_file(const char *filename, int game_mode); + +/** + * @brief 处理保存游戏记录的逻辑 + * @param game_mode 游戏模式 + */ +void handle_save_record(int game_mode); + +/** + * @brief 从文件加载游戏记录 + * @param filename 要加载的文件名 + * @return 0表示成功,非0表示失败 + */ +int load_game_from_file(const char *filename); + +#endif // RECORD_H \ No newline at end of file diff --git a/五子棋.c b/五子棋.c index 0b36773..21f894f 100644 --- a/五子棋.c +++ b/五子棋.c @@ -7,7 +7,7 @@ /** * @brief 将指令复制到powershell - * gcc 五子棋.c gobang.c game_mode.c ai.c -o gobang.exe + * gcc -o gobang.exe gobang.c ai.c game_mode.c init_board.c record.c 五子棋.c * gcc 为编译器,五子棋.c gobang.c game_mode.c 为源文件,output/为输出目录 * @brief 将指令复制到powershell * .\gobang.exe @@ -47,6 +47,7 @@ int main(int argc, char *argv[]) } else if (mode == 4) { + printf("感谢使用五子棋游戏!\n"); break; } }