美文网首页cocos2d-Lua
cocos2d-x3.14中国象棋AI(八)AI1

cocos2d-x3.14中国象棋AI(八)AI1

作者: lable | 来源:发表于2017-06-22 22:24 被阅读162次

我们要通过AI类计算下一步棋该如何走的话就必需让AI获得当前棋盘上棋子的局势、轮到谁执棋等各种棋盘上的信息,而这种信息都可以从LayerGameMain上获取,所以我们需要做的是将LayerGameMain对象指针传入AI类的成员函数中让AI类获取棋盘上的各种信息后进行运算。
要实现AI类计算功能我们先需要有一个评分标准,在这我们将所有棋种都给一个分数,每一方刚开始的局面分是:∑ 当前己方棋子*对应棋种分数。
可见,开始时两边的局面分是相同的,而AI类会通过枚举出所有局面再对比计算出我方下一步最差的局面分并选中该局面作为电脑的“下一步”。

棋子的分值:

//对应enum TYPE{CHE, MA, XIANG, SHI, JIANG, PAO, BING};
//给各类棋子设置分数
static int g_score[7] =
{
    100,//车
    45,//马
    10,//象
    20,//士
    200,//将
    60,//炮
    10//兵
};

在计算“下一步”前需要把全部局面枚举出来,枚举出来的所有局面将被存进一个链表中等待计算。
这里我们需要一个函数枚举出所有的局面,并返回一个记录所有局面的链表:

//获取所有局面
list<Step*> AI::getAllPossibleMove(LayerGameMain* main)
{
    list<Step *> ret;
    int min, max;
    //根据当前执棋方颜色决定可移动步骤的信息。
    if (main->_redTurn)
    {
        min = 0, max = 16;
    }
    else
    {
        min = 16, max = 32;
    }
    for (int i = min; i < max; i++)
    {
        if (!main->getStoneFromArrById(i)->_isdead)
        {
            //记录未死的棋子移动方案
            for (int row = 0; row <= 9; row++)
            {
                for (int col = 0; col <= 8; col++)
                {
                    int killid = main->getStoneIdFromRowCol(row, col);
                    //红棋只能杀死黑棋,黑棋只能杀死红棋
                    if (main->_redTurn && main->canMove(i, row, col, killid) && killid >= 16)
                    {
                        Step *step = new Step();
                        step->moveid = i;
                        step->killid = killid;
                        step->rowFrom = main->getStoneFromArrById(i)->_row;
                        step->colFrom = main->getStoneFromArrById(i)->_col;
                        step->rowTo = row;
                        step->colTo = col;
                        ret.push_back(step);
                    }
                    if (!main->_redTurn && main->canMove(i, row, col, killid) && killid < 16)
                    {
                        Step *step = new Step();
                        step->moveid = i;
                        step->killid = killid;
                        step->rowFrom = main->getStoneFromArrById(i)->_row;
                        step->colFrom = main->getStoneFromArrById(i)->_col;
                        step->rowTo = row;
                        step->colTo = col;
                        ret.push_back(step);
                    }
                }
            }
        }
    }
    return ret;
}

在得到局面链表后,可以通过一个函数计算出链表中每一个局面的局面分。
这里我们需要一个计算局面分的函数:

int AI::getScore(LayerGameMain *main)
{
    //初始化两边局面分
    int blackScore = 0;
    int redScore = 0;
    //检测黑方存在的棋子,并统计局面分
    for (int i = 16; i < 32; i++)
    {
        if (!main->getStoneFromArrById(i)->_isdead)
        {
            blackScore += g_score[main->getStoneFromArrById(i)->_type];
        }
    }
    //检测红方存在的棋子,并统计局面分
    for (int j = 0; j < 16; j++)
    {
        if (!main->getStoneFromArrById(j)->_isdead)
        {
            redScore += g_score[main->getStoneFromArrById(j)->_type];
        }
    }
    //返回局面分差,局面分越低则抛弃该局面
    return blackScore - redScore;
}

在得到每一个局面的局面分后我们就可以配合假设移动的方法使用最大值最小值算法来计算出AI的最优局面了。
这里需要一个模拟移动函数、还原移动函数和获取最优局面的函数:

