三子棋游戏设计与实现(C 语言版)

一、需求分析

目标:实现一个简单的人机对战三子棋,支持以下功能:

  1. 初始化空棋盘,清晰展示落子状态。
  2. 玩家通过坐标落子(X 代表玩家),电脑随机落子(O 代表电脑)。
  3. 实时判断胜负:某方三子连成一线(行 / 列 / 对角线)则获胜;棋盘填满则平局。

二、设计思路

1. 模块拆分(高内聚低耦合)

  • 数据层 :用二维数组 char board[ROW][COL] 存储棋盘状态。
  • 逻辑层:封装独立函数,如初始化棋盘、打印棋盘、落子、胜负判断。
  • 交互层:通过菜单让用户选择 "开始游戏" 或 "退出",控制游戏流程。

三、代码结构解析

1. 多文件组织(工程化思路)

  • Noughts and crosses.h:声明函数、定义常量(如 ROW=3),解耦接口与实现。
  • Noughts and crosses.c:实现具体逻辑(初始化、打印、落子等),隐藏细节。
  • test.c:负责游戏流程控制(菜单、主循环),专注交互。

2. 关键函数拆解

1.头文件:

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include<windows.h>


//棋盘尺寸
#define ROW 3
#define COL 3

//初始化
void InitBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//判断输赢
char CheckWin(char board[ROW][COL], int row, int col);
//设置控制台文本颜色
void SetColor(int color);

2.关键函数分析(每个函数的定义写在Noughts and crosses.c):

cpp 复制代码
void InitBoard(char board[ROW][COL], int row, int col)
{
    for (int i = 0;i < row;i++)
    {
        for (int j = 0;j < col;j++)
        {
            board[i][j] = ' ';
        }
    }
    //随机种字(用于电脑下棋随机化)
    srand((unsigned int)time(NULL));
}

初始化,通过二维数组来实现棋盘,将9个格子初始化为空。 srand((unsigned int)time(NULL));这段代码:是指time(NULL)以获取当前系统时间(从 1970 年 1 月 1 日到现在的秒数,也叫 "时间戳" )。将其类型转换为unsigned int 来适配srand 这样就会生成随机数的 "起点,生成真随机值。如果是一个常数,那么会生成固定的序列。
2.

cpp 复制代码
void DisplayBoard(char board[ROW][COL], int row, int col)
{
    system("cls");//清空
    printf("=== 三子棋游戏 ===\n");
    for (int i = 0;i < row;i++)
    {
        for (int j = 0;j < col;j++)
        {
            if (board[i][j] == 'X')
            {
                SetColor(12);//红色
            }
            else if (board[i][j] == 'O')
            {
                SetColor(9);//蓝色
            }
            else
            {
                SetColor(7);//白色默认色
            }
            printf(" %c ", board[i][j]);
            if (j < col - 1)
            {
                SetColor(7);
                printf("|");
            }
            
        }

        printf("\n");
        if (i < row - 1)
        {
            SetColor(7);
            for (int j = 0; j < col; j++)
            {
                printf("---");
                if (j < col - 1)
                {
                    printf("|");
                }
            }
            printf("\n");
        }
    }
}

打印棋盘,棋盘里会有X ,O ," " ,---,|,等元素。通过循环遍历用判断语句来给这其中的元素附加颜色,然后再打印二维数组的元数 ,再通过判断语句的条件,让 | 这个放在其该在的位置(这里的是落子后面的 | )再"\n"换行 。这就第一行完事了接着我们要想第二行,在第一个循环里加上行的判断条件,先给它附上颜色 ,再加嵌套循环,来给--- 加上 ,再加上 | 。再换行。
3.

cpp 复制代码
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
    int x, y;
    printf("玩家下棋(输入坐标,如:1 1)");
    while (1)
    {
        scanf_s("%d %d", &x, &y);
        if (x >= 0 && x < row && y >= 0 && y < col)
        {
            if (board[x][y] == ' ')
            {
                board[x][y] = 'X';
                break;
            }
            else
            {
                printf("该位置已占有,请重新输入");
            }
        }
        else
        {
            printf("坐标无效,请重新输入(l~%d, l~%d):",row,col);
        }
    }

}

