《Cocos2d学习之路》六、小实践-象棋,简单AI对战

转载请说明出处:http://blog.csdn.net/lsmfeixiang/article/details/43226843

github地址:https://github.com/teffy/cocos2dx


最近在跟着一些视频资料学习,所以更新的更慢了。这段时间跟着视频做了一个象棋游戏,目前做了一个简单的AI版,不过AI智商太低,AI算法比较差,优化的不够,当作练习项目够用了。


一、一些注意的事项

1、C++11新特性,auto,lambda

auto i = 1;

编译器知道i是个整数类型

auto director =Director::getInstance();

根据getInstance的返回,编译器知道是Director类型的指针

建议:别滥用auto,实在不行的时候用,该定义什么类型还定义什么类型,全写成auto,以后代码看起来和维护都很痛苦

有关lambda的知识,可以看一下这里,http://www.cnblogs.com/yanhuiw/p/3931759.html,里面有关lamda的使用

2、一些cocos2d的注意事项

每一个Action只能使用一次,如果要复用,可以使用clone

EventListenerTouchOneByOne也是只能对一个Sprite使用

Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener, jiang);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener->clone(), shuai);

官方解释

注意:当再次使用 listener1 的时候,需要使用clone()方法创建一个新的克隆,因为在使用addEventListenerWithSceneGraphPriority或者addEventListenerWithFixedPriority方法时,会对当前使用的事件监听器添加一个已注册的标记,这使得它不能够被添加多次。另外,有一点非常重要,FixedPriority listener添加完之后需要手动remove,而SceneGraphPriority listener是跟Node绑定的,在Node的析构函数中会被移除。

3、C++代码的一个技巧(其实是我对于C++不太熟)

bool GameScene::checkTouchPositionStone(Vec2 touchPosition, int &x, int &y){
for (x = 0; x < 9; x++){
for (y = 0; y < 10; y++){
Vec2 theXYPostion = Vec2(stone_x + x*def, stone_y + y*def);
float distance = touchPosition.getDistance(theXYPostion);
if (distance < def / 2){
return true;
}}}
return false;
}

这样定义,x,y,在函数执行完成,就会被赋上值并返回

二、象棋实践

1、首先整理一下整个的思路。

棋盘界面所用到的资源有

1)游戏开始,Guide页面


两个棋子,点击任意一个棋子,之后两个棋子向中间滚动,当相撞的时候进入棋盘界面,并把点击的红棋还是黑棋传递到棋盘界面

2)棋盘界面,以及一些走棋的逻辑




1、首先是一个背景图片,floor.png,然后是棋盘background.png

2、摆放32个棋子,放在数组中,根据从guide界面来的点击的是否是红色棋子,如果是,摆放棋子的时候1-16的棋子图片资源是红色资源,否则是黑色图片资源

3、将棋盘分成横向为x轴,竖向为y轴的象棋坐标系,各个棋子放的位置为各个坐标(x,y),x为0-8,y为0-9


初始化的时候各个棋子所摆放的位置是固定的坐标,然后通过象棋坐标转换为真正在游戏中的坐标,每一个象棋坐标x,y差一个就是一个棋子的直径,根据各个棋子在象棋坐标系中的xy位置,乘以棋子的直径再加上相对于棋盘边的位移差就是各个棋子在游戏中的坐标。

4、棋子摆放好之后,就是要开始走棋了,走棋需要两个步骤,第一步是点击一个棋子,然后选中该棋子,第二步点击目标位置,这时候要判断目标位置是否有棋子,是否是同一方,还要根据这个棋子的走棋规则去判断是否可以走到目标位置上去;如果可以移动到目标位置上,先产生移动的step,执行动画将棋子移动过去,动画完成之后,目标位置上如果有敌方棋子,要修改敌方棋子的一些属性,将敌方棋子干死,并隐藏。


5、AI,是基于积分的机制根据一定的算法,产生对于当前棋局界面上积分的一些判断,进而产生一些可以走的步骤,然后根据这些步骤计算出对自己最有利的走法步骤,然后去执行这一步棋,棋的走法循环上面步骤4

2、具体实现过程

1、Guide界面的实现