//假设杀死某点上的棋子,实际棋子未移动
void AI::fakeMove(LayerGameMain *main, Step* step)
{
    Stone* moveStone = main->getStoneFromArrById(step->moveid);
    //将当前的棋子行列设为“下一步”的行列
    moveStone->_row = step->rowTo;
    moveStone->_col = step->colTo;

    if (step->killid != -1)//试图杀死棋子,并改变棋子状态
    {
        Stone *killedStone = main->getStoneFromArrById(step->killid);
        killedStone->_isdead = true;
    }

    main->_redTurn = !main->_redTurn;//交出执棋权
}
//还原移动棋子的状态及被杀死的棋子状态
void AI::unfakeMove(LayerGameMain *main, Step* step)
{
    Stone *moveStone = main->getStoneFromArrById(step->moveid);
    //把当前棋子行列设为原来的行列
    moveStone->_row = step->rowFrom;
    moveStone->_col = step->colFrom;

    if (step->killid != -1)//还原被杀棋子的状态
    {
        Stone *killedStone = main->getStoneFromArrById(step->killid);
        killedStone->_isdead = false;
    }

    main->_redTurn = !main->_redTurn;//交出执棋权
}

//获取电脑的一步
Step* AI::getStep(LayerGameMain *main)
{
    int highScore = -3000;//设置局面分初值,设置为无限小,-3000相对而言就是无限小
    Step *ret = NULL;

    //将所有假设尝试一遍,得出最高分局面
    list<Step *>allMove = getAllPossibleMove(main);
    list<Step *>::iterator it;
    //取出所有局面,假设移动并计算局面分,记录局面分,比较局面分,然后还原移动。
    for (it = allMove.begin(); it != allMove.end(); ++it)
    {
        Step* step = *it;//获取step指针
        fakeMove(main, step);//模拟棋子移动
        int score = getScore(main);//棋子移动后统计局面分
        unfakeMove(main, step);//还原棋子棋子
        if (score > highScore)//判断当前局面分与上一最高局面分大小
        {
            highScore = score;//如果当前局面分比最大局面分大,则当前局面分为最大局面分
            ret = step;//将=当前局面移动状态赋值给返回值
        }
    }
    //从链表中删除多余的移动状态
    for (it = allMove.begin(); it != allMove.end(); ++it)
    {
        Step *step = *it;
        if (step != ret)
        {
            delete step;
        }
    }

    return ret;
}

最后我们只要在LayerGameMain中的触控事件回调函数touchEndedCallBack中在调用完moveStone后使用定时器调用AI类并完成黑棋的移动。
这里直接贴出AI类和LayerGameMain类出来:
AI.h

#ifndef __AI_H__
#define __AI_H__

#include "LayerGameMain.h"

class AI
{
public:
    //获取电脑下一步
    static Step* getStep(LayerGameMain *main);
    //获取局面分数
    static int getScore(LayerGameMain *main);
    //获取下一步的所有可能
    static list<Step*> getAllPossibleMove(LayerGameMain* main);
    //伪造移动
    static void fakeMove(LayerGameMain *main, Step* step);
    //还原棋子状态
    static void unfakeMove(LayerGameMain *main, Step* step);

    //多部智能算法,最大值最小值算法
    static Step* getStep(LayerGameMain *main, int level);
    static int getMinScore(LayerGameMain *main, int level, int curMinScore);
    static int getMaxScore(LayerGameMain *main, int level, int curMaxScore);
};

#endif

AI.cpp

#include "AI.h"

//enum TYPE{CHE, MA, XIANG, SHI, JIANG, PAO, BING};
//给各类棋子设置分数
static int g_score[7] =
{
    100,//车
    45,//马
    10,//象
    20,//士
    200,//将
    60,//炮
    10//兵
};

