IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言实现三子棋小游戏---格局打开版( -> 正文阅读

[C++知识库]C语言实现三子棋小游戏---格局打开版(

1.项目目标

使用C语言在控制台窗口实现不同模式下的三子棋游戏
1.简单模式下电脑任意下子
2.复杂模式下电脑能对玩家获胜进行阻拦

2.游戏构思

存储结构:二维数组
交互方式:玩家输入坐标位置
我们将项目分成 game.h,game.c,test.c 来让项目更清晰

功能模块:
1.游戏规则 (普通的printf实现)
2.游戏菜单 (do while,switch case)
3.选择模式 (函数实现,简单返回1,复杂返回2)
4.选择先手 (函数实现,玩家返回1,电脑返回2)
5.初始棋盘 (二维数组,for循环)
6.打印棋盘 (二维数组,for循环)
7.玩家下棋 (二维数组 ,scanf)
8.电脑下棋 (随机数生成,取胜,拦截)
9.判断输赢 (函数实现,返回不同字符,代表不同结果)

各个模块都较容易实现,其中复杂模式电脑下棋的取胜与拦截的思路如下:

电脑再下一子取得胜利时,应优先取胜,通过扫描棋盘,记录行,列,对角线已有两子时返回能连成线的坐标值(通过指针实现)。

如再下一子不能取得胜利时,就对棋手的取胜进行阻拦,用上述的函数,添加一个棋子字符参数,通过改变参数,实现记录已棋手已有两子时返回能连成线的坐标值。

除些之外的情况可通过随机数,随机生成电脑棋子的位置。

不同功能模块的实现还会用到其他的工具函数,将会在下面进行具体介绍。

相信看到这里大家应该有了思路,现在来看一下具体的实现。

3.功能实现

将主要模块的函数声明和要用到的系统头文件写在game.h中

#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 3
#define COL 3
#define KEY 2
//选择模式
int Model();
//选择先手
int Offensive();
//初始化棋盘
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,int model);
//判断输赢
/*返回
* ‘*’---玩家嬴
* ‘#’---电脑嬴
* ‘D’---平局
* ‘C’---继续
*/
char VictoryJudgment(char board[ROW][COL], int row, int col);

用宏定义#define 定义的不仅仅是常量,更是我们打开的格局,这样做更便于我们更改常量,不会在想改动时修改大量代码。

在game.c中我们实现具体的功能模块
要注意在文件开头引用game.h头文件

选择模块实现

void model_choose()
{
	printf("***************************************************************\n");
	printf("*****                    1.Easy Mode                      *****\n");
	printf("*****                    2.Complex mod                    *****\n");
	printf("***************************************************************\n");
}
int Model()
{
	char choose = '0';
	do {
		model_choose();
		printf("Please select:>");
		scanf("%c", &choose);
		getchar();
		switch (choose)
		{
		case '1':
			return 1;
		case '2':
			return 2;
		default:
			printf("Selection error\n");
			break;
		}

	} while (choose!='1' && choose != '2');
	
}

选择先手模块与上述相同这里不做赘述
打印游戏规则相信大家都会,只要会打helloworld的都会打,初始化棋盘就是将二维数组中每个字符元素置成空格此处也省略。

打印棋盘实现如下:

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i, 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");
		}
	}
}

打印效果如下图
在这里插入图片描述
如果将行和列的常量改变,此代码依旧能打印出相应的棋盘。
将ROW,和COL改为5后
在这里插入图片描述
不过这应该就不算三子棋了(手动滑稽)

下面为玩家下棋模块:

void PlayerMove(char board[ROW][COL], int row, int col)
{
	printf("Players:>\n");
	int x, y = 0;
	while (1)
	{
		scanf("%d %d", &x, &y);
		getchar();//防止死循环
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}
			else
			{
				printf("This coordinate is occupied, please re-enter\n");
			}
		}
		else
		{
			printf("Illegal coordinates, please re-enter!\n");
		}

	}
}

