实现方块旋转
Tetris.h
#pragma once
#include <vector>
#include <graphics.h>
#include "Block.h"
using namespace std;
class Tetris
{
public://构造函数
Tetris(int rows,int cols,int left,int top,int blockSize);
void init(); //初始化
void play(); //开始游戏
private: //封装
void keyEvent(); //内部使用
void updateWindow();
//返回距离上一次调用该函数,间隔了多长时间,单位毫秒
//第一次调用该函数,返回0
int getDelay();
void drop();
void clearLine();
void moveLeftRight(int offset);
void rotate(); //旋转
private:
int delay;
bool update; //是否更新
//int map[20][10];写死不利于修改
//0:空白,没有任何方块
//5:是第5种俄罗斯方块
vector<vector<int>> map; //表示二维数组
int rows; //总行数
int cols; //总列数
int leftMargin;
int topMargin;
int blockSize;
IMAGE imgBg; //背景图片
Block* curBlock; //当前方块
Block* nextBlock; //预告方块
Block bakBlock; //备用方块,当前方块坠落过程中,备份上一个合法位置
};
Tetris.cpp
#include "Tetris.h"
#include <stdlib.h>
#include <time.h>
#include "Block.h"
#include <graphics.h>
#include <conio.h>
const int SPEED_NORMAL = 500; //普通速度 (不同关卡不同速度)
const int SPEED_QUICK = 50; //快速降落速度
Tetris::Tetris(int rows, int cols, int left, int top, int blockSize)
{
this->rows = rows; //当形参和成员变量同名时,可用this指针来区分
this->cols = cols;
this->leftMargin = left;
this->topMargin = top;
this->blockSize = blockSize;
//二维数组添加元素
for (int i = 0; i < rows; i++) {
vector<int> mapRow; //空行
for (int j = 0; j < cols; j++) {
mapRow.push_back(0); //在数组最后面加一个0
}
map.push_back(mapRow); //添加行
}
}
void Tetris::init()
{
delay = SPEED_NORMAL;
//配置随机种子
srand(time(NULL));
initgraph(938,896); //创建游戏窗口,496,448
loadimage(&imgBg, "res/bg2.png"); //加载背景图片
//初始化游戏区中的数据
char data[20][10];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
map[i][j] = 0;
}
}
}
void Tetris::play()
{
init(); //初始化
nextBlock = new Block; //预告方块
curBlock = nextBlock; //预告方块的下一个方块
nextBlock = new Block; //预告方块进入游戏后的预告方块
//渲染画面CPU高消耗,处理方法,增加延时
int timer = 0;
while (1)
{
//接受用户输入
keyEvent();
//距离上一次循环间隔多长时间,这样对系统消耗并不大
timer += getDelay();//统计输入
//判断时间到没到
if (timer > delay) {
timer = 0;//保证下次循环也间隔相同毫秒(清零)
drop(); //进入游戏的动作,下降
//渲染游戏画面
update = true; //更新游戏标记
}
//检查标记
if (update) {
update = false;
updateWindow();
//更新游戏数据
clearLine();
}
}
}
void Tetris::keyEvent()
{
int dx = 0; //偏移量
bool rotateFlag = false; //旋转标记
unsigned char ch = 0;
//按键输入
while (_kbhit()) {
unsigned char ch = _getch(); //接受输入
if (ch == 224) {
ch = _getch();
//如果按下方向建会自动返回两个字符
//如果按向上方向键,会先返回224,在返回72
switch (ch) {
case 72:
rotateFlag = true;
break;
case 80: //下
delay = SPEED_QUICK; //快速降落
break;
case 75: //左
dx = -1;
break;
case 77: //右
dx = 1;
break;
default:
break;
}
}
}
if (dx != 0) {
//左右移动
moveLeftRight(dx);
update = true; //更新游戏画面
}
if (rotateFlag) {
rotate();
update = true;
}
}
void Tetris::updateWindow()
{
putimage(0, 0, &imgBg); //在x零坐标y零坐标把图片显示出来
//测试
//Block block;
//block.draw(leftMargin,topMargin); //画方块,先画边界方块
//底部方块出现,可以通过判断,大于零说明已经落到底部,在绘制
IMAGE** imgs = Block::getImages();
BeginBatchDraw(); //开始绘制
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (map[i][j] == 0) continue; //等于零说明没方块
//确定位置
int x = j * blockSize + leftMargin; //列乘方块宽度加左侧边界
int y = i * blockSize + topMargin;
putimage(x, y, imgs[map[i][j] - 1]); //绘制方块
}
}
//出现方块
curBlock->draw(leftMargin,topMargin); //出现在游戏中
nextBlock->draw(689,150); //需要计算偏移量,出现在右上角
EndBatchDraw(); //结束绘制
}
//第一次调用返回0
//之后调用需要返回距离上一次调用间隔多少毫秒
int Tetris::getDelay()
{
static unsigned long long lastTime = 0;
unsigned long long currentTime = GetTickCount();
if (lastTime == 0) { //等于零说明为第一次调用
lastTime = currentTime; //将当前时钟数赋值给lastTime
return 0;
}
else {
int ret = currentTime - lastTime; //当前时间减去上一次lastTime说明间隔时间
lastTime = currentTime;
return ret;
}
}
void Tetris::drop()
{
bakBlock = *curBlock;
curBlock->drop(); //方块坠落
//检查方块有没有到底部
if (curBlock->blockInMap(map)==false) {
//方块到底部不动
bakBlock.solidify(map);
delete curBlock; //释放方块
//curBlock = new Block;
curBlock = nextBlock;
nextBlock = new Block; //当前方块生成新方块
}
delay = SPEED_NORMAL; //还原到正常速度
}
void Tetris::clearLine()
{
}
void Tetris::moveLeftRight(int offset)
{
bakBlock = *curBlock; //备份
curBlock->moveLeftRight(offset);
//判断左右移动有没有出界
if (!curBlock->blockInMap(map)) {
*curBlock = bakBlock; //非法位置还原方块
}
}
void Tetris::rotate()
{
if (curBlock->getBlockType() == 7) return;
bakBlock = *curBlock; //备份
curBlock->rotate();
if (!curBlock->blockInMap(map)) {
*curBlock = bakBlock; //非法位置还原方块
}
}
Block.h
#pragma once
#include <graphics.h>
#include <vector>
using namespace std;
struct Point
{
int row;
int col;
};
class Block
{
public:
Block();
void drop(); //下降
void moveLeftRight(int offset); //移动
void rotate(); //旋转
void draw(int leftMargin,int topMargin); //绘制
static IMAGE** getImages();
Block& operator=(const Block& other);
//Point* getSmallBlocks();
bool blockInMap(const vector<vector<int>>&map); //方块是否在一个地图
void solidify(vector<vector<int>>& map); //固化方块
int getBlockType();
private:
int x;
int y;
int blockType;
Point smallBlocks[4];//结构体中存储四种小方块
IMAGE* img; //定义变量 图片表示小方块,指针指向图片
private:
static int size;
static IMAGE* imgs[7]; //静态变量,里面有七个图片数组
};
Block.cpp
#include "Block.h"
#include <stdlib.h>
//初始化
IMAGE* Block::imgs[7] = { NULL, };
int Block::size = 36; //方块大小
Block::Block()
{
// 仅初始化一次
if (imgs[0] == NULL) {
//切割图片,放入内存
IMAGE imgTmp;
loadimage(&imgTmp, "res/tiles.png");//爆红 项目属性改为多节字符集
SetWorkingImage(&imgTmp);
for (int i = 0; i < 7; i++) {
imgs[i] = new IMAGE; //切第几刀放第几个,分配块内存
//第一个参数指向内存放哪,第二,三个是切割点 从哪切,是x y坐标。四五是长宽
getimage(imgs[i], i * size, 0, size, size);
}
SetWorkingImage(); //恢复工作区
//srand(time(NULL));
}
int blocks[7][4] = {
1,3,5,7, // I
2,4,5,7, // Z 1型
3,5,4,6, // Z 2型
3,5,4,7, // T
2,3,5,7, // L
3,5,7,6, // J
2,3,4,5, // 田
};
//随机生成一种俄罗斯方块,新方块类型
blockType= 1+rand() % 7;//这样就是1--7
//初始化,小方块位置几行几列,坐标
for (int i = 0; i < 4; i++) {
smallBlocks[i].row = blocks[blockType - 1][i] / 2;
smallBlocks[i].col = blocks[blockType - 1][i] % 2;
}
//新方块图片配置
img = imgs[blockType - 1];
}
void Block::drop()
{
//一种for(inti=0;i<4;i++){ smallBlocks[il.row++; } 改变结构体成员行标
//第二种
for (auto& block : smallBlocks)
{
block.row++;
}
}
void Block::moveLeftRight(int offset)
{
for (int i = 0; i < 4; i++) {
smallBlocks[i].col += offset; //列坐标加偏移量
}
}
void Block::rotate()
{
Point p = smallBlocks[1];
for (int i=0; i < 4; i++) {
Point tmp = smallBlocks[i];
smallBlocks[i].col = p.col - tmp.row + p.row; //旋转中心的列减去当前行标加上旋转中心行标
smallBlocks[i].row = p.row + tmp.col - p.col; //旋转中心行标加上原来位置列减去旋转中心列标
}
}
void Block::draw(int leftMargin, int topMargin)
{
//绘制正在降落过程的方块
for (int i = 0; i < 4; i++) {
int x = smallBlocks[i].col * size + leftMargin; //左侧边界加上第几列宽度
int y = smallBlocks[i].row * size + topMargin; //顶部
putimage(x, y, img);
}
}
IMAGE** Block::getImages()
{
return imgs; //直接返回数组
}
Block& Block::operator=(const Block& other)
{
if (this == &other) return *this; //如果是当前对象本身,直接返回
this->blockType = other.blockType;
for (int i = 0; i < 4; i++) {
this->smallBlocks[i] = other.smallBlocks[i];
}
return *this;
}
bool Block::blockInMap(const vector<vector<int>>& map)
{
//判断当前方块在此位置是否合法
int rows = map.size();
int cols = map[0].size();
for(int i = 0; i < 4; i++) {
//行标太小或太大,列标太小或太大,当前位置是否有小方块
if (smallBlocks[i].col < 0 || smallBlocks[i].col >= cols
|| smallBlocks[i].row < 0 || smallBlocks[i].row >= rows
|| map[smallBlocks[i].row][smallBlocks[i].col] != 0) {
return false;
}
}
return true;
}
void Block::solidify(vector<vector<int>>& map)
{
for (int i = 0; i < 4; i++) {
// 设置标记,“固化”对应位置
map[smallBlocks[i].row][smallBlocks[i].col] = blockType;
}
}
int Block::getBlockType()
{
return blockType;
}