int AI::getScore(LayerGameMain *main)
{
    //初始化两边局面分
    int blackScore = 0;
    int redScore = 0;
    //检测黑方存在的棋子,并统计局面分
    for (int i = 16; i < 32; i++)
    {
        if (!main->getStoneFromArrById(i)->_isdead)
        {
            blackScore += g_score[main->getStoneFromArrById(i)->_type];
        }
    }
    //检测红方存在的棋子,并统计局面分
    for (int j = 0; j < 16; j++)
    {
        if (!main->getStoneFromArrById(j)->_isdead)
        {
            redScore += g_score[main->getStoneFromArrById(j)->_type];
        }
    }
    //返回局面分差,局面分越低则抛弃该局面
    return blackScore - redScore;
}
//获取所有局面
list<Step*> AI::getAllPossibleMove(LayerGameMain* main)
{
    list<Step *> ret;
    int min, max;
    //根据当前执棋方颜色决定可移动步骤的信息。
    if (main->_redTurn)
    {
        min = 0, max = 16;
    }
    else
    {
        min = 16, max = 32;
    }
    for (int i = min; i < max; i++)
    {
        if (!main->getStoneFromArrById(i)->_isdead)
        {
            //记录未死的棋子移动方案
            for (int row = 0; row <= 9; row++)
            {
                for (int col = 0; col <= 8; col++)
                {
                    int killid = main->getStoneIdFromRowCol(row, col);
                    //红棋只能杀死黑棋,黑棋只能杀死红棋
                    if (main->_redTurn && main->canMove(i, row, col, killid) && killid >= 16)
                    {
                        Step *step = new Step();
                        step->moveid = i;
                        step->killid = killid;
                        step->rowFrom = main->getStoneFromArrById(i)->_row;
                        step->colFrom = main->getStoneFromArrById(i)->_col;
                        step->rowTo = row;
                        step->colTo = col;
                        ret.push_back(step);
                    }
                    if (!main->_redTurn && main->canMove(i, row, col, killid) && killid < 16)
                    {
                        Step *step = new Step();
                        step->moveid = i;
                        step->killid = killid;
                        step->rowFrom = main->getStoneFromArrById(i)->_row;
                        step->colFrom = main->getStoneFromArrById(i)->_col;
                        step->rowTo = row;
                        step->colTo = col;
                        ret.push_back(step);
                    }
                }
            }
        }
    }
    return ret;
}
//假设杀死某点上的棋子,实际棋子未移动
void AI::fakeMove(LayerGameMain *main, Step* step)
{
    Stone* moveStone = main->getStoneFromArrById(step->moveid);
    //将当前的棋子行列设为“下一步”的行列
    moveStone->_row = step->rowTo;
    moveStone->_col = step->colTo;

    if (step->killid != -1)//试图杀死棋子,并改变棋子状态
    {
        Stone *killedStone = main->getStoneFromArrById(step->killid);
        killedStone->_isdead = true;
    }

    main->_redTurn = !main->_redTurn;//交出执棋权
}
//还原移动棋子的状态及被杀死的棋子状态
void AI::unfakeMove(LayerGameMain *main, Step* step)
{
    Stone *moveStone = main->getStoneFromArrById(step->moveid);
    //把当前棋子行列设为原来的行列
    moveStone->_row = step->rowFrom;
    moveStone->_col = step->colFrom;

    if (step->killid != -1)//还原被杀棋子的状态
    {
        Stone *killedStone = main->getStoneFromArrById(step->killid);
        killedStone->_isdead = false;
    }

    main->_redTurn = !main->_redTurn;//交出执棋权
}

//获取电脑的一步
Step* AI::getStep(LayerGameMain *main)
{
    int highScore = -3000;//设置局面分初值,设置为无限小,-3000相对而言就是无限小
    Step *ret = NULL;

    //将所有假设尝试一遍,得出最高分局面
    list<Step *>allMove = getAllPossibleMove(main);
    list<Step *>::iterator it;
    //取出所有局面,假设移动并计算局面分,记录局面分,比较局面分,然后还原移动。
    for (it = allMove.begin(); it != allMove.end(); ++it)
    {
        Step* step = *it;//获取step指针
        fakeMove(main, step);//模拟棋子移动
        int score = getScore(main);//棋子移动后统计局面分
        unfakeMove(main, step);//还原棋子棋子
        if (score > highScore)//判断当前局面分与上一最高局面分大小
        {
            highScore = score;//如果当前局面分比最大局面分大,则当前局面分为最大局面分
            ret = step;//将=当前局面移动状态赋值给返回值
        }
    }
    //从链表中删除多余的移动状态
    for (it = allMove.begin(); it != allMove.end(); ++it)
    {
        Step *step = *it;
        if (step != ret)
        {
            delete step;
        }
    }

    return ret;
}

LayerGameMain.h

#ifndef __LAYERGAMEMAIN_H__
#define __LAYERGAMEMAIN_H__

#include "cocos2d.h"
#include "Plate.h"
#include "Stone.h"
#include <list>
#include <thread>
#include <iostream>
#include "Judger.h"

using namespace std;

/*
cocos2d-x3.14.1版本将大部分类的前缀CC去掉了,不过有部分还是兼容,如果用旧的命名创建对应的类会有警告提示/错误提示
*/

USING_NS_CC;

//定义一个结构体记录每一步信息
typedef struct _step
{
    int moveid;//移动棋子的id
    int killid;//被杀棋子的id
    int rowFrom;//移动前的行
    int colFrom;//移动前的列
    int rowTo;//移动后的行
    int colTo;//移动后的列
}Step;

class LayerGameMain : public Layer
{
public:
    static Scene* createScene();
    virtual bool init();
    CREATE_FUNC(LayerGameMain);

    //添加棋盘和棋子
    void addPlate();//用于在LayerGameMain上添加棋盘层/精灵
    void addStones();//用于在LayerGameMain上添加棋子层/精灵

