Initial commit: C language learning code
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
double a, b, c;
|
||||
scanf("%lf %lf", &a, &b);
|
||||
c = a / b;
|
||||
printf("%.0f/%.0f=%.2f\n", a, b, c);
|
||||
|
||||
return 0;
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int max(int a, int b);
|
||||
|
||||
int main()
|
||||
{
|
||||
int a, b,c;
|
||||
scanf("%d %d", &a, &b);
|
||||
|
||||
c = max(a, b);
|
||||
|
||||
printf("%d和%d的最大数是%d", a, b, c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int max(int a, int b)
|
||||
{
|
||||
int c = a;
|
||||
|
||||
if (a<b)
|
||||
{
|
||||
c = b;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
int sign = 1;
|
||||
int n;
|
||||
printf("ÇëÊäÈën: ");
|
||||
scanf("%d", &n);
|
||||
double deno = 2.0, sum = 1.0, term;
|
||||
|
||||
while (deno <= n)
|
||||
{
|
||||
sign = -sign;
|
||||
term = sign / deno;
|
||||
sum = sum + term;
|
||||
deno = deno + 1;
|
||||
}
|
||||
printf("%f\n", sum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
char c = 'Y';
|
||||
putchar(66); // B
|
||||
putchar('O'); // O
|
||||
putchar(c); // Y
|
||||
putchar('\n');
|
||||
|
||||
char a, b;
|
||||
a = getchar();
|
||||
b = getchar();
|
||||
putchar(a);
|
||||
putchar(b);
|
||||
|
||||
putchar(getchar());
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
char c1, c2;
|
||||
printf("请输入一个大写字母");
|
||||
c1 = getchar();
|
||||
c2 = c1 + 32;
|
||||
printf("转换后的小写字母为");
|
||||
putchar(c2);
|
||||
putchar('\n');
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
int a, b, c;
|
||||
a = b = c = 0;
|
||||
a++ &&b++ | c++;
|
||||
printf("%d,%d,%d\n",a,b,c);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
double a, b,c;
|
||||
printf("输入你的体重(kg)和你的身高(cm)\n");
|
||||
scanf("%lf %lf",&a,&b);
|
||||
|
||||
b /= 100;
|
||||
c=a/(b*b);
|
||||
|
||||
if(c< 18)
|
||||
{
|
||||
printf("BMI是%.2f,你太轻了\n",c);
|
||||
}
|
||||
else if (c > 24)
|
||||
{
|
||||
printf("BMI是%.2f,你有点胖\n",c);
|
||||
}
|
||||
else if (18 <= c && c <= 24)
|
||||
{
|
||||
printf("BMI是%.2f,你的体重正常\n",c);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
# 五子棋AI实现详解
|
||||
|
||||
## 算法概述
|
||||
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。核心算法流程如下:
|
||||
|
||||
1. **威胁检测阶段**:优先检查并阻止玩家即将形成的活四、冲四等威胁
|
||||
2. **搜索决策阶段**:使用α-β剪枝的极小极大算法搜索最佳落子位置
|
||||
3. **评估优化**:结合位置权重和棋型价值进行综合评分
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 棋盘表示
|
||||
```c
|
||||
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 25x25最大棋盘
|
||||
```
|
||||
- `0`表示空位
|
||||
- `1`表示玩家棋子(X)
|
||||
- `2`表示AI棋子(○)
|
||||
|
||||
### 步数记录
|
||||
```c
|
||||
typedef struct {
|
||||
int player; // 下棋方(1或2)
|
||||
int x, y; // 坐标(0-based)
|
||||
} Step;
|
||||
|
||||
Step steps[MAX_STEPS]; // 最大步数记录
|
||||
```
|
||||
|
||||
### 方向信息
|
||||
```c
|
||||
typedef struct {
|
||||
int continuous_chess; // 连续同色棋子数
|
||||
bool check_start; // 起始方向是否开放
|
||||
bool check_end; // 结束方向是否开放
|
||||
} DirInfo;
|
||||
```
|
||||
|
||||
## 核心函数说明
|
||||
|
||||
### 1. ai_move(int depth)
|
||||
**AI决策主函数**
|
||||
```c
|
||||
void ai_move(int depth);
|
||||
```
|
||||
- 参数:`depth` - 当前搜索深度
|
||||
- 流程:
|
||||
1. 优先防御:检查并阻止玩家的威胁棋型
|
||||
2. 主动进攻:使用α-β剪枝搜索最佳位置
|
||||
3. 执行落子
|
||||
|
||||
### 2. dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing)
|
||||
**α-β剪枝搜索核心**
|
||||
```c
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
|
||||
```
|
||||
- 参数:
|
||||
- `x,y` - 当前测试位置
|
||||
- `player` - 当前玩家
|
||||
- `depth` - 剩余搜索深度
|
||||
- `alpha/beta` - 剪枝参数
|
||||
- `is_maximizing` - 是否最大化玩家(AI)
|
||||
|
||||
- 返回值:评估分数
|
||||
|
||||
### 3. evaluate_pos(int x, int y, int player)
|
||||
**位置评估函数**
|
||||
```c
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
```
|
||||
评估标准:
|
||||
- 四个方向(水平、垂直、对角线)分别评估
|
||||
- 考虑棋型(活棋、眠棋)和开放程度
|
||||
- 加入位置权重(中心区域价值更高)
|
||||
|
||||
### 4. count_specific_direction(int x, int y, int dx, int dy, int player)
|
||||
**方向分析函数**
|
||||
```c
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
```
|
||||
- 分析特定方向的连续棋子情况
|
||||
- 返回结构包含:
|
||||
- 连续棋子数
|
||||
- 两端开放状态
|
||||
|
||||
## 评估系统
|
||||
|
||||
### 棋型评分标准
|
||||
| 棋型 | 条件 | 分数 |
|
||||
|-------------|--------------------|-------|
|
||||
| 活四 | 两端开放的四连 | 100000|
|
||||
| 冲四 | 一端开放的四连 | 10000 |
|
||||
| 活三 | 两端开放的三连 | 5000 |
|
||||
| 眠三 | 一端开放的三连 | 1000 |
|
||||
| 活二 | 两端开放的二连 | 500 |
|
||||
| 眠二 | 一端开放的二连 | 100 |
|
||||
|
||||
### 位置权重
|
||||
- 中心区域奖励:`50 * (BOARD_SIZE - 距离中心曼哈顿距离)`
|
||||
- 边缘区域:基础分
|
||||
|
||||
## 搜索策略
|
||||
|
||||
### α-β剪枝优化
|
||||
1. **极大节点(AI)**:
|
||||
- 更新α值
|
||||
- 当α ≥ β时剪枝
|
||||
|
||||
2. **极小节点(玩家)**:
|
||||
- 更新β值
|
||||
- 当β ≤ α时剪枝
|
||||
|
||||
### 搜索优化
|
||||
1. **局部搜索**:仅考虑已有棋子周围2格范围内的位置
|
||||
2. **对称剪枝**:避免重复计算对称位置
|
||||
3. **深度控制**:默认3层,可通过参数调整
|
||||
|
||||
## 威胁检测机制
|
||||
|
||||
### 防御优先级
|
||||
1. 立即阻止的威胁:
|
||||
- 活四(必输)
|
||||
- 冲四(必须阻挡)
|
||||
- 双活三(必须阻挡)
|
||||
|
||||
2. 高级威胁:
|
||||
- 活三
|
||||
- 冲三
|
||||
- 多种棋型组合
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **评估缓存**:重复位置评估优化
|
||||
2. **早期终止**:发现必胜/必败立即返回
|
||||
3. **搜索排序**:优先搜索高价值区域
|
||||
|
||||
## 典型场景处理
|
||||
|
||||
### 必胜局面
|
||||
- 直接落子形成五连
|
||||
- 优先进攻而非防守
|
||||
|
||||
### 必防局面
|
||||
- 检测玩家的活四/冲四
|
||||
- 强制在关键点落子
|
||||
|
||||
### 平衡策略
|
||||
- 进攻与防守的权重平衡
|
||||
- 根据局势动态调整
|
||||
@@ -0,0 +1,384 @@
|
||||
# 五子棋人机对战AI项目
|
||||
|
||||
## 项目简介
|
||||
这是一个基于C语言实现的高级五子棋人机对战系统,采用α-β剪枝的极小极大算法实现AI决策,支持自定义棋盘大小和完整的游戏复盘功能。
|
||||
|
||||
## 核心功能
|
||||
- 支持5x5到25x25的棋盘尺寸
|
||||
- 玩家(X)与AI(○)对战模式
|
||||
- AI采用α-β剪枝优化的极小极大算法
|
||||
- 详细的棋型评估系统(活四、冲四、活三等)
|
||||
- 完整的游戏复盘功能
|
||||
- 清晰的棋盘界面显示
|
||||
|
||||
## 技术特点
|
||||
1. **AI算法**:
|
||||
- 使用α-β剪枝优化的极小极大算法
|
||||
- 搜索深度可调(当前设置为3层)
|
||||
- 优先处理威胁位置(如阻止玩家形成活四)
|
||||
|
||||
2. **评估系统**:
|
||||
- 考虑四个方向的棋型评估
|
||||
- 区分活棋、眠棋和死棋
|
||||
- 位置奖励(中心位置价值更高)
|
||||
|
||||
3. **游戏流程**:
|
||||
- 完整的胜负判定
|
||||
- 平局检测
|
||||
- 详细的复盘系统
|
||||
|
||||
## 编译运行
|
||||
1. 编译程序:
|
||||
```bash
|
||||
gcc "C语言代码/课上代码练习/五子棋/五子棋 copy 3.c" -o gomoku -lm
|
||||
```
|
||||
|
||||
2. 运行游戏:
|
||||
```bash
|
||||
./gomoku
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
1. 启动后输入棋盘尺寸(5-25,默认15)
|
||||
2. 游戏进行:
|
||||
- 玩家输入坐标格式:行 列(如"8 8")
|
||||
- AI会自动计算最佳落子位置
|
||||
3. 游戏结束:
|
||||
- 显示胜负结果
|
||||
- 可选择查看完整复盘
|
||||
|
||||
## 核心函数说明
|
||||
|
||||
### 1. ai_move() - AI决策函数
|
||||
**功能**:决定AI的最佳落子位置
|
||||
**算法**:
|
||||
1. 优先检查是否需要阻止玩家形成活四/冲四
|
||||
2. 使用α-β剪枝优化的极小极大算法搜索最佳位置
|
||||
3. 考虑已有棋子附近2格范围内的位置
|
||||
|
||||
**参数**:无
|
||||
**返回值**:无(直接修改棋盘状态)
|
||||
|
||||
### 2. evaluate_pos() - 棋型评估函数
|
||||
**功能**:评估特定位置对当前玩家的价值
|
||||
**评分标准**:
|
||||
- 活四: 100000分
|
||||
- 冲四: 10000分
|
||||
- 活三: 5000分
|
||||
- 眠三: 1000分
|
||||
- 活二: 500分
|
||||
- 眠二: 100分
|
||||
-
|
||||
**参数**:
|
||||
- x,y: 待评估位置坐标
|
||||
- player: 当前玩家标识
|
||||
**返回值**:评估分数(越高越好)
|
||||
|
||||
### 3. dfs() - 深度优先搜索
|
||||
**功能**:实现带α-β剪枝的极小极大算法
|
||||
**特点**:
|
||||
- 搜索深度:3层
|
||||
- 使用α-β剪枝优化搜索效率
|
||||
- 评估函数差值作为叶节点值
|
||||
**参数**:
|
||||
- x,y: 当前落子位置
|
||||
- player: 当前玩家
|
||||
- depth: 剩余搜索深度
|
||||
- alpha,beta: 剪枝参数
|
||||
- is_maximizing: 是否最大化玩家
|
||||
|
||||
### 4. count_specific_direction() - 方向分析
|
||||
**功能**:计算特定方向上的连续棋子情况
|
||||
**返回结构**:
|
||||
- continuous_chess: 连续棋子数
|
||||
- check_start/check_end: 两端是否开放
|
||||
**应用**:用于判断棋型(活棋/眠棋)
|
||||
|
||||
### 5. check_win() - 胜负判断
|
||||
**功能**:检查落子后是否形成五连珠
|
||||
**逻辑**:检查四个方向是否存在≥5的连续同色棋子
|
||||
|
||||
## 程序流程图
|
||||
|
||||
```
|
||||
开始
|
||||
│
|
||||
├─> 初始化棋盘
|
||||
│ │
|
||||
│ └─> 设置默认大小(15x15)
|
||||
│
|
||||
├─> 游戏主循环
|
||||
│ │
|
||||
│ ├─> [玩家回合]
|
||||
│ │ ├─> 显示棋盘
|
||||
│ │ ├─> 获取玩家输入坐标
|
||||
│ │ └─> 落子并检查胜负
|
||||
│ │
|
||||
│ ├─> [AI回合]
|
||||
│ │ ├─> AI计算最佳落子(α-β剪枝)
|
||||
│ │ └─> 落子并检查胜负
|
||||
│ │
|
||||
│ └─> 检查平局条件
|
||||
│
|
||||
├─> 游戏结束处理
|
||||
│ ├─> 显示胜负结果
|
||||
│ └─> 询问是否复盘
|
||||
│ ├─> 是: 显示完整复盘
|
||||
│ └─> 否: 退出游戏
|
||||
│
|
||||
└─> 程序结束
|
||||
```
|
||||
|
||||
## 关键代码实现
|
||||
|
||||
### 1. AI决策核心算法
|
||||
```c
|
||||
// α-β剪枝极小极大算法实现
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, int is_maximizing)
|
||||
{
|
||||
if (depth == 0 || check_win(x, y))
|
||||
{
|
||||
return evaluate_pos(x, y, player) - evaluate_pos(x, y, 3 - player);
|
||||
}
|
||||
|
||||
if (is_maximizing)
|
||||
{
|
||||
int max_eval = INT_MIN;
|
||||
for (每个可能位置)
|
||||
{
|
||||
int eval = dfs(x, y, player, depth-1, alpha, beta, 0);
|
||||
max_eval = max(max_eval, eval);
|
||||
alpha = max(alpha, eval);
|
||||
// α剪枝
|
||||
if (beta <= alpha)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return max_eval;
|
||||
}
|
||||
else
|
||||
{
|
||||
int min_eval = INT_MAX;
|
||||
for (每个可能位置)
|
||||
{
|
||||
int eval = dfs(x, y, player, depth-1, alpha, beta, 1);
|
||||
min_eval = min(min_eval, eval);
|
||||
beta = min(beta, eval);
|
||||
// β剪枝
|
||||
if (beta <= alpha)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return min_eval;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 棋型评估函数
|
||||
```c
|
||||
// 评估特定位置的棋型价值
|
||||
int evaluate_pos(int x, int y, int player)
|
||||
{
|
||||
int score = 0;
|
||||
for (四个方向)
|
||||
{
|
||||
ChessPattern pattern =count_specific_direction(x, y, dir);
|
||||
|
||||
if (pattern.continuous_chess >= 5)
|
||||
{
|
||||
return 1000000; // 五连珠
|
||||
}
|
||||
|
||||
// 活棋
|
||||
if (pattern.check_start && pattern.check_end)
|
||||
{
|
||||
if (pattern.continuous_chess == 4)
|
||||
score += 100000; // 活四
|
||||
else if (pattern.continuous_chess == 3)
|
||||
score += 5000; // 活三
|
||||
else if (pattern.continuous_chess == 2)
|
||||
score += 500; // 活二
|
||||
}
|
||||
// 眠棋
|
||||
else if (pattern.check_start || pattern.check_end)
|
||||
{
|
||||
if (pattern.continuous_chess == 4)
|
||||
score += 10000; // 冲四
|
||||
else if (pattern.continuous_chess == 3)
|
||||
score += 1000; // 眠三
|
||||
else if (pattern.continuous_chess == 2)
|
||||
score += 100; // 眠二
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 胜负判断逻辑
|
||||
```c
|
||||
// 检查是否形成五连珠
|
||||
int check_win(int x, int y)
|
||||
{
|
||||
for (四个方向)
|
||||
{
|
||||
int count = 1;
|
||||
// 正向检查
|
||||
for (int i = 1; i < 5; i++)
|
||||
{
|
||||
if (棋盘边界检查 || 棋子不连续)
|
||||
{
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
// 反向检查
|
||||
for (int i = 1; i < 5; i++)
|
||||
{
|
||||
if (棋盘边界检查 || 棋子不连续)
|
||||
{
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
// 五连珠
|
||||
if (count >= 5)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
- `五子棋 copy 3.c` - 主程序文件(当前使用版本)
|
||||
- 其他`五子棋 copy*.c`文件为开发过程中的不同版本
|
||||
|
||||
## 注意事项
|
||||
1. 编译时需要链接数学库(-lm)
|
||||
2. 确保输入坐标在棋盘范围内
|
||||
3. AI思考时间随棋盘大小和搜索深度增加
|
||||
|
||||
## 详细技术实现说明
|
||||
|
||||
### 1. 主函数(main)执行流程
|
||||
1. **初始化阶段**:
|
||||
- 提示用户输入棋盘尺寸(5-25),默认15x15
|
||||
- 初始化棋盘状态(全空)
|
||||
- 打印初始空棋盘
|
||||
|
||||
2. **游戏主循环**:
|
||||
- **玩家回合**:
|
||||
* 提示玩家输入坐标(行列格式)
|
||||
* 验证坐标有效性(是否在棋盘范围内且为空位)
|
||||
* 在棋盘放置玩家棋子(X)
|
||||
* 检查是否形成五连珠(玩家胜利)
|
||||
- **AI回合**:
|
||||
* 调用ai_move()计算最佳落子位置
|
||||
* 在棋盘放置AI棋子(○)
|
||||
* 检查是否形成五连珠(AI胜利)
|
||||
- **平局检查**:
|
||||
* 当棋盘已满且无胜负时判定平局
|
||||
|
||||
3. **游戏结束处理**:
|
||||
- 显示最终胜负结果
|
||||
- 提供复盘功能(逐步展示整个对局过程)
|
||||
|
||||
### 2. AI决策系统(ai_move)
|
||||
**防御策略**:
|
||||
1. 优先检查玩家可能形成的威胁棋型:
|
||||
- 活四(必须立即阻挡)
|
||||
- 冲四(一端被堵的四连珠)
|
||||
- 活三(两端开放的三连珠)
|
||||
|
||||
**进攻策略**:
|
||||
1. 使用α-β剪枝优化的极小极大算法:
|
||||
- 搜索深度:3层
|
||||
- 评估函数:evaluate_pos()差值
|
||||
- 剪枝条件:alpha ≥ beta时终止搜索
|
||||
|
||||
2. 搜索优化:
|
||||
- 仅考虑已有棋子周围2格范围内的空位
|
||||
- 中心位置奖励(棋盘中心区域价值更高)
|
||||
- 对称位置剪枝(减少重复计算)
|
||||
|
||||
### 3. 棋型评估系统(evaluate_pos)
|
||||
**评分标准详细说明**:
|
||||
1. **活棋(两端开放)**:
|
||||
- 活四:100000分(必胜)
|
||||
- 活三:5000分(高威胁)
|
||||
- 活二:500分(潜在威胁)
|
||||
|
||||
2. **眠棋(一端开放)**:
|
||||
- 冲四:10000分(次高威胁)
|
||||
- 眠三:1000分(中等威胁)
|
||||
- 眠二:100分(低威胁)
|
||||
|
||||
3. **死棋(两端封闭)**:
|
||||
- 无威胁价值,仅基础分
|
||||
|
||||
4. **位置奖励**:
|
||||
- 中心区域:额外加分(50*(BOARD_SIZE-距离))
|
||||
- 边角区域:基础分
|
||||
|
||||
**评估过程**:
|
||||
1. 四个方向(水平、垂直、对角线)分别评估
|
||||
2. 综合最高分方向和所有方向加权分
|
||||
3. 考虑当前玩家和对手的分数差值
|
||||
|
||||
### 4. 胜负判断(check_win)
|
||||
**实现细节**:
|
||||
1. 四个方向检查:
|
||||
- 水平(→)
|
||||
- 垂直(↓)
|
||||
- 主对角线(↘)
|
||||
- 副对角线(↙)
|
||||
|
||||
2. 检查逻辑:
|
||||
- 从当前位置向两个方向延伸
|
||||
- 统计连续同色棋子数
|
||||
- 任一方向≥5即判定胜利
|
||||
|
||||
3. 优化措施:
|
||||
- 边界检查避免数组越界
|
||||
- 提前终止(发现5连立即返回)
|
||||
|
||||
### 5. 复盘系统(review_process)
|
||||
**实现机制**:
|
||||
1. 数据结构:
|
||||
- 使用steps数组记录每一步落子
|
||||
- 包含玩家类型、坐标信息
|
||||
|
||||
2. 回放过程:
|
||||
- 逐步重建棋盘状态
|
||||
- 显示每一步的落子位置
|
||||
- 支持暂停/继续控制
|
||||
|
||||
3. 可视化:
|
||||
- 清晰的棋盘显示
|
||||
- 回合计数和玩家标识
|
||||
- 坐标转换(0-base转1-base)
|
||||
|
||||
### 6. 方向分析(count_specific_direction)
|
||||
**算法细节**:
|
||||
1. 输入参数:
|
||||
- 起始坐标(x,y)
|
||||
- 方向向量(dx,dy)
|
||||
- 玩家标识
|
||||
|
||||
2. 分析过程:
|
||||
- 正向延伸(统计连续棋子)
|
||||
- 反向延伸(统计连续棋子)
|
||||
- 检查两端开放状态
|
||||
|
||||
3. 输出结构:
|
||||
- 连续棋子数
|
||||
- 起点开放状态
|
||||
- 终点开放状态
|
||||
|
||||
**应用场景**:
|
||||
1. 胜负判断
|
||||
2. 棋型评估
|
||||
3. 威胁检测
|
||||
@@ -0,0 +1,489 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
// !宏定义
|
||||
#define MAX_BOARD_SIZE 25 //* 最大支持棋盘尺寸
|
||||
int BOARD_SIZE = 15; //* 实际使用的棋盘尺寸(默认15)
|
||||
#define PLAYER 1 //* 玩家棋子标识符
|
||||
#define AI 2 //* AI棋子标识符
|
||||
#define EMPTY 0 //* 空位置标识符
|
||||
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) //* 最大步数(棋盘总格数)
|
||||
|
||||
// !全局变量
|
||||
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; //* 棋盘状态存储数组(默认棋盘全空为0)
|
||||
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; //* 四个方向:向下、向右、右下、左下
|
||||
|
||||
// !落子步骤结构体
|
||||
typedef struct // 落子结构体
|
||||
{
|
||||
int player; // 落子方标识
|
||||
int x, y; // 坐标位置
|
||||
} Step;
|
||||
|
||||
Step steps[MAX_STEPS]; // 存储所有落子步骤
|
||||
|
||||
// !方向信息结构体
|
||||
typedef struct
|
||||
{
|
||||
int continuous_chess; // 连续棋子数量
|
||||
bool check_start; // 序列起点方向是否开放(空位)
|
||||
bool check_end; // 序列终点方向是否开放(空位)
|
||||
} DirInfo;
|
||||
|
||||
int step_count = 0; // 当前步数计数器
|
||||
|
||||
// !初始化棋盘:清空棋盘状态和步数记录
|
||||
void empty_board();
|
||||
|
||||
// !打印当前棋盘状态
|
||||
void print_board();
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y);
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y);
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
|
||||
// !检查特定位置落子后是否获胜
|
||||
bool check_win(int x, int y, int player);
|
||||
|
||||
// !评估特定位置对当前玩家的价值
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
|
||||
// !AI落子算法
|
||||
void ai_move();
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process();
|
||||
|
||||
// !主函数:游戏流程控制
|
||||
int main()
|
||||
{
|
||||
// 初始化阶段:获取棋盘尺寸
|
||||
printf("===== 五子棋人机对战 =====\n");
|
||||
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
|
||||
printf("请输入棋盘大小(5~%d)(默认为标准棋盘):", MAX_BOARD_SIZE);
|
||||
scanf("%d", &BOARD_SIZE);
|
||||
if (BOARD_SIZE < 5 || BOARD_SIZE > MAX_BOARD_SIZE)
|
||||
{
|
||||
BOARD_SIZE = 15;
|
||||
printf("输入无效,使用默认标准棋盘15X15\n");
|
||||
}
|
||||
|
||||
empty_board();
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
|
||||
print_board();
|
||||
|
||||
// 游戏主循环
|
||||
while (1)
|
||||
{
|
||||
// 玩家回合
|
||||
int x, y;
|
||||
printf("\n请输入落子坐标(行 列,1~%d):", BOARD_SIZE);
|
||||
scanf("%d %d", &x, &y);
|
||||
// 转换为0-BOARD_SIZE索引(转为棋盘的坐标)
|
||||
x--;
|
||||
y--;
|
||||
|
||||
// 验证并执行玩家移动
|
||||
if (!player_move(x, y))
|
||||
{
|
||||
printf("坐标无效!请重新输入。\n");
|
||||
continue;
|
||||
}
|
||||
print_board();
|
||||
|
||||
// 检查玩家是否获胜
|
||||
if (check_win(x, y, PLAYER))
|
||||
{
|
||||
printf("\n玩家获胜!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
|
||||
// AI回合
|
||||
printf("\nAI思考中...\n");
|
||||
ai_move();
|
||||
print_board();
|
||||
|
||||
// 检查AI是否获胜
|
||||
Step last_step = steps[step_count - 1];
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
printf("\nAI获胜!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查平局(棋盘已满)
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
|
||||
// !打印当前棋盘状态
|
||||
void print_board()
|
||||
{
|
||||
// 打印列号(1-BOARD_SIZE显示)
|
||||
printf("\n ");
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
printf("%2d", i + 1);
|
||||
if (i + 1 == 9) // 处理两位数列号的对齐
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y)
|
||||
{
|
||||
return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY);
|
||||
}
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y)
|
||||
{
|
||||
if (!have_space(x, y))
|
||||
{
|
||||
return false; // 位置无效返回失败
|
||||
}
|
||||
|
||||
board[x][y] = PLAYER; // 更新棋盘状态
|
||||
steps[step_count++] = (Step){PLAYER, x, y}; // 记录步骤
|
||||
return true;
|
||||
}
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
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;
|
||||
|
||||
// 检查正方向(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;
|
||||
ny += dy;
|
||||
}
|
||||
// 判断正方向端点是否开放
|
||||
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE)
|
||||
{
|
||||
if (board[nx][ny] == EMPTY)
|
||||
{
|
||||
info.check_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查反方向(-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;
|
||||
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)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// !评估特定位置对当前玩家的价值(AI评估下一次落子位置的价值)
|
||||
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}; // 记录四个方向的分数
|
||||
|
||||
// 遍历四个方向
|
||||
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; // 五连珠直接返回最高分
|
||||
}
|
||||
|
||||
// 评估不同棋型的价值
|
||||
switch (info.continuous_chess)
|
||||
{
|
||||
// 四连珠
|
||||
case 4:
|
||||
// 活四(两端开放)
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 100000; // 必胜局面
|
||||
}
|
||||
// 冲四(一端开放)
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 10000; // 威
|
||||
}
|
||||
// 死四(两端封闭)
|
||||
else
|
||||
{
|
||||
line_scores[i] = 500; // 低价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 三连珠
|
||||
case 3:
|
||||
// 活三(两端开放)
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 5000; // 高价值
|
||||
}
|
||||
// 眠三(一端开放)
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 1000; // 中等价值
|
||||
}
|
||||
// 死三(两端封闭)
|
||||
else
|
||||
{
|
||||
line_scores[i] = 50; // 低价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 二连珠
|
||||
case 2:
|
||||
// 活二(两端开放)
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 500; // 发展潜力大
|
||||
}
|
||||
// 眠二(一端开放)
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 100; // 有限潜力
|
||||
}
|
||||
// 死二(两端封闭)
|
||||
else
|
||||
{
|
||||
line_scores[i] = 10; // 几乎无价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 单子
|
||||
case 1:
|
||||
// 单子但两端开放
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 50; // 未来潜力
|
||||
}
|
||||
// 单子但一端开放
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 10; // 有限潜力
|
||||
}
|
||||
// 死单子
|
||||
else
|
||||
{
|
||||
line_scores[i] = 1; // 几乎无价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 五子以上(已处理)
|
||||
default:
|
||||
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;
|
||||
|
||||
board[x][y] = original; // 恢复棋盘
|
||||
|
||||
// 添加位置权重:中央位置>边缘位置
|
||||
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);
|
||||
|
||||
return total_score + position_bonus;
|
||||
}
|
||||
|
||||
// !AI落子算法
|
||||
void ai_move()
|
||||
{
|
||||
int best_score = -1, 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;
|
||||
}
|
||||
|
||||
// 综合评估位置对AI的进攻价值和防御价值
|
||||
int score = evaluate_pos(i, j, AI) + evaluate_pos(i, j, PLAYER) * 0.5;
|
||||
|
||||
// 更新最佳位置
|
||||
if (score > best_score)
|
||||
{
|
||||
best_score = score;
|
||||
best_x = i;
|
||||
best_y = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在最佳位置落子
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process()
|
||||
{
|
||||
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;
|
||||
|
||||
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.player == PLAYER, 则打印“玩家”,反之打印“AI”
|
||||
s.x + 1, s.y + 1); // 显示为(行,列)
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘) =====\n", BOARD_SIZE, BOARD_SIZE);
|
||||
|
||||
// 打印当前棋盘状态
|
||||
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)
|
||||
printf("x ");
|
||||
else if (temp_board[row][col] == AI)
|
||||
printf("○ ");
|
||||
else
|
||||
printf("· ");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (i < step_count - 1)
|
||||
{
|
||||
printf("\n按Enter继续下一步...");
|
||||
while (getchar() != '\n')
|
||||
;
|
||||
}
|
||||
}
|
||||
printf("\n复盘结束!按Enter返回...");
|
||||
|
||||
getchar();
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @brief 将指令复制到powershell
|
||||
* gcc "课上代码练习\五子棋\五子棋 copy 3.c" -o output/五子棋 copy 3.exe
|
||||
* @brief 将指令复制到powershell
|
||||
* .\output\五子棋 copy 3.exe
|
||||
*/
|
||||
|
||||
// !宏定义
|
||||
#define MAX_BOARD_SIZE 25 //* 最大支持棋盘尺寸(5x5到25x25)
|
||||
int BOARD_SIZE = 15; //* 实际使用的棋盘尺寸(默认15)
|
||||
#define PLAYER 1 //* 玩家棋子标识符
|
||||
#define AI 2 //* AI棋子标识符
|
||||
#define EMPTY 0 //* 空位置标识符
|
||||
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) //* 最大步数(棋盘总格数)
|
||||
|
||||
// !全局变量
|
||||
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; //* 棋盘状态存储数组(默认棋盘全空为0)
|
||||
int AI_DEPTH = 3; //* AI思考深度(默认3层)
|
||||
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; //* 四个方向:向下、向右、右下、左下
|
||||
|
||||
// !落子步骤结构体
|
||||
typedef struct // 落子结构体
|
||||
{
|
||||
int player; // 落子方标识
|
||||
int x, y; // 坐标位置
|
||||
} Step;
|
||||
|
||||
// !方向信息结构体
|
||||
typedef struct
|
||||
{
|
||||
int continuous_chess; // 连续棋子数量
|
||||
bool check_start; // 序列起点方向是否开放(空位)
|
||||
bool check_end; // 序列终点方向是否开放(空位)
|
||||
} DirInfo;
|
||||
|
||||
Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
|
||||
int step_count = 0; // 当前步数计数器
|
||||
|
||||
// !初始化棋盘:清空棋盘状态和步数记录
|
||||
void empty_board();
|
||||
|
||||
// !打印当前棋盘状态
|
||||
void print_board();
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y);
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y);
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
|
||||
// !检查特定位置落子后是否获胜
|
||||
bool check_win(int x, int y, int player);
|
||||
|
||||
// !评估特定位置对当前玩家的价值
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
|
||||
// !带α-β剪枝的深度优先搜索(极小极大算法实现)
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
|
||||
|
||||
// !AI落子算法
|
||||
void ai_move(int depth);
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process();
|
||||
|
||||
// !主函数:游戏流程控制
|
||||
int main()
|
||||
{
|
||||
// 初始化阶段:获取棋盘尺寸和AI难度
|
||||
printf("===== 五子棋人机对战 =====\n");
|
||||
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
|
||||
printf("请输入棋盘大小(5~%d)(默认为标准棋盘):", MAX_BOARD_SIZE);
|
||||
scanf("%d", &BOARD_SIZE);
|
||||
|
||||
// 校验输入是否合法,不合法时使用默认值
|
||||
if (BOARD_SIZE < 5 || BOARD_SIZE > MAX_BOARD_SIZE)
|
||||
{
|
||||
BOARD_SIZE = 15;
|
||||
printf("输入无效,使用默认标准棋盘15X15\n");
|
||||
}
|
||||
|
||||
|
||||
printf("请输入AI思考深度(1-5, 数值越大AI越强但思考时间越长, 默认为3):");
|
||||
scanf("%d", &AI_DEPTH);
|
||||
|
||||
// 校验AI深度范围
|
||||
if (AI_DEPTH < 1 || AI_DEPTH > 5)
|
||||
{
|
||||
AI_DEPTH = 3;
|
||||
printf("输入无效,使用默认深度3\n");
|
||||
}
|
||||
|
||||
empty_board(); // 初始化棋盘
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘, AI深度%d) =====", BOARD_SIZE, BOARD_SIZE, AI_DEPTH);
|
||||
print_board(); // 打印初始空棋盘
|
||||
|
||||
// 游戏主循环
|
||||
while (1)
|
||||
{
|
||||
// 玩家回合
|
||||
int x, y;
|
||||
printf("\n请输入落子坐标(行 列,1~%d):", BOARD_SIZE);
|
||||
scanf("%d %d", &x, &y);
|
||||
// 转换用户输入的1-base坐标为0-base索引
|
||||
x--;
|
||||
y--;
|
||||
|
||||
// 验证并执行玩家移动
|
||||
if (!player_move(x, y)) // 无效位置处理
|
||||
{
|
||||
printf("坐标无效!请重新输入。\n");
|
||||
continue; // 跳回循环开头重新输入
|
||||
}
|
||||
print_board(); // 更新后打印棋盘
|
||||
|
||||
// 检查玩家是否获胜
|
||||
if (check_win(x, y, PLAYER))
|
||||
{
|
||||
printf("\n玩家获胜!\n");
|
||||
review_process(); // 展示复盘
|
||||
break; // 退出游戏循环
|
||||
}
|
||||
|
||||
// AI回合
|
||||
printf("\nAI思考中...\n");
|
||||
ai_move(AI_DEPTH); // AI计算最佳落子位置
|
||||
print_board(); // 展示AI落子后的棋盘
|
||||
|
||||
// 检查AI是否获胜(通过最后一步)
|
||||
Step last_step = steps[step_count - 1]; // 获取最后一步
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
printf("\nAI获胜!\n");
|
||||
review_process(); // 展示复盘
|
||||
break; // 退出游戏循环
|
||||
}
|
||||
|
||||
// 检查平局(棋盘已满)
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
review_process(); // 展示复盘
|
||||
break; // 退出游戏循环
|
||||
}
|
||||
}
|
||||
|
||||
return 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; // 重置步数计数器
|
||||
}
|
||||
|
||||
// !打印当前棋盘状态
|
||||
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"); // 每行结束换行
|
||||
}
|
||||
}
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y)
|
||||
{
|
||||
// 校验坐标是否在范围内且该位置为空
|
||||
return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY);
|
||||
}
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y)
|
||||
{
|
||||
// 位置无效则返回false
|
||||
if (!have_space(x, y))
|
||||
return false;
|
||||
|
||||
// 更新棋盘状态
|
||||
board[x][y] = PLAYER;
|
||||
// 记录落子步骤:玩家标识和坐标
|
||||
steps[step_count++] = (Step){PLAYER, x, y};
|
||||
return true;
|
||||
}
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
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; // 终点方向是否开放
|
||||
|
||||
// 检查正方向(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; // 沿当前方向前进
|
||||
ny += dy;
|
||||
}
|
||||
// 判断正方向端点是否开放(遇到空位)
|
||||
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE)
|
||||
if (board[nx][ny] == EMPTY)
|
||||
info.check_end = true;
|
||||
|
||||
// 检查反方向(-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; // 沿相反方向前进
|
||||
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即获胜
|
||||
return true;
|
||||
}
|
||||
return false; // 四个方向都没有五连珠
|
||||
}
|
||||
|
||||
// !评估特定位置对当前玩家的价值
|
||||
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}; // 四个方向的得分
|
||||
|
||||
// 遍历四个方向进行评估
|
||||
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; // 返回最大分
|
||||
}
|
||||
|
||||
// 根据连续棋子数评分
|
||||
switch (info.continuous_chess)
|
||||
{
|
||||
case 4: // 四连珠
|
||||
if (info.check_start && info.check_end) // 活四(两端开放)
|
||||
line_scores[i] = 100000;
|
||||
else if (info.check_start || info.check_end) // 冲四(一端开放)
|
||||
line_scores[i] = 10000;
|
||||
else // 死四(两端封闭)
|
||||
line_scores[i] = 500;
|
||||
break;
|
||||
|
||||
case 3: // 三连珠
|
||||
if (info.check_start && info.check_end) // 活三
|
||||
line_scores[i] = 5000;
|
||||
else if (info.check_start || info.check_end) // 眠三
|
||||
line_scores[i] = 1000;
|
||||
else // 死三
|
||||
line_scores[i] = 50;
|
||||
break;
|
||||
|
||||
case 2: // 二连珠
|
||||
if (info.check_start && info.check_end) // 活二
|
||||
line_scores[i] = 500;
|
||||
else if (info.check_start || info.check_end) // 眠二
|
||||
line_scores[i] = 100;
|
||||
else // 死二
|
||||
line_scores[i] = 10;
|
||||
break;
|
||||
|
||||
case 1: // 单子
|
||||
if (info.check_start && info.check_end) // 开放位置
|
||||
line_scores[i] = 50;
|
||||
else if (info.check_start || info.check_end) // 半开放位置
|
||||
line_scores[i] = 10;
|
||||
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; // 主方向权重更高
|
||||
|
||||
// 位置奖励:越靠近中心分数越高
|
||||
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); // 距离中心越近奖励越高
|
||||
|
||||
board[x][y] = original; // 还原棋盘状态
|
||||
return total_score + position_bonus; // 返回总评估分
|
||||
}
|
||||
|
||||
// !带α-β剪枝的深度优先搜索(极小极大算法实现)
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing)
|
||||
{
|
||||
// 检查当前落子是否获胜
|
||||
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);
|
||||
}
|
||||
|
||||
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)逻辑
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((is_maximizing && best_score >= beta) || (!is_maximizing && best_score <= alpha))
|
||||
{
|
||||
break; // 提前退出外层循环
|
||||
}
|
||||
}
|
||||
|
||||
return best_score;
|
||||
}
|
||||
|
||||
// !使用带α-β剪枝的DFS极小极大算法更新AI落子
|
||||
void ai_move(int depth)
|
||||
{
|
||||
// 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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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格范围内)
|
||||
bool has_nearby_stone = false;
|
||||
for (int di = -2; di <= 2; di++)
|
||||
{
|
||||
for (int dj = -2; dj <= 2; dj++)
|
||||
{
|
||||
int ni = i + di;
|
||||
int nj = j + dj;
|
||||
if (ni >= 0 && ni < BOARD_SIZE &&
|
||||
nj >= 0 && nj < BOARD_SIZE)
|
||||
{
|
||||
if (board[ni][nj] != EMPTY)
|
||||
{
|
||||
has_nearby_stone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
// 更新最佳位置
|
||||
if (current_score > best_score)
|
||||
{
|
||||
best_score = current_score;
|
||||
best_x = i;
|
||||
best_y = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行最佳落子
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process()
|
||||
{
|
||||
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; // 在临时棋盘上落子
|
||||
|
||||
// 打印当前步骤信息
|
||||
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); // 显示1-base坐标
|
||||
|
||||
// 打印当前复盘棋盘
|
||||
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)
|
||||
printf("x ");
|
||||
else if (temp_board[row][col] == AI)
|
||||
printf("○ ");
|
||||
else
|
||||
printf("· ");
|
||||
}
|
||||
printf("\n"); // 行结束换行
|
||||
}
|
||||
|
||||
// 如果不是最后一步,等待用户按键继续
|
||||
if (i < step_count - 1)
|
||||
{
|
||||
printf("\n按Enter继续下一步...");
|
||||
while (getchar() != '\n')
|
||||
; // 等待回车
|
||||
}
|
||||
}
|
||||
printf("\n复盘结束!按Enter返回...");
|
||||
|
||||
getchar(); // 等待用户按键
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
// !宏定义
|
||||
#define MAX_BOARD_SIZE 25 //* 最大支持棋盘尺寸
|
||||
int BOARD_SIZE = 15; //* 实际使用的棋盘尺寸(默认15)
|
||||
#define PLAYER 1 //* 玩家棋子标识符
|
||||
#define AI 2 //* AI棋子标识符
|
||||
#define EMPTY 0 //* 空位置标识符
|
||||
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) //* 最大步数(棋盘总格数)
|
||||
|
||||
// !全局变量
|
||||
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; //* 棋盘状态存储数组(默认棋盘全空为0)
|
||||
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; //* 四个方向:向下、向右、右下、左下
|
||||
|
||||
// !落子步骤结构体
|
||||
typedef struct // 落子结构体
|
||||
{
|
||||
int player; // 落子方标识
|
||||
int x, y; // 坐标位置
|
||||
} Step;
|
||||
|
||||
// !方向信息结构体
|
||||
typedef struct
|
||||
{
|
||||
int continuous_chess; // 连续棋子数量
|
||||
bool check_start; // 序列起点方向是否开放(空位)
|
||||
bool check_end; // 序列终点方向是否开放(空位)
|
||||
} DirInfo;
|
||||
|
||||
Step steps[MAX_STEPS]; // 存储所有落子步骤
|
||||
|
||||
int step_count = 0; // 当前步数计数器
|
||||
|
||||
// !函数声明
|
||||
void empty_board();
|
||||
void print_board();
|
||||
bool have_space(int x, int y);
|
||||
bool player_move(int x, int y);
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
bool check_win(int x, int y, int player);
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
void ai_move();
|
||||
void review_process();
|
||||
|
||||
// !主函数:游戏流程控制
|
||||
int main()
|
||||
{
|
||||
// 初始化阶段:获取棋盘尺寸
|
||||
printf("===== 五子棋人机对战 =====\n");
|
||||
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
|
||||
printf("请输入棋盘大小(5~%d)(默认为标准棋盘):", MAX_BOARD_SIZE);
|
||||
scanf("%d", &BOARD_SIZE);
|
||||
if (BOARD_SIZE < 5 || BOARD_SIZE > MAX_BOARD_SIZE)
|
||||
{
|
||||
BOARD_SIZE = 15;
|
||||
printf("输入无效,使用默认标准棋盘15X15\n");
|
||||
}
|
||||
|
||||
empty_board();
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
|
||||
print_board();
|
||||
|
||||
// 游戏主循环
|
||||
while (1)
|
||||
{
|
||||
// 玩家回合
|
||||
int x, y;
|
||||
printf("\n请输入落子坐标(行 列,1~%d):", BOARD_SIZE);
|
||||
scanf("%d %d", &x, &y);
|
||||
// 转换为0-BOARD_SIZE索引(转为棋盘的坐标)
|
||||
x--;
|
||||
y--;
|
||||
|
||||
// 验证并执行玩家移动
|
||||
if (!player_move(x, y))
|
||||
{
|
||||
printf("坐标无效!请重新输入。\n");
|
||||
continue;
|
||||
}
|
||||
print_board();
|
||||
|
||||
// 检查玩家是否获胜
|
||||
if (check_win(x, y, PLAYER))
|
||||
{
|
||||
printf("\n玩家获胜!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
|
||||
// AI回合
|
||||
printf("\nAI思考中...\n");
|
||||
ai_move();
|
||||
print_board();
|
||||
|
||||
// 检查AI是否获胜
|
||||
Step last_step = steps[step_count - 1];
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
printf("\nAI获胜!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查平局(棋盘已满)
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
|
||||
// !打印当前棋盘状态
|
||||
void print_board()
|
||||
{
|
||||
// 打印列号(1-BOARD_SIZE显示)
|
||||
printf("\n ");
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
printf("%2d", i + 1);
|
||||
if (i + 1 == 9) // 处理两位数列号的对齐
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y)
|
||||
{
|
||||
return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY);
|
||||
}
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y)
|
||||
{
|
||||
if (!have_space(x, y))
|
||||
{
|
||||
return false; // 位置无效返回失败
|
||||
}
|
||||
|
||||
board[x][y] = PLAYER; // 更新棋盘状态
|
||||
steps[step_count++] = (Step){PLAYER, x, y}; // 记录步骤
|
||||
return true;
|
||||
}
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
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;
|
||||
|
||||
// 检查正方向(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;
|
||||
ny += dy;
|
||||
}
|
||||
// 判断正方向端点是否开放
|
||||
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE)
|
||||
{
|
||||
if (board[nx][ny] == EMPTY)
|
||||
{
|
||||
info.check_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查反方向(-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;
|
||||
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)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// !评估特定位置对当前玩家的价值(AI评估下一次落子位置的价值)
|
||||
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}; // 记录四个方向的分数
|
||||
|
||||
// 遍历四个方向
|
||||
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; // 五连珠直接返回最高分
|
||||
}
|
||||
|
||||
// 评估不同棋型的价值
|
||||
switch (info.continuous_chess)
|
||||
{
|
||||
// 四连珠
|
||||
case 4:
|
||||
// 活四(两端开放)
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 100000; // 必胜局面
|
||||
}
|
||||
// 冲四(一端开放)
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 10000; // 威
|
||||
}
|
||||
// 死四(两端封闭)
|
||||
else
|
||||
{
|
||||
line_scores[i] = 500; // 低价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 三连珠
|
||||
case 3:
|
||||
// 活三(两端开放)
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 5000; // 高价值
|
||||
}
|
||||
// 眠三(一端开放)
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 1000; // 中等价值
|
||||
}
|
||||
// 死三(两端封闭)
|
||||
else
|
||||
{
|
||||
line_scores[i] = 50; // 低价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 二连珠
|
||||
case 2:
|
||||
// 活二(两端开放)
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 500; // 发展潜力大
|
||||
}
|
||||
// 眠二(一端开放)
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 100; // 有限潜力
|
||||
}
|
||||
// 死二(两端封闭)
|
||||
else
|
||||
{
|
||||
line_scores[i] = 10; // 几乎无价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 单子
|
||||
case 1:
|
||||
// 单子但两端开放
|
||||
if (info.check_start && info.check_end)
|
||||
{
|
||||
line_scores[i] = 50; // 未来潜力
|
||||
}
|
||||
// 单子但一端开放
|
||||
else if (info.check_start || info.check_end)
|
||||
{
|
||||
line_scores[i] = 10; // 有限潜力
|
||||
}
|
||||
// 死单子
|
||||
else
|
||||
{
|
||||
line_scores[i] = 1; // 几乎无价值
|
||||
}
|
||||
break;
|
||||
|
||||
// 五子以上(已处理)
|
||||
default:
|
||||
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;
|
||||
|
||||
board[x][y] = original; // 恢复棋盘
|
||||
|
||||
// 添加位置权重:中央位置>边缘位置
|
||||
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);
|
||||
|
||||
return total_score + position_bonus;
|
||||
}
|
||||
|
||||
// !AI落子算法
|
||||
void ai_move()
|
||||
{
|
||||
int best_score = -1, 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;
|
||||
}
|
||||
|
||||
// 综合评估位置对AI的进攻价值和防御价值
|
||||
int score = evaluate_pos(i, j, AI) + evaluate_pos(i, j, PLAYER) * 0.5;
|
||||
|
||||
// 更新最佳位置
|
||||
if (score > best_score)
|
||||
{
|
||||
best_score = score;
|
||||
best_x = i;
|
||||
best_y = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在最佳位置落子
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process()
|
||||
{
|
||||
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;
|
||||
|
||||
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.player == PLAYER, 则打印“玩家”,反之打印“AI”
|
||||
s.x + 1, s.y + 1); // 显示为(行,列)
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘) =====\n", BOARD_SIZE, BOARD_SIZE);
|
||||
|
||||
// 打印当前棋盘状态
|
||||
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)
|
||||
printf("x ");
|
||||
else if (temp_board[row][col] == AI)
|
||||
printf("○ ");
|
||||
else
|
||||
printf("· ");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (i < step_count - 1)
|
||||
{
|
||||
printf("\n按Enter继续下一步...");
|
||||
while (getchar() != '\n')
|
||||
;
|
||||
}
|
||||
}
|
||||
printf("\n复盘结束!按Enter返回...");
|
||||
|
||||
getchar();
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @brief 将指令复制到powershell
|
||||
* gcc "课上代码练习\五子棋\五子棋.c" -o output/五子棋.exe
|
||||
* @brief 将指令复制到powershell
|
||||
* .\output\五子棋.exe
|
||||
*/
|
||||
|
||||
// !宏定义
|
||||
#define MAX_BOARD_SIZE 25 //* 最大支持棋盘尺寸
|
||||
int BOARD_SIZE = 15; //* 实际使用的棋盘尺寸(默认15)
|
||||
#define PLAYER 1 //* 玩家棋子标识符
|
||||
#define AI 2 //* AI棋子标识符
|
||||
#define EMPTY 0 //* 空位置标识符
|
||||
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) //* 最大步数(棋盘总格数)
|
||||
|
||||
// !全局变量
|
||||
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; //* 棋盘状态存储数组(默认棋盘全空为0)
|
||||
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; //* 四个方向:向下、向右、右下、左下
|
||||
|
||||
// !落子步骤结构体
|
||||
typedef struct // 落子结构体
|
||||
{
|
||||
int player; // 落子方标识
|
||||
int x, y; // 坐标位置
|
||||
} Step;
|
||||
|
||||
// !方向信息结构体
|
||||
typedef struct
|
||||
{
|
||||
int continuous_chess; // 连续棋子数量
|
||||
bool check_start; // 序列起点方向是否开放(空位)
|
||||
bool check_end; // 序列终点方向是否开放(空位)
|
||||
} DirInfo;
|
||||
|
||||
Step steps[MAX_STEPS]; // 存储所有落子步骤
|
||||
|
||||
int step_count = 0; // 当前步数计数器
|
||||
|
||||
// !初始化棋盘:清空棋盘状态和步数记录
|
||||
void empty_board();
|
||||
|
||||
// !打印当前棋盘状态
|
||||
void print_board();
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y);
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y);
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
|
||||
// !检查特定位置落子后是否获胜
|
||||
bool check_win(int x, int y, int player);
|
||||
|
||||
// !评估特定位置对当前玩家的价值
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
|
||||
// !AI落子算法
|
||||
void ai_move();
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process();
|
||||
|
||||
// !主函数:游戏流程控制
|
||||
int main()
|
||||
{
|
||||
// 初始化阶段:获取棋盘尺寸
|
||||
printf("===== 五子棋人机对战 =====\n");
|
||||
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
|
||||
printf("请输入棋盘大小(5~%d)(默认为标准棋盘):", MAX_BOARD_SIZE);
|
||||
scanf("%d", &BOARD_SIZE);
|
||||
if (BOARD_SIZE < 5 || BOARD_SIZE > MAX_BOARD_SIZE)
|
||||
{
|
||||
BOARD_SIZE = 15;
|
||||
printf("输入无效,使用默认标准棋盘15X15\n");
|
||||
}
|
||||
|
||||
empty_board();
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
|
||||
print_board();
|
||||
|
||||
// 游戏主循环
|
||||
while (1)
|
||||
{
|
||||
// 玩家回合
|
||||
int x, y;
|
||||
printf("\n请输入落子坐标(行 列,1~%d):", BOARD_SIZE);
|
||||
scanf("%d %d", &x, &y);
|
||||
// 转换为0-BOARD_SIZE索引(转为棋盘的坐标)
|
||||
x--;
|
||||
y--;
|
||||
|
||||
// 验证并执行玩家移动
|
||||
if (!player_move(x, y))
|
||||
{
|
||||
printf("坐标无效!请重新输入。\n");
|
||||
continue;
|
||||
}
|
||||
print_board();
|
||||
|
||||
// 检查玩家是否获胜
|
||||
if (check_win(x, y, PLAYER))
|
||||
{
|
||||
printf("\n玩家获胜!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
|
||||
// AI回合
|
||||
printf("\nAI思考中...\n");
|
||||
ai_move();
|
||||
print_board();
|
||||
|
||||
// 检查AI是否获胜
|
||||
Step last_step = steps[step_count - 1];
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
printf("\nAI获胜!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查平局(棋盘已满)
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
review_process();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
|
||||
// !打印当前棋盘状态
|
||||
void print_board()
|
||||
{
|
||||
// 打印列号(1-BOARD_SIZE显示)
|
||||
printf("\n ");
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
printf("%2d", i + 1);
|
||||
if (i + 1 == 9) // 处理两位数列号的对齐
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// !检查位置是否有效(在棋盘范围内且为空位)
|
||||
bool have_space(int x, int y)
|
||||
{
|
||||
/* 检查位置是否有效且为空位 */
|
||||
return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY);
|
||||
}
|
||||
|
||||
// !玩家落子操作
|
||||
bool player_move(int x, int y)
|
||||
{
|
||||
if (!have_space(x, y))
|
||||
{
|
||||
return false; // 位置无效返回失败
|
||||
}
|
||||
|
||||
board[x][y] = PLAYER; // 更新棋盘状态
|
||||
steps[step_count++] = (Step){PLAYER, x, y}; // 记录步骤
|
||||
return true;
|
||||
}
|
||||
|
||||
// !计算特定方向上连续同色棋子数量
|
||||
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;
|
||||
|
||||
// 检查正方向(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;
|
||||
ny += dy;
|
||||
}
|
||||
// 判断正方向端点是否开放
|
||||
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE)
|
||||
{
|
||||
if (board[nx][ny] == EMPTY)
|
||||
{
|
||||
info.check_end = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查反方向(-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;
|
||||
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)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// !评估特定位置对当前玩家的价值(AI评估下一次落子位置的价值)
|
||||
int evaluate_pos(int x, int y, int player)
|
||||
{
|
||||
// 临时模拟落子(确保评估准确)
|
||||
int original = board[x][y];
|
||||
board[x][y] = player;
|
||||
|
||||
int score = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int dx = direction[i][0], dy = direction[i][1];
|
||||
int opponent = (player == PLAYER) ? AI : PLAYER;
|
||||
|
||||
// 获取当前玩家(自身)的棋型信息
|
||||
DirInfo self_info = count_specific_direction(x, y, dx, dy, player);
|
||||
// 获取对手的棋型信息
|
||||
DirInfo oppo_info = count_specific_direction(x, y, dx, dy, opponent);
|
||||
|
||||
// 对手威胁评估(防守逻辑)
|
||||
if (oppo_info.continuous_chess >= 4)
|
||||
{
|
||||
board[x][y] = original; // 恢复棋盘
|
||||
return 10000; // 对手四连珠必须阻止
|
||||
}
|
||||
else if (oppo_info.continuous_chess == 3)
|
||||
{
|
||||
// 玩家三连珠
|
||||
if (oppo_info.check_start && oppo_info.check_end) // 仅当两端开放才高威胁
|
||||
{
|
||||
score += 5000; // 活三(两端开放),高威胁
|
||||
}
|
||||
else if (oppo_info.check_start || oppo_info.check_end) // 死三(两端封闭)不加分
|
||||
{
|
||||
score += 2000; // 眠三(一端开放),中等威胁
|
||||
}
|
||||
}
|
||||
|
||||
// 自身机会评估(进攻逻辑)
|
||||
if (self_info.continuous_chess >= 5)
|
||||
{
|
||||
board[x][y] = original; // 恢复棋盘
|
||||
return 20000; // 五连珠直接获胜
|
||||
}
|
||||
else if (self_info.continuous_chess == 4)
|
||||
{
|
||||
score += 8000; // 四连珠必胜
|
||||
}
|
||||
else if (self_info.continuous_chess == 3)
|
||||
{
|
||||
// AI三连珠
|
||||
if (self_info.check_start && self_info.check_end) // 两端开放才高价值
|
||||
{
|
||||
score += 4000; // 活三(两端开放),高价值
|
||||
}
|
||||
else if (self_info.check_start || self_info.check_end) // 死三(两端封闭)不加分
|
||||
{
|
||||
score += 1500; // 眠三(一端开放),中等价值
|
||||
}
|
||||
}
|
||||
else if (self_info.continuous_chess == 2)
|
||||
{
|
||||
score += 1000; // 二连珠基础分
|
||||
}
|
||||
}
|
||||
|
||||
board[x][y] = original; // 恢复棋盘
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// !AI落子算法
|
||||
void ai_move()
|
||||
{
|
||||
int best_score = -1, 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;
|
||||
}
|
||||
|
||||
// 综合评估位置对AI的进攻价值和防御价值
|
||||
int score = evaluate_pos(i, j, AI) + evaluate_pos(i, j, PLAYER) * 0.5;
|
||||
|
||||
// 更新最佳位置
|
||||
if (score > best_score)
|
||||
{
|
||||
best_score = score;
|
||||
best_x = i;
|
||||
best_y = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在最佳位置落子
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// !复盘游戏过程
|
||||
void review_process()
|
||||
{
|
||||
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;
|
||||
|
||||
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.player == PLAYER, 则打印“玩家”,反之打印“AI”
|
||||
s.x + 1, s.y + 1); // 显示为(行,列)
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘) =====\n", BOARD_SIZE, BOARD_SIZE);
|
||||
|
||||
// 打印当前棋盘状态
|
||||
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)
|
||||
printf("x ");
|
||||
else if (temp_board[row][col] == AI)
|
||||
printf("○ ");
|
||||
else
|
||||
printf("· ");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (i < step_count - 1)
|
||||
{
|
||||
printf("\n按Enter继续下一步...");
|
||||
while (getchar() != '\n')
|
||||
;
|
||||
}
|
||||
}
|
||||
printf("\n复盘结束!按Enter返回...");
|
||||
|
||||
getchar();
|
||||
}
|
||||
Reference in New Issue
Block a user