OpenGL超级宝典(第7版)笔记14 前三章实例 下个五子棋 (下)

  • Post author:
  • Post category:其他




OpenGL超级宝典(第7版)笔记14 前三章实例 下个五子棋 (下)




前言

上一篇我们把从数据到画面的过程说完了,这一篇我们说说如何获取输入,并且修改数据,判断输赢。



1 获取鼠标点击输入

我们之前在笔记2中的输入输出小节中谈到过对鼠标的位置获取,通过mouse_callback()函数来获取鼠标的位置信息,通过glfwSetCursorPosCallback();将当前窗口的鼠标位置回调函数设置为我们写的mouse_callback()。

具体如下

void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    lastx=xpos;
    lasty=ypos;
    //存储鼠标位置信息到全局变量lastx、lasty
}
glfwSetCursorPosCallback(window, mouse_callback);

但是我们并不会获取鼠标的点击信息,这里我们将介绍mouse_button_callback()和glfwSetMouseButtonCallback()来获取鼠标的点击操作:

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods){
	//...
	return;
}
glfwSetMouseButtonCallback(window, mouse_button_callback);

鼠标按键回调函数会获取4个参数,分别是window表示当前窗口,button表示鼠标的按键信息,action表示当前是按下还是松开等状态信息,mods(不太清楚)。

我们现在来编写具体的内容,首先在我们点击发生后,回调函数会启用,我们要立即保存下当前的鼠标位置(用于判断点击的位置是在哪里,需要相应有哪些处理)。我们这里约定鼠标左键为下子,右键为切换棋子种类(白变黑,黑变白),中键为悔棋。具体代码如下(cout便于查找错误):

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods){
	int whichkey=0;//左键为1右键为2中键为3 
	if (action == GLFW_PRESS) 
		switch (button){
			case GLFW_MOUSE_BUTTON_LEFT:
				kickx = lastx;
				kicky = lasty;
				whichkey=1;
				std::cout << "left down" << std::endl;
				std::cout << lastx << "  " << lasty << std::endl;
				break;
			case GLFW_MOUSE_BUTTON_MIDDLE:
				kickx = lastx;
				kicky = lasty;
				whichkey=3;
				std::cout << "mid down" << std::endl;
				std::cout << lastx << "  " << lasty << std::endl;
				break;
			case GLFW_MOUSE_BUTTON_RIGHT:
				kickx = lastx;
				kicky = lasty;
				whichkey=2;
				std::cout << "right down" << std::endl;
				std::cout << lastx << "  " << lasty << std::endl;
				if (nowkind == 1) {
					nowkind = 2;
				}
				else {
					nowkind = 1;
				}
				break;
			default:
				return;
			}
	//上方为part1
	//...
	return;
}

在part1中我们把各种信息写到了全局变量nowkind、kickx、kicky、whichkey中,他们分别记录了当前棋子种类、点击时的xy值、点击的是鼠标上的哪个键。下面我们要根据不同的按键,进行分类处理。

左键按下后:

判断位置是否在棋盘中,判断点击位置对应的棋盘二维数组的下标,判断改棋盘位置是否有棋子,下子后在chessboard和chess_draw_list中添加对应的数据,并且让计数器chess_number加一,下子后判断是否五子连珠(并把结果写入全局变量iswin中,指示后面有人赢得对局了)。

中间按下后:

按照chess_draw_list的指示位置,把chessboard的棋子删除,并且chess_draw_list中抹去最后一个棋子,让计数器chess_number减一。

(cout便于查找错误)