    //触摸事件回调函数
    bool touchBeganCallBack(Touch *pTouch, Event *pEvent);
    void touchEndedCallBack(Touch *pTouch, Event *pEvent);

    //选中棋子相关
    Sprite *selectBox;//选中款精灵
    static int _selectedId;//标记当前选中的棋子id,没有点中为-1
    bool Screen2Plate(Point &point, int &row, int &col);//循环棋盘上所有点,通过Plate2Screen获取到棋子中心点,用引用输出中心点
    Point Plate2Screen(int row, int col);//获取棋子中心点返回到世界坐标给Screen2Plate
    void selectStone(Touch *pTouch);//选中棋子,将选中框精灵移动到选中的棋子上

    //获取棋子相关
    __Array *arrStone;//用于存放棋子
    Stone* getStoneFromArrById(int id);//通过棋子id从arrStone获取对应棋子
    int getStoneIdFromRowCol(int row, int col);//通过行列获取棋子id

    //棋子移动相关
    static bool _redTurn;
    void moveStone(Touch *pTouch);
    int getStoneCount(int row1, int col1, int row2, int col2);
    bool canMove(int moveid, int row, int col, int killid);//筛选移动规则
        //moveid为当前想要移动棋子的id,killid移动的目的点(-1为没有棋子),row和col为目的点的行列
        //炮、兵、将、车移动规则都有点相似
    bool canMoveChe(int moveid, int row, int col);//车移动规则
    bool canMovePao(int moveid, int row, int col, int killid);//炮移动规则
    bool canMoveBing(int moveid, int row, int col);//兵移动规则
    bool canMoveJiang(int moveid, int row, int col, int killid);//将移动规则
    bool canMoveMa(int moveid, int row, int col);//马移动规则
    bool canMoveXiang(int moveid, int row, int col);//象移动规则
    bool canMoveShi(int moveid, int row, int col);//士移动规则
    bool isSameColor(int clickedId);//判断选中棋子颜色是否相同

    //悔棋相关
    list<Step *> _steps;//存放整局棋谱
    void recordStep(int moveid, int killid, int rowFrom, int colFrom, int rowTo, int colTo);//记录悔棋相关参数
    void addCtrlMenu();//添加悔棋等菜单
    void regretItemCallback(Ref *obj);//悔棋菜单回调函数
    void regret1Step();//悔棋1步执行函数

    //AI
    void computerMoveCallBack(float dt);

    //判断输赢相关
    static Judger *layer;
    void judgeWin();

    //菜单相关
    void newgameItemCallback(Ref *obj);
    void startItemCallback(Ref *obj);
    void quitItemCallback(Ref *obj);

    //cocos2d-x3.14.1触摸事件,创建监听
    static EventDispatcher* eventDispatcher;
    static EventListenerTouchOneByOne *listener;
};

#endif // !__LAYERGAMEMAIN_H__

LayerGameMain.cpp

#include "LayerGameMain.h"
#include "AI.h"

using namespace std;

int LayerGameMain::_selectedId = -1;
bool LayerGameMain::_redTurn = true;
Judger* LayerGameMain::layer = nullptr;

EventDispatcher* LayerGameMain::eventDispatcher = Director::getInstance()->getEventDispatcher();
EventListenerTouchOneByOne* LayerGameMain::listener = EventListenerTouchOneByOne::create();

Scene* LayerGameMain::createScene()
{
    auto ret = Scene::create();
    auto layer = LayerGameMain::create();
    ret->addChild(layer);

    return ret;
}

bool LayerGameMain::init()
{
    if (!Layer::init())
    {
        return false;
    }
    arrStone = __Array::create();
    arrStone->retain();//计数器+1,否则会被回收
    _steps.clear();//初始化steps链表
    selectBox = Sprite::create("selected.png");
    selectBox->setVisible(false);
    selectBox->setLocalZOrder(1000);//设置显示优先级
    this->addChild(selectBox);

    this->addPlate();
    this->addStones();

    addCtrlMenu();

    
    listener->setEnabled(false);
    listener->setSwallowTouches(false);
    listener->onTouchBegan = CC_CALLBACK_2(LayerGameMain::touchBeganCallBack, this);
    listener->onTouchEnded = CC_CALLBACK_2(LayerGameMain::touchEndedCallBack, this);
    eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

    return true;
}

void LayerGameMain::addPlate()
{
    Plate *plate = Plate::create();
    
    this->addChild(plate);
}

