Qt C++ 2048

這裏寫圖片描述

C++

  • 使用pointer的時候要注意在Vector裏用“&”,如vector

References

遊戲部分:

2048網頁版:http://2048game.com

ToDo

1. UI 成型(3 Dys)

2. Board的Class和隨機生成一兩個Cells和Cells的值(2 Dys)

3. 讓這兩個隨機的Cell能夠上下左右移動的動畫效果(2 Dys)

4. 把能夠合併的Cell合併,並保證每次在空白格生成Cell(2 Dys)

  1. Debug (n Dys)

  2. 分數存儲

– 響應式的UI設計打算把程序寫完了再改。

Window

初始的創建Qt項目,會帶着個form的ui尾綴文件,在designer裏面設計可以生成XML的文件,這裏的XML和Android項目一樣,但格式不同。如果使用這個form則需要在代碼中找到這個ui 組件然後設置在程序裏自由的活動方式。如果只是簡單的程序,不需要對Form有任何效果,使用form能增加建立代碼效率。但優點對於我來說幾乎是不存在的。
這個2048使用的是純手寫代碼運行來的。我把凡是和ui form 相關的代碼文件都刪除了,並不會有任何影響。

其實Matlab的App項目和Qt在UI設計上是差不多的。

Layout

這裏寫圖片描述

使用Gridlayout時,或任何Layout,w是棕色widget,如果用QGridLayout *boardLayout = new QGridLayout(w);會出現左邊的效果,應該使用w.setlayout(boardLayout) 來達到右邊的效果。

Game Plan

這裏寫圖片描述

Silly Problem

使用for loop生成數組,從0生成到第n個數字: i <= n(每次都愚蠢地漏掉“=”, 錯誤地想着i++會幫它實現+1的)

隨機

在C++裏,調用long random()/ int rand()前,srand都會默認先執行,如果不在使用這兩個Functions前設置srand的算子(時間),那麼random/rand每次出來的值都一樣。
這裏寫圖片描述

Qt Click Listener

在Qt裏使用的不是listener,爲了讓上面純手打UI代碼的Q widget 感應到鍵盤,但把它設置setfocusoolicy() 是不管用的,也不能讓Widget w, 做w->keypreeEvent(Key_Up)這種操作,因爲這個Key Press的方程是內在使用(Protected)會報錯(incompleted blablabla),所以唯一的解決辦法:必須讓整個Widget作爲一個自定義的UI Class 然後引用Key_Event 的 keypressEvent Function。如果一個QWidget的自定義UI Class裏包含多個Q widget,則需要用focus的Function去選擇確定一個Widget,否則,KeyEvent一樣沒反應。

Qt Widget Class

可以定義一個UI Class爲亂七八糟(比如我的BoardGUI),然後用: QWidget 作爲implemented 的方法。一定要用:

Header File

Q_OBJECT
public:
    explicit BoardGUI(QWidget *parent = 0);

CPP File

BoardGUI::BoardGUI(QWidget *w) : QWidget(w){...}

因爲本身整個class就是個GUI,所以在添加控件的時候只用使用:this。試過使用我自己設置的w或者0(我以爲用0表示這是parent,這個Class的GUI)都不行。

MOVE U/D/L/R

Action Type Pos Key
UP (PosKey starts from -1) 0 1
LEFT (PosKey starts from -1) 0 0
DOWN (PosKey starts from 4) 1 1
RIGHT (PosKey starts from 4) 1 0

根據上表,UP 和 LEFT 有相同點,則可以合在一個For Loop。所以4個Actions只需要兩個Functions即可。唯一優點可以整合代碼和減少Compile的時間。分成4個也可以。LEFT的Board座標是[ii, jj], 則 LEFT的座標可以爲[jj, ii].
PosKey即下圖所示的玫紅色星星。
使用00,01,10,11來區分方向,移動並更新Cells,例子如下(左移):

