Qt开发自学13_黑白棋

20200603黑白棋

我们的实现目标如下图所示:(不是五子棋)

 

步骤:1画背景;2画线(横纵各9根);3画棋子(8*8=64个)

掌握:绘图技巧、封装思想

 

New Project:创建棋盘类Chess,不要 界面(不能用设计模式,只能用代码绘制ui界面了)

(1)画背景

1.1 启用绘图事件

protected:    void paintEvent(QPaintEvent *);

背景加载成功!

 

优先使用构造函数,也能实现该功能。

(1)对象被创建的时候,立马自动触发调用构造函数
(2)不用进行函数之间的切换,省了资源开销,提高效率

 

供别人随意改变背景:公共方法改变私有变量

头文件中增加:

private:
    QString bg_filename;
    void Init();

一般情况下,变量通常都是私有的。可以采用方法的形式让外界访问,这体现了面向对象的方式。

方法:void ChangeBGImage(const QString filename); //加const的原因: 防止传进去后被改变

方法的实现如下:驼峰命名法

 

(2)画线

2.1 设置颜色、样式、宽度等,提供一个统一的外部接口(方法)

初始化中给默认值

窗体一改变,立马给它赋值(按比例缩放),测试一下:

同理可得:横线和纵线组成的棋盘格。

(3)画棋子

先找规律

用棋子铺满整个格子:

 

把64个格子的数据(黑OR白)进行保存

public:     
    enum ChessType{Empty=0,White, Black}; //棋子类型:0不显示;1白色;2黑色
private:     
    int chessData[8][8]; //保存棋子数据的容器   //需要初始化,不然里面会有随机数

    //(3)画棋子
    QString chessFilename;//棋子名字
        for(int i=0;i<8;i++){
            for(int j=0;j<8;j++){
                if(chessData[i][j] == White){
                    chessFilename = "../20200603/Images/white.PNG";
                }
                else if(chessData[i][j] == Black){
                    chessFilename = "../20200603/Images/black.PNG";
                }
                else{
                    chessFilename.clear();
                    continue;
                }
            painter.drawPixmap(startX + i* gridWidth, startY + j * gridHeight, gridWidth, gridHeight, QPixmap(chessFilename));
            }
        }

 

规则:中心四个点要占住

 

—————————分割线—————————分割线—————————分割线—————————

(4)接下来实现:鼠标点击事件,得出在哪里落棋子

备注:一定要刷新,不然鼠标点击后棋子不能及时出现。

同理 ChangeBGImage()和ChangeLine()中也要增加刷新语句 this->update();

(5)增加位点,增加界面:

5.1 先定义信号,并在鼠标点击事件里发送该信号。

signals:
    void SignalSendChessData(int i,int j);//发送点击的坐标
5.2 改变棋盘的显示值
public:
    void setChessStatus(void *p);//外面传一个数组进来

实现该方法:

void Chess::setChessStatus(void *p){
    memcpy(chessData, p, sizeof(int)*8*8);//把外面的拷贝过来
    this->update();
}

5.3 代码优化,将业务逻辑分开:初始化棋盘 void InitChess();————private:

转移 Init() 中的部分代码块,实现该方法,记得调用

void Chess::InitChess(){
    //初始化棋盘数据数组
    memset(chessData, 0, sizeof(int)*64);//memeset编译不出错,但这种方式不利于人理解。
    //我们用for循环也可以实现数组的清零
    for(int i = 0; i < 8;i++){
        for(int j = 0; j < 8;j++){
            chessData[i][j] = Empty;
        }
    }

    chessData[3][3] = Black;//指定位置落棋子
    chessData[4][3] = White;
    chessData[3][4] = White;
    chessData[4][4] = Black;
}

5.4 设计UI界面

右击 ---> 添加新文件,Qt设计师界面类QWidget

接下来:设计窗体

用到Frame容器和Grid Layout布局方式

Frame属性设置:frameShape为Box,frameShadow为Raised。

先把3个水平布局,整体再栅格布局。如果觉得太宽太高的话,可以先在属性里把最大宽度/最大高度定死 maximumSize,然后去布局。

把之前的棋盘类导入新界面中,成功实现如下:

 

QWidget 缩小这么多没有意义:可以在*.ui文件中改变minimumSize的值。

    //黑白棋吃子规则,返回值是能吃子的个数
    //x, y: 棋盘二维数组坐标位置     //chess: 棋子状态 //currentRole: 枚举变量(Empty, Black, White)
    //eatChess: true代表吃子,false只是判断有子可吃,默认为true
    int judgeRule(int x, int y, void *chess, Chess::ChessType currentRole, bool eatCehss);
int ChessForm::judgeRule(int x, int y, void *chess, Chess::ChessType currentRole, bool eatCehss){
    //棋盘的八个方向
    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(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(eatCehss = 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;  //返回能吃子的个数

}

加载窗体背景:

设计界面:Label, LCDNumber(digitCount=2), PushButton

谁先谁后显示:ComboBox9(白子先,黑子先),

初始化可以用上述方式实现,也可用下面的“方法”来实现:

    void RoleInit(const QString whiteFilename, const QString blackFilename);

1)把界面初始化,分别实现。点击“人人对战”,设置谁先下。

 void setRole(Chess::ChessType currentRole);  //设置谁先下
    Chess::ChessType currentRole;

2)把棋盘初始化

2.1 一开始默认情况下,棋盘是没有的。——注释相关语句

2.2 把chess.cpp中“初始化棋盘数据数组”的代码复制过来

        int formChessData[8][8]; //窗体中的,跟棋盘中的不一样
        void setChessInit(); //对棋盘初始化

运行代码Ctrl +R,结果显示如下:(LCD应显示2和2)

选择“白子先”,点击“人人对战”,如下:(LCD应显示2和2)

选择“黑子先”,点击“人人对战”,如下:(LCD应显示2和2)

 

绑定,处理信号

connect(myChess,SIGNAL(SignalSendChessData(int,int)),this,SLOT(doProcessChessData(int,int)));

上图说明:主窗体收到棋盘发送过来的信号

上图说明了吃子规则的实现

 

————————————————————分割线————————————————————

1. 角色切换:黑白子切换

2. 数据统计:白子多少个,黑子多少个

void ChessForm::RoleChange(){
    if(currentRole == Chess::White){
        currentRole = Chess::Black;
    }
    else{
        currentRole = Chess::White;
    }
    //界面显示随变
    if(currentRole == Chess::Black){
        ui->label2->setVisible(true);
        ui->label1->setVisible(false);
    }
    else{
        ui->label1->setVisible(true);
        ui->label2->setVisible(false);
    }
}

结果如下:

接下来做数据统计,并显示在LCD上

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::White){
                whiteCount++;
            }
            else if(formChessData[i][j] == Chess::Black){
                blackCount++;
            }
        }
    }
    ui->lcdNum1->display(whiteCount);
    ui->lcdNum2->display(blackCount);
}

注意:点击“人人对战”时,初始显示应为2和2

黑白棋子统计显示结果如下:

 

——————————分割线——————————分割线——————————

人机对战:Button转到槽(一开始,人先下白子)

 

    enum PKType{PVP,PVC,NVN};
    PKType currentPK;
 
机器下子:对空白处遍历,判断在哪里下子(LCD显示数目也要对)

后续:人下不了了就交给机器下,直到对方都下不了了,再根据黑白棋数目判断结果

弹出消息对话框告知用户。

 

等后面学了TCP/UDP后,来实现网络对战。

 

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卢奕冰2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值