void LayerGameMain::addStones()
{
    int i = 0;
    int randX = 0, randY = 0;
    Stone *stone;
    for (i = 0; i < 32; i++)
    {
        randX = rand() % 320;
        randY = rand() % 320;
        stone = Stone::create(i);
        arrStone->addObject(stone);//将棋子添加进数组
        stone->setPosition(Point(randX, randY));
        
        this->addChild(stone);
    }
}

Stone* LayerGameMain::getStoneFromArrById(int stoneId)
{
    Stone *ret;
    ret = nullptr;
    if (stoneId >= 0)
    {
        ret = (Stone *)arrStone->getObjectAtIndex(stoneId);
    }
    
    return ret;
}

bool LayerGameMain::touchBeganCallBack(Touch *pTouch, Event *pEvent)
{

    return true;
}
void LayerGameMain::touchEndedCallBack(Touch *pTouch, Event *pEvent)
{
    if (_selectedId == -1)
    {
        selectStone(pTouch);
    }
    else
    {
        moveStone(pTouch);
        judgeWin();
        //人工智能算法
        if (!_redTurn)
        {
            scheduleOnce(schedule_selector(LayerGameMain::computerMoveCallBack), 0.01f);
        }
    }
    judgeWin();
}

bool LayerGameMain::Screen2Plate(Point &point, int &row, int &col)
{
    int distance = Stone::_d/2 * Stone::_d/2;//以半径作为距离
    for (row = 0; row <= 9; row++)
    {
        for (col = 0; col <= 8; col++)
        {
            Point ptCenter = Plate2Screen(row, col);
            if (distance > ptCenter.getDistanceSq(point))//获取点距的平方并比较,如果小于半径平方则为能选中中心点上的棋子
            {
                return true;
            }
        }
    }

    return false;
}
Point LayerGameMain::Plate2Screen(int row, int col)//该函数与Stone的getPositionFromPlate类似
{
    Point ret = Point(Stone::_offx + col*Stone::_d, Stone::_offy + row*Stone::_d);
    
    return ret;
}
int LayerGameMain::getStoneIdFromRowCol(int row, int col)
{
    int ret = -1;
    Ref *obj;
    CCARRAY_FOREACH(arrStone,obj)
    {
        Stone *stone = (Stone *)obj;
        if (stone->_row == row && stone->_col == col && !stone->_isdead)//已死去的棋子不能选中
        {
            ret = stone->_id;
            return ret;
        }
    }
    return ret;
}

void LayerGameMain::selectStone(Touch *pTouch)
{
    Point point = pTouch->getLocation();
    int row, col;
    if (!Screen2Plate(point, row, col))//点中范围不在棋盘上直接返回
    {
        selectBox->setVisible(false);
        return;
    }
    int clickedId = getStoneIdFromRowCol(row, col);
    if (clickedId < 0)//没选中棋子直接返回
    {
        selectBox->setVisible(false);
        return;
    }
    Stone *clickedStone = getStoneFromArrById(clickedId);
    if (clickedStone->_red != _redTurn)//如果没有轮到,不能选中、移动
    {
        return;
    }
    selectBox->setPosition(Plate2Screen(row, col));
    selectBox->setVisible(true);

    _selectedId = clickedId;//记录下棋子id
}

void LayerGameMain::moveStone(Touch *pTouch)
{
    Point ptClicked = pTouch->getLocation();
    int row, col;
    if (!Screen2Plate(ptClicked, row, col))//获取触点的行列,如果触点不在棋盘上直接返回
    {
        return;
    }
    int clickedId = getStoneIdFromRowCol(row, col);//获取触点上的棋子id,没有棋子为-1
    //如果点中相同颜色的棋子,选中框转移到最后选中的棋子上
    if (clickedId != -1 && isSameColor(clickedId))
    {
        Stone *clickedStone = getStoneFromArrById(clickedId);
        selectBox->setPosition(Plate2Screen(clickedStone->_row, clickedStone->_col));
        _selectedId = clickedId;
        return;
    }

    if (!canMove(_selectedId, row, col, clickedId))
    {
        return;
    }

    Stone *selectedStone = getStoneFromArrById(_selectedId);

    //悔棋相关,移动前记录各项信息
    recordStep(_selectedId, clickedId, selectedStone->_row, selectedStone->_col, row, col);
    
    //移动相关
    Point pt = Plate2Screen(row, col);
    selectedStone->setPosition(pt);
    //MoveTo *to = MoveTo::create(0.1, pt);
    //selectedStone->runAction(to);
    selectedStone->_row = row;
    selectedStone->_col = col;

    if (clickedId != -1)
    {
        Stone *clickedStone = getStoneFromArrById(clickedId);
        clickedStone->setVisible(false);
        clickedStone->_isdead = true;
    }

    //移动完毕初始化选中框和选中id
    selectBox->setVisible(false);
    _selectedId = -1;//移动完成后初始化为-1
    _redTurn = !_redTurn;

}