for(int ii = 0; ii <= 3; ii++){
        int Pos = -1;
        for(int jj = 0; jj <= 3; jj++){
           /*-----------UP--------------------
            ** 做 UP 部分,判斷Board[jj,ii]
            **(a)如果有非空位Cell/合併相同的Cells
            **(b-1)更新Pos和移動當前Cell的位置到new_X/y 
            **(b-2)Move Animation()
            */

            /*-----------LEFT--------------------
            ** 做 LEFT 部分,判斷Board[ii,jj]
            **(a)如果有非空位Cell/合併相同的Cells
            **(b-1)更新Pos和移動當前Cell的位置到new_X/y
            **(b-2)Move Animation()
            */

        }
}

現階段結果:起始是錯開的2和4,先走的左邊使2和4合併到了一個Column裏。

相加兩個格子

在Cell Class 裏除了Cell的value還加一個Propertybool hasDone爲判斷每移動一次,Cell的相加只會操作一次。使用一個Vector 數組存儲for loop 裏操作過的Cell,然後在每次移動前依次清除這個數組裏的Cell的hasDone的值到default。

中途遇到的Bugs

(1)當2與2合併在它們自己前一個位置,但忘記挪動更新後的位置了(這個Bug讓我鬱悶了好久,以爲就快到達真相了,結果貌似要推倒重來,真的已經在推倒重來路上的時候,突然發現只是差了句代碼,然後試了好久才發現真正差的那句代碼是加在哪裏)
這裏寫圖片描述
解決方法:在合併後只需要更新Pos_key(相當於移動的指針)的位置,不需要重新遍歷一遍再從頭開始挪位置:如LEFT 和 UP 的操作:添加Pos_Key -- 的代碼

每次移動增加一個新的Cell

判斷是否可以移動

因爲如果可以移動,Pos_Key 會被更新到非負一(-1是LEFT和UP的default的值)的狀態。所以整個移動的內容,Pos_Key是關鍵。但由於PosKey 是在Loop時每個Row或Column都會重置的量,可以使用奇偶驗證的思路,使用 bool hasMoved = false; 因此只用最多1次的Pos_Key移動痕跡後,把 hasMoved = true,就算多次賦值True也不影響已經有至少1次移動的事實,緊接着Move做完,就可以Generate 一個新的Cell.

移動的動畫製作

用以下式子執行Cell Class(每個Cell)的動畫:從Cell的一個位置移動到另一個位置(根據上面的sudo Code, 這步Animation只是動畫,屬於僞移動,不對真正的Board的內容做改動)這個不是太難,但是很tricky, 加代碼的位置和怎麼加是我遇到的問題。

中途遇到的bugs

  • 真的把我的Cell移走了,把邏輯和GUI混合了,但是一旦改變了Cell的位置對我的Board就不好跟蹤了。
QPropertyAnimation animation(cell, "geometry");

這裏寫圖片描述
解決方法:
不要用interval,而是在Start裏直接設值,做timeout()的工作。在相應的Class的Header裏聲明Slot:

private slots:
    void drawBoardUI();

在某個地方插入下面代碼,好讓Cell的動作完成再更新整個Board的UI。

QTimer *timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(drawBoardUI()));
//timer->setInterval(300);
timer->start(300);
  • 由上面的解決辦法裏,又出來新的問題:由於Cell(QLabel)是分佈建立在Grid Layout裏的,如果移動一個Cell,就會有很混亂的場景

這裏寫圖片描述
解決方法: 加入另一個Widget在同一個位置,並設置背景,讓原來的Widget背景爲透明,這個可以遮蓋移動後的空缺。

  • 另一個Bug:有動畫錯誤軌跡的殘影和更新整個界面的閃爍
    animation->start(QAbstractAnimation::DeleteWhenStopped);
    並不能在Cell的Animation之後人爲設爲空的,實際上Animation本身還存在着,所以會有之前的殘影。所以需要Call它自己的Delete Function進行“自殺”。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章