三子棋游戏
- 1.前言
- 2.功能分析
- 2.1主函数设计及菜单设计
- 2.2打印棋盘与棋盘初始化
- 2.3玩家下棋
- 2.4电脑下棋
- 2.5判断输赢
- 2.5.1代码优化
- 3.game.h头文件展示
- 4.text.c源文件文件展示
- 5.game.c源文件文件展示
所属专栏:C语言
博主首页:初阳785
代码托管:chuyang785
感谢大家的支持,您的点赞和关注是对我最大的支持!!!
博主也会更加的努力,创作出更优质的博文!!
关注我,关注我,关注我,重要的事情说三遍!!!!!!!!
1.前言
在写三子棋这个程序的时候我们不妨会用到很多的函数,我们之前也讲过了,当我们在设计一个比较复杂的程序的时候或者需要很多函数链接成的一个程序的时候,我们都会使用多个文件来编写,而每个文件的功能都是不一样的。以我们写的这个程序为例子。
- text.c文件是我们的测试文件。
- game.c文件是我们的函数实现文件。
- game.h文件是我们的函数声明文件。
2.功能分析
2.1主函数设计及菜单设计
凡是我们写一个C程序,主函数是必不可少的,主函数就相当于我们程序的入口一样。
我们玩游戏肯定==不止就玩一次,而是想玩完一把再玩一把,这个时候我们就可以用到我们之前学的do……while循环,这个循环的特点是至少会循环一次,==刚好符合我们的需求。而我们选择是否玩游戏可用到我们的switch……case语句,输入1玩游戏,输入0退出游戏,输入出这两个数以外的数就重新输入。于是我们就可以这样设计:
int main()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1://选择1就就进入game玩游戏。printf("开始游戏\n\n");game();break;case 0://选择0就退出游戏printf("退出游戏\n");break;printf("选择错误,请重新选择:>");default:break;}} while (input);return 0;
}
既然我们要玩游戏,我们的游戏界面肯定不能少,有了界面玩家才知道怎么开始怎么结束游戏。
所以这里我们就简单的设计一个界面,里面包含了开始信息和结束信息。
void menu()
{printf("***********************\n");printf("******** 1.play *******\n");printf("******** 0.exit *******\n");printf("***********************\n");
}
效果展示:
2.2打印棋盘与棋盘初始化
我们三子棋又称井字棋,顾名思义他的棋盘像一个井字,所以我们不妨就设计出一个井字棋盘。
所以我们想办法设计出这样的棋盘:
这里我们看到了我们设计出的棋盘有三行三列,也就是说我们需要创建一个二维数组。
但是如果我们直接写成char board[3][3] = { 0 };的话,如果我们想玩的是四子棋的话,在用到二维数组的地方我们都要改数组的大小,于是我们就想到了用宏定义:
#define ROW 3
#define COL 3
这样子如果我们想要玩的是四子棋,只需要改宏定义里面的数字就行了。
比如:
这里我们看到刚开始每个格子上都是空白的,也就是说我们棋盘刚开始初始化的是空格,毕竟空格是看不到的。
所以我们就知道了,我们的棋盘都初始化成空格。
void InitBoard(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++){board[i][j] = ' ';//全部都初始化成空格。}}
}
在打印我们的棋盘的时候我们可以把我们的棋盘分解一下,有助于我们更好的打印出我们棋盘。
我们观察一下我们的棋盘我们可以将其拆分成:
拆分成数据区和划分区:
- 第一个区域就是我们后面要下棋的地方,而我们的第二个区域是用来分割我们的棋盘的。
- 而我们仔细观察到我们的(|)总是比我们的棋盘的列少一,也就是说我们的棋盘是三列但是我们的(|)只有2个。
- 而我们的(—)是有多少列就有多少个,但是我们的的(—)却总是比少一行于是我们就把井字棋划分一下打印:
一个框一个循环,而每个循环里面又套一个循环,这就是我们经典的双循环结构:
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");if (i < row - 1) //比行少一行{int j = 0;for (j = 0; j < col; j++){printf("---");//判断(---);if (j < col - 1)printf("|");}}printf("\n");}
}
2.3玩家下棋
我们玩家下棋就只需要数组我么想要下棋的地方的坐标就行了。
但是我们要注意的三点点就是:
- 在我们的编程中我们数组的下标是从0开始的,但是在我们的日常生活中我们都是认为下标是从1开始的所以我们这里就要注意了。
- 还有就是我们下棋的地方必须是下在空格处,不能再已经下过棋子的地方再次下棋,同时我们给定下标也不能是超出棋盘范围的数。
- 如果我们输入了相同的坐标即输入的坐标已经下子了我们就要重新输入,所以还用设计一个循环结构。
同时我们给定玩家下的棋子是 ‘*’。
void PlayMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1) //这里用来一个while循环用来循环输入了相同的坐标后重新输入{printf("请输入坐标,中间以空格隔开:");scanf("%d %d", &x, &y);//判断是否超出棋盘的范围if (x >= 1 && x <= ROW && y >= 1 && y <= COL){if (board[x - 1][y - 1] == ' ')//我们输入的坐标都是比数组下标大1的所以这里我么们要减去。{board[x - 1][y - 1] = '*';break;}else{printf("该坐标已被占用,请重新输入\n");}}else{printf("输入错误,请重新输入\n");}}
}
2.4电脑下棋
电脑下棋的话我们要分析的主要有三点:
- 就是生成随机坐标。
- 生成随即坐标不能是相同的而且生成的坐标要是棋盘范围内的。
- 如果生成了相同的随机坐标即生成的随机坐标已经有子了就要重新生成随机坐标,这里也需要一个循环结构。
- 而生成随机坐标我们之前也学过了,用生成随值函数rand(),而rand总是与srand搭配的。
- 而我们的数组的下标是0-row和0-col,所以我们只需要用rand()%row和rand()%col就可以得到我们想要的下标范围了。
根据我们的分析我们就要可以写出一下代码:
void ComputerMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1)//循环防止生成相同的随机坐标{//生成随机坐标x = rand() % ROW;y = rand() % COL;//判断是空格就下子if (board[x][y] == ' '){board[x][y] = '#';break;}}
}
2.5判断输赢
判断谁赢了的方式有三种:
- 有一行都是一方的棋子。
- 有一列都是一方的棋子。
- 有一对角线都是一方的棋子。
而我们棋盘的状态有四种:
- 玩家赢了,结束。
- 电脑赢了,结束。
- 谁都还没赢,继续。
- 谁都没赢但是棋盘布满了,平局。
这是种状态我们分别用不同的返回值判断:
- 玩家赢了,结束,返回 ‘*’。
- 电脑赢了,结束,返回 ‘#’。
- 谁都还没赢,继续,返回 ‘C’。
- 谁都没赢但是棋盘布满了,平局,返回 ‘Q’。
所以我们就可以这样写:
int IsFull(char board[ROW][COL])
{int i = 0,j = 0;for (i = 0; i < ROW; i++){for (j = 0; j < COL; j++){if (board[i][j] == ' ')return 0;}}return 1;
}
charWhoIsWin(char board[ROW][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[0][0] != ' ')//对边return board[0][0];if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')//对边return board[0][2];if (IsFull(board) == 1)return 'Q';return 'C';
}
这里我们注意的是上述代码中我们并没有明摆着返回 ’ * ’ 或者 ‘#’ 而是返回的数组的值,这里其实用到了一点小技巧,就是我们以来就是说
玩家赢了返回 ’ * ’ 电脑赢了返回 ‘#’ 而刚好我们玩家下的棋子也是 ’*‘ 电脑下的棋子也是 ’#‘ 所以我们只需要返回数组的元素就行了。
2.5.1代码优化
但是这样写也有局限性,那就是当我们玩的不是三子棋的时候而是玩当四子棋的时候这个就不适用了,所以我们可以将我们的代码升级一下,如以下代码:
char WhoIsWin(char board[ROW][COL], int row, int col)
{char ret = 0;//判断行int i = 0;for (i = 0; i < row; i++){int flag = 0;int j = 0;for (j = 0; j < col-1; j++){if (board[i][j] == board[i][j + 1] && board[i][j] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[i][j];}}//判断列for (i = 0; i < col; i++){int flag = 0;int j = 0;for (j = 0; j < row-1; j++){if (board[j][i] == board[j + 1][i] && board[j][i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[j][i];}}//判断对角int flag = 0;for (i = 0; i < row-1; i++){if (board[i][i] == board[i+1][i+1] && board[i][i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1)return board[i][i];for (i = 0; i < row-1; i++){if (board[i][col - 1 - i] == board[i + 1][col - 1 - (i + 1)] && board[i][col - 1 - i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1)return board[i][col - 1 - i];//判断是否布满for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' '){flag = 1;break;}}}if (flag == 0)return 'Q';return 'C';
}
试玩展示:
3.game.h头文件展示
#pragma once
#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);
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
//玩家下棋
void PlayMove(char board[ROW][COL], int row, int col);
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//判断输赢
char WhoIsWin(char board[ROW][COL], int row, int col);
4.text.c源文件文件展示
void menu()
{printf("***********************\n");printf("******** 1.play *******\n");printf("******** 0.exit *******\n");printf("***********************\n");
}
void game()
{char ret = 0;//初始化棋盘char board[ROW][COL] = { 0 };InitBoard(board,ROW,COL);//打印棋盘DisplayBoard(board, ROW, COL);while (1){//玩家下棋PlayMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断输赢ret=WhoIsWin(board, ROW, COL);if (ret != 'C')break;//电脑下棋ComputerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断输赢ret = WhoIsWin(board, ROW, COL);if (ret != 'C')break;}if (ret == '*')printf("玩家赢\n");else if (ret == '#')printf("电脑赢\n");elseprintf("平局\n");}
int main()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("开始游戏\n\n");game();break;case 0:printf("退出游戏\n");break;printf("选择错误,请重新选择:>");default:break;}} while (input);return 0;
}
5.game.c源文件文件展示
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"//棋盘初始化
void InitBoard(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++){board[i][j] = ' ';}}
}//打印棋盘
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");if (i < row - 1){int j = 0;for (j = 0; j < col; j++){printf("---");if (j < col - 1)printf("|");}}printf("\n");}
}//玩家下棋
void PlayMove(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1){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)
{int x = 0;int y = 0;while (1){x = rand() % ROW;y = rand() % COL;if (board[x][y] == ' '){board[x][y] = '#';break;}}
}//判断输赢
char WhoIsWin(char board[ROW][COL], int row, int col)
{char ret = 0;//判断行int i = 0;for (i = 0; i < row; i++){int flag = 0;int j = 0;for (j = 0; j < col-1; j++){if (board[i][j] == board[i][j + 1] && board[i][j] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[i][j];}}//判断列for (i = 0; i < col; i++){int flag = 0;int j = 0;for (j = 0; j < row-1; j++){if (board[j][i] == board[j + 1][i] && board[j][i] != ' '){flag = 1;}else{flag = 0;break;}}if (flag == 1){return board[j][i];}}//判断对角int flag = 0;for (i = 0; i < row-1; i++){if (board[i][i] == board[i+1][i+1] && board[i][i] != ' '){flag = 1;}else{flag = 0;break;}}{if (flag == 1)return board[i][i];for (i = 0; i < row-1; i++){if (board[i][col - 1 - i] == board[i + 1][col - 1 - (i + 1)] && board[i][col - 1 - i] != ' '){flag = 1;}elseflag = 0;break;}}if (flag == 1)return board[i][col - 1 - i];//判断是否布满for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' '){flag = 1;break;}}}if (flag == 0)return 'Q';return 'C';
}