代码拉取完成,页面将自动刷新
同步操作将从 slowrookie/Qt-Tetrix 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
http://git.oschina.net/liujiaxingemail/Tetrix/wikis/home
#####思路:
#####实现(下内容只截取了部分重要的函数做说明,但是整体的思路是完整的。具体的请看代码,代码中也有注释。):
void TetrixBoard::drawSquare(QPainter &painter, int x, int y, TetrixShape shape){
QColor color = colorTable[(int)shape];
//实用颜色填充方块
painter.fillRect(x + 1 , y + 1 , SquareWidth - 2, SquareHeight -2,color);
//设置颜色为浅色
painter.setPen(color.light());
painter.drawLine(x , y + SquareHeight - 1 ,x , y);
painter.drawLine(x , y , x + SquareWidth - 1 , y);
painter.setPen(color.dark());
//x + 1 y + 1是由于已经存在浅色的线条 -1 表示将像素宽度为1的线条画在方块内部
painter.drawLine(x + 1, y + SquareHeight - 1,x + SquareWidth - 1 , y + SquareHeight - 1);
painter.drawLine(x + SquareWidth - 1 , y + SquareHeight -1 ,x + SquareWidth - 1, y + 1);
}
创建TetrixPiece类,定义一个静态的static const int coordsTable[8][4][4][4]三位数组,具体参照源码,没有复杂的变化与计算,一目了然。1表示有值,0表示无值,有值才画。
TetrixShape枚举一 一对应对于三维数组的8种形状,TetrixDirection为每个方块的四种形状
void TetrixPiece::setShape(TetrixShape tetrixShape,TetrixDirection tetrixDirection){
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
coords[i][j] = coordsTable[tetrixShape][tetrixDirection][i][j];
}
}
pieceShape = tetrixShape;
pieceDirection = tetrixDirection;
}
void TetrixPiece::setRandomShape(){
setShape(TetrixShape( qrand() % 7 + 1),TetrixDirection(qrand() % 4));
}
在TetrixBoard中创建TetrixPiece对象,并在TetrixBoard的初始化函数中,给TeitixPiece初始化赋值
//画出当前的形状
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
if(currentPiece.value(i,j) == 0){
continue;
}
int x = (j - currentPiece.x() + curX) * SquareWidth + GameBoardBorder;
int y = (i - currentPiece.y() + curY) * SquareHeight + GameBoardBorder;
drawSquare(painter,x,y,currentPiece.shape());
}
}
bool TetrixBoard::tryMove(const TetrixPiece &newPiece, int newX, int newY){
currentPiece = newPiece;
curX = newX;
curY = newY;
update();
return true;
}
* 在此步骤下该方法只需要这么多,相对于源码去除的地方只是边界的判断。
2. 事件处理
```C++
void TetrixBoard::keyPressEvent(QKeyEvent *event){
switch(event->key()){
case Qt::Key_Left:
tryMove(currentPiece,curX - 1,curY);
break;
case Qt::Key_Right:
tryMove(currentPiece,curX + 1,curY);
break;
case Qt::Key_Down:
if(!tryMove(currentPiece,curX,curY+1)){
pieceDroped();
}
break;
case Qt::Key_J:
tryMove(currentPiece.rotateLeft(),curX,curY);
break;
case Qt::Key_Up:
tryMove(currentPiece.rotateRight(),curX,curY);
break;
case Qt::Key_Space:
dropDown();
break;
default:
//事件传递
QFrame::keyPressEvent(event);
}
}
```
* currentPiece.rotateLeft(),currentPiece.rotateRight()这两个方法就是用来改变当前方块的方向
* 旋转的实现方式先设置好形状和方向,然后在三维数组中找到需要的二维数组模型。
##### <a id="five">游戏边框,越界判断。</a>
1. 对于边界判断
```C++
bool TetrixBoard::tryMove(const TetrixPiece &newPiece, int newX, int newY){
//是否越界
if((newPiece.getHeight() + newY) > BoardHeight){
return false;
}else if((newPiece.getWidth() + newX) > BoardWidth || newX < 0){
return false;
}else{
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
//如果模型中的值为零,则跳过
if(newPiece.value(i,j) == 0){
continue;
}
if(shapeAt(j - currentPiece.x() + newX , i - currentPiece.y() + newY) != NOShape){
return false;
}
}
}
}
currentPiece = newPiece;
curX = newX;
curY = newY;
update();
return true;
}
```
* BoardHeight、BoardWidth分别是整个游戏的行高和列宽。简单点说就是方块的可移动范围都是在TetrixShape coordsBoard[BoardWidth][BoardHeight];这样一个二维数组中变化。
* 在coordsBoard是否有值的判断
```C++
TetrixShape &shapeAt(int x, int y){return coordsBoard[x][y];}
```
* 该方法主要是根据x,y看coordsboard中是否有值,有值则表示无法移动,返回false
* 而此处调用时的计算,主要是由于currentPiece.x(),currentPiece.y()返回真实的模型位置。
##### <a id="six">记录方块,满行判断。</a>
* 在无法移动之后,则调用方法
```C++
void TetrixBoard::dropDown(){
int newY = curY;
while(newY < BoardHeight){
if(!tryMove(currentPiece,curX,curY+1)){
break;
}
++newY;
}
pieceDroped();
}
void TetrixBoard::pieceDroped(){
//无法移动,则根据当前位置将方块赋值到board
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
if(currentPiece.value(i,j) == 0){
continue;
}
shapeAt(j - currentPiece.x() + curX,i - currentPiece.y() + curY) = currentPiece.shape();
}
}
removeFullLines();
newPiece();
}
```
* dropDown的处理主要是用于瞬间落下的效果,按空格键可触发。
* pieceDroped则是将当前无法移动的每个小放宽赋值到界面数组中
* 得分计算
```C++
void TetrixBoard::removeFullLines(){
int numFullLines = 0;
for(int i = 0; i < BoardHeight; i++){
bool isFullLine = true;
for(int j = 0; j < BoardWidth; j++){
if(shapeAt(j,i) == NOShape){
isFullLine = false;
break;
}
}
if(isFullLine){
//消除当前的满行
++ numFullLines;
for(int j = 0; j < BoardWidth; j++){
shapeAt(j,i) = NOShape;
}
//将上一行的数据向下移动一行
for(int k = i; k > 0; k--){
for(int j = 0; j < BoardWidth; j++){
shapeAt(j,k) = shapeAt(j,k - 1);
}
}
}
}
//关卡分数计算
if(numFullLines > 0){
score += numFullLines;
emit scoreChanged(score);
if( score - (level - 1) * 25 >= 25){
++level;
timer.start(1000/level,this);
emit levelChanged(level);
}
update();
}
}
```
* 第一个外层的for循环是行,里面的是列,首先判断该行是否全部为空,如果不是则将其全部赋值为NOShape。
* 在消除后要将消除上方的方块全部往下移动一行。
* numFullLines只是记录当前消除的行数,根据numFullLines计算出分数,然后在每25分一关的记录。
* emit scoreChanged(score); emit levelChanged(level);是实用信号槽来改变界面上的值。
##### <a id="seven">界面布局,效果显示</a>
* 在写完逻辑代码后,就需要有个主窗体了,这个可以随意使用QMainWindow或者QWidget。
* 省略了很多方法,但是主要的思路就是这些,主要是理解整个思路,然后按照自己的理解开发。
* 界面的逻辑比较单一,主要就是布局好,然后连上信号槽就Ok了。组件我都去除边框,并且固定了大小。希望一起交流。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。