一个红棋,一个黑棋,放在屏幕中间位置,之间留一点距离,然后设置touch监听
jiang = Sprite::create("res/bkg1.png");
	jiang->setPosition(Vec2(visibleSize.width / 2 - jiang->getContentSize().width * 2, visibleSize.height / 2));
	this->addChild(jiang);
	shuai = Sprite::create("res/bkg2.png");
	shuai->setPosition(Vec2(visibleSize.width / 2 + shuai->getContentSize().width * 2, visibleSize.height / 2));
	this->addChild(shuai);

	EventListenerTouchOneByOne* touchListener = EventListenerTouchOneByOne::create();
	touchListener->setSwallowTouches(true);
	touchListener->onTouchBegan = CC_CALLBACK_2(GuideScene::onTouchBegan, this);
	touchListener->onTouchMoved = CC_CALLBACK_2(GuideScene::onTouchMoved, this);
	touchListener->onTouchEnded = CC_CALLBACK_2(GuideScene::onTouchEnded, this);
	touchListener->onTouchCancelled = CC_CALLBACK_2(GuideScene::onTouchCancelled, this);
	Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener, jiang);
	Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener->clone(), shuai);// 复用touchListener,需要clone
当点击了任意一个棋子之后执行动画,然后进入下一个界面
void GuideScene::onTouchEnded(Touch* mTouch, Event* mEvent){
	Vec2 touchLocation = mTouch->getLocation();
	bool isClicked = false;
	if (jiang->getBoundingBox().containsPoint(touchLocation) || shuai->getBoundingBox().containsPoint(touchLocation)){
		isClicked = true;
		clickRed = shuai->getBoundingBox().containsPoint(touchLocation) ? true : false;//判断,点击的是黑棋还是红棋
	} else{
		isClicked = false;
	}
	if (isClicked){
		int duration = 1;
		Size winSize = Director::getInstance()->getWinSize();
		MoveTo* moveTo = MoveTo::create(duration, Vec2(winSize.width / 2, winSize.height / 2));
		RotateBy* rotateBy = RotateBy::create(duration, 360);
		Spawn* jiangAction = Spawn::create(moveTo, rotateBy, NULL);
		Spawn* shuaiAction = Spawn::create(moveTo->clone(), rotateBy->clone()->reverse(), NULL);
		jiang->runAction(jiangAction);
		shuai->runAction(shuaiAction);
		scheduleUpdate();//开启一个定时器,去判断两个棋子是否碰在一起了
	}
}
判断碰撞
void GuideScene::update(float delta){
	float jiang_x = jiang->getPositionX();
	float shuai_x = shuai->getPositionX();
	if (abs(jiang_x - shuai_x) < jiang->getContentSize().width){
		Director::getInstance()->replaceScene(GameScene::createScene(clickRed));//如果碰在一起,就进入棋盘界面
	}
}

2、棋盘界面的实现

