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需要马上用,应该可以跳过,但是其中的内容很是很重要,这会让后面涉及变换透视的章节更加易懂,推荐大家看看),之后是蓝宝书或是极客学院翻译的教程比较推荐,这两个还是比较适合你我这样的新手的。
这里不推荐看的是红宝书,这本书我看了有点类似于字典那样的工具书,不太适合新手上手学,而且讲的也并不是很通俗易懂(可能是我的书版本比较老吧…)
加油
当然如果你对我有信心,我也会持续更新(虽然前路漫漫),跟大家一同进步(虽然很可能没人看(╥╯^╰╥),无所谓了,当然如有错误还请大家指正∠(°ゝ°),哪里不懂我会尽力解决,哪里说的不好也可以指出我会及时修改~)
我们下篇见~~