QT:完整的人机五子棋设计(三)运行


QT Creator5.9.9

2.3游戏进行的过程

整个游戏博弈过程用定义的状态来判定运行流程。enum RunState {NO_RUNNING, START, RUNNING, GAME_OVER,RESTART,EXIT};

2.3.1整体流程

游戏运行整体就是一个互奕的过程,当我们执行开始时,就进入开始状态中循环互奕,互奕时当重新开始或判定胜负,就会退出互奕过程。当重新开始,又会进入互奕,此时开始按钮会被屏蔽。需要添加一个短期的延时,不然会一直占用资源,造成窗口崩溃。

image

2.3.2互奕过程

通过一个当前回合变量改变回合,电脑下棋使用AI(初步随机下棋),下棋前判断对方是否提出悔棋请求,若有请求,需要对上一回合做回退处理;下完棋后判断是否五连;如果该回合未结束,则修改回合变量,更换回合;人下棋使用鼠标点击,回合到来进入等待循环,鼠标按下后,根据自定义条件跳出循环,然后根据当前回合棋子类型判断你是否五连结束游戏;如果该回合未结束,则修改回合变量,更换回合。

下棋时,棋子的显示,都是通过修改二维数组中的棋盘信息,然后根据信息刷新来显示的。

鼠标按下的操作,请看前面的随笔,棋盘的制作过程,其主要是通过鼠标事件来监控。

要想验证悔棋过程,还需要在AI下棋前增加一个延时。

image

2.3.3按键功能实现

点击ui文件进入设计模式,在要实现功能的按键上右键=>转到槽……,选择如下图所示的clicked()点击事件,确认后在代码中会自动创建一个槽函数接口,当鼠标点击按钮时,就会运行该函数。

  1 void FiveChess::on_startBtn_clicked()
  2 {
  3 
  4 }

image

按键的屏蔽设置:

startBtn是自己修改的目标名image

  1  ui->startBtn->setEnabled(false); // 修改为true即可开启

退出对话框:

为工程添加会话框,新建->

image

image

修改类名

image

默认添加到工程中

image

设计绘画UI如下:

image

添加功能:

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五子相连判断与处理

如下图所示,每下一个棋子,有四条直线上的结果需要判断;

已知的信息有下棋点的棋盘坐标、当前棋子类型。

image

以落棋点为原点,建立坐标轴,就有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 }


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM