C语言学习笔记20260528—三子棋(井字棋)游戏
2026/5/28 19:12:54 网站建设 项目流程

一. 整体架构规划(三文件分工)

采用模块化拆分,将代码分为头文件、功能实现文件、主程序文件三部分,低耦合、易阅读、易维护,整体遵循「分层设计 + 功能拆解」思想。

1. 文件划分目的

把声明、实现、入口逻辑分离,符合 C 语言工程化写法:
game.h 头文件:统一存放宏定义、头文件引用、函数声明,对外暴露接口。
game.c 源文件:实现所有游戏功能函数,封装核心逻辑。
test.c 主程序文件:程序入口、菜单、游戏流程调度,只负责调用接口。

2. 核心数据结构

使用二维字符数组 char board[3][3] 作为棋盘:
空位:’ ’
玩家棋子:‘X’
电脑棋子:‘O’
数组下标 0~2 对应棋盘行列,用户输入 1~3 做数值转换。

二. 功能模块拆解(按执行顺序)

整个游戏分为 7 大功能模块,逐个实现、依次调用:

模块 1:棋盘初始化 InitBoard

设计思路
游戏开局前,把 3×3 棋盘所有格子全部置为空格 ’ ',清空上一局数据。
双层循环遍历二维数组,完成批量赋值。

模块 2:棋盘打印 DisplayBoard

设计思路
按照井字棋样式排版:字符格 + 竖线 | 做列分隔,横线 — 做行分隔。
规则:最后一列不打印竖线、最后一行不打印横线,保证界面整洁。
纯界面展示函数,不修改棋盘数据。

模块 3:玩家落子 PlayerMove

设计思路
接收用户输入的行列坐标(用户习惯从 1 开始计数)。
坐标转换:输入值 - 1,转为数组下标(0 开始)。
合法性校验两层判断:
坐标是否超出 1~3 范围;
目标位置是否已被占用。
校验通过则写入棋子 ‘X’,否则循环重新输入。

模块 4:电脑落子 ComputerMove

设计思路
借助 rand() 生成随机行列下标,实现随机落子。
提前用 srand(time(NULL)) 设置随机数种子,保证每次运行落点不同。
循环判断随机位置是否为空,找到空位后写入棋子 ‘O’。

模块 5:棋盘判满 IsFull(内部辅助函数)

设计思路
静态函数 static,仅在当前 .c 文件内部使用,对外隐藏。
遍历全部格子,存在空格说明棋盘未满;无空格则判定为已满。
作用:用于判断平局。

模块 6:胜负判断 IsWin(核心逻辑)

设计思路
分 4 种场景依次判断,只要满足其一就判定游戏结束:
判断所有行:同一行三个字符相同且不为空 → 该行持有者获胜;
判断所有列:同一列三个字符相同且不为空 → 该列持有者获胜;
判断两条对角线:对角线三字符相同且不为空 → 对应方获胜;
判断平局:行、列、对角线都不满足,且棋盘已满 → 平局;
以上都不满足 → 游戏继续。
返回值约定
‘X’:玩家胜利
‘O’:电脑胜利
‘D’:平局
’ ':游戏继续

模块 7:主流程调度(菜单 + 游戏循环)

设计思路
菜单功能 menu:提供选项(1 开始游戏 / 0 退出),引导用户操作。
一局游戏 game:固定执行流程:
初始化棋盘 → 循环打印棋盘 → 玩家落子 → 判断结果 → 电脑落子 → 判断结果
一旦分出胜负 / 平局,跳出循环,展示最终棋盘并提示结果。
主函数 main:
初始化随机数种子;
使用 do-while 循环实现重复游玩,输入 0 才彻底退出程序;
通过 switch 分支,响应用户菜单选择。

三.整体执行流程(完整运行顺序)

