链表练习:链表五子棋

目录

一、新建坐标系统

二、初始化棋盘,并且利用C语言图形库绘线函数绘制棋盘

三、生成棋子,并初始化

四、判断胜负

总结:


链表是数据结构课程一个重要概念,为巩固练习,用链表写一个五子棋游戏。可以有白棋黑棋、可以判断胜负,使用到了C语言的图形库EasyX。

一、新建坐标系统

新建坐标系统使用数组存储,利用结构体创建一个15 x 15坐标系,包括横坐标和纵坐标。

#define CROSSINGLINES 15
#define VERTICALLINES 15

struct Board {
	int cro[CROSSINGLINES];
	int ver[VERTICALLINES];
};

二、初始化棋盘,并且利用C语言图形库绘线函数绘制棋盘

void InitBoard(Board board)
{
	for (int i = 0; i < CROSSINGLINES; i++)
	{
		line(board.cro[i], board.ver[0], board.cro[i], board.ver[VERTICALLINES - 1]);	//竖线
		line(board.cro[0], board.ver[i], board.cro[CROSSINGLINES - 1], board.ver[i]);	//横线
	}
}

 初始化绘制棋盘结束后,需要判断每一个棋盘线上每一个交点是否已经有棋子,初始化每一个交点均没有棋子,因此赋值为false,使用二维数组即可完成赋值

	for (int i = 0; i < CROSSINGLINES; i++)
	{
		board.cro[i] = (i + 1) * BOARD_INTERVAL;    //BOARD_INTERVAL为棋盘间距,避免从图形左上顶点开始,以棋盘间距与左上顶点隔开
		board.ver[i] = (i + 1) * BOARD_INTERVAL;
		for (int j = 0; j < VERTICALLINES; j++)
			flag[i][j] = false;
	}	

三、生成棋子,并初始化

棋子包含属性有坐标,以及角色。因此结构体可以设置

struct Player{
	int x;
	int y;
	char player;
	Player* next;
};

初始化棋子,棋子头结点统一置为空节点,利用头插法每次点击给相应的棋手添加节点位置和坐标

	Player* player_white;
	Player* player_black;
	player_white = (Player*)malloc(sizeof(Player*));
	player_black = (Player*)malloc(sizeof(Player*));
	if (!player_white || !player_black)
		exit(OVERFLOW);
	player_white->x = player_white->y = 0;
	player_black->x = player_black->y = 0;
	player_white->next = NULL;
	player_black->next = NULL;
	player_white->player = 'W';
	player_black->player = 'B';

因为用户不一定能够准确点击在棋盘纵横线的交点上,所以寻找距离用户点击最近的节点,用户点击位置整除棋盘间距+1即可找到横坐标上的交点位置,在链表中使用头插法给对应的棋手加入节点坐标信息和棋手信息即可

void PlayGB(Player* player, Board board, int &choice, bool flag[CROSSINGLINES][VERTICALLINES])
{
	MOUSEMSG m;
	if (MouseHit())
	{
		m = GetMouseMsg();	//获取鼠标队列信息
		if (m.x > 600 || m.x < 30 || m.y < 30 || m.y > 600)
			return;
		if (m.uMsg == WM_LBUTTONDOWN)
		{
			int x, y;
			int i, j;
			x = (m.x - BOARD_INTERVAL) / BOARD_INTERVAL;
			y = (m.y - BOARD_INTERVAL) / BOARD_INTERVAL;
			if (x > 15 || x < 0 || y > 15 || y < 0)
				return;
			if ((m.x - board.cro[x]) < (board.cro[x + 1] - m.x))
			{
				i = x;
				x = board.cro[x];
			}
			else {
				i = x + 1;
				x = board.cro[x + 1];
			}
			if ((m.y - board.ver[y]) < (board.ver[y + 1] - m.y))
			{
				j = y;
				y = board.ver[y];
			}
			else
			{
				j = y + 1;
				y = board.ver[y + 1];
			}
			if (flag[i][j])
				return;
			else
				flag[i][j] = true;
			Player* p;
			p = (Player*)malloc(sizeof(Player*));
			if (!p)
				exit(OVERFLOW);
			p->x = x;	p->y = y;
			p->player = player->player;
			p->next = player->next;
			player->next = p;
			if (p->player == 'W')
			{
				setfillcolor(WHITE);
				solidcircle(p->x, p->y, PIECE);
			}
			else if (p->player == 'B')
			{
				setfillcolor(BLACK);
				solidcircle(p->x, p->y, PIECE);
			}
			if (choice == 0)
				choice = 1;
			else
				choice = 0;
		}
	}
}

四、判断胜负

判断胜负即判断棋手刚下棋子上下左右、左上、右上、左下、右下八个方向对应链表存在相应的棋子,但五子棋至少大于等于5才可判断胜负