这里由于需要接收从guide界面穿过来的一个参数,所以create方法需要重写一下
GameScene* GameScene::create(bool _clickRed){
	GameScene* game = new GameScene();
	if (game && game->init(_clickRed))
	{
		game->autorelease();
		return game;
	}
	else
	{
		delete game;
		game = NULL;
		return NULL;
	}
}
界面上,先是一个背景,然后是棋盘,按钮,在init中
	this->aiPlayer = new AIPlayer(this);

	clickRed = _clickRed;
	Size visibleSize = Director::getInstance()->getVisibleSize();
	Vec2 origin = Director::getInstance()->getVisibleOrigin();
	auto closeItem = MenuItemImage::create(
		"CloseNormal.png",
		"CloseSelected.png",
		CC_CALLBACK_1(GameScene::menuCloseCallback, this));
	closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width / 2,
		origin.y + closeItem->getContentSize().height / 2));

	MenuItemImage* backMenu = MenuItemImage::create("res/regret.png", "res/regret_selected.png", CC_CALLBACK_1(GameScene::backQIMenuCallback, this));
	backMenu->setPosition(Vec2(visibleSize.width - 200, visibleSize.height / 2 - 100));

	MenuItemImage* newGameMenu = MenuItemImage::create("res/new.png", "res/new_selected.png", CC_CALLBACK_1(GameScene::newGameMenuCallback, this));
	newGameMenu->setPosition(Vec2(visibleSize.width - 200, visibleSize.height / 2));

	auto menu = Menu::create(closeItem, backMenu, newGameMenu, NULL);
	menu->setPosition(Vec2::ZERO);
	this->addChild(menu, 1);
	/*
	auto label = Label::createWithTTF(getStringByKey("GuideStart"), "fonts/fangzheng.ttf", 24);
	label->setPosition(Vec2(origin.x + visibleSize.width/2,
	origin.y + visibleSize.height - label->getContentSize().height));
	this->addChild(label, 1);
	*/

	Vec2 offset = Vec2(20.0, 10.0);
	auto floor = Sprite::create("res/floor.png");
	floor->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
	floor->setScaleX(visibleSize.width / floor->getContentSize().width);
	floor->setScaleY(visibleSize.height / floor->getContentSize().height);
	this->addChild(floor);

	Sprite* qipan = Sprite::create("res/background.png");
	qipan->setAnchorPoint(Vec2::ZERO);
	qipan->setPosition(offset);
	qipan->setScale((visibleSize.height - offset.y * 2) / qipan->getContentSize().height);
	this->addChild(qipan);
	// 给棋盘添加touch监听
	EventListenerTouchOneByOne *touchListener = EventListenerTouchOneByOne::create();
	touchListener->onTouchBegan = CC_CALLBACK_2(GameScene::onTouchesBegan, this);
	touchListener->onTouchCancelled = CC_CALLBACK_2(GameScene::onTouchCancelled, this);
	touchListener->onTouchEnded = CC_CALLBACK_2(GameScene::onTouchEnded, this);
	touchListener->onTouchMoved = CC_CALLBACK_2(GameScene::onTouchMoved, this);
	EventDispatcher* eventDispatcher = Director::getInstance()->getEventDispatcher();
	eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener->clone(), qipan);
要定义一个棋子类Stone,需要一些参数以及一些方法
.h文件
class Stone : public Sprite
{
public:
	enum TYPE { JIANG, SHI, XIANG, JU, MA, PAO, BING };
	CC_SYNTHESIZE(TYPE, type, Type);//这个<span style="font-family: Arial, Helvetica, sans-serif;">CC_SYNTHESIZE宏定义功能类似于java中使用快捷键alt+shift+s,r自动生成get,set方法,但是这里是隐式的,看不到方法定义但可以直接调用</span>
	CC_SYNTHESIZE(int, x, X);//象棋坐标系中的位置
	CC_SYNTHESIZE(int, y, Y);
	CC_SYNTHESIZE(int, id, ID);
	CC_SYNTHESIZE(bool, isDead, isDead);
	CC_SYNTHESIZE(bool, isRed, isRed);

	static Stone* create(int _id, bool _clickRed){
		Stone* stone = new Stone();
		stone->init(_id, _clickRed);
		stone->autorelease();
		return stone;
	}
	void reset(bool _clickRed);
	bool init(int _id, bool _clickRed){
		const char* stonePic[14] = {//初始资源
			"res/rshuai.png",
			"res/rshi.png",
			"res/rxiang.png",
			"res/rche.png",
			"res/rma.png",
			"res/rpao.png",
			"res/rbing.png",

			"res/bjiang.png",
			"res/bshi.png",
			"res/bxiang.png",
			"res/bche.png",
			"res/bma.png",
			"res/bpao.png",
			"res/bzu.png"
		};
		id = _id;

		// 是否点击的是黑棋,和根据id的位置无关,id<16的就是在下半部分
		if (id < 16){
			type = stonePoints[id].type;
		}
		else{
			type = stonePoints[id - 16].type;
		}
		int iconIndex = -1;

		if (_clickRed){
			// 如果点击的是红棋,id<16的都是需要红棋资源
			iconIndex = (id < 16 ? 0 : 1) * 7 + type;
			isRed = id < 16;
		}
		else{
			// 如果点击的是黑棋,id<16的都是需要黑棋资源
			iconIndex = (id < 16 ? 1 : 0) * 7 + type;
			isRed = id >= 16;
		}
		Sprite::initWithFile(stonePic[iconIndex]);
		//setScale(0.6f);
		reset(_clickRed);
		return true;
	}
	static struct StoneInitPoint{
		int x;
		int y;
		Stone::TYPE type;
	}
	stonePoints[32];
	int getRealX();//在游戏中的真实坐标
	int getRealY();
};
.cpp
Stone::StoneInitPoint Stone::stonePoints[32] =
{//初始的时候各个棋子所在的位置,只列举了1-16棋盘下半部分的棋子
	{ 0, 0, Stone::JU },
	{ 1, 0, Stone::MA },
	{ 2, 0, Stone::XIANG },
	{ 3, 0, Stone::SHI },
	{ 4, 0, Stone::JIANG },
	{ 5, 0, Stone::SHI },
	{ 6, 0, Stone::XIANG },
	{ 7, 0, Stone::MA },
	{ 8, 0, Stone::JU },

	{ 1, 2, Stone::PAO },
	{ 7, 2, Stone::PAO },

	{ 0, 3, Stone::BING },
	{ 2, 3, Stone::BING },
	{ 4, 3, Stone::BING },
	{ 6, 3, Stone::BING },
	{ 8, 3, Stone::BING },
};

