1,简介
QT实现中国象棋单机版,本来以为会花费不少时间的。由于之前2个游戏项目的练手,这个主要只在走棋算法部分花了些时间研究,最后一共2个晚上完成。
由于只是QT学习,没有考虑更多复杂功能,比如人机对战等,那个可能算法就得花很长时间。
2,效果
3,设计思路
准备2个棋盘棋子图片:
棋子类:
#pragma once
#include "qpainter.h"
enum ITEM_TYPE{
ITEM_SHUAI = 0, //帅
ITEM_SHI, //士
ITEM_XIANG, //象
ITEM_JU, //车
ITEM_MA, //马
ITEM_PAO, //炮
ITEM_BING, //兵
ITEM_MAX,
};
enum ITEM_COLOR{
COLOR_RED = 0, //红方
COLOR_BLACK, //黑方
COLOR_MAX,
};
class Item
{
public:
Item(){}
Item(ITEM_TYPE t,ITEM_COLOR c,QPoint pt);
~Item(void);
public:
ITEM_TYPE m_type; //棋子类型
ITEM_COLOR m_color; //红方黑方
QPoint m_pt; //位置
bool m_bShow; //是否显示,实现选中时闪烁效果
};
可以看到,棋子类型的定义和图片中棋子顺序一致,是为了直接以此枚举值计算棋子在图片中的位置。
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "Item.h"
#include "qmap.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void InitItems(); //初始化所有棋子位置
void NewGame(); //新游戏
bool FindItemAtPoint(QPoint pt,Item& item); //给定一个棋盘上的点,查找是否存在棋子
bool DeleteItemAtPoint(QPoint pt,bool& bDeleteSHUAI); //bDeleteSHUAI:是否打掉的是“帅”或“将”
void SetItemShow(Item item,bool bShow); //设置棋子显隐
bool MoveItem(Item item,QPoint ptMoveTo); //走棋逻辑
void ChangeItemPoint(QPoint ptItem,QPoint pt); //改变棋子位置属性
void DrawItem(QPainter& painter,Item item); //绘制棋子
//获取棋子能移动的区域
void GetMoveArea(Item item,QVector<QPoint>& area);
void GetMoveArea_JU(Item item,QVector<QPoint>& area);
void GetMoveArea_MA(Item item,QVector<QPoint>& area);
void GetMoveArea_XIANG(Item item,QVector<QPoint>& area);
void GetMoveArea_SHI(Item item,QVector<QPoint>& area);
void GetMoveArea_SHUAI(Item item,QVector<QPoint>& area);
void GetMoveArea_PAO(Item item,QVector<QPoint>& area);
void GetMoveArea_BING(Item item,QVector<QPoint>& area);
protected:
void paintEvent(QPaintEvent *);
void mousePressEvent(QMouseEvent *);
void timerEvent(QTimerEvent *);
private:
Ui::MainWindow *ui;
int m_nItemWidth;
int m_nItemHeight;
QPixmap m_ItemsImage; //棋子图片
QVector<Item> m_items; //所有棋子
Item m_SelectedItem;
bool m_bExistSelectedItem; //是否已存在选中的棋子
bool m_bIsRedTurn; //当前该红方下
};
#endif // MAINWINDOW_H
其中主要逻辑都在mousePressEvent 鼠标响应:
//鼠标点击,走棋、吃棋等逻辑
void MainWindow::mousePressEvent(QMouseEvent * e)
{
//获得鼠标点所对应的棋盘点pt
QPoint pt;
pt.setX( (e->pos().x() - START_X ) / RECT_WIDTH);
pt.setY( (e->pos().y() - START_Y ) / RECT_HEIGHT);
//是否有选中的棋子
if(m_bExistSelectedItem)
{
//已存在棋子,判断鼠标点击的是否是选中棋子
if (pt == m_SelectedItem.m_pt)
{
//再次点击已经选择的棋子,什么也不做
return;
}
//点击其它棋子
Item ClickedItem;
if (FindItemAtPoint(pt,ClickedItem))
{
//点击的同色的另外一个棋子,改选
if ( (m_bIsRedTurn && ClickedItem.m_color == COLOR_RED) ||
(!m_bIsRedTurn && ClickedItem.m_color != COLOR_RED))
{
SetItemShow(m_SelectedItem,true);
m_SelectedItem = ClickedItem;
return;
}
}
//点击的异色棋子,判断是否能走能吃
QVector<QPoint> moveArea;
GetMoveArea(m_SelectedItem,moveArea); //获取已选择棋子的可移动区域
if (moveArea.contains(pt))
{
//包含当前鼠标点中的棋子,则能吃
bool bDeleteSHUAI = false;
DeleteItemAtPoint(pt,bDeleteSHUAI);
ChangeItemPoint(m_SelectedItem.m_pt,pt);
if (bDeleteSHUAI)
{
QString str = m_bIsRedTurn?QStringLiteral("红方胜利!"):QStringLiteral("黑方胜利!");
QMessageBox::information(NULL, "GAME OVER ",str, QMessageBox::Yes , QMessageBox::Yes);
NewGame();
return ;
}
m_bExistSelectedItem = false;
m_bIsRedTurn = !m_bIsRedTurn;
update();
return ;
}
else
{
//不能走到该位置
return ;
}
}
else
{
//当前没有选中棋子
Item ClickedItem;
if (FindItemAtPoint(pt,ClickedItem))
{
//如果点中一个棋子,是当前走棋方的颜色,就选中了
if ( (m_bIsRedTurn && ClickedItem.m_color == COLOR_RED) ||
(!m_bIsRedTurn && ClickedItem.m_color == COLOR_BLACK))
{
m_SelectedItem = ClickedItem;
m_bExistSelectedItem = true;
return;
}
}
}
}
里面最重要的就是这个GetMoveArea 函数,用来获取每个棋子的可走动的区域:
void MainWindow::GetMoveArea(Item item,QVector<QPoint>& moveArea)
{
switch (item.m_type)
{
case ITEM_JU:
{
GetMoveArea_JU(item,moveArea);
break;
}
case ITEM_MA:
{
GetMoveArea_MA(item,moveArea);
break;
}
case ITEM_XIANG:
{
GetMoveArea_XIANG(item,moveArea);
break;
}
case ITEM_SHI:
{
GetMoveArea_SHI(item,moveArea);
break;
}
case ITEM_SHUAI:
{
GetMoveArea_SHUAI(item,moveArea);
break;
}
case ITEM_PAO:
{
GetMoveArea_PAO(item,moveArea);
break;
}
case ITEM_BING:
{
GetMoveArea_BING(item,moveArea);
break;
}
}
}
比如对于棋子“马”,求可以走到的位置:
void MainWindow::GetMoveArea_MA( Item item,QVector<QPoint>& moveArea )
{
//棋子“马”的计算可移动区域算法简介:
//1,求出8个待选位置,8个位置的偏移是(-2,-1)(-2,1)(2,-1)(2,1)(1,-2)(1,2)(-1,-2)(-1,2)存在关系:|x|+|y|=3
//2,判断待选位置是否在棋盘内
//3,判断中间是否有卡位的棋子
//4,位置上是否已存在同色棋子
Item item2;
for (int i = -2; i<=2; i++)
{
for(int j = -2; j<=2; j++)
{
if (qAbs(i) + qAbs(j) == 3)
{
QPoint ptNew = item.m_pt + QPoint(i,j);
if (ptNew.x() >= 0 && ptNew.x() <= 8 && ptNew.y()>=0 && ptNew.y() <= 9)
{
}
else
{
continue;
}
//求该方向行走路线的 卡位元素位置
QPoint ptDirect(0,0);
if (qAbs(i) > qAbs(j))
{
if (i>0)
{
ptDirect = QPoint(1,0);
}
else
{
ptDirect = QPoint(-1,0);
}
}
else
{
if (j>0)
{
ptDirect = QPoint(0,1);
}
else
{
ptDirect = QPoint(0,-1);
}
}
QPoint ptHit = item.m_pt + ptDirect; //马的卡位元素位置
if (FindItemAtPoint(ptHit,item2))
{
//卡位
continue;
}
if (FindItemAtPoint(ptNew ,item2) && item.m_color == item2.m_color)
{
//有本组item
continue;
}
moveArea.append(ptNew);
}
}
}
}
其它棋子类似,就是仔细处理下就OK了。
4,源码
源码已上传至群文件,可在学习群免费下载!
群号码:1149411109
群名称:Qt实战派学习群