东风一条龙——三子棋游戏/多子琪

1.前言

三子棋游戏,想必大家都有玩过吧。在我们这里,还有一个响亮的名字——东风一条龙。玩法其实很简单,玩家双方轮流落子,当一方棋子连成一条线,即为胜出。那么这篇博客就来详细介绍一下如何用C语言实现一个三子棋盘游戏

2.模块化编程

当我们在写一些项目时,为了维护和管理会把不同模块的代码放在不同的.c源文件里。在.h文件里会写宏定义和函数的声明还有各种头文件的包含。然后再在各个.c源文件里包含我们这个头文件就行即#include"XXX.h"。使用模块化编程能够提高代码的可阅读性、可维护性和可移植性等

3.大致逻辑与思路

  1. 显示开始游戏界面,包含开始、退出还有选错反馈

  2. 初始化棋盘,利用二维数组模拟一个棋盘,对这些二维数组中的元素初始化

  3. 显示棋盘,对二维数组适当装饰,然后显示到屏幕上

  4. 双方下棋,玩家移动还有电脑移动

  5. 结束判断,结束有三种情况,玩家赢、输还有平局,还有一种状态就是双方继续下棋的状态

  6. 保存数据,记录总局数、获胜数还有平局数,计算出胜率,显示屏幕上,然后保存到文件

4.代码实现

①.开始游戏界面

1. 创建颜色函数(这个就是装饰函数,对游戏逻辑实现无影响)

int color(int c)//设置文字和背景颜色
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
	return 0;
}

2.写一个菜单menu

void menu()
{
	color(3);//蓝色
	printf("+----------三子棋----------+\n");
	printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n");
	printf("|★★★★   1.开始 0.退出  ★★★★|\n");
	printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n"); 
	printf("|* = 玩家 -------- # = 电脑|\n");
	printf("+--------------------------+\n");
}

看一下效果:

3.为了防止用户没有正确选择,我们在主函数里面利用do...while循环+switch语句来进行循环判断

int input=0;
do
{
    menu();
    printf("请输入你的选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
	    game();
	    break;
    case 0:
	    printf("退出成功!\n");
	    break;
    default:
	    printf("选择错误,请重新选择\n");
	    break;
    }

}while(input);

②. 初始化棋盘

1.先定义宏常量

#define ROW 3
#define COL 3

2.先创建一个二维数组

	char board[ROW][COL];
	InitBoard(board, ROW, COL);

3. 再写一个初始化函数,在主游戏函数中调用

//初始化棋盘
void InitBoard(char(*board)[COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row;i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

③. 显示棋盘

棋盘显示成代码注释中的样子,二维数组每个元素的内容是空格

//显示棋盘
/*
+-----+
| | | |
+-----+
| | | |
+-----+
| | | |
+-----+
*/
void ShowBoard(char(*board)[COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 2 * row + 1; i++)
	{
		printf("\t");
		for (j = 0; j < 2 * col + 1; j++)
		{
			if (i % 2 == 0)
			{
				if (j == 0 || j == 2 * col)
				{
					printf("+");
				}
				else
				{
					printf("-");
				}
			}
			else
			{
				if (j % 2 == 0)
				{
					printf("|");
				}
				else
				{
					printf("%c", board[(i - 1) / 2][(j - 1) / 2]);
				}
			}

		}
		printf("\n");
	}
}

效果

④. 双方下棋

1.玩家移动,在这里我们利用九键与九宫格棋盘相对应,不需要输入坐标,这样更方便一些

先写一个对应函数turn_xy(int*x, int*y)。在这里要传变量的地址过来哦

void turn_sy(int n, int* x, int* y)
{
	switch (n)
	{
	case 1:
		*x = 2;
		*y = 0;
		break;
	case 2:
		*x = 2;
		*y = 1;
		break;
	case 3:
		*x = 2;
		*y = 2;
		break;
	case 4:
		*x = 1;
		*y = 0;
		break;
	case 5:
		*x = 1;
		*y = 1;
		break;
	case 6:
		*x = 1;
		*y = 2;
		break;
	case 7:
		*x = 0;
		*y = 0;
		break;
	case 8:
		*x = 0;
		*y = 1;
		break;
	case 9:
		*x = 0;
		*y = 2;
		break;
	}
}

然后再写一个玩家落子的函数,对玩家输入的数字先进行判断,如果数字合法再找到该数字对应的坐标

//玩家落子
void PlayerMove(char(*board)[COL], int row, int col)
{
	int n = 0;
	int x = 0;
	int y = 0;
	printf("玩家落子:>");
	while (1)
	{
		scanf("%d", &n);
		if (n > 0 && n < 10)
		{
			turn_sy(n, &x, &y);
			if (board[x][y] == ' ')
			{
				board[x][y] = '*';
				break;
			}
			else
			{
				printf("该坐标已被占用,请重新落子\n");
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入\n");
		}
	}
}

2.电脑落子

写一个电脑下棋的函数,在这里我们让电脑随机下棋,用随机数函数产生两个0到2的随机数即为电脑下棋的坐标,如果下棋位子不合法,就重新生成随机数(只不过现在写得电脑比较弱智)

//电脑落子
void ComputerMove(char(*board)[COL], int row, int col)
{
	while (1)
	{
		int x = rand() % 3;
		int y = rand() % 3;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}
⑤. 结束判断

玩家和电脑每次下完棋后,都要检查棋盘的状态,棋盘有四种状态,我们用四种符号表示,'*'表示玩家胜、'#'表示电脑胜,'C'表示游戏继续,'Q'表示平局'。以此写出一个检查函数返回值为char类型

//判断棋盘状态(只针对3*3的棋盘)
//返回*玩家赢了
//返回#电脑赢了
//返回C继续
//返回Q平局
char CheckBoard(char(*board)[COL], int row, int col)
{
	int i = 0;
	int j = 0;

	//判断行
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] &&
			board[i][0] == board[i][2] && 
			board[i][0] != ' ')
		{
			return board[i][0];
		}
	}
	//判断列
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] &&
			board[0][j] == board[2][j] &&
			board[0][j] != ' ')
		{
			return board[0][j];
		}
	}
	//判断左对角线
	if (board[0][0] == board[1][1] &&
		board[0][0] == board[2][2] &&
		board[0][0] != ' ')
	{
		return board[0][0];
	}
	//判断右对角线
	if (board[1][1] == board[0][2] &&
		board[1][1] == board[2][0] &&
		board[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断是否有空格
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
			{
				return 'C';
			}
		}
	}
	return 'Q';
}

⑥. 保存数据

为了增加游戏的丰富性,我们也可以把每一把的结果记录下来,即总局数、获胜数、平局数,然后计算出胜率,显示到屏幕上,在这里我们就写main函数里边创建一个存放三个数的数组来存放总局数、获胜数、平局数。在每一把游戏开始前先载入之前的游戏数据,在游戏退出时,在保存游戏数据。但是在第一次开始游戏的时候我们要进行判断是否有游戏数据

这里我们就写两个函数

1.保存数据函数

//保存数据
void SaveGame(int* arr)
{
	FILE* pf = fopen("data.txtx", "w");
	if (pf == NULL)
	{
		perror("SaveGame->fopen");
		return;
	}
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		fwrite(arr + i, 4, 1, pf);
	}
	fclose(pf);
	pf = NULL;
}

2.加载游戏数据

int LoadGame(int* arr)
{
	FILE* pf = fopen("data.txtx", "r");
	if (pf == NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		fread(arr + i, 4, 1, pf);
	}
	fclose(pf);
	pf = NULL;
	return 1;
}
⑦.游戏主函数

