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 1537c11..1b2644d 100644 Binary files a/gobang.exe and b/gobang.exe differ 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; } }