前言
想必大家在学习编程语言的道路上的成就感很大一部分是自己实现一个游戏吧。接下来这篇博客将和大家分享如何实现三子棋小游戏,游戏虽小,功能俱全。准备好,上车:
一.代码文件的建立以及对头文件的处理和函数的声明和定义
作为一个不仅仅只是简单的输入和输出的游戏代码文件的建立,所以这里我们将采用分文件来实现代码。也就是按照不同的作用来对代码进行分区。
首先,我们分别建立三个文件,其中两个是以.c结尾,一个是以.h结尾。
其中:
1.以.h结尾的文件用来写入所有的头文件以及函数的声明。
2.test.c是用来写入游戏的整体代码的框架。
3.game.c是用来写入游戏的具体函数和功能的实现。
当然,我这里以test.c,game.h和game.c并不是固定的,只是方便自己或者代码的测试人员去快速分辨各个代码文件的功能。
这样做的好处:
1、方便代码复用: 模块化的代码可以很方便的迁移到其他项目中去,改写模块比重写模块更快;
2、方便分工合作: 各个功能模块分成多个文件同时编辑,可以有效的提高团队开发的分工协作效率;
3、方便后续维护: 项目源码交接时,不管是客户还是同事,对于划分明确的文件是不会排斥的;
4、保证了库支持: 个人函数库都是由c文件产生库,采用“库文件+h文件”的形式,可以实现快速编译和保护源码的效果。
二.游戏界面和进入游戏的搭建
这里首先需要我们打印一个游戏的建议菜单栏目,以便提示玩家选择,若玩家选择<1>则进入游戏;若玩家选择<0>则退出游戏;若玩家选择除<0><1>以外的数字我们将认定为选择错误。这其中我们要用到do–while循环来实现让玩家进行选择,再使用switch–case来判断玩家输入的值是否正确。并且我们需要调用一个菜单menu()来打印游戏界面。
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;
}
三.棋盘初始化
这是我们需要先定义一个实现游戏的主体函数
void game()
为了后续修改的方便,我们将所需打印的棋盘大小用一个宏来定义
#define ROW 3
#define COL 3
然后通过主函数调用void game ()
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择输入操作数:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("三子棋游戏开始………………\n");
game();
break;
case 0:
printf("退出游戏…………………\n");
break;
default:
printf("选择错误,请重新选择……………………\n");
break;
}
} while (input);
return 0;
}
棋盘的初始化其实就是给每一个需要展示的内容赋值为空格。
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{
memset(&board[0][0], ' ', row * col * sizeof(board[0][0]));
}
但是要记得打印数组前以及接下来每一步要定义新的内容时,都需要在头文件中进行声明。
void InitBoard(char board[ROW][COL], int row, int col);
四.打印棋盘
我们要知道打印一个棋盘的实质就是打印一个二维数组,再加上一些分割线。对吧?
但是,这时我们得分析每一个位置需要放置什么内容。
这里我们就简易的设计成这样
也就是说在一行时,我们需要打印三个空格,再打印|,我们不妨把三个空格和一个|看成一组,最后一组只打印空格,第二行我们需要打印_ _ _ 和|,同理,我们不妨把三个_和|看成一组,最后一组只打印三个_。
再把目光放在这整个棋盘上面,我们不妨把前两行看成一组,最后一组只有空格没有_。
如下图
代码示例:
//打印棋盘———实质上就是打印两个数组,再加上一些分割线
void DisplayBoard(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++)
{
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);
整体的逻辑:
只要玩家输入的坐标在所给定的范围内,则将玩家输入的位置用*替换掉
代码示例:
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家开始下棋\n");
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);
电脑下棋这时就不是有我们来操控了喔,所以这是我们只能借助一个时间戳的函数来帮助我们随机生成一个坐标。
srand((unsigned int)time(NULL));
并且需要加上头文件
#include<stdlib.h>
#include<time.h>
代码示例:
//电脑下棋
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;
}
}
}
七.判断输赢
判断输赢这个步骤在每次玩家和电脑下完棋都要进行判断
这里我们可以将每次的判断结果总结为4种情况
1.玩家赢——返回‘ * ’
2.电脑赢——返回‘ # ’
3.平局——返回‘ Q ’
4.游戏继续——返回‘ C ’
这里需要分析一下,只有三个连着三个是一种棋子就算赢。
所以,当一行全是*或者一行全是#,也可以是一列,也可以是对角线相同。
这是我们只需要判断这些坐标上的元素是否相同就解决了。
再判断游戏是平局的条件:
如果是平局就意味着两个人都没有三个连着的棋子并且此时棋盘已经满了。
所以,我们只需要判断棋盘是否还有空格,若有空格则返回0,表示不是平局;若没有空格则返回1,表示是平局。
剩下的一种情况就是游戏继续。
代码示例:
//判断输赢
// 玩家赢--‘*’
// 电脑赢--‘#’
// 平局--‘Q’
//游戏继续--‘C’
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 < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char IsWin(char board[ROW][COL], int row, int 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] && board[0][2] == board[1][1] && board[2][0]!= ' ')
return board[0][0];
//判断是否平局
if (IsFull(board, row, col))
{
return 'Q';
}
//游戏继续
return 'C';
}
这所有的操作必须包含在主函数所调用的game()中
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
char ret = 0;
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);
if (ret != 'C')
{
break;
}
}
if('*'==ret)
{
printf("玩家赢\n");
}
if ('#' == ret)
{
printf("电脑赢\n");
}
if ('Q' == ret)
{
printf("平局\n");
}
else
{
printf("游戏继续\n");
}
}
八.完整代码展示
game.h部分
#pragma once
#include<stdio.h>
#include<string.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 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);
game.c部分
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{
memset(&board[0][0], ' ', row * col * sizeof(board[0][0]));
}
//打印棋盘———实质上就是打印两个数组,再加上一些分割线
void DisplayBoard(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++)
{
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("请输入一个坐标:>");
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;
printf("电脑下棋>\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//判断输赢
// 玩家赢--‘*’
// 电脑赢--‘#’
// 平局--‘Q’
//游戏继续--‘C’
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 < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char IsWin(char board[ROW][COL], int row, int 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 (IsFull(board, row, col))
{
return 'Q';
}
//游戏继续
return 'C';
}
test.c部分
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("*****************************************\n");
printf("*****************************************\n");
printf("*********** 1.play ****************\n");
printf("*********** 0.exit ****************\n");
printf("*****************************************\n");
printf("*****************************************\n");
}
void game()
{
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
char ret = 0;
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);
if (ret != 'C')
{
break;
}
}
if('*'==ret)
{
printf("玩家赢\n");
}
if ('#' == ret)
{
printf("电脑赢\n");
}
if ('Q' == ret)
{
printf("平局\n");
}
else
{
printf("游戏继续\n");
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择输入操作数:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("三子棋游戏开始………………\n");
game();
break;
case 0:
printf("退出游戏…………………\n");
break;
default:
printf("选择错误,请重新选择……………………\n");
break;
}
} while (input);
return 0;
}
九.总结
实现一个三子棋游戏其实是对我们前面所学的C语言内容的检测,其中主要包含了循环、分支循环、数组的遍历、头文件的声明、函数的构建和调用。并且培养我们的编程思维和代码能力,总之,学完这些能自行实现三子棋还是很有必要的。这也是本小白第一次实现小游戏,这其中肯定有很多不足的地方,还希望能得到各位大佬的指正。