void Stone::reset(bool _clickRed){
	this->setisDead(false);
	if (id < 16){
		this->setX(stonePoints[id].x);
		this->setY(stonePoints[id].y);
	}
	else{
		this->setX(8 - stonePoints[id - 16].x);
		this->setY(9 - stonePoints[id - 16].y);
	}
}

int Stone::getRealX(){
	return stone_def_x + getX()*stone_def;//位移差量+棋子直径*象棋坐标
}
int Stone::getRealY(){
	return stone_def_y + getY()*stone_def;
}
向棋盘上摆放棋子,实现一个效果是各个棋子在棋盘的随机位置上出现,然后动画移动到最终目标位置上,在init中
	for (int i = 0; i < 32; i++){
		sts[i] = Stone::create(i, clickRed);
		sts[i]->setPosition(Vec2(rand_0_1()*visibleSize.width, rand_0_1()*visibleSize.height));//初始化随机位置
		MoveTo* moveTo = MoveTo::create(0.6f, Vec2(sts[i]->getRealX(), sts[i]->getRealY()));//执行动画到最终位置
		//MoveTo* moveTo = MoveTo::create(0.6f, Vec2(stone_x + sts[i]->getX()*def, stone_y + sts[i]->getY()*def));
		//sts[i]->setPosition(Vec2(stone_x + sts[i]->getX()*def, stone_y + sts[i]->getY()*def));
		sts[i]->runAction(moveTo);
		this->addChild(sts[i]);
	}

	spriteSelected = Sprite::create("res/selected.png");
	spriteSelected->setVisible(false);
	addChild(spriteSelected, 10);
	select_id = -1;
	//level = 1;
	isRedTurn = true;
	steps = Array::create();
	steps->retain();

3、游戏逻辑-走棋判断

在touch的时候来判断点击的位置是否棋盘,是棋盘上的那个棋子,点击的是棋子,则记录一下点击的棋子的id,在点第二下的时候判断位置是否可以移动过去,然后根据不同棋子的走棋规则加以判断,最后move
判断点击的是否是棋盘
	Vec2 touchPosition = mTouch->getLocation();
	int tox = 0, toy = 0;
	// 判断点击的是不是棋盘内,并把点击的坐标的x,y确定下来
	if (!checkTouchPositionStone(touchPosition, tox, toy)){
		return false;
	}
bool GameScene::checkTouchPositionStone(Vec2 touchPosition, int &x, int &y){
	for (x = 0; x < 9; x++){
		for (y = 0; y < 10; y++){
			Vec2 theXYPostion = Vec2(stone_def_x + x*stone_def, stone_def_y + y*stone_def);
			float distance = touchPosition.getDistance(theXYPostion);
			if (distance < stone_def / 2){
				return true;
			}
		}
	}
	return false;
}
检测点击的是哪个棋子
	int clickStoneID = getStoneIDByXY(tox, toy);//默认返回-1,即点击的位置不是棋子
	if (select_id == -1){//select_id是记录点击的棋子,如果是-1说明是第一次点击,记录这个点,并把选中框显示出来
		setSelectedID(clickStoneID);
	}
	else{
		moveToXY(select_id, clickStoneID, tox, toy);//如果是第二次点击就执行move动作的逻辑
	}
	return true;
