最近学习使用了一款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. };

  这里的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像素,不用做其他任何事情,就达到了我想要的布局效果。当然,左右两边玩家的手牌我也用了同样的方式,只是用的是竖直的排列方式。

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

  

javascript开发HTML5游戏--斗地主(单机模式part2)的更多相关文章

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

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

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

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

  3. JAVASCRIPT开发HTML5游戏--斗地主(网络对战PART4)

    继之前用游戏引擎(青瓷引擎)做了斗地主单机版游戏之后,这里分享下使用socket.io来实现网络对战,代码可已放到github上,在此谈谈自己整个的开发思路吧. 客户端代码 服务端代码 (点击图片进入 ...

  4. 青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 10(排行榜界面&界面管理)

    继上一次介绍了<神奇的六边形>的完整游戏开发流程后(可点击这里查看),这次将为大家介绍另外一款魔性游戏<跳跃的方块>的完整开发流程. (点击图片可进入游戏体验) 因内容太多,为 ...

  5. JS开发HTML5游戏《神奇的六边形》(二)

    近期出现一款魔性的消除类HTML5游戏<神奇的六边形>,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏. (点击图片可进入游戏体验) 因内容 ...

  6. JS开发HTML5游戏《神奇的六边形》(一)

    近期出现一款魔性的消除类HTML5游戏<神奇的六边形>,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏. (点击图片可进入游戏体验) 因内容 ...

  7. JS开发HTML5游戏《神奇的六边形》(四)

    近期出现一款魔性的消除类HTML5游戏<神奇的六边形>,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏. (点击图片可进入游戏体验) 因内容 ...

  8. JS开发HTML5游戏《神奇的六边形》(三)

    近期出现一款魔性的消除类HTML5游戏<神奇的六边形>,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏. (点击图片可进入游戏体验) 因内容 ...

  9. HTML5外包注意事项-开发HTML5游戏的九大坑与解决方法剖析

    随着移动社区兴起,势必带动HTML5的革命.未来一两年内,HTML5移动游戏必将呈现大爆发趋势. 以下是整理的HTML5游戏研发.市场趋势以及渠道布局和技术解决方案的内容.希望大家能从本文中找到对HT ...

随机推荐

  1. soapUi在调用过程中日期参数

    中间加个T 2012-11-05T16:38:30 相关描述:

  2. ZooKeeper参数调优

    zookeeper的默认配置文件为zookeeper/conf/zoo_sample.cfg,需要将其修改为zoo.cfg.其中各配置项的含义,解释如下: 1.tickTime:Client-Serv ...

  3. 将kali linux装入U盘 制作随身携带的kali linux

    一 准备工作 USB3.0 U盘 不小于32G USB2.0的U盘安装速度要比3.0的慢一倍以上,运行也会有明显差别,所以建议使用3.0U盘.安装好之后差不多就得占用十几G,所以16G的太小了,尽量用 ...

  4. 《js高级程序设计》--第三章数据类型

    一.关键字 二.保留字 三.数据类型 (数据类型具有动态性)   1.Undefined 声明变量却未对其加以初始化(赋值) 2.Null null值表示一个空对象指针,而这也正是使用typeof操作 ...

  5. Centos7.2 安装Elasticsearch 6

    下载 elasticsearch.6.0.0.tar.gz 迁移文件到usr/local中 mv elasticsearch-.tar.gz /usr/local/ cd /usr/local tar ...

  6. DataTable转化成实体对象

    /// <summary> /// The data extension. /// </summary> public static class DataExtension { ...

  7. 常规css,js引入

    php // css,js用 $this->assign('MODULE_NAME',MODULE_NAME); $this->assign('ACTION_NAME',ACTION_NA ...

  8. MongoDB(课时27 消除重复数据)

    3.7.2 消除重复数据 在SQL中对于重复的数据可以使用"DISTINCT"消除,在MongoDB中依然支持.(distinct不同的) 范例:查询所有name的信息 本次的操作 ...

  9. 【转】xml节点解析成字符串的方法

    网址:http://blog.csdn.net/shanzhizi/article/details/8817532 ZC: 这是 libxml2的 之前汇总了一篇关于xml文档与字符串转换的文章,文章 ...

  10. Qt5.3.2(VS2010)_调试_查看变量值

    1.菜单栏 -->控件(W) --> 视图 2.在"Debug"状态下,选择一个变量(或者 按住左键 选择变量及其属性/方法)--> 右键 --> 添加表达 ...