窗口布局与游戏截图:
实现思路:
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; } } } } }