void mouse_button_callback(GLFWwindow* window, int button, int action, int mods){
	//上方的part1省略
	switch(whichkey){
		case 1:
			if (kickx > 150 && kicky > 150 && kickx < 850 && kicky < 850) {
				arrayx = (kickx - 170) / 38.75f + 1.0f;
				arrayy = (kicky - 170) / 38.75f + 1.0f;
				std::cout << "arrayx=" << arrayx << "  " << "arrayy=" << arrayy << std::endl;
				if (chessboard[arrayx][arrayy].chesskind == 0) {
					chessboard[arrayx][arrayy].chesskind = nowkind;
					chess_draw_list[chess_number][0]=nowkind;
					chess_draw_list[chess_number][1]=arrayx;
					chess_draw_list[chess_number][2]=arrayy;
					chess_number++;
					if (judgewin(arrayx, arrayy) == 1) {
						std::cout << "nowkind " << nowkind << " is win!!" << std::endl;
						iswin = nowkind;
					}
					if (nowkind == 1)
						nowkind = 2;
					else
						nowkind = 1;
				}
			}
		break;
		case 3:
			if (chess_number > 0) {
				chess_number--;
				chessboard[chess_draw_list[chess_number][1]][chess_draw_list[chess_number][2]].chesskind = 0;
				chess_draw_list[chess_number][0]=0;
				chess_draw_list[chess_number][1]=0;
				chess_draw_list[chess_number][2] = 0;
				if (nowkind == 1)
					nowkind = 2;
				else
					nowkind = 1;
			}
		break;
		default:
		break;
	}
	return;
}

当然弄好了回调函数后,要在主程序中告诉glfw我们的鼠标按键回调函数是mouse_button_callback,具体设置如下:

glfwSetMouseButtonCallback(window, mouse_button_callback);

别忘了我们上面用到了一些全局变量:

GLdouble lastx = 0.0f;
GLdouble lasty = 0.0f;
GLdouble kickx = 0.0f;
GLdouble kicky = 0.0f;
GLint arrayx = 0;
GLint arrayy = 0;

struct chess{
	GLchar chesskind=0;
};
chess chessboard[17][17];
GLchar chess_draw_list[17*17][3]={0}; 
GLint chess_number=0;
GLint nowkind = 1;//黑为1白为2
int iswin = 0;

GLint judgewin(GLint x, GLint y);
//判断五子连珠的函数

我们留了个小尾巴,就是judgewin(arrayx, arrayy);的函数,我们怎样通过当前下子的xy并结合当前棋盘上的信息(chessboard)来判断是否五子连珠,大家想一想,马上给出我的解法。



2 判断五子连珠

其实在我们写这个函数之前就默认了一件事情:只有当下棋子的时候需要判断五子连珠,每当有新的棋子下下去的时候,该棋子附近的同色棋子就有有可能会发生五子连珠。如果没有新子下下去就不需要判断。虽然可能有点废话,但是我们确定了该函数应该在回调函数中调用(因为回调函数只在有输入时调用),也确定了只需要在下子的附近,对同颜色的棋子进行五子连珠判断就行了。

我们现在把连珠的情况分为4类,1是水平方向,2是垂直方向,3是左上到右下,4是右上到左下,我们分别按照该情况向两端探测,并将连珠数进行加和,最终对连珠数进行判断,看是否五子连珠,具体思路看下图:

在这里插入图片描述

具体代码看下图(可能有点长,不同方向上思路是一样的):

