环境准备
在运行代码之前,你需要安装 EasyX 图形库。

(图片来源网络,侵删)
-
如果你使用 Visual Studio:
- 下载 EasyX 安装包:http://www.easyx.cn/
- 运行安装程序,它会自动配置好 Visual Studio。
-
如果你使用 Dev-C++ (较新的版本):
- 下载支持 EasyX 的 Dev-C++ 版本或手动配置。
- 在编译选项中加入
-lgraphics -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32。
-
如果你使用 MinGW / GCC:
- 下载 EasyX 的 MinGW 版本。
- 将头文件和库文件路径正确配置到你的编译器中。
- 编译时链接必要的库:
gcc -o gomoku gomoku.c -lgraphics -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32
C语言代码 (Gomoku.c)
#include <graphics.h> // EasyX 图形库头文件
#include <stdio.h>
#include <stdlib.h>
#include <conio.h> // 用于 _kbhit() 和 _getch()
#include <time.h> // 用于 srand() 和 time()
// --- 游戏常量定义 ---
#define GRID_SIZE 40 // 棋盘格子大小
#define BOARD_WIDTH 15 // 棋盘宽度 (格子数)
#define BOARD_HEIGHT 15 // 棋盘高度 (格子数)
#define MARGIN 50 // 棋盘边距
#define WINDOW_WIDTH (BOARD_WIDTH * GRID_SIZE + 2 * MARGIN)
#define WINDOW_HEIGHT (BOARD_HEIGHT * GRID_SIZE + 2 * MARGIN + 50) // 额外空间用于显示信息
// --- 棋子类型 ---
#define EMPTY 0
#define BLACK 1
#define WHITE 2
// --- 全局变量 ---
int board[BOARD_HEIGHT][BOARD_WIDTH]; // 棋盘数组
int currentPlayer = BLACK; // 当前玩家 (黑棋先手)
int gameOver = 0; // 游戏是否结束
int aiThinking = 0; // AI是否正在思考
// --- 函数声明 ---
void initGame();
void drawBoard();
void drawPieces();
void drawGameInfo();
void handleClick(int x, int y);
int makeMove(int row, int col, int player);
int checkWin(int row, int col, int player);
void playerMove();
void aiMove();
int evaluatePosition(int player);
int minimax(int depth, int alpha, int beta, int maximizingPlayer);
void findBestMove(int *bestRow, int *bestCol);
// --- 主函数 ---
int main() {
// 初始化图形窗口
initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
randomize(); // 初始化随机数种子
// 初始化游戏
initGame();
// 游戏主循环
while (1) {
// 清空屏幕
cleardevice();
// 绘制游戏界面
drawBoard();
drawPieces();
drawGameInfo();
// 处理玩家输入
if (!gameOver && currentPlayer == BLACK && !aiThinking) {
MOUSEMSG msg;
if (MouseHit()) {
msg = GetMouseMsg();
if (msg.uMsg == WM_LBUTTONDOWN) {
handleClick(msg.x, msg.y);
}
}
}
// AI回合
if (!gameOver && currentPlayer == WHITE) {
aiThinking = 1;
aiMove();
aiThinking = 0;
}
// 检查游戏是否结束 (通过按键)
if (_kbhit()) {
if (_getch() == 27) { // ESC键退出
break;
}
if (_getch() == 'r' || _getch() == 'R') { // R键重新开始
initGame();
}
}
// 延迟,避免CPU占用过高
Sleep(10);
}
// 关闭图形窗口
closegraph();
return 0;
}
// --- 函数实现 ---
/**
* @brief 初始化游戏状态
*/
void initGame() {
// 清空棋盘
for (int i = 0; i < BOARD_HEIGHT; i++) {
for (int j = 0; j < BOARD_WIDTH; j++) {
board[i][j] = EMPTY;
}
}
currentPlayer = BLACK;
gameOver = 0;
aiThinking = 0;
}
/**
* @brief 绘制棋盘
*/
void drawBoard() {
setbkcolor(LIGHTGRAY); // 设置背景色
cleardevice();
setlinecolor(BLACK);
setlinestyle(PS_SOLID | PS_ENDCAP_FLAT, 1);
// 绘制横线
for (int i = 0; i < BOARD_HEIGHT; i++) {
line(MARGIN, MARGIN + i * GRID_SIZE, MARGIN + (BOARD_WIDTH - 1) * GRID_SIZE, MARGIN + i * GRID_SIZE);
}
// 绘制竖线
for (int i = 0; i < BOARD_WIDTH; i++) {
line(MARGIN + i * GRID_SIZE, MARGIN, MARGIN + i * GRID_SIZE, MARGIN + (BOARD_HEIGHT - 1) * GRID_SIZE);
}
// 绘制天元和星位
setfillcolor(BLACK);
fillcircle(MARGIN + 7 * GRID_SIZE, MARGIN + 7 * GRID_SIZE, 5);
fillcircle(MARGIN + 3 * GRID_SIZE, MARGIN + 3 * GRID_SIZE, 5);
fillcircle(MARGIN + 11 * GRID_SIZE, MARGIN + 3 * GRID_SIZE, 5);
fillcircle(MARGIN + 3 * GRID_SIZE, MARGIN + 11 * GRID_SIZE, 5);
fillcircle(MARGIN + 11 * GRID_SIZE, MARGIN + 11 * GRID_SIZE, 5);
}
/**
* @brief 绘制所有棋子
*/
void drawPieces() {
for (int i = 0; i < BOARD_HEIGHT; i++) {
for (int j = 0; j < BOARD_WIDTH; j++) {
if (board[i][j] != EMPTY) {
int x = MARGIN + j * GRID_SIZE;
int y = MARGIN + i * GRID_SIZE;
if (board[i][j] == BLACK) {
setfillcolor(BLACK);
} else {
setfillcolor(WHITE);
}
fillcircle(x, y, GRID_SIZE / 2 - 2);
setlinecolor(BLACK);
circle(x, y, GRID_SIZE / 2 - 2);
}
}
}
}
/**
* @brief 绘制游戏信息 (当前玩家、游戏结果)
*/
void drawGameInfo() {
setbkmode(TRANSPARENT);
settextstyle(24, 0, "微软雅黑");
if (gameOver) {
if (currentPlayer == BLACK) {
outtextxy(WINDOW_WIDTH / 2 - 60, WINDOW_HEIGHT - 40, "白棋胜利!");
} else {
outtextxy(WINDOW_WIDTH / 2 - 60, WINDOW_HEIGHT - 40, "黑棋胜利!");
}
outtextxy(WINDOW_WIDTH / 2 - 80, WINDOW_HEIGHT - 20, "按 R 重新开始");
} else {
if (currentPlayer == BLACK) {
outtextxy(WINDOW_WIDTH / 2 - 40, WINDOW_HEIGHT - 40, "轮到黑棋");
} else {
if(aiThinking) {
outtextxy(WINDOW_WIDTH / 2 - 60, WINDOW_HEIGHT - 40, "AI思考中...");
} else {
outtextxy(WINDOW_WIDTH / 2 - 40, WINDOW_HEIGHT - 40, "轮到白棋");
}
}
outtextxy(WINDOW_WIDTH / 2 - 40, WINDOW_HEIGHT - 20, "按 ESC 退出");
}
}
/**
* @brief 处理鼠标点击事件
* @param x 鼠标x坐标
* @param y 鼠标y坐标
*/
void handleClick(int x, int y) {
// 计算点击的棋盘格子坐标
int col = (x - MARGIN + GRID_SIZE / 2) / GRID_SIZE;
int row = (y - MARGIN + GRID_SIZE / 2) / GRID_SIZE;
// 检查点击是否在有效范围内
if (row >= 0 && row < BOARD_HEIGHT && col >= 0 && col < BOARD_WIDTH && board[row][col] == EMPTY) {
if (makeMove(row, col, currentPlayer)) {
if (checkWin(row, col, currentPlayer)) {
gameOver = 1;
} else {
currentPlayer = (currentPlayer == BLACK) ? WHITE : BLACK; // 切换玩家
}
}
}
}
/**
* @brief 在指定位置落子
* @param row 行
* @param col 列
* @param player 玩家
* @return 落子是否成功
*/
int makeMove(int row, int col, int player) {
if (board[row][col] == EMPTY) {
board[row][col] = player;
return 1;
}
return 0;
}
/**
* @brief 检查是否获胜
* @param row 最后落子的行
* @param col 最后落子的列
* @param player 玩家
* @return 是否获胜
*/
int checkWin(int row, int col, int player) {
// 检查方向: 水平、垂直、两个对角线
int directions[4][2] = {{0, 1}, {1, 0}, {1, 1}, {1, -1}};
for (int i = 0; i < 4; i++) {
int count = 1; // 从当前棋子开始计数
// 正向检查
for (int j = 1; j < 5; j++) {
int r = row + j * directions[i][0];
int c = col + j * directions[i][1];
if (r >= 0 && r < BOARD_HEIGHT && c >= 0 && c < BOARD_WIDTH && board[r][c] == player) {
count++;
} else {
break;
}
}
// 反向检查
for (int j = 1; j < 5; j++) {
int r = row - j * directions[i][0];
int c = col - j * directions[i][1];
if (r >= 0 && r < BOARD_HEIGHT && c >= 0 && c < BOARD_WIDTH && board[r][c] == player) {
count++;
} else {
break;
}
}
if (count >= 5) {
return 1; // 赢了
}
}
return 0; // 未赢
}
/**
* @brief AI下棋逻辑
*/
void aiMove() {
int bestRow, bestCol;
findBestMove(&bestRow, &bestCol);
if (makeMove(bestRow, bestCol, WHITE)) {
if (checkWin(bestRow, bestCol, WHITE)) {
gameOver = 1;
} else {
currentPlayer = BLACK; // 切换到玩家回合
}
}
}
/**
* @brief 使用Minimax算法寻找最佳落子点
* @param bestRow 最佳落子行指针
* @param bestCol 最佳落子列指针
*/
void findBestMove(int *bestRow, int *bestCol) {
int bestScore = -1000000;
int depth = 3; // 搜索深度,可根据性能调整
// 遍历棋盘,寻找所有空位
for (int i = 0; i < BOARD_HEIGHT; i++) {
for (int j = 0; j < BOARD_WIDTH; j++) {
if (board[i][j] == EMPTY) {
// 模拟落子
board[i][j] = WHITE;
int score = minimax(depth, -1000000, 1000000, 0); // AI是最大化玩家
board[i][j] = EMPTY; // 撤销模拟
// 如果是第一步,随机选择一个靠近中心的位置
if (score > bestScore && (board[7][7] == EMPTY && score > 0)) {
bestScore = score;
*bestRow = i;
*bestCol = j;
} else if (score > bestScore) {
bestScore = score;
*bestRow = i;
*bestCol = j;
}
}
}
}
}
/**
* @brief Minimax算法的Alpha-Beta剪枝实现
* @param depth 当前搜索深度
* @param alpha Alpha值
* @param beta Beta值
* @param maximizingPlayer 是否是最大化玩家 (AI)
* @return 当前局面的评分
*/
int minimax(int depth, int alpha, int beta, int maximizingPlayer) {
// 检查终止条件: 深度为0 或有一方获胜
if (depth == 0) {
return evaluatePosition(WHITE) - evaluatePosition(BLACK);
}
// 简单的胜负判断,提高效率
for(int i = 0; i < BOARD_HEIGHT; i++){
for(int j = 0; j < BOARD_WIDTH; j++){
if(board[i][j] != EMPTY){
if(checkWin(i, j, WHITE)) return 1000000 + depth;
if(checkWin(i, j, BLACK)) return -1000000 - depth;
}
}
}
if (maximizingPlayer) { // AI的回合 (最大化玩家)
int maxEval = -1000000;
for (int i = 0; i < BOARD_HEIGHT; i++) {
for (int j = 0; j < BOARD_WIDTH; j++) {
if (board[i][j] == EMPTY) {
board[i][j] = WHITE;
int eval = minimax(depth - 1, alpha, beta, 0);
board[i][j] = EMPTY;
maxEval = (eval > maxEval) ? eval : maxEval;
alpha = (alpha > eval) ? alpha : eval;
if (beta <= alpha) {
return maxEval; // Alpha-Beta剪枝
}
}
}
}
return maxEval;
} else { // 玩家的回合 (最小化玩家)
int minEval = 1000000;
for (int i = 0; i < BOARD_HEIGHT; i++) {
for (int j = 0; j < BOARD_WIDTH; j++) {
if (board[i][j] == EMPTY) {
board[i][j] = BLACK;
int eval = minimax(depth - 1, alpha, beta, 1);
board[i][j] = EMPTY;
minEval = (eval < minEval) ? eval : minEval;
beta = (beta < eval) ? beta : eval;
if (beta <= alpha) {
return minEval; // Alpha-Beta剪枝
}
}
}
}
return minEval;
}
}
/**
* @brief 评估当前局面对AI的分数
* @param player 要评估的玩家
* @return 分数
*/
int evaluatePosition(int player) {
int score = 0;
int opponent = (player == WHITE) ? BLACK : WHITE;
// 定义连子分数
int scores[5] = {0, 10, 100, 1000, 10000}; // 1, 2, 3, 4, 5连
// 检查所有方向
for (int i = 0; i < BOARD_HEIGHT; i++) {
for (int j = 0; j < BOARD_WIDTH; j++) {
if (board[i][j] == player || board[i][j] == opponent) {
int p = (board[i][j] == player) ? player : opponent;
int o = (p == player) ? opponent : player;
// 水平方向
int count_p = 1, count_o = 0;
if (j + 4 < BOARD_WIDTH) {
for (int k = 1; k < 5; k++) {
if (board[i][j + k] == p) count_p++;
else if (board[i][j + k] == o) { count_o++; break; }
}
if (count_o == 0) score += scores[count_p];
}
// 垂直方向
count_p = 1; count_o = 0;
if (i + 4 < BOARD_HEIGHT) {
for (int k = 1; k < 5; k++) {
if (board[i + k][j] == p) count_p++;
else if (board[i + k][j] == o) { count_o++; break; }
}
if (count_o == 0) score += scores[count_p];
}
// 对角线方向 (左上到右下)
count_p = 1; count_o = 0;
if (i + 4 < BOARD_HEIGHT && j + 4 < BOARD_WIDTH) {
for (int k = 1; k < 5; k++) {
if (board[i + k][j + k] == p) count_p++;
else if (board[i + k][j + k] == o) { count_o++; break; }
}
if (count_o == 0) score += scores[count_p];
}
// 对角线方向 (右上到左下)
count_p = 1; count_o = 0;
if (i + 4 < BOARD_HEIGHT && j - 4 >= 0) {
for (int k = 1; k < 5; k++) {
if (board[i + k][j - k] == p) count_p++;
else if (board[i + k][j - k] == o) { count_o++; break; }
}
if (count_o == 0) score += scores[count_p];
}
}
}
}
return score;
}
如何运行和操作
- 保存代码: 将上面的代码保存为
Gomoku.c文件。 - 编译运行: 在你的IDE(如Visual Studio)中打开并编译运行此文件。
- 游戏操作:
- 黑棋 (你): 使用鼠标点击棋盘的交叉点来落子。
- 白棋 (AI): AI会自动进行思考并落子。
- 获胜: 任意一方在横、竖、斜方向上连成五子即获胜。
- 退出游戏: 按键盘上的
ESC键。 - 重新开始: 游戏结束后,按
R键可以重新开始一局新游戏。
代码核心逻辑解析
-
棋盘与界面 (
initGame,drawBoard,drawPieces):
(图片来源网络,侵删)- 使用一个二维数组
board[BOARD_HEIGHT][BOARD_WIDTH]来存储棋盘状态,0代表空,1代表黑棋,2代表白棋。 EasyX库负责绘制图形。drawBoard()画出网格线,drawPieces()遍历数组,根据数组状态画出黑棋和白棋。
- 使用一个二维数组
-
游戏流程 (
main,handleClick):main函数是主循环,不断重绘界面并处理事件。handleClick将鼠标的屏幕坐标转换为棋盘的格子坐标,并调用makeMove尝试落子。
-
胜负判定 (
checkWin):在每次落子后,从当前落子点出发,检查四个方向(水平、垂直、两个对角线)上是否有连续五个同色棋子,这是一个非常直接且高效的实现。
-
AI算法 (
aiMove,findBestMove,minimax,evaluatePosition):
(图片来源网络,侵删)- 这是整个程序的核心,AI采用了 Minimax算法 结合 Alpha-Beta剪枝 来进行决策。
evaluatePosition(评估函数): 这是AI的“眼睛”,它扫描整个棋盘,为AI(白棋)的每一个潜在落子位置打分,评分标准是连子数,比如连成2个子得10分,连成3个子得100分,以此类推,这个函数会同时考虑AI的进攻和防守(即评估对手的得分)。minimax(极小化极大算法): 这是一个递归算法,AI会模拟未来几步(depth)的对弈。- Maximizing Player (AI): 在它的回合,它会选择一个能让得分最高的走法。
- Minimizing Player (玩家): 在玩家的回合,它会假设玩家会选择一个能让AI得分最低(即对玩家最有利)的走法。
findBestMove: 它是AI的决策入口,它遍历所有空位,对每一个空位调用minimax来模拟走完这一步之后,经过几层递归推演,最终得到的分数,它选择那个能让最终分数最高的空位作为最佳落子点。- Alpha-Beta剪枝: 这是
minimax的一个优化,它通过维护alpha(AI当前能得到的最大分)和beta(玩家能强加给AI的最小分)两个值,来“剪掉”那些明显不会成为最优解的分支,极大地减少了计算量,使得AI可以搜索得更深。
这个AI虽然不是最强的,但足以提供不错的挑战性,并且很好地展示了游戏AI的基本原理,你可以通过调整 minimax 的搜索深度来改变AI的难度(深度越深,AI越强,但计算时间也越长)。
