用VS软件开发“中国象棋“游戏<笔记摘录>

这是一个使用C语言编写的中国象棋游戏程序,包括棋盘的二维数组表示、棋子结构体定义、棋子移动规则判断、鼠标点击事件处理等功能。程序初始化棋盘布局,通过bool型函数判断棋子移动合法性,并实现棋子的移动。当满足游戏结束条件时,输出胜利者。

 整体架构如上

1.很直观地去看这个中国象棋的界面,数一下它有多少行和多少列.

10行,9列:要注意这里数的是安放象棋的位置,有10行9列

这里我们首先想到的必然是二维数组,每一个行列交叉的点都设置成二维数组a[i][j]这样的格式,以此来确定棋盘上面每一个棋子的位置和走向.

我们把上面安放棋子的二维数组定义成一个地图,也就是map[i][j]的格式,但同时要注意,每一个象棋棋子都有行,列,颜色,是否过河和名称(也就是什么棋子)几种定义区分.

那么这里我们就需要把棋子定义成一个结构.如下:

struct  Chesscoordinate//棋子综合信息
{
    int x;
    int y;
    DWORD type;  //颜色
    bool river;//是否过河
    int id;
};

当定义了棋子综合信息,我们是不是需要每一个信息都拓展一下?

x代表的是在棋盘上面的列,也就是竖行,y代表的是棋子在棋盘上面的行,也就是横线,而type代表的棋子的颜色,棋子颜色可以分为黑色和红色两种;river设置成bool型的,只需要判断棋子是否过河就可以了.最后id定义的是棋子上面的名称,比如将,帅等.

接下来我们就来VS当中进行棋子的程序定义拓展:

#define distance  35//窗口线与棋盘边界线的距离
#define   longth  65//棋盘方格的长
#define   high   61//棋盘方格的宽

distance longth 和high我们都把其设置成宏,这个就要回溯到棋盘上面,把棋盘假设成一个xy的二维坐标,那么要定义每一个棋子的位置,或者说是每一行与每一列的交叉点,就要用到上面三个宏定义.

比如左上角第一个棋子的坐标可以表示为:

map[0][0].x=distance;

map[0][0].y=distance

而第一行第二个棋子的坐标可以表示为:

map[1][0].x=distance+longth;

map[1][0].y=distance

依次类推......

int  xx(int a)//数组下标转换成坐标
{
    a = distance + longth * a;
    return a;
}
int  yy(int a)
{
    a = distance + high * a;
    return a;
}

我们可以推断出每一个棋子的坐标,都可以通过上面几个宏定义以及棋子的实际行列表示出来,

enum pieces
{
    SPACE = -1,
    車, 馬, 象, 士, 将, 炮, 卒,
    车, 马, 相, 仕, 帥, 砲, 兵,
    BEGIN, END,

};
enum pieces redchess[] = { 車,馬,象,士,将,炮,卒, };
enum pieces blackchess[] = { 车,马,相,仕,帥,砲,兵, };
enum pieces state = BEGIN;
struct move {  //鼠标选中的棋子
    int beginx;
    int beginy;
    int endx;
    int endy;
    int state;
}moving = { -1,-1,-1,-1,BEGIN };
const char* chessname[] = { "車","馬","象","士","将","炮","卒","车","马","相","仕","帥","砲","兵", };

这里我们把棋子的id设置成一个联合结构数组,因为里面的棋子的id都基本不同,同时我们考虑到了棋子要进行运动,把棋子的开始状态设置为BEGIN

而新建一个move的结构,表示鼠标选中棋子的基本信息.begin的行列和结束的行列和运行状态.

最后回到棋子id上面,我们是不是需要把这些id都设置到棋子或者棋盘上面啊,采用常量字符数组的形式,把棋盘上面所有的棋子都表示出来.

而把刚才的map[i][j]二维数组同样定义成一个二维结构数组.如下:

struct Chesscoordinate  map[9][10];//坐标
struct Chesscoordinate  AImap[9][10];//坐标

同时,我们设置了AImap[i][j]的二维结构数组,看看是否下面在进行棋子移动的时候,我们会用到.

