refactor: 重构项目为纯GUI版本并清理冗余代码

- 移除控制台版本相关代码,包括game_mode、ui、init_board等模块
- 将empty_board函数移至gobang.c核心模块
- 简化main.c仅保留GUI启动逻辑
- 更新Makefile仅构建GUI版本
- 清理过时文档和配置文件
- 优化GUI菜单和游戏窗口交互逻辑
- 添加AI难度配置支持
This commit is contained in:
2026-03-17 16:57:27 +08:00
parent 0baab8bec6
commit dd2b6fd903
30 changed files with 295 additions and 3402 deletions
-105
View File
@@ -1,105 +0,0 @@
## 🎯 项目优势
### ✅ 已经做得很好的方面
- 📚 代码质量优秀 :注释覆盖率30.1%,使用Doxygen格式文档
- 🏗️ 架构设计合理 :模块化设计,职责分离清晰
- 🎮 功能完整丰富 :支持人机对战、双人对战、网络对战、GUI界面
- 🔧 技术栈现代化 :SDL3图形库、双版本架构、专业安装包
- 📋 文档完善 :详细的README、AI增强指南、架构重构指南
- 🌐 跨平台考虑 :已有条件编译支持Windows/Linux
## 🚀 改进建议
### 1. 🧪 测试体系建设(高优先级)
当前状况 :项目缺乏自动化测试
建议改进
- 添加单元测试框架(如Unity或自制简单测试框架)
- 为核心模块编写测试用例:AI算法、棋盘逻辑、网络通信
- 添加集成测试验证各模块协作
- 创建性能基准测试
### 2. 🛡️ 错误处理和安全性增强(高优先级)
当前状况 :基础错误处理存在,但可以更完善
建议改进
- 增强输入验证,防止缓冲区溢出
- 添加网络通信的安全验证机制
- 完善内存管理,添加内存泄漏检测
- 增加异常恢复机制
### 3. 🎯 AI算法优化(中优先级)
当前状况 :基础Minimax+α-β剪枝,搜索深度3层
建议改进
- 实现置换表缓存系统
- 添加开局库和残局库
- 实现迭代加深搜索
- 增加威胁检测优化
- 支持多线程并行搜索
### 4. 🔧 构建和部署优化(中优先级)
当前状况 :编译脚本功能完善,但可以更自动化
建议改进
- 添加CMake构建系统支持
- 实现持续集成/持续部署(CI/CD)
- 添加依赖管理和版本控制
- 完善跨平台构建脚本
### 5. 📊 性能监控和分析(中优先级)
建议添加
- AI决策时间统计
- 内存使用监控
- 网络延迟分析
- 性能瓶颈识别工具
### 6. 🎮 用户体验提升(低优先级)
建议改进
- 添加音效和动画效果
- 实现主题和皮肤系统
- 增加游戏统计和成就系统
- 支持自定义快捷键
### 7. 🌐 网络功能增强(低优先级)
建议改进
- 实现房间系统和匹配机制
- 添加观战功能
- 支持断线重连
- 实现聊天系统
## 📋 具体实施建议
### 立即可实施(1-2周)
1. 1.
创建基础测试框架
2. 2.
添加输入验证和边界检查
3. 3.
完善错误日志记录
4. 4.
优化编译警告处理
### 短期目标(1-2月)
1. 1.
完善单元测试覆盖
2. 2.
实现置换表和开局库
3. 3.
添加性能监控
4. 4.
增强网络安全性
### 长期规划(3-6月)
1. 1.
实现神经网络AI
2. 2.
完整跨平台支持
3. 3.
添加数据库支持
4. 4.
实现云端对战
## 🏆 总体评价
你的项目已经达到了 专业级别的开源项目标准 !代码架构清晰、功能完整、文档详尽。主要的改进空间在于测试覆盖和一些高级特性的添加。
项目亮点
- 从v7.0的架构重构到v8.2的编译优化,版本迭代思路清晰
- SDL3图形化界面实现现代化
- 网络对战功能完整
- 代码注释和文档非常专业
这是一个非常值得继续完善和推广的优秀项目!🎉
-157
View File
@@ -1,157 +0,0 @@
# 🧠 五子棋AI实现详解
## 📜 算法概述
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。
```mermaid
graph TD
A[AI决策开始] --> B{威胁检测}
B -->|有威胁| C[防御性落子]
B -->|无威胁| D[α-β剪枝搜索]
D --> E[评估候选位置]
E --> F[选择最优落子]
```
## 🔢 数据结构
### 🎲 棋盘表示
```c
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 25x25最大棋盘
```
- `0` 空位
- `1` 玩家(✖)
- `2` AI(◯)
### 📝 步数记录
```c
typedef struct {
int player; // 1=玩家, 2=AI
int x, y; // 坐标(0-based)
} Step;
Step steps[MAX_STEPS]; // 最大步数记录
```
### 🧭 方向分析
```c
typedef struct {
int continuous_chess; // 连续同色棋子数
bool check_start; // 起始方向开放
bool check_end; // 结束方向开放
} DirInfo;
```
### 🌐 网络对战支持
```c
// 网络模式下的AI决策
void network_ai_move(int depth, int player_id);
// 同步AI决策到网络对手
void sync_ai_decision(int x, int y);
```
### 🎨 GUI界面AI支持 (v8.0新增)
```c
// GUI模式下的AI可视化决策
void gui_ai_move_with_animation(int x, int y);
// 显示AI思考过程
void show_ai_thinking_process(SDL_Renderer* renderer);
// AI决策结果的图形化展示
void render_ai_decision_info(SDL_Renderer* renderer, int score, int depth);
```
## ⚙️ 核心函数
### 1. ai_move(int depth)
```c
void ai_move(int depth);
```
**执行流程**:
1. 🔍 扫描棋盘检测威胁
2. 🛡️ 优先防御关键威胁
3. 🔎 使用α-β剪枝搜索最佳位置
4. ✅ 执行最优落子
### 2. dfs() - α-β剪枝核心
```c
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
```
**剪枝条件**:
- 极大节点: α ≥ β
- 极小节点: β ≤ α
### 3. evaluate_pos() - 位置评估
**评分标准**:
| 棋型 | 图示 | 分数 |
|------|------|------|
| 活四 | ○○○○● | 100000 |
| 冲四 | ○○○○■ | 10000 |
| 活三 | ○○○●● | 5000 |
## 🏆 评估系统
### 棋型评分表
| 棋型 | 分数 | 示例 |
|------|------|------|
| 活四 | 100000 | `-----○-----` |
| 冲四 | 10000 | `----○■----` |
| 活三 | 5000 | `---○●●---` |
### 位置权重计算
```python
权重 = 50 * (BOARD_SIZE - |x-center| - |y-center|)
```
## ⚡ 性能优化
1. **评估缓存**:
- 哈希表存储重复位置评估
- 命中率: ~85%
2. **搜索优化**:
- 局部搜索范围: 2格
- 平均剪枝率: 65%
3. **典型搜索深度**:
- 基础难度: 3层
- 最高难度: 5层
4. **网络优化**:
- 异步AI计算,避免网络延迟
- 决策结果实时同步
- 支持断线重连后状态恢复
## 🎯 典型场景
### 必胜局面处理
```
局面: ○○○○_
决策: 立即落子形成五连
```
### 双活三防御
```
威胁: 玩家有两个活三
应对: 必须阻挡关键交叉点
```
## 📊 性能基准
| 指标 | 15x15棋盘 | 19x19棋盘 |
|------|-----------|-----------|
| 平均决策时间 | 120ms | 350ms |
| 最大搜索节点 | 8,200 | 24,500 |
| 平均剪枝率 | 68% | 62% |
## 🛠️ 开发建议
1. **调试技巧**:
- 启用`DEBUG_MODE`查看搜索过程
- 使用`print_board()`可视化评估
- 网络模式下使用`network_debug()`监控通信
2. **扩展方向**:
- 添加开局库
- 实现并行搜索
- 优化评估函数
- 增强网络对战AI适应性
- 支持AI难度动态调整
```
-270
View File
@@ -1,270 +0,0 @@
# 五子棋项目代码架构重构指南
## 📋 概述
本文档详细记录了五子棋项目在v7.0-v8.0版本中进行的重大代码架构重构,包括重构的目标、实施过程、技术细节和带来的改进。v8.0版本新增了SDL3图形化界面模块,实现了双版本架构设计。
## 🎯 重构目标
### 主要目标
1. **代码模块化** - 实现清晰的模块分离和职责划分
2. **配置统一管理** - 集中管理所有配置参数和宏定义
3. **全局变量规范化** - 统一管理全局变量,避免散乱分布
4. **类型定义标准化** - 集中定义所有数据结构和类型
5. **提升可维护性** - 降低代码耦合度,提高可读性和可维护性
6. **双版本架构** - (v8.0新增) 实现控制台版本和GUI版本的并行支持
7. **图形化界面集成** - (v8.0新增) 无缝集成SDL3图形库,提供现代化用户界面
### 预期收益
- 减少代码重复和冗余
- 提高开发效率和调试便利性
- 增强代码的可扩展性和可移植性
- 为后续功能开发奠定坚实基础
## 🏗️ 重构实施
### 1. 配置参数统一管理
#### 重构前状态
- 配置参数散落在多个头文件中
- 存在重复定义和不一致的问题
- 网络相关配置分散在`network.h`
- 缺乏统一的配置管理机制
#### 重构措施
- **集中到config.h**:将所有配置宏定义迁移到`config.h`文件
- **分类管理**:按功能模块对配置参数进行分组
- **消除重复**:移除重复的宏定义,确保唯一性
- **标准化命名**:统一配置参数的命名规范
#### 配置分类结构
```c
// 棋盘配置
#define BOARD_SIZE 15
#define MIN_BOARD_SIZE 5
#define MAX_BOARD_SIZE 25
// 游戏模式配置
#define MODE_HUMAN_VS_AI 1
#define MODE_HUMAN_VS_HUMAN 2
#define MODE_NETWORK_BATTLE 3
// AI参数配置
#define DEFAULT_AI_DEPTH 3
#define MAX_AI_DEPTH 6
#define AI_TIMEOUT_MS 5000
// 网络配置
#define DEFAULT_PORT 8888
#define BUFFER_SIZE 1024
#define MAX_IP_LENGTH 16
// 评分参数
#define SCORE_FIVE 100000
#define SCORE_LIVE_FOUR 10000
#define SCORE_RUSH_FOUR 1000
```
### 2. 全局变量统一管理
#### 重构前状态
- 全局变量分散在各个源文件中
- 缺乏统一的声明和定义管理
- 变量作用域不清晰
- 初始化逻辑分散
#### 重构措施
- **创建globals模块**:新建`globals.h``globals.c`文件
- **集中声明**:在`globals.h`中统一声明所有全局变量
- **集中定义**:在`globals.c`中统一定义和初始化
- **访问规范化**:通过包含`globals.h`访问全局变量
#### 全局变量分类
```c
// 游戏状态变量
extern int current_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
extern int current_player;
extern int game_over;
// 配置变量
extern GameConfig game_config;
extern AIConfig ai_config;
extern NetworkConfig network_config;
// 统计变量
extern GameStats game_stats;
extern int total_games_played;
```
### 3. 类型定义标准化
#### 重构前状态
- 结构体定义分散在各个头文件中
- 类型定义不统一
- 缺乏标准的数据结构规范
#### 重构措施
- **创建type.h**:集中定义所有数据结构和类型
- **标准化命名**:采用统一的命名规范
- **逻辑分组**:按功能对类型进行分组
- **文档化**:为每个类型添加详细注释
#### 类型定义结构
```c
// 基础类型定义
typedef enum {
PLAYER_NONE = 0,
PLAYER_BLACK = 1,
PLAYER_WHITE = 2
} PlayerType;
// 游戏配置结构
typedef struct {
int board_size;
int ai_level;
int enable_forbidden;
int time_limit;
} GameConfig;
// 网络消息结构
typedef struct {
int type;
int x, y;
int player;
char data[256];
} NetworkMessage;
```
### 4. 网络配置重构
#### 具体实施
1. **迁移宏定义**:将`network.h`中的配置宏移动到`config.h`
2. **统一命名**:规范网络相关宏的命名
3. **添加引用**:在`network.h`中添加`#include "config.h"`
4. **消息类型统一**:将消息类型定义集中管理
#### 迁移的配置项
```c
// 从network.h迁移到config.h
#define DEFAULT_PORT 8888
#define BUFFER_SIZE 1024
#define MAX_IP_LENGTH 16
#define MSG_MOVE 1
#define MSG_CHAT 2
#define MSG_SURRENDER 3
#define MSG_DISCONNECT 4
```
## 📊 重构效果评估
### 代码质量提升
- **模块耦合度降低**:各模块职责更加清晰
- **代码重复减少**:消除了重复的宏定义和类型定义
- **可读性增强**:统一的命名规范和代码结构
- **维护性提高**:集中管理使得修改更加便捷
### 开发效率提升
- **配置修改便捷**:只需在一个地方修改配置参数
- **调试更容易**:全局变量集中管理,状态更清晰
- **扩展更简单**:标准化的接口和数据结构
- **错误减少**:统一管理避免了不一致性错误
### 性能影响
- **编译时间**:略有增加(由于更多的头文件包含)
- **运行时性能**:无显著影响
- **内存使用**:无显著变化
- **整体评估**:性能影响微乎其微,收益远大于成本
## 🔧 技术细节
### 文件结构变化
#### 新增文件
- `type.h` - 类型定义集中文件
- `globals.h` - 全局变量声明文件
- `globals.c` - 全局变量定义文件
#### 修改文件
- `config.h` - 扩展为完整的配置管理文件
- `network.h` - 移除配置定义,添加config.h引用
- 所有源文件 - 更新头文件包含关系
### 编译依赖关系
```
type.h (基础类型)
config.h (配置参数)
globals.h (全局变量声明)
各功能模块头文件
源文件实现
```
### 包含关系规范
1. **type.h**:被所有需要类型定义的文件包含
2. **config.h**:被所有需要配置参数的文件包含
3. **globals.h**:被所有需要访问全局变量的文件包含
4. **功能模块头文件**:按需包含上述基础头文件
## 📝 最佳实践
### 配置管理
1. **新增配置参数**:统一添加到`config.h`的相应分组中
2. **命名规范**:使用描述性的宏名称,避免缩写
3. **分组管理**:按功能模块对配置进行逻辑分组
4. **文档注释**:为每个配置参数添加清晰的注释
### 全局变量管理
1. **声明规范**:在`globals.h`中使用extern声明
2. **定义规范**:在`globals.c`中进行实际定义和初始化
3. **访问规范**:通过包含`globals.h`访问,避免重复声明
4. **初始化管理**:在`globals.c`中集中进行初始化
### 类型定义管理
1. **命名规范**:使用PascalCase命名结构体和枚举
2. **分组管理**:按功能对类型进行逻辑分组
3. **文档化**:为每个类型和字段添加详细注释
4. **版本兼容**:考虑结构体的向后兼容性
## 🚀 未来扩展
### 短期计划
1. **配置文件增强**:支持更多配置项的动态加载
2. **类型安全增强**:添加更多的类型检查和验证
3. **模块接口标准化**:定义标准的模块接口规范
### 长期规划
1. **插件架构**:基于当前架构实现插件系统
2. **配置热重载**:支持运行时配置的动态更新
3. **跨平台适配**:利用统一架构实现跨平台支持
## 📚 参考资料
### 相关文档
- [C语言编程规范](https://www.kernel.org/doc/html/latest/process/coding-style.html)
- [软件架构设计原则](https://en.wikipedia.org/wiki/Software_architecture)
- [模块化编程最佳实践](https://en.wikipedia.org/wiki/Modular_programming)
### 工具推荐
- **静态分析**:使用cppcheck进行代码质量检查
- **格式化**:使用clang-format统一代码格式
- **文档生成**:使用Doxygen生成API文档
## 📈 总结
v7.0版本的代码架构重构是一次重要的技术升级,通过系统性的重构实现了:
**配置参数的统一管理** - 提高了配置的一致性和可维护性
**全局变量的规范化** - 降低了代码的复杂度和耦合度
**类型定义的标准化** - 增强了代码的可读性和类型安全
**模块结构的优化** - 为后续功能扩展奠定了坚实基础
这次重构不仅解决了当前的技术债务,更为项目的长期发展提供了良好的架构基础。后续的功能开发将能够更加高效和稳定地进行。
---
*本文档将随着项目的发展持续更新,记录架构演进的每一个重要节点。*
-83
View File
@@ -1,83 +0,0 @@
# 五子棋游戏编译指南
本项目现在支持使用 `make` 命令进行编译,提供了更便捷的构建方式。
## 系统要求
- MinGW64 编译器
- SDL3 开发库(用于图形界面)
- Make 工具
## 编译命令
### 查看所有可用命令
```bash
make help
```
### 编译所有版本
```bash
make all
```
这将同时编译控制台版本和GUI版本。
### 只编译控制台版本
```bash
make console
```
生成 `gobang_console.exe`
### 只编译GUI版本
```bash
make gui
```
生成 `gobang_gui.exe`
### 清理编译文件
```bash
make clean
```
删除所有目标文件(.o)和可执行文件(.exe)
### 编译并运行
```bash
# 编译并运行控制台版本
make run-console
# 编译并运行GUI版本
make run-gui
```
## 生成的文件
- `gobang_console.exe` - 控制台版本,支持所有功能包括图形界面模式
- `gobang_gui.exe` - GUI版本,与控制台版本功能相同
## 注意事项
1. 两个版本都包含完整功能,包括图形界面支持
2. 需要确保SDL3库路径正确配置在Makefile中
3. 编译时会显示一些警告,这是正常的
4. 如果遇到编译错误,请检查MinGW64和SDL3的安装路径
## 传统编译方式
如果不使用Makefile,仍可以使用传统的gcc命令:
```bash
# 控制台版本
gcc -std=c17 -o gobang.exe *.c -lws2_32
# GUI版本
gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
```
-78
View File
@@ -1,78 +0,0 @@
# 为五子棋游戏添加图标指南
## 问题说明
在尝试为可执行文件添加图标时,发现icon文件夹中的图标文件格式不正确:
- `gobang_icon1.ico``gobang_icon2.ico` 实际上是HTML文件和PNG文件,而不是真正的ICO格式文件
## 解决方案
### 方法一:获取正确的ICO文件
1. **下载或创建真正的ICO文件**
- 使用在线ICO转换工具将PNG/JPG转换为ICO格式
- 推荐网站:https://www.icoconverter.com/
- 或使用GIMP、Photoshop等图像编辑软件导出ICO格式
2. **替换现有文件**
- 将正确的ICO文件保存为 `icon/gobang_icon.ico`
- 确保文件是真正的ICO格式(文件头应为 00 00 01 00)
3. **修改资源文件**
- 编辑 `gobang.rc` 文件
- 取消注释图标行:`IDI_APPLICATION ICON "icon\\gobang_icon.ico"`
4. **重新编译**
```bash
windres gobang.rc -o gobang.res
gcc -std=c17 -o gobang.exe *.c gobang.res -lws2_32
```
### 方法二:使用现有PNG文件(需要转换)
如果你有PNG格式的图标文件,可以:
1. **在线转换**
- 访问 https://convertio.co/png-ico/
- 上传PNG文件并转换为ICO格式
- 下载转换后的ICO文件
2. **使用ImageMagick(如果已安装)**
```bash
magick convert icon/your_image.png icon/gobang_icon.ico
```
### 方法三:使用Windows资源编辑器
1. 编译不带图标的exe文件(当前状态)
2. 使用Resource Hacker等工具后期添加图标
3. 下载Resource Hackerhttp://www.angusj.com/resourcehacker/
## 当前状态
- ✅ 程序可以正常编译和运行
- ✅ 包含版本信息资源
- ❌ 暂时没有应用程序图标
- ✅ 提供了完整的构建脚本
## 编译指令
### 不带图标版本(当前可用)
```bash
gcc -std=c17 -o gobang.exe *.c -lws2_32
```
### 带图标版本(需要正确的ICO文件)
```bash
windres gobang.rc -o gobang.res
gcc -std=c17 -o gobang.exe *.c gobang.res -lws2_32
```
## 验证ICO文件格式
可以使用以下PowerShell命令检查文件是否为真正的ICO格式:
```powershell
Get-Content icon/gobang_icon.ico -AsByteStream -TotalCount 4 | ForEach-Object { '{0:X2}' -f $_ }
```
正确的ICO文件应该显示:`00 00 01 00`
-150
View File
@@ -1,150 +0,0 @@
# 五子棋网络对战使用说明
## 功能概述
本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。v8.0版本新增了GUI界面的网络对战支持,提供更直观的可视化网络游戏体验。
### v8.2 (2025-10-08)
- 专业安装包制作 - 支持Inno Setup和NSIS双重安装方案
- 安装程序优化 - 完整的文件打包和快捷方式创建
- 分发体系完善 - 提供标准化的软件分发解决方案
- 用户体验提升 - 一键安装部署,支持完整卸载功能
## 编译方法
### 控制台版本
```bash
gcc -std=c17 -o gobang.exe *.c -lws2_32
```
### GUI版本
```bash
gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
```
**注意:**
- Windows系统需要添加 `-lws2_32` 链接库
- GUI版本需要SDL3库支持
- Linux系统不需要ws2_32链接库参数
## 使用方法
### 1. 启动游戏
运行编译后的程序
```bash
.\gobang.exe
```
### 2. 选择网络对战模式
在主菜单中选择 `3. 网络对战`
### 3. 选择连接模式
#### 模式1:创建房间(作为服务器)
- 选择 `1. 创建房间(作为服务器)`
- 输入监听端口(默认8888,建议使用1024-65535范围内的端口)
- 程序会显示本机IP地址,将此IP告知对方
- 等待对方连接
#### 模式2:加入房间(连接到服务器)
- 选择 `2. 加入房间(连接到服务器)`
- 输入服务器IP地址
- 输入服务器的端口号(与服务器设置的端口一致)
- 连接到服务器
### 4. 开始游戏
- 连接成功后游戏自动开始
- 服务器为玩家1(黑棋),客户端为玩家2(白棋)
- 玩家1先手
## 游戏操作
### 基本操作
- **落子**:输入坐标 `行号 列号`,例如:`7 7`
- **认输**:输入 `S``s`
- **悔棋**:输入 `R``r`(需要对方同意)
### 网络功能
- **自动同步**:落子操作会自动同步给对方
- **连接检测**:自动检测网络连接状态
- **延时显示**:支持回合延时显示,避免过快操作
- **悔棋协商**:悔棋需要对方同意才能生效
## 网络配置
### 端口设置
- 默认端口:8888
- 可设端口范围:1024-65535
- 确保防火墙允许选定端口的通信
### IP地址
- **局域网**:使用内网IP地址,例如:192.168.1.100
- **广域网**:使用公网IP地址,可能需要路由器端口转发
### 防火墙设置
如果连接失败,请检查防火墙设置:
#### Windows防火墙
1. 打开Windows安全中心
2. 选择"防火墙和网络保护"
3. 选择"允许应用通过防火墙"
4. 添加gobang.exe到允许列表
#### 路由器设置(用于广域网对战)
如果通过互联网对战,可能需要:
1. 在路由器中设置端口转发
2. 将选定端口转发到服务器内网IP
3. 将路由器的公网IP告知对方
## 故障排除
### 常见问题
1. **连接失败**
- 检查IP地址和端口是否正确
- 确认防火墙设置
- 确保两台设备网络连通
2. **游戏中断**
- 检查网络连接稳定性
- 重新启动游戏重新连接
3. **端口被占用**
- 更换其他端口号
- 关闭占用端口的其他程序
### 网络测试
可以使用以下命令测试网络连通性:
```bash
# 测试网络连通
ping <对方IP地址>
# 测试端口(需要telnet客户端)
telnet <对方IP地址> <端口号>
```
## 技术细节
- **协议**TCP/IP
- **消息格式**:自定义二进制协议
- **支持功能**
- 棋盘同步
- 悔棋处理
- 认输协商
- 连接检测
- 延时控制
## 安全注意事项
1. **局域网使用**:建议安全的家庭或办公环境
2. **广域网使用**
- 不要使用默认端口8888
- 游戏结束后及时关闭程序
- 注意保护个人网络信息
**开发者:** 刘航宇
**联系邮箱:** 3364451258@qq.com
**项目主页:** https://github.com/LHY0125/Gobang-Game
-137
View File
@@ -1,137 +0,0 @@
# 五子棋游戏 - 图形化界面说明 (v8.3)
## 概述
v8.3版本引入了基于IUP的现代化图形界面,实现了完整的双版本架构:
- **控制台界面**:传统的文本界面,保持原有功能完整性
- **图形化界面**:基于IUP的现代图形界面,提供可视化操作体验
## v8.3 新增功能
-**IUP图形化界面**:原生控件风格,轻量高效
-**鼠标交互支持**:点击落子,直观操作
-**窗口管理优化**:自动居中,响应式设计
-**事件驱动架构**:流畅的用户交互体验
-**安装包支持**:提供专业的安装程序
-**双版本并行**:控制台和GUI版本独立运行
## 环境要求
### IUP库配置
项目已内置IUP库(位于 `libs/iup-3.31_Win64_dllw6_lib`),无需额外安装。
### 编译环境
- GCC编译器(MinGW-w64
- Make工具(mingw32-make
- Windows 10/11操作系统
## 编译方法
### 方法一:使用Makefile(推荐)
```bash
# 编译图形化版本
mingw32-make gui
```
### 方法二:手动编译
```bash
# GUI版本
gcc -std=c17 -o bin/gobang_gui.exe src/*.c -Iinclude -Ilibs/iup-3.31_Win64_dllw6_lib/include -Llibs/iup-3.31_Win64_dllw6_lib -liup -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32 -lws2_32
# 复制IUP动态库到bin目录
copy libs\iup-3.31_Win64_dllw6_lib\iup.dll bin\
```
## 运行方法
### 图形化版本
```bash
.\bin\gobang_gui.exe
```
或者在主菜单选择模式8启动图形化界面。
## 图形化界面功能
- **窗口大小**800x600像素
- **棋盘显示**:15x15标准五子棋棋盘
- **鼠标操作**:点击棋盘交叉点进行落子
- **键盘操作**
- `ESC`:退出图形化界面返回主菜单
- **游戏状态**
- 实时显示当前玩家(黑子/白子)
- 显示游戏进度和状态
- 胜负结果提示
- **视觉效果**
- 清晰的黑白棋子
- 当前玩家指示器
- 平滑的图形渲染
- **胜负判定**:自动检测五子连珠并显示获胜者
## 故障排除
### 编译错误
1. **找不到IUP头文件**
- 检查Makefile中的`IUP_PATH`配置是否正确指向`libs`目录
2. **链接错误**
- 确保链接了所有必要的Windows系统库(`-lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32`
3. **运行时错误**
- 确保`iup.dll`位于可执行文件同级目录或系统PATH中
### 图形化界面启动失败
- 确保`iup.dll`文件在exe同目录下
- 尝试以管理员权限运行
- 确认Windows版本兼容性
### 运行时问题
- **中文乱码**:确保代码中已启用UTF-8模式 `IupSetGlobal("UTF8MODE", "YES");`
- **鼠标点击无响应**:确认点击在棋盘交叉点附近
## 使用说明
### 快速开始
1. 运行 `mingw32-make gui` 编译图形化版本
2. 运行 `bin\gobang_gui.exe` 启动程序
3. 在主菜单选择 "8. 图形化界面"
4. 使用鼠标点击棋盘进行游戏
5. 按ESC键退出图形化界面
### 游戏规则
- 黑子先行,轮流落子
- 率先形成五子连珠者获胜
- 支持横、竖、斜四个方向的连珠判定
## 开发说明
### 文件结构
- `include/gui.h` - 图形化界面头文件
- `src/gui.c` - 图形化界面实现(基于IUP
- `src/main.c` - 主程序(已添加图形化模式)
- `Makefile` - 构建脚本
### 扩展功能
图形化界面支持进一步扩展:
- 更多IUP控件集成
- 主题切换
- 网络对战界面优化
- AI难度可视化调节
---
**注意**:首次使用图形化界面前,请确保IUP动态库已正确复制。
+14 -28
View File
@@ -1,5 +1,5 @@
# 五子棋游戏 Makefile # 五子棋游戏 Makefile
# 支持编译控制台版本和GUI版本 (IUP) # 支持编译GUI版本 (IUP)
# 编译器设置 # 编译器设置
CC = gcc CC = gcc
@@ -22,35 +22,27 @@ OBJ_DIR = obj
BIN_DIR = bin BIN_DIR = bin
# 源文件 # 源文件
COMMON_SOURCES = $(SRC_DIR)/main.c $(SRC_DIR)/gobang.c $(SRC_DIR)/ai.c $(SRC_DIR)/config.c \ COMMON_SOURCES = $(SRC_DIR)/gobang.c $(SRC_DIR)/ai.c $(SRC_DIR)/config.c \
$(SRC_DIR)/game_mode.c $(SRC_DIR)/globals.c $(SRC_DIR)/init_board.c \ $(SRC_DIR)/globals.c \
$(SRC_DIR)/network.c $(SRC_DIR)/record.c $(SRC_DIR)/ui.c $(SRC_DIR)/gui.c \ $(SRC_DIR)/network.c $(SRC_DIR)/record.c $(SRC_DIR)/gui.c \
$(SRC_DIR)/gui_menu.c $(SRC_DIR)/gui_menu.c
GUI_SOURCES = $(COMMON_SOURCES)
CONSOLE_SOURCES = $(COMMON_SOURCES)
# 目标文件 (src/xxx.c -> obj/xxx.o) # 目标文件 (src/xxx.c -> obj/xxx.o)
COMMON_OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(COMMON_SOURCES)) COMMON_OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(COMMON_SOURCES))
# 可执行文件 # 可执行文件
CONSOLE_TARGET = $(BIN_DIR)/gobang_console.exe
GUI_TARGET = $(BIN_DIR)/gobang_gui.exe GUI_TARGET = $(BIN_DIR)/gobang_gui.exe
# 默认目标 # 默认目标
all: directories $(CONSOLE_TARGET) $(GUI_TARGET) all: directories $(GUI_TARGET)
# 创建目录 (PowerShell 语法) # 创建目录 (PowerShell 语法)
directories: directories:
if (!(Test-Path "$(OBJ_DIR)")) { New-Item -ItemType Directory -Path "$(OBJ_DIR)" | Out-Null } if (!(Test-Path "$(OBJ_DIR)")) { New-Item -ItemType Directory -Path "$(OBJ_DIR)" | Out-Null }
if (!(Test-Path "$(BIN_DIR)")) { New-Item -ItemType Directory -Path "$(BIN_DIR)" | Out-Null } if (!(Test-Path "$(BIN_DIR)")) { New-Item -ItemType Directory -Path "$(BIN_DIR)" | Out-Null }
# 控制台版本
$(CONSOLE_TARGET): $(COMMON_OBJECTS)
$(CC) $(CFLAGS) $(IUP_INCLUDE) -o $@ $^ $(IUP_LIBS) $(LDFLAGS)
# GUI版本 # GUI版本
$(GUI_TARGET): $(COMMON_OBJECTS) $(GUI_TARGET): $(COMMON_OBJECTS) $(OBJ_DIR)/main.o
$(CC) $(CFLAGS) $(IUP_INCLUDE) -o $@ $^ $(IUP_LIBS) $(LDFLAGS) $(CC) $(CFLAGS) $(IUP_INCLUDE) -o $@ $^ $(IUP_LIBS) $(LDFLAGS)
Copy-Item -Path "$(subst /,\,$(IUP_PATH))\iup.dll" -Destination "$(BIN_DIR)" -Force Copy-Item -Path "$(subst /,\,$(IUP_PATH))\iup.dll" -Destination "$(BIN_DIR)" -Force
@@ -58,27 +50,23 @@ $(GUI_TARGET): $(COMMON_OBJECTS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) $(IUP_INCLUDE) -c -o $@ $< $(CC) $(CFLAGS) $(IUP_INCLUDE) -c -o $@ $<
# 编译 main.c
$(OBJ_DIR)/main.o: $(SRC_DIR)/main.c
$(CC) $(CFLAGS) $(IUP_INCLUDE) -c -o $@ $<
# 清理规则 (PowerShell 语法) # 清理规则 (PowerShell 语法)
clean: clean:
if (Test-Path "$(OBJ_DIR)") { Remove-Item -Path "$(OBJ_DIR)" -Recurse -Force } if (Test-Path "$(OBJ_DIR)") { Remove-Item -Path "$(OBJ_DIR)" -Recurse -Force }
if (Test-Path "$(BIN_DIR)") { Remove-Item -Path "$(BIN_DIR)" -Recurse -Force } if (Test-Path "$(BIN_DIR)") { Remove-Item -Path "$(BIN_DIR)" -Recurse -Force }
# 编译控制台版本 # 编译GUI版本
console: directories $(CONSOLE_TARGET)
# 只编译GUI版本
gui: directories $(GUI_TARGET) gui: directories $(GUI_TARGET)
# 安装规则(可选) # 安装规则(可选)
install: all install: all
Write-Host "Installing executables..." Write-Host "Installing executables..."
Copy-Item -Path "$(CONSOLE_TARGET)" -Destination "C:\Program Files\Gobang\" -Force
Copy-Item -Path "$(GUI_TARGET)" -Destination "C:\Program Files\Gobang\" -Force Copy-Item -Path "$(GUI_TARGET)" -Destination "C:\Program Files\Gobang\" -Force
# 运行控制台版本
run-console: $(CONSOLE_TARGET)
& ".\$(CONSOLE_TARGET)"
# 运行GUI版本 # 运行GUI版本
run-gui: $(GUI_TARGET) run-gui: $(GUI_TARGET)
& ".\$(GUI_TARGET)" & ".\$(GUI_TARGET)"
@@ -86,14 +74,12 @@ run-gui: $(GUI_TARGET)
# 帮助信息 # 帮助信息
help: help:
@echo Available targets: @echo Available targets:
@echo all - Build both console and GUI versions @echo all - Build GUI version
@echo console - Build console version only @echo gui - Build GUI version
@echo gui - Build GUI version only
@echo clean - Remove all object files and executables @echo clean - Remove all object files and executables
@echo run-console - Build and run console version
@echo run-gui - Build and run GUI version @echo run-gui - Build and run GUI version
@echo install - Install executables to system directory @echo install - Install executables to system directory
@echo help - Show this help message @echo help - Show this help message
# 声明伪目标 # 声明伪目标
.PHONY: all clean console gui install run-console run-gui help directories .PHONY: all clean gui install run-gui help directories
-193
View File
@@ -1,193 +0,0 @@
五子棋对战系统 - 代码统计报告
========================================
项目名称:五子棋多模式对战系统
统计时间:2025年10月8日
项目版本:v8.2
开发语言:C语言 + SDL3图形库
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
========================================
📊 代码行数统计
========================================
【C源文件 (.c)】
├── main.c 85行
├── gobang.c 269行
├── game_mode.c 917行
├── ai.c 589行
├── record.c 531行
├── init_board.c 118行
├── ui.c 204行
├── config.c 331行
├── network.c 426行
├── globals.c 37行
└── gui.c 450行 (v8.0新增)
【头文件 (.h)】
├── gobang.h 101行
├── game_mode.h 99行
├── ai.h 39行
├── record.h 45行
├── init_board.h 35行
├── ui.h 62行
├── config.h 170行
├── network.h 186行
├── globals.h 41行
├── type.h 93行
└── gui.h 85行 (v8.0新增)
========================================
📈 总计统计
========================================
总代码行数:4,933行 (v8.0新增535行)
文件类型分布:
• C源文件:3,977行 (80.6%)
• 头文件:956行 (19.4%)
v8.0版本新增:
• GUI图形界面模块:535行 (10.8%)
• 安装包配置文件:2个
• 编译脚本文件:1个
模块代码分布:
• 游戏模式模块:917行 (18.6%)
• AI智能模块:589行 (11.9%)
• GUI图形界面模块:535行 (10.8%) (v8.0新增)
• 记录系统模块:531行 (12.1%)
• 网络对战模块:426行 (9.7%)
• 配置管理模块:331行 (7.5%)
• 核心游戏模块:269行 (6.1%)
• 用户界面模块:204行 (4.6%)
• 配置参数模块:170行 (3.9%)
• 棋盘初始化模块:118行 (2.7%)
• 类型定义模块:93行 (2.1%)
• 主程序模块:84行 (1.9%)
• 全局变量模块:41行 (0.9%)
========================================
💬 注释统计
========================================
【注释统计】
总注释行数:1,248行
注释覆盖率:30.1%
注释类型分布:
• 函数说明注释:498行 (39.9%)
• 代码逻辑注释:425行 (34.1%)
• 文件头注释:325行 (26.0%)
注释质量分析:
• 文件头注释:每个文件都有详细的文档头
• 函数注释:使用Doxygen格式的完整函数文档
• 行内注释:关键逻辑的解释说明
• 分块注释:代码段落的功能说明
估算注释字数:约8,500-10,000字
注释内容包括:
• 详细的函数参数和返回值说明
• 算法逻辑的中文解释
• 代码块的功能描述
• 重要变量和常量的用途说明
• 网络协议和数据结构的详细文档
========================================
🏆 代码质量评价
========================================
【优秀特点】
✓ 注释覆盖率高:几乎每个函数都有详细文档
✓ 代码结构清晰:模块化设计,职责分离明确
✓ 命名规范:变量和函数名具有良好的可读性
✓ 文档完整:包含完整的API文档和使用说明
✓ 架构合理:网络模块、AI模块、UI模块分离
✓ 跨平台支持:Windows和Linux双平台兼容
【技术亮点】
• SDL3图形化界面实现(v8.0新增)
• 双版本架构设计(控制台+GUI)(v8.0新增)
• 鼠标交互和事件驱动架构(v8.0新增)
• 专业安装包制作支持(v8.2新增)
• Inno Setup和NSIS双重打包方案(v8.2新增)
• 完整的软件分发体系(v8.2新增)
• 完整的网络对战功能实现
• 智能AI算法与评估系统
• 灵活的配置管理系统
• 详细的游戏记录与复盘功能
• 规范的禁手规则实现
• 实时计时器系统
• 全局变量统一管理
• 跨平台网络通信支持
• 代码架构模块化重构
• 配置参数集中化管理
• 类型定义标准化
【总体评价】
这是一个非常优秀的C语言项目,代码量适中但功能完整,
注释详尽,体现了良好的编程习惯和专业素养!
项目从单一的五子棋游戏发展为支持多种对战模式的完整系统,
包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
是C语言项目开发的优秀范例。
v8.2版本进一步完善了软件分发体系,
通过Inno Setup和NSIS双重安装包方案,
实现了专业级的软件打包和部署功能,
为用户提供了便捷的一键安装体验,
标志着项目从开发阶段向产品化阶段的重要转变。
v8.0版本的图形化界面是项目发展的重大突破,
通过SDL3图形库实现了现代化的可视化界面,
支持鼠标交互操作,大幅提升了用户体验。
双版本架构设计既保持了控制台版本的轻量特性,
又提供了GUI版本的现代化体验,满足不同用户需求。
网络对战功能的加入使得项目具备了现代化游戏的特征,
支持实时在线对战,为用户提供了更丰富的游戏体验。
v7.0版本的代码架构重构是项目发展的重要里程碑,
通过配置统一管理、全局变量规范化、类型定义标准化等措施,
大幅提升了代码的可维护性和扩展性,为v8.0的GUI功能
奠定了坚实的架构基础。
========================================
📋 项目文件结构
========================================
核心模块:
• main.c/gobang.c - 主程序和核心游戏逻辑
• game_mode.c/h - 游戏模式管理(人机/双人/网络)
• ai.c/h - AI智能算法实现
• gui.c/h - SDL3图形化界面模块(v8.0新增)
• network.c/h - 网络对战功能
• record.c/h - 游戏记录与复盘
• ui.c/h - 用户界面管理
• config.c/h - 配置文件管理
• init_board.c/h - 棋盘初始化
• globals.c/h - 全局变量统一管理
• type.h - 数据结构和类型定义集中文件(v7.0新增)
配置文件:
• gobang_config.ini - 游戏配置文件
• compile_gui.bat - GUI版本编译脚本(v8.0新增)
安装包目录:
• installer/ - 安装包制作目录(v8.0新增)
• setup.iss - Inno Setup安装脚本
• installer.nsi - NSIS安装脚本
文档目录:
• MD/ - 项目文档目录
• README_GUI.md - GUI版本使用指南(v8.0新增)
• TXT/ - 文本文档目录
• records/ - 游戏记录存储目录
开发环境:
• .vscode/ - VS Code配置
• .idea/ - IntelliJ IDEA配置
========================================
-108
View File
@@ -1,108 +0,0 @@
/**
* @file 五子棋对战系统
* @brief C语言五子棋多模式对战系统
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统,v8.3新增IUP图形化界面
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
* @date 2026-03-16
* @version 8.3
* @note
* 1. v8.3 图形界面重构:
* - 🎨 IUP图形化界面:迁移至轻量级IUP库,无需复杂配置即可运行
* - 🏗️ 构建系统优化:引入Makefile,实现标准化编译流程
* - 📂 目录结构重构:分离src、obj、bin目录,项目结构更清晰
* - 🌐 本地化资源:集成第三方库到libs目录,实现开箱即用
*
* 2. v8.2专业安装包:
* - 📦 双重打包方案:支持Inno Setup和NSIS两种安装包制作
* - 🚀 一键安装部署:完整的软件分发解决方案
* - 🔧 安装程序优化:自动创建快捷方式和注册表项
* - 🗂️ 完整文件打包:包含所有源码、文档和依赖文件
* - 🔄 完整卸载功能:支持干净的软件卸载和清理
* - 💼 产品化部署:从开发工具向商业软件的转变
*
* 3. v8.0图形化界面:
* - 🎨 SDL3图形化界面:实现现代化可视化棋盘界面(已废弃,迁移至IUP)
* - 🖱️ 鼠标交互支持:直观的点击落子操作
* - 🏗️ 双版本架构:控制台版本和GUI版本并行支持
* - 🪟 窗口管理优化:自动居中、响应式设计
* - ⚡ 事件驱动架构:流畅的用户交互体验
* - 🌐 GUI网络支持:图形化界面支持网络对战
* 4. v7.0架构重构:
* - 🏗️ 代码架构全面重构,实现模块化设计
* - 📋 配置参数统一管理,所有配置集中到config.h
* - 🔧 全局变量规范化,统一在globals模块管理
* - 📝 类型定义标准化,集中在type.h中定义
* - 🌐 网络配置重构,从network.h迁移到config.h
* - 🔄 消除重复定义,提高代码一致性
* - 📚 完善文档体系,新增架构重构指南
* 5. v6.1完善功能:
* - 🌐 完善的网络对战模式,支持服务器/客户端架构
* - 🔗 实时数据同步,支持落子、悔棋、认输等网络功能
* - 🛡️ 网络安全验证和连接状态管理
* - 📡 跨平台网络支持(Windows/Linux
* - 🔧 全局变量统一管理,优化代码结构
* - 📋 宏定义统一管理,消除重复定义
* - 🔄 网络协议优化,改进通信稳定性
* 6. 核心游戏功能:
* - 增加了对禁手规则的支持,防止玩家进行无意义的走法。
* - 新增了游戏计时器功能,限制每回合的思考时间。
* - 添加了复盘功能,支持保存和回顾对局记录。
* - 实现了评分系统,可以对每一步棋进行评分和分析。
* 7. 性能优化:
* - 🚀 优化了AI算法,使用Alpha-Beta剪枝提高搜索效率
* - 🎨 改进了棋盘渲染算法,减少了不必要的重绘操作
* - 💾 增加了内存管理优化,避免内存泄漏问题
* - ⚡ 网络通信优化,支持异步消息处理
* - 🔍 智能评分算法优化,提升AI决策质量
* 8. 用户界面改进:
* - 🎮 美化了游戏界面,增加了更多的视觉效果
* - ⌨️ 改进了用户交互体验,增加了快捷键支持
* - 🔊 添加了音效和背景音乐,提升游戏沉浸感
* - 💬 网络对战聊天界面,支持实时交流
* - 📊 游戏状态显示优化,清晰展示连接状态
* 9. 代码结构优化:
* - 🏗️ 重构了代码架构,提高了代码的可读性和可维护性
* - 📝 增加了详细的注释和文档,便于理解和修改
* - 🧩 采用了模块化设计,各功能模块相对独立
* - 🌍 新增网络模块,完整的网络通信架构
* - 🔧 全局状态统一管理,消除代码重复
* - 📋 配置文件标准化,支持灵活配置
* 10. 异常处理:
* - 🛡️ 增加了输入错误的异常处理机制,确保游戏的稳定性
* - 💡 优化了错误提示信息,帮助用户快速定位问题
* - 🔄 增加了程序崩溃恢复功能,提高游戏的可靠性
* - 🌐 网络连接异常处理,自动重连和超时管理
* - 📡 消息传输错误处理,确保数据完整性
* 11. 文档更新:
* - 📚 更新了README文件,提供详细的安装和使用说明
* - 💬 增加了代码注释,提高代码的可读性
* - 👨‍💻 添加了开发者文档,便于后续的功能扩展
* - 🌐 新增网络对战使用指南和配置说明
* - 🔧 API文档完善,支持二次开发
* 12. 版本控制:
* - 📦 使用Git进行版本控制,便于代码管理和协作开发
* - 🚀 建立了清晰的版本发布流程,确保代码质量
* - 🏷️ v7.0版本更新,代码架构全面重构
* - 🏷️ v6.1版本更新,网络功能完善优化
* - 📋 完整的变更日志,追踪功能演进
* 13. 测试:
* - ✅ 进行了全面的功能测试,确保各项功能正常运行
* - 🧪 增加了单元测试,提高代码的可靠性
* - ⚡ 进行了性能测试,优化了程序的运行效率
* - 🌐 网络功能压力测试,确保多人对战稳定性
* - 🔒 安全性测试,验证网络通信安全
* - 🔄 协议兼容性测试,确保通信协议稳定
* 14. 开源协议:
* - 📄 选择了MIT开源协议,允许用户自由使用、修改和分发代码
* - 🤝 欢迎社区贡献,共同完善项目
* 15. 贡献者:
* - 👨‍💻 感谢所有为项目做出贡献的开发者和用户
* - 🌟 特别感谢网络功能开发、测试和优化的贡献者
* 16. 联系信息:
* - 📧 如有问题或建议,请联系开发者:
* - 3364451258@qq.com
* - 15236416560@163.com
* - lhy3364451258@outlook.com
* - 🐛 Bug报告和功能建议欢迎通过邮件反馈
* - 💡 网络对战相关问题请详细描述网络环境和连接状态
*/
-71
View File
@@ -1,71 +0,0 @@
项目要求文档 - 五子棋游戏 (v8.3)
1. 项目概述
- 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。
- v8.3版本(当前版本)引入了IUP图形库,实现了轻量级、跨平台的图形界面。
- v8.2版本完善了专业安装包制作体系,提供企业级的软件分发解决方案。
- v8.0版本实现双版本架构:支持命令行界面和GUI图形化界面。
- 包括游戏配置、记录保存、复盘功能和专业安装包。
- 提供现代化的可视化用户体验和传统控制台体验。
2. 功能需求
- **游戏模式**
- 单人模式:玩家 vs AI
- 双人模式:本地两人对战
- 网络模式:通过TCP/IP进行远程对战
- **棋盘和规则**
- 默认15x15棋盘,支持自定义大小(5-25)
- 支持禁手规则选项
- 先手黑子,五连珠获胜
- **AI功能**
- 实现AI算法,支持不同难度级别(1-5级)
- AI应能计算最佳落子位置
- 支持Alpha-Beta剪枝算法和威胁检测
- **配置管理**
- 支持修改棋盘大小、禁手规则、计时器、网络端口等
- 配置保存到INI文件
- 默认配置和重置功能
- 统一的输入验证机制
- **游戏记录**
- 自动保存对局记录到CSV文件
- 支持复盘功能,查看历史对局
- 记录包含时间戳、玩家信息、棋局步骤等
- **用户界面**
- **控制台界面**:传统命令行界面显示棋盘
- **图形化界面**(v8.3新增IUP):基于IUP的轻量级GUI界面
- **鼠标交互**:支持点击落子操作
- **窗口管理**:自动居中、响应式设计
- **事件驱动**:流畅的用户交互体验
- 支持坐标输入落子(数字格式:行 列)
- 显示当前玩家、计时、游戏状态等信息
- 菜单系统和配置界面
- **网络功能**
- 支持服务器/客户端模式
- 实时传输棋子位置
- 超时和断线处理
- 支持端口配置
3. 技术要求
- 使用C语言开发
- Windows平台,包含Winsock网络库(-lws2_32链接)
- **IUP图形库**(v8.3新增):用于GUI界面开发,替代原SDL3方案
- **双版本架构**:控制台版本和GUI版本并行支持
- 模块化设计:分离游戏逻辑、AI、配置、网络、UI、GUI等模块
- **安装包制作**:支持Inno Setup和NSIS专业安装程序
- **构建系统**:使用Makefile管理编译流程
- 错误处理和统一的输入验证
- 支持跨平台编译(Windows/Linux
4. 非功能需求
- 性能:响应时间<1秒(AI计算除外)
- 可维护性:代码模块化,注释清晰
- 兼容性:Windows 11,支持GCC编译器
- 可扩展性:易于添加新的AI算法和游戏模式
- 部署便捷:依赖库本地化,开箱即用
5. 交付物
- 源代码文件(.c/.h文件)
- 配置文件(gobang_config.ini
- 记录文件夹(records/
- 文档:README.md、AI增强指南、架构重构指南、网络功能说明、图标指南等
- 代码统计报告和项目简介
Binary file not shown.
BIN
View File
Binary file not shown.
+3
View File
@@ -16,3 +16,6 @@ NETWORK_PORT=8888
# 网络超时时间 (毫秒) # 网络超时时间 (毫秒)
NETWORK_TIMEOUT=5000 NETWORK_TIMEOUT=5000
# AI难度 (1-5)
AI_DIFFICULTY=3
-30
View File
@@ -153,34 +153,4 @@ void save_game_config();
*/ */
void reset_to_default_config(); void reset_to_default_config();
/**
* @brief 显示当前配置
*/
void display_current_config();
/**
* @brief 配置棋盘大小
*/
void config_board_size();
/**
* @brief 配置禁手规则
*/
void config_forbidden_moves();
/**
* @brief 配置计时器
*/
void config_timer();
/**
* @brief 配置网络参数
*/
void config_network();
/**
* @brief 配置管理主菜单
*/
void config_management_menu();
#endif // CONFIG_H #endif // CONFIG_H
-108
View File
@@ -1,108 +0,0 @@
/**
* @file game_mode.h
* @brief 五子棋游戏框架头文件
* @note 本文件定义了五子棋游戏的四种主要模式:
* 1. AI对战模式
* 2. 双人对战模式
* 3. 网络对战模式
* 4. 复盘模式
*/
#ifndef GAME_MODE_H
#define GAME_MODE_H
#include "gobang.h"
#include "config.h"
/**
* @brief 从用户获取整数输入
*
* @param prompt 提示信息
* @param min 最小值
* @param max 最大值
* @return int 输入的整数
*/
int get_integer_input(const char *prompt, int min, int max);
/**
* @brief 处理玩家回合
*
* @param x 玩家输入的横坐标
* @param y 玩家输入的纵坐标
* @return true 输入有效
* @return false 输入无效
*/
bool parse_player_input(int *x, int *y);
/**
* @brief 解析网络对战模式下的玩家输入
* @param x 行坐标指针
* @param y 列坐标指针
* @return true 有效坐标输入
* @return false 特殊命令或无效输入
*/
bool parse_network_player_input(int *x, int *y);
/**
* @brief 处理AI回合
*
* @param current_player 当前玩家
*/
bool handle_player_turn(int current_player);
/**
* @brief AI对战模式
* 实现玩家与AI的对战逻辑
*/
void run_ai_game();
/**
* @brief 双人对战模式
* 实现两个玩家之间的对战逻辑
*/
void run_pvp_game();
/**
* @brief 复盘模式
* 加载并重现历史对局
*/
void run_review_mode();
/**
* @brief 网络对战模式
* 实现两台设备之间的在线对战
*/
void run_network_game();
/**
* @brief 处理网络玩家回合
* @param current_player 当前玩家
* @param is_local_turn 是否为本地玩家回合
* @return true 回合处理成功
* @return false 游戏结束或网络错误
*/
bool handle_network_player_turn(int current_player, bool is_local_turn);
/**
* @brief 网络游戏主循环
* @return true 游戏正常结束
* @return false 网络错误
*/
bool network_game_loop();
/**
* @brief 显示游戏规则
*/
void show_game_rules();
/**
* @brief 显示关于游戏信息
*/
void show_about_game();
/**
* @brief 启动图形化界面
*/
void run_gui_mode();
#endif // GAME_MODE_H
+5
View File
@@ -20,6 +20,11 @@
// 函数原型 // 函数原型
// --- 游戏核心逻辑 --- // --- 游戏核心逻辑 ---
/**
* @brief 初始化棋盘,将所有位置设置为空(EMPTY)
*/
void empty_board();
/** /**
* @brief 检查指定坐标是否为有效落子点(在棋盘内且为空) * @brief 检查指定坐标是否为有效落子点(在棋盘内且为空)
* @param x 待检查的行坐标 (0-based) * @param x 待检查的行坐标 (0-based)
+12
View File
@@ -81,4 +81,16 @@ void start_pve_game_gui();
*/ */
void start_replay_gui(); void start_replay_gui();
/**
* @brief 启动图形化界面模式
* @note 替代原来的 main 函数中的 GUI 分支逻辑
*/
int init_gui(); // Already declared
/**
* @brief 运行图形化界面模式
* @details 主循环处理事件、渲染画面和更新状态
*/
void run_gui_mode();
#endif // GUI_H #endif // GUI_H
-42
View File
@@ -1,42 +0,0 @@
/**
* @file init_board.h
* @brief 初始化游戏棋盘头文件
* @note 本文件定义了初始化游戏棋盘的相关函数和全局变量。
* 它负责设置游戏的初始状态,包括棋盘大小、玩家标识、游戏规则等。
*/
#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
-12
View File
@@ -10,12 +10,6 @@
#include "gobang.h" #include "gobang.h"
// --- 复盘与记录功能 --- // --- 复盘与记录功能 ---
/**
* @brief 进入复盘流程,回顾整局游戏
* @param game_mode 游戏模式(1为人机对战,2为双人对战)
*/
void review_process(int game_mode);
/** /**
* @brief 将当前对局记录保存到文件 * @brief 将当前对局记录保存到文件
* @param filename 要保存到的文件名 * @param filename 要保存到的文件名
@@ -24,12 +18,6 @@ void review_process(int game_mode);
*/ */
int save_game_to_file(const char *filename, int game_mode); int save_game_to_file(const char *filename, int game_mode);
/**
* @brief 处理保存游戏记录的逻辑
* @param game_mode 游戏模式
*/
void handle_save_record(int game_mode);
/** /**
* @brief 从文件加载游戏记录 * @brief 从文件加载游戏记录
* @param filename 要加载的文件名 * @param filename 要加载的文件名
-69
View File
@@ -1,69 +0,0 @@
/**
* @file ui.h
* @brief
* @note 本文件定义了用户界面相关的函数和数据结构。
* 它负责处理用户输入、显示游戏界面、提示信息等与用户交互的功能。
*/
#ifndef UI_H
#define UI_H
#include "gobang.h"
/**
* @brief UI模块 - 用户界面相关功能
* @author 刘航宇
* @date 2025-07-10
* @version 5.0
*/
/**
* @brief 显示游戏主菜单
*/
void display_main_menu();
/**
* @brief 显示棋盘
*/
void display_board();
/**
* @brief 显示游戏状态信息
* @param current_player 当前玩家
* @param step_count 当前步数
*/
void display_game_status(int current_player, int step_count);
/**
* @brief 显示获胜信息
* @param winner 获胜者
*/
void display_winner(int winner);
/**
* @brief 显示游戏设置菜单
*/
void display_settings_menu();
/**
* @brief 清屏函数
*/
void clear_screen();
/**
* @brief 暂停等待用户输入
* @param prompt 提示信息
*/
void pause_for_input(const char* prompt);
/**
* @brief 显示游戏规则
*/
void display_game_rules();
/**
* @brief 显示关于信息
*/
void display_about();
#endif // UI_H
-127
View File
@@ -5,8 +5,6 @@
*/ */
#include "config.h" #include "config.h"
#include "ui.h"
#include "game_mode.h"
#include "globals.h" #include "globals.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -132,128 +130,3 @@ void reset_to_default_config()
printf("已重置为默认配置\n"); printf("已重置为默认配置\n");
} }
/**
* @brief 显示当前配置
*/
void display_current_config()
{
printf("\n===== 当前游戏配置 =====\n");
printf("棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
printf("禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
printf("计时器: %s\n", use_timer ? "开启" : "关闭");
if (use_timer)
{
printf("时间限制: %d 分钟\n", time_limit / 60);
}
printf("网络端口: %d\n", network_port);
printf("网络超时: %d 毫秒\n", network_timeout);
printf("=====================\n");
}
/**
* @brief 配置棋盘大小
*/
void config_board_size()
{
printf("\n当前棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
int new_size = get_integer_input("请输入新的棋盘大小: ", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
BOARD_SIZE = new_size;
printf("棋盘大小已设置为: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
}
/**
* @brief 配置禁手规则
*/
void config_forbidden_moves()
{
printf("\n当前禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
int choice = get_integer_input("是否启用禁手规则?(1=开启, 0=关闭): ", 0, 1);
use_forbidden_moves = (choice != 0);
printf("禁手规则已%s\n", use_forbidden_moves ? "开启" : "关闭");
}
/**
* @brief 配置计时器
*/
void config_timer()
{
printf("\n当前计时器: %s\n", use_timer ? "开启" : "关闭");
int choice = get_integer_input("是否启用计时器?(1=开启, 0=关闭): ", 0, 1);
use_timer = choice;
if (use_timer)
{
int new_limit = get_integer_input("请输入时间限制(分钟): ", 1, 999);
time_limit = new_limit * 60; // 转换为秒数存储
printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60);
}
else
{
printf("计时器已关闭\n");
}
}
/**
* @brief 配置网络参数
*/
void config_network()
{
printf("\n===== 网络配置 =====\n");
printf("当前网络端口: %d\n", network_port);
printf("当前网络超时: %d 毫秒\n", network_timeout);
int new_port = get_integer_input("请输入新的网络端口: ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
network_port = new_port;
printf("网络端口已设置为: %d\n", network_port);
int new_timeout = get_integer_input("请输入网络超时时间(毫秒, 建议1000-10000): ", 1000, 60000);
network_timeout = new_timeout;
printf("网络超时已设置为: %d 毫秒\n", network_timeout);
}
/**
* @brief 配置管理主菜单
*/
void config_management_menu()
{
int choice;
while (1)
{
clear_screen();
display_settings_menu();
display_current_config();
choice = get_integer_input("请选择操作(0-5): ", 0, 5);
switch (choice)
{
case 1:
config_board_size();
break;
case 2:
config_forbidden_moves();
break;
case 3:
config_timer();
break;
case 4:
config_network();
break;
case 5:
printf("AI难度设置功能开发中...\n");
break;
case 0:
save_game_config();
return;
default:
printf("无效的选择!\n");
break;
}
pause_for_input("按任意键继续...");
}
}
-971
View File
@@ -1,971 +0,0 @@
/**
* @file game_mode.c
* @brief 五子棋游戏框架源文件
* @note 本文件定义了五子棋游戏的四种主要模式:
* 1. AI对战模式
* 2. 双人对战模式
* 3. 网络对战模式
* 4. 复盘模式
*/
#include "game_mode.h"
#include "init_board.h"
#include "gobang.h"
#include "ai.h"
#include "record.h"
#include "config.h"
#include "network.h"
#include "ui.h"
#include "gui.h"
#include "globals.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ctype.h>
// 全局变量现在在globals.c中定义
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <conio.h>
#endif
/**
* @brief 从用户获取整数输入
*
* @param prompt 提示信息
* @param min 最小值
* @param max 最大值
* @return int 用户输入的整数
*/
int get_integer_input(const char *prompt, int min, int max)
{
int value;
int result;
char ch;
while (1)
{
printf("%s", prompt);
result = scanf("%d", &value);
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);
}
}
}
/**
* @brief 处理玩家回合
*
* @param current_player
* @return true
* @return false
*/
bool parse_player_input(int *x, int *y)
{
char input[10];
while (1)
{
if (_kbhit())
{
scanf("%s", input);
break;
}
Sleep(100); // 短暂延迟以防止CPU占用过高
}
if (sscanf(input, "%d", x) == 1)
{
// 成功解析第一个数字,现在解析第二个
if (scanf("%d", y) != 1)
{
// 如果第二个数字不可用,则检查特殊命令
if (*x == INPUT_UNDO)
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
if (return_move(steps_to_undo * 2))
{
printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
}
else
{
printf("无法悔棋!\n");
}
return 0; // 特殊命令已处理
}
else if (*x == INPUT_SAVE)
{
// ... 处理保存 ...
return false; // 特殊命令
}
else if (*x == INPUT_EXIT)
{
// ... 处理退出 ...
return false; // 特殊命令
}
printf("无效输入,请输入两个数字坐标。\n");
while (getchar() != '\n')
;
return 0; // 无效输入
}
}
else
{
// sscanf失败,检查特殊命令
if (input[0] == 'r' || input[0] == 'R')
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
if (return_move(steps_to_undo * 2))
{
printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
}
else
{
printf("无法悔棋!\n");
}
return false; // 特殊命令
}
else if (input[0] == 's' || input[0] == 'S')
{
*x = INPUT_SURRENDER;
int confirm = get_integer_input("确认认输?(1:是/0:否): ", 0, 1);
if (confirm)
{
printf("玩家选择认输!\n");
return 1; // 正常回合完成 // 返回认输命令
}
else
{
printf("取消认输!\n");
return false; // 取消认输
}
}
printf("无效输入,请输入数字坐标、'r'悔棋或's'认输。\n");
return 0; // 无效输入
}
return 1; // 有效坐标
}
/**
* @brief 解析网络对战模式下的玩家输入
* @param x 行坐标指针
* @param y 列坐标指针
* @return true 有效坐标输入
* @return false 特殊命令或无效输入
*/
bool parse_network_player_input(int *x, int *y)
{
char input[10];
while (1)
{
if (_kbhit())
{
scanf("%s", input);
break;
}
Sleep(100); // 短暂延迟以防止CPU占用过高
}
if (sscanf(input, "%d", x) == 1)
{
// 成功解析第一个数字,现在解析第二个
if (scanf("%d", y) != 1)
{
printf("无效输入,请输入两个数字坐标。\n");
while (getchar() != '\n')
;
return false; // 无效输入
}
}
else
{
// sscanf失败,检查特殊命令
if (input[0] == 'r' || input[0] == 'R')
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
printf("发送悔棋请求给对方...\n");
if (send_undo_request(steps_to_undo))
{
printf("悔棋请求已发送,等待对方回应...\n");
// 等待对方回应
NetworkMessage msg;
time_t start_time = time(NULL);
while (difftime(time(NULL), start_time) < 30) // 30秒超时
{
if (receive_network_message(&msg, 1000))
{
if (msg.type == MSG_UNDO_RESPONSE && msg.x == steps_to_undo)
{
if (msg.y == 1) // 对方同意
{
if (return_move(steps_to_undo * 2))
{
printf("对方同意悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
return 2; // 悔棋成功,需要重新开始回合
}
else
{
printf("悔棋失败!\n");
return 0; // 悔棋失败,继续当前回合
}
}
else // 对方拒绝
{
printf("对方拒绝了悔棋请求。\n");
return 0; // 悔棋被拒绝,继续当前回合
}
}
}
if (!is_network_connected())
{
printf("网络连接断开\n");
return 0;
}
}
printf("悔棋请求超时,对方未回应。\n");
}
else
{
printf("发送悔棋请求失败!\n");
}
return 0; // 特殊命令已处理
}
else if (input[0] == 's' || input[0] == 'S')
{
*x = INPUT_SURRENDER;
int confirm = get_integer_input("确认认输?(1:是/0:否): ", 0, 1);
if (confirm)
{
printf("你选择认输!\n");
*x = INPUT_SURRENDER;
return -1; // 返回认输命令
}
else
{
printf("取消认输!\n");
return 0; // 取消认输
}
}
printf("无效输入,请输入数字坐标、'r'悔棋或's'认输。\n");
return false; // 无效输入
}
return true; // 有效坐标
}
/**
* @brief 处理玩家回合
* @param current_player 当前玩家
* @return true
*/
bool handle_player_turn(int current_player)
{
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n玩家%d, 请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输:", current_player, BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\n玩家%d超时, 对方获胜!\n", current_player);
return false; // Timeout
}
}
if (parse_player_input(&x, &y))
{
if (x == INPUT_SURRENDER)
{
printf("\n玩家%d选择认输,对方获胜!\n", current_player);
return false; // 游戏结束,认输
}
input_received = true;
}
else
{
// 已处理特殊命令或无效输入,继续循环
return true;
}
}
x--;
y--;
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
}
else
{
printf("坐标无效!请重新输入。\n");
// 继续循环,重新输入坐标
}
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n玩家%d获胜!\n", current_player);
return false; // 游戏结束
}
return true; // 成功落子
}
/**
* @brief 运行AI游戏
* @note 从文件中加载历史记录并进行复盘
* @param AI_DEPTH AI的搜索深度
*/
void run_ai_game()
{
// 重置评分计算标志,确保每局游戏都会重新计算评分
scores_calculated = 0;
// AI对战模式
int AI_DEPTH = DEFAULT_AI_DEPTH;
AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5);
/**
* @brief AI的防守系数,系数越大越倾向于防守
* @note 1~1.5
* 2~1.6
* 3~1.7
* 4~1.8
* 5~1.9
*/
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (AI_DEPTH - 1) * 0.1;
empty_board();
int current_player = determine_first_player(PLAYER, AI);
print_board();
while (1)
{
if (current_player == PLAYER)
{
int old_step_count = step_count;
if (!handle_player_turn(current_player))
{
break; // 游戏结束或超时
}
if (step_count > old_step_count)
{
// 检查玩家是否获胜
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, PLAYER))
{
printf("\n玩家获胜!\n");
break;
}
current_player = AI;
}
}
else
{
printf("\nAI思考中...\n");
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
ai_move(AI_DEPTH);
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\nAI超时, 玩家获胜!\n");
break;
}
}
print_board();
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, AI))
{
printf("\nAI获胜!\n");
break;
}
current_player = PLAYER;
}
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
break;
}
}
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_AI); // AI对战模式
handle_save_record(GAME_MODE_AI); // AI对战模式
}
/**
* @brief 运行双人对战模式
* @note 从文件中加载历史记录并进行复盘
*/
void run_pvp_game()
{
// 重置评分计算标志,确保每局游戏都会重新计算评分
scores_calculated = 0;
// 双人对战模式
empty_board();
int current_player = determine_first_player(PLAYER1, PLAYER2);
print_board();
while (1)
{
int old_step_count = step_count;
if (!handle_player_turn(current_player))
{
break; // 游戏结束或超时
}
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
break;
}
if (step_count > old_step_count)
{
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
}
}
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_PVP); // 双人对战模式
handle_save_record(GAME_MODE_PVP); // 双人对战模式
}
/**
* @brief 运行复盘模式
* @note 从文件中加载历史记录并进行复盘
*/
void run_review_mode()
{
char filename[100];
char record_files[100][100];
int file_count = 0;
#ifdef _WIN32
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile("records\\*", &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
strcpy(record_files[file_count++], ffd.cFileName);
}
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
#endif
if (file_count > 0)
{
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);
int choice = get_integer_input(prompt, 0, file_count);
if (choice > 0)
{
strcpy(filename, record_files[choice - 1]);
}
else
{
printf("请输入完整文件名: ");
scanf("%s", filename);
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
int possible_choice = atoi(filename);
if (possible_choice > 0 && possible_choice <= file_count)
{
strcpy(filename, record_files[possible_choice - 1]);
}
}
}
else
{
printf("未找到任何复盘文件,请输入复盘文件地址: ");
scanf("%s", filename);
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
}
int game_mode = load_game_from_file(filename);
if (game_mode != 0)
{
if (game_mode == 1)
{
printf("加载AI对战模式复盘文件成功!\n");
}
else if (game_mode == 2)
{
printf("加载双人对战模式复盘文件成功!\n");
}
review_process(game_mode);
}
else
{
printf("加载复盘文件失败!可能是旧版本文件格式或文件损坏\n");
}
}
/**
* @brief 网络对战模式
*/
void run_network_game()
{
// 重置评分计算标志
scores_calculated = 0;
// 初始化网络模块
if (!init_network())
{
printf("网络初始化失败!\n");
pause_for_input("按任意键返回主菜单...");
return;
}
printf("=== 网络对战模式 ===\n");
printf("1. 创建房间(作为主机)\n");
printf("2. 加入房间(连接到主机)\n");
int choice = get_integer_input("请选择模式(1-2): ", 1, 2);
bool connection_success = false;
if (choice == 1)
{
// 服务器模式
int port = get_integer_input("请输入监听端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0)
port = network_port;
printf("\n正在创建房间...\n");
connection_success = create_server(port);
}
else
{
// 客户端模式
char ip[MAX_IP_LENGTH];
// 循环直到输入有效的IP地址或用户选择退出
while (1)
{
printf("请输入服务器IP地址 (输入'exit'退出): ");
if (scanf("%s", ip) != 1)
{
printf("输入错误,请重新输入。\n");
// 清除输入缓冲区
while (getchar() != '\n')
;
continue;
}
// 检查是否要退出
if (strcmp(ip, "exit") == 0 || strcmp(ip, "EXIT") == 0)
{
printf("取消连接,返回主菜单。\n");
cleanup_network();
pause_for_input("按任意键返回主菜单...");
return;
}
// 简单的IP地址格式验证
if (strlen(ip) < 7 || strlen(ip) > 15)
{
printf("IP地址格式错误!请输入有效的IP地址(如:192.168.1.100\n");
continue;
}
// 检查IP地址是否包含有效字符
bool valid_ip = true;
for (int i = 0; i < strlen(ip); i++)
{
if (!(isdigit(ip[i]) || ip[i] == '.'))
{
valid_ip = false;
break;
}
}
if (!valid_ip)
{
printf("IP地址格式错误!只能包含数字和点号。\n");
continue;
}
// 检查点号数量
int dot_count = 0;
for (int i = 0; i < strlen(ip); i++)
{
if (ip[i] == '.')
dot_count++;
}
if (dot_count != 3)
{
printf("IP地址格式错误!应包含3个点号(如:192.168.1.100\n");
continue;
}
printf("输入的IP地址: %s\n", ip);
int confirm = get_integer_input("确认连接到此IP?(1:是/0:否,重新输入): ", 0, 1);
if (confirm)
{
break; // 确认IP地址,跳出循环
}
// 如果选择否,继续循环重新输入
}
int port = get_integer_input("请输入服务器端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0)
port = network_port;
printf("\n正在连接到服务器 %s:%d...\n", ip, port);
connection_success = connect_to_server(ip, port);
}
if (!connection_success)
{
printf("网络连接失败!\n");
cleanup_network();
pause_for_input("按任意键返回主菜单...");
return;
}
printf("\n网络连接成功!游戏即将开始...\n");
printf("你是玩家%d%s先手\n",
network_state.local_player_id,
network_state.local_player_id == PLAYER1 ? "" : "对方");
// 开始网络游戏
empty_board();
print_board();
if (network_game_loop())
{
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_NETWORK); // 网络对战模式的复盘
handle_save_record(GAME_MODE_NETWORK); // 保存为网络对战模式记录
}
else
{
printf("游戏因网络错误而结束\n");
}
// 清理网络连接
disconnect_network();
pause_for_input("按任意键返回主菜单...");
}
/**
* @brief 显示游戏规则
*/
void show_game_rules()
{
clear_screen();
display_game_rules();
pause_for_input("\n按任意键返回主菜单...");
}
/**
* @brief 显示关于游戏信息
*/
void show_about_game()
{
clear_screen();
display_about();
pause_for_input("\n按任意键返回主菜单...");
}
/**
* @brief 启动图形化界面
*/
void run_gui_mode()
{
if (init_gui() == 0)
{
printf("启动图形化界面...\n");
printf("图形化界面已启动,窗口应该可见\n");
printf("如果看不到窗口,请检查任务栏或按Alt+Tab切换\n");
while (gui_running && handle_events())
{
render_game();
}
printf("退出图形化界面\n");
cleanup_gui();
}
else
{
printf("图形化界面启动失败!请检查SDL3库是否正确安装。\n");
pause_for_input("按任意键返回主菜单...");
}
}
/**
* @brief 处理网络玩家回合
*/
bool handle_network_player_turn(int current_player, bool is_local_turn)
{
if (is_local_turn)
{
// 本地玩家回合
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n轮到你了,请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输: ", BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\n你超时了,对方获胜!\n");
send_surrender(); // 发送认输消息
return 0; // 游戏结束
}
}
int parse_result = parse_network_player_input(&x, &y);
if (parse_result == 1) // 有效坐标输入
{
input_received = true;
}
else if (parse_result == -1) // 认输命令
{
printf("\n你选择认输,对方获胜!\n");
send_surrender();
return 0; // 游戏结束
}
else if (parse_result == 2) // 悔棋成功
{
return 2; // 悔棋发生,需要重新开始回合
}
else // parse_result == 0, 特殊命令已处理或无效输入
{
// 继续等待输入
continue;
}
}
x--;
y--; // 转换为0-based坐标
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
}
else
{
printf("坐标无效!请重新输入。\n");
// 继续循环,重新输入坐标
}
}
// 发送落子消息
if (!send_move(x, y, current_player))
{
printf("发送落子消息失败!\n");
return 0; // 游戏结束
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n你获胜了!\n");
return 0; // 游戏结束
}
}
else
{
// 等待对方落子
printf("\n等待对方落子...\n");
NetworkMessage msg;
time_t start_time = time(NULL);
while (1)
{
if (receive_network_message(&msg, 1000))
{
// 1秒超时
if (msg.type == MSG_MOVE && msg.player_id == current_player)
{
// 收到落子消息
if (!player_move(msg.x, msg.y, current_player))
{
printf("收到无效的落子坐标!\n");
return 0; // 游戏结束
}
printf("对方落子: (%d, %d)\n", msg.x + 1, msg.y + 1);
print_board();
if (check_win(msg.x, msg.y, current_player))
{
printf("\n对方获胜!\n");
return 0; // 游戏结束
}
break;
}
else if (msg.type == MSG_SURRENDER)
{
printf("\n对方认输,你获胜了!\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_DISCONNECT)
{
printf("\n对方已断开连接\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_CHAT)
{
printf("[对方]: %s\n", msg.message);
}
else if (msg.type == MSG_UNDO_REQUEST)
{
int steps = msg.x;
printf("\n对方请求悔棋 %d 步,是否同意?(1:同意/0:拒绝): ", steps);
int response = get_integer_input("", 0, 1);
if (response && return_move(steps * 2))
{
printf("同意悔棋,双方各退 %d 步\n", steps);
send_undo_response(true, steps);
print_board();
// 悔棋后需要重新开始当前回合,不改变current_player
return 2; // 悔棋发生,需要重新开始回合
}
else
{
printf("拒绝悔棋\n");
send_undo_response(false, steps);
// 继续等待对方落子
}
}
}
// 检查超时
if (use_timer && difftime(time(NULL), start_time) > time_limit)
{
printf("\n对方超时,你获胜!\n");
return 0; // 游戏结束
}
// 检查网络连接
if (!is_network_connected())
{
printf("\n网络连接断开\n");
return 0; // 游戏结束
}
}
}
return 1; // 正常回合完成
}
/**
* @brief 网络游戏主循环
*/
bool network_game_loop()
{
int current_player = PLAYER1; // 总是从玩家1开始
while (1)
{
bool is_local_turn = (current_player == network_state.local_player_id);
int turn_result = handle_network_player_turn(current_player, is_local_turn);
if (turn_result == 0) // 游戏结束
{
return true;
}
else if (turn_result == 2) // 悔棋发生,重新开始当前回合
{
continue; // 不切换玩家,重新开始当前回合
}
// 检查平局
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
return true;
}
// 切换玩家
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
// 检查网络连接
if (!is_network_connected())
{
printf("\n网络连接断开\n");
return false;
}
}
return true;
}
+17 -2
View File
@@ -5,8 +5,6 @@
* AI决策等功能 * AI决策等功能
*/ */
#include "game_mode.h"
#include "init_board.h"
#include "gobang.h" #include "gobang.h"
#include "ai.h" #include "ai.h"
#include "record.h" #include "record.h"
@@ -16,6 +14,23 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h> #include <time.h>
/**
* @brief
* EMPTYstep_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, y) * @brief (x, y)
* @param x (0-base) * @param x (0-base)
+192 -49
View File
@@ -9,9 +9,7 @@
#include "gui.h" #include "gui.h"
#include <iup.h> #include <iup.h>
#include <iupdraw.h> #include <iupdraw.h>
#include "ui.h"
#include "globals.h" #include "globals.h"
#include "init_board.h"
#include "gobang.h" #include "gobang.h"
#include "gui_menu.h" #include "gui_menu.h"
#include "ai.h" #include "ai.h"
@@ -26,13 +24,15 @@ static Ihandle *lbl_player = NULL;
static Ihandle *lbl_status = NULL; static Ihandle *lbl_status = NULL;
static int gui_loop_running = 0; static int gui_loop_running = 0;
static int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay static int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay
static int replay_total_steps = 0; // For Replay Mode static int replay_total_steps = 0; // 为了复盘而记录的总步数
// 回调函数 // 回调函数
static int action_cb(Ihandle *ih); static int action_cb(Ihandle *ih);
static int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status); static int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status);
static int k_any_cb(Ihandle *ih, int c); static int k_any_cb(Ihandle *ih, int c);
void create_game_window(); // 移除前向声明 void create_game_window(); // 移除前向声明
static int btn_replay_sel_ok_cb(Ihandle *ih);
static int btn_replay_sel_cancel_cb(Ihandle *ih);
// 辅助函数:设置绘图颜色 // 辅助函数:设置绘图颜色
static void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b) static void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b)
@@ -123,7 +123,7 @@ static void draw_stones_iup(Ihandle *ih)
} }
// 标记最后落子位置 (红色小点) // 标记最后落子位置 (红色小点)
if (step_count > 0) if (step_count > 0 && step_count <= MAX_STEPS)
{ {
// 绘制最后一步的标记 // 绘制最后一步的标记
// 最后一步的坐标是 steps[step_count-1] // 最后一步的坐标是 steps[step_count-1]
@@ -179,6 +179,17 @@ static void update_ui_labels()
} }
} }
/**
* @brief
*/
void show_message(const char *message)
{
strncpy(status_message, message, sizeof(status_message) - 1);
status_message[sizeof(status_message) - 1] = '\0';
update_ui_labels();
printf("%s\n", message);
}
// ACTION 回调:负责重绘 // ACTION 回调:负责重绘
static int action_cb(Ihandle *ih) static int action_cb(Ihandle *ih)
{ {
@@ -294,7 +305,7 @@ static int btn_replay_prev_cb(Ihandle *ih)
static int btn_replay_next_cb(Ihandle *ih) static int btn_replay_next_cb(Ihandle *ih)
{ {
(void)ih; (void)ih;
if (step_count < replay_total_steps) if (step_count < replay_total_steps && step_count >= 0) // Ensure step_count is valid
{ {
Step s = steps[step_count]; Step s = steps[step_count];
board[s.x][s.y] = s.player; board[s.x][s.y] = s.player;
@@ -310,12 +321,29 @@ static int btn_replay_next_cb(Ihandle *ih)
static int btn_back_cb(Ihandle *ih) static int btn_back_cb(Ihandle *ih)
{ {
(void)ih; (void)ih;
printf("DEBUG: Back to Menu clicked\n");
// 1. Show main menu FIRST
show_main_menu();
printf("DEBUG: Main menu shown\n");
// 2. Destroy game window
// Important: We must destroy the game window to free resources and potentially stop its event loop contribution.
// But we must NOT close the application.
// If we only Hide, it stays in memory.
// If we Destroy, it's gone.
if (dlg) if (dlg)
{ {
IupHide(dlg); // IupHide(dlg); // Hiding is safer if Destroy causes loop exit
show_main_menu(); // printf("DEBUG: Hiding game window\n");
Ihandle *old_dlg = dlg;
dlg = NULL; // Clear global pointer first
IupDestroy(old_dlg);
printf("DEBUG: Destroyed game window\n");
} }
return IUP_DEFAULT;
return IUP_IGNORE; // Return IUP_IGNORE to prevent default processing (like closing if CLOSE_CB)
} }
// 鼠标点击回调 // 鼠标点击回调
@@ -421,14 +449,27 @@ static int k_any_cb(Ihandle *ih, int c)
// 创建游戏窗口 // 创建游戏窗口
void create_game_window() void create_game_window()
{ {
printf("DEBUG: create_game_window start\n");
// if (dlg)
// {
// IupDestroy(dlg); // 销毁旧窗口
// dlg = NULL;
// }
// Only destroy if it exists and is not the current dialog?
// Actually, creating a new dialog while old one is valid is fine, but we should destroy old one to save memory.
// However, if destroying dlg causes main loop to exit because it was the last visible window (if main menu was hidden)...
if (dlg) if (dlg)
{ {
IupDestroy(dlg); // 销毁旧窗口 IupDestroy(dlg);
dlg = NULL; dlg = NULL;
} }
// 创建Canvas (Board) // 创建Canvas (Board)
board_canvas = IupCanvas(NULL); board_canvas = IupCanvas(NULL);
if (!board_canvas)
printf("ERROR: Failed to create board_canvas\n");
IupSetAttribute(board_canvas, "ACTION", "action_cb"); IupSetAttribute(board_canvas, "ACTION", "action_cb");
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb); IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb); IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
@@ -443,10 +484,10 @@ void create_game_window()
// 创建标签 (玩家信息和游戏状态) // 创建标签 (玩家信息和游戏状态)
lbl_player = IupLabel("当前玩家: 黑子"); lbl_player = IupLabel("当前玩家: 黑子");
IupSetAttribute(lbl_player, "FONT", "SimHei, 14"); // IupSetAttribute(lbl_player, "FONT", "SimHei, 14"); // Comment out potentially problematic font setting
lbl_status = IupLabel("准备开始"); lbl_status = IupLabel("准备开始");
IupSetAttribute(lbl_status, "FONT", "SimHei, 12"); // IupSetAttribute(lbl_status, "FONT", "SimHei, 12");
Ihandle *vbox_controls; Ihandle *vbox_controls;
@@ -509,10 +550,14 @@ void create_game_window()
// 创建Dialog // 创建Dialog
dlg = IupDialog(hbox_main); dlg = IupDialog(hbox_main);
if (!dlg)
printf("ERROR: Failed to create dialog\n");
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本"); IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
IupSetAttribute(dlg, "RESIZE", "NO"); IupSetAttribute(dlg, "RESIZE", "NO");
IupMap(dlg); IupMap(dlg);
printf("DEBUG: create_game_window end\n");
} }
void start_pvp_game_gui() void start_pvp_game_gui()
@@ -533,6 +578,7 @@ void start_pvp_game_gui()
void start_pve_game_gui() void start_pve_game_gui()
{ {
printf("DEBUG: start_pve_game_gui start\n");
gui_game_mode = 1; gui_game_mode = 1;
// ai_difficulty is global // ai_difficulty is global
empty_board(); empty_board();
@@ -540,67 +586,162 @@ void start_pve_game_gui()
game_over = 0; game_over = 0;
create_game_window(); create_game_window();
printf("DEBUG: create_game_window returned\n");
if (dlg)
{
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
printf("DEBUG: IupShowXY called\n");
}
else
{
printf("ERROR: dlg is NULL in start_pve_game_gui\n");
return;
}
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
sprintf(status_message, "人机对战模式 - 玩家执黑先行"); sprintf(status_message, "人机对战模式 - 玩家执黑先行");
update_ui_labels(); update_ui_labels();
printf("DEBUG: update_ui_labels returned\n");
// Force initial draw
if (board_canvas) if (board_canvas)
{
IupUpdate(board_canvas); IupUpdate(board_canvas);
// IupFlush(); // Comment out to check if this caused the crash
}
printf("DEBUG: start_pve_game_gui end\n");
} }
void start_replay_gui() #ifdef _WIN32
#include <windows.h>
#endif
// 选择复盘文件
static void select_replay_file_gui()
{ {
// Open file dialog // List files in records/ directory
Ihandle *file_dlg = IupFileDlg(); char record_files[100][100];
IupSetAttribute(file_dlg, "DIALOGTYPE", "OPEN"); int file_count = 0;
IupSetAttribute(file_dlg, "TITLE", "选择复盘文件");
IupSetAttribute(file_dlg, "FILTER", "*.csv");
IupSetAttribute(file_dlg, "FILTERINFO", "CSV Files");
IupPopup(file_dlg, IUP_CENTER, IUP_CENTER); #ifdef _WIN32
WIN32_FIND_DATA ffd;
if (IupGetInt(file_dlg, "STATUS") != -1) HANDLE hFind = FindFirstFile("records\\*.csv", &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{ {
char *filename = IupGetAttribute(file_dlg, "VALUE"); do
{
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
strcpy(record_files[file_count++], ffd.cFileName);
if (file_count >= 100)
break;
}
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
#endif
char *base_name = strrchr(filename, '\\'); if (file_count == 0)
if (!base_name) {
base_name = strrchr(filename, '/'); IupMessage("提示", "未找到复盘记录文件 (records/*.csv)");
if (base_name) show_main_menu();
base_name++; return;
else }
base_name = filename;
if (load_game_from_file(base_name)) // returns game_mode (non-zero) on success // 创建列表框
Ihandle *list = IupList(NULL);
IupSetAttribute(list, "EXPAND", "YES");
IupSetAttribute(list, "VISIBLELINES", "10");
for (int i = 0; i < file_count; i++)
{
IupSetAttributeId(list, "", i + 1, record_files[i]);
}
IupSetAttribute(list, "VALUE", "1");
// 创建确定和取消按钮
Ihandle *btn_ok = IupButton("确定", NULL);
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_replay_sel_ok_cb);
IupSetAttribute(btn_ok, "SIZE", "60x30");
Ihandle *btn_cancel = IupButton("取消", NULL);
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_replay_sel_cancel_cb);
IupSetAttribute(btn_cancel, "SIZE", "60x30");
Ihandle *vbox = IupVbox(
IupLabel("请选择复盘文件:"),
list,
IupHbox(btn_ok, btn_cancel, NULL),
NULL);
IupSetAttribute(vbox, "MARGIN", "10x10");
IupSetAttribute(vbox, "GAP", "10");
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
Ihandle *dlg_sel = IupDialog(vbox);
IupSetAttribute(dlg_sel, "TITLE", "选择复盘文件");
IupSetAttribute(dlg_sel, "MINBOX", "NO");
IupSetAttribute(dlg_sel, "MAXBOX", "NO");
IupSetAttribute(dlg_sel, "RESIZE", "NO");
// 存储列表框句柄到对话框属性
IupSetAttribute(dlg_sel, "MY_LIST", (char *)list);
}
static int btn_replay_sel_ok_cb(Ihandle *ih)
{
Ihandle *dlg = IupGetDialog(ih);
Ihandle *list = (Ihandle *)IupGetAttribute(dlg, "MY_LIST");
char *filename = IupGetAttribute(list, "VALUE"); // Returns index
int index = atoi(filename);
char *selected_file = IupGetAttributeId(list, "", index);
if (selected_file)
{
if (load_game_from_file(selected_file))
{ {
replay_total_steps = step_count; replay_total_steps = step_count;
step_count = 0; step_count = 0;
// load_game_from_file already cleared board when reading steps, but steps were read into array
// wait, load_game_from_file calls empty_board() then reads steps.
// But it doesn't set board array.
// So board is empty.
gui_game_mode = 2; // Replay gui_game_mode = 2; // Replay
create_game_window(); create_game_window();
IupShowXY(dlg, IUP_CENTER, IUP_CENTER); // 展示
Ihandle *dlg_sel = dlg;
IupHide(dlg_sel);
IupDestroy(dlg_sel);
IupShowXY(dlg, IUP_CENTER, IUP_CENTER); IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
sprintf(status_message, "复盘模式 - %s", base_name);
sprintf(status_message, "复盘模式 - %s", selected_file);
update_ui_labels(); update_ui_labels();
if (board_canvas) if (board_canvas)
IupUpdate(board_canvas); IupUpdate(board_canvas);
return IUP_DEFAULT;
} }
else else
{ {
IupMessage("错误", "无法加载复盘文件"); IupMessage("错误", "无法加载复盘文件");
show_main_menu();
} }
} }
else
{
show_main_menu();
}
IupDestroy(file_dlg); IupHide(dlg);
IupDestroy(dlg);
show_main_menu();
return IUP_DEFAULT;
}
static int btn_replay_sel_cancel_cb(Ihandle *ih)
{
Ihandle *dlg = IupGetDialog(ih);
IupHide(dlg);
IupDestroy(dlg);
show_main_menu();
return IUP_DEFAULT;
}
void start_replay_gui()
{
select_replay_file_gui();
} }
/** /**
@@ -691,12 +832,14 @@ void draw_ui_elements()
} }
/** /**
* @brief * @brief
* @note
*/ */
void show_message(const char *message) void run_gui_mode()
{ {
strncpy(status_message, message, sizeof(status_message) - 1); if (init_gui() == 0)
status_message[sizeof(status_message) - 1] = '\0'; {
update_ui_labels(); IupMainLoop(); // 使用IUP的主循环
printf("%s\n", message); cleanup_gui();
}
} }
+27 -9
View File
@@ -11,16 +11,20 @@ static Ihandle *menu_dlg = NULL;
static int btn_pvp_cb(Ihandle *ih) static int btn_pvp_cb(Ihandle *ih)
{ {
(void)ih; (void)ih;
hide_main_menu(); printf("DEBUG: Starting PvP Game\n");
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
start_pvp_game_gui(); start_pvp_game_gui();
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
return IUP_DEFAULT; return IUP_DEFAULT;
} }
static int btn_pve_cb(Ihandle *ih) static int btn_pve_cb(Ihandle *ih)
{ {
(void)ih; (void)ih;
hide_main_menu(); printf("DEBUG: Starting PvE Game\n");
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
start_pve_game_gui(); start_pve_game_gui();
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
return IUP_DEFAULT; return IUP_DEFAULT;
} }
@@ -45,22 +49,28 @@ static int btn_save_settings_cb(Ihandle *ih)
// Update globals // Update globals
int new_size = IupGetInt(txt_board_size, "VALUE"); int new_size = IupGetInt(txt_board_size, "VALUE");
if (new_size < MIN_BOARD_SIZE) new_size = MIN_BOARD_SIZE; if (new_size < MIN_BOARD_SIZE)
if (new_size > MAX_BOARD_SIZE) new_size = MAX_BOARD_SIZE; new_size = MIN_BOARD_SIZE;
if (new_size > MAX_BOARD_SIZE)
new_size = MAX_BOARD_SIZE;
BOARD_SIZE = new_size; BOARD_SIZE = new_size;
use_forbidden_moves = IupGetInt(tgl_forbidden, "VALUE"); use_forbidden_moves = IupGetInt(tgl_forbidden, "VALUE");
use_timer = IupGetInt(tgl_timer, "VALUE"); use_timer = IupGetInt(tgl_timer, "VALUE");
if (use_timer) { if (use_timer)
{
int minutes = IupGetInt(txt_time_limit, "VALUE"); int minutes = IupGetInt(txt_time_limit, "VALUE");
if (minutes < 1) minutes = 1; if (minutes < 1)
minutes = 1;
time_limit = minutes * 60; time_limit = minutes * 60;
} }
int ai_level = IupGetInt(lst_ai, "VALUE"); int ai_level = IupGetInt(lst_ai, "VALUE");
if (ai_level < 1) ai_level = 1; if (ai_level < 1)
if (ai_level > 5) ai_level = 5; ai_level = 1;
if (ai_level > 5)
ai_level = 5;
ai_difficulty = ai_level; ai_difficulty = ai_level;
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1; defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
@@ -197,7 +207,9 @@ static int btn_exit_cb(Ihandle *ih)
void create_main_menu() void create_main_menu()
{ {
if (menu_dlg) return; if (menu_dlg)
return;
printf("DEBUG: create_main_menu\n");
Ihandle *lbl_title = IupLabel("五子棋 (Gobang)"); Ihandle *lbl_title = IupLabel("五子棋 (Gobang)");
IupSetAttribute(lbl_title, "FONT", "SimHei, 24"); IupSetAttribute(lbl_title, "FONT", "SimHei, 24");
@@ -248,13 +260,19 @@ void create_main_menu()
// 设置对话框关闭回调 (点X关闭程序) // 设置对话框关闭回调 (点X关闭程序)
IupSetCallback(menu_dlg, "CLOSE_CB", (Icallback)btn_exit_cb); IupSetCallback(menu_dlg, "CLOSE_CB", (Icallback)btn_exit_cb);
IupMap(menu_dlg); // Map immediately
} }
void show_main_menu() void show_main_menu()
{ {
printf("DEBUG: show_main_menu start\n");
if (!menu_dlg) if (!menu_dlg)
{
printf("DEBUG: Creating main menu\n");
create_main_menu(); create_main_menu();
}
IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER); IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER);
printf("DEBUG: show_main_menu end\n");
} }
void hide_main_menu() void hide_main_menu()
-125
View File
@@ -1,125 +0,0 @@
/**
* @file init_board.c
* @brief
* @note
*
*/
#include "init_board.h"
#include "gobang.h"
#include "game_mode.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* @brief
* EMPTYstep_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;
}
}
+3 -60
View File
@@ -4,22 +4,16 @@
* @note * @note
* @brief powershell * @brief powershell
* *
* !
* mingw32-make console
.\bin\gobang_console.exe
*
* !IUP库 * !IUP库
* mingw32-make gui * mingw32-make gui
.\bin\gobang_gui.exe .\bin\gobang_gui.exe
* *
* @note gcc -lws2_32链接Windows网络库 * @note gcc -lws2_32链接Windows网络库
* @note IUP libs\iup-3.31_Win64_dllw6_lib * @note IUP libs\iup-3.31_Win64_dllw6_lib
* @brief & "D:\Program Files (x86)\NSIS\makensis.exe" "installer\\installer.nsi"
* @brief & "D:\Program Files (x86)\Inno Setup 6\iscc.exe" installer\\installer.iss * @brief & "D:\Program Files (x86)\Inno Setup 6\iscc.exe" installer\\installer.iss
*/ */
#include "game_mode.h" #include "gui.h"
#include "ui.h"
#include "config.h" #include "config.h"
#include <stdio.h> #include <stdio.h>
#ifdef _WIN32 #ifdef _WIN32
@@ -40,58 +34,7 @@ int main(int argc, char *argv[])
// 加载游戏配置 // 加载游戏配置
load_game_config(); load_game_config();
// 选择模式 // 启动图形化界面
while (1) run_gui_mode();
{
clear_screen();
display_main_menu();
int mode = get_integer_input("请输入模式(0-8): ", 0, 8);
switch (mode)
{
// 1. 人机对战
case 1:
run_ai_game();
break;
// 2. 玩家对战
case 2:
run_pvp_game();
break;
// 3. 网络对战
case 3:
run_network_game();
break;
// 4. 复盘模式
case 4:
run_review_mode();
break;
// 5. 配置管理
case 5:
config_management_menu();
break;
// 6. 游戏规则
case 6:
show_game_rules();
break;
// 7. 关于游戏
case 7:
show_about_game();
break;
// 8. 图形化界面
case 8:
run_gui_mode();
break;
// 0. 退出游戏
case 0:
save_game_config();
printf("感谢使用五子棋游戏!\n");
return 0;
default:
printf("无效的选择!\n");
pause_for_input("按任意键继续...");
break;
}
}
return 0; return 0;
} }
+1 -185
View File
@@ -6,10 +6,7 @@
*/ */
#include "record.h" #include "record.h"
#include "game_mode.h"
#include "gobang.h" #include "gobang.h"
#include "init_board.h"
#include "ui.h"
#include "config.h" #include "config.h"
#include "globals.h" #include "globals.h"
#include <stdio.h> #include <stdio.h>
@@ -47,139 +44,7 @@
* - * -
* - calculate_final_score() * - calculate_final_score()
*/ */
void review_process(int game_mode) void calculate_game_scores();
{
int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
// 如果评分尚未计算,则计算评分
if (!scores_calculated)
{
calculate_game_scores();
}
else
{
// 评分已从文件中加载,直接使用
printf("从记录文件中加载评分数据\n");
}
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 == GAME_MODE_AI)
{
// 人机对战
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 if (game_mode == GAME_MODE_PVP)
{
// 双人对战
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);
}
else if (game_mode == GAME_MODE_NETWORK)
{
// 网络对战
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===== 对局结果 =====");
if (strcmp(winner_info, "玩家获胜") == 0)
{
printf("\n? 恭喜!玩家获胜!\n");
}
else if (strcmp(winner_info, "AI获胜") == 0)
{
printf("\n? AI获胜!\n");
}
else if (strcmp(winner_info, "玩家1获胜") == 0)
{
printf("\n? 恭喜!玩家1(黑棋)获胜!\n");
}
else if (strcmp(winner_info, "玩家2获胜") == 0)
{
printf("\n? 恭喜!玩家2(白棋)获胜!\n");
}
else
{
printf("\n?? 对局平局或未完成\n");
}
printf("\n复盘结束!按Enter查看评分...");
getchar(); // 等待用户按键
}
// 显示评分结果
display_game_scores(game_mode);
getchar();
}
/** /**
* @brief * @brief
@@ -290,57 +155,8 @@ void display_game_scores(int game_mode)
} }
} }
/**
* @brief
* @return int (0-, 1-, 2-, 3-)
*/
void handle_save_record(int game_mode)
{
printf("===== 游戏结束 =====\n");
int save_choice = get_integer_input("是否保存游戏记录? (1-是, 0-否): ", 0, 1);
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.csv", t);
int save_status = save_game_to_file(filename, game_mode);
switch (save_status)
{
case 0: // 成功
printf("\n游戏记录已成功保存至: %s (CSV格式)\n", filename);
printf("您可以使用以下命令进行复盘: .\\gobang.exe -l %s\n", filename);
printf("CSV格式文件可以直接用Excel打开查看和分析\n");
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 filename
* @return int :
* 0:
* 1:
* 2:
* 3:
*/ */
int save_game_to_file(const char *filename, int game_mode) int save_game_to_file(const char *filename, int game_mode)
{ {
-212
View File
@@ -1,212 +0,0 @@
/**
* @file ui.c
* @brief
* @note
*
*/
#include "ui.h"
#include "gobang.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <unistd.h>
#endif
/**
* @brief
*/
void display_main_menu()
{
printf("===== 五子棋游戏 =====\n");
printf("1. AI模式\n");
printf("2. 玩家比赛\n");
printf("3. 网络对战\n");
printf("4. 复盘模式\n");
printf("5. 游戏设置\n");
printf("6. 游戏规则\n");
printf("7. 关于游戏\n");
printf("8. 图形化界面\n");
printf("0. 退出游戏\n");
printf("=====================\n");
}
/**
* @brief
*/
void display_board()
{
printf("\n ");
// 打印列号
for (int j = 0; j < BOARD_SIZE; j++)
{
printf("%2d", j);
}
printf("\n");
// 打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d", i); // 打印行号
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == EMPTY)
{
printf(" ·"); // 空位用点表示
}
else if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
{
printf(""); // 玩家1用实心圆表示
}
else
{
printf(""); // 玩家2/AI用空心圆表示
}
}
printf("\n");
}
printf("\n");
}
/**
* @brief
* @param current_player
* @param step_count
*/
void display_game_status(int current_player, int step_count)
{
printf("当前步数: %d\n", step_count);
if (current_player == PLAYER || current_player == PLAYER1)
{
printf("当前玩家: ●\n");
}
else
{
printf("当前玩家: ○\n");
}
}
/**
* @brief
* @param winner
*/
void display_winner(int winner)
{
printf("\n游戏结束!\n");
if (winner == PLAYER)
{
printf("玩家获胜!\n");
}
else if (winner == AI)
{
printf("AI获胜!\n");
}
else if (winner == PLAYER1)
{
printf("玩家1获胜!\n");
}
else if (winner == PLAYER2)
{
printf("玩家2获胜!\n");
}
else
{
printf("平局!\n");
}
}
/**
* @brief
*/
void display_settings_menu()
{
printf("\n===== 游戏设置 =====\n");
printf("1. 棋盘大小设置\n");
printf("2. 禁手规则设置\n");
printf("3. 计时器设置\n");
printf("4. 网络配置设置\n");
printf("5. AI难度设置\n");
printf("0. 返回主菜单\n");
printf("==================\n");
}
/**
* @brief
*/
void clear_screen()
{
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
/**
* @brief
* @param prompt
*/
void pause_for_input(const char *prompt)
{
printf("%s", prompt);
#ifdef _WIN32
_getch();
#else
getchar();
#endif
}
/**
* @brief
*/
void display_game_rules()
{
printf("\n===== 五子棋游戏规则 =====\n");
printf("1. 🎮 游戏目标:\n");
printf(" 在棋盘上连成五个同色棋子(横、竖、斜均可)\n\n");
printf("2. 🔄 游戏流程:\n");
printf(" - ⚫ 黑棋先行,双方轮流落子\n");
printf(" - 📍 输入坐标格式:行号 列号(如:7 7)\n");
printf(" - ✨ 棋子落在棋盘交叉点上\n\n");
printf("3. 🏆 胜负判定:\n");
printf(" - 🎉 率先连成五子者获胜\n");
printf(" - 🤝 棋盘下满无人获胜则为平局\n\n");
printf("4. 🚫 禁手规则(可选):\n");
printf(" - ❌ 三三禁手:同时形成两个活三\n");
printf(" - ❌ 四四禁手:同时形成两个冲四\n");
printf(" - ❌ 长连禁手:连成六子或以上\n\n");
printf("5. 🛠️ 特殊功能:\n");
printf(" - ↩️ 悔棋:输入 'R' 或 'r' 可悔棋\n");
printf(" - 💾 保存:游戏结束后可保存对局记录\n");
printf(" - 📖 复盘:可加载历史对局进行复盘\n");
printf("============================\n");
}
/**
* @brief
*/
void display_about()
{
printf("\n===== 关于五子棋游戏 =====\n");
printf("🎮 游戏名称:五子棋人机对战\n");
printf("📦 版本:7.0\n");
printf("👨‍💻 开发者:刘航宇\n");
printf("📧 联系邮箱:3364451258@qq.com\n");
printf("🌐 项目主页:https://github.com/LHY0125/Gobang-Game\n\n");
printf("✨ 主要特性:\n");
printf(" 🤖 智能AI对战(支持多种难度)\n");
printf(" 👥 双人对战模式\n");
printf(" 🌐 网络对战(局域网/互联网)\n");
printf(" 📝 对局记录与复盘\n");
printf(" 🚫 禁手规则支持\n");
printf(" ⏱️ 计时器功能\n");
printf(" 📏 自定义棋盘大小\n");
printf(" 📊 评分系统\n\n");
printf("🙏 感谢使用!\n");
printf("========================\n");
}