建立文件
注:此项目使用的环境是Visual Studio 2019
step1
拟新建三个文件:
game.h:函数声明
game.c:函数实现
main.c:函数调用
step2
进行头文件的包含和常用库函数的引用
game.h中写入:
#pragma once
//作用:防止该头文件被多次引用
#define _CRT_SECURE_NO_WARNINGS
//防止scanf函数报错
#include <stdio.h>
#include <string.h>
//库函数的引用
game.c中写入:
#include "game.h"
//头文件的包含
main.c中写入:
#include "game.h"
//头文件的包含
函数调用的实现
1、主函数的实现
游戏逻辑尽量单独放入game() 函数中,主函数只进行调用
思路:
玩家在进行一局游戏后,有可能进行第二局游戏,这就要用到while循环结构;
玩家也可能退出游戏,可以使用switch - case语句供玩家进行选择
进入游戏后,需要提示玩家选择游戏还是退出,要写一个菜单
在game.h中进行声明:
void menu();
//函数不用返回值,只进行打印
game.c中实现menu():
void menu()
{
printf("***************************\n");
printf("**** 1.play 0.exit *****\n");
printf("***************************\n");
printf("Please Select:");
}
然后开始写主函数:
int main()
{
int input = 0;
do
{
menu();
scanf("%d", &input);
switch(input)
{
case 1:
game();
break;
case 0:
printf("bye bye!\n");
break;
default:
printf("Select Error, Please Select Again!\n");
break;
}
}while(input);
}
2、测试
为了测试,我们可以暂时让game()打印一段字符
game.h中进行函数的声明:
void game();
game.c中进行函数的实现:
void game()
{
printf("hello game!\n");
}
运行后输入1、输入2或3、输入0发现一切都正常运行,函数调用完成
3、使用while结构
上面使用do-while结构是因为,进入程序后至少要选择一次,契合do-while循环的特点
当然,使用while结构也是可以的,如下:
int main()
{
int input = 0;
int quit = 0;
while(!quit)
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
quit = 1;
printf("bye bye!\n");
break;
default:
printf("Select Error, Please Select Again!\n");
break;
}
}
}
增加了一个变量"quit",初始为0,!quit为真,进入循环,当选择0退出时,quit被更改为1(更改为其他非零也行),!quit就为假,循环终止
4、扩展
通过更改game()函数就可以实现不同的作用,也可以增添更多的case来增添更多的选项和功能
示例:
int main()
{
int input = 0;
do
{
menu();
scanf("%d", &input);
switch(input)
{
case 1:
game();
break;
case 2:
//函数2
break;
case 3:
//函数3
case 4:
//函数4
break;
//......
case 0:
printf("bye bye!\n");
break;
default:
printf("Select Error, Please Select Again!\n");
break;
}
}while(input);
}
同时需要更改menu()
注意:无论增添多少功能,都要将case 0设置为退出的选项,因为0可以同时控制case以及终止循环
game.c的实现
此次实现的五子棋是单机版本,用户1和用户2对弈
step1
在主函数中选择1后进入game()中,首先要定义一个二维数组当作棋盘
void game()
{
int board[ROW][COL];
}
为了方便更改棋盘的大小,我们使用define定义宏:
game.h中写入:
#define ROW 20
#define COL 20
step2
然后初始化棋盘,将初始棋盘内容都放成0:
void game()
{
int board[ROW][COL];
memset(board, 0, sizeof(board));
}
这里出现了一个函数memset
memset - 来自C库 - <memory.h> or <string.h>
函数声明为:void* memset(void* dest, int c, size_t count);
功能:从dest这个位置开始,之后的count(字节)的数据全部更改为c
示例:
int main()
{
int arr1[] = {0, 1, 2, 3, 4};
memset(arr1, 5, sizeof(arr1));
//更改后数组arr1的内容就全部变为5
return 0;
}
step3
棋盘有了,也初始化好了,下一步开始下棋
下棋之前要先打印棋盘给玩家看(需要一个打印棋盘的函数)
ShowBoard(int board[][COL], int row, int col);
玩家下棋(需要一个玩家下棋的函数)
PlayerMove(int board[][COL], int row, int col, int who);
每下一步棋,都需要判定输赢以及棋局是否结束:
int IsOver(int board[][COL], int row, int col)
如果棋局没有结束,将再次打印棋盘,然后玩家下棋…
重复上述步骤
step4
设定IsOver
函数的返回值
棋局的状态共有四种:
1、玩家1赢了(返回1)
2、玩家2赢了(返回2)
3、平局(棋盘已经占满且没有玩家赢)(返回3)
4、游戏继续(没有人赢也没有平局)(返回0)
game.h中定义宏:
#define PLAYER1 1
#define PLAYER2 2
#define DRAW 3
#define NEXT 0
step5
完成game():
void game()
{
int board[ROW][COL];
memset(board, 0, sizeof(board));
int result = NEXT;//判断棋局是否结束
do
{
ShowBoard(board, ROW, COL);//打印棋盘
PlayerMove(board, ROW, COL, PLAYER1);//玩家落子
result = IsOver(board, ROW, COL);
if (result)//判断棋局是否结束,如果棋局结束,IsOver将返回非0赋值给result,为真,进入if,break跳出循环
break;
ShowBoard(board, ROW, COL);
PlayerMove(board, ROW, COL, PLAYER2);
result = IsOver(board, ROW, COL);
if (result)//判断棋局是否结束
break;
} while (1);
//三种情况下出循环:
//1、玩家1赢了
//2、玩家2赢了
//3、平局了
switch (result)
{
case PLAYER1:
printf("恭喜玩家1,你已经赢了!\n");
break;
case PLAYER2:
printf("恭喜玩家2,你已经赢了!\n");
break;
case DRAW:
printf("平局\n");
break;
default:
break;//不可能进入此语句,因为result的值只有上面三种情况
}
ShowBoard(board, ROW, COL);
}
PlayerMove的实现
定义两个全局变量 x, y 记录最近一次落子的位置:
game.c中写入:
int x = 0;
int y = 0;
//注意:棋盘纵横坐标的编号从1开始,则棋子的数组下标为(x-1, y-1)
PlayerMove
第一步:打印提示语
第二步:scanf录入数据
第三步:将数据对应的二维数组的元素进行更改
void PlayerMove(int board[][COL], int row, int col, int who)
{
printf("Player[%d] Please Enter Your Pos:", who);
scanf("%d %d", &x, &y);
board[x - 1][y - 1] = who;//who即为PLAYER1或PLAYER2
}
但是玩家输入的坐标有可能是负数或者是输入的位置已经有棋子,即为非法输入,需要使用if-else语句进行筛选,剔除不合法的坐标,并使用while语句让玩家再次输入:
void PlayerMove(int board[][COL], int row, int col, int who)
{
while (1)
{
printf("Player[%d] Please Enter Your Pos:", who);
scanf("%d %d", &x, &y);
if (x<1 || x>row || y<1 || y>col)//输入的坐标非法,不在棋盘内
{
printf("Pos isn't Right Pos!\n");
continue;
}
else if (board[x-1][y-1] != 0)//输入的坐标已被占用
{
printf("Pos Is Occupied!\n");
continue;
}
else
{
board[x - 1][y - 1] = who;
break;
}
}
}
IsOver的实现
以最近一次落子的位置为中心,五子连珠共有四种情况:
1、左右连珠
2、上下连珠
3、左上 - 右下连珠
4、左下 - 右上连珠
这四条线上,有任意一条线相同棋子连续超过4个,就代表有人赢了
定义八个方向,game.h中写入:
enum Dir
{
LEFT,
RIGHT,
UP,
DOWN,
LEFT_UP,
LEFT_DOWN,
RIGHT_UP,
RIGHT_DOWN
};
定义一个函数ChessCount()
,它可以计算某一特定方向上连珠棋子的个数,那么IsOver()
就可以写成:
int IsOver(int board[][COL], int row, int col)
{
int count1 = ChessCount(board, row, col, LEFT) + ChessCount(board, row, col, RIGHT) + 1;//左右方向
//+1是因为除了各个方向的棋子,最近一次落子的棋子也要算上
int count2 = ChessCount(board, row, col, UP) + ChessCount(board, row, col, DOWN) + 1;//上下方向
int count3 = ChessCount(board, row, col, LEFT_UP) + ChessCount(board, row, col, RIGHT_DOWN) + 1;//左上-右下方向
int count4 = ChessCount(board, row, col, LEFT_DOWN) + ChessCount(board, row, col, RIGHT_UP) + 1;//左下-右上方向
if (count1 > 4 || count2 > 4 || count3 > 4 || count4 > 4)//进入此if说明有人赢了
{
return board[x - 1][y - 1];//这里返回的就是最近一次的落子(1或者2)
}
//到这里有两种情况:
//1、平局
//2、非平局,继续下棋
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == 0)//棋盘还有位置,没有平局
{
return NEXT;//返回0
}
}
}
return DRAW;//返回3
}
ChessCount的实现
该函数用于计算某一方向上连珠棋子的个数
思路:定义一个计数器count,以最近一次落子的位置为起点,往某一指定方向移动一格,使用if-else语句判定是否为同一种棋子,如果是则再往下移动一格,重复上述步骤,进行循环;如果不是则break跳出循环,如下:
int ChessCount(int board[][COL], int row, int col, enum Dir d)//计算各个方向连珠棋子的个数
{
int _x = x - 1;
int _y = y - 1;
int count = 0;
while (1)
{
switch (d)
{
case LEFT:
_y--;
break;
case RIGHT:
_y++;
break;
case UP:
_x--;
break;
case DOWN:
_x++;
break;
case LEFT_UP:
_x--;
_y--;
break;
case LEFT_DOWN:
_x++;
_y--;
break;
case RIGHT_UP:
_x--;
_y++;
break;
case RIGHT_DOWN:
_x++;
_y++;
break;
default:
break;
}
if (_x<0 || _x>row - 1 || _y<0 || _y>col - 1)//跑出棋盘,不合法
{
break;
}
if (board[x - 1][y - 1] == board[_x][_y])//棋子相等
count++;
else
{
break;
}
}
return count;
}
ShowBoard的实现:
显示棋盘
思路:打印二维数组,使用双层嵌套for循环,如下:
void ShowBoard(int board[][COL], int row, int col)
{
printf("\x1b[H\x1b[2J");//刷屏
printf(" ");
int i = 0;
for (i = 0; i < col; i++)
{
printf("%3d", i + 1);
}
printf("\n");
for (i = 0; i < row; i++)
{
printf("%2d ", i + 1);
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == 0)
{
printf(" . ");
}
else if (board[i][j] == 1)
{
printf(" * ");
}
else
{
printf(" # ");//这三种符号可以自定义
}
}
printf("\n");
}
}