bool Win(Player* player, int x, int y)
{
	if(ElemExist(player, x + BOARD_INTERVAL, y) && ElemExist(player, x + 2 * BOARD_INTERVAL, y)
		&& ElemExist(player, x + 3 * BOARD_INTERVAL, y) && ElemExist(player, x + 4 * BOARD_INTERVAL, y))
		return true;	//右方五子
	if (ElemExist(player, x - 1 * BOARD_INTERVAL, y) && ElemExist(player, x - 2 * BOARD_INTERVAL, y)
		&& ElemExist(player, x - 3 * BOARD_INTERVAL, y) && ElemExist(player, x - 4 * BOARD_INTERVAL, y))
		return true;	//左方五子
	if (ElemExist(player, x, y + 1 * BOARD_INTERVAL) && ElemExist(player, x, y + 2 * BOARD_INTERVAL)
		&& ElemExist(player, x, y + 3 * BOARD_INTERVAL) && ElemExist(player, x, y + 4 * BOARD_INTERVAL))
		return true;	//上方五子
	if (ElemExist(player, x, y - 1 * BOARD_INTERVAL) && ElemExist(player, x, y - 2 * BOARD_INTERVAL)
		&& ElemExist(player, x, y - 3 * BOARD_INTERVAL) && ElemExist(player, x, y - 4 * BOARD_INTERVAL))
		return true;	//下方五子
	if (ElemExist(player, x + 1 * BOARD_INTERVAL, y - 1 * BOARD_INTERVAL) && ElemExist(player, x + 2 * BOARD_INTERVAL, y - 2 * BOARD_INTERVAL)
		&& ElemExist(player, x + 3 * BOARD_INTERVAL, y - 3 * BOARD_INTERVAL) && ElemExist(player, x + 4 * BOARD_INTERVAL, y - 4 * BOARD_INTERVAL))
		return true;	//右上方五子
	if (ElemExist(player, x + 1 * BOARD_INTERVAL, y + 1 * BOARD_INTERVAL) && ElemExist(player, x + 2 * BOARD_INTERVAL, y + 2 * BOARD_INTERVAL)
		&& ElemExist(player, x + 3, y + 3 * BOARD_INTERVAL) && ElemExist(player, x + 4, y + 4 * BOARD_INTERVAL))
		return true;	//右下方五子
	if (ElemExist(player, x - 1 * BOARD_INTERVAL, y - 1 * BOARD_INTERVAL) && ElemExist(player, x - 2 * BOARD_INTERVAL, y - 2 * BOARD_INTERVAL)
		&& ElemExist(player, x - 3 * BOARD_INTERVAL, y - 3 * BOARD_INTERVAL) && ElemExist(player, x - 4 * BOARD_INTERVAL, y - 4 * BOARD_INTERVAL))
		return true;	//左上方五子
	if (ElemExist(player, x - 1 * BOARD_INTERVAL, y + 1 * BOARD_INTERVAL) && ElemExist(player, x - 2 * BOARD_INTERVAL, y + 2 * BOARD_INTERVAL)
		&& ElemExist(player, x - 3 * BOARD_INTERVAL, y + 3 * BOARD_INTERVAL) && ElemExist(player, x - 4 * BOARD_INTERVAL, y + 4 * BOARD_INTERVAL))
		return true;	//左下方五子
	return false;
}
bool ElemExist(Player* player, int x, int y)
{
	Player* p;
	p = (Player*)malloc(sizeof(Player*));
	if (!p)
		exit(OVERFLOW);
	p = player->next;
	while (p) {
		if (p->x == x && p->y == y)
		{
			//free(p);
			return true;
		}
		p = p->next;
	}
	//free(p);
	return false;
}

总结:

C语言很难难在指针和内存,尤其是链表运用到的指针方面很多,需要多加练习和使用。以上代码均是在多次debug和思考之后才可以正常运行。如果突然遇到一大堆错误提示,比如提示else需要声明,说明在某处代码粗心多加分号,需要找很久但不一定能找出来,但是当程序可以正常运行之后,满足感是很强的,加油!