getchar();可以在不小心输入非法字符比如’a’时防止程序出现死循环。

下面着重讲一下电脑下棋的部分,先看一下代码

void ComputerMove(char board[ROW][COL], int row, int col,int model)
{
	int x, y = 0;
	printf("Computer:>\n");
	if (1 == model)//简单模式
	{
		while (1)
		{
			x = rand() % ROW;
			y = rand() % COL;
			if (board[x][y] == ' ')
			{
				board[x][y] = '#';
				break;
			}
		}
	}
	else//复杂模式
	{
	
		int* a = &x;
		int* b = &y;
		char c = '0';
		while (1)
		{   //优先取胜
			c = '#';
			//行取胜
			if (1 == LineWin(board, row, col, a, b,c))
			{
				board[*a][*b] = '#';
				break;
			}
			//列取胜
			if (1 == ColumnWin(board, row, col, a, b,c))
			{
				board[*a][*b] = '#';
				break;
			}
			//对角线取胜
			if (1 == DiagonalWin(board, row, col, a, b,c))
			{
				board[*a][*b] = '#';
				break;
			}
			//优先拦截
			c = '*';
			//行拦截
			if (1 == LineWin(board, row, col, a, b, c))
			{
				board[*a][*b] = '#';
				break;
			}
			//列拦截
			if (1 == ColumnWin(board, row, col, a, b, c))
			{
				board[*a][*b] = '#';
				break;
			}
			//对角线拦截
			if (1 == DiagonalWin(board, row, col, a, b, c))
			{
				board[*a][*b] = '#';
				printf("%d %d@\n", *a, *b);
				break;
			}
			x = rand() % ROW;
			y = rand() % COL;
			if (board[x][y] == ' ')
			{
				board[x][y] = '#';
				break;
			}
		}
	
	}
	
	
}

其中有一些还未实现的函数,思路我前面已经讲过,现在看一下具体过程。

int LineWin(char board[ROW][COL], int row, int col, int* a, int* b,char c )
{
	int i,j = 0;
	int count = 0;
	for (i = 0; i < row; i++)
	{
		count = 0;
		*a = -1;
		*b = -1;
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == c)
			{
				count++;
			}
			if (board[i][j] == ' ')
			{
				*a = i;
				*b = j;
			}
				
		}
		if (count == KEY && *a != -1 && *b != -1)
		{
			return 1;
		}
	}
	return 0;
}

ColumnWin函数与LineWin函数原理相同,LineWin函数是想求每一行可能取胜的位置,ColumnWin函数则是求按列的。KEY值定为2是因为对面都下两个子啦,再不拦截不就输了嘛- -!

下面是对角线判定函数:

int DiagonalWin(char board[ROW][COL], int row, int col, int* a, int* b,char c)
{
	int i = 0;
	int count = 0;
	*a = -1;
	*b = -1;
	for (i = 0; i < row; i++)
	{
		if (board[i][i] == c)
		{
			count++;
		}
		if (board[i][i] == ' ')
		{
			*a = i;
			*b = i;
		}
		if (count == KEY && *a != -1 && *b != -1)
		{
			return 1;
		}
	}
	count = 0;
	*a = -1;
	*b = -1;
	for (i = 0; i < row; i++)
	{
		if( board[i][COL - 1 - i] == c)
		{
			count++;
		}
		if (board[i][COL - 1 - i] == ' ')
		{
			*a = i;
			*b = COL-1-i;
			
		}
		if (count == KEY && *a != -1 && *b != -1)
		{
			return 1;
		}
	}
	return 0;
}

这种如果一时看不出来,拿张纸把对角线坐标写出来很快就能知道规律了,看着可能不如简单写法的代码简练,但是家人们要把格局打开,写死了就没意思啦。

判断输赢模块也是分行、列、对角线判断的思路。

