小小三子棋,细节多又多
game.h(头文件)
#include<stdio.h>
#include<time.h>
#include<stdlib.h> //像这儿可以在头文件中将一些后面game.c和test.c要包涵的头文件写在这,这样在game.c和test.c中只要包涵game.h就行了,因为game.h已将将game.c和test.c要包涵的头文件包涵进去了
#define ROW 3 //define 后面不需要加分号
#define COL 3 //这样做的目的是方便以后改变棋盘的大小
void initborad(char borad[ROW][COL], int row, int col); //因为char borad[][]是数组,所以申明的这个borad的写法不能为char borad [row][col],因为[]里面要是一个常量才行
void Displayborad(char borad[ROW][COL], int row, int col);
void player(char borad[ROW][COL], int row, int col);
void computerplay(char borad[ROW][COL], int row, int col);
char check(char borad[ROW][COL], int row, int col);
char full(char borad[ROW][COL], int row, int col);
一些我在写的过程中犯的错误,或者重要的地方已经在代码中备注了,
1,将一些后面要用得头文件包涵在game.h中这样就免得后面在写了。
2, define定义相当于一个常量,注意前面要加#,后面不要加冒号 =。
3,char borad[ROW][COL] 这里的[ ]内一定要写ROW,COL而不是row,col因为row,col是自己定义出来的变量,不能写在数组的括号内,其次ROW,COL是define定义的常量故可以写在括号内。
4,函数声明时是要加上类型的,与接收函数一样,但传一个函数时里面的括号是不需要添加类型的,还有就是函数申明时最后要加冒号,因为申明相当于一个语句,而接收函数时不需要。
game.c(三子棋各个部分功能的实现)
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void initborad(char borad[ROW][COL],int row,int col) //因为char borad[][]是数组,所以接收的这个borad的写法不能为char borad [row][col],因为[]里面要是一个常量才行
{
int i, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
borad[i][j] = ' ';
}
}
}
//void Displayborad(char borad[ROW][COL], int row, int col) // 不需要这一步,还没有到向棋盘填充符号的那一步,因为这一步,所有打印出来的棋盘前面会多出9个空格
//{
// int i, j = 0;
// for (i = 0; i < row; i++)
// {
// for (j = 0; j < col; j++)
// {
// printf("%c", borad[i][j]);
// }
// }
//}
void Displayborad(char borad[ROW][COL], int row, int col)
{
int i, j = 0;
/*for (i = 0; i < row; i++)
{
if (i == row - 1)
{
printf(" %c | %c | %c ");
}
for (j = 0; j < col; j++)
{
if (j == col - 1)
{
printf(" %c ");
}
printf(" %c |");
}
printf("\n");
}*/
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf(" %c ", borad[i][j]); //这边要注意写的方式,要使其正好满足才可以,不行的话可以多试几种拼法
if (j <col - 1)
{
printf("|");
}
}
printf("\n"); //注意换行号
if (i < row - 1)
{
for (j = 0; j < col; j++)
{
printf("---");
}
printf("\n");
}
}
}
void player(char borad[ROW][COL], int row, int col)
{
int x, y = 0;
/*printf("请输入坐标");
scanf("%d%d", &x, &y);*/
/*if ((x >= 1 && x <= row) && (y >= 1 && y <= col) && borad[x - 1][y - 1] != '#')
{
borad[x - 1][y - 1] = '*';
}
else if (borad[x - 1][y - 1] == '#')
{
printf("该位置已被占用,请重新选择");
}
else
{
printf("不在范围内,请重新输入");
}*/
//很明显这边要写成一个循环,但是用while循环是最好的,do while 也可以
do
{
printf("玩家走,请输入坐标\n"); //玩家走尽量写上去,这样下棋的时候看的更加明白,换行号也尽量不要少
scanf("%d%d", &x, &y);
if (((x >= 1 && x <= row) && (y >= 1 && y <= col) && borad[x - 1][y - 1] != '#'))
{
borad[x - 1][y - 1] = '*';
break;
}
else
{
printf("请重新输入\n");
}
} while (x<1 || x>row || y<1 || y>col || borad[x - 1][y - 1] == '#');
}
void computerplay(char borad[ROW][COL], int row, int col)
{
printf("电脑走\n");
int x, y = 0;
x = rand() % row;
y = rand() % col; //巧妙的利用了取余的性质
while (1)
{
if (/*borad[x][y] != '*'||*/ //(写这一句就有bug了,因为他可以继续下在‘#’的位置)
borad[x][y]==' ')
{
borad[x][y] = '#';
break;
}
else
{
x = rand() % row;
y = rand() % col;//巧妙的利用了取余的性质
}
}
}
char full(char borad[ROW][COL], int row, int col)
{
int i, j = 0;
int flag = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (borad[i][j] == ' ')
{
flag = 1;
}
}
}
if (flag == 1)
{
return 'c';
}
else
{
return 'q';
}
}
char check(char borad[ROW][COL], int row, int col)
{
//行里面连续3个
int i = 0;
for (i = 0; i < row; i++)
{
if (borad[i][0] == borad[i][1] && borad[i][1] == borad[i][2] && borad[i][0] != ' ')
{
return borad[i][0];
}
}
//列里面连续3个
int j = 0;
for (j = 0; j < col; j++)
{
if (borad[0][j] == borad[1][j] && borad[1][j] == borad[2][j] && borad[0][j] != ' ')
{
return borad[0][j];
}
}
//对角线3个相等
//for ((borad[0][0] == borad[1][1] && borad[1][1] == borad[2][2] && borad[1][1] != ' ') || (borad[0][2] == borad[1][1] && borad[1][1] == borad[2][0] && borad[1][1] != ' '))
//{
// return borad[1][1];
//}
if (borad[0][0] == borad[1][1] && borad[1][1] == borad[2][2] && borad[1][1] != ' ')
{
return borad[1][1];
}
if (borad[0][2] == borad[1][1] && borad[1][1] == borad[2][0] && borad[1][1] != ' ')
{
return borad[1][1];
}
if (full(borad, row, col) == 'q')
{
return 'q';
}
return 'c';
/*下面这种方法会出现问题,因为几种判断方法是在for的大循环里面的,所以判断的时候可以认为是一起判读的,而上面那种方法是一个一个判断的,这种方法在极端情况下会出现问题,他对角线和行同时相等或行列
同时相等等情况时就会出现问题*/
//int i = 0;
//for (i = 0; i < row; i++) //这种方式更见解,减少重复性,更重要的是这样可以很方式的表示出‘c’的这种情况
//{
// if (borad[i][0] == borad[i][1] && borad[i][1] == borad[i][2] && borad[i][0] != ' ')
// {
// return borad[i][0];
// }
// else if (borad[0][i] == borad[1][i] && borad[1][i] == borad[2][i] && borad[0][i] != ' ')
// {
// return borad[0][i];
// }
// else if ((borad[0][0] == borad[1][1] && borad[1][1] == borad[2][2] && borad[1][1] != ' ') || (borad[0][2] == borad[1][1] && borad[1][1] == borad[2][0] && borad[1][1] != ' '))
// {
// return borad[1][1];
// }
// else if ('q' == full(borad, row, col))
// {
// return 'q';
// }
// else
// {
// return 'c';
// }
//}
}
这里面一些注释掉的代码都是一开始写错得代码,至于一些原因都注释在旁边了,还要一些注意点也标注在相应代码旁边了,
下面说几个我感觉比较坑的点吧
1,`
for (j = 0; j < col; j++)
{
printf(" %c ", borad[i][j]); //这边要注意写的方式,要使其正好满足才可以,不行的话可以多试几种拼法
if (j <col - 1)
{
printf("|");
}
这边需要你多试几次,使其要正好满足棋盘的画法
2,一些地方要增加换行符,这样打印棋盘时看的更舒服,像“玩家下,电脑下”其实也是为了更加清晰可视的棋盘。
3,一些地方要写成循环的形式,因为有些地方输入的坐标要满足条件才行,同时满足后别忘了跳出来,如果不满足的话,就一直循环,也要注意,这儿用while循环最好。
4,电脑走时生成随机一个电脑坐标时,用到了时间戳,别忘了引头文件,同时这里也巧妙的利用了取余的性质。
5,最后同时也是上面这段代码最坑我的地方(在那弄了一下午才发现了这个错误)
首先请读者们看看下面这段代码,是否有什么问题?
int i = 0;
for (i = 0; i < row; i++) //这种方式更见解,减少重复性,更重要的是这样可以很方式的表示出‘c’的这种情况
{
if (borad[i][0] == borad[i][1] && borad[i][1] == borad[i][2] && borad[i][0] != ' ')
{
return borad[i][0];
}
else if (borad[0][i] == borad[1][i] && borad[1][i] == borad[2][i] && borad[0][i] != ' ')
{
return borad[0][i];
}
else if ((borad[0][0] == borad[1][1] && borad[1][1] == borad[2][2] && borad[1][1] != ' ') || (borad[0][2] == borad[1][1] && borad[1][1] == borad[2][0] && borad[1][1] != ' '))
{
return borad[1][1];
}
else if ('q' == full(borad, row, col))
{
return 'q';
}
else
{
return 'c';
}
}
这段代码表面上似乎没有什么错误,其实犯了一个逻辑上重大的错误,如果读者仔细的看过上面的代码,就会发现正面的代码是每一种情况单个单个判断的,而我这段代码是将其全部放在for( )里面来判断的,这样就会出现错误,不过这个错误要考虑极端情况,就是当棋盘的对角线和行同时相等或行列同时相等或对角线和列同时相等时就会出现问题,这里结合check和后面test.c中game函数里的判断仔细分析,就会发现其中的问题。
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("******************************\n");
printf("****0 exit 1 paly ***\n");
printf("******************************\n");
}
void game() //game 这个函数并不是三子棋这个游戏各个功能的实现,所以没必要写在game.c(用来写三子棋各个功能实现的函数)里面
{
char ret = 0;//用来检查结果谁赢得
char borad[ROW][COL] = { 0 };//这个数组必须要在外面创建,如果在各个函数里面创建的话,那他出函数就会销毁了,无用
initborad(borad,ROW,COL); //初始化棋盘
Displayborad(borad, ROW, COL);//打印棋盘,方便看到下棋的情况
while (1)
{
player(borad, ROW, COL);
Displayborad(borad, ROW, COL);
ret = check(borad, ROW, COL);
if (ret == '*')
{
printf("player win\n");
break;
}
if (ret == 'q') //玩家走完之后也同样要判断一下,因为玩家也有可能是平局
{
printf("平局\n");
break;
}
computerplay(borad, ROW, COL);
Displayborad(borad, ROW, COL);
ret = check(borad, ROW, COL);
if (ret == '#')
{
printf("computer win\n");
break;
}
if (ret == 'q')
{
printf("平局\n");
break;
}
/*ret=check(borad, ROW, COL);*/
}
}
int main()
{
srand((unsigned int)time(NULL));//时间戳的写法
menu();
int input = 0;
do
{
printf("请选择->");
scanf("%d", &input); //忘了switch case用得时的标点和注意项,因此此处选择用if语句了
if (1 == input)
{
game();
}
else if (0 == input)
{
break;
}
else
{
printf("选择错误,请重新选择");
}
} while (input);
}
test.c中要注意的地方
1,这个borad数组不能再game.c中的各个函数中创建,因为出了这些函数,borad这个数组就会自动销毁了,故要在game()函数中创建。
2,玩家下完和电脑下完后都要判断一下是否出现了平局的情况,我一开始就犯了这个错误,当时只想着最后判断一下就行了,当时想着要不是’*‘要不就是’#‘,这两种情况判断后只剩’q‘了,故就要最后写了一句判断平局的代码,其实大错特错, 因为玩家和电脑下完后都有可能出现平局,都每个后面都要写。
写后思考总结
总体来说,三子棋中的知识点并不难,都是一些很基础的知识点,但其实难得是它的函数的封装,和如果巧妙的构思这个游戏代码,以及亿点点的细节。这次写下来并不是很顺利,主要是卡在游戏的代码构思方面了。这可能与我们学校大一下学期没学专业课有关,但我想,更关键的时,大一下学期的前一大部分时间被我荒废掉了,当时沉迷于LOL的冲分,最后止步于峡谷的钻石,玩久了游戏,再加上自己平时看到的,思考的一些东西,终于从中逐渐走了出来,正在慢慢寻找学习和生活之间的平衡点,如果正确的度过每一天的一些方式。