玩家下棋,我这里是通过坐标落子,要定义两个变量下x,y。因为我要将每次下完棋更新再同一棋盘,而不是每下完其再在小黑屏里加一个棋盘,所以我加了个while循环。首先我们要判断x,y的定义域。接着还在注意落子的地方是不是空的。还有下棋时,你填的坐标是否正确。不正确如何处理。这些可以用判断语句来实现。
4.

cpp 复制代码
// 策略AI:优先封堵玩家即将获胜的位置,否则随机落子
void ComputerMove(char board[ROW][COL], int row, int col)
{
    printf("电脑思考中...\n");
    // 1. 先检查电脑自己能否获胜,有机会则直接落子
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            if (board[i][j] == ' ') 
            {
                // 尝试在此处落子
                board[i][j] = 'O';
                // 检查是否能赢
                if (CheckWin(board, row, col) == 'O') 
                {
                    return; // 能赢则直接落子
                }
                // 不能赢则撤销尝试
                board[i][j] = ' ';
            }
        }
    }
    // 2. 再检查玩家是否即将获胜,若有则封堵
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            if (board[i][j] == ' ')
            {
                // 模拟玩家在此处落子
                board[i][j] = 'X';
                // 检查玩家是否能赢
                if (CheckWin(board, row, col) == 'X') 
                {
                    // 封堵这个位置
                    board[i][j] = 'O';
                    return;
                }
                // 玩家不能赢则撤销尝试
                board[i][j] = ' ';
            }
        }
    }
    // 3. 若没有需要封堵的位置,随机落子
    while (1) 
    {
        int x = rand() % row;
        int y = rand() % col;
        if (board[x][y] == ' ') {
            board[x][y] = 'O';
            break;
        }
    }
}

为了让游戏不是那么简单。我加了电脑3种下棋方式:1.判断电脑下棋是否能赢,就下。2.判断玩家下棋能赢就阻断。3.最后是没有前几种情况就随机下。这里要用到CheckWin函数。其实这里的逻辑就是:预测。你们可以分析代码就知道了。这样也能找到些许乐趣。
5.

cpp 复制代码
// 判断胜负
// 返回值:
// 'X' -> 玩家胜
// 'O' -> 电脑胜
// 'Q' -> 平局
// 'C' -> 继续
char CheckWin(char board[ROW][COL], int row, int col)
{
    // 1. 检查行
    for (int i = 0; i < row; i++) 
    {
        if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
        {
            return board[i][0];
        }
    }
    // 2. 检查列
    for (int j = 0; j < col; j++)
    {
        if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ') 
        {
            return board[0][j];
        }
    }
    // 3. 检查对角线
    if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ') 
    {
        return board[0][0];
    }
    if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ') 
    {
        return board[0][2];
    }
    // 4. 检查是否平局(无空格)
    int full = 1;
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++) 
        {
            if (board[i][j] == ' ')
            {
                full = 0;
                break;
            }
        }
        if (!full) break;
    }
    if (full) 
    {
        return 'Q';
    }
    // 5. 继续游戏
    return 'C';
}

检查输赢:我们首先要給输赢 ,平or 继续游戏用一些符号表示作为返回值。'X' -> 玩家胜 'O' -> 电脑胜 'Q' -> 平局 'C' -> 继续。X 与 O是玩家和电脑的子的形式,可以通过行,列,对角线的检查 方便返回二维数组里的元素。平局:我们要定义一个变量full来查看棋盘是否满了。最后只剩:C 继续游戏了。
6.

cpp 复制代码
void SetColor(int color) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, color);
}

这是我们要改颜色用的函数。