这里i=9,j=10是根据我们上面看中国象棋棋盘得到的数据.

2.我们要开始加载图片信息了,把我们已经有的棋盘素材放在同文件下面,然后采用下面程序,就可以在窗口当中显示出中国棋盘的背景图.

void begining()
{
    loadimage(&img, "D:/program/Project3-chess successful/1.png");
    initgraph(img.getwidth(), img.getheight(), 1); //初始化图形系统
    putimage(0, 0, &img);//输出开始界面;

至于,loadimage()和initgraph()函数以及putimage()函数三者的基本理解和解释,建议去搜索,都有比较详细的解释.这里我们解释一下initgraph()函数的基本含义:

这个函数用于初始化绘图环境。

HWND initgraph(
    int width,
    int height,
    int flag = NULL
);

参数:

width   绘图环境的宽度。

height   绘图环境的高度。

flag  绘图环境的样式,默认为 NULL。

上面程序的基本思路也就不言而喻:先加载或者找到我们素材的地址,然后定义出素材的宽和长度,然后再输出这个棋盘背景.

当输出完背景之后,我们是不是要先把所有的棋子放在初始位置,也就是初始化棋盘?

void  coord()//棋子信息赋值
{
    loadimage(&img, "D:/program/Project3-chess successful/1.png");
    putimage(0, 0, &img);//输出棋盘

    for (int i = 0; i <= 8; i++)
    {

        for (int j = 0; j <= 9; j++)//遍历二维数组
        {
            enum pieces chessid = SPACE;//先把全部位置的id赋值为SAPCE
            DWORD  chesstype;//定义颜色中间变量
            if (j <= 4)
            {
                chesstype = BLACK;//黑方
                if (j == 0)
                {
                    if (i <= 4)
                    {
                        chessid = blackchess[i];//
                    }
                    else
                    {
                        chessid = blackchess[8 - i];
                    }

                }
                if (j == 2 && (i == 1 || i == 7))
                {
                    chessid = blackchess[5];

                }

                if (j == 3 && (i == 0 || i == 2 || i == 4 || i == 4 || i == 6 || i == 8))
                {
                    chessid = blackchess[6];
                }

            }
            else
            {
                chesstype = RED;//红方
                if (j == 6 && (i == 0 || i == 2 || i == 4 || i == 6 || i == 8))
                {
                    chessid = redchess[6];
                }
                if (j == 7 && (i == 1 || i == 7))
                {
                    chessid = redchess[5];

                }

                if (j == 9)
                {

                    if (i <= 4)
                    {
                        chessid = redchess[i];
                    }
                    else
                    {
                        chessid = redchess[8 - i];
                    }
                }

            }//依次赋值
            map[i][j].id = chessid;
            map[i][j].river = false;
            map[i][j].type = chesstype;
            map[i][j].x = distance + longth * i;
            map[i][j].y = distance + high * j;

        }
    }
    for (int i = 0; i <= 8; i++)
    {
        for (int j = 0; j <= 9; j++)
        {
            if (map[i][j].id == SPACE)
            {
                map[i][j].type = YELLOW;
            }
        }
    }

}

看上述程序:

首先先加载出棋盘背景,然后把棋盘上面行和列交叉的位置都设置为SPACE,可以理解为初始化棋盘.然后如果j<=4,也就是楚河的一侧,我们定义棋子type全部都为黑色,而棋子的id要根据行列的位置来确定,首先看第一行,前5列数据,是不是应该是"車,馬,象,士,将"而后四列数据,是不是应该是前面去除将的反序,而观察一下,如果把前面定义成i列,后面对应相等的字符则是8-i列.那么我们就定义完了第一行的数据.

而炮和兵的数据,以及红方的数据是不是同上述思路一样.

然后就开始定义每一个棋子初始化的状态,回溯到棋子的基本信息结构当中,有x,y,river,type和id,我们要依次对其进行定义,

注意在初始状态的时候,棋子的river都为false,因为都没有过楚河

定义完棋子的位置,那么剩下没有棋子的位置,我们是不是要把刚开始初始化的状态都修改为棋盘的背景颜色啊.上述定义完,再次输出棋盘

void  qiban()
{
    loadimage(&img, "D:/program/Project3-chess successful/1.png");
    initgraph(img.getwidth(), img.getheight(), 1);
    putimage(0, 0, &img);//输出棋盘
}

这个时候就已经初始化了中国象棋的棋盘.

接下来是不是要把每一个棋子都定义到棋盘上面.开始绘画棋子

void getbackground() //画棋子
{
    int x_start, y_start;
    x_start = distance;
    y_start = distance;

    settextstyle(30, 0, "黑体");//棋子大小颜色
    setbkmode(0);
    for (int i = 0; i <= 8; i++)
    {

        for (int j = 0; j <= 9; j++)
        {

            if (map[i][j].id != SPACE)//在棋盘上输出
            {
                setfillcolor(RGB(253, 216, 161));
                //    setlinestyle(BLACK);
                settextcolor(map[i][j].type);
                fillcircle(map[i][j].x, map[i][j].y, 24);
                fillcircle(map[i][j].x, map[i][j].y, 18);
                outtextxy(map[i][j].x - 13, map[i][j].y - 13, chessname[map[i][j].id]);

            }
        }
    }

}

如果上面棋盘行列之间的id不是SPACE,那自然就是我们定义的棋子应该安置的位置.从第0行第0列开始,依次进行定义:

画一个棋子,两个圆环,其颜色要画成背景颜色,而棋子上面的文本颜色要根据实际行列定义的type,设置成黑色和红色,

最后便是采用outtextxy()把每一个棋子的字符输入进行.注意此处根据棋盘x和y的地址均减13,是根据实际调试来进行,并不是确定常量.

写到这里,是不是就把棋子和棋盘都初始化好了?yes,of course.

3.棋盘棋子都初始化好了,那么接下来我们是不是就可以开始移动棋盘上面的棋子,进行下棋了?

这里有一个有意思的思考:我刚开始认为吃掉棋子和走到空白的地方是两种不同的情况,后面仔细思考了一下,是同一状态,只需要把原来的修改为结束的就可以了,而原来的只需要位置只需要修改颜色和id为空也就没了,不管他end地址有没有棋子.

void movechess(int a, int b, int c, int d)//移动棋子,改变其坐标
{

    map[c][d].id = map[a][b].id;
    map[c][d].river = map[a][b].river;
    map[c][d].type = map[a][b].type;
    map[c][d].x = xx(c);
    map[c][d].y = yy(d);
    map[a][b].id = SPACE;
    map[a][b].type = YELLOW;


}
int  xx(int a)//数组下标转换成坐标
{
    a = distance + longth * a;
    return a;
}
int  yy(int a)
{
    a = distance + high * a;
    return a;
}

把[a][b]位置的棋子信息,全部给了[c][d]位置处的棋子,而[a][b]棋子此处的信息要记得修改id和type,id设置为空,而颜色设置成背景颜色.而xx(c),yy(d)就成了棋子移动到下一个点的坐标

void MouseControl()//获取鼠标点击信息并完响应
{
    //getbackground();
    if (MouseHit())
    {

        float beginrow, beginrol, endrow, endrol;//第一次按下时的坐标
        int intbeginrow, intbeginrol, intendrow, intendrol;//第二次按下时的坐标
        MOUSEMSG msg = GetMouseMsg();/*这个函数用于获取一个鼠标消息。如果当前鼠标消息队列中没有,就一直等待。*/
        if (msg.uMsg == WM_LBUTTONDOWN)//按下鼠标左键时
        {
            //获取鼠标点击的数组的下标
        //    printf("(%d,%d)", msg.x, msg.y);
            //回溯转换成行列
            beginrow = (float)(msg.x - distance) / longth;
            beginrol = (float)(msg.y - distance) / high;

            intbeginrow = round(beginrow);
            intbeginrol = round(beginrol);

            if (moving.state == BEGIN)
            {
                moving.state = END;
                moving.beginx = intbeginrow;
                moving.beginy = intbeginrol;
                //    printf(
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值