Qt黑白棋游戏的实现(附压缩包资源)

一、前言

Qt提供了UI控件和信号与槽以及多种绘制方法,结合这些方法,进行黑白棋游戏的实现,包含人机对战和人人对战以及网络对战功能,并且实时记录双方棋子数量。

二、思路和功能

黑白棋游戏,是由棋盘和两种棋子组成的吃子游戏。

1.棋盘绘制

通过QPainter类中的画线方法绘制8*8的棋盘,并且设置背景图片,同时重写调整事件,防止调整窗体大小后导致棋盘布局紊乱

2.落子功能

在画好的棋盘上通过坐标区分格子,通过二维数组进行存储格子中的值,通过判断进行棋子的绘制

3.吃子功能

黑白棋的吃子是在横向,竖向,斜向两同色棋子围住时进行吃子,所以吃子功能应该在落点进行周围八个格子的判断,不出界且相邻棋子是对方棋子的时候,才有吃子可能,然后对该方向进行循环前进,遇到空位时跳出循环,找到自己的棋子时则循环后退并将棋子都标记为自己的棋子。

4.PVP模式

设置变量currentRole对应当前下子玩家,并且在下子之前遍历棋盘,判断是否有可以下子的条件,无法下子则切换角色。

5.PVE模式

遍历棋盘寻找可下子的格子进行下子

三、函数功能和具体实现(附代码)

1.棋盘,棋子绘制和初始化函数

(1)棋盘绘制

通过QPainter类的drawPixmap绘制出背景

Qpen类中setColor,setStyle,setWidth,setPen进行画笔样式的设置和画笔选择,通过循环9次进行画线,同时设置gridwidth和gridheight变量将窗体长宽分为十份,进行坐标点的循环绘制。

void chess::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    //绘制背景
    QRect rec(QPoint(0,0),QPoint(this->width(),this->height()));
    QPixmap pix(bgFilename);
//    pix.load("../mychess/background.png");
    painter.drawPixmap(rec,pix);
    //绘制棋盘
    //画线
    QPen pen1;
    pen1.setColor(lineColor);
    pen1.setStyle(lineStyle);
    pen1.setWidth(lineWidth);
    painter.setPen(pen1);
    for(int i=0;i<9;i++)
    {
        painter.drawLine(startX,startY+ i*gridheight,9*gridwidth,startY+i*gridheight);
        painter.drawLine(startX+i*gridwidth,startY,startX+gridwidth*i,gridheight*9);
    }
(2)棋子绘制

定义chessFilename变量和chessData二维数组记录棋盘中棋子的颜色,通过drawPixmap()方法绘制黑白棋图片

//画棋子
    QString chessFilename;
    for (int i=0; i<8;i++)
    {
        for(int j=0;j<8;j++)
        {
            if(chessData[i][j] == White)
            {
                chessFilename = "../mychess/whitestone.png";
            }
            else if(chessData[i][j] == Black)
            {
                chessFilename = "../mychess/blackstone.png";
            }
            else
            {
                chessFilename.clear();
                continue;
            }
            painter.drawPixmap(startX+i*gridwidth,startY+j*gridheight,gridwidth,gridheight,QPixmap(chessFilename));
        }
    }
(3)初始化

设置图片和画笔样式,将棋盘二维数组全部设置为空。

void chess::Init()
{

    bgFilename.clear();
    bgFilename = "../mychess/background.jpg";
    lineColor  = Qt::black;
    lineStyle = Qt::SolidLine;
    lineWidth = 3;
}
void chess::InitChess()
{
    //初始化棋盘数据
//    memset(chessData,0,sizeof(int)*64);
    for(int i=0;i<8;i++)
    {
        for(int j=0;j<8;j++)
        {
            chessData[i][j] = Empty;
        }
    }
}

2.落子,棋子显示,计数显示,游戏模式

(1)落子

重写mousePressEvent()事件,利用点击位置坐标进行判断属于哪个格子,传输出格子信号

连接信号和槽,编写槽函数,并判断该处是否可以落子,可以落子时进行游戏模式判断,再调用对应函数进行落子

void chess::mousePressEvent(QMouseEvent *event)
{
    int x = event->x();
    int y = event->y();
    //x
    if(x>=startX && (x<=startX+8*gridwidth))
    {
        //y
        if(y>=startY && (y<=startY+8*gridheight))
        {
            //得出当前坐标属于哪个格子
            int i = 0,j = 0;
            i = (x-startX)/gridwidth;
            j = (y-startY)/gridheight;
//            chessData[i][j] = Black;
//            this -> update();

            SignalSendChessData(i,j);
        }
    }
}
(2)棋子显示

编写setChessStatus(void *p)方法,调用update()方法进行棋盘更新,并通过二维数组确定各位置棋子的颜色

void chess::setChessStatus(void *p)
{
    memcpy(chessData,p,sizeof(int)*8*8);
    this->update();
}
(3)计数显示

编写ChessShow()方法,遍历二维数组,对不同颜色的棋子进行计数,通过UI控件的lcdNumber进行显示

void ChessForm::ChessShow()
{
    int blackcount = 0,whitecount =0;
    for(int i=0;i<8;i++)
    {
        for (int j=0;j<8;j++)
        {
            if(formChessData[i][j]==chess::Black)
            {
                blackcount++;
            }
            else if(formChessData[i][j] == chess::White)
            {
                whitecount++;
            }
        }
    }
    ui->lcdNumber1->display(whitecount);
    ui->lcdNumber2->display(blackcount);
}
(4)游戏模式

设定黑白子先行模式,并将人人设置为PVP,人机设置为PVC,利用currentPK变量进行存储,并设置点击时进行棋盘初始化和先手棋子的选择