int GameScene::getStoneIDByXY(int x, int y){
	for (int i = 0; i < 32; i++){
		if (sts[i]->getX() == x && sts[i]->getY() == y && !sts[i]->getisDead()){
			return sts[i]->getID();
		}
	}
	return -1;
}
void GameScene::setSelectedID(int clickID){
	if (clickID == -1){
		return;
	}
	if (isRedTurn != sts[clickID]->getisRed()){//判断该谁走了
		return;
	}

	select_id = clickID;//记录选中的棋子的id
	spriteSelected->setVisible(true);//选中框显示出来,并设置位置到选中的棋子位置上去
	spriteSelected->setPosition(sts[select_id]->getPosition());
}
move的逻辑判断
棋子move的判断还要根据各个棋子的走法规则来判断是否可以move
void GameScene::moveToXY(int moveID, int killID, int tox, int toy){
	// 判断是否可以移动到xy位置,如果是并移动到xy的位置,并将select_id 置为-1
	if (killID != -1 && sts[moveID]->getisRed() == sts[killID]->getisRed()){
		setSelectedID(killID);
		return;
	}
	// 判断是否可以移动棋子
	bool canMove = isCanMove(moveID, killID, tox, toy);
	if (!canMove){
		return;
	}
}
bool GameScene::isCanMove(int moveID, int killID, int tox, int toy){
	/*根据要移动的棋子的类型来分别判断*/
	Stone* moveStone = sts[moveID];
	switch (moveStone->getType()){
	case Stone::JIANG:
		return canMoveJIANG(moveID, killID, tox, toy);
	case Stone::SHI:
		return canMoveSHI(moveID, tox, toy);
	case Stone::XIANG:
		return canMoveXIANG(moveID, killID, tox, toy);
	case Stone::JU:
		return canMoveJU(moveID, killID, tox, toy);
	case Stone::MA:
		return canMoveMA(moveID, killID, tox, toy);
	case Stone::PAO:
		return canMovePAO(moveID, killID, tox, toy);
	case Stone::BING:
		return canMoveBING(moveID, killID, tox, toy);
	default:return false;
	}
}
每种棋子的走法规则
bool GameScene::canMoveJIANG(int moveID, int killID, int tox, int toy){
	/*如果要杀的对面的将或帅,则直接按车的走法*/
	if (killID != -1 && sts[killID]->getType() == Stone::JIANG){
		return canMoveJU(moveID, killID, tox, toy);
	}
	/*判断是否走的一格*/
	Stone* moveStone = sts[moveID];
	int m_x = moveStone->getX();
	int m_y = moveStone->getY();
	int xoff = abs(m_x - tox);
	int yoff = abs(m_y - toy);
	int checkXY = xoff * 10 + yoff;
	if (checkXY != 1 && checkXY != 10){
		return false;
	}

	/*判断将或帅是否走出9宫格*/
	if (tox > 5 || tox < 3){
		return false;
	}
	if (clickRed == sts[moveID]->getisRed()){
		if (toy > 2 || toy < 0){
			return false;
		}
	}
	else{
		if (toy > 9 || toy < 7){
			return false;
		}
	}
	return true;
}
bool GameScene::canMoveSHI(int moveID, int tox, int toy){
	Stone* moveStone = sts[moveID];
	int m_x = moveStone->getX();
	int m_y = moveStone->getY();
	int xoff = abs(m_x - tox);
	int yoff = abs(m_y - toy);
	if (xoff != 1 || yoff != 1){
		return false;
	}

	/*判断将或帅是否走出9宫格*/
	if (tox > 5 || tox < 3){
		return false;
	}
	if (clickRed == sts[moveID]->getisRed()){
		if (toy > 2 || toy < 0){
			return false;
		}
	}
	else{
		if (toy > 9 || toy < 7){
			return false;
		}
	}
	return true;
}
bool GameScene::canMoveXIANG(int moveID, int killID, int tox, int toy){
	Stone* moveStone = sts[moveID];
	int m_x = moveStone->getX();
	int m_y = moveStone->getY();
	int xoff = abs(m_x - tox);
	int yoff = abs(m_y - toy);
	if (xoff != 2 || yoff != 2){
		return false;
	}
	/*判断象眼位置*/
	int mid_x = (m_x + tox) / 2;
	int mid_y = (m_y + toy) / 2;
	int mid_stone = getStoneIDByXY(mid_x, mid_y);
	if (mid_stone != -1){
		return false;
	}
	//如果是在棋盘下面,y如果超过4,就越过楚河汉界了
	if (clickRed == sts[moveID]->getisRed()){
		if (toy > 4){
			return false;
		}
	}
	else{
		//如果是在棋盘上面,y如果小于5,就越过楚河汉界了
		if (toy < 5){
			return false;
		}
	}
	return true;
}
bool GameScene::canMoveJU(int moveID, int killID, int tox, int toy){
	int count = getStoneCountInSingleXY(moveID, killID, tox, toy);
	if (count != 0){
		return false;
	}
	return true;
}
bool GameScene::canMoveMA(int moveID, int killID, int tox, int toy){
	Stone* moveStone = sts[moveID];
	int m_x = moveStone->getX();
	int m_y = moveStone->getY();
	int xoff = abs(m_x - tox);
	int yoff = abs(m_y - toy);
	int checkXY = xoff * 10 + yoff;
	if (checkXY != 12 && checkXY != 21){
		return false;
	}
	if (xoff == 2 && (getStoneIDByXY((m_x + tox) / 2, m_y) != -1)){
		return false;
	}
	else if (yoff == 2 && (getStoneIDByXY(m_x, (m_y + toy) / 2) != -1)){
		return false;
	}
	else{}
	return true;
}
bool GameScene::canMovePAO(int moveID, int killID, int tox, int toy){
	int count = getStoneCountInSingleXY(moveID, killID, tox, toy);

	if (killID != -1 && count == 1){
		return true;
	}
	if (killID == -1 && count == 0){
		return true;
	}
	return false;
}
bool GameScene::canMoveBING(int moveID, int killID, int tox, int toy){
	Stone* moveStone = sts[moveID];
	int m_x = moveStone->getX();
	int m_y = moveStone->getY();
	int xoff = abs(m_x - tox);
	int yoff = abs(m_y - toy);
	int checkXY = xoff * 10 + yoff;
	if (checkXY != 1 && checkXY != 10){
		return false;
	}
	if (clickRed == moveStone->getisRed()){//棋盘的下半部分
		if (toy < m_y){
			return false;
		}
		if (m_y <= 4 && m_x != tox){//过河了
			return false;
		}
	}
	else{//棋盘的上半部分
		if (toy > m_y){
			return false;
		}
		if (m_y >= 5 && m_x != tox){//过河了
			return false;
		}
	}
	return true;
}
//判断在走的X,Y轴上,moveID和killID之间有多少个棋子
int GameScene::getStoneCountInSingleXY(int moveID, int killID, int tox, int toy){
	int count = 0;
	Stone* moveStone = sts[moveID];
	if (moveStone->getX() != tox && moveStone->getY() != toy){
		return -1;
	}
	int m_x = moveStone->getX();
	int m_y = moveStone->getY();
	if (m_x == tox){
		int y_min = m_y < toy ? m_y : toy;
		int y_max = m_y > toy ? m_y : toy;
		for (int i = y_min + 1; i < y_max; i++){
			if (getStoneIDByXY(m_x, i) != -1){
				count++;
			}
		}
	}
	else if (m_y == toy){
		int x_min = m_x < tox ? m_x : tox;
		int x_max = m_x > tox ? m_x : tox;
		for (int i = x_min + 1; i < x_max; i++){
			if (getStoneIDByXY(i, m_y) != -1){
				count++;
			}
		}
	}
	else{
		return -1;
	}
	return count;
}
根据判断的结果来执行真正的move
由于还有一个悔棋的功能,所以每一步走棋都需要记录下来,定义一个步骤Step类
class Step : public cocos2d::Object{
public:
	int moveID;
	int killID;
	int moveFromX;
	int moveFromY;
	int moveToX;
	int moveToY;
	static Step* create(
		int _moveID,
		int _killID,
		int _moveFromX,
		int _moveFromY,
		int _moveToX,
		int _moveToY)
	{
		Step* step = new Step();
		step->moveID = _moveID;
		step->killID = _killID;
		step->moveFromX = _moveFromX;
		step->moveFromY = _moveFromY;
		step->moveToX = _moveToX;
		step->moveToY = _moveToY;
		step->autorelease();
		return step;
	}
};
记录每一步,
	// 记录走的每一步
	Step* itemsStep = Step::create(moveID, killID, sts[moveID]->getX(), sts[moveID]->getY(), tox, toy);
	steps->addObject(itemsStep);
