1、实现三子棋
test.c //测试游戏的逻辑
game.c //游戏代码的实现
game.h //游戏代码的声明(函数声明,符号定义)
1、
在主函数里实现,首先有个界面,让玩家选择是否玩游戏。上来就先打印菜单,用do ... while结构。(这里menu(){ ... }menu前面要有void,我忘记写了。)
2、
根据输入的选择做出相应的功能。用switch语句
3、
当选择了0时,break退出这个switch,到了while,如果while这里放input,此时intput=0,刚好退出循环,结束游戏。如果选择了非0,到了default这里break退出switch语句到了while这里刚好为真,接着循环,打印菜单。
4、
先测试一下:
#include<stdio.h>
menu()
{printf("*********************************\n");printf("*********** 1、play ***********\n");printf("*********** 0、exit ***********\n");printf("*********************************\n");}
int main()
{int input = 0;//接收玩家输入的选择do{menu();//菜单printf("请选择:>");scanf("%d", &input);switch (input)//根据选择做出相应的响应{case 1:printf("三子棋\n");break;case 0:printf("退出游戏\n");break;default:printf("选择错误!\n");break;}} while (input);return 0;
}
测试结果:
整个当前的逻辑没有问题。开始写三子棋的逻辑。
5、三子棋逻辑
封装一个函数game();来写游戏逻辑
6、
当玩家下棋的之后,电脑下棋的时候,玩家下的位置得记录起来。他得存储这个数据,否则我只是这次打印了下,下次就没有了。当存起来的时候,有9个坐标,九宫格。3行3列的二维数组。
这个游戏必须有存放数据的数组。
有了这个数组,这个时候就可以开始下棋,
7、
但还应该打印这个棋盘,而且有玩家下棋,电脑下棋。这样的动作,而且这个游戏在玩的过程中应该还有人会赢,有人会输。应该还有判断输赢的功能。
得有这些功能:打印棋盘,玩家下棋,电脑下棋,判断输赢。
8、打印棋盘
打印棋盘,这个打印的时候,玩家下棋的地方打印*,没下的打印空格。
所以这个游戏刚刚开始的时候,这个棋盘其实就应该已经存在,这个棋盘上每个位置应该对应的都是空格。这些位置应该打印的时空格,而这些位置又对应的数组。也就是我刚刚传进去的数组。那数组最开始里面那应该每个元素放一个空格才对。那这样打印这个棋盘的时候,这些位置上才打印空格,这样才让我们这个棋盘看起来我们打印了东西,但我们看起来又好像什么都没有。
那怎么做到这个数组中每个元素都是空格?
那这个地方写个初始化棋盘的功能,初始化棋盘本质上还是初始化这个数组。实际上就是想给我们这个数组board初始化空格进去。那这个必然要把board传进去。
数组传参,传个数组名就可以了,那这个时候数组几行几列最好也能够传过去。
这样就可以了,当我们这样写的时候,我们就要写这个函数InitBoard,而这个函数其实就是棋盘,初始化函数,那跟这个游戏就有关系了,这是初始化游戏棋盘的函数,那我们就把这个InitBoard声明往game.h 这里放。
这个地方声明她的时候,这个返回类型可以不要void,但参数这个不能不要,参数这个地方你既然给我传的是数组,又传了行、列,那我就要接收。
你给我传的是二维数组,那我就拿二维数组来接收,char board[3][3],行就是row,列就是col
这三个参数就给定了。
9、
这是三个形参,那我们就要拿过来实现,geme.c 里面就是实现
这个地方接下来往下写肯定是没有问题的,这里说个东西,
这里有3,那里也有3,到处都有3 ,那我如果有一天这里不是三子棋而是五子棋呢,棋盘要更大,那岂不是所有涉及到这个3、3的都要改,那改起来就不是一个地方了,所以我建议,这里不要固定的写成3、3,要这样写,在game.h中,
ROW是行,COL是列,
注意:这里 int row 和 int col 这里不能改,这两个是形参名。不能用这个ROE和COL。
ROE和COL这是两个值,row和col是形参的变量名。那相同的道理,其他地方也是
但发现这个地方不行啊,因为他俩来自头文件啊,这是在头文件里面定义的啊,你在这里怎么用,这个时候很简单,只要包含一下这个头文件就可以了。
这个时候就可以用了,那相同的道理,这两个地方也可以用了
想象一下,如果我们用行和列的地方,全部用ROW和COL代替,那未来我要改成10行10列,这两个改成10
所有地方是不是都能改,这样改变起来就非常的方便,用起来就非常的方便,而不至于说这个地方我跟着把所有3都改一遍。
10、
注意,这个game.c地方定义还没写呢,那就是把这个数组的九个元素给他初始化全部空格。
这就是初始化代码。
11、
那此时我很想知道我放进去之后到底放的对不对,就把棋盘打印出来看一下,那这个地方一个DisplayBoard()
这个函数是必须要有的,因为一会儿之后,我是不是下完之后就要打印棋盘,下完之后就要打印棋盘,所以这个打印棋盘的功能是必须要有的。
那打印棋盘本质上打印的什么,不还是这个数组吗。那这个地方再来写个打印数据。
这样一给,那我们就是把board里面的行和列进行打印,这就是它的功能。
11、初始化棋盘
那我们这个地方再声明。
再进行实现:
注意:这ROW和COL都来自头文件,来自头文件的时候我们最终再包含以下这个头文件。
包含一下就可以了,然后再来写这个功能:
那怎么打印呢
那怎么实现呢?
注意,此时game.c这个文件要包含头文件 stdio.h ,既然此时game.c 和 test.c 两个文件都要包含头文件 stdio.h,那我直接把这个头文件 stdio.h 放到game.h 文件里,然后只要game.c 和 test.c 这两个文件都包含game.h 这个头文件,那game.c 和 test.c 这两个文件也就都包含了头文件 stdio.h
这样就方便些。
结果:
发现不是我想要的,最后一组多了一个分割行,
这里打印了3行,但实际上第三行不用打印。
第一个版本:
那如果此时没有初始化棋盘会怎么样?
就会这样子,这个地方其实是不对的,初始化后才是我们想要的那样。
但这个时候这个代码还是不好的,如果把这里改成10行和10列。
会发现10行没有问题,但列我只打印了3列。,为什么只打印了3列呢?
因为打印数据这里写死了,所以这个地方其实不合适,不方便,用起来不方便。
第二个版本:
让她的行和列可以动。
那他是打印这样的几组信息啊,有几列,是不是就打印几组这样的信息。
那打印分割信息就是这样 ---| 是一组,最后一组没有 | 。
这个时候目前全代码
game.c 全代码:
#include"game.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; j < col; j++){board[i][j] = ' ';}}
}//第一个版本
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (i = 0; i < row; i++)
// {
// //打印数据
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//
// //打印分割信息
// if (i < row - 1)//-1就少打印了一组
// {
// printf("---|---|---\n");
// }
//
// }
//}void DisplayBoard(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){//打印数据//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);int j = 0;for (j = 0; j < col; j++){printf(" %c ", board[i][j]);if (j < col - 1){printf("|");//但这个 | 不是所以情况下都打印,最后一组打印完之后就不打印了}}printf("\n");//打印分割信息//printf("---|---|---\n"); if (i < row - 1)//-1就少打印了一组{int j = 0;for (j = 0; j < col; j++){printf("---");if (j < col - 1){printf("|");//这个 | 也是最后一组打印完之后就不打印了}}printf("\n");}}
}
game.h 全代码:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>
#define ROW 3
#define COL 3//初始化棋盘
void InitBoard(char board[ROW][COL],int row,int col);//行就是row,列就是col//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
test.c 全代码:
#include"game.h"
menu()
{printf("*********************************\n");printf("*********** 1、play ***********\n");printf("*********** 0、exit ***********\n");printf("*********************************\n");}
void game()
{char board[ROW][COL] = { 0 };//board棋盘,存*和#//初始化棋盘的功能InitBoard(board, ROW, COL);//初始化棋盘DisplayBoard(board, ROW, COL);
}
int main()
{int input = 0;//接收玩家输入的选择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;
}
存放地 图:
此时代码运行结果:
10*10的:
3*3的
12、开始下棋
下棋下着下着必定会有人赢有人输,
玩家下完电脑下,电脑下完玩家下,就是一直循环,用while语句,先定死循环
先写玩家下棋:
当玩家下棋的时候,还是把数据下在棋盘上,对应的就是把数据放到board这个数组里面去了。所以玩家下棋最终还是对数组进行操作,玩家下棋的时候还要知道有几行几列,要不然下到外面去了,就把数组,列,行传进去。
然后在game.h 里把这个函数声明一下,
接着到 game.c 文件里面实现:
x和y放进去了,那么问题来了,这个x和y是不是合理的,如果玩家输入9 9,那么这个明显超出了我的棋盘范围。最后,我们程序员知道这个3*3棋盘第一个元素的位置是 0 0,但是玩家不知道啊,他们要下在这个位置
就会输入1 1 ,不敢要求玩家,这个左上角这个位置是0 0 ,要求玩家输入0 0 ,不现实,
所以,我们要求这个地方,行输入的是1到3,列是1到3,玩家输入的时候行是1到3的值,列也是1到3的值,所以这个时候应该判断一下玩家输入的对不对,有没有超出棋盘范围等问题:
那此时是判合法了,如果不和法还要提示一下,重新输入,那么此时要重新输入,这里就要循环了,用while循环。先写个while(1)死循环。
如果这里输入坐标,行和列是合法的,就要判断坐标范围是否合法,坐标有没有被占用,有没有被下过:
结果:
电脑下棋:
前面这些参数跟玩家下棋一样。
这里电脑下棋的地方写简单的,找没有下棋的地方随机下棋,让电脑随机生成一个坐标。如果这个坐标没下过棋,就可以把他下进去。
用rand函数生成随机值,在之前写猜数字的时候,学过要用rand这个之前用srand函数设计随机数起点,只用设置一次就行,所以把srand函数放到main函数里面,调用time函数设置起点,参数传空指针,这样就返回一个时间戳,把这个时间戳给srand就可以了,这个time函数的返回类型本来应该是time_t 的,所以这里强制类型转换成unsigned int 类型。
这里不管是rand还是srand都得引用头文件stdlib.h,还有time函数引用time.h头文件
然后就可以生成随机数了,随机数生成之后,就看一下这个坐标到底有没有被占用。没有被占,下棋,被占,不能下这。注意
这里x和y已经是0到2了,是合法的,所以这个地方我只要判断这个地方有没有被占用就可以了。
注:这个rand产生的随机数的范围是0到32767,然后这个row和col都是3,0到32767之间的任何一个数模上3,余数都是0到2,所以她的范围都是0到2,所以是合法的。
入如果它没有被占用,就下#,被设置了就要重新下,所以这里需要循环,用while语句。
如果是空格,就下,然后跳出循环,不是空格,就接着循环。
此时全代码:
test.c全代码:
#include"game.h"
menu()
{printf("*********************************\n");printf("*********** 1、play ***********\n");printf("*********** 0、exit ***********\n");printf("*********************************\n");}
void game()
{char board[ROW][COL] = { 0 };//board棋盘,存*和#//初始化棋盘的功能InitBoard(board, ROW, COL);//初始化棋盘DisplayBoard(board, ROW, COL);//下棋while (1){PlayerMove(board, ROW, COL);//玩家下棋DisplayBoard(board, ROW, COL);ComputerMove(board, ROW, COL);//电脑下棋DisplayBoard(board, ROW, COL);}
}
int main()
{srand((unsigned int)time(NULL));//设置随机数的生成起点int input = 0;//接收玩家输入的选择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 全代码:
#include"game.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; j < col; j++){board[i][j] = ' ';}}
}//第一个版本
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (i = 0; i < row; i++)
// {
// //打印数据
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//
// //打印分割信息
// if (i < row - 1)//-1就少打印了一组
// {
// printf("---|---|---\n");
// }
//
// }
//}//第二个版本
void DisplayBoard(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){//打印数据//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);int j = 0;for (j = 0; j < col; j++){printf(" %c ", board[i][j]);if (j < col - 1){printf("|");//但这个 | 不是所以情况下都打印,最后一组打印完之后就不打印了}}printf("\n");//打印分割信息//printf("---|---|---\n"); if (i < row - 1)//-1就少打印了一组{int j = 0;for (j = 0; j < col; j++){printf("---");if (j < col - 1){printf("|");//这个 | 也是最后一组打印完之后就不打印了}}printf("\n");}}
}//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{int x = 0;//横坐标int y = 0;//纵坐标while (1){printf("玩家下棋:>\n");printf("请输入坐标:>");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)
{printf("电脑下棋:>\n");int x = 0;int y = 0;while (1){x = rand() % row;//0~2y = rand() % col;//0~2if (board[x][y] == ' '){board[x][y] = '#';break;}}
}
game.h 全代码:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#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);//行就是row,列就是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);
存放 图:
运行结果:
到这的时候已经下满了,它不管下那都不行,电脑就死循环了,所以这里代码肯定有问题,但先不管它,现在玩家下和电脑下就已经搞起来了。
13、判断输赢
1、玩家下棋之后就应该判断输赢,万一玩家赢了呢。同样的道理,电脑下完之后也得判断一下输赢。如果输入的时候有斜3,横3,竖3,一样,就赢了。
2、这个电脑下棋有点太笨了,把它搞清楚一点。(这个暂时还没实现)
判断输赢,写个IsWin函数。
要告诉我游戏当前是谁赢了,还是平局,如果玩家没有赢,电脑没有赢,也没有平局,这个游戏就还有进行下去。这个时候约定一下,玩家赢的时候返回 * ,电脑赢了返回 # ,平局返回 Q,继续返回 C。这个IsWin函数函数就是判断斜3,横3,竖3三个相等。
所以这个地方判断输赢还是在判断数组,参数也是数组名,行和列。然后返回的值用char类型的ret来接收。
如果玩家下棋这里判断ret不等于C(就是出最终结果了),就跳出循环。电脑下棋这里也一样。
跳出循环来到这,判断ret是等于哪个符号
不管是电脑赢还是玩家赢,还是什么,游戏的结局棋盘打印一下。
IsWin函数声明:
IsWin函数定义:
先判断行,判断行相等且都不为空格,如果这里返回* 或者 # ,那这里还得判断,很麻烦,那我就直接返回行其中一个元素的值。
判断列,一样的道理。改造一下
判断对角线:
只要前面任何一个if语句返回了值,说明一顶有人赢了,而当我走完了所有的if语句到最后的时候,说此时此刻没有人赢,就要平局。
平局就是判断棋盘满了吗,写个函数IsFull来判断棋盘是否满了,而要判断这个棋盘是不是满了就是判断这个数组里面有没有空格,空格是最开始的状态,放一个字符就去掉一个空格,要判断这个数组,就要把行和列一次数组名传过去。
然后就要实现它:
返回类型就写int,然后约定一下,满了就返回1,没满返回0
然后就是遍历一下数组看里面有没有空格。
IsFull返回1,说明满了,平局,返回Q,IsFull返回1,说明没满,继续,最后返回C。
这里判断输赢是写死了。
如果运行发现代码出了问题,但确定前面部分代码是没有问题的,想直接到问题出开始调试,就在想进去的语句处选中再按ctrl+F9,就显示一个红色断点,然后再按ctrl+F10进行调试,这个时候调试的界面就显示断点之前的代码全部运行完成了,从选中的地方就可以开始调试了。
游戏全代码:
game.h 全代码:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#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);//行就是row,列就是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);//判断输赢
//电脑赢 --'#'
//玩家赢 --'*'
//平局 --'Q'
//继续 --'C'
char IsWin(char board[ROW][COL], int row, int col);
game.c 全代码:
#include"game.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; j < col; j++){board[i][j] = ' ';}}
}//第一个版本
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (i = 0; i < row; i++)
// {
// //打印数据
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//
// //打印分割信息
// if (i < row - 1)//-1就少打印了一组
// {
// printf("---|---|---\n");
// }
//
// }
//}//第二个版本
void DisplayBoard(char board[ROW][COL], int row, int col)//打印棋盘
{int i = 0;for (i = 0; i < row; i++){//打印数据//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);int j = 0;for (j = 0; j < col; j++){printf(" %c ", board[i][j]);if (j < col - 1){printf("|");//但这个 | 不是所以情况下都打印,最后一组打印完之后就不打印了}}printf("\n");//打印分割信息//printf("---|---|---\n"); if (i < row - 1)//-1就少打印了一组{int j = 0;for (j = 0; j < col; j++){printf("---");if (j < col - 1){printf("|");//这个 | 也是最后一组打印完之后就不打印了}}printf("\n");}}
}//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{int x = 0;//横坐标int y = 0;//纵坐标while (1){printf("玩家下棋:>\n");printf("请输入坐标:>");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)
{printf("电脑下棋:>\n");int x = 0;int y = 0;while (1){x = rand() % row;//0~2y = rand() % col;//0~2if (board[x][y] == ' '){board[x][y] = '#';break;}}
}
//满了返回1,没满返回0
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;//有元素是空格}}}//到这里都没有返回,说明满了,返回1return 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][1] != ' '){return board[i][1];}}//判断列int j = 0;//列for (j = 0; j < col; j++){if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' '){return board[1][j];}}//判断对角线//一条对角线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';
}
test.c 全代码:
#include"game.h"
void menu()
{printf("*********************************\n");printf("*********** 1、play ***********\n");printf("*********** 0、exit ***********\n");printf("*********************************\n");}
void game()
{char ret = 0;//接收判断输赢的返回符号char board[ROW][COL] = { 0 };//board棋盘,存*和#//初始化棋盘的功能InitBoard(board, ROW, COL);//初始化棋盘DisplayBoard(board, ROW, COL);//打印棋盘//下棋while (1){PlayerMove(board, ROW, COL);//玩家下棋//判断输赢ret=IsWin(board, ROW, COL);if (ret != 'C'){break;}DisplayBoard(board, ROW, COL);ComputerMove(board, ROW, COL);//电脑下棋//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}DisplayBoard(board, ROW, COL);}if (ret == '*'){printf("玩家赢\n");}else if (ret == '#'){printf("电脑赢\n");}else{printf("平局\n");}DisplayBoard(board, ROW, COL);
}
int main()
{srand((unsigned int)time(NULL));//设置随机数的生成起点int input = 0;//接收玩家输入的选择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;
}
结果: