前言:为了可以理解代码,我们需要达到的水平为:掌握几种循环、二维数组以及一些基本的c语言知识。
一、三子棋实现的整体逻辑:
1.把棋盘初始化。
在三子棋游戏中,一个有9个格子,所以我们需要创建一个3*3的二维数组,如图所示
将二维数组初始化为‘ ’ 。
二维数组初始化完成之后,要将二维数组以一定的格式打印出来,由于使用的是控制台,没有学控件,所以要用字符设计一个棋盘的样子:
大概这个样子:
2.自己下棋设计。
给自己下的棋规定一个符号为" * ",因为棋盘本质上为二维数组,所以下棋时,只需要更改二维数组即可。
3.计算机下棋设计。
给计算机下的棋规定一个符号为" # "以便区分,在计算机下棋时,让计算机生成随机数,让生成的随机数保证在二维数组内不越界。
4.判断输赢
三子棋判断输赢只有一种方法,即三个符号为一线,利用这个特性,设计出横着三个,竖着三个的判断。同时还要判断棋盘是否还有空位,如果有空位则继续下棋,没有空位,且分不出输赢则退出程序。
用流程图表示:
流程已知,开始敲代码:
二、代码阶段
1.创建二维数组并且将二维数组初始化:
void PrintBoard(char* board) {
for (int i = 0; i <= 2; i++) {
for (int j = 0; j <= 2; j++) {
printf("_%c_", *(board + i * 3 + j));
if (j<=1) {
printf("|");
}
}
printf("\n");
}
}
使用malloc申请地址空间,在访问二维数组时,假象将二维数组一维化
2.打印棋盘:
void PrintBoard(char* board) {
for (int i = 0; i <= 2; i++) {
for (int j = 0; j <= 2; j++) {
printf("_%c_", *(board + i * 3 + j));
if (j<=1) {
printf("|");
}
}
printf("\n");
}
}
难点在于: printf("_%c_", *(board + i * 3 + j));
这行代码,打印出来的字符是:符号"_" +通过下标访问二维数组获取到的值+"_"。2
3.用户下棋
//人下棋为
void personBoard(char *board) {
int i, j;
printf("请输入要下棋的位置:");
scanf_s("%d", &i);
scanf_s("%d", &j);
//这里可以为一个限制i与j大小的语句,以防止i与j在访问数组时发生越界行为。
while ((i < 1 || i >= 4) || (j < 1 || j >= 4)) {
printf("\n输入位置有错,请重新输入:");
scanf_s("%d", &i);
scanf_s("%d", &j);
}
i -= 1; //我们通过scanf_s输入的是非程序员思维,即数组的第一个下标为1,
//所以要访问到数组首地址时,需要i-=1。
j -= 1;
do
{
//首先判断下棋的地方是否已经有棋
if (*(board + 3 * i + j) != ' ') {//有旗了
printf("下的地方已经有棋子了,请重新输入:\n");
scanf_s("%d", &i);
scanf_s("%d", &j);
i--;
j--;
}else {
*(board + 3 * i + j) = '*';
//打印棋盘:
PrintBoard(board);
break;
}
} while (1);
}
用户在进行下棋时,通过输入x与y坐标,对应在x,y坐标处下子,首先程序应该接收我们输入的数据i与j,并判断i与j是否在二维数组中越界;如果发生越界行为,要求用户重新输入。
输入数据没有问题后,判断输入的坐标是否已经有棋子,如果有则要求重新输入,如果没有则将"*"放在该坐标处,打印棋盘。
4.电脑下棋
void copmuterBoard(char *board) {
do
{
srand(time(NULL)); // 设置随机数种子
int i = rand() % 10 / 3; // 生成一个随机数
int j = rand() % 10 / 3;
if (*(board + 3 * i + j) != ' ') {//说明有棋子,需要重新下
continue;
}
else {//为空
*(board + 3 * i + j) = '#';
PrintBoard(board);
break;
}
} while (1);
}
电脑下棋这里只使用最简单的例子,不考虑算法。使用srand生成随机种子,经过rand() % 10 / 3计算后,可以让生成的随机值落在0-3这个区间上。把生成的随机值当作电脑的x与y坐标进行输入,同样经过判断是否有棋子在该坐标处,如果有则重新生成,知道可以落子。
5.判断空位
在进行下棋时,只有当棋盘中有空位时,即当二维数组中仍然有位置时,才能放棋子。
static unsigned int isFull(char *board) {
for (int i = 0; i <= 8; i++) {
if (*(board + i) == ' ') {
//有空位
return 0;
}
}
return 1;
}
判断空位很容易,只要将二维数组遍历一遍,如果里面有字符' ',则代表有空位。有空位返回0,无空位返回1。
6.判断胜负
unsigned int isWin(char *board) {
//判断输赢
for (int i = 0; i <= 2;i++) { //在1-2-3行上
if ((*(board + 3 * i + 0) == '*' && *(board + 3 * i + 1) == '*' && *(board + 3 * i + 2) == '*')
|| (*(board + 3 * i + 0) == '#' && *(board + 3 * i + 1) == '#' && *(board + 3 * i + 2) == '#'))
{
if (*(board + 3 * i + 0) == '*') {//人影了
printf("你赢了");
return 2;
}
else {
printf("你输了");
return 1;
}
}
}
for (int j = 0; j <= 2;j++) {
if ((*(board + 3 * 0 + j) == '*' && *(board + 3 * 1 + j) == '*' && *(board + 3 * 2 + j) == '*')
|| (*(board + 3 * 0 + j) == '#' && *(board + 3 * 1 + j) == '#' && *(board + 3 * 2 + j) == '#'))
{
if (*(board + 3 * 0 + j) == '*') {//人影了
printf("你赢了");
return 2;
}
else {
printf("你输了");
return 1;
}
}
}
if (*(board + 3 * 0 + 0)=='*'&& *(board + 3 * 1 + 1) == '*'&& *(board + 3 * 2 + 2) == '*'||
*(board + 3 * 0 + 0) == '#' && *(board + 3 * 1 + 1) == '#' && *(board + 3 * 2 + 2) == '#'
) {
if (*(board + 3 * 1 + 1) == '*') {//人影了
printf("你赢了");
return 2;
}
else {
printf("你输了");
return 1;
}
}
if (*(board + 3 * 0 + 2) == '*' && *(board + 3 * 1 + 1) == '*' && *(board + 3 * 2 + 0) == '*' ||
*(board + 3 * 0 + 2) == '#' && *(board + 3 * 1 + 1) == '#' && *(board + 3 * 2 + 0) == '#'
) {
if (*(board + 3 * 1 + 1) == '*') {//人影了
printf("你赢了");
return 2;
}
else {
printf("你输了");
return 1;
}
}
//都没有赢?
return 0;
}
别被这么长的代码给吓到了,看第一个for循环:if里面是啥意思呢?
if ((*(board + 3 * i + 0) == '*' && *(board + 3 * i + 1) == '*' && *(board + 3 * i + 2) == '*')
|| (*(board + 3 * i + 0) == '#' && *(board + 3 * i + 1) == '#' && *(board + 3 * i + 2) == '#'))在这里暂且将i换为0,则有
if ((*(board + 3 * 0 + 0) == '*' && *(board + 3 * 0 + 1) == '*' && *(board + 3 * 0 + 2) == '*')
|| (*(board + 3 * 0 + 0) == '#' && *(board + 3 * 0 + 1) == '#' && *(board + 3 * 0 + 2) == '#'))*(board + 3 * 0 + 0) -> *(board) 数组首元素 --第一行第一列的值,对应二维数组为arr[0][1]
*(board + 3 * 0 + 1) ->*(board+1)数组第二个元素--第一行第二列的值,对应二维数组为arr[0][2]
*(board + 3 * 0 + 2) ->*(board+1)数组第三个元素--第一行第三列的值,对应二维数组为arr[0][3]
只有这三个值都为'*'或者都为‘#’才表示用户或者电脑有一方获胜。
这仅仅代表的是第一行,那如果数组的第二行中全为'*'呢?,
是不是我们要接着判断第二行?
第二行咋判断呢,原理和第一行一样,让第二行的每一个值如果都相等且等于'*'或者'#',则有一方获胜
第三行同样如此。
至此第一个for循环结束
第二个for循环,则是判断第一、二、三列中的值是否相同,原理与第一个for循环相似。
第三个与第四个if语句,分辨判断两条对角线上的元素是否相同
7.将代码根据逻辑合并
void game() {
char* board = InitBoard();
//首先打印棋盘:
PrintBoard(board);
//开始下棋-从人开始
while (1) {
if (isFull(board) == 0 && isWin(board) == 0) {//棋盘没有满,且没有人赢
personBoard(board);
}else {//棋盘满了,或者有人赢了
break;//退出
}
if (isFull(board) == 0 && isWin(board) == 0) {//棋盘没有满,且没有人赢
printf("轮到电脑下了\n");
copmuterBoard(board);
}else {//棋盘满了,或者有人赢了
break;//退出
}
if (isFull(board) == 1 && isWin(board) == 0) {//棋盘满了,且没有人赢
printf("平局!\n");
break;
}
}
free(board);//记得释放内存
}
首先将棋盘初始化,并打印出棋盘以供用户下棋。
在while循环中,轮到用户或者电脑下棋时,先判断一下棋盘是否满了或者是否有人赢了,当棋盘没有满且没有人赢时,用户/电脑才可以下棋;当棋盘满了但仍然没有人赢时,宣布平局。
8.主函数的代码:
void gameStart() {
int isPlay = 0;
do
{
printf("菜单\n");
printf("1.play\n");
printf("2.退出\n请选择:");
scanf_s("%d", &isPlay);
switch (isPlay)
{
case 1:
game();
break;
case 2:
printf("退出游戏");
break;
default:
printf("输入有误!请重新输入:\n");
scanf_s("%d", &isPlay);
break;
}
} while (isPlay != 2);
}
void main() {
gameStart();
}
结束语:
三子棋并不算难,可以锻炼自己的代码逻辑能力,可以试着写一写,冲呀!