char VictoryJudgment(char board[ROW][COL], int row, int col)
{
	int i,j = 0;
	int flag = 0;
	//判断行
	for (i = 0; i < row; i++)
	{
		flag = 0;
		for (j = 0; j < col; j++)
		{
			if (board[i][0] != board[i][j]||board[i][j]==' ')
				flag = 1;
		}
		if (flag == 0)
		{
			return board[i][0];
		}
	}
	flag = 0;
	//判断列
	for (i = 0; i < col; i++)
	{
		flag = 0;
		for (j = 0; j < row; j++)
		{
			if (board[0][i] != board[j][i] || board[j][i] == ' ')
				flag = 1;
		}
		if (flag == 0)
		{
			return board[0][i];
		}
	}
	flag = 0;
	//判断对角线
	for (i = 0; i < row; i++)
	{
		if (board[0][0] != board[i][i] || board[i][i] == ' ')
			flag = 1;
		}
	if (flag == 0)
	{
		return board[0][0];
	}
	flag = 0;
	for (i = 0; i < row; i++)
	{
		if (board[0][COL - 1] != board[i][COL - 1 - i] || board[i][COL-1-i] == ' ')
			flag = 1;
	}
	if (flag == 0)
	{
		return board[0][COL-1];
	}
	//判断平局
	if (1 == is_full(board, row, col))
	{
		return 'D';
	}
	//继续
	return 'C';
}

is_full函数就是看数组中是否还有空格,有就是棋盘不满,继续,否则就是棋盘已满,平局。

各个模块都实现完了,来看看有main函数的test.c部分(其实我写的时候,是看要用到什么函数再去game.c实现的,缺什么补什么,但是我觉得换种方式呈现给大家更好一点儿)。

void test()
{
	char input = '0';
	char choose = '0';
	char attacker = '0';
	rule();
	do
	{
		menu();
		printf("Please select:>");
		scanf("%c", &input);
		getchar();
		switch (input)
		{
		case '1':
			choose = Model();
			attacker = Offensive();
			game(choose,attacker);
			break;
		case '0':
			printf("Exit the game\n");
			break;
		default:
			printf("Selection error\n");
			break;
		}
	} while (input != '0');
}
int main()
{
	srand((unsigned int)time(NULL));
	test();
	return 0;
}

在这个菜单框架里没有实现game();函数,因为为了让程序清晰,把它移出去了。

void game(int choose,int attacker)
{
	//用字符的二维数组存储,玩家下棋是"*",电脑下棋是"#"
	char board[ROW][COL]={0};//棋盘初始数组应该是空格
	InitBoard(board,ROW,COL);//初始化棋盘
	//打印棋盘
	DisplayBoard(board,ROW,COL);
	printf("Your chess piece is \"*\"\n");
	//下棋
	char ret = 0;
	while (1)
	{
		//棋手先下
		if (1==attacker )
		{
			PlayerMove(board, ROW, COL);
			DisplayBoard(board, ROW, COL);
			ret = VictoryJudgment(board, ROW, COL);
			if (ret != 'C')
			{
				break;
			}
			ComputerMove(board, ROW, COL,choose);
			DisplayBoard(board, ROW, COL);
			ret = VictoryJudgment(board, ROW, COL);
			if (ret != 'C')
			{
				break;
			}
			
		}
		else//电脑先下
		{
			ComputerMove(board, ROW, COL,choose);
			DisplayBoard(board, ROW, COL);
			ret = VictoryJudgment(board, ROW, COL);
			if (ret != 'C')
			{
				break;
			}
			PlayerMove(board, ROW, COL);
			DisplayBoard(board, ROW, COL);
			ret = VictoryJudgment(board, ROW, COL);
			if (ret != 'C')
			{
				break;
			}
		}
	}
	if (ret == '*')
	{
		printf("Victory!\n");
	}
	else if (ret == '#')
	{
		printf("Defeat!\n");
	}
	else
	{
		printf("Draw!you're on the same level\n");
	}
	
}

而game函数也是为了充分的使用前面实现的功能模块,就是用造好的零部件组装成我们要的东西。

4.效果图

来看一下效果图吧!
在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:34:21  更:2022-07-20 18:35:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 5:22:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码