C语言学习笔记20260528—三子棋(井字棋)游戏

C语言学习笔记20260528—三子棋(井字棋)游戏 一. 整体架构规划三文件分工采用模块化拆分将代码分为头文件、功能实现文件、主程序文件三部分低耦合、易阅读、易维护整体遵循「分层设计 功能拆解」思想。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程序启动 → main函数执行设置随机数种子打印菜单等待用户选择├─ 选择 0 → 退出程序└─ 选择 1 → 进入一局游戏1) 初始化空棋盘2) 打印棋盘3) 玩家输入坐标下棋4) 判断胜负/平局有结果则结束本局5) 电脑随机下棋6) 再次判断胜负/平局7) 若无结果回到步骤2继续轮流下棋8) 游戏结束展示最终结果回到菜单可重新开局或退出四.关键设计细节与容错考虑下标兼容区分「用户视角 (1~3)」和「程序数组下标 (0~2)」做数值转换降低使用门槛。输入容错对非法坐标、重复落子做循环拦截程序不会崩溃。代码封装功能独立成函数单一函数只做一件事便于修改、拓展比如后续增加 AI 智能下棋、双人对战。权限控制IsFull 设为静态函数仅内部调用避免外部误调用。解耦思想头文件只做声明实现全部放在 .c 文件工程结构标准。五.代码game.h#define_CRT_SECURE_NO_WARNINGS#ifndef__GAME_H__#define__GAME_H__// 包含标准输入输出库printf、scanf#includestdio.h// 包含stdlib库rand、srand#includestdlib.h// 包含时间库time用于随机数种子#includetime.h// 定义棋盘行数为3#defineROW3// 定义棋盘列数为3#defineCOL3/* * 函数声明 */// 初始化棋盘把所有格子设为空格 voidInitBoard(charboard[ROW][COL],introw,intcol);// 打印棋盘以可视化方式输出当前棋盘状态voidDisplayBoard(charboard[ROW][COL],introw,intcol);// 玩家下棋输入坐标把对应位置设为XvoidPlayerMove(charboard[ROW][COL],introw,intcol);// 电脑下棋随机找空位设为OvoidComputerMove(charboard[ROW][COL],introw,intcol);// 判断胜负返回X/O/D/ // X玩家赢O电脑赢D平局 继续charIsWin(charboard[ROW][COL],introw,intcol);#endifgame.c#includegame.h/* * 函数名InitBoard * 功能初始化棋盘所有位置填充为空格 * 参数board棋盘数组row/col行列数 */voidInitBoard(charboard[ROW][COL],introw,intcol){// 遍历每一行for(inti0;irow;i){// 遍历每一列for(intj0;jcol;j){// 赋值为空格board[i][j] ;}}}/* * 函数名DisplayBoard * 功能打印棋盘做成井字棋样式 */voidDisplayBoard(charboard[ROW][COL],introw,intcol){// 遍历行for(inti0;irow;i){// 打印一行内容格子 竖线for(intj0;jcol;j){// 打印一个格子带空格美观printf( %c ,board[i][j]);// 不是最后一列打印竖线分隔if(jcol-1)printf(|);}// 换行准备下一行printf(\n);// 不是最后一行打印横线分隔if(irow-1){for(intj0;jcol;j){printf(---);if(jcol-1)printf(|);}printf(\n);}}}/* * 函数名PlayerMove * 功能玩家输入坐标下棋棋子为X * 说明玩家习惯输入1~3程序内部用0~2下标 */voidPlayerMove(charboard[ROW][COL],introw,intcol){intx0;// 玩家输入的行inty0;// 玩家输入的列printf(玩家下棋输入坐标如1 1);// 循环直到输入合法且位置为空才跳出循环while(1){// 读取玩家输入的两个整数scanf(%d%d,x,y);// 转成数组下标用户1~3 → 程序0~2x--;y--;// 检查坐标是否在0~2范围内if(x0xrowy0ycol){// 检查该位置是否为空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~2intxrand()%row;// 随机生成列号0~2intyrand()%col;// 判断位置为空if(board[x][y] ){// 电脑落子Oboard[x][y]O;// 成功跳出break;}}}/* * 静态函数IsFull * 功能判断棋盘是否已满无空格 * 返回1满0未满 */staticintIsFull(charboard[ROW][COL],introw,intcol){// 遍历所有格子for(inti0;irow;i){for(intj0;jcol;j){// 发现空格 → 未满if(board[i][j] )return0;}}// 全部填满return1;}/* * 函数名IsWin * 功能判断当前局面是否有人赢或平局 * 返回 * X → 玩家赢 * O → 电脑赢 * D → 平局 * → 继续游戏 */charIsWin(charboard[ROW][COL],introw,intcol){inti0;// 1. 判断所有行是否三个相同且非空for(i0;irow;i){if(board[i][0]board[i][1]board[i][1]board[i][2]board[i][0]! ){// 返回获胜棋子returnboard[i][0];}}// 2. 判断所有列for(i0;icol;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)){returnD;}// 6. 都不满足 → 继续游戏return ;}test.c#includegame.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);// 判断是否结束retIsWin(board,ROW,COL);if(ret! )break;// 电脑下棋ComputerMove(board,ROW,COL);// 判断是否结束retIsWin(board,ROW,COL);if(ret! )break;}// 最后再打印一次最终棋盘DisplayBoard(board,ROW,COL);// 根据返回值判断结果并提示if(retX){printf(恭喜你你赢了\n);}elseif(retO){printf(很遗憾电脑赢了再接再厉\n);}elseif(retD){printf(平局\n);}}/* * 主函数程序入口 */intmain(){intinput0;// 设置随机数种子保证每次电脑下棋不一样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;}六.部分运行结果