之前我们实现了叫地主、玩家和电脑自动出牌主要功能,但是还有个问题,出牌的时候,没有有效性检查和比较牌力大小。比如说,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9。

  • 问题1:出牌检查有效性,就是出牌类型判断,像单张、对子、顺子、炸弹等等类型;
  • 问题2:上家出牌后,下家再出牌的时候,要判断当前牌力是否大于上家的牌力;

那本篇我们主要解决以上2个问题。

卡牌信息类重构

首先,原先的卡牌类,已经实现了单张卡牌牌力的比较,但是有些复杂,我们先对这个比较逻辑进行优化。思路是卡牌的cardIndex就表示在此类型卡牌中的大小权重,所有,在初始化卡牌的过程中,对cardIndex进行特殊的处理:

cardIndex=(卡牌的原始索引+10)%13

比如:普通牌3--cardIndex=(3+10)%13=0,转换后排最小

普通牌J--cardIndex=(11+10)%13=8,转换后排第8张,如此,我们就可以轻松比较cardIndex,来判断单牌的实际大小了。

再看看优化后的代码,是不是比以前简洁很多?更主要的是,为了方便后面的复杂组合牌力的判断。

public class CardInfo : IComparable
{
public string cardName; //卡牌图片名
public CardTypes cardType; //牌的类型
public int cardIndex; //牌在所在类型的索引3-10,J,Q,K,A,2(0-12)
public bool isSelected; //是否选中 public CardInfo(string cardName)
{
this.cardName = cardName;
var splits = cardName.Split('_'); switch (splits[])
{
case "":
cardType = CardTypes.Hearts;
cardIndex = (int.Parse(splits[]) + ) % ;
break;
case "":
cardType = CardTypes.Spades;
cardIndex = (int.Parse(splits[]) + ) % ;
break;
case "":
cardType = CardTypes.Diamonds;
cardIndex = (int.Parse(splits[]) + ) % ;
break;
case "":
cardType = CardTypes.Clubs;
cardIndex = (int.Parse(splits[]) + ) % ;
break;
case "joker":
cardType = CardTypes.Joker;
cardIndex = (int.Parse(splits[]) + ) % ;
break;
default:
throw new Exception(string.Format("卡牌文件名{0}非法!", cardName));
}
} //卡牌大小比较
public int CompareTo(object obj)
{
CardInfo other = obj as CardInfo; if (other == null)
throw new Exception("比较对象类型非法!"); //如果当前是大小王
if (cardType == CardTypes.Joker)
{
//对方也是大小王
if (other.cardType == CardTypes.Joker)
{
return cardIndex.CompareTo(other.cardIndex);
}
//对方不是大小王
return ;
}
//如果是一般的牌
else
{
//对方是大小王
if (other.cardType == CardTypes.Joker)
{
return -;
}
//如果对方也是一般的牌
else
{
//计算牌力
if (cardIndex == other.cardIndex)
{
return -cardType.CompareTo(other.cardType);
} return cardIndex.CompareTo(other.cardIndex);
}
}
} }

出牌类型基类

接下来,那怎么判断出牌有效性和出牌的牌力大小判断呢?

我们这样想,每次出牌都是一组牌堆,那我们首先要判断这一组牌堆的类型,比如3带1,单张、炸弹等等;

其次,确定牌堆类型后,我们需要判断这个牌堆是否是合法的,其实就遍历验证是否符合上述的牌堆类型,如果满足,就认为合法,如果所有类型都不满足,则出牌无效,不允许出牌;

再者,怎么判断下家出牌的牌力要大于上家呢,这里我们还得有一个方法,判断相同类型的2个牌堆,牌堆1是否比牌堆2牌力大;

最后,为了实现电脑出牌的AI,还得有自动查找1个跟上家出牌类型一样的牌堆,而且比上家的牌堆大,如果有这种牌,则可以压住上家,否则自动过牌;

有了整体思路,我们这样设计,先定义一个虚基类--出牌类型基类,这里定义了所有需要子类牌堆类型实现的方法

    /// <summary>
/// 出牌类型基类
/// </summary>
public abstract class FollowCardsBase
{
/// <summary>
/// 验证类型
/// </summary>
/// <returns></returns>
public abstract bool Validate(List<CardInfo> cardInfos);
/// <summary>
/// 找到最小满足的牌组
/// </summary>
/// <returns></returns>
public abstract List<CardInfo> FindBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos);
/// <summary>
/// 判断是否牌大过要比较的牌组
/// </summary>
/// <param name="handCardInfos"></param>
/// <param name="cardInfos"></param>
/// <returns></returns>
public abstract bool IsBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos);
}
}

