四国军棋界面开发(6)复盘功能及其他修改

这次主要增加了一个复盘的功能,另外代码也改了很多地方,现在做一个集中的说明。

1.复盘功能

1.1复盘控件

复盘时需要有一个前进与后退的按钮,另外还需要有一个比例滑块,通过拉动滑块来快速跳转到某一步,最后显示“步数”文字的label也是一个控件。我们把这几个控件组合在一起,都放在一个pJunqi->step_fixed里,这样只要隐藏pJunqi->step_fixed就可以把所有的控件都隐藏。

由于前进与后退按钮要表明方向,但是在GTK中没找到方向按钮,所以通过以下按钮图片素材
这里写图片描述
截取图片上的按钮贴在按钮控件上作为前进与后退的按钮。

接下来是新建一个滑块,新建滑块前需要一个adjustment控件绑定在滑块上,adjustment可以设定滑块的取值范围,注意滑块一定要设置长和宽,否则显示的滑块将不能拉动。

    GtkAdjustment *adj;
    //只需关注前3个参数,分别为当前值,最小值和最大值
    adj = gtk_adjustment_new (0, 0, 100, 1, 1, 0);
    slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj);
    //比例控件以后会用到,调入复盘时设定最大步数为滑块的最大值
    pJunqi->slider_adj = adj;
    //设置步数改变时的回调函数
    g_signal_connect (adj, "value-changed",
                      G_CALLBACK (on_slider_value_change),
                      pJunqi);
    //必须,否则滑块将不能拉动
    gtk_widget_set_size_request(slider, length,20);
    //小数点位数为0
    gtk_scale_set_digits(GTK_SCALE (slider), 0);
    gtk_fixed_put(GTK_FIXED(fixed), slider, 0, 50);
    gtk_widget_show (slider);

最后复盘界面的整体效果如下
这里写图片描述

1.2打开和保存复盘

首先在菜单中增加打开复盘和保存复盘2个按钮

    gBoard.open_menu = SetMenuItem(menu, "打开复盘", event_handle, "open");
    gBoard.save_menu = SetMenuItem(menu, "保存复盘", event_handle, "save");

这里写图片描述

在对战模式下,打开复盘按钮是灰色的,在新建和复盘模式下,保存复盘按钮是灰色的,相关函数如下:

    //设置打开菜单灰色
    gtk_widget_set_sensitive(gBoard.open_menu, FALSE);
    //激活保存按钮
    gtk_widget_set_sensitive(gBoard.save_menu, TRUE);

点击菜单后将弹出一个对话框,使用tk_file_chooser_native_new函数,如果是打开选择GTK_FILE_CHOOSER_ACTION_OPEN参数,如果是保存则选择GTK_FILE_CHOOSER_ACTION_SAVE选项

    native = gtk_file_chooser_native_new ("Open File",
                                        NULL,
                                        GTK_FILE_CHOOSER_ACTION_OPEN,
                                        "_Open",
                                        "_Cancel");

这里写图片描述
复盘格式如下

偏移 长度 说明
0 4 复盘标识,固定0x57,0x04,0,0
4 4 复盘总步数
8 - ? - 每4字节为一步,前2字节为起始坐标,后2字节为目标坐标

棋盘是一个17*17网格,最右边的横坐标为0,最上面的纵坐标为0。

每移动一步都会记录在pJunqi->aReplay数组里,最后保存总的步数,在点击保存按钮后,将pJunqi->aReplay保存到指定的文件,pJunqi->iReOfst记录文件的末尾地址

    if (response_id == GTK_RESPONSE_ACCEPT)
    {
        name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (native));
        ConvertFilename(name);
        fd = open(name, O_RDWR|O_CREAT, 0600);
        assert( pJunqi->iReOfst>=MOVE_OFFSET );
        iTolalStep = (pJunqi->iReOfst-MOVE_OFFSET)/4;
        SetReplayData(pJunqi, (u8*)&iTolalStep, 4, 4);
        OsWrite(fd, pJunqi->aReplay, pJunqi->iReOfst, 0);
    }

打开复盘时先要校对复盘文件标识符,如果正确则导入复盘文件,初始化棋盘,初始化布阵等等相关初始化操作

        OsRead(fd, aBuf, PAGE_SIZE, 0);
        if( memcmp(aBuf, aMagic, 4)==0 )
        {
            pJunqi->bReplay = 1;
            //导入复盘文件
            memcpy(pJunqi->aReplay, aBuf, PAGE_SIZE);
            //初始化棋盘
            ReSetChessBoard(pJunqi);
            //导入布阵
            LoadReplayLineup(pJunqi);
            //显示比例滑块
            ShowReplaySlider(pJunqi);
            //设定最大步数为滑块上限
            int max_step = *(int *)(&pJunqi->aReplay[4]);
            gtk_adjustment_set_upper(pJunqi->slider_adj, max_step);

            pJunqi->bStart = 1;
            pJunqi->bStop = 1;
            pJunqi->iRpStep = 0;
        }

1.3播放复盘

