今天我将向大家分享如何用C语言的一些入门语法实现一个小游戏——三子棋
1.准备工作
首先,我们打开visual stdio(我用的是2022的版本,没有的小伙伴可以在电脑自带的Microsoft store里面自行下载安装,安装过程也很简单,网上也有很多教程)
然后选择创建新项目
项目模板选择空项目
项目名称我们命名为game1
在解决方案资源管理器界面中的源文件选项选择源文件—添加项—新建项创建一个名为test.c的文件,这是我们的游戏测试文件
然后,我们再按照同样的方法创建一个名为game.c的文件,这个文件用来存放我们程序所用到的函数
最后,在头文件选项中用同样的方法创建一个名为game.h的头文件(注意这里要在命名界面选择头文件类型)
准备工作结束,我们开始编写我们的程序
2.创建主函数
首先在测试文件test.c中创建主函数
int main() {
test();
return 0;
}
我们所有的功能将在test()函数中完成
3.书写菜单函数
进入test()函数,我们希望根据用户的输入内容决定他是否开始游戏,同时应该存在一个引导用户选择输入内容的菜单,这个菜单我们可以用一个简单的menu()函数来实现,函数如下
void menu() {
printf("*******************\n");
printf("***1.play 0.exit***\n");
printf("*******************\n");
}
4.判断游戏是否运行
由此玩家便可以自行选择游玩或者退出该游戏了
这里我们应该意识到,游戏可能因为玩家的选择而无线重复,且游戏的菜单打印和用户输入至少应该执行一次,所以在这里我们需要一个do-while循环结构
要根据玩家输入数值的不同而运行不同的内容,我们需要一个switch case结构
要保存玩家输入的数据,我们应该定义一个变量input
具体函数如下
void test() {
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);
}
在这里,玩家选择了1之后,程序会接着运行game()函数,这里存放我们游戏的主要部分;如果玩家选择0,则会直接结束该程序;如果选择其他数据则会认为是错误输入
5.棋盘打印
想要下棋,我们至少应该现有一个棋盘,在终端上想要实现一个棋盘,我们可以通过-和|来进行分隔,大致呈现出如下的效果
| |
---|---|---
| |
---|---|---
| |
在终端运行时你会看到一个较为规整的九宫格
5.1棋盘打印方法一
在这里,如果我们只是想要运行一个三子棋棋盘的打印,可以采用方法一:
定义一个char类型的二维数组,名称为board,行数和列数分别固定为3,对这个数组进行初始化,使其中所有的元素为' ',并按照上述格式对棋盘进行打印。我们将对棋盘的打印定义为一个函数,函数名为DisplayBoard,返回类型为void,参数为一个char类型的二位数组。数组定义和初始化以及棋盘打印函数如下所示
char board[3][3] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',};
void DisplayBoard(char board[][])
{
printf(" %c | %c | %c \n---|---|---\n %c | %c | %c \n---|---|---\n %c | %c | %c ", board[0][0], board[0][1],
board[0][2], board[1][0], board[1][1], board[1][2], board[2][0], board[2][1], board[2][2]);
}
5.2棋盘打印方法二
如果我们希望棋盘的大小可以由我们自由定义,可以采用方法二:
5.2.1创建字符数组
我们不妨在game.h中进行如下操作
#define ROW 3
#define COL 3
由此我们完成了对ROW(行)和COL(列)的定义,使得ROW等同于int row=3,COL等同于int col=3,这样创建一个char board[ROW][COL]就为我们自定义棋盘大小布局创造了条件
5.2.2初始化字符数组
数组创建后,我们需要对其进行初始化,我们希望在玩家和电脑未下子前,棋盘应该是空的,所以数组中的全部元素应该是' '
数组的初始化可以通过定义一个函数InitBoard来实现,函数的参数为数组board和行数ROW、列数COL,这里的初始化只需一个简单的遍历就能完成,具体函数如下
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] = ' ';
}
}
}
完成棋盘的初始化之后,我们就需要打印棋盘了。与上述的简单方法不同,由于我们的棋盘大小可以自行定义,所以打印棋盘的函数也必须由更多的循环来进行控制
5.2.3打印棋盘
我们将打印棋盘的函数命名为DisplayBoard,返回类型为void,参数为数组board和行数ROW、列数COL。棋盘函数的运行逻辑应该是先打印一个空格,再打印一个数组元素,再打印一个空格,将这 %c 作为一个整体看待;而后在打印一个竖向分割竖杠|,将两个打印放在一个for循环中即可打印出完整一行。然后我们打印棋盘的水平分割,同样是将---当作一个整体先打印,然后打印竖向分割竖杠|,将两个打印放在一个for循环中。这里我们要注意,期盼边缘的水平分割和竖向分割都是多余的,所以到行末和列末时不应该打印分割,我们可以在打印前使用if条件判断来完成。
最后将上述打印都放入一个for循环中便可完成棋盘的打印,具体函数如下所示
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) {
for (j = 0; j < col; j++) {
printf("---");
if (j < col - 1) {
printf("|");
}
}
printf("\n");
}
}
}
程序后面部分的内容书写基于方法二,有兴趣的小伙伴可以在方法一的基础上继续书写,逻辑都是相同的只需要一些稍微的调整即可
6.实现落子
棋盘打印完成,便可以让玩家和电脑分别落子,该过程反复进行,所以我们需要一个循环,并在合适的条件下跳出循环(如玩家或电脑中的任意一方获胜或者是达成平局),所以我们这里选择一个while循环,并固定循环条件为1
玩家落子和电脑落子我们分别用两个函数PlayerMove和ComputerMove来完成
6.1实现玩家落子
玩家下棋时,可以手动选择落子的位置,所以需要两个int类型的变量x,y来保存玩家输入的数据,另外,我们需要根据玩家输入的坐标值来为数组赋值,这里我们用'*'来表示玩家的棋子。也就是说,我们需要对玩家所指定棋盘位置所对应的数组赋予'*'。(注意,这里的x和y的值均需要-1后才是对应数组元素,因为普通玩家通常不会像程序员一样从0开始计数)
同时我们还需要完成两个工作,那就是限定玩家输入棋子坐标的范围和对已经存在棋子的棋盘位置的保护这里我们可以用两个嵌套的if条件句完成
玩家落子函数具体实现如下
void PlayerMove(char board[ROW][COL], int row, int col) {
int x = 0;
int y = 0;
printf("玩家走:>\n");
while (1) {
printf("请输入你的棋子位置(第一个数字表示行号,第二个数字表示列号,中间以空格隔开):>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= col && y >= 1 && y <= col) {
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该位置已被占用,请重新选择");
}
}
else {
printf("位置坐标非法,请重新输入");
}
}
}
6.2实现电脑落子
接着是对电脑落子函数的书写,我们同样需要一个棋子的位置坐标,但是这个只能使用生成的随机数来确定。为了确保生成的坐标值在0-2之间,我们可以让生成的x和y分别对3做取余,然后就可以按照同玩家落子时一样的方法对电脑确定的棋子坐标对应的数组元素进行赋值,这里我们使用'#'表示电脑的棋子,电脑落子的函数如下
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑走:>\n");
while (1) {
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
注意,在本函数中使用了rand()函数进行随机数生成,需要在test()函数中使用srand()函数和time()函数,time()的参数为空指针NULL,对time()的返回值进行强制类型转化为unsigned int,然后作为参数传入srand()函数。同时,要调用srand()函数和time()函数,需要在test.c头部进行相关声明,声明和修改后的test()函数如下
#include <stdlib.h>
#include <time.h>
void test() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
为了帮助玩家判断棋局情况,我们需要在玩家落子和电脑落子后分别打印一下棋盘
7.棋局判断
由此就可以实现玩家和电脑的对弈,最后我们需要对终止条件进行判定,我们可以定义这样一个条件判断函数。终止条件可能发生在玩家或者电脑任意一方落子后产生,所以在玩家落子和电脑落子后都应该进行判断。对落子后情况的判断可以有四种,分别是玩家获胜、电脑获胜、达成平局、继续进行,我们分别返回四种字符来表示这四种情况,它们分别是'*''#*'Q''C'
7.1返回字符函数
获胜的情况有四种,分别是行相同、列相同、左上到右下对角线相同、右上到左下对角线相同;不管是那种情况,我们只需返回相同的行、列或对角线中的任意一个字符即可(但是要注意不能出现返回空格的情况),并且这样返回值可以判断出是玩家获胜还是电脑获胜。
除此之外,我们还要判断是否出现平局。很容易想到,当棋盘已经被下满时(没有' '存在了)就必然平局了,所以我们只需要简单遍历一下board数组,如果有' '存在,则可能还未平,所以我们返回0;反之则返回1
如果上述情况均不符合,我们就返回继续游戏的结果
为了体现函数的单一出口原则,我们可以先定义一个名为consequence的char变量并初始化为'C',满足一方胜利或平局时则赋予其对应的字符,由此即可实现返回落子后的情况
具体函数如下
返回值函数IsWin
char IsWin(char board[ROW][COL], int row, int col) {
char consequence = 'C';
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] != ' ') {
consequence = 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] != ' ') {
consequence = board[0][i];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ') {
consequence = board[0][0];
}
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[2][0] != ' ') {
consequence = board[2][0];
}
if (IsFull(board, ROW, COL)) {
consequence = 'Q';
}
return consequence;
}
7.2棋局已满判定
棋盘已满函数IsFull
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 < row; j++) {
if (board[i][j] == ' ') {
return 0;
}
}
}
return 1;
}
7.3完整函数
当返回值为'C'时我们需要不断进行玩家和电脑的落子行为,为其他值时表明本轮游戏已经结束,需要脱离对弈的循环,所以很明显这里需要进行一个if条件的判断,并在合适情况下break,我们可以先定义一个名为ret的char类型变量,吧IsWin函数的返回值赋给ret,当返回值为'C'时不跳出对弈循环,不为'C'时跳出循环,并在跳出循环后根据ret当前的值进行结果判断——是一方胜利还是平局
具体函数如下所示
void game() {
char ret = 0;
char board[ROW][COL] = {0};
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
while (1) {
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C') {
break;
}
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C') {
break;
}
}
if (ret == '*') {
printf("恭喜你赢了!\n");
}
else if (ret == '#') {
printf("很遗憾你输了。\n");
}
else if (ret=='Q') {
printf("打成平局,再接再厉!\n");
}
}
8.注意事项
说完了程序的书写思路,再分享一些小的注意事项
8.1game.h
game.h这个头文件中可以用来存放函数声明、函数库引用和符号定义,我在本程序中的game.h中的内容为,为了方便起见我们可以把一些常用的函数库引用放在头文件中,后续文件只需引用头文件即可(引用自己的头文件方式为#include "头文件名")
#define ROW 3
#define COL 3
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void InitBoard(char board[ROW][COL], int row, int 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);
char IsWin(char board[ROW][COL], int row, int col);
8.2game.c
game.c文件中可以用来存放game.h中声明的函数本体,我的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++) {
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) {
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;
printf("玩家走:>\n");
while (1) {
printf("请输入你的棋子位置(第一个数字表示行号,第二个数字表示列号,中间以空格隔开):>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= col && y >= 1 && y <= col) {
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该位置已被占用,请重新选择");
}
}
else {
printf("位置坐标非法,请重新输入");
}
}
}
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑走:>\n");
while (1) {
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
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 < row; j++) {
if (board[i][j] == ' ') {
return 0;
}
}
}
return 1;
}
char IsWin(char board[ROW][COL], int row, int col) {
char consequence = 'C';
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] != ' ') {
consequence = 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] != ' ') {
consequence = board[0][i];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ') {
consequence = board[0][0];
}
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[2][0] != ' ') {
consequence = board[2][0];
}
if (IsFull(board, ROW, COL)) {
consequence = 'Q';
}
return consequence;
}
8.3test.c
最后是测试程序test.c,内容如下
#include "game.h"
void game() {
char ret = 0;
char board[ROW][COL] = {0};
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
while (1) {
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C') {
break;
}
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C') {
break;
}
}
if (ret == '*') {
printf("恭喜你赢了!\n");
}
else if (ret == '#') {
printf("很遗憾你输了。\n");
}
else if (ret=='Q') {
printf("打成平局,再接再厉!\n");
}
}
void menu() {
printf("*******************\n");
printf("***1.play 0.exit***\n");
printf("*******************\n");
}
void test() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
int main() {
test();
return 0;
}
8.4其他
由于直接运行scanf编译器会报错,所以需要
#define _CRT_SECURE_NO_WARNINGS 1
9.结语
以上就是关于写作本程序我的全部思考啦!第一次写这么长的文章,却觉得很过瘾啊!如果你觉得对你有帮助的话还请不要吝惜你的点赞和关注!你的鼓励就是我持续更新的最大动力!!我们下期再见!!