然后执行move的动画
	sts[moveID]->setX(tox);
	sts[moveID]->setY(toy);
	sts[moveID]->setZOrder(sts[moveID]->getZOrder() + 1);
	MoveTo* moveTo = MoveTo::create(0.5f, Vec2(sts[moveID]->getRealX(), sts[moveID]->getRealY()));
	CallFuncN* actionCallback = CallFuncN::create(CC_CALLBACK_1(GameScene::onActionComplete, this, moveID, killID));
	Sequence* seq = Sequence::create(moveTo, actionCallback, NULL);
	sts[moveID]->runAction(seq);

	spriteSelected->setVisible(false);
	select_id = -1;
	isRedTurn = !isRedTurn;
需要加一个有回调的CallFuncN的action,用来把选中框隐藏和将敌方棋子干掉

CallFuncN 动画使用可以参考这里

http://blog.csdn.net/crayondeng/article/details/18767407

void GameScene::onActionComplete(Node* node, int moveID, int killID){
	/*当MoveAction执行完成*/
	sts[moveID]->setZOrder(sts[moveID]->getZOrder() - 1);
	if (killID != -1){
		sts[killID]->setisDead(true);
		sts[killID]->setVisible(false);
		if (sts[killID]->getType() == Stone::JIANG){
			reStartGame();
		}
	}
<span style="white-space:pre">	</span>// 判断是否该AI走棋
<span style="white-space:pre">	</span>if (isRedTurn == !clickRed){
<span style="white-space:pre">		</span>aiGostep();
<span style="white-space:pre">	</span>}
}
这样走棋的功能就完成了
悔棋功能
前面已经记录了每一步走的步骤,那悔棋自然就简单多了,取出array的最后一个step,把数据还原回去,然后delete这个步骤,就实现悔棋了
void GameScene::backQIMenuCallback(cocos2d::Ref* pSender){
	if (steps->count() == 0){
		return;
	}
	Step* lastStep = (Step*)steps->getLastObject();
	sts[lastStep->moveID]->setX(lastStep->moveFromX);
	sts[lastStep->moveID]->setY(lastStep->moveFromY);
	sts[lastStep->moveID]->setPosition(Vec2(sts[lastStep->moveID]->getRealX(), sts[lastStep->moveID]->getRealY()));
	if (lastStep->killID != -1){
		sts[lastStep->killID]->setVisible(true);
		sts[lastStep->killID]->setisDead(false);
	}
	spriteSelected->setVisible(false);
	isRedTurn = !isRedTurn;
	steps->removeLastObject();
}

