C实现简版五子棋

建立文件

注:此项目使用的环境是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");
	}
}

完!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值