【深圳大学计算机图形学】期中大作业 俄罗斯方块

目录

实验目的与要求

实验过程及内容

实验结论

实验代码


实验目的与要求

  1. 强化OpenGL的基本绘制方法、键盘等交互事件的响应逻辑,实现更加复杂的绘制操作,完成一个简化版俄罗斯方块游戏。
  2. 方块/棋盘格的渲染和方块向下移动。创建OpenGL绘制窗口,然后绘制网格线来完成对棋盘格的渲染。随机选择方块并赋上颜色,从窗口最上方中间开始往下自动移动,每次移动一个格子。初始的方块类型和方向也必须随机选择,另外可以通过键盘控制方块向下移动的速度,在方块移动到窗口底部的时候,新的方块出现并重复上述移动过程。
  3. 方块叠加。不断下落的方块需要能够相互叠加在一起,即不同的方块之间不能相互碰撞和叠加。另外,所有方块移动不能超出窗口的边界。
  4. 键盘控制方块的移动。通过方向键(上/下/左/右)来控制方块的移动。按“上”键使方块以旋转中心顺(逆)时针旋转,每次旋转90°,按“左”和“右”键分别将方块向左/右方向移动一格,按“下”键加速方块移动。
  5. 游戏控制。当游戏窗口中的任意一行被方块占满,该行即被消除,所有上面的方块向下移动一格子。当整个窗口被占满而不能再出现新的方块时,游戏结束。通过按下“q”键结束游戏,和按下“r”键重新开始游戏。
  6. 其他扩展。在以上基本内容的基础上,可以增加更多丰富游戏性的功能,如通过空格键使方块快速下落等。

实验过程及内容

1.创建多种方块类型

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第1张图片

2.添加变量记录方块类型。

3. 更改newtile函数使得每次随机选取方块进行生成,同时更改allRotationsLshape数组。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第2张图片

4.因为更改了allRotationsLshape数组,因此同样需要更改旋转的rotate函数,此时可以看出shapeLike变量的作用是保持旋转前后方块类型不变。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第3张图片

5.运行代码看一下效果,发现确实可以随机生成各种类型的方块。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第4张图片

6.更改标题栏,将窗口标题设为:“学号_姓名_期中大作业”。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第5张图片

7.接下来为每一个类型的方块都赋上不同的颜色,首先添加颜色的种类并将名字存储到数组中。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第6张图片

8.因为是每个方块类型对应一种颜色,因此通过colors数组和shapeLike变量既可以确定一种颜色。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第7张图片

9.运行代码,发现每种类型的方块都对应了一种颜色。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第8张图片

10.在main函数的while循环中添加时钟记录时间,使得每隔一秒钟方块下落一格。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第9张图片

11.完善restart函数,使得按r键可以重新开始游戏。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第10张图片

12. 添加空格键,能够使得方块直接下落到底部。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第11张图片

13. 添加方块间的检测判断,判断是否已经有方块放置。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第12张图片

14.运行代码,此时已经可以叠加堆放了,如图所示。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第13张图片

15.接下来需要实现当一行的格子被放满后,消除一整行格子的操作,首先需要创建一个数组记录每个格子的颜色。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第14张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第15张图片

16.在settile函数中对每一个格子所在的行进行检测,看该行是否已满。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第16张图片

17.接下来完善checkfullrow函数,检查棋盘格在row行有没有被填充满,如果没有填满row行则返回函数,否则消去那一行,并将其上的格子下移一格。

从被消除的行开始向上判断,若上面一行有格子,则复制到当前行,否则填充黑色。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第17张图片

18.运行代码,发现其成功消除满了的行。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第18张图片 【深圳大学计算机图形学】期中大作业 俄罗斯方块_第19张图片

19.其实到这里游戏的基础功能就已经基本做好了,加下来需要考虑的是优化人机交互界面即UI设计,以及游戏难易度调整,记分以及按键功能提示等等。

