1 什么是三子棋
率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了
2 明白棋盘中的元素构成
2.1 实现:棋盘中的线路
对此我们使用的是键盘中的竖线:’|‘,以及键盘中的下划线:’_‘
最终我们想要实现的效果是怎样的呢?(如下图所示)
那么我们应该如何使用代码来实现呢?
首先我们要棋盘背后的真实情况:(如下图所示)
看似3*3的棋盘,实际上我们所需要的是6*11的棋盘,其中☆所占用的位置用来存放棋子
而存放棋子,我们使用二维数组arr[3][3]
如何将我们的想法呈现在我们的眼前呢?(不考虑棋子arr[3][3]中存放的是什么)
首先需要明白每一行所包含的内容:(除开最后一行)
同样每一行的内容中(除开最后一列)的均为:
分为两行:
一行为:空格+棋子+空格+竖线
一行为:下划线+下划线+下划线+竖线
最后一列:同前面的所有列,不同之处在于最后一列少一条竖线(仅此而已)
代码实现如下:(除开最后一行)
//代表第一行的内容
for (int j = 0; j < col; j++) {
//先打印空格+棋子+空格
printf(" %c ", arr[i][j]);
//用以区分最后一列:最后一列没有竖线
if (j < col - 1) {
//打印最后的竖线
printf("|");
}
}
printf("\n");//一定要记得换行
//代表第二行的内容
for (int j = 0; j < col; j++) {
//打印三条下划线
printf("___");
//用以区分最后一列:最后一列没有竖线
if (j < col - 1) {
//打印最后的竖线
printf("|");
}
}
而最后一行不同于其他行的地方为:第二行处(没有三条下划线)
//判断是否为最后一行
if (i < row - 1) {
for (int j = 0; j < col; j++) {
printf("___");
if (j < col - 1) {
printf("|");
}
}
}
//最后一行为以下代码:无三条下划线
else {
for (int j = 0; j < col; j++) {
printf(" ");
if (j < col - 1) {
printf("|");
}
}
}
所以对于棋盘来说:最终的代码实现为(将其封装到名为:Displ_arr函数中)
void Display_arr(char arr[ROW][COL], int row, int col) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
printf(" %c ", arr[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("|");
}
}
}
else {
for (int j = 0; j < col; j++) {
printf(" ");
if (j < col - 1) {
printf("|");
}
}
}
printf("\n");
}
}
2.2 实现:棋盘中的棋子
正式棋盘中使用‘×’, ‘O’两种符号来表示,而我们为了简化一点,使用‘*’, ‘#’,分别代表两种棋子
用来存放棋子的位置如上图所示:所以需要一个二维数组来存放棋子
但在最起初时,每个位置都必须是空白的,所以需要初始化
void Init_arr(char arr[ROW][COL], int row, int col) {
//初始化,将每个位置都初始化为空格
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
//单引号才是字符,双引号时字符串
arr[i][j] = ' ';
}
}
}
那么有了初始的数组以及棋盘我们之后应该考虑什么呢?
没错就是开始下棋!
3 如何实现下棋的操作
3.1 玩家下棋
玩家下棋时需要解决两个问题,一是,如何告知电脑想要下的位置,二是,下棋时如何判断是否该位置可以下子(是否已有棋子)
问题一:如何告知电脑想要下的位置
如上面的2.2 已拥有了存放棋子的二维数组,arr[ROW][COL],此时我们只需要告知电脑我们的横纵坐标是什么即可
(但需要确定横纵坐标都符合[0,ROW)或者[0,COL))
void Player_move(char arr[ROW][COL], int row, int col) {
//告知电脑横纵坐标
int x = 0;
int y = 0;
printf("请输入下棋的坐标:\n");
scanf("%d %d", &x, &y);
//简单判断下,是否在可输入的坐标之内
if (x >= 0 && x < ROW && y >= 0 && y < COL) {
//在则将该位置的符号改为‘*’
arr[x - 1][y - 1] = '*';
}
//不满足坐标范围的话,告知使用者,重新输入坐标
else
printf("请重新输入有效坐标:");
}
此时我们输入的是从下表为0的位置处开始的,可能程序员们觉得很正常,但是常理来说应该从1开始,所以我们此时做出一个小小的改变,让其从1开始(两处改变)
void Player_move(char arr[ROW][COL], int row, int col) {
int x = 0;
int y = 0;
printf("请输入下棋的坐标:\n");
scanf("%d %d", &x, &y);
//此时判断条件也相应的进行了修正
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
//将x,y两者都进行-1的操作即可
arr[x - 1][y - 1] = '*';
}
//不满足坐标范围的话,告知使用者,重新输入坐标
else
printf("请重新输入有效坐标:");
}
问题一已经解决,此时我们需要解决问题二
问题二:下棋时如何判断是否该位置可以下子(是否已有棋子)
解决思路:在下棋时判断是否该位置为空或者判断是否已有棋子
(两种均可,我们采用前者,因为前者只需判断是否为‘ ’,后者需要判断是否为‘*’‘#’。麻烦一点)
void Player_move(char arr[ROW][COL], int row, int col) {
int x = 0;
int y = 0;
printf("请输入下棋的坐标:\n");
//为了保证一定可以下到棋子,我们采用while(1){},仅当成功下了棋子的时候跳出
while (1) {
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
//判断该位置是否为‘ ’
if (arr[x - 1][y - 1] == ' ') {
arr[x - 1][y - 1] = '*';
//成功下棋后,直接跳出
break;
}
//不为空的情况,告知使用者,重新输入坐标
else {
printf("该坐标已被占用,请重新输入:");
}
}
//不满足坐标范围的话,告知使用者,重新输入坐标
else
printf("请重新输入有效坐标:");
}
}
至此两种问题均已解决,玩家下棋已被成功实现!
并将其封装至名为:Player_move的函数中
3.2 电脑下棋
玩家下棋可以由我们来控制下棋的位置输入,那么电脑下棋由谁来进行输入呢?
没错,由电脑自主进行输入,我们采用随机值的办法来让电脑进行输入
同理,也需要解决位置是否可用的问题
void Computer_move(char arr[ROW][COL], int row, int col) {
int x = 0;
int y = 0;
//同样的使用while(1)的方式,来保证电脑至少可以输入一颗棋子
while (1) {
//采用%的办法,将x,y的值控制在可输入的坐标范围之内
x = rand() % row;
y = rand() % col;
//同样需要判断该位置是否可以下棋子
if (arr[x][y] == ' ') {
arr[x][y] = '#';
//成功下棋子后,退出
break;
}
}
}
至此电脑下棋已被成功实现!
并将其封装至名为:Computer_move的函数中
但下棋也应该有一个终点,及判断谁输谁赢,或者平局的情况,所以我们还应该解决判断输赢的问题
4 如何判断输赢
4.1 输赢的标志是什么
三子棋,有任何一方连成一条直线时,获得胜利,棋子填满棋盘,但并未有任意一方连成直线时,为平局
连成直线有几种方式呢?
有着横,竖,主对角线,副对角线四种情况,所以我们首先要进行四种情况的判断
但不要忘记了平局的情况,所以有五种情况的判断
char Is_win(char arr[ROW][COL], int row, int col) {
//判断行
for (int i = 0; i < row; i++) {
int count = 0;
for (int j = 0; j < col-1; j++) {
if (arr[i][j] == arr[i][j + 1] && arr[i][j] != ' ') {
count++;
}
}
if (count == col - 1) {
return arr[i][0];
}
}
//判断列
for (int i = 0; i < col; i++) {
int count = 0;
for (int j = 0; j < row - 1; j++) {
if (arr[j][i] == arr[j + 1][i] && arr[j][i] != ' ') {
count++;
}
}
if (count == row - 1) {
return arr[0][i];
}
}
//判断主对角线
int count1 = 0;
for (int i = 0; i < row-1; i++) {
if (arr[i][i] == arr[i + 1][i + 1] && arr[i][i] != ' ') {
count1++;
}
if (count1 == row - 1) {
return arr[i][i];
}
}
//判断副对角线
int count2 = 0;
for (int i = 0; i < row - 1; i++) {
if (arr[i][row - i] == arr[i + 1][row - 1 - i] && arr[i][row - i] != ' ') {
count2++;
}
if (count2 == row - 1) {
return arr[i][row - i];
}
}
//判断平局
if (Is_full(arr,ROW,COL) == 1) {
return 'Q';
}
//继续
return 'C';
}
以上函数我们使用char类型的返回值,平局返回‘Q’,未分胜负返回‘C’,已分胜负返回各自对应的字符‘*’或者‘#’
并将其封装至名为:Is_win的函数中
至此所有可能被应用的一系列函数已经封装结束
5 如何应用以上函数
//游戏
void game() {
//棋盘
char arr[ROW][COL] = { 0 };
//初始化棋盘
Init_arr(arr,ROW,COL);
//打印棋盘
Display_arr(arr, ROW, COL);
//判断输赢
char ret = 0;
while (1) {
//玩家下棋
Player_move(arr, ROW, COL);
Display_arr(arr, ROW, COL);
//判断输赢
ret = Is_win(arr, ROW, COL);
if (ret != 'C') {
break;
}
//电脑下棋
Computer_move(arr, ROW, COL);
Display_arr(arr, ROW, COL);
//判断输赢
ret = Is_win(arr, ROW, COL);
if (ret != 'C') {
break;
}
}
if (ret == '*') {
printf("恭喜,你赢了!\n");
}
else if (ret == '#') {
printf("抱歉,你输了!\n");
}
else {
printf("游戏结束,平局!\n");
}
}
游戏的总体我们将之封装为game函数中
首先我们创建好存放棋子的二维数组,并将其初始化
初始化完成后打印棋盘,展示出来,告诉使用者棋盘的样子
然后开始游戏,进入循环,首先玩家下棋,然后电脑下棋,直至游戏结束
因为下棋子后,我们需要了解当前棋子的情况,所以我们需要每次下棋之后都要打印一下棋盘
因为游戏的结束只有在下棋子之后才能判断,所以我们可以在每次下棋之后进行判断
最后Is_win函数的返回值来判断游戏的最终结果
6 完善游戏
int main() {
//选项
int input = 0;
//为了随机值处所写
srand((unsigned int)time(NULL));
//开始进入游戏中
do {
//目录
catalog();
//选择
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("游戏结束!");
break;
default:
printf("您的输入有误,请重新输入");
break;
}
} while (input);
return 0;
}
上文中提到的目录如下文所示:
//目录
void catalog() {
printf("#####################\n");
printf("##### 1. Play #######\n");
printf("##### 0. End #######\n");
printf("#####################\n");
}
至此游戏开发完成,可供正常使用
7 全部代码展示
game.h
game.c
test.c