三字棋顾名思义
横向竖向或者斜向完成三连就可以获得胜利
学习三字棋的重点还是要梳理繁琐的代码和编程的逻辑思路
首先我们需要创建两个.c源文件和一个.h头文件方便我们在写的时候思路清晰,井井有条
.h头文件需要用来引头文件和自定义函数,或者是宏定义,.c源文件则需要存放游戏的底层逻辑和思路。这里test.c存放的就是游戏运行的逻辑;game.h是函数的声明;game.c则是h函数的实现。
首先整理一下思路
我们要写的是一个游戏,玩儿完一局不够过瘾还想继续玩儿
我们不妨写一个do while结构,并且写一个简易的菜单;
既然要玩家选择,就需要scanf让玩家从键盘输入选择;
用switch语句根据玩家选择的数字要进入哪一个分支;
(代码后的注释也有助于理解)
void menu()
{
printf("**********************");
printf("****** 1.PLAY ******");
printf("****** 0.EXIT ******");
printf("**********************");
}//一个简易的菜单,不需要返回类型
do
{
menu();//打印菜单
printf("请选择:");//提示玩家选择1和0
scanf("%d", &input);//由玩家键盘输入是否需要玩儿游戏
switch (input)//根据玩家选择的数字来进入哪一个表达式
{
case 1://如果是玩家输入1,则进入游戏
game();//游戏实现的逻辑,下面会讲解
break;
case 0://如果玩家输入0,则退出游戏
printf("退出游戏\n");
break;
default://如果玩家输入非0非1的其他数字,则输入错误
printf("选择错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
这就是测试游戏的逻辑,当菜单打印出来后,需要用scanf输入一个数来选择是否玩游戏,选择1则开始游戏,选择0则退出游戏,输入1和0以外的数则输入错误,需要重新输入,这样就完成了简单的逻辑;
接下来就是game()函数的内容
1.定义棋盘
三字棋棋盘是一个三行三列的棋盘,与我们的二维数组相似,所以我们需要创建一个三行三列的二维数组来记录棋盘的信息;
void game()//自定义game函数实现游戏
{
char board[3][3]={0};//定义一个三行三列的棋盘
}
但是我们在将来不只是要完成三字棋,还要完成五子棋甚至更多的的棋子连在一起,这时我们就需要利用#define来宏定义
使用例:
当然也别忘记要包含自己的头文件
自己引 自己的头文件需要用双引号
利用宏定义来声明一个二维数组时,就可以如上图所示使用,在.h头文件宏定义ROW(行)和COL(列),这样改变游戏规则和棋盘大小只需改变宏定义中行和列的值即可;
2.初始化棋盘
那么初始化棋盘就是要使棋盘上都是空的,以便我们来落子下棋,所以我们需要初始化棋盘,这
里我用自定义函数初始化棋盘,
初始化棋盘时我需要用到这个名为borad的数组,但是这个board数组是几行几列呢?所以还需要将宏定义的行和列传到函数中,所以实参中有数组,行,列,那么形参中也需要有相应的数据,
那么直接上函数体的内容
void InitBoard(char board[ROW][COL], int row, int col)//
初始化棋盘,将棋盘上下棋的地方都放为空格,其中有实参的数组,整形的行和列
{
int i = 0;
/*int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; i < col; j++)
{
board[i][j] = ' ';
}
}*/
memset(&board[0][0], ' ', row * col * sizeof(board[0][0]));
}
这里有两种初始化棋盘的格式,一种是利用for循环(在如上代码屏蔽符号内的代码),另一种是memset函数(使用时要引头文件string.h),
首先说for循环的初始化,简单理解就是遍历这个3*3的数组,并且将其内容都变成”空格“。
memset的用法(初始化元素的第一个地址,想要初始化的内容,想要初始化多少个字节),
那么以上我们不难看出&board[0][0]就是元素的首地址,' '指的是想要把整个数组都初始化成空格,而row*col*sizeof(board[0][0])指的是这个数组的总大小(row行*col列*一个元素的大小),这样就可以做到初始化棋盘;
注意:使用memset别忘记在game.h中包含string.h的头文件
3.打印棋盘
初始化棋盘结束后接下来就看一看自己的棋盘
我们需要打印棋盘,依然需要自定义函数
自定义函数的实参依然需要board数组,还有其行和列
以下为棋盘的代码
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
/*printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);*/
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");/*printf("---|---|---\n");*/
}
}
那么我们最终打印出来的棋盘就是这个样子:
现在从开始游戏到打印棋盘的逻辑我们都已经理清;下一步就是梳理玩儿游戏的逻辑,
那么按照逻辑顺序来就是1.玩家下棋2.打印棋盘落子3.判断输赢4.电脑下棋5.打印棋盘落子6.判断输赢,这么这六个步骤就可以理解为一个循环,直到一方胜利,游戏结束。
既然这六个步骤是循环,我们不妨将它放在while循环中,同样每一步不妨也都自定义函数来解决问题:
1.玩家下棋
我们不妨自定义函数名PlayerMove,同样也要将数组,和其对应的行和列数进行传参,
那么自定义函数就要用相应的内容接受,实参是数组,形参也是数组,实参中行和列都是整形,则形参中行和列也都是整形;函数体如下:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;//定义两个键盘输入的变量
printf("玩家下棋>\n");//提示玩家下棋
while (1)//while循环,非0为真,我们需要进入这个while循环
{
printf("请输入下棋的坐标:\n");//提示玩家下棋;
scanf("%d%d", &x, &y);//玩家键盘输入一个坐标;
if (x >= 1 && x <= row && y >= 1 && y <= col)//当输入的横坐标和纵坐标都是大于等于1且小于棋盘的行数时才能进入里面的条件语句
{
if (board[x - 1][y - 1] = ' ')//处理行数问题;让玩家眼中的第1行变成第0行;第二行变成第1行
{
board[x-1][y-1] = '*';//如果玩家下棋的坐标是空的话,那么这个空的地方就会变成'*'
break;
}
else
printf("坐标被占用,请重新输入\n");//如果玩家输入的坐标不是空的,则要提示玩家坐标被占用
}
else//如果玩家输入的坐标行数出现'0',或者行数列数都大于棋盘的行列数,那么就打印输入非法,需要重新输入
printf("输入非法,请重新输入\n");
}
}
因为棋盘是二维3*3的二维数组的方式,所以我们下棋需要输入一个二维的坐标。
但是需要注意的是,玩家我们不是我们程序员,他不会想到第0行是他们眼中的第1行,所以我们需要确保获取玩家信息都有效性,所以我们要设计代码来解决问题,如以上代码后的注释所示;
2.电脑随机下棋
电脑下棋和人类下棋同样,我们不妨自定义函数名为ComputerMove,当然,同样是在board这个数组中下棋,也要明确下棋的行和列,所以我们传参时和玩家下棋是相似的,只是落子时选的坐标是随机的,随机生成一个坐标,如果这个坐标是空的,电脑就在此落子;要生成随机数则需要用到rand(),既然是电脑生成的坐标我们不妨干脆直接让他生成的坐标在0~2之间,
void ComputerMove(char board[ROW][COL], int row, int col)//电脑下棋
{
int x = 0;
int y = 0;
printf("电脑下棋>\n");
while (1)
{
x = rand() % row;//row是一个整数,返回值为0到row的随机数,包含0,但不包含row,生成列数也是如此。
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';如果玩家下棋的坐标是空的话,那么这个空的地方就会变成'*',很容易理解;
break;//电脑下完棋后跳出循环
}
}
}
值得注意的是当我们在使用rand()函数时,我们需要调用srand来使用随机数的生成器,方法也很简单只需要在整个游戏的运行逻辑中使用一次就可以
可以注意到srand((unsiged int)time(NULL);就是调用srand使用随机数生成器。还要注意其包含的头文件<stdlib.h>和<time.h>;
3.判断输赢
玩家下棋和电脑下棋后,会把棋盘放满,所以我们还需要判断输赢的函数,玩家落子后判断输赢,电脑落子后判断输赢,这样游戏就完整起来;我们不放自定义函数名IsWin,同样需要在数组borad中找出数据判断输赢,所以需要数组和其对应的行和列;
我们还需要考虑的
1.玩家怎么样获胜
2.电脑怎么样获胜
3.棋盘满了就是平局
4.棋盘未满时就需要继续下棋
我们所设计的IsWin函数就需要告诉我们结局;
因为玩家和电脑的子不同,所以我们不妨设置如果玩家赢的话就返回'*',如果电脑赢就返回一个'*';如果平局就返回'Q';如果不平局就返回'C';
判断输赢代码如下
char Iswin(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)//row的值是形参ROW传值为3,我们需要判断三颗棋子是否为横行的连续三颗棋子且不能是空格
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')//i代表行数,无论棋子在棋盘的哪一行,只要横着三个相连就能胜利
{
return board[i][0];//*代表玩家胜利
}
}
for (i = 0; i < col; i++)//col的值是的值是形参ROW传值为3,这里是判断竖向是否为三颗连续棋子
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')//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))
{
return 'Q';
}
return 'C';
}//判断输赢
①赢游戏有多重方法:横着三个相同棋子即为胜利,竖着三个相同棋子即为胜利,斜着三个棋子也为胜利,那么围绕这三中条件,我们来构思函数体,如上代码后的注释;
②还需要判断是否会平局
也就是说当前面的判断是否有连续三颗棋子的条件都未达成
所以我们不妨继续自定义函数Isfull来判断是否平局
其实平局的结果无非就是棋盘被占满,依然无法分出胜负时就为平局所以我们依然自定义函数Isfull:
同样也需要其数组和其对应的行列进行传参;
int Isfull(char board[ROW][COL], int row, int col)//判断棋盘是否满载
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j < 0; j < col; j++)
{
if (board[i][j] = ' ')
return 0;
}
}
return 1;
}
这段函数体应该很好理解,我们只需要判断棋盘内是否还有多余的空间来下棋,如果有空间就说明不是平局,需要返回'0';再搭配上if语句,if()括号中非0是真,0为徦,所以当我们棋盘没有满的时候需要返回0;不让他进入if的图条件,如果棋盘满了,就返回1,1是真,就会进入到if条件中,执行代码,所以这里的代码如下”:
if (Isfull(board, row, col))
{
return 'Q';
}
return 'C';
并且要和之前规定的一样,如果平局就返回'Q';
但是如果前面没人赢,也没有出现平局,那就返回'C'继续游戏;
到这里IsWin函数就完成了
既然'C'是继续游戏的标志,并且Isfull函数返回的是一个整形,那么我们就需要在game()函数中将其返回值利用
如图所示
既然返回值是int类型,我们不妨的定义int类型的ret,如果ret不等于C(意思就是如果下棋结果不是继续游戏,也就是出现了一方胜利或者平局的结果,就跳出循环)。
当然在最后我们也要将比赛的结果打印出来才知道哪一方获得了胜利或者双方平局。
以下是test.c的全部代码,用与整体游戏进行的逻辑
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"game1.h"
void menu()
{
printf("******************************\n");
printf("************1.play************\n");
printf("************0.exit************\n");
printf("******************************\n");
}
void game()
{
//存放数据要3*3的二维数组
char board[ROW][COL] = { 0 };
//初始化棋盘,其中都是空格
InitBoard(board,ROW,COL);
//打印棋盘
DisplayBoard(board,ROW,COL);
char ret = 0;
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
ret = Iswin(board, ROW, COL);
//判断输赢
if (ret != 'C')
{
break;
}
//电脑下棋
ComputerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
ret = Iswin(board, ROW, COL);
//判断输赢
if (ret != 'C')
{
break;
}
}
if ('*'==ret)
{
printf("玩家赢\n");
}
else if ('#' == ret)
{
printf("电脑赢\n");
}
else if ('Q' == ret)
{
printf("平局\n");
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
以下是game.c的全部内容,用于实现自定义函数的内容
#define _CRT_SECURE_NO_WARNINGS 1
#include"game1.h"
void InitBoard(char board[ROW][COL], int row, int col)//初始化棋盘,将棋盘上下棋的地方都放为空格
{
int i = 0;
/*int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; i < col; j++)
{
board[i][j] = ' ';
}
}*/
memset(&board[0][0], ' ', row * col * sizeof(board[0][0]));
}
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
/*printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);*/
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");/*printf("---|---|---\n");*/
}
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋>\n");
while (1)
{
printf("请输入下棋的坐标:\n");
scanf("%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");
}
}
void ComputerMove(char board[ROW][COL], int row, int col)//电脑下棋
{
int x = 0;
int y = 0;
printf("电脑下棋>\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int Isfull(char board[ROW][COL], int row, int col)//判断棋盘是否满载
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j < 0; j < col; j++)
{
if (board[i][j] = ' ')
return 0;
}
}
return 1;
}
char Iswin(char board[ROW][COL], int row, int col)
{
int i = 0;
for (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];//*代表玩家胜利
}
}
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][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))
{
return 'Q';
}
return 'C';
}//判断输赢
以下是game.h的内容,用与宏定义,添加函数声明和头文件的声明
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.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 Iswin(char board[ROW][COL], int row, int col);//判断输赢
希望对大家有所帮助