1 #ifndef GAMEWIDGET_H 2 #define GAMEWIDGET_H 3 4 #include <QWidget> 5 #include <QMouseEvent> 6 #include <QEventLoop> 7 #include <QTimer> 8 #include <QPainter> 9 #include <QList> 10 11 // 手势的方向 12 enum GestureDirect 13 { 14 LEFT = 0, // 向左 15 RIGHT = 1, // 向右 16 UP = 2, // 向上 17 DOWN = 3 // 向下 18 }; 19 20 // 定义动画的类型 21 enum AnimationType 22 { 23 MOVE = 0, // 方格移动动画 24 APPEARANCE = 1 // 方格出现动画 25 }; 26 27 // 动画结构体 28 struct Animation 29 { 30 AnimationType type; // 动画类型 31 GestureDirect direct; // 方向 32 QPointF startPos; // 起始点坐标 出现动画仅仅使用这个坐标 33 QPointF endPos; // 终止点坐标 移动动画的终点坐标 34 int digit; // 数码 35 int digit2; // 第二数码 数码可能被合并 36 }; 37 38 // 游戏部件类 继承自QWidget 39 class GameWidget : public QWidget 40 { 41 Q_OBJECT 42 public: 43 // 构造函数 44 explicit GameWidget(QWidget *parent = 0); 45 46 private: 47 // 游戏面板 存储每个格子的数值 48 int board[4][4]; 49 // 数码的个数 存储当前面板上的数字的个数 50 int digitCount; 51 // 分数 存储当前得分 52 int score; 53 // 起始点坐标 存储手势起点坐标 54 QPoint startPos; 55 // 存储所有需要展现的动画 56 QList<Animation> animationList; 57 // 小格子的宽度和高度 58 qreal w, h; 59 // 缓存图像 60 QImage *cacheImg; 61 // 是否在播放动画效果 62 bool isAnimating; 63 64 // 检测游戏是否结束 65 bool checkGameOver(); 66 // 检测游戏是否获胜 67 bool checkWin(); 68 /* 获取一个数字的二进制位数 当然这里获取的不完全是二进制位数 而是对应颜色数组的下标 69 比如 2 对应 0 8 对应 2*/ 70 int getBitCount(int); 71 // 绘制动画效果 72 bool playAnimation(Animation&, QPainter&); 73 // 鼠标按下触发的事件 74 void mousePressEvent(QMouseEvent *); 75 // 鼠标释放触发的时间 76 void mouseReleaseEvent(QMouseEvent *); 77 // 绘制事件 78 void paintEvent(QPaintEvent *); 79 80 // 以下为一些信号 81 signals: 82 // 手势移动信号 83 void GestureMove(GestureDirect); 84 // 分数增加信号 85 void ScoreInc(int); 86 // 游戏结束信号 87 void GameOver(); 88 // 游戏获胜信号 89 void win(); 90 91 // 以下为一些槽函数 92 public slots: 93 // 处理手势移动信号的槽函数 94 void onGestureMove(GestureDirect); 95 // 重新开始的槽函数 96 void restart(); 97 98 }; 99 100 #endif // GAMEWIDGET_H
1 #include "GameWidget.h" 2 3 // 颜色数组 存储每个数字对应的背景色 4 QColor digitBkg[11] = {QColor::fromRgb(0xFF, 0xFF, 0xCC), QColor::fromRgb(0xFF, 0xFF, 0x99), 5 QColor::fromRgb(0xFF, 0xCC, 0xCC), QColor::fromRgb(0xFF, 0xCC, 0x99), 6 QColor::fromRgb(0xFF, 0x99, 0x99), QColor::fromRgb(0xFF, 0x99, 0x66), 7 QColor::fromRgb(0xFF, 0x66, 0x66), QColor::fromRgb(0xCC, 0x99, 0x66), 8 QColor::fromRgb(0xCC, 0x33, 0x33), QColor::fromRgb(0xCC, 0x00, 0x33), 9 QColor::fromRgb(0xFF, 0x00, 0x00)}; 10 11 // 每个方向位置的增量 12 QPointF dPos[5] = {QPointF(-10, 0), QPointF(10, 0), QPointF(0, -10), QPointF(0, 10), QPointF(-2, -2)}; 13 14 GameWidget::GameWidget(QWidget *parent) : 15 QWidget(parent) 16 { 17 // 连接手势移动信号和相应的槽函数 18 connect(this, SIGNAL(GestureMove(GestureDirect)), SLOT(onGestureMove(GestureDirect))); 19 // 初始化board数组 20 memset(board, 0, sizeof(board)); 21 // 随机填入两个2 22 board[rand() % 4][rand() % 4] = 2; 23 board[rand() % 4][rand() % 4] = 2; 24 // 分数初始化为0 25 score = 0; 26 // 数码个数初始化为2 27 digitCount = 2; 28 isAnimating = false; 29 cacheImg = NULL; 30 } 31 32 void GameWidget::mousePressEvent(QMouseEvent *e) 33 { 34 // 获取起点坐标 35 startPos = e->pos(); 36 } 37 38 void GameWidget::mouseReleaseEvent(QMouseEvent *e) 39 { 40 // 如果在播放动画效果则直接退出防止重复产生手势事件 41 if (isAnimating) 42 return; 43 // 根据终点坐标和起点坐标计算XY坐标的增量 44 float dX = (float)(e->pos().x() - startPos.x()); 45 float dY = (float)(e->pos().y() - startPos.y()); 46 // 确定手势方向 47 if (abs(dX) > abs(dY)) 48 { 49 if (dX < 0) 50 emit GestureMove(LEFT); 51 else 52 emit GestureMove(RIGHT); 53 } 54 else 55 { 56 if (dY < 0) 57 emit GestureMove(UP); 58 else 59 emit GestureMove(DOWN); 60 } 61 } 62 63 void GameWidget::onGestureMove(GestureDirect direct) 64 { 65 int i, j, k; 66 Animation a; 67 // 是否合并过方格 68 bool combine = false; 69 // 处理不同方向 70 switch (direct) 71 { 72 // 向左 73 case LEFT: 74 // 循环每一行 75 for (i = 0; i < 4; i++) 76 { 77 /* 初始化j k为0 78 * 这里j表示要交换的数字列号 79 * k表示交换到的位置的列号 80 * */ 81 j = 0, k = 0, combine = false; 82 while (true) 83 { 84 // 循环找到第一个不是0的数字对应的列号 85 while (j < 4 && board[i][j] == 0) 86 j++; 87 // 如果超过了3则说明搜索完毕 推出循环 88 if (j > 3) 89 break; 90 // 交换两个数字 91 qSwap(board[i][k], board[i][j]); 92 // 记录动画信息 93 a.type = MOVE; 94 a.startPos = QPointF(7 + (w + 5) * j, 7 + (h + 5) * i); 95 a.endPos = QPointF(7 + (w + 5) * k, 7 + (h + 5) * i); 96 a.digit = a.digit2 = board[i][k]; 97 a.direct = LEFT; 98 //如果交换后的数字与其前一列的数字相同 99 if (!combine && k > 0 && board[i][k] == board[i][k - 1]) 100 { 101 // 前一列的数字*2 102 board[i][k - 1] <<= 1; 103 // 这一列的数字置为0 104 board[i][k] = 0; 105 // 记录动画信息 106 a.digit2 = board[i][k - 1]; 107 a.endPos = QPointF(7 + (w + 5) * (k - 1), 7 + (h + 5) * i); 108 // 增加分数 109 score += board[i][k - 1]; 110 // 发射增加分数的信号 111 emit ScoreInc(score); 112 // 数码个数-1 113 digitCount--; 114 combine = true; 115 } 116 else 117 k++; 118 j++; 119 // 添加到动画链表 120 animationList.append(a); 121 } 122 } 123 break; 124 // 其余三个方向与左向类似 125 case RIGHT: 126 for (i = 0; i < 4; i++) 127 { 128 j = 3, k = 3, combine = false; 129 while (true) 130 { 131 while (j > -1 && board[i][j] == 0) 132 j--; 133 if (j < 0) 134 break; 135 qSwap(board[i][k], board[i][j]); 136 a.type = MOVE; 137 a.startPos = QPointF(7 + (w + 5) * j, 7 + (h + 5) * i); 138 a.endPos = QPointF(7 + (w + 5) * k, 7 + (h + 5) * i); 139 a.digit = a.digit2 = board[i][k]; 140 a.direct = RIGHT; 141 if (!combine && k < 3 && board[i][k] == board[i][k + 1]) 142 { 143 board[i][k + 1] <<= 1; 144 board[i][k] = 0; 145 a.digit2 = board[i][k + 1]; 146 a.endPos = QPointF(7 + (w + 5) * (k + 1), 7 + (h + 5) * i); 147 score += board[i][k + 1]; 148 emit ScoreInc(score); 149 digitCount--; 150 combine = true; 151 } 152 else 153 k--; 154 j--; 155 animationList.append(a); 156 } 157 } 158 break; 159 case UP: 160 for (i = 0; i < 4; i++) 161 { 162 j = 0, k = 0, combine = false; 163 while (true) 164 { 165 while (j < 4 && board[j][i] == 0) 166 j++; 167 if (j > 3) 168 break; 169 qSwap(board[k][i], board[j][i]); 170 a.type = MOVE; 171 a.startPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * j); 172 a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * k); 173 a.digit = a.digit2 = board[k][i]; 174 a.direct = UP; 175 if (!combine && k > 0 && board[k][i] == board[k - 1][i]) 176 { 177 board[k - 1][i] <<= 1; 178 board[k][i] = 0; 179 a.digit2 = board[k - 1][i]; 180 a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * (k - 1)); 181 score += board[k - 1][i]; 182 emit ScoreInc(score); 183 digitCount--; 184 combine = true; 185 } 186 else 187 k++; 188 j++; 189 animationList.append(a); 190 } 191 } 192 break; 193 case DOWN: 194 for (i = 0; i < 4; i++) 195 { 196 j = 3, k = 3, combine = false; 197 while (true) 198 { 199 while (j > -1 && board[j][i] == 0) 200 j--; 201 if (j < 0) 202 break; 203 qSwap(board[k][i], board[j][i]); 204 a.type = MOVE; 205 a.startPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * j); 206 a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * k); 207 a.digit = a.digit2 = board[k][i]; 208 a.direct = DOWN; 209 if (!combine && k < 3 && board[k][i] == board[k + 1][i]) 210 { 211 board[k + 1][i] <<= 1; 212 board[k][i] = 0; 213 a.digit2 = board[k + 1][i]; 214 a.endPos = QPointF(7 + (w + 5) * i, 7 + (h + 5) * (k + 1)); 215 score += board[k + 1][i]; 216 emit ScoreInc(score); 217 digitCount--; 218 combine = true; 219 } 220 else 221 k--; 222 j--; 223 animationList.append(a); 224 } 225 } 226 break; 227 } 228 // 如果数字木有填满 229 if (digitCount != 16) 230 { 231 // 随机产生行号和列号 232 i = rand() % 4, j = rand() % 4; 233 // 循环直到行和列对应的元素为0 234 while (board[i][j] != 0) 235 i = rand() % 4, j = rand() % 4; 236 // 填入2 237 board[i][j] = (rand() % 2 + 1) * 2; 238 // 记录动画信息 239 a.type = APPEARANCE; 240 a.startPos = a.endPos = QPointF(7 + (w + 5) * j, 7 + (h + 5) * i); 241 a.startPos += QPointF(w / 2, h / 2); 242 a.digit = board[i][j]; 243 // 数码个数加一 244 digitCount++; 245 } 246 else 247 { 248 // 如果数字填满了 检测游戏是否over 249 if (checkGameOver()) 250 emit GameOver();// 如果游戏over了则发射GameOver信号 251 } 252 253 // 开始绘制动画效果 254 isAnimating = true; 255 // 动画列表的迭代器 256 QList<Animation>::iterator it; 257 // 事件循环 用于延时 258 QEventLoop eventLoop; 259 // 删除之前的缓存图像 260 if (cacheImg) 261 delete cacheImg; 262 // 建立缓存图像 263 cacheImg = new QImage(width(), height(), QImage::Format_ARGB32); 264 // 清空图像 265 cacheImg->fill(0); 266 // 构造一个QPainter对象 267 QPainter painter(cacheImg); 268 // 字体 269 QFont font; 270 font.setFamily("Consolas"); 271 font.setBold(true); 272 font.setPixelSize(40); 273 painter.setFont(font); 274 // 标识所有方格动画是否都播放完毕 275 bool ok = false; 276 while (true) 277 { 278 // 构造一个画刷 颜色为R G B分量分别为141 121 81的颜色 279 QBrush brush(QColor::fromRgb(141, 121, 81)); 280 // 使painter应用这个画刷 281 painter.setBrush(brush); 282 283 // 设置画笔为空笔 目的是使绘制的图形没有描边 284 painter.setPen(Qt::NoPen); 285 286 // 绘制一个矩形 287 painter.drawRect(2, 2, width() - 4, height() - 4); 288 289 // 设置画刷颜色为 RGB分量为171 165 141的颜色 290 brush.setColor(QColor::fromRgb(171, 165, 141)); 291 // 应用这个画刷 292 painter.setBrush(brush); 293 294 // 循环绘制游戏面板 295 for (int i = 0; i < 4; i++) 296 for (int j = 0; j < 4; j++) 297 // 绘制小方格 298 painter.drawRect(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h)); 299 300 // 假设都播放完毕 301 ok = true; 302 303 // 循环播放每个方格动画 304 for (it = animationList.begin(); it != animationList.end(); it++) 305 if (!playAnimation(*it, painter)) 306 ok = false; 307 308 // 刷新部件 309 update(); 310 311 // 全部播放完则退出 312 if (ok) 313 break; 314 315 // 延时5ms 316 QTimer::singleShot(5, &eventLoop, SLOT(quit())); 317 eventLoop.exec(); 318 } 319 // 播放方格出现的动画 320 while (!playAnimation(a, painter)) 321 { 322 update(); 323 QTimer::singleShot(5, &eventLoop, SLOT(quit())); 324 eventLoop.exec(); 325 } 326 //清除所有动画 327 animationList.clear(); 328 //刷新当前部件 329 isAnimating = false; 330 331 // 检测游戏是否获胜 332 if (checkWin()) 333 emit win();// 如果获胜则发射win信号 334 335 update(); 336 } 337 338 bool GameWidget::playAnimation(Animation& a, QPainter& painter) 339 { 340 bool rtn = false; 341 QBrush brush(Qt::SolidPattern); 342 343 // 移动方格位置 344 if (a.type == MOVE) 345 { 346 switch (a.direct) 347 { 348 case LEFT: 349 if (a.startPos.x() > a.endPos.x()) 350 a.startPos += dPos[LEFT]; 351 else 352 a.startPos = a.endPos, rtn = true; 353 break; 354 case RIGHT: 355 if (a.startPos.x() < a.endPos.x()) 356 a.startPos += dPos[RIGHT]; 357 else 358 a.startPos = a.endPos, rtn = true; 359 break; 360 case UP: 361 if (a.startPos.y() > a.endPos.y()) 362 a.startPos += dPos[UP]; 363 else 364 a.startPos = a.endPos, rtn = true; 365 break; 366 case DOWN: 367 if (a.startPos.y() < a.endPos.y()) 368 a.startPos += dPos[DOWN]; 369 else 370 a.startPos = a.endPos, rtn = true; 371 } 372 // 如果方格移动到终点 373 if (!rtn) 374 { 375 brush.setColor(digitBkg[getBitCount(a.digit)]); 376 painter.setBrush(brush); 377 painter.drawRect(QRectF(a.startPos.x(), a.startPos.y(), w, h)); 378 painter.setPen(QColor::fromRgb(0, 0, 0)); 379 painter.drawText(QRectF(a.startPos.x(), a.startPos.y(), w, h), Qt::AlignCenter, 380 QString::number(a.digit)); 381 } 382 else 383 { 384 brush.setColor(digitBkg[getBitCount(a.digit2)]); 385 painter.setBrush(brush); 386 painter.drawRect(QRectF(a.startPos.x(), a.startPos.y(), w, h)); 387 painter.setPen(QColor::fromRgb(0, 0, 0)); 388 painter.drawText(QRectF(a.startPos.x(), a.startPos.y(), w, h), Qt::AlignCenter, 389 QString::number(a.digit2)); 390 } 391 painter.setPen(Qt::NoPen); 392 } 393 else 394 { 395 // 方格出现的动画效果 396 if (a.startPos.x() > a.endPos.x()) 397 a.startPos += dPos[4]; 398 else 399 a.startPos = a.endPos, rtn = true; 400 brush.setColor(digitBkg[getBitCount(a.digit)]); 401 painter.setBrush(brush); 402 painter.drawRect(QRectF(a.startPos.x(), a.startPos.y(), 403 w - 2 * (a.startPos.x() - a.endPos.x()), 404 h - 2 * (a.startPos.y() - a.endPos.y()))); 405 painter.setPen(QColor::fromRgb(0, 0, 0)); 406 painter.drawText(QRectF(a.endPos.x(), a.endPos.y(), w, h), 407 Qt::AlignCenter, QString::number(a.digit)); 408 painter.setPen(Qt::NoPen); 409 } 410 return rtn; 411 } 412 413 void GameWidget::paintEvent(QPaintEvent *) 414 { 415 // 构造一个QPainter对象 使用它来进行绘图 416 QPainter painter(this); 417 418 // 如果正在播放动画效果则绘制缓存位图 419 if (isAnimating) 420 { 421 painter.drawImage(0, 0, *cacheImg); 422 return; 423 } 424 425 // 构造一个画刷 颜色为R G B分量分别为141 121 81的颜色 426 QBrush brush(QColor::fromRgb(141, 121, 81)); 427 // 使painter应用这个画刷 428 painter.setBrush(brush); 429 430 // 设置画笔为空笔 目的是使绘制的图形没有描边 431 painter.setPen(Qt::NoPen); 432 433 // 绘制一个矩形 434 painter.drawRect(2, 2, width() - 4, height() - 4); 435 436 // 计算每个小格子的宽度和高度 437 w = width() - 4, h = height() - 4; 438 w = (w - 25) / 4, h = (h - 25) / 4; 439 440 /* 构造一个字体 441 * 字体名字为Consolas 442 * 字体设置为粗体 443 * 字体大小为40像素 444 * */ 445 QFont font; 446 font.setFamily("Consolas"); 447 font.setBold(true); 448 font.setPixelSize(40); 449 // 使painter应用这个字体 450 painter.setFont(font); 451 452 // 循环绘制游戏面板 453 for (int i = 0; i < 4; i++) 454 for (int j = 0; j < 4; j++) 455 { 456 // 如果方格中有数字 457 if (board[i][j]) 458 { 459 // 设置画刷颜色为数码对应的颜色 460 brush.setColor(digitBkg[getBitCount(board[i][j])]); 461 // 应用这个画刷 462 painter.setBrush(brush); 463 // 绘制一个小方格 464 painter.drawRect(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h)); 465 // 设置画笔为黑色画笔 466 painter.setPen(QColor::fromRgb(0, 0, 0)); 467 // 绘制数码 468 painter.drawText(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h), Qt::AlignCenter, 469 QString::number(board[i][j])); 470 // 设置画笔为空笔 471 painter.setPen(Qt::NoPen); 472 } 473 // 如果方格中没有数字 474 else 475 { 476 // 设置画刷颜色为 RGB分量为171 165 141的颜色 477 brush.setColor(QColor::fromRgb(171, 165, 141)); 478 // 应用这个画刷 479 painter.setBrush(brush); 480 // 绘制小方格 481 painter.drawRect(QRectF(7 + (w + 5) * j, 7 + (h + 5) * i, w, h)); 482 } 483 } 484 } 485 486 void GameWidget::restart() 487 { 488 // 初始化相关变量 同构造函数 489 score = 0; 490 digitCount = 2; 491 memset(board, 0, sizeof(board)); 492 board[rand() % 4][rand() % 4] = 2; 493 board[rand() % 4][rand() % 4] = 2; 494 emit ScoreInc(score); 495 update(); 496 } 497 498 bool GameWidget::checkGameOver() 499 { 500 // 循环检测是否含有相邻的相同数码 501 for (int i = 0; i < 4; i++) 502 for (int j = 0; j < 4; j++) 503 { 504 if (j != 3 && board[i][j] == board[i][j + 1]) 505 return false; 506 if (i != 3 && board[i][j] == board[i + 1][j]) 507 return false; 508 } 509 return true; 510 } 511 512 bool GameWidget::checkWin() 513 { 514 // 循环检测是否某个方格的数字为2048 515 for (int i = 0; i < 4; i++) 516 for (int j = 0; j < 4; j++) 517 if (board[i][j] == 2048) 518 return true; 519 return false; 520 } 521 522 int GameWidget::getBitCount(int n) 523 { 524 // 循环获取数字二进制位数 525 int c = 0; 526 while (n >>= 1) 527 c++; 528 // 返回位数-1 529 return c - 1; 530 }
时间: 2024-10-28 08:40:20