首先先加上一些欢迎语以及按键功能提示。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第20张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第21张图片

20.添加记分板功能。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第22张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第23张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第24张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第25张图片

21.补充上一个游戏结束判断条件,即在生成方块时判断是否可以成功生成,如果不能则游戏结束。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第26张图片

注意坐标转换:

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第27张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第28张图片

22.添加激励机制,即记录历史最高得分,以提高玩家游玩时长。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第29张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第30张图片

21. 添加梯度难度设计,消除方块后下落速度提高。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第31张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第32张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第33张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第34张图片

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第35张图片

实验结论

通过俄罗斯方块的实验,学到了一些与编程和游戏开发相关的重要知识和技能。

游戏开发技能:通过编写俄罗斯方块游戏,熟悉了游戏开发的基本概念,包括游戏循环、用户输入处理、图形渲染等。了解了如何将理论知识转化为实际的游戏项目。

图形编程:俄罗斯方块游戏需要绘制图形,包括方块的渲染和窗口的管理。加深了对OpenGL和图形编程的基本原理的理解。

游戏设计思维:俄罗斯方块实验提高了设计游戏的思维。了解了游戏难度平衡、用户体验、界面设计等方面的原则。

在实验中也有遇到一些小bug:

1.伪随机生成

每次运行生成的图案顺序都一样。后来发现是因为随机数发生器的种子没有被设置,导致它使用了默认种子,从而产生相同的随机序列。

添加srand(time(0));设置随机数种子后即可实现真正的伪随机。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第36张图片

2.遇到标题栏中文乱码问题

产生以下乱码

添加#pragma execution_character_set("utf-8"),这是用于告诉编译器源代码文件中使用的字符集是UTF-8。可以确保你的源代码中的字符串文字被正确解释为UTF-8 编码。在编译程序时,编译器将使用UTF-8字符集来解释字符串文字,以便在程序运行时以UTF-8编码的形式进行处理。

添加后可正常输出中文,如图所示

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第37张图片

3.方块瞬间落至底部

本来是想着每隔一秒让方块下落一格,结果却是每过一秒方块直接瞬闪到最底部。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第38张图片