plaintext

  1. 程序启动 → main函数执行
  2. 设置随机数种子
  3. 打印菜单,等待用户选择
    ├─ 选择 0 → 退出程序
    └─ 选择 1 → 进入一局游戏
    1) 初始化空棋盘
    2) 打印棋盘
    3) 玩家输入坐标下棋
    4) 判断胜负/平局,有结果则结束本局
    5) 电脑随机下棋
    6) 再次判断胜负/平局
    7) 若无结果,回到步骤2继续轮流下棋
    8) 游戏结束,展示最终结果
  4. 回到菜单,可重新开局或退出

四.关键设计细节与容错考虑

下标兼容:区分「用户视角 (1~3)」和「程序数组下标 (0~2)」,做数值转换,降低使用门槛。
输入容错:对非法坐标、重复落子做循环拦截,程序不会崩溃。
代码封装:功能独立成函数,单一函数只做一件事,便于修改、拓展(比如后续增加 AI 智能下棋、双人对战)。
权限控制:IsFull 设为静态函数,仅内部调用,避免外部误调用。
解耦思想:头文件只做声明,实现全部放在 .c 文件,工程结构标准。

五.代码

game.h

#define_CRT_SECURE_NO_WARNINGS#ifndef__GAME_H__#define__GAME_H__// 包含标准输入输出库(printf、scanf)#include<stdio.h>// 包含stdlib库(rand、srand)#include<stdlib.h>// 包含时间库(time),用于随机数种子#include<time.h>// 定义棋盘行数为3#defineROW3// 定义棋盘列数为3#defineCOL3/* * 函数声明 */// 初始化棋盘:把所有格子设为空格' 'voidInitBoard(charboard[ROW][COL],introw,intcol);// 打印棋盘:以可视化方式输出当前棋盘状态voidDisplayBoard(charboard[ROW][COL],introw,intcol);// 玩家下棋:输入坐标,把对应位置设为'X'voidPlayerMove(charboard[ROW][COL],introw,intcol);// 电脑下棋:随机找空位,设为'O'voidComputerMove(charboard[ROW][COL],introw,intcol);// 判断胜负:返回'X'/'O'/'D'/' '// 'X'=玩家赢,'O'=电脑赢,'D'=平局,' '=继续charIsWin(charboard[ROW][COL],introw,intcol);#endif

game.c

