zoukankan      html  css  js  c++  java
  • javascript开发HTML5游戏--斗地主(单机模式part2)

    最近学习使用了一款HTML5游戏引擎(青瓷引擎),并用它尝试做了一个斗地主的游戏,简单实现了单机对战和网络对战,代码可已放到github上,在此谈谈自己如何通过引擎来开发这款游戏的。

     客户端代码

       服务端代码

              (点击图片进入游戏体验)

    前文链接:

    javascript开发HTML5游戏--斗地主(单机模式part1)

    本文章为第二部分内容,主要包括发牌、抢地主流程。主要内容如下:

    1. 发牌
    2. 抢地主流程
    3. 确定地主
    4. 手牌布局问题

    一、发牌

      发牌,就是取3张底牌,然后3个玩家各发17张牌,之前我把一副牌的信息都放置在了Scripts/logic/clone/Card.js中,也就是qc.landlord.Card类。存放在这个类的属性data中,这是一个数组,数组中的对象有3个属性,如下:

    • icon : 牌图片文件名,用于显示这张牌;
    • val : 牌值,也就是大小,由于在斗地主规则中除大小王外,2最大,A其次,所以这里并不完全按照数值排大小,A是14,2是15,小王16,大王17。其它牌自己的数值就是大小;
    • type : 花色,虽然说在斗地主中没有花色大小问题,但是为了手牌美观,一般都会把同等大小的牌按照一定的顺序(黑桃-红心-草花-方块)来排列。

      javascript的数组有许多强大的方法,在发牌这块上算是很多都派上用场了,整个发牌流程的思路如下:

    1. 使用数组slice方法复制一副牌来发牌,保证原牌组不会变动;
    2. 使用数组splice方法结合随机数,抽取一张牌,这里利用splice做删除时会以一个数组形式返回被删除的几个元素,得到返回的对象后加入到对应玩家的手牌的数组中。这样就不用去做一个洗牌的代码,好比如买了一副新牌经常我们都要洗一下再玩,但是也可以不洗,三个人每人随机从中抽一张,直到17张为止,大概就是这个思路。
    3. 使用数组sort方法对每个玩家手牌排序,这里需要我们自己写一个数组元素的比较方法。因为按照上面的方法,每个玩家的手牌都是乱序的,我们要求是从大到小并且等值的按照花色排序,排序不仅是为了展示美观,后面AI分析牌也用于判断顺子、连对之类的牌型,这里给出牌比较的代码,如下:
     1 /**
     2  * 卡牌排序
     3  * @method cardSort
     4  * @param  {Object} a [description]
     5  * @param  {Object} b [description]
     6  * @return 1 : a < b ,-1 a : > b   [description]
     7  */
     8 GameRule.prototype.cardSort = function (a, b){
     9     var va = parseInt(a.val);
    10     var vb = parseInt(b.val);
    11     if(va === vb){
    12         return a.type > b.type ? 1 : -1;
    13     } else if(va > vb){
    14         return -1;
    15     } else {
    16         return 1;
    17     }
    18 };

      发牌的时候,利用定时器每0.2秒给每个玩家都发一张牌,共发17张,这样玩家就可以看到一个发牌的动画。左右边的AI玩家不需要显示牌,只需要显示背面,所以每次只需要在各自的手牌容器中加一个牌的图片就可以;但是玩家自己的牌要按顺序显示,所以每次取牌,都要根据大小判断位置再放进去。代码在Scripts/ui/PlayUI.js中,这里给主要的代码,如下:

     1 //发牌
     2 PlayUI.prototype.dealCards = function (){
     3     var self = this,
     4         cards = G.cardMgr.getNewCards();
     5     //抽三张底牌
     6     for (var i = 0; i < 3; i++) {
     7         G.hiddenCards.push(self.getOneCard(cards));
     8     }
     9     //总牌数
    10     var total = 17;
    11     var deal = function (){
    12         //左边电脑玩家发牌
    13         card = self.getOneCard(cards);
    14         G.leftPlayer.cardList.push(card);
    15         var c = self.game.add.clone(self.cardPrefab, self.leftPlayerArea.getScript('qc.engine.PlayerUI').cardContainer);
    16         c.visible = true;
    17         c.interactive = false;
    18         //右边电脑玩家发牌
    19         card = self.getOneCard(cards);
    20         G.rightPlayer.cardList.push(card);
    21         c = self.game.add.clone(self.cardPrefab, self.rightPlayerArea.getScript('qc.engine.PlayerUI').cardContainer);
    22         c.visible = true;
    23         c.interactive = false;
    24         //左边电脑玩家发牌
    25         //玩家的牌
    26         card = self.getOneCard(cards);
    27         G.ownPlayer.cardList.push(card);
    28         self.insertOneCard(card);
    29         if ( --total > 0) {
    30             self.dealTimer = self.game.timer.add(200, deal);
    31         } else {
    32             G.leftPlayer.cardList.sort(G.gameRule.cardSort);
    33             G.rightPlayer.cardList.sort(G.gameRule.cardSort);
    34             G.ownPlayer.cardList.sort(G.gameRule.cardSort);
    35             for (i = 0; i < G.currentCards.length; i++) {
    36                 G.currentCards[i].getScript('qc.engine.CardUI').isSelected = false;
    37             }
    38             //进入抢地主阶段
    39             self.robLandlord();
    40         }
    41     };
    42     deal();
    43 
    44 };

     二、抢地主流程

      1、流程介绍

      抢地主,就是玩家轮换叫分的过程,代码的流程如下:

    2、AI手牌评分

      这里实现的AI抢地主,先根据手牌对AI玩家进行手牌评分,如果评分大于上家的叫分,就叫分,否则不叫。整体思路是看了以下这篇文章来写的,包括后面的AI出牌之类都是从这边看的,文章链接:斗地主ai设计

         叫牌原则分析
           因为在斗地主中,火箭、炸弹、王和2可以认为是大牌,所以叫牌需要按照这些牌的多少来判断。下面是一个简单的原则,来自于上面这篇文章: 
           假定火箭为8分,炸~弹为6分,大王4分,小王3分,一个2为2分,则当分数 
           大于等于7分时叫3分; 
           大于等于5分时叫2分; 
           大于等于3分时叫1分; 
           小于三分不叫

      我在Scripts/logci/AILogic.js下创建了AILogic类,在创建对象时需要传入一个玩家对象,该类会对玩家手牌进行分析归类,这些在AI出牌中再详细阐述,这里我们先看看AI手牌评分的代码吧。如下:

     1 /**
     2  * 手牌评分,用于AI根据自己手牌来叫分
     3  * @method function
     4  * @return {[nmber]} 所评得分
     5  */
     6 AILogic.prototype.judgeScore = function() {
     7     var self = this,
     8         score = 0;
     9     score += self._bomb.length * 6;//有炸弹加六分
    10     if(self._kingBomb.length > 0 ){//王炸8分
    11         score += 8;
    12     } else {
    13         if(self.cards[0].val === 17){
    14             score += 4;
    15         } else if(self.cards[0].val === 16){
    16             score += 3;
    17         }
    18     }
    19     for (var i = 0; i < self.cards.length; i++) {
    20         if(self.cards[i].val === 15){
    21             score += 2;
    22         }
    23     }
    24     console.info(self.player.name + "手牌评分:" + score);
    25     if(score >= 7){
    26         return 3;
    27     } else if(score >= 5){
    28         return 2;
    29     } else if(score >= 3){
    30         return 1;
    31     } else {//4相当于不叫
    32         return 4;
    33     }
    34 };

      3、轮换抢地主

      继发牌完成之后,就进入到了抢地主阶段,发完牌后随机选取一个玩家开始叫分。由于进入单机模式便给每一个玩家添加了一个nextPlayer指向自己下一家,形成一个循环的引用,所以很容易找到自己下一家。如果是玩家则给玩家显示叫分按钮,AI则给出分数,主要代码如下:

     1 /**
     2  * 抢地主阶段
     3  * @method robLandlord
     4  */
     5 PlayUI.prototype.robLandlord = function (){
     6     var self = this;
     7     //随机获取从哪一家开始
     8     var fb = G.gameRule.random(1,3);
     9     var firstPlayer = fb === 1 ? G.ownPlayer : (fb == 2 ? G.rightPlayer : G.leftPlayer);
    10     self.provideScore(firstPlayer);
    11 };
    12 
    13 /**
    14  * 轮换叫分
    15  * @method robLandlord
    16  */
    17 PlayUI.prototype.provideScore = function(player){
    18     var self = this;
    19     if(player.isAI){//AI玩家随机出分
    20         self.scoreThree.visible = false;
    21         self.scoreTwo.visible = false;
    22         self.scoreOne.visible = false;
    23         self.scoreZero.visible = false;
    24         self.game.timer.add(1000, function (){
    25             var s = (new AILogic(player)).judgeScore();
    26             var area = player.nextPlayer.isAI ? window.landlordUI.rightCards : window.landlordUI.leftCards;
    27             if(s < 4 && s > self.currentScore){//小于3分
    28                 console.info(player.name + ":叫" + s);
    29                 self.currentScore = s;
    30                 self.scorePanel.text = s + '';
    31                 self.currentLandlord = player;
    32                 //根据下家是否是AI判断他的出牌区
    33                 for (var i = 0; i < area.children.length; i++) {//清空
    34                     area.children[i].destroy();
    35                 }
    36                 var mesg = self.game.add.clone(self.msgPrefab, area);
    37                 mesg.text = s + '分';
    38                 if(s === 3){//三分,得地主
    39                     self.setLandlord(player);
    40                     return;
    41                 }
    42             } else {
    43                 var mesg = self.game.add.clone(self.msgPrefab, area);
    44                 mesg.text = '不叫';
    45                 console.info(player.name + "没有叫分抢地主");
    46             }
    47             if(++self.round  === 3){//已经三次不再进行
    48                 if(self.currentLandlord){//有叫分的得地主
    49                     self.setLandlord(self.currentLandlord);
    50                 } else {//没有叫分,重新发牌
    51                     self.showRestartMesg();
    52                     self.startGame();
    53                 }
    54             } else {
    55                 self.provideScore(player.nextPlayer);
    56             }
    57         });
    58     } else {
    59         self.scoreZero.visible = true;
    60         self.scoreThree.visible = true;
    61         if(self.currentScore < 2)
    62             self.scoreTwo.visible = true;
    63         if(self.currentScore < 1)
    64             self.scoreOne.visible = true;
    65     }
    66 };
    67 
    68 /**
    69  * 玩家给分(抢地主)
    70  * @method function
    71  * @return {[type]} [description]
    72  */
    73 PlayUI.prototype.playerProvideScore = function(score){
    74     var self = this;
    75     if(score < 4){//小于3分
    76         self.currentScore = score;
    77         self.scorePanel.text = score + '';
    78         self.currentLandlord = G.ownPlayer;
    79         var mesg = self.game.add.clone(self.msgPrefab, window.landlordUI.ownCards);
    80         mesg.text = score + '分';
    81         if(score === 3){//三分,得地主
    82             self.setLandlord(G.ownPlayer);
    83             return;
    84         }
    85     } else {
    86         var mesg = self.game.add.clone(self.msgPrefab, window.landlordUI.ownCards);
    87         mesg.text = '不叫';
    88     }
    89     if(++self.round  === 3){//已经三次不再进行
    90         if(self.currentLandlord){//有叫分的得地主
    91             self.setLandlord(self.currentLandlord);
    92         } else {//没有叫分,重新发牌
    93             self.showRestartMesg();
    94             self.startGame();
    95         }
    96     } else {
    97         self.provideScore(G.ownPlayer.nextPlayer);
    98     }
    99 };
    View Code

       这里的playerProvideScore方法是玩家叫分,玩家有四个叫分按钮:1分、2分、3分、不叫,每个按钮事件都是调用这个方法,只是传入不同的分数。详细完整代码可以在github上查看,去玩玩这个游戏结合代码应该更好理解。

    三、确定地主

      完成抢地主后,确定地主的环节也是有不少事情要做,主要是以下几点:

    • 将底牌给地主,这里AI玩家只要修改牌数量,玩家的则需要将3张底牌插入对应位置,保证顺序
    • 显示出底牌
    • 界面上标明各个玩家身份
    • 保存本局的分数,也就是地主叫的分数
    • 让地主开始出牌

      代码如下:

     1 //设置地主
     2 PlayUI.prototype.setLandlord = function(player){
     3     var self = this;
     4     self.scorePanel.text = self.currentScore + '';
     5     self.scoreThree.visible = false;
     6     self.scoreTwo.visible = false;
     7     self.scoreOne.visible = false;
     8     self.scoreZero.visible = false;
     9     //显示底牌
    10     var oldHiddenCard = self.hiddenContainer.children;
    11     for (var i = 0; i < self.hiddenContainer.children.length; i++) {
    12         self.hiddenContainer.children[i].frame = G.hiddenCards[i].icon;
    13     }
    14     //self.startBtn.visible = false;
    15     //设置地主及农民信息
    16     G.ownPlayer.isLandlord = false;
    17     G.leftPlayer.isLandlord = false;
    18     G.rightPlayer.isLandlord = false;
    19     player.isLandlord = true;
    20     self.setAIStation(self.leftPlayerArea, G.leftPlayer.isLandlord);
    21     self.setAIStation(self.rightPlayerArea, G.rightPlayer.isLandlord);
    22     self.ownPlayerArea.getScript('qc.engine.PlayerUI').headPic.frame = G.ownPlayer.isLandlord ? 'landlord.png' : 'peasant.png';
    23     self.ownPlayerArea.getScript('qc.engine.PlayerUI').headPic.visible = true;
    24     //把底牌给地主
    25     player.cardList = player.cardList.concat(G.hiddenCards);
    26     player.cardList.sort(G.gameRule.cardSort);
    27     self.reDraw();
    28     if(!player.isAI){//不是AI需要重新渲染牌组
    29         for (i = 0; i < G.hiddenCards.length; i++) {
    30             self.insertOneCard(G.hiddenCards[i]);
    31         }
    32     }
    33     for (i = 0; i < G.currentCards.length; i++) {
    34         G.currentCards[i].getScript('qc.engine.CardUI').isSelected = false;
    35     }
    36     console.info('本轮地主是' + player.name);
    37     //由地主开始出牌
    38     window.landlordUI.cleanAllPlayArea();
    39     window.landlordUI.playCard(player);
    40 };

    四、手牌布局问题

      

      完成上面的步骤后,游戏也进入可以愉快打牌的阶段了,这里分享下我在用青瓷引擎做手牌布局显示的时候遇到的些问题。如上图,每个区域的手牌,左右两边玩家显示倒是问题不大,因为其只是显示相应数量的牌,都以背面显示,并不需要真正显示牌。主要还是在玩家手牌的问题,如果每张牌都我们去控制布局,会很繁琐,我一开始就走了一个错误的路线,用这样的方法:每张牌都放在手牌区域底下,每张牌设置不同的AnchoredX属性值,来达到每张牌错开的效果,但是会导致一些问题:

    • 每当玩家出牌后,需要对所有牌重新布局
    • 每当玩家出牌后,剩下牌难以居中显示
    • 要加入底牌时,由于要找在对应位置,难以实现

      后面才发现青瓷引擎为我们提供了一个很好的布局组件:表格布局组件(点击我看文档),很好帮我实现了这个功能。真是一开始没看全文档,浪费了不少时间。实现的话,在牌的容器节点加入TableLayout组件,属性设置如下图,然后就只要往如图cardList加子节点(卡牌图片),删除子节点(卡牌图片),所有牌整体居中显示,而且每张牌固定错开30像素,不用做其他任何事情,就达到了我想要的布局效果。当然,左右两边玩家的手牌我也用了同样的方式,只是用的是竖直的排列方式。

      确定完了地主,就可以进入玩牌了,我会在下一篇文章分享单机模式斗地主剩下的流程。

     

      

  • 相关阅读:
    python 练习洗牌
    python 生成二维码
    转载 HTTP协议
    分别使用python和java练习冒泡排序
    python-字符串
    [转]三层架构与MVC之间的区别
    [转]JAVA异常
    [转]JavaEE开发基础
    [转]JAVA对象容器
    数据库操作实例
  • 原文地址:https://www.cnblogs.com/yilyl/p/5097872.html
Copyright © 2011-2022 走看看