打上断点进行测试,发现单步测试的时候方块是一格一格下落的。那么方块瞬移的原因应该是在这一秒内瞬间叠加了多次下移操作,即多次运行了movetile(glm::vec2(0, -1)。仔细分析movetile函数后发现,它是到底部才会返回false,所以这个if就成了相判断其是否到了底部,只有到了底部才会更新时间,而不是我所设想的下移一格更新一次时间。既然如此,将更新时间的语句移到if语句外即可。

【深圳大学计算机图形学】期中大作业 俄罗斯方块_第39张图片

实验代码

期中大作业——main.cpp

/*
 *        Computer Graphics Course - Shenzhen University
 *    Mid-term Assignment - Tetris implementation sample code
 * ============================================================
 *
 * - 本代码仅仅是参考代码,具体要求请参考作业说明,按照顺序逐步完成。
 * - 关于配置OpenGL开发环境、编译运行,请参考第一周实验课程相关文档。
 *
 * - 已实现功能如下:
 * - 1) 绘制棋盘格和‘L’型方块
 * - 2) 键盘左/右/下键控制方块的移动,上键旋转方块
 *
 * - 未实现功能如下:
 * - 1) 绘制‘J’、‘Z’等形状的方块
 * - 2) 随机生成方块并赋上不同的颜色
 * - 3) 方块的自动向下移动
 * - 4) 方块之间、方块与边界之间的碰撞检测
 * - 5) 棋盘格中每一行填充满之后自动消除
 * - 6) 其他
 *
 *	实现功能:
 * 1.添加了多种方块类型
 * 2.为每种方块颜色添加了颜色
 * 3.实现了每次随机生成方块类型
 * 4.更改了标题栏,处理了中文乱码问题
 * 5.使得每隔一秒方块下落一格
 * 6.完善restart函数,使得按r键可以重新开始游戏
 * 7.添加空格键,能够使得方块直接下落到底部
 * 8.消除满了的行并下移上面的格子
 * 9.优化UI设计,添加游玩说明、按键说明等
 * 10.添加记分板,消除后实时更新分数
 * 11.添加梯度难度设计,消除方块后下落速度提高
 */

#include "Angel.h"

#include 
#include 
#include 
#include
#include 
using namespace std;

int starttime;			// 控制方块向下移动时间
int rotation = 0;		// 控制当前窗口中的方块旋转
glm::vec2 tile[4];		// 表示当前窗口中的方块
bool gameover = false;	// 游戏结束控制变量
int xsize = 400;		// 窗口大小(尽量不要变动窗口大小!)
int ysize = 720;
int shapeLike = 0;		//方块类型
int score = 0;			//记分板
float lowSpeed = -1;		//设置下落速度
int maxScore = 0;		//记录历史最高得分

// 单个网格大小
int tile_width = 33;

// 网格布大小
const int board_width = 10;
const int board_height = 20;

// 网格三角面片的顶点数量
const int points_num = board_height * board_width * 6;

// 我们用画直线的方法绘制网格
// 包含竖线 board_width+1 条
// 包含横线 board_height+1 条
// 一条线2个顶点坐标
// 网格线的数量
const int board_line_num = (board_width + 1) + (board_height + 1);

//存储方块类型的数组
glm::vec2 allRotationsLshape[7][4][4] = {
	{	// O型
		{glm::vec2(0, 0), glm::vec2(-1, 0), glm::vec2(0, -1), glm::vec2(-1, -1)},
		{glm::vec2(0, 0), glm::vec2(-1, 0), glm::vec2(0, -1), glm::vec2(-1, -1)},
		{glm::vec2(0, 0), glm::vec2(-1, 0), glm::vec2(0, -1), glm::vec2(-1, -1)},
		{glm::vec2(0, 0), glm::vec2(-1, 0), glm::vec2(0, -1), glm::vec2(-1, -1)}},
	{	// I型
		{glm::vec2(-2, 0), glm::vec2(-1, 0), glm::vec2(1, 0), glm::vec2(0, 0)},
		{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(0, -2)},
		{glm::vec2(-2, 0), glm::vec2(-1, 0), glm::vec2(1, 0), glm::vec2(0, 0)},
		{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(0, -2)}},
	{	// S型
		{glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(-1, -1), glm::vec2(1, 0)},
		{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, -1)},
		{glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(-1, -1), glm::vec2(1, 0)},
		{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, -1)}},
	{	//  Z型
		{glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(1, -1)},
		{glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(1, 0), glm::vec2(1, 1)},
		{glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(1, -1)},
		{glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(1, 0), glm::vec2(1, 1)}},
	{	//  L型
		{glm::vec2(0, 0), glm::vec2(-1, 0), glm::vec2(1, 0), glm::vec2(-1, -1)},
		{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(1, -1)},
		{glm::vec2(1, 1), glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(1, 0)},
		{glm::vec2(-1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(0, -1)}},
	{	//  J型
		{glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, -1)},
		{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(0, -1), glm::vec2(1, 1)},
		{glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(-1, 1)},
		{glm::vec2(-1, -1), glm::vec2(0, -1), glm::vec2(0, 0), glm::vec2(0, 1)}},
	{	//  T型
		{glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(0, -1)},
		{glm::vec2(0, -1), glm::vec2(0, 0), glm::vec2(0, 1), glm::vec2(1, 0)},
		{glm::vec2(-1, 0), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(0, 1)},
		{glm::vec2(-1, 0), glm::vec2(0, -1), glm::vec2(0, 0), glm::vec2(0, 1)}},
};

