前言与概述
本文章讲述如何通过C语言开发一款三子棋的小游戏。笔者才识浅薄,如有错误,欢迎各位编程大佬在评论区批评指正,笔者不胜感激。
游戏介绍
三子棋是一款益智的趣味小游戏。多名玩家在3*3的棋盘下棋,棋盘共九个方格,每个方格最多只能放置一枚棋子。只要有一名玩家下的三个棋子可以连成一条直线,则该玩家取得游戏的胜利,游戏结束。如果棋盘已满,还是没有玩家下的棋子可以连成直线,那么游戏平局。
游戏实现路径
①:创建并初始化棋盘 ②:打印棋盘 ③:玩家和电脑下棋 ④:判断游戏状态
游戏运行全过程
当用户输入1进入游戏,程序会打印未下棋子的棋盘,用户通过输入要下棋子的坐标,将棋子下在棋盘的指定位置。电脑会在棋盘的随机位置下棋,打印下棋后棋盘的状况。如果玩家或电脑下的三个棋子可以连成直线,则玩家或电脑取胜。如果棋盘已满,但玩家和电脑下的棋子都不能连成直线,则游戏平局。否则,游戏继续。当游戏结束,用户输入0可以退出程序。
游戏开发前准备
为了更好的实现游戏的相关功能,笔者在该工程目录下创建了四个文件,分别是main.cpp、test.cpp、game.cpp、game.h。main.cpp用于实现用户与玩家的交互界面。test.cpp用于测试游戏的实现逻辑。game.cpp用于游戏实现的相关函数。game.h用于存放源文件所需要的头文件、符号常量、函数声明,当其它源文件需要调用这些内容,直接引用即可。
交互界面
实现要求
游戏至少运行一次,如果玩家玩完一把,感觉不过瘾,还可以继续玩。直到玩家想主动退出游戏。这里就可以使用do-while循环语句。用户输入1,进入游戏;用户输入0,退出游戏;用户输入其它数字,提示:“输入错误,请重新输入”,直到用户输入正确数字。这里就可以使用switch-case语句,对用户输入的情况进行判断,并作出相应的反馈。当然,我们需要制作并打印一个简易的菜单,让用户可以轻松的与程序进行交互。
示例代码
//用于实现玩家与游戏的交互和打印主界面
#include "game.h"
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;
}
menu()函数代码
void menu()
{
printf("***************************\n");
printf("******** 1.play *******\n");
printf("******** 0.exit *******\n");
printf("***************************\n");
}
模拟运行
补充说明
①:do-while循环语句,循环条件是变量input的值。变量input保存着用户输入的值,当用户输入的值非0,循环条件一直成立,程序持续运行,如果用户输入的值为0,循环条件不成立,程序退出。
②:main.cpp文件引用了自定义的头文件,引用方式为”头文件名.h”引用该头文件,相当于将头文件里所有的内容复制到main.cpp文件中。
创建和初始化棋盘
创建棋盘
三子棋是3*3的棋盘,在C语言中可以抽象的理解为3*3的二维数组。二维数组的行和列可以是3与3的常量,也可以是#define定义常量3。笔者更倾向于#define定义常量3。因为数组需要存放“棋子”,所以数组类型是char类型,数组可以初始化为0。
创建二维数组示例代码
//创建二维数组
char board[3][3] = { 0 };//常量3作为二维数组行和列
#define ROW 3
#define COLUMN 3
char board[ROW][COLUMN] = { 0 };//使用#define 定义常量3作为二维数组行和列
初始化棋盘
初始化棋盘就是把数组里所有的元素都改为空格。for循环可以用作遍历数组里的元素,“=”赋值符可以修改数组里任意元素的内容。
示例代码
void format_board(char board[ROW][COLUMN], int row, int column)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < column; j++)
{
board[i][j] = ' ';
}
}
}
代码分析
对棋盘初始化不需要任何返回值,所以返回类型使用void类型(空类型)即:没有返回值,也不需要返回值。在实参方面,因为需要修改数组,所以需要将数组名传过去(本质上传的是数组首元素的地址),同时for循环遍历二维数组需要知道数组的行和列,需要将常量ROW和常量COLUMN传过去。在形参方面,因为数组是char类型的二维数组,所以需要交代数组行和列的大小(行可以省去)。同时传来的还有常量ROW和常量COLUMN的值,所以需要用int类型的变量接收(注意:如果使用变量名是row和column,必须小写,防止与常量ROW和常量COLUMN发生冲突,导致编译器报错)。循环变量i、j的初始值是0,因为数组下标从0开始,for循环的循环条件是变量i或j的值小于变量row或变量column,而不是小于等于,是为了避免出现数组元素溢出。“=”将空格赋予数组,不可以只敲一个 ,因为空格是字符,所以需要以字符的形式(‘ ‘)传给数组。
打印棋盘
打印棋盘的实质就是访问数组中的每个元素,为了使棋盘更加美观,我们可以适当的使用空格、-(中划线-英文)、|(竖线)。
示例代码1
void print_board(char board[ROW][COLUMN], int row, int column)
{
int i = 0;
for (i = 0; i < row; i++)
{
printf(" %c | %c | %c ", board[i][0], board[i][1], board[i][2]);
printf("\n");
if (i < row - 1)
{
printf("---|---|---");
printf("\n");
}
}
}
示例代码2
void print_board(char board[ROW][COLUMN], int row, int column)
{
int i, j;
for (i = 0; i < row;i++)
{
for (j = 0; j < column;j++)
{
printf(" %c ", board[i][j]);
if (j < column - 1)
{
printf("|");
}
}
printf("\n");
int z = 0;
if (i < row - 1)
{
for (z = 0; z < row; z++)
{
printf("---");
if (z < row - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
模拟运行
补充说明
示例代码1相对简单,易于理解。不过仅仅只适用于3*3的数组中,示例代码2代码量更大,使用多个if语句和for循环,将单行打印拆分成多个部分,使得该函数可以打印任意行的数组。
玩家与电脑下棋
玩家下棋
玩家通过输入坐标,将棋子放置在棋盘的指定位置。对坐标有一定的限制:首先,坐标不可以越界,即横坐标和纵坐标必须是大于0小于4的整数。其次,一个方格只能放一个棋子,如果坐标上已经有棋子,就不可以再放了。玩家棋子用“*”表示。
示例代码
void player_move(char board[ROW][COLUMN], int row, int column)
{
while (1)
{
int x, y;
printf("玩家下棋>");
scanf("%d %d", &x, &y);
if (x > 0 && x < 4 && y > 0 && y < 4)
{
if (board[x-1][y-1] == ' ')
{
board[x-1][y-1] = '*';
break;
}
else
{
printf("该处已布置棋子,请重新下棋>\n");
}
}
else
{
printf("坐标不合规,请重新输入>\n");
}
}
}
运行结果
代码分析
函数内使用while循环,因为玩家在输入坐标时,可能会输错。当玩家下错棋,程序会提示错因,并等待重新输入坐标,直到坐标正确,就会将棋子放在棋盘指定的位置,并通过break语句跳出循环。判断坐标是否被占用、棋子放置棋盘都将变量x和变量y的值减一,是因为玩家输入的坐标始终比实际在数组中的位置横纵坐标各多一。例如玩家输入的坐标是1 1,实际在数组中的位置是下标0 0。
电脑下棋
电脑下棋同样也是通过坐标,将棋子放置在棋盘的指定位置。不过,对于计算机而言,我们可以通过控制计算机生成随机数的范围,来避免出现坐标不合规的问题。由于,计算机生成的坐标是随机的,所以可能存在坐标被占用的情况,当生成的坐标被占用,就让计算机重新生成坐标,直到生成的坐标没有被占用,就将棋子放置在生成的坐标位置处。
示例代码
void computer_move(char board[ROW][COLUMN], int row, int column)
{
int x = 0;
int y = 0;
printf("电脑下>\n");
while (1)
{
x = rand() % row;
y = rand() % column;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
运行结果
代码分析
rand()函数用于生成随机数,需要先调用srand()函数,同时srand()函数需要整型数字作为种子,但如果种子不变,那生成的随机数就不变,所以需要一个时刻变化的数作为种子。时间戳是无时无刻不在变化。time(NULL)用于获取当前时间的时间戳,但时间戳并不是整型数字,就需要强制转换为int类型((unsigned int))。在程序运行中,srand((unsigned int)time(NULL))只需要调用一次即可,多次快速调用,会导致生成的随机数相近甚至相同。rand()函数生成的随机数较大,通过% row,就可以将生成的随机数控制在0-2之间。
判断游戏状态
玩家赢或电脑赢
算法
三子棋获胜的规则:任何一方下的三个棋子,可以连成直线。连成直线有三种可能:同一行、同一列、对角线。
在同一行中,横坐标的值相等;在同一列中,纵坐标的值相等。由于同一行、同一列都各有三种可能性,所以可以使用for循环,遍历三次。
三个棋子可以连成直线,就是数组中三个元素的值相等。判断三个元素相等的方式是第一个元素等于第二个元素,第二个元素等于第三个元素。为了避免数组中三个元素等于空格,导致程序误判,所以需要判断其中一个元素不等于空格。
返回三个相同元素中的一个,根据返回的元素判断谁赢。如果返回的是*(玩家下的棋),表明玩家赢;如果返回的是#(电脑下的棋),表示电脑赢。
示例代码
char win(char board[ROW][COLUMN], int row, int column)
{
int i = 0;
int j = 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];
}
}
for (j = 0; j < column; 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];
}
int result = 0;
result = is_full(board, row, column);
if (result == 0)
{
return 'c';
}
else
{
return 'q';
}
}
平局或游戏继续
算法
判断平局的实现方式:在任何一方都没有取胜的情况下,棋盘已经没有空余的方格。实际上就是遍历完数组所有元素,没有一个元素是空格。游戏继续的实现方式:在任何一方都没有取胜的情况下,棋盘还有空余的方格。实际上就是遍历数组,还有数组的元素是空格。笔者定义变量result,用于接收返回值。如果返回值是0,说明数组中还有元素是空格,棋盘未满,返回字符c,表示游戏继续;如果返回值是1,说明数组中所有元素都不是空格,棋盘已满,返回字符q,表示平局。
示例代码
int is_full(char board[ROW][COLUMN], int row, int column)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < column; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
游戏总测试
源代码
main.cpp源代码
#define _CRT_SECURE_NO_WARNINGS 1
//用于实现玩家与游戏的交互和打印主界面
#include "game.h"
int main()
{
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);
return 0;
}
test.cpp源代码
#define _CRT_SECURE_NO_WARNINGS 1
//用于测试游戏的实现逻辑
#include "game.h"
void game()
{
//创建二维数组
char board[ROW][COLUMN] = { 0 };
//格式化棋盘
format_board(board, ROW, COLUMN);
//打印棋盘
print_board(board, ROW, COLUMN);
//玩家下棋
while (1)
{
char amount = 0;
player_move(board, ROW, COLUMN);
amount = win(board, ROW, COLUMN);
if (amount == '*')
{
printf("玩家赢!\n");
print_board(board, ROW, COLUMN);
break;
}
else if (amount == '#')
{
printf("电脑赢!\n");
print_board(board, ROW, COLUMN);
}
else if (amount == 'q')
{
printf("平局!\n");
print_board(board, ROW, COLUMN);
break;
}
computer_move(board, ROW, COLUMN);
amount = win(board, ROW, COLUMN);
if (amount == '*')
{
printf("玩家赢!\n");
print_board(board, ROW, COLUMN);
break;
}
else if (amount == '#')
{
printf("电脑赢!\n");
print_board(board, ROW, COLUMN);
}
else if (amount == 'q')
{
printf("平局!\n");
print_board(board, ROW, COLUMN);
break;
}
print_board(board, ROW, COLUMN);
}
}
game.h源代码
//用于存放源文件所需要的头文件、符号常量、函数声明
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu();
void game();
#define ROW 3
#define COLUMN 3
void format_board(char board[ROW][COLUMN], int row, int column);
void print_board(char board[ROW][COLUMN], int row, int column);
void player_move(char board[ROW][COLUMN], int row, int column);
void computer_move(char board[ROW][COLUMN], int row, int column);
char win(char board[ROW][COLUMN], int row, int column);
int is_full(char board[ROW][COLUMN], int row, int column);
game.cpp源代码
#define _CRT_SECURE_NO_WARNINGS 1
//用于游戏实现的相关函数
#include "game.h"
void menu()
{
printf("***************************\n");
printf("******** 1.play *******\n");
printf("******** 0.exit *******\n");
printf("***************************\n");
}
void format_board(char board[ROW][COLUMN], int row, int column)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < column; j++)
{
board[i][j] = ' ';
}
}
}
void print_board(char board[ROW][COLUMN], int row, int column)
{
int i, j;
for (i = 0; i < row;i++)
{
for (j = 0; j < column;j++)
{
printf(" %c ", board[i][j]);
if (j < column - 1)
{
printf("|");
}
}
printf("\n");
int z = 0;
if (i < row - 1)
{
for (z = 0; z < row; z++)
{
printf("---");
if (z < row - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
void player_move(char board[ROW][COLUMN], int row, int column)
{
while (1)
{
int x, y;
printf("玩家下棋>");
scanf("%d %d", &x, &y);
if (x > 0 && x < 4 && y > 0 && y < 4)
{
if (board[x-1][y-1] == ' ')
{
board[x-1][y-1] = '*';
break;
}
else
{
printf("该处已布置棋子,请重新下棋>\n");
}
}
else
{
printf("坐标不合规,请重新输入>\n");
}
}
}
void computer_move(char board[ROW][COLUMN], int row, int column)
{
int x = 0;
int y = 0;
printf("电脑下>\n");
while (1)
{
x = rand() % row;
y = rand() % column;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
char win(char board[ROW][COLUMN], int row, int column)
{
int i = 0;
int j = 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];
}
}
for (j = 0; j < column; 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];
}
int result = 0;
result = is_full(board, row, column);
if (result == 0)
{
return 'c';
}
else
{
return 'q';
}
}
int is_full(char board[ROW][COLUMN], int row, int column)
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < column; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
标签:int,三子,C语言,COLUMN,小游戏,board,printf,ROW,row
From: https://blog.csdn.net/2401_84689376/article/details/141537637