GLint judgewin(GLint x, GLint y) {
	GLint result = 0;
	GLint same = 1;
	GLint direction = 0;
	int i = x, j = y;


	//水平方向判定
	i = x;	j = y;	same = 1;	direction = 0;
	while (1) {
		if (direction == 0) {
			i++;
			if (i >= 0 && i <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i - 1][j].chesskind)
					same++;
				else {
					direction = 1;
					i = x;
					j = y;
				}
			}
			else {
				direction = 1;
				i = x;
				j = y;
			}
		}
		else {
			i--;
			if (i >= 0 && i <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i + 1][j].chesskind)
					same++;
				else
					break;
			}
			else
				break;
		}
	}
	if (same >= 5) {result= 1;}

	//垂直方向判定
	i = x;	j = y;	same = 1;	direction = 0;
	while (1) {
		if (direction == 0) {
			j++;
			if (j >= 0 && j <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i][j-1].chesskind)
					same++;
				else {
					direction = 1;
					i = x;
					j = y;
				}
			}
			else {
				direction = 1;
				i = x;
				j = y;
			}
		}
		else {
			j--;
			if (j >= 0 && j <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i][j+1].chesskind)
					same++;
				else
					break;
			}
			else
				break;
		}
	}
	if (same >= 5) {result = 1;}

	//左上到右下
	i = x;	j = y;	same = 1;	direction = 0;
	while (1) {
		if (direction == 0) {
			i++;j++;
			if (i >= 0 && i <= 16&&j >= 0 && j <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i - 1][j-1].chesskind)
					same++;
				else {
					direction = 1;
					i = x;
					j = y;
				}
			}
			else {
				direction = 1;
				i = x;
				j = y;
			}
		}
		else {
			i--;j--;
			if (i >= 0 && i <= 16 && j >= 0 && j <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i + 1][j+1].chesskind)
					same++;
				else
					break;
			}
			else
				break;
		}
	}
	if (same >= 5) { result = 1; }

	//右上到左下
	i = x;	j = y;	same = 1;	direction = 0;
	while (1) {
		if (direction == 0) {
			i++;j--;
			if (i >= 0 && i <= 16 && j >= 0 && j <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i - 1][j + 1].chesskind)
					same++;
				else {
					direction = 1;
					i = x;
					j = y;
				}
			}
			else {
				direction = 1;
				i = x;
				j = y;
			}
		}
		else {
			i--;j++;
			if (i >= 0 && i <= 16 && j >= 0 && j <= 16) {
				if (chessboard[i][j].chesskind == chessboard[i + 1][j - 1].chesskind)
					same++;
				else
					break;
			}
			else
				break;
		}
	}
	if (same >= 5) { result = 1; }

	return result;
}



3 胜利

现在,我们把从输入到改写数据,数据到最终的绘制都进行完了,我们只差一点点了,就是当胜利了之后我们要显示最终的胜者,并且清空数据并从新开始游戏,这里要用到一个windows.h中的函数MessageBox。

还记得那个render所在的循环吗,我们要在每次绘制完成后,根据iswin来进行判断是否有人胜利,并采取相应操作:

while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        double timeValue = glfwGetTime();
        render(timeValue);
        glfwSwapBuffers(window);
        //我们要在绘制完成后再判断
        //因为我们要看到最后一个关键的子下下去后再显示胜利

		if (iswin == 1) {
			MessageBox(0, TEXT("黑棋胜利!!"), TEXT("result"), 0);
			iswin = 0;
			for (;chess_number > 0;) {
				if (chess_number > 0) {
					chess_number--;
					chessboard[chess_draw_list[chess_number][1]][chess_draw_list[chess_number][2]].chesskind = 0;
					chess_draw_list[chess_number][0] = 0;
					chess_draw_list[chess_number][1] = 0;
					chess_draw_list[chess_number][2] = 0;
				}
			}
		}
		else if (iswin == 2) {
			MessageBox(0, TEXT("白棋胜利!!"), TEXT("result"), 0);
			iswin = 0;
			for (;chess_number > 0;) {
				if (chess_number > 0) {
					chess_number--;
					chessboard[chess_draw_list[chess_number][1]][chess_draw_list[chess_number][2]].chesskind = 0;
					chess_draw_list[chess_number][0] = 0;
					chess_draw_list[chess_number][1] = 0;
					chess_draw_list[chess_number][2] = 0;
				}
			}
		}
    }

这里有两点要注意,一是要在绘制之后判断胜利并重启游戏,因为我们要让玩家看到最终是哪五个子连珠,二是用MessageBox要包含头文件,MessageBox会弹出一个窗口显示胜者的棋子是哪一种。

#include<Windows.h>
//MessageBox需要包含的头文件

MessageBox的运行结果

在这里插入图片描述

在这里插入图片描述

