1.基本介绍
软件基于Qt5.6.1,利用C++语言进行编写。
五子棋有两种游戏模式:人机对战和双人对战。
在进入游戏时玩家选择游戏模式。 选择人机对战玩家会执黑棋、电脑执白棋进行对决。选择双人对战会由玩家双方各自操作鼠标进行对战。
2.详细设计
2.1 模式选择界面
进入游戏首先呈现的是模式选择界面。模式选择界面逻辑较为简单,不需要多余装饰,故可以使用QMessageBox类来呈现,根据用户点击的按钮进入到对应的游戏模式,并弹出游戏界面。
2.2 游戏界面
五子棋为15*15一共225个点,14*14格。棋盘结构较为简单,可利用画家类QPainter自行绘制出棋盘,棋子同理。落子位置利用对mouseMoveEvent(QMouseEvent* event)重写来实现,根据鼠标移动来捕获落子标记点。
2.3 电脑算法
电脑算法采用对落子位置计分来实现,电脑对不同位置的子有不同的计分方式,最终选出最优计分,将棋子落在得分最高处。电脑算法以守为主,守的分数>攻的分数。
3.主要编码实现
3.1 模式选择界面的编码实现:
//初始化
void MainWindow::initGame(){
game = new GameModel();
QMessageBox msgBox;
msgBox.setWindowTitle("五子棋对战");
msgBox.setText("请选择对战模式");
QPushButton *Button1 = msgBox.addButton(tr("人机对战"), QMessageBox::ActionRole);
QPushButton *Button2 = msgBox.addButton(tr("本地玩家对战"), QMessageBox::ActionRole);
msgBox.setStandardButtons(QMessageBox::Close);
msgBox.exec();
if( msgBox.clickedButton() == Button1){
game_type = AI;
}else if (msgBox.clickedButton() == Button2) {
game_type = MAN;
}else{exit(0);}
game->gameStatus = PLAYING;
game->startGame(game_type);
update();
}
3.2 游戏界面的编码实现:
void MainWindow::paintEvent(QPaintEvent* event){
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); //设置抗锯齿
//画棋盘
for(int i=1;i<BOARD_GRAD_SIZE;i++){
painter.drawLine(MARGIN+BLOCK_SIZE*i,MARGIN+BLOCK_SIZE,
MARGIN+BLOCK_SIZE*i,this->height()-MARGIN-BLOCK_SIZE);
painter.drawLine(MARGIN+BLOCK_SIZE,MARGIN+BLOCK_SIZE*i,
this->width()-MARGIN-BLOCK_SIZE,MARGIN+BLOCK_SIZE*i);
}
//绘制选中点
QBrush brush;
brush.setStyle(Qt::SolidPattern);
//选中点标记
if(clickPosRow>0 && clickPosRow<BOARD_GRAD_SIZE &&
clickPosCol>0 && clickPosCol<BOARD_GRAD_SIZE &&
game->gameMapVec[clickPosRow][clickPosCol]==0){
if(game->playerFlag){
brush.setColor(Qt::black);
}else{
brush.setColor(Qt::white);
}
painter.setBrush(brush);
painter.drawRect(MARGIN+BLOCK_SIZE*clickPosCol-MARK_SIZE/2,MARGIN+BLOCK_SIZE*clickPosRow-MARK_SIZE/2,MARK_SIZE,MARK_SIZE);
}
//落子绘制
for(int i=0;i<BOARD_GRAD_SIZE;i++){
for(int j=0;j<BOARD_GRAD_SIZE;j++){
if(game->gameMapVec[i][j]==1){
brush.setColor(Qt::black);
painter.setBrush(brush);
painter.drawEllipse(MARGIN+BLOCK_SIZE*j-CHESS_RADIUS,MARGIN+BLOCK_SIZE*i-CHESS_RADIUS,CHESS_RADIUS*2,CHESS_RADIUS*2);
}else if(game->gameMapVec[i][j]==-1){
brush.setColor(Qt::white);
painter.setBrush(brush);
painter.drawEllipse(MARGIN+BLOCK_SIZE*j-CHESS_RADIUS,MARGIN+BLOCK_SIZE*i-CHESS_RADIUS,CHESS_RADIUS*2,CHESS_RADIUS*2);
}
}
}
//判断输赢
if(clickPosCol>0 && clickPosCol<BOARD_GRAD_SIZE &&
clickPosRow>0 && clickPosRow<BOARD_GRAD_SIZE &&
(game->gameMapVec[clickPosRow][clickPosCol]==1||game->gameMapVec[clickPosRow][clickPosCol]==-1)){ //代碼解析:game->gameMapVec[clickPosRow][clickPosCol]==1||game->gameMapVec[clickPosRow][clickPosCol]==-1,防止因為5個0(空白)相連也被判勝利
if(game->isWin(clickPosRow,clickPosCol) && game->gameStatus == PLAYING){
game->gameStatus = WIN;
QString str;
str = game->gameMapVec[clickPosRow][clickPosCol]==1?"黑棋":"白棋";
QMessageBox::StandardButton btnValue = QMessageBox::information(this,"五子棋嬴家",str+"勝利");
if(btnValue == QMessageBox::Ok){
game->startGame(game_type);
game->gameStatus = PLAYING;
}
}
}
}
棋盘,落子标记,黑子,白子如上图所示。
3. 电脑算法的编码实现
void GameModel::calculateScore(){
//统计玩家及电脑连成的子
int personNum = 0; //玩家连成子的个数
int botNum = 0; //AI连成子的个数
int emptyNum = 0; //各方向空白位的个数
//清空评分数组
scoreMapVec.clear();
for(int i=0;i<BOARD_GRAD_SIZE;i++){
std::vector<int> lineScores;
for(int j=0;j<BOARD_GRAD_SIZE;j++){
lineScores.push_back(0);
}
scoreMapVec.push_back(lineScores);
}
//计分
/*计分个人理解:
*遍历每一个格子,判断哪些是空白的点(即为0的点),以该点为中心,判断周围的八个点向外延伸的四格里,
*有多少个是黑子、白子、空白,以此作为依据来评分。电脑算法是以守为主,所以守的分数>攻的分数
*/
for(int row=0;row<BOARD_GRAD_SIZE;row++){
for(int col=0;col<BOARD_GRAD_SIZE;col++){
//空白点才算
if(row>0 && col>0 && gameMapVec[row][col]==0){
//遍历周围8个方向
for(int y=-1;y<=1;y++){
for(int x=-1;x<=1;x++){
//重置
personNum = 0;
botNum = 0;
emptyNum = 0;
//原坐标不算
if(!(y==0 && x==0)){
//每个方向延伸4个子
//对玩家的黑子评分(正反两个方向)
for(int i=1;i<=4;i++){
if(row+i*y>0 && row+i*y<BOARD_GRAD_SIZE &&
col+i*x>0 && col+i*x<BOARD_GRAD_SIZE &&
gameMapVec[row+i*y][col+i*x]==1){ //真人玩家的子
personNum++;
}else if(row+i*y>0 && row+i*y<BOARD_GRAD_SIZE &&
col+i*x>0 && col+i*x<BOARD_GRAD_SIZE &&
gameMapVec[row+i*y][col+i*x]==0){ //空白位
emptyNum++;
break;
}else{ //出边界,或有白子
break;
}
}
for(int i=1;i<=4;i++){
if(row-i*y>0 && row-i*y<BOARD_GRAD_SIZE &&
col-i*x>0 && col-i*x<BOARD_GRAD_SIZE &&
gameMapVec[row-i*y][col-i*x]==1){ //真人玩家的子
personNum++;
}else if(row-i*y>0 && row-i*y<BOARD_GRAD_SIZE &&
col-i*x>0 && col-i*x<BOARD_GRAD_SIZE &&
gameMapVec[row-i*y][col-i*x]==0){ //空白位
emptyNum++;
break;
}else{ //出边界,或有白子
break;
}
}
if(personNum == 1){ //守玩家的第2子
scoreMapVec[row][col]+=10;
}else if(personNum == 2){ //守玩家的第3子
if(emptyNum == 1)
scoreMapVec[row][col]+=30;
else if(emptyNum == 2)
scoreMapVec[row][col]+=40;
}else if(personNum == 3){ //守玩家的第4子
//量变空位不一样,优先级不一样
if(emptyNum == 1)
scoreMapVec[row][col]+=60;
else if(emptyNum == 2)
scoreMapVec[row][col]+=110;
}else if(personNum == 4){ //守玩家的第5子
scoreMapVec[row][col]+=10100;
}
//进行一次清空
emptyNum = 0;
//对AI白子评分
for(int i=1;i<=4;i++){
if(row+i*y>0 && row+i*y<BOARD_GRAD_SIZE &&
col+i*x>0 && col+i*x<BOARD_GRAD_SIZE &&
gameMapVec[row+i*y][col+i*x]==-1){ //AI的子
botNum++;
}else if(row+i*y>0 && row+i*y<BOARD_GRAD_SIZE &&
col+i*x>0 && col+i*x<BOARD_GRAD_SIZE &&
gameMapVec[row+i*y][col+i*x]==0){ //空白位
emptyNum++;
break;
}else{ //出边界
break;
}
}
for(int i=1;i<=4;i++){
if(row-i*y>0 && row-i*y<BOARD_GRAD_SIZE &&
col-i*x>0 && col-i*x<BOARD_GRAD_SIZE &&
gameMapVec[row-i*y][col-i*x]==-1){ //AI的子
botNum++;
}else if(row-i*y>0 && row-i*y<BOARD_GRAD_SIZE &&
col-i*x>0 && col-i*x<BOARD_GRAD_SIZE &&
gameMapVec[row-i*y][col-i*x]==0){ //空白位
emptyNum++;
break;
}else{ //出边界
break;
}
}
if(botNum == 0){
scoreMapVec[row][col]+=5; //攻1
}else if(botNum == 1){
scoreMapVec[row][col]+=10; //攻2
}else if(botNum == 2){ //攻3
if(emptyNum == 1)
scoreMapVec[row][col]+=25;
else if(emptyNum == 2)
scoreMapVec[row][col]+=50;
}else if(botNum == 3){ //攻4
if(emptyNum == 1)
scoreMapVec[row][col]+=55;
else if(emptyNum == 2)
scoreMapVec[row][col]+=100;
}else if(botNum >= 4){ //攻5
scoreMapVec[row][col]+=20000;
}
}
}
}
}
}
}
}
//AI执行下棋
void GameModel::actionByAI(int &clickRow,int &clickCol){
//计算评分
calculateScore();
//从评分中找出最大分数的位置
int maxScore = 0;
std::vector<std::pair<int,int>> maxPoints;
for(int row = 1;row<BOARD_GRAD_SIZE;row++){
for(int col = 1;col<BOARD_GRAD_SIZE;col++){
//前提是这个坐标是空的
if(gameMapVec[row][col] == 0){
if(scoreMapVec[row][col]>maxScore){//找最大数和坐标
maxPoints.clear();
maxScore = scoreMapVec[row][col];
maxPoints.push_back(std::make_pair(row,col));
}else if(scoreMapVec[row][col] == maxScore){
//如果有多个最大值就将他们存储起来,在后面的代码随机抽1个
maxPoints.push_back(std::make_pair(row,col));
}
}
}
}
//随机落子,如果有多个点的话
srand((unsigned)time(0));
int index = rand()%maxPoints.size();
std::pair<int,int> pointPair = maxPoints.at(index);
clickRow = pointPair.first;
clickCol = pointPair.second;
updateGameMap(clickRow,clickCol);
}
4. 游戏效果
游戏能够实现两种对战模式,并正确判断输赢。