前些天刚学习了c语言的数组 ,为了能够及时巩固知识 ,决定写个小项目 , 三子棋小游戏正好符合 , 是个很好的练习编程的小游戏 。
一、框架搭建
首先,在写程序之前分文件来写会使代码条理清晰,可读性强, 也是一个很好的编程习惯t首先写一个test.c文件用来写主函数框架,在写一个game.c文件把test.c内所需要的函数全部在此文件内实现,再写一个game.h头文件引用一些c标准库里面头文件以及用对函数的声明,在game.c和test.c内包含game.h即可。
在文件分完之后, 进行主函数框架搭建,首先,如果想要玩游戏不过瘾玩完一次还想再玩一次,那么就需要把内容放在循环内,在开始游戏之前需要一个菜单进行游戏选择 ,单独分一个菜单函数menu()设置1为开始游戏,0为结束游戏。
对菜单的选择进行分类 ,所以需要switch来对选择的值进行细分,设置一个变量input放在循环外 ,再循环内输入input进行选择,如果选择为0时进入switch case 0:则为退出游戏,选择为1进入switch case 1:为开始游戏,将input放在循环体条件上,当输入input为0时同时也终止了循环,这是一种很好的设计思路。
想要当选择为1的时候进入游戏 , 那就需要在case 1:后面封装一个game()函数,来进行游戏的开始,那么接下来进入game()函数,如果想要进行一局游戏需要哪些条件?首先是不是需要定义一个棋盘来输出显示数值,所以定义一个char类型的board[][]的二维数组,来表示棋盘。在有了棋盘之后就需要初始化棋盘,则定义一个InitBoard函数来进行初始化函数 ,为了使游戏界面尽量看起来舒服 , 就定义一个函数ShowBoard函数来对游戏界面进行稍微美化,将棋盘打印到控制台上。当棋盘已经显示出来时,就可以开始游戏了,开始游戏需要玩家操作和电脑操作 ,则封装两个函数PlayerOP,ComputerOP来表示玩家操作和电脑操作,最后需要判断谁赢了游戏,则继续封装函数WhoWin,这里存在一个问题,玩家操作至少需要三次才能赢得比赛,每次落子还需要判断是否胜利,所以将PlayerOP,ComputerOP,WhoWin,放在一个死循环内,当满足结束条件时在跳出循环。
代码演示如下:
#include"game.h" void menu() { printf("***********************\n"); printf("****** 1 . Play *****\n"); printf("****** 0 . Exit *****\n"); printf("***********************\n"); return; } void game() { char ret; char board[ROW][COL] = { 0 }; //初始化棋盘 InitBoard(board, ROW, COL); //显示棋盘 Showboard(board, ROW, COL); while (1) { //玩家操作 PlayerOP(board, ROW, COL); //显示棋盘 Showboard(board, ROW, COL); //判断谁赢了 WhoWin(board, ROW, COL); //电脑操作 ComputerOP(board, ROW, COL); //显示棋盘 Showboard(board, ROW, COL); //判断谁赢游戏 WhoWin(board, ROW, COL); } return; } int main() { srand((unsigned int)time(NULL)); int input = 0; do { menu(); printf("请输入内容:>\n"); scanf_s("%d", &input); switch (input) { case 0: printf("退出游戏!\n"); break; case 1: game(); break; default: printf("选择错误请重新选择!\n"); break; } } while (input); return 0; }
二、函数搭建
根据game内所用函数进行一一实现
首先初始化棋盘。因为想要改变棋盘大小一个一个函数改太麻烦,所以用宏来表示数据,三子棋的棋盘是3*3的棋盘则二维数组应为board[3][3] ,用宏ROW,COL表示棋盘的行数和列数,则在game.h中定义即可。
#pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 3 #define COL 3 //初始化棋盘 void InitBoard(char board[ROW][COL], int row, int col); //显示棋盘 void Showboard(char board[ROW][COL], int row, int col); //玩家操作 void PlayerOP(char board[ROW][COL], int row, int col); //电脑操作 void ComputerOP(char board[ROW][COL], int row, int col); //判断谁赢得游戏 char WhoWin(char board[ROW][COL], int row, int col);
初始化棋盘:
需要传入board二维数组以及行数列数,双层for循环将数组全部置为空格表示没有数据即可。
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] = ' '; return; }
显示棋盘:
需要传入二维数组board,以及行数列数,想要实现如图所示的方格把每个落子位置空出来,则需要对每一行每一列进行操作,观察图形每行每列都有一定的规律,首先来先看行,行中有两种模式分类一种是数据+| 一种是---加| 首先对每一行数据进行打印则先用for循环便利每行内部在嵌套for便利每一列,在第二个for循环内部打印出空格+数据+空格 以及 | 由于在最后一列不需要打印|所以将两种分开打印首先打印数据,在打印|之前限制|只能打印两列,只需在打印|前加上 if(j < col - 1)即可。同理在打印---的时候也是相同,在第一个for循环内另嵌套一个for前应该控制只打印两行所以要加上if(i < row - 1)在嵌套for循环for内部实现与上述原理相同,还有一点,记得每个for循环后面要加上换行即可打印出棋盘。
代码如下(当改变宏时可以实现n*n行列的打印):
//显示棋盘 void Showboard(char board[ROW][COL], int row , int col) { for (int i = 0; i < row; i++) { //将每一行的数据先打印出来 for (int j = 0; j < col; j++) { printf(" %c " , board[i][j]); if(j < col - 1) printf("|"); } printf("\n"); //将每一列的分割线打印 if (i < row - 1) { for (int j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } printf("\n"); } } return; }
玩家操作:
打印完棋盘后就可以开始游戏了,则第一步是玩家落子,采用坐标的形式对每个某个位置打印,定义变量x,y初始化都为0,来表示玩家输入的x,y坐标,当满足x,y在每行每列的范围内,否则就会发生越界,以及满足当想要落子的位置内容为空格时才能落子,用'*'表示玩家落子,不是空格就代表这个位置已经被下过了。当落子错误的时候需要重新落子,于是将所有内容放在while循环内,置为死循环,只有落子成功时才跳出循环。
代码如下:
void PlayerOP(char board[ROW][COL], int row, int col) { int x = 0, y = 0; while (1) { scanf_s("%d %d", &x, &y); if (x >= 1 && x <= ROW && y >= 1 && y <= COL) { if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else { printf("已经被下过辣!\n"); } } else { printf("出界辣!\n"); } } }
电脑操作:
玩家落完子后就该电脑落子了,电脑落子比较简单,定义x,y将x,y置为随机值,需要用到前面学过的srand和time函数,逻辑和玩家落子相同,只要这两个随机值坐标在键盘上为空格时就可以进行落子,用'#'表示电脑落子,同样放在for内。
代码如下:
void ComputerOP(char board[ROW][COL], int row, int col) { int x, y; while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } return; }
判断谁赢了比赛:
比较关键的一步操作,判断输赢,代码逻辑是:首先便利每一行是否有三个值是相等的,如果有就返回这个值 ,所以此函数返回类型为char,而且在test.c文件的game函数内while循环外定义一个局部变量char ret用来接收判断谁赢了的函数的返回值,当满足某一方赢时跳出死循环,在WhoWin中如果游戏还没结束就返回一个值代表游戏继续,当每行判断完继续判断每列是否有三个相同的落子,如果有就返回那个值,还剩下的就是对角线,满足的时候同样返回满足的值。最后有一种情况是平局,即棋盘已经满了,但是没分出胜负,此时就是平局,则设计IsWin函数返回值为int 类型,用来判断棋盘是否已经满了,如果未满返回0否则返回1。设'T'为平局'G'为继续游戏,在test.c中的game函数中循环内用ret接受WhoWin的返回值在玩家操作和电脑操作后面各调用一次判断每次操作后是否赢了游戏,如果赢了游戏跳出循环,循环外加上if语句由返回值确定谁赢了游戏。
test.c中game函数代码(T表示平局):
void game() { char ret; char board[ROW][COL] = { 0 }; //初始化棋盘 InitBoard(board, ROW, COL); //显示棋盘 Showboard(board, ROW, COL); while (1) { //玩家操作 PlayerOP(board, ROW, COL); //显示棋盘 Showboard(board, ROW, COL); //判断谁赢了 ret = WhoWin(board, ROW, COL); if (ret != 'G') break; //电脑操作 ComputerOP(board, ROW, COL); //显示棋盘 Showboard(board, ROW, COL); //判断谁赢游戏 ret = WhoWin(board, ROW, COL); if (ret != 'T' && ret != 'G') { break; } } if (ret == '*') { printf("恭喜你获胜!\n"); } else if (ret == '#') { printf("很遗憾,电脑获胜!\n"); } return; }
WhoWin判断谁赢得函数代码:
char WhoWin(char board[ROW][COL], int row, int col) { //判断行 for (int i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][2] != ' ') { return board[i][0]; } } //判断列 for (int i = 0; i < col; i++) { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ') { return board[0][i]; } } //判断对角线 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ') return board[1][1]; if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ') return board[1][1]; //判断棋盘是否满了 if (IsFull(board, row, col) == 1) { return 'T'; } //G代表继续游戏 return 'G'; }
判断是否平局的代码:
int IsFull(char board[ROW][COL], int row, int col) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; } } return 1; }
这样我们就实现完了所有代码了。
三、总结
通过三子棋小游戏的实现可以很好的锻炼编程能力,在代码过程中熟悉了搭建简单框架,以及了解了分文件编写代码的好处。希望大家能一起学习一起进步,相互交流技术,相互进步呀!