int LayerGameMain::getStoneCount(int row1, int col1, int row2, int col2)
{
    int ret = 0;//记录棋子的个数
    if (row1 != row2 && col1 != col2) return -1;//行列都不同,没有直线
    if (row1 == row2 && col1 == col2) return -1;//行列都相同,两点相同,不需要移动

    if (row1 == row2)//列相同
    {
        int mincol = col1 < col2 ? col1 : col2;//获取两点间较小的行
        int maxcol = col1 > col2 ? col1 : col2;//获取两点间较大的行
        for (int col = mincol+1; col < maxcol; col++)//遍历两点间所有点
        {
            if (getStoneIdFromRowCol(row1, col) > 0)//如果存在棋子,ret棋子计数器+1
            {
                ++ret;
            }
        }
    }
    else if(col1 == col2)
    {
        int minrow = row1 < row2 ? row1 : row2;//获取两点间较小的列
        int maxrow = row1 > row2 ? row1 : row2;//获取两点间较大的列
        for (int row = minrow + 1; row < maxrow; row++)//遍历两点间所有点
        {
            if (getStoneIdFromRowCol(row, col1) > 0)//如果存在棋子,ret棋子计数器+1
            {
                ++ret;
            }
        }
    }

    return ret;
}

bool LayerGameMain::canMove(int moveid, int row, int col, int killid)
{
    Stone *stone = getStoneFromArrById(moveid);
    switch (stone->_type)
    {
        case Stone::CHE:
            return canMoveChe(moveid, row, col);
            break;
        case Stone::JIANG:
            return canMoveJiang(moveid, row, col, killid);
            break;
        case Stone::BING:
            return canMoveBing(moveid, row, col);
            break;
        case Stone::PAO:
            return canMovePao(moveid, row, col, killid);
            break;
        case Stone::MA:
            return canMoveMa(moveid, row, col);
            break;
        case Stone::XIANG:
            return canMoveXiang(moveid, row, col);
            break;
        case Stone::SHI:
            return canMoveShi(moveid, row, col);
            break;
        default:
            break;
    }
    return false;
}
bool LayerGameMain::canMoveChe(int moveid, int row, int col)
{
    //车只有在两点间不存在棋子时才能走
    Stone *che = getStoneFromArrById(moveid);
    return getStoneCount(che->_row, che->_col, row, col) == 0;
}
bool LayerGameMain::canMovePao(int moveid, int row, int col, int killid)
{
    //炮可以行走只有两种情况:1.两点间没有棋子。2.两点间存在一个棋子,并且目的点也存在棋子。
    Stone *pao = getStoneFromArrById(moveid);
    if (killid == -1)
    {
        return getStoneCount(pao->_row, pao->_col, row, col) == 0;
    }

    return getStoneCount(pao->_row, pao->_col, row, col) == 1;
}
bool LayerGameMain::canMoveBing(int moveid, int row, int col)
{
    //兵行走规则:1.只能走一格。2.不能回退。3.过河前只能直走
    Stone *bing = getStoneFromArrById(moveid);
    int dRow = abs(bing->_row - row);
    int dCol = abs(bing->_col - col);
    int d = dRow * 10 + dCol;//此处类似标记效果,10等价于(1,0),1代表可移动,第一位说明可以左右移动一格,第二位说明可以上下移动一格
    if (d != 1 && d != 10)
    {
        return false;
    }

    if (bing->_red)
    {
        //兵不能回退
        if (row < bing->_row)
        {
            return false;
        }
        //兵过河前不能左右移动
        if (bing->_row <= 4 && bing->_row == row)
        {
            return false;
        }
    }
    else
    {
        if (row > bing->_row)
        {
            return false;
        }
        if (bing->_row >= 5 && bing->_row == row)
        {
            return false;
        }
    }

    return true;
}
bool LayerGameMain::canMoveJiang(int moveid, int row, int col, int killid)
{
    //将规则:1.一次只能移动一格。2.不能出九宫格。3.将帅照面时可以直接对杀。
    Stone *jiang = getStoneFromArrById(moveid);
    //将照面杀
    if (killid != -1)
    {
        Stone *kill = getStoneFromArrById(killid);
        if (kill->_type == Stone::JIANG)
        {
            return canMoveChe(moveid, row, col);//复用车的移动方法,减少代码量
        }
    }
    int dRow = abs(jiang->_row - row);
    int dCol = abs(jiang->_col - col);
    int d = dRow * 10 + dCol;//此处类似标记效果,10等价于(1,0),1代表可移动,第一位说明可以左右移动一格,第二位说明可以上下移动一格
    if (d != 1 && d != 10)
    {
        return false;
    }
    //将不能左右出九宫
    if (col < 3 || col > 5)
    {
        return false;
    }
    //将不能上下出九宫
    if (jiang->_red)
    {
        if (row > 2 || row <0)
        {
            return false;
        }
    }
    else
    {
        if (row < 7 || row >9)
        {
            return false;
        }
    }
    return true;
}
bool LayerGameMain::canMoveMa(int moveid, int row, int col)
{
    //马规则:1.马走日。2.卡住马脚不能移动。
    Stone *ma = getStoneFromArrById(moveid);
    int dRow = abs(ma->_row - row);
    int dCol = abs(ma->_col - col);
    int d = dRow * 10 + dCol;
    //12为横日,21为竖日,类似于(1,2)(2,1)做法
    if (d == 12 || d == 21)
    {
        int cRow, cCol;
        if (d == 12)
        {
            //蹩脚点
            cCol = (col + ma->_col) / 2;
            cRow = ma->_row;
        }
        else
        {
            cCol = ma->_col;
            cRow = (ma->_row + row) / 2;
        }
        //获取蹩脚点后判断蹩脚点是否有棋子
        if (getStoneIdFromRowCol(cRow, cCol) == -1)
        {
            return true;
        }
    }
    return false;
}
bool LayerGameMain::canMoveXiang(int moveid, int row, int col)
{
    //象规则:1.象走田。2.被卡象眼不能移动。3.象不能过河
    Stone *xiang = getStoneFromArrById(moveid);
    int dRow = abs(xiang->_row - row);
    int dCol = abs(xiang->_col - col);
    int d = dRow * 10 + dCol;
    if (d != 22)
    {
        return false;
    }

    int cRow, cCol;
    cRow = (row + xiang->_row) / 2;
    cCol = (col + xiang->_col) / 2;
    if (getStoneIdFromRowCol(cRow, cCol) != -1)//卡主香烟不能移动
    {
        return false;
    }

    if (xiang->_red)
    {
        if (row > 4) return false;
    }
    else
    {
        if (row < 5) return false;
    }

    return true;
}
bool LayerGameMain::canMoveShi(int moveid, int row, int col)
{
    //士规则:1.一次斜着走一格。2.不能出九宫格
    Stone *shi = getStoneFromArrById(moveid);
    int dRow = abs(shi->_row - row);
    int dCol = abs(shi->_col - col);
    int d = dRow * 10 + dCol;
    //只能斜线走
    if (d != 11)
    {
        return false;
    }
    //不能两侧走出九宫
    if (col<3 || col>5)
    {
        return false;
    }
    //不能上下走出九宫
    if (shi->_red)
    {
        if (row>2 || row<0)
        {
            return false;
        }
    }
    else
    {
        if (row<7 || row>9)
        {
            return false;
        }
    }

    return true;
}