4、AI对战

AI对战,就是让电脑通过写的算法来自动产生对电脑方有利的走起步骤,算法也是剪枝,写的很简单,优化空间还很大,用来学习就可以了,真正目的不是在搞这个算法上,有兴趣的可以继续优化
.h
class AIPlayer
{
public:
	AIPlayer(GameScene* _gameScene);
	~AIPlayer();
	Step* getOneMoveAction(int level);
	int getScores();
	Array* getAllPosibleStep();
	void getAllPosibleStep(int i, Array* stepArray);
	int getMinScore(int level, int max);
	int getMaxScore(int level, int min);
public:
	GameScene* gameScene;
	static int stone_scores[7];
};
.c
#include "AIPlayer.h"
#include "GameScene.h"

AIPlayer::AIPlayer(GameScene* _gameScene){
	this->gameScene = _gameScene;
}

AIPlayer::~AIPlayer(){}

//enum TYPE { JIANG, SHI, XIANG, JU, MA, PAO, BING }; 各个棋子的分值对应
int AIPlayer::stone_scores[7] = { 1500, 10, 10, 100, 50, 50, 20 };

int AIPlayer::getScores(){
	int black_score = 0;
	int red_score = 0;
	for (int i = 0; i < 32; i++){
		Stone* stone = gameScene->sts[i];
		if (!stone->getisDead()){
			if (stone->getisRed()){
				red_score += stone_scores[stone->getType()];
			}
			else{
				black_score += stone_scores[stone->getType()];
			}
		}
	}
	return black_score - red_score;
}

