参考文章:【Cocos Creator 实战教程(1)】——人机对战五子棋(节点事件相关)
源码:goBang
五子棋是起源于中国古代的传统黑白棋种之一。现代五 子棋日文称之为“连珠”,英译为“Renju”,英文称之为 “Gobang”或“FIR”(Five in a Row 的缩写),亦有“连五子”、 “五子连”、“串珠”、“五目”、“五目碰”等多种称谓。
思考一:作为对手的系统用什么算法下棋?
估值函数、搜索算法和胜负判断等
博弈算法,在极大极小值搜索中应用alpha-beta剪枝
智能五子棋博弈程序的核心算法
智能五子棋中的算法研究
人机版五子棋两种算法概述
思考二:人机博弈的要点
1.棋局的状态能够在机器中表示出来,并能让程序知道当时的博弈状态
2.合法的走法规则如何在机器中实现,以便不让机器随便乱走而有失公平
3.如何让机器从所有的合法走法中选择最佳的走法
4.一种判断博弈状态优劣的方法,并能让机器能够做出智能的选择
5.一个显示博弈状态的界面,有了这样的界面程序才能用的起来而有意义
思考三:五子棋下棋规矩
作为了解,新手的我没做
五子棋对局,执行黑方指定开局、三手可交换、五手两打的规定。
整个对局过程中黑方有禁手,白方无禁手。
黑方禁手有三三禁手、四四禁手和长连禁手三种
思考四:人机下棋逻辑
系统先下,黑棋落子,交换下子顺序
玩家下,监测胜负(无胜负,交换下子顺序)
系统下(五元组中找最优位置),监测胜负(无胜负,交换下子顺序)
。。。
直到分出胜负(这里未考虑平局)
出现提示窗,告知玩家战局结果,同时可选择“返回菜单”或“再来一局”
具体实现:涉及知识点
1. 预制棋子资源
官方文档--预制资源
将其改名为Chess拖入下面assets文件夹使其成为预制资源
2. 按钮添加事件
1.在canvas节点上挂载Menu脚本组件
2.在按钮事件中,拖拽和选择相应的Target,Component和Handler
Button事件
属性 | 功能说明 |
---|---|
Target | 带有脚本组件的节点 |
Component | 脚本组件名称 |
Handler | 指定一个回调函数,当用户点击 Button 并释放时会触发此函数 |
CustomEventData | 用户指定任意的字符串作为事件回调的最后一个参数传入 |
3. 添加用户脚本组件
4.初始化棋盘225个棋子节点
- 计算使每个棋子节点位于指定位置
cc.Class({
extends: cc.Component,
properties: {
chessPrefab:{//棋子的预制资源
default:null,
type:cc.Prefab
},
chessList:{//棋子节点的集合,用一维数组表示二维位置
default: [],
type: [cc.node]
},
whiteSpriteFrame:{//白棋的图片
default:null,
type:cc.SpriteFrame
},
blackSpriteFrame:{//黑棋的图片
default:null,
type:cc.SpriteFrame
},
touchChess:{//每一回合落下的棋子
default:null,
type:cc.Node,
visible:false//属性窗口不显示
}
},
onLoad: function(){
var self = this;
for(var y = 0; y < 15; y++){
for(var x = 0; x < 15; x++){
var newNode = cc.instantiate(this.chessPrefab); //复制Chess预制资源
this.node.addChild(newNode);
//根据棋盘和棋子大小计算使每个棋子节点位于指定位置
newNode.setPosition(cc.p(x*40+20,y*40+20));
newNode.tag = y*15+x;//根据每个节点的tag就可以算出其二维坐标
this.chessList.push(newNode);
}
}
}
})
初始化棋子节点断点截图
- 为每个棋子节点添加事件
//此代码包含在onLoad for循环内,方便解读,提出来
//玩家点击棋子节点触发以下代码
newNode.on(cc.Node.EventType.TOUCH_END,function(event){
self.touchChess = this;
//下子顺序到白棋
if(self.gameState === 'white' && this.getComponent(cc.Sprite).spriteFrame === null){
//下子后添加棋子图片使棋子显示
this.getComponent(cc.Sprite).spriteFrame = self.whiteSpriteFrame;
//监测胜负
self.judgeOver();
//轮到电脑下棋
if(self.gameState == 'black'){
self.scheduleOnce(function(){self.ai()},1);//延迟一秒电脑下棋
}
}
});
5.添加五元数组(横,竖,右上斜,右下斜),572个
//横向
for(var y=0;y<15;y++){
for(var x=0;x<11;x++){
this.fiveGroup.push([y*15+x,y*15+x+1,y*15+x+2,y*15+x+3,y*15+x+4]);
}
}
//纵向
for(var x=0;x<15;x++){
for(var y=0;y<11;y++){
this.fiveGroup.push([y*15+x,(y+1)*15+x,(y+2)*15+x,(y+3)*15+x,(y+4)*15+x]);
}
}
//右上斜向
for(var b=-10;b<=10;b++){
for(var x=0;x<11;x++){
if(b+x<0||b+x>10){
continue;
}else{
this.fiveGroup.push([(b+x)*15+x,(b+x+1)*15+x+1,(b+x+2)*15+x+2,(b+x+3)*15+x+3,(b+x+4)*15+x+4]);
}
}
}
//右下斜向
for(var b=4;b<=24;b++){
for(var y=0;y<11;y++){
if(b-y<4||b-y>14){
continue;
}else{
this.fiveGroup.push([y*15+b-y,(y+1)*15+b-y-1,(y+2)*15+b-y-2,(y+3)*15+b-y-3,(y+4)*15+b-y-4]);
}
}
}
6.系统在最高分的五元组中找到最优位置落子
系统为黑棋的评分表:
//五元组中无子
GGTupleTypeBlank = 7,
//五元组中包含一个黑子
GGTupleTypeB = 35,
//五元组中包含两个黑子
GGTupleTypeBB = 800,
//五元组中包含三个黑子
GGTupleTypeBBB = 15000,
//五元组中包含四个黑子
GGTupleTypeBBBB = 800000,
//五元组中包含一个白子
GGTupleTypeW = 15,
//五元组中包含两个白子
GGTupleTypeWW = 400,
//五元组中包含三个白子
GGTupleTypeWWW = 1800,
//五元组中包含四个白子
GGTupleTypeWWWW = 100000,
//五元组中包含黑白子都有,此五元组为无效的,评分为零
GGTupleTypePolluted = 0
//评分
for(var i=0;i0&&w>0){
this.fiveGroupScore[i] = 0;
}else if(b==0&&w==1){
this.fiveGroupScore[i] = 15;
}else if(b==0&&w==2){
this.fiveGroupScore[i] = 400;
}else if(b==0&&w==3){
this.fiveGroupScore[i] = 1800;
}else if(b==0&&w==4){
this.fiveGroupScore[i] = 100000;
}else if(w==0&&b==1){
this.fiveGroupScore[i] = 35;
}else if(w==0&&b==2){
this.fiveGroupScore[i] = 800;
}else if(w==0&&b==3){
this.fiveGroupScore[i] = 15000;
}else if(w==0&&b==4){
this.fiveGroupScore[i] = 800000;
}
}
找最优位置下子
//找最高分的五元组
var hScore=0;
var mPosition=0;
for(var i=0;ihScore){
hScore = this.fiveGroupScore[i];
mPosition = (function(x){//js闭包
return x;
})(i);
}
}
//在最高分的五元组里找到最优下子位置(这段我没明白啥逻辑,for循环中只保留第一个判断也可以)
var flag1 = false;//无子
var flag2 = false;//有子
var nPosition = 0;
for(var i=0;i<5;i++){
if(!flag1&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){
nPosition = (function(x){return x})(i);
}
if(!flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame != null){
flag1 = true;
flag2 = true;
}
if(flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){
nPosition = (function(x){return x})(i);
break;
}
}
//在最最优位置下子
this.chessList[this.fiveGroup[mPosition][nPosition]].getComponent(cc.Sprite).spriteFrame = this.blackSpriteFrame;
this.touchChess = this.chessList[this.fiveGroup[mPosition][nPosition]];
- 最优位置断点
- 最优位置截图
7.监测胜负
judgeOver:function(){
var x0 = this.touchChess.tag % 15;
var y0 = parseInt(this.touchChess.tag / 15);
//判断横向
var fiveCount = 0;
for(var x = 0;x < 15;x++){
if((this.chessList[y0*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){
fiveCount++;
if(fiveCount==5){
if(this.gameState === 'white'){
this.overLabel.string = "你赢了";
this.overSprite.node.x = 0;
}else{
this.overLabel.string = "你输了";
this.overSprite.node.x = 0;
}
this.gameState = 'over';
return;
}
}else{
fiveCount=0;
}
}
//判断纵向
fiveCount = 0;
for(var y = 0;y < 15;y++){
if((this.chessList[y*15+x0].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){
fiveCount++;
if(fiveCount==5){
if(this.gameState === 'white'){
this.overLabel.string = "你赢了";
this.overSprite.node.x = 0;
}else{
this.overLabel.string = "你输了";
this.overSprite.node.x = 0;
}
this.gameState = 'over';
return;
}
}else{
fiveCount=0;
}
}
//判断右上斜向
var f = y0 - x0;
fiveCount = 0;
for(var x = 0;x < 15;x++){
if(f+x < 0 || f+x > 14){
continue;
}
if((this.chessList[(f+x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){
fiveCount++;
if(fiveCount==5){
if(this.gameState === 'white'){
this.overLabel.string = "你赢了";
this.overSprite.node.x = 0;
}else{
this.overLabel.string = "你输了";
this.overSprite.node.x = 0;
}
this.gameState = 'over';
return;
}
}else{
fiveCount=0;
}
}
//判断右下斜向
f = y0 + x0;
fiveCount = 0;
for(var x = 0;x < 15;x++){
if(f-x < 0 || f-x > 14){
continue;
}
if((this.chessList[(f-x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){
fiveCount++;
if(fiveCount==5){
if(this.gameState === 'white'){
this.overLabel.string = "你赢了";
this.overSprite.node.x = 0;
}else{
this.overLabel.string = "你输了";
this.overSprite.node.x = 0;
}
this.gameState = 'over';
return;
}
}else{
fiveCount=0;
}
}
//没有输赢交换下子顺序
if(this.gameState === 'black'){
this.gameState = 'white';
}else{
this.gameState = 'black';
}
}
个人想法
开局后,隔一会儿(如1分钟),玩家未落子,出提示窗,确认是否继续玩
设置积分,奖励制度
这是我学习五子棋游戏开发的记录,后续还会写其他游戏开发,加油!