QT Creator5.9.9
2.3游戏进行的过程
整个游戏博弈过程用定义的状态来判定运行流程。enum RunState {NO_RUNNING, START, RUNNING, GAME_OVER,RESTART,EXIT};
2.3.1整体流程
游戏运行整体就是一个互奕的过程,当我们执行开始时,就进入开始状态中循环互奕,互奕时当重新开始或判定胜负,就会退出互奕过程。当重新开始,又会进入互奕,此时开始按钮会被屏蔽。需要添加一个短期的延时,不然会一直占用资源,造成窗口崩溃。
2.3.2互奕过程
通过一个当前回合变量改变回合,电脑下棋使用AI(初步随机下棋),下棋前判断对方是否提出悔棋请求,若有请求,需要对上一回合做回退处理;下完棋后判断是否五连;如果该回合未结束,则修改回合变量,更换回合;人下棋使用鼠标点击,回合到来进入等待循环,鼠标按下后,根据自定义条件跳出循环,然后根据当前回合棋子类型判断你是否五连结束游戏;如果该回合未结束,则修改回合变量,更换回合。
下棋时,棋子的显示,都是通过修改二维数组中的棋盘信息,然后根据信息刷新来显示的。
鼠标按下的操作,请看前面的随笔,棋盘的制作过程,其主要是通过鼠标事件来监控。
要想验证悔棋过程,还需要在AI下棋前增加一个延时。
2.3.3按键功能实现
点击ui文件进入设计模式,在要实现功能的按键上右键=>转到槽……,选择如下图所示的clicked()点击事件,确认后在代码中会自动创建一个槽函数接口,当鼠标点击按钮时,就会运行该函数。
1 void FiveChess::on_startBtn_clicked() 2 { 3 4 }
按键的屏蔽设置:
1 ui->startBtn->setEnabled(false); // 修改为true即可开启
退出对话框:
为工程添加会话框,新建->
修改类名
默认添加到工程中
设计绘画UI如下:
添加功能:
ui中分别添加两个按键,并右键=>转到槽……,添加槽函数
void ExitShow::on_confirmBtn_clicked() { reject(); // 确认 } void ExitShow::on_cancelBtn_clicked() { accept(); // 取消 }
会话类代码中,填充背景和提示信息
void ExitShow::paintEvent(QPaintEvent *e) { QPainter painter(this); QFont font; QPixmap exitShowPic(QString(":/images/backGround5.png")); painter.drawPixmap(0,0, this->width(),this->height(),exitShowPic); painter.drawRect(0,0,this->width()-1,this->height()-1); font = ui->confirmBtn->font(); painter.setFont(font); QRect textRect(0,0,this->width(), this->height()*2/3); painter.drawText(textRect, Qt::AlignCenter, "你确定要退出吗?"); }
在主窗口退出按键中,槽函数实现如下:
定义一个自定义的会话框实例,然后设置为模态模式(模态模式只能处理当前窗口,主窗口无效),进行显示后进入d.exec()循环当中,当会话框中的按键被按下,触发reject()或accpt()时,退出循环,当是确认时,触发reject(), exec()退出值是QDialog::Rejected。
void FiveChess::on_exitBtn_clicked() { ExitShow d(this); d.setModal(true); d.show(); if (d.exec() == QDialog::Rejected) { state = EXIT; // 退出游戏过程,结束循环 qApp->quit(); // 退出app,程序中存在死循环,会造成后台无法退出 } }
2.3.4AI算法(随机位置下棋)
主要要获取一个随机的空闲的棋盘坐标。
初始化中定义一个随机计数器qsrand(time(0)); 然后通过多次调用qrand()取得随机数,最后得到一个符合范围的空闲棋盘坐标,修改该棋盘坐标信息,即完成了下棋操作。
所需头文件:#include <QTime>
1 bool FiveChess::computerDropAI() 2 { 3 int i, j; 4 while(1) { 5 i = qrand() % chessboard->getchequerNumOfLine(); 6 j = qrand() % chessboard->getchequerNumOfLine(); 7 if (chessboard->getChessInfo(i, j) == ChessBoard::noChess) { 8 break; 9 } 10 } 11 chessboard->setChessInfo(i, j, computerChess); 12 lastChessX = i; 13 lastChessY = j; 14 chessboard->update(); 15 if (chessboard->isGameOver(i, j)) { 16 return true; 17 } 18 return false; 19 }
2.3.5五子相连判断与处理
如下图所示,每下一个棋子,有四条直线上的结果需要判断;
已知的信息有下棋点的棋盘坐标、当前棋子类型。
以落棋点为原点,建立坐标轴,就有8个方向上的结果需要考虑。
先考虑获取单一方向上的连子数,然后获取直线上连子数,最后获取所有的结果,判断五连。
单一方向上的连子数处理如下:
polarityW、polarityY为坐标轴上的极性,用来递进棋盘坐标。
x、y:为落棋点棋盘坐标。
返回单一方向上相同类型的棋子数。
1 int ChessBoard::chessNumcalPart(int polarityW, int polarityY, int x, int y) 2 { 3 int val = 0; 4 int W; 5 int Y; 6 dropW = x; 7 dropH = y; 8 // 当前坐标 dropW dropH 9 for (int i = 1; i < 5; i++) { 10 W = dropW + i * polarityW; 11 Y = dropH + i * polarityY; 12 if (W < 0 || Y < 0 || W >= 15 || Y >= 15) { 13 break; 14 } 15 if(chessInfo[W][Y] == currentChess) { 16 val++; 17 } else { 18 break; 19 } 20 } 21 return val; 22 }
直线上的棋子数处理:
分别取直线上的两个方向的结果,最终判断是否五连。注意两组极性的入参需要是在一条直线上的变化。
1 bool ChessBoard::chessNumcalAll(int polarityW1, int polarityY1, int polarityW2, int polarityY2, int x, int y) 2 { 3 int val = 1; 4 val += chessNumcalPart(polarityW1, polarityY1, x, y); 5 val += chessNumcalPart(polarityW2, polarityY2, x, y); 6 if(val >= 5) { 7 return true; 8 } 9 return false; 10 }
结果判定:
1 bool ChessBoard::isGameOver(int x, int y) 2 { 3 bool result = false; 4 // 判断行, 参数为判断方向上变化的极性 5 result |= chessNumcalAll(-1, 0, 1, 0, x, y); 6 // 判断列 7 result |= chessNumcalAll(0, -1, 0, 1, x, y); 8 // 判断反斜线 9 result |= chessNumcalAll(-1, 1, 1, -1, x, y); 10 // 判断正斜线 11 result |= chessNumcalAll(-1, -1, 1, 1, x, y); 12 return result; 13 }
ChessBoard::isGameOver(iint, int)是棋盘类的方法,由于电脑和人下棋时刻不是在一块,电脑在互奕回合中判断结果,人在棋盘落子时判断结果,但是最终游戏结束时的处理是由主窗口统一处理的,所以把游戏结束的处理函数定义主窗口类中的槽函数,而棋盘类中自定义结束信号;电脑获胜时可以直接调用槽函数,人获胜时,发射结束信号;如此电脑和人获胜都是同一函数处理。
槽函数实现:
1 // 定义 2 class FiveChess : public QWidget 3 { 4 …… 5 private slots: 6 void GameOver(); 7 …… 8 } 9 // 实现 10 void FiveChess::GameOver() 11 { 12 gameOverFlag = true; 13 qDebug() << "game is over!!!"; 14 resultLabel->setVisible(true); 15 timeLine->stop(); 16 17 if (currentRound == computer) { 18 GameOverPrompt(computer); 19 ui->msgLabel->setText("电脑获胜!"); 20 if (computerChess == ChessBoard::blackChess) { 21 qDebug() << "win is computer by black chess!!"; 22 } else { 23 qDebug() << "win is computer by white chess!!"; 24 } 25 } else { 26 GameOverPrompt(player); 27 ui->msgLabel->setText("获得胜利!"); 28 if (playerChess == ChessBoard::blackChess) { 29 qDebug() << "win is player by black chess!!"; 30 } else { 31 qDebug() << "win is player by white chess!!"; 32 } 33 } 34 35 state = GAME_OVER; 36 soundList->setCurrentIndex(3); 37 music->play(); 38 }
棋盘类信号定义:
1 class ChessBoard : public QWidget 2 { 3 …… 4 signals: 5 void boardGameOver(); 6 …… 7 } 8
信号不需要实现具体过程,只需在合适的地方发射信号即可。
信号发射:emit 信号名(); // 如果有参数,需要带入实参
1 if(isGameOver(dropW, dropH)) 2 { 3 emit boardGameOver(); 4 }
2.3.6延时函数
1 void FiveChess::myMSleep(unsigned int msec) 2 { 3 QTime _Timer = QTime::currentTime().addMSecs(msec); 4 while( QTime::currentTime() < _Timer ) 5 QCoreApplication::processEvents(QEventLoop::AllEvents, 100); 6 }