Validate方法:子类实现验证出牌是否满足此类型;

IsBigger方法:给定2个出牌的牌堆,判断此类型牌堆1是否满足牌力大于牌堆2;

FindBigger方法:在给定的手牌中,找出符合类型中满足牌力大于给定牌堆的组合;

这样,我们定义好基类,再利用子类去实现各自的方法,比如对子牌类型的子类,我们判断如果是对子,出牌的时候Validate判断是否也是对子,如果出牌是对子,而且IsBigger

,就允许出牌;当然AI出牌的时候,通过FindBigger,找到满足对子类型,且比给定的对子牌力大的牌组,进行后续出牌操作。

定义各个类型牌型

因为斗地主涉及的牌型有很多,我们可以简单归纳一下。

我这里把单张和顺子作为一种牌组类型来实现,因为考虑顺子其实就是一种单张的特殊情景,只是约束条件是大于等于连续5张的单张牌。所以对子和连对、3带1或3带1的飞机等等,就都可以归为一类,个人感觉实现会简单些。

这里还是以单张和顺子举例:

        /// <summary>
/// 验证类型
/// </summary>
/// <returns></returns>
public override bool Validate(List<CardInfo> cardInfos)
{
cardInfos.Sort(); if (cardInfos.Count == ) //单张
{
return true;
}
else if (cardInfos.Count >= ) //顺子
{
//如果最大的牌是王或者2,则不是顺子
if (cardInfos.Last().cardType == CardTypes.Joker || cardInfos.Last().cardIndex == )
return false; for (int i = ; i < cardInfos.Count - ; i++)
{
if (cardInfos[i].cardIndex + != cardInfos[i + ].cardIndex)
return false;
}
return true;
}
return false;
}

按照上一节所述,我们牌组类型子类,首先需要实现Validate方法,来验证是否属于单张或顺子。

  • 如果是一张牌,毫无疑问,是单张
  • 如果是大于等于5张牌,最大的牌不是王或者2,而且是连续的牌,则是顺子
  • 其他情况肯定不是单张或顺子
        /// <summary>
/// 判断是否牌大过要比较的牌组
/// </summary>
/// <param name="handCardInfos"></param>
/// <param name="cardInfos"></param>
/// <returns></returns>
public override bool IsBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos)
{
cardInfos.Sort();
handCardInfos.Sort(); //牌数一样且最小牌比要比较的牌组的最小牌大
if (handCardInfos.Count == cardInfos.Count && Validate(handCardInfos) && Validate(cardInfos))
{
if (handCardInfos[].CompareTo(cardInfos[]) > )
return true;
}
return false;
}

接着,怎么判断2组牌组的牌力大小呢?

  • 第一步,将2牌组按照从小到大排序
  • 判断2牌组的牌数是否一样
  • 判断2牌组的类型是否都是单张或顺子
  • 如果满足以上2个条件,再判断2牌组的第一张大小
  • 如果牌组1的第一张大于牌组2的第一张,则可以认为牌组1牌力大于牌组2
  • 反之也成立
  • 否则,如果相等,则牌力一样
        /// <summary>
/// 找到最小满足的牌组
/// </summary>
/// <returns></returns>
public override List<CardInfo> FindBigger(List<CardInfo> handCardInfos, List<CardInfo> cardInfos)
{
cardInfos.Sort();
handCardInfos.Sort(); if (cardInfos.Count == ) //单张
{
var cardInfo = handCardInfos.FirstOrDefault(s => s.CompareTo(cardInfos[]) > && s.cardIndex != cardInfos[].cardIndex);
if (cardInfo != null)
{
var result = new List<CardInfo>();
result.Add(cardInfo);
return result;
}
return null;
} else if (cardInfos.Count >= ) //顺子
{
var count = handCardInfos.Count - cardInfos.Count; if (count >= )
{
//手牌比牌组多count,则有count + 1可能满足牌组
for (int i = ; i < count + ; i++)
{
var mayBiggerCardInfos = handCardInfos.Skip(i).Take(cardInfos.Count).ToList();
//是顺子,且最小的牌比要比较的牌组最小牌要大
if (Validate(mayBiggerCardInfos) && mayBiggerCardInfos[].CompareTo(cardInfos[]) > && mayBiggerCardInfos[].cardIndex != cardInfos[].cardIndex) {
return mayBiggerCardInfos;
}
}
return null;
}
return null;
}
return null;
}