3.菜单

  1. 代码:

    cpp 复制代码
     char board[ROW][COL];
     char result = 'C';
     int player_wins = 0;
     int computer_wins = 0;
     int ties = 0;
     char play_again = 'Y';
    
     while (play_again == 'Y' || play_again == 'y') 
     {
         // 初始化棋盘
         InitBoard(board, ROW, COL);
         printf("=== 三子棋游戏 ===\n");
         printf("总战绩:玩家 %d 胜 | 电脑 %d 胜 | 平局 %d 次\n",
             player_wins, computer_wins, ties);
         DisplayBoard(board, ROW, COL);
    
         // 游戏循环
         while (1) 
         {
             // 玩家回合
             PlayerMove(board, ROW, COL);
             DisplayBoard(board, ROW, COL);
             // 判断结果
             result = CheckWin(board, ROW, COL);
             if (result == 'X') 
             {
                 printf("=== 玩家获胜! ===\n");
                 player_wins++;
                 break;
             }
             else if (result == 'Q') 
             {
                 printf("=== 平局! ===\n");
                 ties++;
                 break;
             }
    
             // 电脑回合
             ComputerMove(board, ROW, COL);
             DisplayBoard(board, ROW, COL);
             // 判断结果
             result = CheckWin(board, ROW, COL);
             if (result == 'O') {
                 printf("=== 电脑获胜! ===\n");
                 computer_wins++;
                 break;
             }
             else if (result == 'Q') {
                 printf("=== 平局! ===\n");
                 ties++;
                 break;
             }
         }
    
         // 询问是否再来一局
         printf("是否再来一局?(Y/N): ");
         scanf_s(" %c", &play_again);
         while (play_again != 'Y' && play_again != 'y' &&play_again != 'N' && play_again != 'n') 
         {
             printf("输入无效,请输入 Y 或 N: ");
             scanf_s(" %c", &play_again);
         }
     }
    
     // 显示最终战绩
     printf("\n=== 游戏结束 ===\n");
     printf("最终战绩:玩家 %d 胜 | 电脑 %d 胜 | 平局 %d 次\n",player_wins, computer_wins, ties);
    
     if (player_wins > computer_wins) 
     {
         printf("玩家胜率更高!\n");
     }
     else if (player_wins < computer_wins)
     {
         printf("电脑胜率更高!\n");
     }
     else {
         printf("双方势均力敌!\n");
     }
    
     return 0;

    2.分析:这里我们要有 游戏继续还是结束 记录比赛比分 胜率的分析等的功能 。因此我们要定义 6个变量 。第一个while决定着游戏的继续还是结束,如果继续就初始化以及打印棋盘。第二个while来按照先玩家再电脑的顺序落子,里面要有打印棋盘刷新棋盘。还有判断落子后的效果。第三个while则是与第一个while呼应判断玩家是否玩还是不玩,以及玩家输入错误信息的处理方式。最后结束游戏打印结果胜率的分析。

相关推荐
AA陈超9 小时前
虚幻引擎UE5专用服务器游戏开发-20 添加基础能力类与连招能力
c++·游戏·ue5·游戏引擎·虚幻
Liudef061 天前
儿童趣味记忆配对游戏
css·游戏·css3
crazy_yun1 天前
通用游戏前端架构设计思考
游戏
向宇it2 天前
【unity小技巧】在 Unity 中将 2D 精灵添加到 3D 游戏中,并实现阴影投射效果,实现类《八分旅人》《饥荒》等等的2.5D游戏效果
游戏·3d·unity·编辑器·游戏引擎·材质
witton2 天前
C语言使用Protobuf进行网络通信
c语言·开发语言·游戏·c·模块化·protobuf·protobuf-c
alansoulKing2 天前
2025年7月:打cs1.5 600元装机台式电脑方案A
游戏·电脑
HMS Core2 天前
用AI重塑游戏体验:《诛仙2》携手HarmonyOS SDK实现性能与功耗双赢
人工智能·游戏·harmonyos
向宇it2 天前
Unity Universal Render Pipeline/Lit光照材质介绍
游戏·unity·c#·游戏引擎·材质
困惑阿三2 天前
Mac mini 高性价比扩容 + Crossover 游戏实测 全流程手册
游戏·macos