在上一篇文章讲了如何显示棋盘和棋子,接下来开始实现鼠标点击棋子时移动棋子。
1.获取鼠标位置
整个棋盘都是在fixed容器里,而fixed容器不可以与鼠标点击事件直接绑定,这里我们用到了消息事件的容器,fixed不是直接放在盒状容器里,而是先放在消息盒子里,再把消息盒子放在盒状容器里,然后把事件盒状与鼠标点击的事件绑定在一起,这样当鼠标点击棋盘时,就会触发鼠标事件的回调函数。
GtkWidget *event_box = gtk_event_box_new();
gtk_box_pack_start (GTK_BOX (vbox), event_box, TRUE, TRUE, 0);
GtkWidget *fixed = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(event_box),fixed);
g_signal_connect(gtk_widget_get_parent(fixed), "button-press-event",
G_CALLBACK(deal_mouse_press), pJunqi);
此时我们就可以在deal_mouse_press的回调函数里获取鼠标的坐标,再由鼠标的坐标计算出棋盘上对应的棋子的坐标
void deal_mouse_press(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
int x,y;
BoardChess *pChess;
Junqi *pJunqi = (Junqi *)data;
x = event->x;
y = event->y;
pChess=GetChessPos(pJunqi,x,y);
... ...
}
在获取棋盘位置时,先把棋盘分割成5个区域,如下图
然后根据鼠标的位置,算出落在哪个区域。在计算时每个矩形的左上角坐标和长宽要事先用宏定义好,判断代码如下:
if( (x>=MIDX) && (x<= MIDX+HORIZONTAL_LONG1) )
{
if( (y>=BOTTOMY) && (y<=BOTTOMY+VERTICAL_LONG) )
{
自家范围
}
else if( (y>=TOPY) && (y<=TOPY+VERTICAL_LONG) )
{
对家范围
}
else if( (y>=NINE_GRID_Y) && (y<=NINE_GRID_Y+HORIZONTAL_LONG2) )
{
九宫范围
}
}
else if( (y>=MIDY) && (y<=MIDY+HORIZONTAL_LONG2) )
{
if((x>=RIGHTX) && (x<= RIGHTX+VERTICAL_LONG))
{
下家范围
}
else if( (x>=LEFTX) && (x<= LEFTX+VERTICAL_LONG) )
{
上家范围
}
}
知道在哪一家区域后,再把区域划分成6*5的方阵,在每个格子里填放着棋子。首先确定鼠标在哪个格子里,取得格子的左上角坐标,一个棋子并没有填满格子,还需判断鼠标是否落在棋子上而不是落在旁边的空隙里,此后返回选中的棋子。
2.初始化棋盘元素
棋盘相当于存放棋子的容器,用slot来表示存放棋子的格子,总共有4*30+9个格子,slot的位置是固定不变的,而slot里存放的棋子随着棋子的移位是会发生变化的,相关定义如下:
typedef struct ChessLineup
{
enum ChessType type;
GtkWidget *pImage[4];
}ChessLineup;
typedef struct BoardChess BoardChess;
struct BoardChess
{
enum ChessType type;
enum ChessDir iDir;
ChessLineup *pLineup;
int xPos;
int yPos;
};
ChessLineup Lineup[4][30];
BoardChess ChessPos[4][30];
BoardChess NineGrid[9];
ChessLineup定义棋子结构体,ChessLineup.type是棋子类型,ChessLineup.pImage表示同一个棋子在四家区域的不同显示方式。BoardChess定义了棋盘的slot,xPos和yPos为slot的左上角左边,在布阵时初始化,此后保持不变,iDir定义了slot所在区域,这个变量是为了显示棋子用的,pLineup就是当前slot存放的棋子。
3.移动棋子
有了上面的基础后就可以移动棋子了,目前先不管军棋的移动规则。实现的功能为,鼠标点击棋子,则棋子显示白色选中框,在点击目标位置,棋子移动到目标位置,如果目标位置有棋子则被覆盖掉。
相关代码如下,说明见注释
void deal_mouse_press(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
int x,y;
BoardChess *pChess;
BoardChess *pSrc, *pDst;
Junqi *pJunqi = (Junqi *)data;
x = event->x;
y = event->y;
//获取鼠标点击时的棋盘slot元素
pChess=GetChessPos(pJunqi,x,y);
if( pChess==NULL )
{
//为空说明点到了棋盘有效区之外的地方
return;
}
//此时获得了棋盘的slot,此条件表示棋盘当前未处于选中状态
if(!pJunqi->bSelect)
{
//没有棋子选中时,点到没有棋子的slot则返回
if( pChess->type==NONE )
{
return;
}
pJunqi->bSelect = 1;
pJunqi->pSelect = pChess;
//显示白色选择框
gtk_widget_hide(pJunqi->redRectangle[0]);
gtk_widget_hide(pJunqi->redRectangle[1]);
ShowRectangle(pJunqi, pChess, RECTANGLE_WHITE);
}
else
{
//此时棋子处于选中状态,点击目标位置进入这里,先隐藏白色选中框
gtk_widget_hide(pJunqi->whiteRectangle[0]);
gtk_widget_hide(pJunqi->whiteRectangle[1]);
pJunqi->bSelect = 0;
if(pChess==pJunqi->pSelect)
{
//选中的是当前棋子,则取消选中
pJunqi->pSelect = NULL;
return;
}
pDst = pChess;
//如果目标位置上有棋子则隐藏棋子
HideChess(pDst,pDst->iDir);
pSrc = pJunqi->pSelect;
//把目标棋子替换为选中的棋子
pDst->pLineup = pSrc->pLineup;
把选中棋子移动到目标位置上
gtk_fixed_move(GTK_FIXED(pJunqi->fixed),
pSrc->pLineup->pImage[pDst->iDir],
pDst->xPos,pDst->yPos);
//替换目标棋子类型,注意!这部分代码有很强的顺序关系
pDst->type = pSrc->type;
//隐藏选中的棋子
HideChess(pSrc,pSrc->iDir);
//将原来选中棋子位置上的棋子类型置为空,表示该位置上无棋子
pSrc->type = NONE;
//将移动后的棋子显示出来
gtk_widget_show(pSrc->pLineup->pImage[pDst->iDir]);
//显示红色矩形框
ShowRectangle(pJunqi, pChess, RECTANGLE_RED);
}
}
4.画矩形框
矩形框是通过cairo来画图的,先新建一块画布surface ,大小随意,再在画布上新建一支画笔,为了使背景色透明,需要选择CAIRO_FORMAT_ARGB32
surface = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, 800, 900) ;
cr = cairo_create (surface);
接下来把矩形画在surface上,从surface里提取pixbuf,要获取竖直矩形框,还需要对pixbuf进行旋转,使用gdk_pixbuf_rotate_simple()函数,最后把pixbuf转成图像即可
image = gtk_image_new_from_pixbuf(pixbuf);
效果如下