// 绘制窗口的颜色变量
glm::vec4 white = glm::vec4(1.0, 1.0, 1.0, 1.0);
glm::vec4 black = glm::vec4(0.0, 0.0, 0.0, 1.0);
glm::vec4 orange = glm::vec4(1.0, 0.5, 0.0, 1.0);
glm::vec4 red = glm::vec4(1.0, 0.0, 0.0, 1.0);
glm::vec4 green = glm::vec4(0.0, 1.0, 0.0, 1.0);
glm::vec4 blue = glm::vec4(0.0, 0.0, 1.0, 1.0);
glm::vec4 yellow = glm::vec4(1.0, 1.0, 0.0, 1.0);
glm::vec4 purple = glm::vec4(0.5, 0.0, 0.5, 1.0);
glm::vec4 cyan = glm::vec4(0.0, 1.0, 1.0, 1.0);
glm::vec4 colors[] = { orange,red,green,blue,yellow,purple,cyan };

// 当前方块的位置(以棋盘格的左下角为原点的坐标系)
glm::vec2 tilepos = glm::vec2(5, 19);

// 布尔数组表示棋盘格的某位置是否被方块填充,即board[x][y] = true表示(x,y)处格子被填充。
// (以棋盘格的左下角为原点的坐标系)
bool board[board_width][board_height];

// 当棋盘格某些位置被方块填充之后,记录这些位置上被填充的颜色
glm::vec4 board_colours[points_num];

//记录每个格子的颜色
glm::vec4 posColors[board_width][board_height];

GLuint locxsize;
GLuint locysize;

GLuint vao[3];
GLuint vbo[6];

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}

// 修改棋盘格在pos位置的颜色为colour,并且更新对应的VBO
void changecellcolour(glm::vec2 pos, glm::vec4 colour)
{
	// 每个格子是个正方形,包含两个三角形,总共6个定点,并在特定的位置赋上适当的颜色
	for (int i = 0; i < 6; i++)
		board_colours[(int)(6 * (board_width * pos.y + pos.x) + i)] = colour;

	glm::vec4 newcolours[6] = { colour, colour, colour, colour, colour, colour };

	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);

	// 计算偏移量,在适当的位置赋上颜色
	int offset = 6 * sizeof(glm::vec4) * (int)(board_width * pos.y + pos.x);
	glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(newcolours), newcolours);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	//记录每个格子的颜色
	posColors[(int)pos.x][(int)pos.y] = colour;
}

// 当前方块移动或者旋转时,更新VBO
void updatetile()
{
	glBindBuffer(GL_ARRAY_BUFFER, vbo[4]);

	// 每个方块包含四个格子
	for (int i = 0; i < 4; i++)
	{
		// 计算格子的坐标值
		GLfloat x = tilepos.x + tile[i].x;
		GLfloat y = tilepos.y + tile[i].y;

		glm::vec4 p1 = glm::vec4(tile_width + (x * tile_width), tile_width + (y * tile_width), .4, 1);
		glm::vec4 p2 = glm::vec4(tile_width + (x * tile_width), tile_width * 2 + (y * tile_width), .4, 1);
		glm::vec4 p3 = glm::vec4(tile_width * 2 + (x * tile_width), tile_width + (y * tile_width), .4, 1);
		glm::vec4 p4 = glm::vec4(tile_width * 2 + (x * tile_width), tile_width * 2 + (y * tile_width), .4, 1);

		// 每个格子包含两个三角形,所以有6个顶点坐标
		glm::vec4 newpoints[6] = { p1, p2, p3, p2, p3, p4 };
		glBufferSubData(GL_ARRAY_BUFFER, i * 6 * sizeof(glm::vec4), 6 * sizeof(glm::vec4), newpoints);
	}
	glBindVertexArray(0);

}