好了整个程序就是这样(整体的代码我会放在之后单独一篇中),输入、判断、绘制、我们一路过来遇到了不少问题,当然我们都想办法解决了。以后还会学习更多的知识,等到时候在来重置这个游戏吧,到时候也许是3D的了,没准还会有光源还有各种反射,甚至是光线追踪(这就扯远了),到时候我们还会拿它来作为练习,提出更高的要求,用新的知识让它更真实,让它更快速。



4 总结

本次我们把鼠标输入和连珠判断向大家介绍了一遍,其中其实有很多可以优化的地方,不管是从代码上,还是技术上,或是逻辑上,希望我们能在之后的学习中向其中添加更多的效果,让我们的游戏更真实,更流畅。

对于游戏的代码,我会单独开一篇将整个程序的代码放在其中,方便大家阅读,注意最终的版本可能和之前说的不太一样,主要是一些细节上的填充,一些注释上的省略,整体的逻辑和流程是一样的,我一方面希望大家自己动手写一写,另一方面也希望大家能看看整个程序,看看能不能修改让它变得更好~

下一篇我们将重新踏上OpenGL的学习之路,我们会先向大家介绍一点点数学上的知识,为之后变换矩阵做铺垫(就是实现3D效果,3维中的立体图形,3维中的移动等等),可能会有一点难(如果你了解过线性代数,那对你来说相当轻松),如果实在不会可以按照规律记下来,毕竟我们并不需要很强的数学功底,当然我也会提供代码,大家也可以拿来用,但是我还是希望你能理解。

我们下一篇见~~


关于书籍的问题


如果你手中没有该书,我还是建议你购买一本,毕竟书本毕竟更加严谨专业,我这里难免遗漏一些细节,主要是提供实例,并做一个消化,将很混乱的流程为大家理清,但这笔记一定是通俗的,是对新手友好的(当然有时候你需要在某些方面自己努努力,比如后面出现的基本线性代数的内容,还有C语言或是c++的基础知识,虽然我可能也不太懂O(∩_∩)O,慢慢来吧)。


别被吓住


刚开始的时候很容易被OpenGL的巨长的函数和超级复杂的流程吓到,其实并没有那么可怕,只要对这样或那样的流程熟悉之后,一切都变得相当简单(当然如果你能提出一个更好的流程那就更好了,当我们把很多基础的工作做完,我们会不断的提出新问题新点子,用新的技术来实现它,最终完成OpenGL的学习)

虽然我也不知道后面将是怎样的道路,但至少努力学习是没错的。


我看过的相关内容


以下并不是全看完了,大部分看了15%就看不下去了,实在是没看懂。(本人没什么计算机编程基础,算是野生程序员吧,很多内容都不能标准表述,望见谅)

如果你对opengl的工作有了一定的了解,我一开始也是从这里开始的,但是仍然有很多的不懂的,最后至今为止,我杂糅了很多的网站内容包括

LearnOpenGL



极客学院



哔哩哔哩的闫令琪计算机图形学



哔哩哔哩的傅老师的OpenGL课程

、OpenGL编程指南”也称为红宝书”、OpenGL超级宝典”也称为蓝宝书”、当然还有很多的csdn文章O(∩_∩)O这就不介绍了,等用到是时候我在放链接吧O(∩_∩)O

这里面图形学比较易懂也很基础推荐可以作为开始(如果你是学OpenGL需要马上用,应该可以跳过,但是其中的内容很是很重要,这会让后面涉及变换透视的章节更加易懂,推荐大家看看),之后是蓝宝书或是极客学院翻译的教程比较推荐,这两个还是比较适合你我这样的新手的。

这里不推荐看的是红宝书,这本书我看了有点类似于字典那样的工具书,不太适合新手上手学,而且讲的也并不是很通俗易懂(可能是我的书版本比较老吧…)


加油


当然如果你对我有信心,我也会持续更新(虽然前路漫漫),跟大家一同进步(虽然很可能没人看(╥╯^╰╥),无所谓了,当然如有错误还请大家指正∠(°ゝ°),哪里不懂我会尽力解决,哪里说的不好也可以指出我会及时修改~)

我们下篇见~~



版权声明:本文为qq_44016525原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。