bool LayerGameMain::isSameColor(int clickedId)
{
    Stone *clickedStone = getStoneFromArrById(clickedId);
    Stone *selectedStone = getStoneFromArrById(_selectedId);

    return selectedStone->_red == clickedStone->_red;
}

void LayerGameMain::recordStep(int moveid, int killid, int rowFrom, int colFrom, int rowTo, int colTo)
{
    Step *step = new Step();
    step->moveid = moveid;
    step->killid = killid;
    step->rowFrom = rowFrom;
    step->colFrom = colFrom;
    step->rowTo = rowTo;
    step->colTo = colTo;
    
    _steps.push_back(step);
}

void LayerGameMain::addCtrlMenu()
{
    Menu *menu = Menu::create();
    MenuItemImage *newgameItem = MenuItemImage::create("new.png",
                                                      "newselected.png",
                                                      CC_CALLBACK_1(LayerGameMain::newgameItemCallback, this));
    MenuItemImage *regretItem = MenuItemImage::create("regret.png",
                                                      "regretselected.png",
                                                      CC_CALLBACK_1(LayerGameMain::regretItemCallback, this));
    MenuItemImage *startItem = MenuItemImage::create("start.png",
                                                      "startselected.png",
                                                      CC_CALLBACK_1(LayerGameMain::startItemCallback, this));
    MenuItemFont *quitItem = MenuItemFont::create("quit", 
                                                  CC_CALLBACK_1(LayerGameMain::quitItemCallback, this));
    
    menu->addChild(startItem);
    menu->addChild(newgameItem);
    menu->addChild(regretItem);
    menu->addChild(quitItem);

    this->addChild(menu);
    menu->alignItemsVerticallyWithPadding(10);
    menu->setPosition(Point(400, 160));
}