再来看,我们怎么根据给定的单张或顺子,在手牌中找到对应牌力大于上家的牌组;

这里提供了一个简单的实现方式,找到最小满足的牌组,当然,如果要实现智能的电脑出牌的AI,或者智能提示出牌,肯定要复杂很多,就不在探讨范围内了。

  • 第一步,将2牌组按照从小到大排序
  • 如果上家牌是单张,那简单,从手牌中找到第1张满足牌力大于上家牌的单张即可;
  • 如果上家牌是顺子,判断手牌比上家牌的牌数只差,比如手牌17张,上家牌是5张的顺子,那就是17-5=12,理论上有12+1最多可能满足的牌组组合;
  • 那从第一张手牌开始,遍历12+1次,每次取当前手牌后的5张牌,判断是否比上家牌牌力大,如果不满足,则继续下轮遍历;
  • 如果找到,则返回该牌组,可以认为这牌组牌力大于上家牌,且类型为单张或顺子,牌数跟上家牌一样;
  • 如果遍历完还没找到,认为手牌中没有大于上家牌的牌组,游戏玩家可以跳过次回合;

至此,我们的单张和顺子类型的出牌检验逻辑,大体上就完成了。接下来,我们只需要稍微修改下之前的出牌逻辑~

出牌逻辑调整

这里需要调整2处,一方面,在玩家出牌时,要增加判断,如果玩家选择的牌堆,牌力不够,需要提示玩家,很简单,不在复述;

再来看电脑出牌的逻辑,我们在PlayOthre类中,增加以下这段代码:

if (CardManager._instance.cardManagerState == CardManagerStates.Playing)
{
if (Input.GetKeyDown(KeyCode.Q)) //出牌
{
var singleCards = new SingleCards();
var cardInfos = singleCards.FindBigger(this.cardInfos, CardManager._instance.currentCardInfos);
if (cardInfos != null)
{
cardInfos.ForEach(s => s.isSelected = true);
ForFollow();
}
else
{
NotFollow();
}
}
}

电脑AI出牌的时候,通过FindBigger,去匹配手牌中有没有满足牌力大于上家牌的牌组类型,如果有,将返回的牌组出出去,如果没有,则自动过牌。

那现在玩家出牌、电脑AI出牌就可以正常处理了,我们来看一下效果:

写在最后

我们的斗地主游戏开发有段时间了,本文就作为阶段性的结束篇~当然,远算不上是一个成品游戏,我只是简单的实现了部分功能,有些代码现在看来还是偷工减料。要完成一个成品游戏,需要大量的精力和时间,条件也不允许,以后有机会的话,可能会对这个项目完善和优化。

这是我第一次写一个系列的东西,写得不好请大家多多包涵~推出这个系列,我的主旨是想和大家分享下Unity3D开发一个斗地主游戏的整体架构和设计模式,虽然是个半成品,但是有些代码我是真的用心去写的,尽量的把我实现的思路和对游戏的理解展现给大家。就像一千个人眼中有一千个哈姆雷特,由于我们对游戏理解的不同,同样的斗地主游戏,不同的开发者,实现的方法可能就是不同的。我能做的,我希望做的,就是把个人的思想剖析给你们,如果你们能理解,或者对你们有所帮助,也是我的幸事。

最后,谢谢各位的一路陪伴。也欢迎大家关注,我们一同学习,共同进步,以后会坚持出新的系列!

资源

项目源码