Array* AIPlayer::getAllPosibleStep(){
	Array* stepsArray = Array::create();
	for (int i = 16; i < 32; i++){
		getAllPosibleStep(i, stepsArray);
	}
	return stepsArray;
}
void AIPlayer::getAllPosibleStep(int i, Array* stepArray){
	Stone* stone = gameScene->sts[i];
	if (stone->getisDead()){
		return;
	}
	for (int x = 0; x < 9; x++){
		for (int y = 0; y < 10; y++){
			int killID = gameScene->getStoneIDByXY(x, y);
			if (killID != -1 && gameScene->sts[killID]->getisRed() == stone->getisRed()){
				continue;
			}
			if (stone->getX() == x && stone->getY() == y){
				continue;
			}
			if (gameScene->isCanMove(stone->getID(), killID, x, y)){
				Step* step = Step::create(stone->getID(), killID, stone->getX(), stone->getY(),x, y);
				stepArray->addObject(step);
			}
		}
	}
}
Step* AIPlayer::getOneMoveAction(int level){
	int maxScore = -100000;
	Step* reStep;
	Array* possibleMove = getAllPosibleStep();
	Object* obj;
	CCARRAY_FOREACH(possibleMove, obj){
		Step* step = (Step*)obj;
		gameScene->fakeMove(step);
		int score = getMinScore(level - 1, maxScore);
		gameScene->unfakeMove(step);
		if (score > maxScore) {
			maxScore = score;
			reStep = step;
		}
	}
	return reStep;
}
int AIPlayer::getMinScore(int level, int max){
	if (level <= 0){
		return getScores();
	}
	int minScore = 100000;
	Step* reStep;
	Array* possibleMove = getAllPosibleStep();
	Object* obj;
	CCARRAY_FOREACH(possibleMove, obj){
		Step* step = (Step*)obj;
		gameScene->fakeMove(step);
		int score = getMaxScore(level - 1, minScore);
		gameScene->unfakeMove(step);
		if (score >= max) {
			return score;
		}
		if (score < minScore){
			minScore = score;
		}
	}
	return minScore;
}
int AIPlayer::getMaxScore(int level, int min){
	if (level <= 0){
		return getScores();
	}
	int maxScore = -100000;
	Step* reStep;
	Array* possibleMove = getAllPosibleStep();
	Object* obj;
	CCARRAY_FOREACH(possibleMove, obj){
		Step* step = (Step*)obj;
		gameScene->fakeMove(step);
		int score = getMinScore(level - 1, maxScore);
		gameScene->unfakeMove(step);
		if (score <= min) {
			return score;
		}
		if (score > maxScore){
			maxScore = score;
		}
	}
	return maxScore;
}
在GameScene的init中要初始化
this->aiPlayer = new AIPlayer(this);
然后还需要起一个delay的方法来判断是否进来就是ai走棋
scheduleOnce(CC_SCHEDULE_SELECTOR(GameScene::updateAutoGo), 1.0f);
void GameScene::updateAutoGo(float delay){
	unschedule(CC_SCHEDULE_SELECTOR(GameScene::updateAutoGo));
	if (!clickRed && isRedTurn){
		aiGostep();
	}
}
aiGostep方法就是让电脑计算出一个步骤的函数,需要在刚进入游戏的时候delay执行,在玩家走一步之后oncomplete中执行
	// 判断是否该AI走棋
	if (isRedTurn == !clickRed){
		aiGostep();
	}
在restart game的时候还需要再次delay执行
void GameScene::reStartGame(){
	Director::getInstance()->replaceScene(GameScene::createScene(clickRed));
	scheduleOnce(CC_SCHEDULE_SELECTOR(GameScene::updateAutoGo), 1.0f);
}
然后就可以虐电脑了,算法太简陋了,电脑实在太弱了!!!

OK,基本完事了,看教程里后面有一个网络对战版和一个Code优化的过程,暂时先到这里,后续慢慢跟进。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值