void LayerGameMain::regretItemCallback(Ref *obj)
{
    if (_steps.size() <= 1)//只走了一步不能悔棋
    {
        return;
    }
    if (!_redTurn)//AI:轮到黑棋不能悔棋
    {
        return;
    }
    for (int i = 0; i < 2; i++)//悔棋两步
    {
        regret1Step();
    }

    return;
}
void LayerGameMain::newgameItemCallback(Ref *obj)
{
    Stone *stone;
    for (int i = 0; i < 32; i++)
    {
        stone = getStoneFromArrById(i);
        stone->removeFromParent();
    }
    arrStone->removeAllObjects();
    for (int i = 0; i < 32; i++)
    {
        stone = Stone::create(i);
        arrStone->addObject(stone);
        stone->setPosition(Plate2Screen(stone->_row, stone->_col));
        addChild(stone);
    }
    _redTurn = true;
    _steps.clear();
    if (layer!=nullptr)
    {
        layer->removeFromParent();
        layer = nullptr;
    }
    return;
}
void LayerGameMain::startItemCallback(Ref *obj)
{
    Stone *stone;
    listener->setEnabled(true);
    listener->setSwallowTouches(true);
    for (int i = 0; i < 32; i++)
    {
        stone = getStoneFromArrById(i);
        stone->setPosition(stone->getPositionFromPlate());
        //Point pos = Plate2Screen(stone->_row, stone->_col);
        //MoveTo *to = MoveTo::create(1, pos);
    }
    return;
}
void LayerGameMain::quitItemCallback(Ref *obj)
{
    exit(0);
}

void LayerGameMain::regret1Step()
{
    Step *step = *_steps.rbegin();
    Stone *stone = getStoneFromArrById(step->moveid);
    stone->_row = step->rowFrom;
    stone->_col = step->colFrom;
    stone->setPosition(Plate2Screen(stone->_row, stone->_col));
    if (step->killid != -1)//如果目标点有棋子,将棋子回归为原来状态
    {
        stone = getStoneFromArrById(step->killid);
        stone->_isdead = false;
        stone->setVisible(true);
    }
    _redTurn = !_redTurn;//执棋权归还给对方
    delete step;
    _steps.pop_back();

    //防止选中棋子情况下悔棋
    selectBox->setVisible(false);
    _selectedId = -1;

    return;
}

void LayerGameMain::computerMoveCallBack(float dt)
{
    Step *step = AI::getStep(this);

    Stone* moveStone = getStoneFromArrById(step->moveid);
    moveStone->_row = step->rowTo;
    moveStone->_col = step->colTo;
    
    moveStone->setPosition(moveStone->getPositionFromPlate());
    /*MoveTo *moveTo = MoveTo::create(0.1f, moveStone->getPositionFromPlate());
    moveStone->runAction(moveTo);*/
    _redTurn = !_redTurn;

    if (step->killid != -1)
    {
        Stone *killedStone = getStoneFromArrById(step->killid);
        killedStone->_isdead = true;
        killedStone->setVisible(false);
    }

    _steps.push_back(step);
}

void LayerGameMain::judgeWin()
{
    Stone *jiang = getStoneFromArrById(23);
    if (jiang->_isdead)
    {
        layer = Judger::create(1);
        addChild(layer);
        layer->setPosition(Point(Director::getInstance()->getWinSize().width / 2, Director::getInstance()->getWinSize().height / 2));
        listener->setEnabled(false);
        listener->setSwallowTouches(false);
        return;
    }
    Stone *shuai = getStoneFromArrById(7);
    if (shuai->_isdead)
    {
        layer = Judger::create(0);
        addChild(layer);
        layer->setPosition(Point(Director::getInstance()->getWinSize().width / 2, Director::getInstance()->getWinSize().height / 2));
        listener->setEnabled(false);
        listener->setSwallowTouches(false);
        return;
    }
}

至此我们就可以跟人机互动下棋了,不过因为这里电脑只计算了一步,所以显得非常无脑,而要计算多步需要用到递归,因为篇幅太长,所以还是在下一篇才对算法加强一下。

相关文章

网友评论

  • 9ef2b7de8963:你的cocos2d中国象棋写的很好啊,作为一个新手,很容易就看懂了,为什么不更新了呢?

本文标题:cocos2d-x3.14中国象棋AI(八)AI1

本文链接:https://www.haomeiwen.com/subject/xulrqxtx.html