#include"game.h"/* * 函数名:InitBoard * 功能:初始化棋盘,所有位置填充为空格' ' * 参数:board:棋盘数组;row/col:行列数 */voidInitBoard(charboard[ROW][COL],introw,intcol){// 遍历每一行for(inti=0;i<row;i++){// 遍历每一列for(intj=0;j<col;j++){// 赋值为空格board[i][j]=' ';}}}/* * 函数名:DisplayBoard * 功能:打印棋盘,做成井字棋样式 */voidDisplayBoard(charboard[ROW][COL],introw,intcol){// 遍历行for(inti=0;i<row;i++){// 打印一行内容:格子 + 竖线for(intj=0;j<col;j++){// 打印一个格子(带空格美观)printf(" %c ",board[i][j]);// 不是最后一列,打印竖线分隔if(j<col-1)printf("|");}// 换行,准备下一行printf("\n");// 不是最后一行,打印横线分隔if(i<row-1){for(intj=0;j<col;j++){printf("---");if(j<col-1)printf("|");}printf("\n");}}}/* * 函数名:PlayerMove * 功能:玩家输入坐标下棋,棋子为'X' * 说明:玩家习惯输入1~3,程序内部用0~2下标 */voidPlayerMove(charboard[ROW][COL],introw,intcol){intx=0;// 玩家输入的行inty=0;// 玩家输入的列printf("玩家下棋(输入坐标,如:1 1):");// 循环:直到输入合法且位置为空才跳出循环while(1){// 读取玩家输入的两个整数scanf("%d%d",&x,&y);// 转成数组下标:用户1~3 → 程序0~2x--;y--;// 检查坐标是否在0~2范围内if(x>=0&&x<row&&y>=0&&y<col){// 检查该位置是否为空if(board[x][y]==' '){// 玩家落子:Xboard[x][y]='X';// 成功,跳出循环break;}else{printf("该位置已被占用,请重新输入:");}}else{printf("坐标非法,请重新输入:");}}}/* * 函数名:ComputerMove * 功能:电脑随机下棋,棋子为'O' */voidComputerMove(charboard[ROW][COL],introw,intcol){printf("电脑下棋:\n");// 循环:直到找到一个空位while(1){// 随机生成行号:0~2intx=rand()%row;// 随机生成列号:0~2inty=rand()%col;// 判断位置为空if(board[x][y]==' '){// 电脑落子:Oboard[x][y]='O';// 成功,跳出break;}}}/* * 静态函数:IsFull * 功能:判断棋盘是否已满(无空格) * 返回:1=满,0=未满 */staticintIsFull(charboard[ROW][COL],introw,intcol){// 遍历所有格子for(inti=0;i<row;i++){for(intj=0;j<col;j++){// 发现空格 → 未满if(board[i][j]==' ')return0;}}// 全部填满return1;}/* * 函数名:IsWin * 功能:判断当前局面是否有人赢或平局 * 返回: * 'X' → 玩家赢 * 'O' → 电脑赢 * 'D' → 平局 * ' ' → 继续游戏 */charIsWin(charboard[ROW][COL],introw,intcol){inti=0;// 1. 判断所有行:是否三个相同且非空for(i=0;i<row;i++){if(board[i][0]==board[i][1]&&board[i][1]==board[i][2]&&board[i][0]!=' '){// 返回获胜棋子returnboard[i][0];}}// 2. 判断所有列for(i=0;i<col;i++){if(board[0][i]==board[1][i]&&board[1][i]==board[2][i]&&board[0][i]!=' '){returnboard[0][i];}}// 3. 判断主对角线(左上→右下)if(board[0][0]==board[1][1]&&board[1][1]==board[2][2]&&board[0][0]!=' '){returnboard[0][0];}// 4. 判断副对角线(右上→左下)if(board[0][2]==board[1][1]&&board[1][1]==board[2][0]&&board[0][2]!=' '){returnboard[0][2];}// 5. 判断是否平局(棋盘满)if(IsFull(board,row,col)){return'D';}// 6. 都不满足 → 继续游戏return' ';}

test.c

#include"game.h"/* * 函数名:menu * 功能:打印游戏菜单 */voidmenu(){printf("====================\n");printf(" 1. 开始三子棋 \n");printf(" 0. 退出游戏 \n");printf("====================\n");}/* * 函数名:game * 功能:一局完整的游戏逻辑 */voidgame(){// 定义3x3棋盘数组charboard[ROW][COL];// 初始化棋盘InitBoard(board,ROW,COL);// 存储本局结果:X/O/D/空格charret=' ';// 循环:轮流下棋,直到分出胜负或平局while(1){// 打印当前棋盘DisplayBoard(board,ROW,COL);// 玩家下棋PlayerMove(board,ROW,COL);// 判断是否结束ret=IsWin(board,ROW,COL);if(ret!=' ')break;// 电脑下棋ComputerMove(board,ROW,COL);// 判断是否结束ret=IsWin(board,ROW,COL);if(ret!=' ')break;}// 最后再打印一次最终棋盘DisplayBoard(board,ROW,COL);// 根据返回值判断结果并提示if(ret=='X'){printf("恭喜你,你赢了!\n");}elseif(ret=='O'){printf("很遗憾,电脑赢了,再接再厉!\n");}elseif(ret=='D'){printf("平局!\n");}}/* * 主函数:程序入口 */intmain(){intinput=0;// 设置随机数种子,保证每次电脑下棋不一样srand((unsignedint)time(NULL));// 菜单循环:选择开始或退出do{// 打印菜单menu();printf("请选择:");// 读取用户选择scanf("%d",&input);// 根据选择分支switch(input){case1:// 开始一局游戏game();break;case0:printf("退出游戏\n");break;default:printf("输入错误,请重新输入\n");break;}}while(input!=0);// 输入0才退出循环return0;}

六.部分运行结果

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询