学了那么多关于C语言的知识,也该进行一下实操了。三子棋游戏应该是大家学生时代课间比较喜欢娱乐消遣的一种方式吧。那么我们今天就来说说如何实现简单版本的三子棋对战小游戏吧。
三子棋游戏介绍
三子棋游戏类似于五子棋,不同的是它的棋盘大小是九宫格且达到如下图一种结果则算赢。三子棋游戏的规则是必须 横向 或 纵向 或 斜线 填满相同的棋子才算赢。
三子棋游戏实现的编程框架
1. 游戏不退出,当结束一局可以进行下一局(循环)。
2. 应用多文件的形式完成代码。在企业团队进行项目开发时,多文件形式写代码是必不可少的,因此这里我们也用多文件形式写代码,这些文件都必须放在同一个工程(即同一个文件夹)下。
test.c -- 测试游戏的,即主要是main函数内对游戏功能进行框架搭建和测试。
game.c -- 游戏函数的实现,即主要是完成游戏核心功能的函数的具体实现。
game.h -- 游戏函数的声明,即主要是对这些函数的声明和简介。
不同文件的实现
(一)game.h文件
该文件为实现三子棋项目的声明。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
void InitGame(char board[ROW][COL], int row, int col);//初始化棋盘
void DisPlayBoard(char board[ROW][COL], int row, int col);//显示棋盘
void PlayerGame(char board[ROW][COL], int row, int col);//玩家下棋
void ComputerGame(char board[ROW][COL], int row, int col);//电脑下棋
//玩家赢返回‘*’
//电脑赢返回‘#’
//平局返回‘Q’
//可以继续下棋返回‘C’
char is_win(char board[ROW][COL], int row, int col);//判断输赢
int if_full(char board[ROW][COL], int row, int col);//判断棋盘是否下满
宏定义的变量ROW 和COL表示棋盘的大小是三行三列的九宫格,在game.h中声明棋盘大小。如果想改变棋盘的大小,直接在该文件中进行修改即可。
(二)test.c文件
1.菜单功能
实现一个简易的菜单功能,提醒玩家选择开始游戏还是结束游戏。选择1表示开始游戏,选择0表示结束游戏。
void menu()
{
printf("*********************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*********************************\n");
}
2.main函数实现连续多玩
由于我们游戏开发的目标是实现玩家可以选择连续多玩游戏,因此本段代码主要的逻辑是使用循环控制。而且我们程序运行时必须至少进行一次游戏,所以采取do……while循环进行控制,具体代码逻辑可以参考之前写的博客C语言之猜数字小游戏。
int main()
{
int input = 0;
do {
menu();
scanf("%d", &input);
switch (input)
{
default:
printf("出错啦,公主请重新输入:\n");
break;
case 1:
printf("尊敬的公主,请开始你的游戏吧!\n");
game();
break;
case 0:
printf("游戏退出,公主拜拜!\n");
break;
}
} while (input);
return 0;
}
3.game函数的实现
game函数是实现游戏流程,如打印棋盘,玩家下棋,电脑下棋,并判断输赢等。当然game函数中只是对这些函数的调用,核心功能的函数实现放在game.c源文件中。代码如下:
void game()
{
char board[ROW][COL] = { 0 };
srand((unsigned int)time(NULL));//设置随机种子,实现每次落子随机
char ret = 0;//对决输赢的返回值
InitGame(board, ROW, COL);//初始化棋盘
DisPlayBoard(board, ROW, COL);
while (1)
{
PlayerGame(board, ROW, COL);//玩家下棋
DisPlayBoard(board, ROW, COL);//显示对决情况
ret = is_win(board, ROW, COL);//判断输赢
if (ret != 'C')
break;
ComputerGame(board, ROW, COL);//电脑下棋
DisPlayBoard(board, ROW, COL);//显示对决情况
ret = is_win(board, ROW, COL);//判断输赢
if (ret != 'C')
break;
}
if (ret == 'Q')
{
printf("达成平局啦!\n");
}
else if (ret == '*')
{
printf("恭喜公主获胜啦!\n");
}
else
{
printf("公主还要加油啦,电脑获胜咯!\n");
}
}
本代码使用'“*”表示玩家棋子,“#”表示电脑棋子,使用二维数组存储棋子的位置,而此二维数组相当于棋盘。最开始时要初始化棋盘并打印棋盘,以让玩家值观看见棋盘并准备下棋。下棋时不是只下一颗棋就结束了,所以要用循环,直至分出结果后结束循环,判断游戏结局。同时,玩家和电脑每下一颗棋时都要打印棋盘并判断是否分出胜负,如果没有分出胜负则继续下棋(不跳出循环)。
(三)game.c文件
该文件是实现整个游戏最核心的逻辑部分。
1.初始化棋盘(InitGame)
游戏实现是使用字符作为玩家和电脑的棋子,在初始化时,用空格来初始化棋盘(空格也是一个字符)。二维数组用两层for循环对数组元素进行赋值即可。
void InitGame(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
2.显示棋盘对决情况(DisPlayBoard)
//版本1 不推荐
//该版本在修改棋盘大小时,printf函数中的内容也要进行相应的修改,增加了后续维护的工作量
//void DisplayBoard(char board[ROW][COL], int row, int col) {
// int i = 0;
// for (i = 0; i < row; i++) {
// // 1. 打印数据
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
// // 2. 打印分割线
// if (i < row - 1) {
// printf("---|---|---\n");
// }
// }
//}
//版本2 推荐版本,针对棋盘大小发生变化时可以动态修改
void DisPlayBoard(char board[ROW][COL], int row, int col)
{
//1 打印数据
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1) {
printf("|");
}
}
printf("\n");
//2 打印分割线
for (int j = 0; j < col; j++)
{
printf("___");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
printf("\n");
}
棋盘设计情况如下图
3.玩家下棋 (PlayerGame)
在玩家下棋时,需要考虑玩家输入的坐标是否合法问题,如果玩家输入坐标越界或者玩家输入的坐标已经有棋子时都要给出相应的提醒,并让玩家重新输入坐标直至输入正确。因此使用循环实现整个代码逻辑。
当然,在我们玩家眼中坐标都是1,2,3,而数组的下标是从0开始的,所以当玩家输入坐标后需要进行减1。
void PlayerGame(char board[ROW][COL], int row, int col)
{
printf("公主请落子,输入落子的坐标:");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
while (1)
{
if (x >= 1 && x <= row && y >= 1 && y <= col && board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("尊敬的公主,输入的坐标不对嗷,请重新输入:");
scanf("%d %d", &x, &y);
}
}
}
4.电脑下棋(ComputerGame)
void ComputerGame(char board[ROW][COL], int row, int col)
{
printf("下面是电脑落子!\n");
int x = 0, y = 0;
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
这里的row和col都是3,所以rand()%row,rand()%col已经满足了电脑随机产生的坐标不会越界,取余产生的数字也是0、1、2,刚好与数组下标相符,而当遇到空格子时电脑才完成下棋,因此电脑下棋并不会覆盖原来已经下好的棋子。
5.判断棋盘是否下满(if_full)
int if_full(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
若还有一个空位,则说明棋盘没有下满,此时返回0;反之,返回1,即棋盘已经下满。
6.判断输赢(is_win)
玩游戏,不仅要享受玩的过程,还要注重游戏的结果。在本篇文章开头已经介绍了三子棋游戏胜利的8种情况,因此在函数编写时则按照行、列、斜线依次进行结果的判定。
设定该函数返回的结果对应的下棋对决结果如下:
- 玩家赢 ------ ‘*’
- 电脑赢 ------ ‘#’
- 平局 ------ ‘Q’
- 可以继续下棋 ------ ‘C’
//玩家赢返回‘*’
//电脑赢返回‘#’
//平局返回‘Q’
//可以继续下棋返回‘C’
char is_win(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
int row_flag = 1;//判断行是否相同,假设是相同
for (int j = 1; j < col; j++)
{
if (board[i][j] != board[i][0])
{
row_flag = 0;//有一个不同,则行不同
break;
}
}
if (row_flag == 1 && board[i][0] != ' ') return board[i][0];
}
for (int i = 0; i < col; i++)
{
int col_flag = 1;//判断列是否相同,假设是相同
for (int j = 1; j < row; j++)
{
if (board[j][i] != board[0][i])
{
col_flag = 0;//有一个不同,则列不同
break;
}
}
if (col_flag == 1 && board[0][i] != ' ') return board[0][i];
}
//判断正对角线是否相同
int zflag = 1;
for (int i = 1; i < row; i++)
{
if (board[i][i] != board[0][0]) {
zflag = 0;
break;
}
}
if (zflag == 1 && board[0][0] != ' ') return board[0][0];
//判断反对角线是否相同
int fflag = 1;
for (int i = 0; i < row; i++)
{
if (board[0][COL - 1] != board[i][COL - 1 - i])
{
fflag = 0;
break;
}
}
if (fflag == 1 && board[0][COL - 1] != ' ') return board[0][COL - 1];
//棋盘下满返回平局
if (if_full(board,ROW,COL) == 1) return 'Q';
//棋盘没下满、玩家或者电脑没有一方获胜,则可以继续下棋。
return 'C';
}
完整代码
game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
void InitGame(char board[ROW][COL], int row, int col);
void DisPlayBoard(char board[ROW][COL], int row, int col);
void PlayerGame(char board[ROW][COL], int row, int col);
void ComputerGame(char board[ROW][COL], int row, int col);
char is_win(char board[ROW][COL], int row, int col);
int if_full(char board[ROW][COL], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitGame(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
void DisPlayBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1) {
printf("|");
}
}
printf("\n");
for (int j = 0; j < col; j++)
{
printf("___");
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
printf("\n");
}
void PlayerGame(char board[ROW][COL], int row, int col)
{
printf("公主请落子,输入落子的坐标:");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
while (1)
{
if (x >= 1 && x <= row && y >= 1 && y <= col && board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("尊敬的公主,输入的坐标不对嗷,请重新输入:");
scanf("%d %d", &x, &y);
}
}
}
void ComputerGame(char board[ROW][COL], int row, int col)
{
printf("下面是电脑落子!\n");
int x = 0, y = 0;
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int if_full(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
//玩家赢返回‘*’
//电脑赢返回‘#’
//平局返回‘Q’
//可以继续下棋返回‘C’
char is_win(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
int row_flag = 1;//判断行是否相同,假设是相同
for (int j = 1; j < col; j++)
{
if (board[i][j] != board[i][0])
{
row_flag = 0;//有一个不同,则行不同
break;
}
}
if (row_flag == 1 && board[i][0] != ' ') return board[i][0];
}
for (int i = 0; i < col; i++)
{
int col_flag = 1;//判断列是否相同,假设是相同
for (int j = 1; j < row; j++)
{
if (board[j][i] != board[0][i])
{
col_flag = 0;//有一个不同,则列不同
break;
}
}
if (col_flag == 1 && board[0][i] != ' ') return board[0][i];
}
//判断正对角线是否相同
int zflag = 1;
for (int i = 1; i < row; i++)
{
if (board[i][i] != board[0][0]) {
zflag = 0;
break;
}
}
if (zflag == 1 && board[0][0] != ' ') return board[0][0];
//判断反对角线是否相同
int fflag = 1;
for (int i = 0; i < row; i++)
{
if (board[0][COL - 1] != board[i][COL - 1 - i])
{
fflag = 0;
break;
}
}
if (fflag == 1 && board[0][COL - 1] != ' ') return board[0][COL - 1];
//棋盘下满返回平局
if (if_full(board,ROW,COL) == 1) return 'Q';
//棋盘没下满、玩家或者电脑没有一方获胜,则可以继续下棋。
return 'C';
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*********************************\n");
printf("********** 1. play **********\n");
printf("********** 0. exit **********\n");
printf("*********************************\n");
}
void game()
{
char board[ROW][COL] = { 0 };
srand((unsigned int)time(NULL));
char ret = 0;
InitGame(board, ROW, COL);
DisPlayBoard(board, ROW, COL);
while (1)
{
PlayerGame(board, ROW, COL);
DisPlayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
break;
ComputerGame(board, ROW, COL);
DisPlayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
break;
}
if (ret == 'Q')
{
printf("达成平局啦!\n");
}
else if (ret == '*')
{
printf("恭喜公主获胜啦!\n");
}
else
{
printf("公主还要加油啦,电脑获胜咯\n");
}
}
int main()
{
int input = 0;
do {
menu();
scanf("%d", &input);
switch (input)
{
default:
printf("出错啦,公主请重新输入:\n");
break;
case 1:
printf("尊敬的公主,请开始你的游戏吧!\n");
game();
break;
case 0:
printf("游戏退出,公主拜拜!\n");
break;
}
} while (input);
return 0;
}
程序运行效果
希望这篇博客能对大家有一定的帮助,激发对C语言学习的兴趣。预告一下,下一篇将带大家实现扫雷小游戏!敬请期待吧!