我们把以上六步写进一个游戏主函数中,利用while循环使“玩家落子、显示棋盘、棋盘状态判断和电脑落子、显示棋盘、棋盘状态判断”循环起来,直到结束

void game(int*arr)
{
	char ret = 0;
	char board[ROW][COL];
	InitBoard(board, ROW, COL);
	while (1)
	{
		ShowBoard(board, ROW, COL);
		PlayerMove(board, ROW, COL);
		ShowBoard(board, ROW, COL);
		ret = CheckBoard(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		ComputerMove(board, ROW, COL);
		ShowBoard(board, ROW, COL);
		ret=CheckBoard(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	arr[0]++;
	if (ret == '*')
	{
		printf("你赢了!\n");
		arr[1]++;
	}
	else if (ret == '#')
	{
		printf("你输了\n");
	}
	else
	{
		printf("平局\n");
		arr[2]++;
	}

}
⑧.main函数

主函数里边调用srand函数产生随机数,创建一个存放总局数、获胜数、平局数这三个数的数组,还有利用do...while循环实现玩一把不过瘾还能接着玩的逻辑

int main()
{
	srand((unsigned)time(NULL));
	int input = 0;
	int arr[3] = { 0 };
	int ret = LoadGame(arr);
	do
	{
		menu();
		if (ret)
			printf("总局数:%d\t获胜数:%d\n平局数:%d\t胜率:%.2lf%%\n", arr[0], arr[1],
				arr[2], ((double)arr[1] * 100 / arr[0]));
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game(arr);
			break;
		case 0:
			SaveGame(arr);
			printf("退出成功!\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}

	} while (input);
	
	return 0;
}

5.模块化代码实现

①.test.c

测试游戏的逻辑。

#include"game.h"


int color(int c)//设置文字和背景颜色
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
	return 0;
}

void menu()
{
	color(3);//蓝色
	printf("+----------三子棋----------+\n");
	printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n");
	printf("|★★★★   1.开始 0.退出  ★★★★|\n");
	printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n"); 
	printf("|* = 玩家 -------- # = 电脑|\n");
	printf("+--------------------------+\n");
}

void game(int*arr)
{
	char ret = 0;
	char board[ROW][COL];
	InitBoard(board, ROW, COL);
	while (1)
	{
		ShowBoard(board, ROW, COL);
		PlayerMove(board, ROW, COL);
		ShowBoard(board, ROW, COL);
		ret = CheckBoard(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		ComputerMove(board, ROW, COL);
		ShowBoard(board, ROW, COL);
		ret=CheckBoard(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	arr[0]++;
	if (ret == '*')
	{
		printf("你赢了!\n");
		arr[1]++;
	}
	else if (ret == '#')
	{
		printf("你输了\n");
	}
	else
	{
		printf("平局\n");
		arr[2]++;
	}

}



int main()
{
	srand((unsigned)time(NULL));
	int input = 0;
	int arr[3] = { 0 };
	int ret = LoadGame(arr);
	do
	{
		menu();
		if (ret)
			printf("总局数:%d\t获胜数:%d\n平局数:%d\t胜率:%.2lf%%\n", arr[0], arr[1],
				arr[2], ((double)arr[1] * 100 / arr[0]));
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game(arr);
			break;
		case 0:
			SaveGame(arr);
			printf("退出成功!\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}

	} while (input);
	
	return 0;
}
②.game.h

关于游戏包含的函数声明,符号声明头文件的包含以及宏定义。

#pragma once
#include<stdio.h>
#include<windows.h>
#include<time.h>

#define ROW 3
#define COL 3


//初始化棋盘
void InitBoard(char(*board)[COL], int row, int col);

//显示棋盘
void ShowBoard(char(*board)[COL], int row, int col);

//玩家落子
void PlayerMove(char(*board)[COL], int row, int col);

//电脑落子
void ComputerMove(char(*board)[COL], int row, int col);

//判断棋盘状态
char CheckBoard(char(*board)[COL], int row, int col);

//保存数据
void SaveGame(int*arr);

//加载数据
int LoadGame(int* arr);

③.game.c 

游戏和相关函数实现

#include"game.h"



//初始化棋盘
void InitBoard(char(*board)[COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row;i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}


//显示棋盘
/*
+-----+
| | | |
+-----+
| | | |
+-----+
| | | |
+-----+
*/
void ShowBoard(char(*board)[COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 2 * row + 1; i++)
	{
		printf("\t");
		for (j = 0; j < 2 * col + 1; j++)
		{
			if (i % 2 == 0)
			{
				if (j == 0 || j == 2 * col)
				{
					printf("+");
				}
				else
				{
					printf("-");
				}
			}
			else
			{
				if (j % 2 == 0)
				{
					printf("|");
				}
				else
				{
					printf("%c", board[(i - 1) / 2][(j - 1) / 2]);
				}
			}

		}
		printf("\n");
	}
}


void turn_sy(int n, int* x, int* y)
{
	switch (n)
	{
	case 1:
		*x = 2;
		*y = 0;
		break;
	case 2:
		*x = 2;
		*y = 1;
		break;
	case 3:
		*x = 2;
		*y = 2;
		break;
	case 4:
		*x = 1;
		*y = 0;
		break;
	case 5:
		*x = 1;
		*y = 1;
		break;
	case 6:
		*x = 1;
		*y = 2;
		break;
	case 7:
		*x = 0;
		*y = 0;
		break;
	case 8:
		*x = 0;
		*y = 1;
		break;
	case 9:
		*x = 0;
		*y = 2;
		break;
	}
}

//玩家落子
void PlayerMove(char(*board)[COL], int row, int col)
{
	int n = 0;
	int x = 0;
	int y = 0;
	printf("玩家落子:>");
	while (1)
	{
		scanf("%d", &n);
		if (n > 0 && n < 10)
		{
			turn_sy(n, &x, &y);
			if (board[x][y] == ' ')
			{
				board[x][y] = '*';
				break;
			}
			else
			{
				printf("该坐标已被占用,请重新落子\n");
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入\n");
		}
	}
}


//电脑落子
void ComputerMove(char(*board)[COL], int row, int col)
{
	while (1)
	{
		int x = rand() % 3;
		int y = rand() % 3;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}



//判断棋盘状态(只针对3*3的棋盘)
//返回*玩家赢了
//返回#电脑赢了
//返回C继续
//返回Q平局
char CheckBoard(char(*board)[COL], int row, int col)
{
	int i = 0;
	int j = 0;

	//判断行
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] &&
			board[i][0] == board[i][2] && 
			board[i][0] != ' ')
		{
			return board[i][0];
		}
	}
	//判断列
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] &&
			board[0][j] == board[2][j] &&
			board[0][j] != ' ')
		{
			return board[0][j];
		}
	}
	//判断左对角线
	if (board[0][0] == board[1][1] &&
		board[0][0] == board[2][2] &&
		board[0][0] != ' ')
	{
		return board[0][0];
	}
	//判断右对角线
	if (board[1][1] == board[0][2] &&
		board[1][1] == board[2][0] &&
		board[1][1] != ' ')
	{
		return board[1][1];
	}
	//判断是否有空格
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
			{
				return 'C';
			}
		}
	}
	return 'Q';
}

//保存数据
void SaveGame(int* arr)
{
	FILE* pf = fopen("data.txtx", "w");
	if (pf == NULL)
	{
		perror("SaveGame->fopen");
		return;
	}
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		fwrite(arr + i, 4, 1, pf);
	}
	fclose(pf);
	pf = NULL;
}

int LoadGame(int* arr)
{
	FILE* pf = fopen("data.txtx", "r");
	if (pf == NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		fread(arr + i, 4, 1, pf);
	}
	fclose(pf);
	pf = NULL;
	return 1;
}

5.效果展示

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值