// 检查在cellpos位置的格子是否被填充或者是否在棋盘格的边界范围内
bool checkvalid(glm::vec2 cellpos)
{
	if ((cellpos.x >= 0) && (cellpos.x < board_width) && (cellpos.y >= 0) && (cellpos.y < board_height)
		&& board[(int)cellpos.x][(int)cellpos.y] == false)	//添加方块间的检测判断,判断是否已经有方块放置
		return true;
	else
		return false;
}

// 设置当前方块为下一个即将出现的方块。在游戏开始的时候调用来创建一个初始的方块,
// 在游戏结束的时候判断,没有足够的空间来生成新的方块。
void newtile()
{
	// 将新方块放于棋盘格的最上行中间位置并设置默认的旋转方向
	tilepos = glm::vec2(5, 19);
	rotation = 0;

	srand(time(0));
	shapeLike = rand() % 7;
	for (int i = 0; i < 4; i++)
	{
		tile[i] = allRotationsLshape[shapeLike][0][i];

		// 检测是否有位置放置新方块
		if (!checkvalid(glm::vec2((int)tile[i].x + board_width / 2, (int)tile[i].y + board_height - 1))) {
			if (score > maxScore)
				maxScore = score;
			system("cls");
			cout << "游戏结束!\n你的分数为:" << score << endl;
			cout << "当前最高分为:" << maxScore << endl;
			cout << "请按q退出游戏或按r重新开始游戏" << endl;
			return;
		}
	}

	updatetile();

	// 给新方块赋上颜色
	glm::vec4 newcolours[24];
	for (int i = 0; i < 24; i++)
		newcolours[i] = colors[shapeLike];

	glBindBuffer(GL_ARRAY_BUFFER, vbo[5]);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(newcolours), newcolours);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	glBindVertexArray(0);
}

void welcome() {
	cout << "欢迎来到俄罗斯方块,祝您玩的愉快!" << endl << endl;
	cout << "游戏玩法:通过调整下落的随机方块,使其填充满一行后消除获得分数,分数越高下落速度越快哦" << endl << endl;
	cout << "按键说明:使用上键旋转方块\n          使用左或右键移动方块\n          使用下键加速方块下落\n          使用空格键下落方块至底部\n";
	cout << "          使用q键或Esc键退出游戏\n          使用r键重新开始游戏\n" << endl;

	cout << "当前下落速度:" << lowSpeed << endl;
	cout << "当前分数:" << score << endl;
}

