五子棋人机博弈游戏(cocos creator)

参考文章:【Cocos Creator 实战教程(1)】——人机对战五子棋(节点事件相关)
源码:goBang

五子棋是起源于中国古代的传统黑白棋种之一。现代五 子棋日文称之为“连珠”,英译为“Renju”,英文称之为 “Gobang”或“FIR”(Five in a Row 的缩写),亦有“连五子”、 “五子连”、“串珠”、“五目”、“五目碰”等多种称谓。

思考一:作为对手的系统用什么算法下棋?

估值函数、搜索算法和胜负判断等

博弈算法,在极大极小值搜索中应用alpha-beta剪枝

智能五子棋博弈程序的核心算法

智能五子棋中的算法研究

人机版五子棋两种算法概述

思考二:人机博弈的要点

1.棋局的状态能够在机器中表示出来,并能让程序知道当时的博弈状态

2.合法的走法规则如何在机器中实现,以便不让机器随便乱走而有失公平

3.如何让机器从所有的合法走法中选择最佳的走法

4.一种判断博弈状态优劣的方法,并能让机器能够做出智能的选择

5.一个显示博弈状态的界面,有了这样的界面程序才能用的起来而有意义

思考三:五子棋下棋规矩

作为了解,新手的我没做

五子棋对局,执行黑方指定开局、三手可交换、五手两打的规定。

整个对局过程中黑方有禁手,白方无禁手。

黑方禁手有三三禁手、四四禁手和长连禁手三种

思考四:人机下棋逻辑

系统先下,黑棋落子,交换下子顺序

玩家下,监测胜负(无胜负,交换下子顺序)

系统下(五元组中找最优位置),监测胜负(无胜负,交换下子顺序)

。。。

直到分出胜负(这里未考虑平局)

出现提示窗,告知玩家战局结果,同时可选择“返回菜单”或“再来一局”

具体实现:涉及知识点

1. 预制棋子资源

官方文档--预制资源

press.png

将其改名为Chess拖入下面assets文件夹使其成为预制资源

2. 按钮添加事件

1.在canvas节点上挂载Menu脚本组件

node.png

2.在按钮事件中,拖拽和选择相应的Target,Component和Handler

buttonEvent.png

Button事件

属性 功能说明
Target 带有脚本组件的节点
Component 脚本组件名称
Handler 指定一个回调函数,当用户点击 Button 并释放时会触发此函数
CustomEventData 用户指定任意的字符串作为事件回调的最后一个参数传入

3. 添加用户脚本组件

user.png

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);
            }
        }
    }
})

初始化棋子节点断点截图


console1.png
  • 为每个棋子节点添加事件
//此代码包含在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;i<this.fiveGroup.length;i++){
    var b=0;//五元组里黑棋的个数
    var w=0;//五元组里白棋的个数
    for(var j=0;j<5;j++){
        if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.blackSpriteFrame){
            b++;
        }else if(this.chessList[this.fiveGroup[i]         [j]].getComponent(cc.Sprite).spriteFrame == this.whiteSpriteFrame){
            w++;
        }
    }

    if(b+w==0){
        this.fiveGroupScore[i] = 7;
    }else if(b>0&&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;i<this.fiveGroupScore.length;i++){
    if(this.fiveGroupScore[i]>hScore){
        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]];
  1. 最优位置断点
bestPosition1.png
  1. 最优位置截图
bestPosition2.png

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. 开局后,隔一会儿(如1分钟),玩家未落子,出提示窗,确认是否继续玩

  2. 设置积分,奖励制度

这是我学习五子棋游戏开发的记录,后续还会写其他游戏开发,加油!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • “ 喜欢象棋,现在深圳创办了深圳好棋。接触揭棋后,就是真心喜欢这个游戏,感觉自己是下揭棋的人才,第一天学习揭棋,就...
    刀狼阅读 10,250评论 0 3
  • 近日,点名时间CEO张佑在其朋友圈公开透露,已经将公司交棒给91金融。而91金融董事长许泽玮也已证实确有此事。此次...
    王子健阅读 710评论 0 5
  • 海马爸爸家长能量场专业技能培训 微信群上的聊天记录 ————— 2017-05-28 ————— 张棂翰妈妈王健 ...
    企鹅妈妈阅读 426评论 0 0
  • 今天来到公司心总是静不下来,莫名的有些烦躁。有的时候就是这样,心情总不会那么一直平静的。不过或多或少也是因为遇到了...
    永久爱意杨阅读 225评论 0 0
  • 毽球是近年来兴起的一种群众性的运动项目。毽球是由单人的踢毽发展而来,两人便可开踢。 毽球运动之所以能迅速推开,是它...
    刘爱国阅读 281评论 0 2