void ChessForm::on_btn_pvp_clicked()
{
    currentPK = PVP;
    //把界面初始化
    if(ui->cbox_item->currentIndex() == 0)
    {
        setRole(chess::White);
    }
    else
    {
        setRole(chess::Black);
    }
    //把棋盘初始化
    setChessInit();

}

3.吃子判定,机器落子,无法落子的角色切换

(1)吃子判定

定义一个8行2列数组代表当前坐标的八个方向,循环八个方向对相邻棋子进行判定,如果是对方棋子,则继续朝该方向移动,当遇到空位或者出界时,跳出循环进行另外方向的判定,当该方向找到自己的棋子时候,循环后退到当前落子点,并将沿途棋子标记为自己的棋子。

int ChessForm::judegRule(int x, int y, void *chess, chess::ChessType currentRole, bool eatChess)
{
    //棋盘的八个方向
    int dir[8][2] = {{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1},{1,1}};
    //临时保存棋盘数组坐标位置
    int  temp_x = x,temp_y = y;
    //初始化数据
    int i = 0, eatNum = 0;
    //自定义类型
    typedef int (*p)[8];
    //类型转换
    p chessFlag = p(chess);
    //如果此方格内已有棋子,返回;
    if(chessFlag[temp_x][temp_y] != chess::Empty)
    {
        return 0;
    }
    //棋盘的八个方向
    for(int i=0;i<8;i++)
    {
        //准备判断相邻棋子
        temp_x += dir[i][0];
        temp_y += dir[i][1];
        //如果没有出界,且相邻棋子是对方棋子,才有吃子可能
        if((temp_x <8 && temp_x>=0 && temp_y <8 && temp_y>=0) && (chessFlag[temp_x][temp_y]!=currentRole) && (chessFlag[temp_x][temp_y]!=chess::Empty))
        {
            temp_x+=dir[i][0];temp_y+=dir[i][1];
            while(temp_x <8 && temp_x >=0 && temp_y <8 && temp_y >=0)
            {
                //遇到空位,代表不能吃子,跳出
                if(chessFlag[temp_x][temp_y] == chess::Empty)
                {
                    break;
                }
                //找到自己的棋子,可以吃子
                if(chessFlag[temp_x][temp_y] == currentRole)
                {
                    //确定吃子
                    if(eatChess == true)
                    {
                        //标记为自己的棋子
                        chessFlag[x][y] = currentRole;
                        //后退一步
                        temp_x -= dir[i][0];temp_y -= dir[i][1];
                        //只要没有回到开始的位置就执行
                        while((temp_x!=x)||(temp_y!=y))
                        {
                            //标记为自己的棋子
                            chessFlag[temp_x][temp_y] = currentRole;
                            //继续后退一步
                            temp_x -= dir[i][0];temp_y -= dir[i][1];
                            //累计
                            eatNum++;
                        }
                    }
                    //不吃子,判断这个位置能不能吃子
                    else
                    {
                        //后退一步
                        temp_x -= dir[i][0];temp_y -= dir[i][1];
                        //计算可以吃子的个数
                        while((temp_x!=x)||(temp_y!=y))
                        {
                            //继续后退一步
                            temp_x -= dir[i][0];temp_y -= dir[i][1];
                            eatNum++;
                        }
                    }
                    //跳出循环
                    break;
                 }
                //没有找到自己的棋子,向前一步
                temp_x += dir[i][0];temp_y += dir[i][1];
            }
        }
        //如果这个方向不能吃子,就换一个方向
        temp_x = x;temp_y = y;
    }
    return eatNum;
}
(2)机器落子

进行棋盘的遍历,记录可落子位置的格子,调用吃子判定函数进行落子,然后切换角色界面显示。

机器落子或许可以结合深度学习中的局部最优思想进行算法编写,本文注重实现功能,不做详解和实现

void ChessForm::RebootRole(chess::ChessType role)
{
    //0,不能下子,1,能下子
    int flag =0;
    int ret,oldret;
    int c_i,c_j;
    for(int i=0;i<8;i++)
    {
        for(int j=0;j<8;j++)
        {
            //遍历,能否下子
            if(formChessData[i][j] == chess::Empty)
            {
                ret = judegRule(i,j,formChessData,role,false);
                if(ret>0)
                {
                    flag++;
                    c_i = i,c_j =j;
                    oldret = ret;
                }
            }
        }
    }
    if(flag)
    {
        //下子
        judegRule(c_i,c_j,formChessData,currentRole,true);
        mychess->setChessStatus(formChessData);
        //切换界面显示
        RoleChange();
    }
    else
    {
        RoleChange();
    }
}
(3)无法落子的角色切换

定义一个落子判断函数,可以落子返回值为1,不可落子返回值为0

人人对战时:当一方无法落子时,调用切换角色函数,更新棋盘

人机对战时:当玩家无法落子时,调用切换角色函数和机器落子函数

        if(currentPK == PVP)
        {
            //角色切换
            RoleChange();
            //判断能否落子
            int judgment = JudgmentRole(currentRole);
            if(judgment)
            {
                //数据统计
                ChessShow();
            }
            else
            {
                RoleChange();
                ChessShow();
            }

        }
        else if(currentPK == PVC)
        {            
            //机器下子
            RoleChange();
            RebootRole(currentRole);
            int judgment = JudgmentRole(currentRole);
            if(judgment)
            {
                //数据统计
                ChessShow();
            }
            else
            {
                RoleChange();
                RebootRole(currentRole);
                //数据统计
                ChessShow();
            }

        }

 

 

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值