五子棋AI实战:手把手教你用C语言实现三种难度的人机对战

五子棋AI实战:从零构建具备三种思维层次的智能对手

最近在整理大学时期的代码仓库,翻出了一个尘封已久的五子棋项目。当时为了完成课程设计,硬是用C语言从零搭建了一个带图形界面的五子棋游戏,最让我自豪的是那个花了整整两周调试的AI模块。现在回头看,那个AI的设计思路其实很有意思——它不像现在深度学习模型那样“黑箱”,而是用清晰的逻辑规则模拟了人类下棋时的三种思考层次。今天我就把这个项目的核心AI实现拆解出来,分享给对游戏AI开发感兴趣的朋友们。

如果你已经掌握了C语言的基础语法,想找一个既有挑战性又能真正学到东西的实战项目,那么亲手实现一个五子棋AI会是个绝佳的选择。它不像大型游戏引擎那样复杂,但涉及的状态评估、搜索策略等核心思想,却是所有游戏AI的基石。更重要的是,你能亲眼看到自己写的代码从“胡乱落子”到“步步为营”的进化过程,这种成就感是单纯看书无法比拟的。

1. 项目环境搭建与基础框架

在开始编写AI之前,我们需要先搭建一个能运行的游戏框架。这个框架不需要太复杂,但必须清晰地将界面、逻辑和数据分开,这样后续添加AI时才不会陷入混乱。

1.1 选择图形库与开发环境

对于C语言的图形界面,我推荐使用 EasyX 库。它专为初学者设计,API简单直观,在Windows环境下安装方便,能快速实现窗口、绘图等基础功能。如果你使用的是Visual Studio,可以直接通过其自带的包管理器安装;如果是其他IDE,也可以从官网下载手动配置。

// 示例:EasyX基础窗口初始化
#include <graphics.h>

int main() {
    initgraph(640, 720);  // 创建640x720的窗口
    setbkcolor(RGB(240, 217, 181)); // 设置背景色为米黄色
    cleardevice(); // 清屏
    
    // 这里开始绘制棋盘和界面
    // ...
    
    getch(); // 等待按键
    closegraph(); // 关闭图形窗口
    return 0;
}

注意:EasyX主要兼容Windows平台。如果你的开发环境是Linux或macOS,可以考虑使用SDL2或GTK等跨平台库,但API会相对复杂一些。

1.2 设计核心数据结构

棋盘是五子棋游戏的灵魂,如何表示它直接影响后续所有算法的效率。一个15×15的标准棋盘,用二维数组是最直观的选择。

#define BOARD_SIZE 15

// 棋盘状态:0-空位,1-黑棋,2-白棋
int chessBoard[BOARD_SIZE][BOARD_SIZE] = {0};

// 棋子链表节点,用于记录每一步历史,方便悔棋功能
typedef struct ChessNode {
    int x;
    int y;
    int player; // 1或2
    struct ChessNode* next;
} ChessNode;

// 游戏全局状态
typedef struct GameState {
    int currentPlayer; // 当前该谁下:1-黑棋,2-白棋
    int gameMode;      // 0-双人,1-人机
    int aiDifficulty;  // AI难度:0-简单,1-困难,2-地狱
    int gameOver;      // 游戏是否结束
    ChessNode* historyHead; // 历史记录链表头
} GameState;

为什么用链表记录历史而不是数组?因为五子棋最多225步,链表的内存开销可以接受,而它的最大优势是悔棋操作变得极其简单——只需要删除尾节点并恢复棋盘状态即可。这在调试AI时特别有用,你可以随时回退到上一步,观察AI在不同局面下的选择。

1.3 绘制模块与用户交互

图形界面不仅要好看,更要好用。我的经验是,把绘制代码封装成独立的函数,每个函数只负责一个视觉元素。

