窗口布局與游戲截圖:
實現思路:
1.使用二維數組模擬整個游戲網格,並將二維數組中每個數的大小用作游戲中每個網格中的數據。
2.將對游戲的數據及數據的操作(即玩家的操作對游戲數據的影響)作為一個類,游戲的界面作為一個類,前一個類作為后一個類的成員存在
具體實現:
第一個類:游戲數據與數據操作
類的定義:
class myGame{ public: myGame(); void InitGame(); // 初始化游戲,清除已有數據,並在4*4的地圖中隨機在一個空位產生數字2 void CreateNextTwo(); // 隨機一個空位,並將這個位置的數字置為2 // 游戲操作,即將整個地圖中的數字向上(下、左、右)移動,直到碰到地圖邊界或者被某個已存 // 在數字的位置擋住,在兩個含有相同數字的位置碰撞時,兩個數字會疊加作為前面那個位置新的 // 數字,而后一個位置被置為空 void Up(); void Down(); void Left(); void Right(); public: int data[MODE][MODE]; // 游戲數據,即地圖上每個位置存在的數字大小,如果某個位置為空(即沒有數字),則用0表示 bool isEmpty[MODE][MODE]; // 用於記錄每個位置是否為空 int cntEmpty; // 記錄此時游戲中空位置的個數 // 用途:1.當空位置為0時,游戲失敗 // 2.用於產生隨機數,來確定下一次出現數字2的位置 int score; // 用於計分,計分規則:整個地圖中所有數字之和 };
類中函數的具體實現:
myGame::myGame() { InitGame(); CreateNextTwo(); } void myGame::InitGame() { score = 0; // 將得分初始化為0 // 初始化所有位置上的數字,使其全為0 for (int i = 0; i < MODE; i++){ for (int j = 0; j < MODE; j++){ data[i][j] = 0; } } // 重置cntEmpty的大小 cntEmpty = MODE * MODE; } void myGame::CreateNextTwo() { srand((unsigned)time(NULL)); // 初始化隨機數生成器 int area = rand() % cntEmpty; // 產生下一個隨機出現數字的位置 int cnt = 0; // 遍歷整個二位數組,將隨機到的位置上的數字設為2, for (int i = 0; i < MODE; i++){ for (int j = 0; j < MODE; j++){ if (data[i][j] == 0){ cnt++; if (area == cnt - 1){ data[i][j] = 2; break; } } } if (area == cnt - 1){ break; } } // 地圖空位減一 cntEmpty--; score += 2; // 總分加2 } void myGame::Up() { for (int i = 0; i < MODE; i++) { for (int j = 0; j < MODE; j++) { int cnt = j; while (data[j][i] == 0) { cnt++; // 將正在處理的數的下面的數據向上移動 for (int k = j; k < MODE - 1; k++) { data[k][i] = data[k + 1][i]; } // 將最下面的數置為0 data[MODE - 1][i] = 0; if (cnt >= MODE - 1) { break; } } if (j != 0 && data[j][i] == data[j - 1][i]) { data[j - 1][i] *= 2; data[j][i] = 0; cnt = j; while (data[j][i] == 0) { cnt++; // 將正在處理的數的下面的數據向上移動 for (int k = j; k < MODE - 1; k++) { data[k][i] = data[k + 1][i]; } // 將最下面的數置為0 data[MODE - 1][i] = 0; if (cnt >= MODE - 1) { break; } } } } } } void myGame::Down() { for (int i = 0; i < MODE; i++) { for (int j = 0; j < MODE; j++) { int cnt = j; while (data[MODE - 1 - j][i] == 0) { cnt++; // 將此事正在處理的數的上面的數向下平移 for (int k = j; k < MODE - 1; k++) { data[MODE - 1 - k][i] = data[MODE - 1 - k - 1][i]; } // 將最上面的數置為0 data[0][i] = 0; if (cnt >= MODE - 1) { break; } } if (j != 0 && data[MODE - 1 - j][i] == data[MODE -j][i]) { data[MODE - j][i] *= 2; data[MODE - 1 - j][i] = 0; cnt = j; while (data[MODE - 1 - j][i] == 0) { cnt++; // 將此事正在處理的數的上面的數向下平移 for (int k = j; k < MODE - 1; k++) { data[MODE - 1 - k][i] = data[MODE - 1 - k - 1][i]; } // 將最上面的數置為0 data[0][i] = 0; if (cnt >= MODE - 1) { break; } } } } } } void myGame::Left() { for (int i = 0; i < MODE; i++) { for (int j = 0; j < MODE; j++) { int cnt = j; // 用於計數 // 當此時處理的數為0時 while (data[i][j] == 0) { cnt++; // 將這個數后面的數向前移動 for (int k = j; k < MODE - 1; k++) { data[i][k] = data[i][k + 1]; } // 將最后面的數置為0 data[i][MODE - 1] = 0; // 如果處理次數達到最大,退出循環,避免因為一組數據全部為0時導致無限循環 if (cnt >= MODE - 1) { break; } } // 當此時處理的數據不是第一個數據,且其與在它前一位的數據相等時 if (j != 0 && data[i][j] == data[i][j - 1]) { // 依據游戲規則,將這兩個數合並,並將合並后的值作為前一個數的值,並將后一個數的值置為0 data[i][j - 1] *= 2; data[i][j] = 0; cnt = j; // 同上一步,處理為0的數據 while (data[i][j] == 0) { cnt++; // 將這個數后面的數向前移動 for (int k = j; k < MODE - 1; k++) { data[i][k] = data[i][k + 1]; } // 將最后面的數置為0 data[i][MODE - 1] = 0; // 如果處理次數達到最大,退出循環,避免因為一組數據全部為0時導致無限循環 if (cnt >= MODE - 1) { break; } } } } } } void myGame::Right() { for (int i = 0; i < MODE; i++) { for (int j = 0; j < MODE; j++) { int cnt = j; while (data[i][MODE - 1 - j] == 0) { cnt++; // 將左邊的數據向右移動 for (int k = j; k < MODE - 1; k++) { data[i][MODE - 1 - k] = data[i][MODE - 1 - k - 1]; } // 將最左邊的數據置為0 data[i][0] = 0; if (cnt >= MODE - 1) { break; } } if (j != 0 && data[i][MODE - 1 - j] == data[i][MODE - j ]) { data[i][MODE - j] *= 2; data[i][MODE - 1 - j] = 0; int cnt = j; while (data[i][MODE - 1 - j] == 0) { cnt++; // 將左邊的數據向右移動 for (int k = j; k < MODE - 1; k++) { data[i][MODE - 1 - k] = data[i][MODE - 1 - k - 1]; } // 將最左邊的數據置為0 data[i][0] = 0; if (cnt >= MODE - 1) { break; } } } } } }
第二個類:游戲界面與游戲中的操作
類的定義:
class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); void ShowGame(); private slots: void on_pushButton_up_clicked(); void on_pushButton_down_clicked(); void on_pushButton_left_clicked(); void on_pushButton_right_clicked(); void on_actionReset_triggered(); void on_actionQuit_triggered(); private: Ui::MainWindow *ui; QTextBrowser *textBrower[MODE][MODE]; myGame game; };
類的構造函數:
整個界面使用Qt Designer畫的
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 關聯 textBrower[0][0] = ui->textBrowser1; textBrower[0][1] = ui->textBrowser_2; textBrower[0][2] = ui->textBrowser_3; textBrower[0][3] = ui->textBrowser_4; textBrower[1][0] = ui->textBrowser_5; textBrower[1][1] = ui->textBrowser_6; textBrower[1][2] = ui->textBrowser_7; textBrower[1][3] = ui->textBrowser_8; textBrower[2][0] = ui->textBrowser_9; textBrower[2][1] = ui->textBrowser_10; textBrower[2][2] = ui->textBrowser_11; textBrower[2][3] = ui->textBrowser_12; textBrower[3][0] = ui->textBrowser_13; textBrower[3][1] = ui->textBrowser_14; textBrower[3][2] = ui->textBrowser_15; textBrower[3][3] = ui->textBrowser_16; ShowGame(); }
信號、槽(對用戶操作的反應):
void MainWindow::ShowGame() // 將整個游戲數據顯示到游戲界面 { for (int i = 0; i < MODE; i++) { for (int j = 0; j < MODE; j++) { if (game.data[i][j] != 0) // 當不為0時顯示數字 { textBrower[i][j]->setText(QString::number(game.data[i][j])); } else // 當為0時不顯示 { textBrower[i][j]->setText(" "); } } } ui->lineEdit->setText(QString::number(game.score)); ui->lineEdit_2->setText(QString::number(MODE)); } // 各個操作 void MainWindow::on_pushButton_up_clicked() { game.Up(); game.CreateNextTwo(); ShowGame(); } void MainWindow::on_pushButton_down_clicked() { game.Down(); game.CreateNextTwo(); ShowGame(); } void MainWindow::on_pushButton_left_clicked() { game.Left(); game.CreateNextTwo(); ShowGame(); } void MainWindow::on_pushButton_right_clicked() { game.Right(); game.CreateNextTwo(); ShowGame(); } void MainWindow::on_actionReset_triggered() { game.InitGame(); game.CreateNextTwo(); ShowGame(); } void MainWindow::on_actionQuit_triggered() { close(); }
總結:
在整個的實現過程中,卡殼的地方主要是對整個數據的上下左右的操作的實現。總是想着一下子將整個操作步驟寫完,認為自己
想的實現方法在邏輯上沒有問題,那么實現之后也就不會出問題。然而總會發現有着自己沒有考慮到的地方,並且程導致序運行
后出現的錯誤不止如何改正。后來學乖了,老老實實的將整個步驟一步步拆開,一步步的實現。
在寫將二維數組的整個數據向某個方向移動時(上述類 myGame中寫的游戲操作),雖然移動的方向不同,但整體邏輯是一樣的,
並且在每一的方向上的移動,都可以將二維數組拆分為MODE個一維數組,而對於每個一維數組的操作都是相同的,可以先實現
對一維數組的操作,然后在推廣到二維數組。代碼如下:
void OnePart(int *data) { for (int i = 0; i < MODE; i++){ int cnt = i; // 為0的情況 while (data[i] == 0){ cnt++; for (int j = i; j < MODE - 1; j++){ data[j] = data[j + 1]; } data[MODE - 1] = 0; if (cnt >= MODE - 1){ break; } } // 不為0且此時處理的數不是這一組第一個被處理的數的情況 if (i != 0 && data[i] == data[i - 1]){ data[i - 1] = 2 * data[i - 1]; data[i] = 0; cnt = i; while (data[i] == 0){ cnt++; for (int k = i; k < 5 - 1; k++){ data[k] = data[k + 1]; } data[MODE - 1] = 0; if (cnt >= MODE - 1){ break; } } } } }