C++俄羅斯方塊


前言

一個禮拜前想做一個俄羅斯方塊小游戲,因為想用c++實現,但又受制於界面,於是苦讀了幾天的Qt。昨天開工連帶一個不眠夜,總算是大功告成,個中滋味,怕是只有自己知道。


簡介

俄羅斯方塊,c++,qt。
功能:常規俄羅斯方塊具有的:方塊旋轉,左移,右移,下落加速,消行,提示下一塊樣式等等都已實現。同時實現了記分以及暫停的功能。


效果圖


游戲過程效果圖

這里寫圖片描述


暫停效果圖

這里寫圖片描述


游戲結束效果圖

這里寫圖片描述


實現思路


提到俄羅斯方塊,稍微麻煩一些的地方只有三點


1. 方塊旋轉

#1##
#1##
#11#
####

####
##1#
111#
####

####
#11#
##1#
##1#

####
#111
#1##
####

上面四個小矩陣我們很容易看出,它是L型圖案的四種不同形態,用4*4的矩陣來將它們統一表示,再細心一點的小伙伴還會發現,它們的順序也是按照逆時針旋轉進行排列的。
那么加入我們對上面四個小矩陣進行編號為1,2,3,4。
那么顯然1-旋轉-> 2 -旋轉-> 3 -旋轉-> 4 -旋轉->1
如果我們開辟一NEXT數組用來保存方塊對應的旋轉后的方塊編號。
則NEXT[1] = 2; NEXT[2] = 3; NEXT[3] = 4; NEXT[4] = 1;
舉一反三,別的形狀方塊也是一樣的。


2. 碰撞檢測

上文,我們用一個四維小矩陣來表示方塊,我們可以對它規定一個重心,索性就用左上角(0, 0)點來作為重心吧。
這個重心只是用來與實際地圖相對應的一個相對點而已。
我們以將小矩陣忽略,只在意重心那個點,通過重心點所在地圖的坐標,顯然可以求出其他點位於地圖的坐標。
這樣一來,每次操作方塊時,將方塊將要變換到的位置與地圖進行比較,通過簡單的判斷,可以得出是否有重復部分,若重復,則可以移動,否則不可以。


3. 方塊自動下降

這個是最容易解決的,qt里有個QTimer類,有定時功能,設定一定時間間隔,觸發timerEvent事件。在事件里,做你想做的即可。


代碼構成

全部代碼僅實現三個類

1. Board 游戲地圖信息

class Board
{

public:
int score;//當前分數
int maxScore;//最高分
int time;//每次下落的間隔時間
int width;//地圖寬
int height;//地圖高
Block *block;//下落的方塊
char map[100][100];//保存地圖信息

Board(int, int);
void confirm();//將下落到底的塊更新到map
bool isEnd();//判斷是否游戲結束
};

2. Block 方塊信息及操作

class Block
{

public:
char BLOCKS[20][5][5];//各個類型的方塊
int NEXT[20];//模擬指向用於方塊旋轉
Qt::GlobalColor COLOR[20];//各個類型方塊的顏色
int x;//塊重心起始坐標
int y;
int type;//塊id
int nextType;//下一個塊id
Board *board;
Block(Board *);//構造函數
void toNext();//更改塊id
void moveUp();//變形
void moveRight();//加速
void moveLeft();//左移
void moveDown();//右移
bool detect(int);//碰撞檢測
};

3. mainWindow 游戲界面部分

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
bool flag;//判斷是否暫停狀態
QTimer *timer;//定時器
Board *board;//游戲所用地圖類

MainWindow(Board *, QWidget *parent = 0);
void paintEvent(QPaintEvent *event);//繪制界面
void keyPressEvent(QKeyEvent *);//鍵盤事件處理

signals:

public slots:
void timerEvent();//定時事件

private:
};

核心代碼

粗略估計了下,代碼總量約500行,所以這里就只貼自己認為核心的部分,完整項目文件及可執行程序會上傳到git上,下面會給出鏈接。有興趣的朋友去down一下即可。

碰撞檢測

bool Block::detect(int flag)
{
//獲得當前操作的目標狀態:目標坐標,目標方塊類型。
int nextX, nextY, nextType;
// 0,1,2,3對應上下左右
switch(flag)
{
case 0: nextX = x; nextY = y; nextType = NEXT[type]; break;
case 1: nextX = x+1; nextY = y; nextType = type; break;
case 2: nextX = x; nextY = y-1; nextType = type; break;
case 3: nextX = x; nextY = y+1; nextType = type; break;
}

for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
{
//tx,ty表示i,j所對應的地圖的實際位置。
int tx = nextX+i;
int ty = nextY+j;
//邊界處理
if(tx < 0 || tx > board->height+1 || ty < 0 || ty > board->width+1)
continue;
//如果塊與地圖牆相重合,則發生碰撞
if(BLOCKS[nextType][i][j] != '#' && (ty == 0 || ty == board->width+1))
return false;
if(BLOCKS[nextType][i][j] != '#' && board->map[tx][ty] != '#')
return false;
}
return true;
}

方塊沉底后的地圖信息更新及消行記分操作

void Board::confirm()
{
//將塊更新到map
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
{
int tx = block->x + i;
int ty = block->y + j;
if(tx<1 || tx > height || ty < 1 || ty > width)
continue;
if(block->BLOCKS[block->type][i][j] != '#')
map[tx][ty] = block->BLOCKS[block->type][i][j];
}

//消去完整的行並計算行個數
int cnt = 0;
for(int i=height; i>=1; i--)
{
bool flag = false;
for(int j=1; j<=width; j++)
if(map[i][j] == '#')
{
flag = true;
break;
}
if(flag)
continue;
cnt++;
for(int j=i; j>=1; j--)
for(int k=1; k<=width; k++)
map[j][k] = map[j-1][k];
}

//每下落一個塊加5分
score += 5;
//根據同時消去的行的數量指數型記分
//1-10 2-20 3-40 4-80
if(cnt)
score += 10*(1<<cnt);

//實時更新最大值
maxScore = std::max(maxScore, score);

//每下落一個塊,時間間隔減2
time -= 2;
if(time < 0)
time = 0;

//更新塊
block->toNext();
}

完整代碼

我的github鏈接:
https://github.com/shiyi1996/project/tree/master/eluosi

哈哈,分享成果的過程總是令人快樂的。
最后,如果有道友發現其中有出錯的地方,還望不吝指出。
若有更好的寫法也可交流一二,大家一起進步嘛!


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com