H5 斗地主游戏后端教程(一)

2019/02/22

1. 前言

几个月的断断续续开发,终于也将H5斗地主游戏的前后端都基本完成了,也实现了电脑端和手机端的适应。总的来说,从这个项目中学到的挺多,不论是后台业务的开发还是前端功能的实现。从想法的最初萌生到功能一次次的累加和优化以至完成,都经历了精心的设计和尝试。

因此,特地开了这系列博客,希望能记录下设计的大致思路和逻辑。如果你感兴趣的话,那便是极好的。在第一节,我将会给大家介绍应用的模块分割,以及个体的设计(对应在编程则是 Java 实体类)。

2. 应用设计

我将后台应用主要分成了以下的三个模块:

算法模块

算法模块构成了游戏的规则判断的实现,有点类似 “ 裁判 ” 的角色,它主要提供的功能有:

  • 构造牌并分成三份牌,同时分配给三个玩家;
  • 判断用户打出牌的类型;
  • 比较两个玩家打出牌的牌,并比较它们的大小,以判断是否能管得住上家的牌。

业务模块

业务模块搭建和玩家客户端(前端)的通信桥梁,负责处理房间管理(创建房间、退出房间),叫牌、出牌以及游戏结束后计分逻辑的具体实现。

通知模块

在H5中,我们需要及时更新房间内的信息(例如某个玩家加入、游戏开始、玩家出牌等操作),就必须要使用到webSocket协议来支持。因此需要利用通过模块来通知前端这些事件的发生,让玩家的客户端能及时反应这些事件的发生。

3. 个体设计

接下来我们再来针对个体来讨论,并根据游戏中的个体创建所对应的POJO实体类。让我们想象这个游戏有哪些个体?

  • 房间:每个房间都是一个个体,一个房间所包含了三个玩家;
  • 玩家:三个玩家,每个玩家都是一个个体。每个玩家都包含了17张(地主为20张);
  • 牌:每张牌都是一个个体。

接下来我们根据这些个体,来创建对应的实体类。

房间

房间的属性并不复杂,主要的属性由以下几个。有个特殊的属性stepNum主要是用来判断当前是否是某个玩家的出牌回合,后面会做具体的讲解。

@Data
public class Room {
    
    private String id;                // 房间号

    private String password;          // 房间密码
    
    private boolean locked;           // 房间是否设置密码,true为设置
    
    private List<Player> playerList;  // 当前玩家列表
    
    private RoomStatusEnum status;    // 房间的状态(准备/开始)
    
    private int multiple;             // 房间底分
    
    private int stepNum;              // 每局走的步数,用来控制玩家的出牌回合
}

public enum RoomStatusEnum {
    PLAYING("游戏中"),
    PREPARING("准备中");
}

玩家

因为每个玩家都有各自的出牌顺序,所以需要为每个玩家分配一个id号,并可以通过id获得下家出牌的玩家。在该应用的设计中,会将Player玩家对象和User用户对象相关联,目的是将它们的属性区分。

@Data
public class Player {
    
    private Integer id;             // 玩家在当前房间的座位顺序
    
    private User user;              // 玩家的用户对象
    
    private List<Card> cards;       // 玩家当前手中的牌
    
    private boolean ready;          // 玩家是否准备
    
    private IdentityEnum identity;  // 当前局的身份(地主、农民)
}

public enum IdentityEnum {
    LANDLORD("地主"),
    FARMER("农民");
}

牌的设计稍微复杂一些, 如下面一张牌所示:

我们通过三个标识属性来区分每一张牌:

@Data
public class Card implements Comparable<Card> {
    
    private int id;                // 牌的数字ID,用于分牌操作
    
    private CardTypeEnum type;     // 牌的类型
    
    private CardNumberEnum number; // 牌的数值

    private CardGradeEnum grade;   // 牌的等级
    
}

cardType

对应着牌的花色,如黑桃、红桃、方块、梅花,以及特殊的大小王。枚举类为:

public enum CardTypeEnum {

    SPADE("黑桃"),
    HEART("红桃"),
    CLUB("梅花"),
    DIAMOND("方块"),

    SMALL_JOKER("小王"),
    BIG_JOKER("大王");
}

cardNumber

即牌的数字,从1 ~ 15;枚举类的定义为:

public enum CardNumberEnum {

    ONE(1),         
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5),
    SIX(6),
    SEVEN(7),
    EIGHT(8),
    NINE(9),
    TEN(10),

    JACK(11),         // J
    LADY(12),         // Q
    KING(13),         // K

    SMALL_JOKER(14),  // 小王
    BIG_JOKER(15);    // 大王
}

cardGrade

即牌的等级,也是从1 ~ 15;枚举类的定义为:

public enum CardGradeEnum {

    // 3 ~ K
    FIRST(1),      // 3
    SECOND(2),     // 4
    THIRD(3),      // 5
    FOURTH(4),     // 6
    FIFTH(5),      // 7
    SIXTH(6),      // 8
    SEVENTH(7),    // 9
    EIGHTH(8),     // 10
    NINTH(9),      // J
    TENTH(10),     // Q
    ELEVENTH(11),  // K

    // A 和 2
    TWELFTH(12),
    THIRTEENTH(13),

    // 大小王
    FOURTEENTH(14),
    FIFTEENTH(15);
}

看到这里你可能会纳闷,这个属性和cardNumber有什么区别。的确,这是一个比较特殊的标识属性,我们都知道,虽然从数学意义上来说,A23小;但是,在斗地主的规则中却比3大。

因此,cardNumber只是用来显示牌的数值;而cardGrade用来衡量牌的等级,可以用于比较牌的大小