void drawChessboard() {
    setlinecolor(RGB(101, 67, 33)); // 深棕色线条
    setlinestyle(PS_SOLID, 2);
    
    // 绘制棋盘网格
    for (int i = 0; i < BOARD_SIZE; i++) {
        // 横线
        line(MARGIN, MARGIN + i * GRID_SIZE, 
             MARGIN + (BOARD_SIZE-1) * GRID_SIZE, 
             MARGIN + i * GRID_SIZE);
        // 竖线
        line(MARGIN + i * GRID_SIZE, MARGIN,
             MARGIN + i * GRID_SIZE,
             MARGIN + (BOARD_SIZE-1) * GRID_SIZE);
    }
    
    // 绘制五个天元和星位
    fillcircle(MARGIN + 7 * GRID_SIZE, MARGIN + 7 * GRID_SIZE, 5);
    // ... 其他四个星位
}

void drawChessPiece(int x, int y, int player) {
    if (player == 1) {
        setfillcolor(BLACK);
        solidcircle(MARGIN + x * GRID_SIZE, 
                    MARGIN + y * GRID_SIZE, 
                    PIECE_RADIUS);
    } else {
        setfillcolor(WHITE);
        setlinecolor(BLACK);
        circle(MARGIN + x * GRID_SIZE, 
               MARGIN + y * GRID_SIZE, 
               PIECE_RADIUS);
        solidcircle(MARGIN + x * GRID_SIZE, 
                    MARGIN + y * GRID_SIZE, 
                    PIECE_RADIUS - 2);
    }
}

用户交互的核心是将鼠标点击坐标转换为棋盘坐标。这里有个细节需要注意:因为棋子是落在交叉点上,不是格子内,所以转换时要四舍五入到最近的交叉点。

// 将鼠标坐标转换为棋盘坐标
int getBoardPos(int mouseX, int mouseY, int *boardX, int *boardY) {
    // 检查是否在棋盘范围内
    if (mouseX < MARGIN - GRID_SIZE/2 || mouseX > MARGIN + (BOARD_SIZE-1)*GRID_SIZE + GRID_SIZE/2 ||
        mouseY < MARGIN - GRID_SIZE/2 || mouseY > MARGIN + (BOARD_SIZE-1)*GRID_SIZE + GRID_SIZE/2) {
        return 0; // 不在棋盘内
    }
    
    // 计算最近的交叉点
    *boardX = (int)round((mouseX - MARGIN) / (double)GRID_SIZE);
    *boardY = (int)round((mouseY - MARGIN) / (double)GRID_SIZE);
    
    // 确保坐标在有效范围内
    if (*boardX < 0) *boardX = 0;
    if (*boardX >= BOARD_SIZE) *boardX = BOARD_SIZE - 1;
    if (*boardY < 0) *boardY = 0;
    if (*boardY >= BOARD_SIZE) *boardY = BOARD_SIZE - 1;
    
    return 1;
}

2. 胜负判定与基础棋型识别

在让AI学会思考之前,它至少得知道游戏什么时候结束、什么样的棋型是危险的。胜负判定看似简单,但写起来有很多优化空间。

2.1 高效的四方向检测算法

最直观的胜负判定方法是:每落一子,就从该位置向四个方向(横、竖、左斜、右斜)检查是否有连续五个同色棋子。但直接写四个循环会有大量重复代码,维护起来很麻烦。

// 方向数组:横、竖、左斜、右斜
const int dirs[4][2] = {
    {1, 0},  // 水平向右
    {0, 1},  // 垂直向下
    {1, 1},  // 右下对角线
    {1, -1}  // 右上对角线
};

int checkWin(int board[BOARD_SIZE][BOARD_SIZE], int x, int y) {
    int player = board[x][y];
    if (player == 0) return 0;
    
    for (int d = 0; d < 4; d++) {
        int count = 1; // 当前位置已经有一颗棋子
        
        // 正向检查
        int dx = dirs[d][0], dy = dirs[d][1];
        int nx = x + dx, ny = y + dy;
        while (nx >= 0 && nx < BOARD_SIZE && 
               ny >= 0 && ny < BOARD_SIZE && 
               board[nx][ny] == player) {
            count++;
            nx += dx;
            ny += dy;
        }
        
        // 反向检查
        dx = -dirs[d][0];
        dy = -dirs[d][1];
        nx = x + dx;
        ny = y + dy;
        while (nx >= 0 && nx < BOARD_SIZE && 
               ny >= 0 && ny <
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值