// 游戏和OpenGL初始化
void init()
{
	//分数初始化为0
	score = 0;

	//下降速度初始化为-1
	lowSpeed = -1;

	//输出提示语
	welcome();

	// 初始化棋盘格,这里用画直线的方法绘制网格
	// 包含竖线 board_width+1 条
	// 包含横线 board_height+1 条
	// 一条线2个顶点坐标,并且每个顶点一个颜色值

	glm::vec4 gridpoints[board_line_num * 2];
	glm::vec4 gridcolours[board_line_num * 2];

	// 绘制网格线
	// 纵向线
	for (int i = 0; i < (board_width + 1); i++)
	{
		gridpoints[2 * i] = glm::vec4((tile_width + (tile_width * i)), tile_width, 0, 1);
		gridpoints[2 * i + 1] = glm::vec4((tile_width + (tile_width * i)), (board_height + 1) * tile_width, 0, 1);
	}

	// 水平线
	for (int i = 0; i < (board_height + 1); i++)
	{
		gridpoints[2 * (board_width + 1) + 2 * i] = glm::vec4(tile_width, (tile_width + (tile_width * i)), 0, 1);
		gridpoints[2 * (board_width + 1) + 2 * i + 1] = glm::vec4((board_width + 1) * tile_width, (tile_width + (tile_width * i)), 0, 1);
	}

	// 将所有线赋成白色
	for (int i = 0; i < (board_line_num * 2); i++)
		gridcolours[i] = white;

	// 初始化棋盘格,并将没有被填充的格子设置成黑色
	glm::vec4 boardpoints[points_num];
	for (int i = 0; i < points_num; i++)
		board_colours[i] = black;

	// 对每个格子,初始化6个顶点,表示两个三角形,绘制一个正方形格子
	for (int i = 0; i < board_height; i++)
		for (int j = 0; j < board_width; j++)
		{
			glm::vec4 p1 = glm::vec4(tile_width + (j * tile_width), tile_width + (i * tile_width), .5, 1);
			glm::vec4 p2 = glm::vec4(tile_width + (j * tile_width), tile_width * 2 + (i * tile_width), .5, 1);
			glm::vec4 p3 = glm::vec4(tile_width * 2 + (j * tile_width), tile_width + (i * tile_width), .5, 1);
			glm::vec4 p4 = glm::vec4(tile_width * 2 + (j * tile_width), tile_width * 2 + (i * tile_width), .5, 1);
			boardpoints[6 * (board_width * i + j) + 0] = p1;
			boardpoints[6 * (board_width * i + j) + 1] = p2;
			boardpoints[6 * (board_width * i + j) + 2] = p3;
			boardpoints[6 * (board_width * i + j) + 3] = p2;
			boardpoints[6 * (board_width * i + j) + 4] = p3;
			boardpoints[6 * (board_width * i + j) + 5] = p4;
		}

	// 将棋盘格所有位置的填充与否都设置为false(没有被填充)
	for (int i = 0; i < board_width; i++)
		for (int j = 0; j < board_height; j++) {
			board[i][j] = false;
			posColors[i][j] = black;
		}


	// 载入着色器
	std::string vshader, fshader;
	vshader = "shaders/vshader.glsl";
	fshader = "shaders/fshader.glsl";
	GLuint program = InitShader(vshader.c_str(), fshader.c_str());
	glUseProgram(program);

	locxsize = glGetUniformLocation(program, "xsize");
	locysize = glGetUniformLocation(program, "ysize");

	GLuint vPosition = glGetAttribLocation(program, "vPosition");
	GLuint vColor = glGetAttribLocation(program, "vColor");


	glGenVertexArrays(3, &vao[0]);
	glBindVertexArray(vao[0]);		// 棋盘格顶点

	glGenBuffers(2, vbo);

	// 棋盘格顶点位置
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, (board_line_num * 2) * sizeof(glm::vec4), gridpoints, GL_STATIC_DRAW);
	glVertexAttribPointer(vPosition, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vPosition);

	// 棋盘格顶点颜色
	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, (board_line_num * 2) * sizeof(glm::vec4), gridcolours, GL_STATIC_DRAW);
	glVertexAttribPointer(vColor, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vColor);


	glBindVertexArray(vao[1]);		// 棋盘格每个格子

	glGenBuffers(2, &vbo[2]);

	// 棋盘格每个格子顶点位置
	glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
	glBufferData(GL_ARRAY_BUFFER, points_num * sizeof(glm::vec4), boardpoints, GL_STATIC_DRAW);
	glVertexAttribPointer(vPosition, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vPosition);

	// 棋盘格每个格子顶点颜色
	glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
	glBufferData(GL_ARRAY_BUFFER, points_num * sizeof(glm::vec4), board_colours, GL_DYNAMIC_DRAW);
	glVertexAttribPointer(vColor, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vColor);


	glBindVertexArray(vao[2]);		// 当前方块

	glGenBuffers(2, &vbo[4]);

	// 当前方块顶点位置
	glBindBuffer(GL_ARRAY_BUFFER, vbo[4]);
	glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(glm::vec4), NULL, GL_DYNAMIC_DRAW);
	glVertexAttribPointer(vPosition, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vPosition);

	// 当前方块顶点颜色
	glBindBuffer(GL_ARRAY_BUFFER, vbo[5]);
	glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(glm::vec4), NULL, GL_DYNAMIC_DRAW);
	glVertexAttribPointer(vColor, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vColor);


	glBindVertexArray(0);

	glClearColor(0, 0, 0, 0);

	// 游戏初始化
	newtile();
	// starttime = glutGet(GLUT_ELAPSED_TIME);
}