播放复盘时通过pJunqi->iRpStep记录当前的步数,如果pJunqi->iRpStep的值改变了,则棋盘从初始开始一直播放到pJunqi->iRpStep这一步,再显示最终的局面。

    for(i=0; i<pJunqi->iRpStep; i++)
    {
        获取每一步的起始和目标坐标,根据坐标得到棋子pSrc和pDst的信息
        然后和正常行棋一样,比较2个棋子的大小,再做最后的移动
        int type;
        type = CompareChess(pSrc, pDst);
        PlayResult(pJunqi, pSrc, pDst, type);
    }

点击前进和后退时会改变滑块控件的值,这时会触发回调函数,在回调函数里调用ShowReplayStep()播放复盘

    if( strcmp(zDir,"left" )==0 )
    {
        if( pJunqi->iRpStep>0 )
        {
            gtk_adjustment_set_value(pJunqi->slider_adj, pJunqi->iRpStep-1);
        }

    }
    else if( strcmp(zDir,"right" )==0 )
    {
        if( pJunqi->iRpStep<max_step )
        {
            pJunqi->iRpStep++;
            gtk_adjustment_set_value(pJunqi->slider_adj, pJunqi->iRpStep);
            ShowReplayStep(pJunqi, 1);
        }
    }

只有在下一步时才播放声音,其他时候不播放声音

static void
on_slider_value_change (GtkAdjustment *adjustment,
                      gpointer       data)
{
    ... ...
    if( value!=pJunqi->iRpStep )
    {
        pJunqi->iRpStep = value;
        ShowReplayStep(pJunqi, 0);
        pJunqi->sound_type = 0;
    }

}

2.其他功能

2.1时间功能

先创建一个100ms的定时器

g_timeout_add(100, (GSourceFunc)time_event, pJunqi);

每方下棋的时间为30s,30s到达后算一次超时,轮到下一家下。

static int tick = 300;
if( tick==0 )
{
    AddEventToReplay(pJunqi, JUMP_EVENT, pJunqi->eTurn);
    IncJumpCnt(pJunqi, pJunqi->eTurn);
    ChessTurn(pJunqi);
    now_dir = pJunqi->eTurn;
    tick = 300;
}

如超时或跳过5次将被判定为投降

    if( cntJump==5 )
    {
        SendSoundEvent(pJunqi,DEAD);
        DestroyAllChess(pJunqi, iDir);

        HideJumpButton(iDir);
    }

正常下棋后,重新显示30s

    if( now_dir!=pJunqi->eTurn )
    {
        now_dir = pJunqi->eTurn;
        tick = 300;
    }

以下是显示时间的函数,通过sapn标签来改变字体的大小

void SetTimeStr(char *label_str, int time)
{
    char str[100] = "<span foreground=\"#FFFF00\" font='16'>";
    int szStr;
    memcpy(label_str,str,strlen(str));
    sprintf(label_str+strlen(str),"%02d",time);
    szStr = strlen(label_str);
    memcpy(label_str+szStr,"</span>",8);
}
    SetTimeStr(label_str, (tick+9)/10);
    gtk_label_set_markup(GTK_LABEL(pJunqi->pTimeLabel),label_str);

最后效果如下
这里写图片描述
另外还可以通过设定里的暂停菜单来停止计时

2.2跳过和投降按钮

在跳过或行棋时会改变下棋者,这里要考虑阵亡的情况,如果出现阵亡则紧接着跳到下一家

void ChessTurn(Junqi *pJunqi)
{
    pJunqi->eTurn = (pJunqi->eTurn+1)%4;
    //下家阵亡
    if( pJunqi->aInfo[pJunqi->eTurn].bDead )
    {
        pJunqi->eTurn = (pJunqi->eTurn+1)%4;
        //下下家阵亡
        if( pJunqi->aInfo[pJunqi->eTurn].bDead )
        {
            pJunqi->eTurn = (pJunqi->eTurn+1)%4;
        }
    }
    //跳过时取消选中的棋子
    pJunqi->bSelect = 0;
    gtk_widget_hide(pJunqi->whiteRectangle[0]);
    gtk_widget_hide(pJunqi->whiteRectangle[1]);
}

点击投降时需要弹出一个确认对话框
这里写图片描述

    dialog = gtk_dialog_new_with_buttons ("Interactive Dialog",
                                        GTK_WINDOW (pJunqi->window),
                                        GTK_DIALOG_MODAL| GTK_DIALOG_DESTROY_WITH_PARENT,
                                        ("_OK"),
                                        GTK_RESPONSE_OK,
                                        "_Cancel",
                                        GTK_RESPONSE_CANCEL,
                                        NULL);
    content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
    label = gtk_label_new_with_mnemonic ("您确定投降吗");
    gtk_box_pack_start (GTK_BOX (content_area), label, FALSE, FALSE, 0);
    gtk_widget_show(label);
    response = gtk_dialog_run (GTK_DIALOG (dialog));

确定后将会销毁这一家的所有棋子

    if (response == GTK_RESPONSE_OK)
    {
        SendSoundEvent(pJunqi,DEAD);
        DestroyAllChess(pJunqi, iDir);
        AddEventToReplay(pJunqi, SURRENDER_EVENT, iDir);
        ChessTurn(pJunqi);

        HideJumpButton(iDir);
    }

3.源代码

https://github.com/pfysw/JunQi

猜你喜欢

转载自blog.csdn.net/pfysw/article/details/81672749