此游戏完成基本功能,其他功能小伙伴可自行完善。想要源码的小伙伴私信我,喜欢给作者点个赞呗。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
五子棋的核心算法 一、相关的数据结构 关于盘面情况的表示,以链表形式表示当前盘面的情况,目的是可以允许用户进行悔棋、回退等操作。 CList StepList; 其中Step结构的表示为: struct Step { int m; //m,n表示两个坐标值 int n; char side; //side表示下子方 }; 以数组形式保存当前盘面的情况, 目的是为了在显示当前盘面情况时使用: char FiveArea[FIVE_MAX_LINE][FIVE_MAX_LINE]; 其中FIVE_MAX_LINE表示盘面最大的行数。 同时由于需要在递归搜索的过程中考虑时间和空间有效性,只找出就当前情况来说相对比较好的几个盘面,而不是对所有的可下子的位置都进行搜索,这里用变量CountList来表示当前搜索中可以选择的所有新的盘面情况对象的集合: CList CountList; 其中类CBoardSituiton为: class CBoardSituation { CList StepList; //每一步的列表 char FiveArea[FIVE_MAX_LINE][FIVE_MAX_LINE]; struct Step machineStep; //机器所下的那一步 double value; //该种盘面状态所得到的分数 } 二、评分规则 对于下子的重要性评分,需要从六个位置来考虑当前棋局的情况,分别为:-,¦,/,\,//,\\ 实际上需要考虑在这六个位置上某一方所形成的子的布局的情况,对于在还没有子的地方落子以后的当前局面的评分,主要是为了说明在这个地方下子的重要性程度,设定了一个简单的规则来表示当前棋面对机器方的分数。 基本的规则如下: 判断是否能成5, 如果是机器方的话给予100000分,如果是人方的话给予-100000 分; 判断是否能成活4或者是双死4或者是死4活3,如果是机器方的话给予10000分,如果是人方的话给予-10000分; 判断是否已成双活3,如果是机器方的话给予5000分,如果是人方的话给予-5000 分; 判断是否成死3活3,如果是机器方的话给予1000分,如果是人方的话给予-1000 分; 判断是否能成死4,如果是机器方的话给予500分,如果是人方的话给予-500分; 判断是否能成单活3,如果是机器方的话给予200分,如果是人方的话给予-200分; 判断是否已成双活2,如果是机器方的话给予100分,如果是人方的话给予-100分; 判断是否能成死3,如果是机器方的话给予50分,如果是人方的话给予-50分; 判断是否能成双活2,如果是机器方的话给予10分,如果是人方的话给予-10分; 判断是否能成活2,如果是机器方的话给予5分,如果是人方的话给予-5分; 判断是否能成死2,如果是机器方的话给予3分,如果是人方的话给予-3分。 实际上对当前的局面按照上面的规则的顺序进行比较,如果满足某一条规则的话,就给该局面打分并保存,然后退出规则的匹配。注意这里的规则是根据一般的下棋规律的一个总结,在实际运行的时候,用户可以添加规则和对评分机制加以修正。 三、胜负判断 实际上,是根据当前最后一个落子的情况来判断胜负的。实际上需要从四个位置判断,以该子为出发点的水平,竖直和两条分别为 45度角和135度角的线,目的是看在这四个方向是否最后落子的一方构成连续五个的棋子,如果是的话,就表示该盘棋局已经分出胜负。具体见下面的图示: 四、搜索算法实现描述 注意下面的核心的算法中的变量currentBoardSituation,表示当前机器最新的盘面情况, CountList表示第一层子节点可以选择的较好的盘面的集合。核心的算法如下: void MainDealFunction() { value=-MAXINT; //对初始根节点的value赋值 CalSeveralGoodPlace(currentBoardSituation,CountList); //该函数是根据当前的盘面情况来比较得到比较好的可以考虑的几个盘面的情况,可以根据实际的得分情况选取分数比较高的几个盘面,也就是说在第一层节点选择的时候采用贪婪算法,直接找出相对分数比较高的几个形成第一层节点,目的是为了提高搜索速度和防止堆栈溢出。 pos=CountList.GetHeadPosition(); CBoardSituation* pBoard; for(i=0;ivalue=Search(pBoard,min,value,0); Value=Select(value,pBoard->value,max); //取value和pBoard->value中大的赋给根节点 } for(i=0;ivalue) //找出那一个得到最高分的盘面 { currentBoardSituation=pBoard; PlayerMode=min; //当前下子方改为人 Break; } } 其中对于Search函数的表示如下:实际上核心的算法是一个剪枝过程,其中在这个搜索过程中相关的四个参数为:(1)当前棋局情况;(2)当前的下子方,可以是机器(max)或者是人(min);(3)父节点的值oldValue;(4)当前的搜索深度depth。 double Search(CBoardSituation& board,int mode,double oldvalue,int depth) { CList m_DeepList; if(deptholdvalue))== TRUE) { if(mode==max) value=select(value,search(successor Board,min,value,depth+1),max); else value=select(value,search(successor Board,max,value,depth+1),min); } return value; } else { if ( goal(board)<>0) //这里goal(board)<>0表示已经可以分出胜负 return goal(board); else return evlation(board); } } 注意这里的goal(board)函数是用来判断当前盘面是否可以分出胜负,而evlation(board)是对当前的盘面从机器的角度进行打分。 下面是Select函数的介绍,这个函数的主要目的是根据 PlayerMode情况,即是机器还是用户来返回节点的应有的值。 double Select(double a,double b,int mode) { if(a>b && mode==max)¦¦ (a< b && mode==min) return a; else return b; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值