// 在棋盘上有足够空间的情况下旋转当前方块
void rotate()
{
	// 计算得到下一个旋转方向
	int nextrotation = (rotation + 1) % 4;

	// 检查当前旋转之后的位置的有效性
	if (checkvalid((allRotationsLshape[shapeLike][nextrotation][0]) + tilepos)
		&& checkvalid((allRotationsLshape[shapeLike][nextrotation][1]) + tilepos)
		&& checkvalid((allRotationsLshape[shapeLike][nextrotation][2]) + tilepos)
		&& checkvalid((allRotationsLshape[shapeLike][nextrotation][3]) + tilepos))
	{
		// 更新旋转,将当前方块设置为旋转之后的方块
		rotation = nextrotation;
		for (int i = 0; i < 4; i++)
			tile[i] = allRotationsLshape[shapeLike][rotation][i];

		updatetile();
	}
}

// 检查棋盘格在row行有没有被填充满
void checkfullrow(int row)
{
	//如果没有填满row行则返回函数
	int i;
	for (i = 0; i < board_width; i++) {
		if (board[i][row] == false)
			return;
	}

	//消除成功,进行加分
	score += 10;
	lowSpeed -= 0.05;	//加快降落速度
	system("cls");
	welcome();

	//否则消去那一行,并将其上的格子下移一格
	for (int j = row; j < board_height; j++) {		//从被消除的行开始向上判断
		for (i = 0; i < board_width; i++) {

			if (j + 1 < board_height && board[i][j + 1] == true) {
				//若上面一行有格子,则复制到当前行
				changecellcolour(glm::vec2(i, j), posColors[i][j + 1]);
				board[i][j] = true;
			}
			else {	//否则填充黑色
				changecellcolour(glm::vec2(i, j), black);
				board[i][j] = false;
			}
		}
	}
}

// 放置当前方块,并且更新棋盘格对应位置顶点的颜色VBO
void settile()
{
	// 每个格子
	for (int i = 0; i < 4; i++)
	{
		// 获取格子在棋盘格上的坐标
		int x = (tile[i] + tilepos).x;
		int y = (tile[i] + tilepos).y;
		// 将格子对应在棋盘格上的位置设置为填充
		board[x][y] = true;
		// 并将相应位置的颜色修改
		changecellcolour(glm::vec2(x, y), colors[shapeLike]);
	}

	//对每一个格子所在的行进行检测,看该行是否已满
	for (int i = 0; i < 4; i++)
	{
		// 获取格子在棋盘格上的纵坐标并进行检测
		int y = (tile[i] + tilepos).y;
		checkfullrow(y);
	}
}

// 给定位置(x,y),移动方块。有效的移动值为(-1,0),(1,0),(0,-1),分别对应于向
// 左,向下和向右移动。如果移动成功,返回值为true,反之为false
bool movetile(glm::vec2 direction)
{
	// 计算移动之后的方块的位置坐标
	glm::vec2 newtilepos[4];
	for (int i = 0; i < 4; i++)
		newtilepos[i] = tile[i] + tilepos + direction;

	// 检查移动之后的有效性
	if (checkvalid(newtilepos[0]) && checkvalid(newtilepos[1])
		&& checkvalid(newtilepos[2]) && checkvalid(newtilepos[3]))
	{
		// 有效:移动该方块
		tilepos.x = tilepos.x + direction.x;
		tilepos.y = tilepos.y + direction.y;

		updatetile();

		return true;
	}

	return false;
}

// 重新启动游戏
void restart()
{
	system("cls");		//清除终端内容
	cout << "游戏已重新开始!\n期待您再次大放异彩!!!" << endl << endl;
	init();
}