Unity3D手机斗地主游戏开发实战(04)_出牌判断大小的更多相关文章

  1. Unity3D手机斗地主游戏开发实战(04)_出牌判断大小(已完结)

    之前我们实现了叫地主.玩家和电脑自动出牌主要功能,但是还有个问题,出牌的时候,没有有效性检查和比较牌力大小.比如说,出牌3,4,5,目前是可以出牌的,然后下家可以出任何牌如3,6,9. 问题1:出牌检 ...

  2. Unity3D手机斗地主游戏开发实战(01)_发牌功能实现

    园子荒废多年,闲来无事,用Unity3D来尝试做一个简单的小游戏,一方面是对最近研究的Unity3D有点总结,一方面跟广大的园友相互学习和提高.话不多说,进入正题~ 一.创建项目 1.创建Unity2 ...

  3. Unity3D手机斗地主游戏开发实战(03)_地主牌显示和出牌逻辑(不定期更新中~~~)

    Hi,之前有同学说要我把源码发出来,那我就把半成品源码的链接放在每篇文件的最后,有兴趣的话可以查阅参考,有问题可以跟我私信,也可以关注我的个人公众号,互相交流嘛.当然,代码也是在不断的持续改进中~ 上 ...

  4. Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现(不定期更新中~~~)

    目录 Unity3D手机斗地主游戏开发实战(01)_发牌功能实现 Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现 一.大体思路 前面我们实现了点击开始游戏按钮,系统依次给玩家发牌的逻辑和 ...

  5. Unity3D手机斗地主游戏开发实战(02)_叫地主功能实现

    大体思路 前面我们实现了点击开始游戏按钮,系统依次给玩家发牌的逻辑和动画,并展示当前的手牌.这期我们继续实现接下来的功能--叫地主. 1.首先这两天,学习了DOTween,这是一个强大的Unity动画 ...

  6. 【转】 [Unity3D]手机3D游戏开发:场景切换与数据存储(PlayerPrefs 类的介绍与使用)

    http://blog.csdn.net/pleasecallmewhy/article/details/8543181 在Unity中的数据存储和iOS中字典的存储基本相同,是通过关键字实现数据存储 ...

  7. (转)火溶CEO王伟峰:Unity3D手机网游开发

    今天看到这篇文章,感觉很不错,尤其是那句“Unity3D的坑我觉得最严重的坑就是没有懂3D的程序员,把Unity当成Office用”. 转自http://blog.csdn.net/wwwang891 ...

  8. 手机3D游戏开发:自定义Joystick的相关设置和脚本源码

    Joystick在手游开发中非常常见,也就是在手机屏幕上的虚拟操纵杆,但是Unity3D自带的Joystick贴图比较原始,所以经常有使用自定义贴图的需求. 下面就来演示一下如何实现自定义JoySti ...

  9. cocos2d-x游戏开发实战原创视频讲座系列1之2048游戏开发

     cocos2d-x游戏开发实战原创视频讲座系列1之2048游戏开发 的产生 视持续更新中.... 视频存放地址例如以下:http://ipd.pps.tv/user/1058663622     ...

随机推荐

  1. silverlight属性改变事件通知

    工作中遇到silverlight本身没有提供的某些属性改变事件,但又需要在属性改变时得到通知,Google搬运stack overflow,原地址 /// Listen for change of t ...

  2. sql的转义字符单引号

    在SQL中,我们都知道单引号 ' 表示字符串的开始和结束符号,如: select * from students where name = '小明'; 但如果字符串里面有单引号时,应该怎么查询呢? 这 ...

  3. 简介 - MongoDB

    1- NoSQL简介 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL": NoSQL是指非关系型的数据库,有时也称作Not Only SQL的缩写, ...

  4. Event Loop浅谈

    event loop 即事件循环.最初了解到js的event loop机制是通过自己对js中异步.同步的疑惑.今天聊一聊自己的理解,希望和大家一起学习. 首先,让我们看一个经典的setTimeOut的 ...

  5. 一看看懂Protocol Buffer(协议篇)

    前言 由于笔者业团队的业务对即时通讯服务有很大的依赖,春节结束后的第一天,红包没到,产品同学先到了,产品同学和我说要做一款IM,看到需求文档后和设计图后笔者大吃一斤 这不就是一个翻版的web qq吗? ...

  6. Maven 概要介绍

    Maven 简介 Apache Maven 是一套软件工程管理和整合工具.基于工程对象模型(POM)的概念,通过一个中央信息管理模块,Maven 能够管理项目的构建.报告和文档. Maven 工程结构 ...

  7. Linux下安装、启动、停止mongodb

    1.下载完安装包,并解压 tgz(以下演示的是 64 位 Linux上的安装) curl .tgz # 下载 tar .tgz # 解压 mv mongodb/ /usr/local/mongodb ...

  8. Java核心技术及面试指南 集合部分总的面试题归纳以及答案

    3.6.1ArrayList和LinkedList有什么差别?在哪种场景里应当用ArrayList(或LinkedList)? 大家如果学过数据结构,这个问题不难回答:前者是基于数组,数组比较擅长索引 ...

  9. TypeError: Cannot red property 'style' of null 错误解决

    错误信息如下: JSP代码如下: <c:if test ="${not empty excelErrors}"> <div id="excelError ...

  10. java.sql.SQLException: The SQL statement must not be null or empty.这个错误

    今天发现了这个错误 java.sql.SQLException: The SQL statement must not be null or empty. 并且看了些网页:综合说下这个错误. 一般都是 ...