// 游戏渲染部分
void display()
{
	glClear(GL_COLOR_BUFFER_BIT);

	glUniform1i(locxsize, xsize);
	glUniform1i(locysize, ysize);

	glBindVertexArray(vao[1]);
	glDrawArrays(GL_TRIANGLES, 0, points_num); // 绘制棋盘格 (width * height * 2 个三角形)
	glBindVertexArray(vao[2]);
	glDrawArrays(GL_TRIANGLES, 0, 24);	 // 绘制当前方块 (8 个三角形)
	glBindVertexArray(vao[0]);
	glDrawArrays(GL_LINES, 0, board_line_num * 2);		 // 绘制棋盘格的线

}

// 在窗口被拉伸的时候,控制棋盘格的大小,使之保持固定的比例。
void reshape(GLsizei w, GLsizei h)
{
	xsize = w;
	ysize = h;
	glViewport(0, 0, w, h);
}

// 键盘响应事件中的特殊按键响应
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
	if (!gameover)
	{
		switch (key)
		{
			// 控制方块的移动方向,更改形态
		case GLFW_KEY_UP:	// 向上按键旋转方块
			if (action == GLFW_PRESS || action == GLFW_REPEAT)
			{
				rotate();
				break;
			}
			else
			{
				break;
			}
		case GLFW_KEY_DOWN: // 向下按键移动方块
			if (action == GLFW_PRESS || action == GLFW_REPEAT) {
				if (!movetile(glm::vec2(0, -1)))
				{
					settile();
					newtile();
					break;
				}
				else
				{
					break;
				}
			}
		case GLFW_KEY_LEFT:  // 向左按键移动方块
			if (action == GLFW_PRESS || action == GLFW_REPEAT) {
				movetile(glm::vec2(-1, 0));
				break;
			}
			else
			{
				break;
			}
		case GLFW_KEY_RIGHT: // 向右按键移动方块
			if (action == GLFW_PRESS || action == GLFW_REPEAT) {
				movetile(glm::vec2(1, 0));
				break;
			}
			else
			{
				break;
			}
			// 游戏设置。
		case GLFW_KEY_ESCAPE:
			if (action == GLFW_PRESS) {
				exit(EXIT_SUCCESS);
				break;
			}
			else
			{
				break;
			}
		case GLFW_KEY_Q:
			if (action == GLFW_PRESS) {
				exit(EXIT_SUCCESS);
				break;
			}
			else
			{
				break;
			}

		case GLFW_KEY_R:
			if (action == GLFW_PRESS) {
				restart();
				break;
			}
			else
			{
				break;
			}
		case GLFW_KEY_SPACE: // 空格键,快速下降到底部
			if (action == GLFW_PRESS || action == GLFW_REPEAT) {
				while (movetile(glm::vec2(0, -1))) {
					// 继续下移方块
				}
				// 下移到底部后,设置方块并生成新方块
				settile();
				newtile();
			}
			break;
		}
	}
}


int main(int argc, char** argv)
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

	// 创建窗口
//设置字符格式
#pragma execution_character_set("utf-8");
	GLFWwindow* window = glfwCreateWindow(500, 900, "2021150047_hyf_期中大作业", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window!" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	glfwSetKeyCallback(window, key_callback);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	init();
	int flag = clock();
	while (!glfwWindowShouldClose(window))
	{

		display();
		glfwSwapBuffers(window);
		glfwPollEvents();

		//使得每隔一秒方块下落一格
		int now = clock();		//记录当前时间
		if (now - flag >= 1000)		//相差1s
		{
			if (!movetile(glm::vec2(0, lowSpeed)))	//向下移动,若下移失败则执行下面部分
			{
				settile();			//放置在底部
				newtile();			//新建方块
			}
			flag = now;				//更新当前时间
		}
	}
	glfwTerminate();
	return 0;
}

(by 归忆)

你可能感兴趣的:(计算机图形学,俄罗斯方块,深圳大学,计算机图形学,opengl)