不到一个月独自一人开发斗地主游戏(h5 + 安卓 + 苹果)

开篇感言

不枉我深入学习基础知识,算法与数据结构,编译原理,java并发编程,这些东西都有用得到的时候。

再极端的时间内学习了cocos creator引擎,并学以致用,有极大的增加了自己的自信心。

在公司总是拧螺丝,一个人开发一个完成的作品,我又觉得自己彷佛回到了色彩斑斓的世界中。

前端代码地址:https://github.com/neroHua/neroGameClient
后端代码地址:https://github.com/neroHua/neroGameServer

背景

本人使用java工作已经7年之多了,js和ts基本懂一些。
打算开发一款逮狗腿子的棋牌游戏(合同学们玩),先仿制一款斗地主游戏,试一下。

涉及到的知识点技能

编码功底:

  1. 算法和数据结构
  2. 设计模式(策略,责任链)
  3. 敏捷开发。

游戏引擎:
1.Cocos creator 2.0.4

网络通信:
1. http
2. websocket

编程语言:
1.java (后端)
2.typeScript(前端)

后端:
1.spring,springMVC,spring boot, mybatis
2.flyway,jwt,h2,junit

前端:
h5

架构设计及其思路

  1. 服务器整体为单机结构(以后有需要,再扩容好了),提供 h5,安卓,苹果 三种客户端接入服务器,且前端由Cocos 引擎处理,直接打包成三个端 。
  2. 客户端和服务器端的通信协议选择为http,和 websocket (三端都支持的通信方式)。用户点击等等事件由客户端发送http请求。通过websocket,服务器向客户端推送消息。服务器端不接收用户通过websocket推送到服务器的消息。只用用户进入房间以后,才会开启websocket通信。
  3. 游戏的主要模型/功能设计。用户,房间,游戏,游戏控制器,游戏回合。房间是核心: 用户再房间里面玩游戏。每一个房间都有一个游戏控制器用以控制游戏的进程。用户有手牌。游戏控制器,通过游戏回合控制每回合的进行。

详细设计

1 单张卡牌设计

为了便于,排序,比较,等操作,设计如下:

这里的code呢,等于卡牌前端的图片名称(不过我这里前端使用的图集)。
这里的value呢,代表了单张卡牌的权值大小,可用于比较牌面的大小。
这里的message呢,代表了卡牌的中文名称。
这里不需要花色,所以没有设计花色字段

package com.nero.hua.enumeration;

import lombok.Getter;

@Getter
public enum CardEnumeration {

    CARD_103("card_103", 3, "方块3"),
    CARD_104("card_104", 4, "方块4"),
    CARD_105("card_105", 5, "方块5"),
    CARD_106("card_106", 6, "方块6"),
    CARD_107("card_107", 7, "方块7"),
    CARD_108("card_108", 8, "方块8"),
    CARD_109("card_109", 9, "方块9"),
    CARD_110("card_110", 10, "方块10"),
    CARD_111("card_111", 11, "方块J"),
    CARD_112("card_112", 12, "方块Q"),
    CARD_113("card_113", 13, "方块K"),
    CARD_114("card_114", 14, "方块1"),
    CARD_115("card_115", 15, "方块2"),
    CARD_203("card_203", 3, "梅花3"),
    CARD_204("card_204", 4, "梅花4"),
    CARD_205("card_205", 5, "梅花5"),
    CARD_206("card_206", 6, "梅花6"),
    CARD_207("card_207", 7, "梅花7"),
    CARD_208("card_208", 8, "梅花8"),
    CARD_209("card_209", 9, "梅花9"),
    CARD_210("card_210", 10, "梅花10"),
    CARD_211("card_211", 11, "梅花J"),
    CARD_212("card_212", 12, "梅花Q"),
    CARD_213("card_213", 13, "梅花K"),
    CARD_214("card_214", 14, "梅花1"),
    CARD_215("card_215", 15, "梅花2"),
    CARD_303("card_303", 3, "红桃3"),
    CARD_304("card_304", 4, "红桃4"),
    CARD_305("card_305", 5, "红桃5"),
    CARD_306("card_306", 6, "红桃6"),
    CARD_307("card_307", 7, "红桃7"),
    CARD_308("card_308", 8, "红桃8"),
    CARD_309("card_309", 9, "红桃9"),
    CARD_310("card_310", 10, "红桃10"),
    CARD_311("card_311", 11, "红桃J"),
    CARD_312("card_312", 12, "红桃Q"),
    CARD_313("card_313", 13, "红桃K"),
    CARD_314("card_314", 14, "红桃1"),
    CARD_315("card_315", 15, "红桃2"),
    CARD_403("card_403", 3, "黑桃3"),
    CARD_404("card_404", 4, "黑桃4"),
    CARD_405("card_405", 5, "黑桃5"),
    CARD_406("card_406", 6, "黑桃6"),
    CARD_407("card_407", 7, "黑桃7"),
    CARD_408("card_408", 8, "黑桃8"),
    CARD_409("card_409", 9, "黑桃9"),
    CARD_410("card_410", 10, "黑桃10"),
    CARD_411("card_411", 11, "黑桃J"),
    CARD_412("card_412", 12, "黑桃Q"),
    CARD_413("card_413", 13, "黑桃K"),
    CARD_414("card_414", 14, "黑桃1"),
    CARD_415("card_415", 15, "黑桃2"),
    CARD_500("card_500", 0, "背面"),
    CARD_508("card_508", 8, "狗子"),
    CARD_516("card_516", 16, "小王"),
    CARD_517("card_517", 17, "大王");

    private String code;
    private int value;
    private String message;

    CardEnumeration(String code, int value, String message) {
        this.code = code;
        this.value = value;
        this.message = message;
    }
}

2 打牌时,卡牌组合的牌型设计

为了便于,排序,比较,等操作,设计如下:
权值大的牌型,比权值小的牌型大。
权值相同的牌型,需要牌型一样(牌型为同一个枚举)方可比较

这里的code呢,一种编码。
这里的value呢,代表的组合的权值。
这里的message呢,代表了卡牌的中文名称。
逮狗腿子,有些牌型不支持,还需要更多的牌型,后面再弄了,不过思路是已经想好了。

package com.nero.hua.enumeration;

import lombok.Getter;

@Getter
public enum PlayCardTypeEnumeration {

    SINGLE("single", 0, "单牌"),
    STRAIGHT("straight", 0, "顺子"),

    PAIR("pair", 0, "对子"),
    PAIR_STRAIGHT("pairStraight", 0, "连对"),

    TRIPLE("triple", 0, "三不带"),
    TRIPLE_SINGLE("tripleSingle", 0, "三带一"),
    TRIPLE_PAIR("triplePAIR", 0, "三带一对"),

    AIRPLANE("airplane", 0, "飞机不带"),
    AIRPLANE_SINGLE("airplaneSingle", 0, "飞机带单"),
    AIRPLANE_PAIR("airplanePair", 0, "飞机带对"),

    FOUR_SINGLE("fourSingle", 0, "4带2"),
    FOUR_PAIR("fourPair", 0, "4带2对"),

    BOMB("bomb", 1, "炸弹"),
    BOMB_KING("bombKing", 2, "王炸");

    private String code;
    private int value;
    private String message;

    PlayCardTypeEnumeration(String code, int value, String message) {
        this.code = code;
        this.value = value;
        this.message = message;
    }
}

3 一种通用的牌型及其比较算法

1.为了便于比较牌型,先把牌按照一定格式格式化, 比如下面的。(对于有变种的牌型可以让客户端选择)

    /**
     * 客户端需要根据牌型把本字段格式化并排序降低服务器计算压力
     *
     * 比如
     *  顺子:7, 6, 5, 4, 3
     *  对子:5,5, 4, 4, 3, 3
     *  三带一: 3, 3, 3, 2
     *  三带对: 3, 3, 3, 2, 2
     *  飞机带单: 4, 4, 4, 3, 3, 3, 2, 1
     *  飞机带对: 4, 4, 4, 3, 3, 3, 2, 2, 1, 1
     *  王炸:大王, 小王
     *
     */

2.对于格式化后的牌型,比较的办法如下:

    public static boolean currentPlayCardListBetterThanLastPlayCardList(UserPlayCardTurnMO lastUserPlayCardTurnMO, List<CardEnumeration> playCardList, PlayCardTypeEnumeration playCardTypeEnumeration) {
        if (null == lastUserPlayCardTurnMO) {
            return Boolean.TRUE;
        }

        PlayCardTypeEnumeration lastPlayCardTypeEnumeration = lastUserPlayCardTurnMO.getPlayCardTypeEnumeration();
        if (playCardTypeEnumeration.getValue() < lastPlayCardTypeEnumeration.getValue()) {
            return Boolean.FALSE;
        }
        else if (playCardTypeEnumeration.getValue() > lastPlayCardTypeEnumeration.getValue()) {
            return Boolean.TRUE;
        }

        if (lastPlayCardTypeEnumeration != playCardTypeEnumeration) {
            return Boolean.FALSE;
        }

        List<CardEnumeration> lastPlayCardList = lastUserPlayCardTurnMO.getCardList();
        return lastPlayCardList.size() == playCardList.size()
                && lastPlayCardList.get(0).getValue() < playCardList.get(0).getValue();
    }
  1. 牌型的格式化算法
    整体式为: 把牌按照相同单牌权值的数量的大小进行分割成小块,再根据数量的大小把小块合并。把原来的大块按照单牌权值的大小进行排序,可以方便上诉操作
    public static int formatCardList(List<CardEnumeration> playCardList) {
        Map<Integer, Integer> playCardValueCountMap = getPlayCardValueCountMap(playCardList);

        quickSortOneCardList(0, playCardList.size() - 1, playCardList);

        Map<Integer, List<CardEnumeration>> playCardCountListMap = getPlayCardCountListMap(playCardList, playCardValueCountMap);

        List<Integer> countList = getSortedPlayCardCountList(playCardCountListMap);

        for (int i = 0, k = 0; i < countList.size(); i++) {
            List<CardEnumeration> cardEnumerationList = playCardCountListMap.get(countList.get(i));
            for (int j = 0; j < cardEnumerationList.size(); j++) {
                playCardList.set(k, cardEnumerationList.get(j));
                k++;
            }
        }

        return countList.get(0);
    }

计算单牌权值及其数量Map

    private static Map<Integer, Integer> getPlayCardValueCountMap(List<CardEnumeration> playCardList) {
        Map<Integer, Integer> playCardValueCountMap = new HashMap<>();

        for (int i = 0; i < playCardList.size(); i++) {
            Integer count = playCardValueCountMap.get(playCardList.get(i).getValue());
            if (null == count) {
                playCardValueCountMap.put(playCardList.get(i).getValue(), 1);
            }
            else {
                playCardValueCountMap.put(playCardList.get(i).getValue(), count + 1);
            }
        }

        return playCardValueCountMap;
    }

对牌按照单牌权值递减的顺序进行排序(这里使用快排)

    public static void quickSortOneCardList(int start, int end, List<CardEnumeration> cardList) {
        if (start >= end) {
            return;
        }

        CardEnumeration keyCard = cardList.get(start);
        int i = start;
        int j = end;
        while (i != j) {
            if (cardList.get(i).getValue() < keyCard.getValue()) {
                CardEnumeration temp = cardList.get(j);
                cardList.set(j, cardList.get(i));
                cardList.set(i, temp);
                j--;
            }
            else {
                i++;
            }
        }

        int middle = cardList.get(i).getValue() >= keyCard.getValue() ? i : i - 1;
        if (middle >= start && middle <= end) {
            CardEnumeration temp = cardList.get(middle);
            cardList.set(middle, cardList.get(start));
            cardList.set(start, temp);
        }

        quickSortOneCardList(start, middle - 1, cardList);
        quickSortOneCardList(middle + 1, end, cardList);
    }

把已经排序好的牌,按照相同单牌权值的数量,平滑分割成多个小数组

    private static Map<Integer, List<CardEnumeration>> getPlayCardCountListMap(List<CardEnumeration> sortedPlayCardList, Map<Integer, Integer> playCardValueCountMap) {
        Map<Integer, List<CardEnumeration>> playCardCountListMap = new HashMap<>();
        for (Integer i : playCardValueCountMap.keySet()) {
            playCardCountListMap.put(playCardValueCountMap.get(i), new LinkedList<>());
        }

        for (int i = 0; i < sortedPlayCardList.size(); i++) {
            CardEnumeration cardEnumeration = sortedPlayCardList.get(i);
            Integer count = playCardValueCountMap.get(cardEnumeration.getValue());
            List<CardEnumeration> cardEnumerationList = playCardCountListMap.get(count);
            cardEnumerationList.add(cardEnumeration);
        }
        return playCardCountListMap;
    }

把相同单牌权值的数量进行排序(由大到小)

    private static List<Integer> getSortedPlayCardCountList(Map<Integer, List<CardEnumeration>> playCardCountListMap) {
        List<Integer> countList = new ArrayList<>();
        for (Integer i : playCardCountListMap.keySet()) {
            countList.add(i);
        }

        selectionSort(countList);
        return countList;
    }
    public static void selectionSort(List<Integer> countList) {
        if (1 == countList.size()) {
            return;
        }

        for (int i = 0; i < countList.size() - 1; i++) {
            Integer current = countList.get(i);
            for (int j = i + 1; j < countList.size(); j++) {
                Integer tobeCompared = countList.get(j);
                if (tobeCompared > current) {
                    countList.set(i, tobeCompared);
                    countList.set(j, current);
                    current = tobeCompared;
                }
            }
        }
    }

把按照相同单牌权值数量分割的碎片,根据数量的大小拼接起来

        for (int i = 0, k = 0; i < countList.size(); i++) {
            List<CardEnumeration> cardEnumerationList = playCardCountListMap.get(countList.get(i));
            for (int j = 0; j < cardEnumerationList.size(); j++) {
                playCardList.set(k, cardEnumerationList.get(j));
                k++;
            }
        }
  1. 对格式化的牌进行牌型的识别
    可以使用责任链模式,对于变种的可以可以处理。(playCardTypeValidateList 可以通过spring或者静态代码初始化进去),这种方式比较面向对象,看着更加优雅。不过考虑牌型的概率分布我使用了另外一种方式。
        for (PlayCardTypeValidate playCardTypeValidate : playCardTypeValidateList) {
            if (playCardTypeValidate.match(formattedPlayCardList)) {
                playCardTypeMap.put(playCardTypeValidate.getPlayCardTypeEnumeration(), formattedPlayCardList);
                break;
            }
        }

对于格式化好的三带二可以使用下面的代码识别。

package com.nero.hua.validate.impl;

import com.nero.hua.enumeration.CardEnumeration;
import com.nero.hua.enumeration.PlayCardTypeEnumeration;
import com.nero.hua.validate.PlayCardTypeValidate;

import java.util.List;

public class AirplanePairValidate implements PlayCardTypeValidate {

    private static final int MIN_COUNT = 10;
    private static final int GROUP_COUNT = 5;
    private static final int TRIPLE_COUNT = 3;

    @Override
    public PlayCardTypeEnumeration getPlayCardTypeEnumeration() {
        return PlayCardTypeEnumeration.AIRPLANE_PAIR;
    }

    @Override
    public boolean match(List<CardEnumeration> cardEnumerationList) {
        if (cardEnumerationList.size() < MIN_COUNT) {
            return Boolean.FALSE;
        }

        if (0 != cardEnumerationList.size() % GROUP_COUNT) {
            return Boolean.FALSE;
        }

        for (int i = 0; i < cardEnumerationList.size() / GROUP_COUNT; i += 3) {
            if (cardEnumerationList.get(i).getValue() != cardEnumerationList.get(i + 1).getValue()
                || cardEnumerationList.get(i + 1).getValue() != cardEnumerationList.get(i + 2).getValue()) {
                return Boolean.FALSE;
            }
        }

        for (int i = 0; i < cardEnumerationList.size() / GROUP_COUNT; i += 3) {
            if (cardEnumerationList.get(i).getValue() - 1 != cardEnumerationList.get(i + 3).getValue()) {
                return Boolean.FALSE;
            }
        }

        if (cardEnumerationList.get(0).getValue() >= CardEnumeration.CARD_415.getValue()) {
            return Boolean.FALSE;
        }

        int lastTripleStartIndex = this.calculateLastTripleStartIndex(cardEnumerationList.size());
        for (int i = lastTripleStartIndex + 3; i < cardEnumerationList.size(); i += 2) {
            if (cardEnumerationList.get(i).getValue() != cardEnumerationList.get(i + 1).getValue()) {
                return Boolean.FALSE;
            }
        }

        return Boolean.TRUE;
    }

    private int calculateLastTripleStartIndex(int size) {
        return (size / GROUP_COUNT - 1) * TRIPLE_COUNT;
    }

}

房间的设计

房间中由用户,游戏控制器。

package com.nero.hua.model.room;

import com.nero.hua.enumeration.CardEnumeration;
import com.nero.hua.enumeration.PlayCardTypeEnumeration;
import com.nero.hua.enumeration.RoomEnumeration;
import com.nero.hua.exception.RoomException;
import com.nero.hua.game.manager.GameManager;
import com.nero.hua.model.user.GameUserMO;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

@Setter
@Getter
public class RoomMO {

    private Long roomId;

    private GameManager gameManager;

    private List<GameUserMO> gameUserMOList = new ArrayList<>();

    public void joinUser(String userId) {
        if (gameUserMOList.size() > this.gameManager.getMaxUserCount()) {
            throw new RoomException(RoomEnumeration.ROOM_NOT_FOUND);
        }

        GameUserMO gameUserMO = new GameUserMO();
        gameUserMO.setUserId(userId);
        gameUserMO.setPrepared(Boolean.TRUE);
        gameUserMOList.add(gameUserMO);
    }

    public void leaveUser(String userId) {
        Iterator<GameUserMO> iterator = gameUserMOList.iterator();
        while (iterator.hasNext()) {
            GameUserMO next = iterator.next();
            if (userId.equals(next.getUserId())) {
                iterator.remove();
                break;
            }
        }
    }

    public List<String> getAllUserList() {
        List<String> userIdList = new LinkedList<>();
        for (GameUserMO gameUserMO : this.getGameUserMOList()) {
            userIdList.add(gameUserMO.getUserId());
        }
        return userIdList;
    }

    public List<String> getAllOtherUserList(String userId) {
        List<String> userIdList = new LinkedList<>();
        for (GameUserMO gameUserMO : this.getGameUserMOList()) {
            if (!userId.equals(gameUserMO.getUserId())) {
                userIdList.add(gameUserMO.getUserId());
            }
        }
        return userIdList;
    }

    public void changeUserPrepareStatus(String userId, boolean prepared) {
        Iterator<GameUserMO> iterator = gameUserMOList.iterator();
        while (iterator.hasNext()) {
            GameUserMO next = iterator.next();
            if (userId.equals(next.getUserId())) {
                next.setPrepared(prepared);
                break;
            }
        }
    }

    public boolean empty() {
        return null == gameUserMOList ? Boolean.TRUE : CollectionUtils.isEmpty(gameUserMOList);
    }

    public boolean shouldNotStartGame() {
        return gameManager.shouldNotStartGame(this.gameUserMOList);
    }

    public void startGame() {
        this.gameManager.startGame(this.gameUserMOList);
    }

    public String chooseOneUserToRobLandlord() {
        return this.gameManager.chooseOneUserToRobLandlord(this.gameUserMOList);
    }

    public void doRob(String userId) {
        this.gameManager.doRob(userId);
    }

    public void doNotRob(String userId) {
        this.gameManager.doNotRob(userId);
    }

    public List<CardEnumeration> getLandlordCardList() {
        return this.gameManager.getLandlordCardList();
    }

    public void giveLandlordCardToThisGuy(String userId) {
        this.gameManager.giveLandlordCardToThisGuy(userId, this.gameUserMOList);
    }

    public boolean hasNextOneToStartRob() {
        return this.gameManager.hasNextOneToStartRob();
    }

    public String makeNextUserToStartRob() {
        return this.gameManager.makeNextUserToStartRob(this.gameUserMOList);
    }

    public String makeLastUserRobLandlordCard() {
        return this.gameManager.makeLastUserRobLandlordCard(this.gameUserMOList);
    }

    public void doPlayCard(String userId, List<CardEnumeration> cardEnumerationList, PlayCardTypeEnumeration playCardTypeEnumeration) {
        this.gameManager.doPlayCard(userId, cardEnumerationList, playCardTypeEnumeration, this.gameUserMOList);
    }

    public boolean thisGuyWin(String userId) {
        return this.gameManager.thisGuyWin(userId, this.gameUserMOList);
    }

    public String makeNextUserToStartPlayCard() {
        return this.gameManager.makeNextUserToStartPlayCard(this.gameUserMOList);
    }

    public void doNotPlayCard(String userId) {
        this.gameManager.doNotPlayCard(userId);
    }

    public boolean hasNextOneToStartPlayCard() {
        return this.gameManager.hasNextOneToStartPlayCard();
    }

    public String makeLastPlayCardUserToStartPlayCard() {
        return this.gameManager.makeLastPlayCardUserToStartPlayCard();
    }
}

游戏控制器与回合的设计

回合实体类和gameManager配合完成,回合开始,回合结束,下一个该谁打牌

package com.nero.hua.game.manager;

import com.nero.hua.enumeration.CardEnumeration;
import com.nero.hua.enumeration.PlayCardEnumeration;
import com.nero.hua.enumeration.PlayCardTypeEnumeration;
import com.nero.hua.enumeration.RobLandlordEnumeration;
import com.nero.hua.exception.PlayCardException;
import com.nero.hua.exception.RobLandlordException;
import com.nero.hua.model.user.*;
import com.nero.hua.util.CardUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.CollectionUtils;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Getter
@Setter
public class GameManager {

    private static final int COLOR_CARD_COUNT = 52;

    private static final int NORMAL_USER_CARD_COUNT = 17;

    private static final int LANDLORD_CARD_COUNT = 3;

    private static final int MAX_USER_COUNT = 3;

    private List<CardEnumeration> landlordCardList;

    private RobLandlordRoundMO robLandlordRoundMO;

    private PlayCardRoundMO playCardRoundMO;

    public int getMaxUserCount() {
        return MAX_USER_COUNT;
    }

    public int getNormalUserCardCount() {
        return NORMAL_USER_CARD_COUNT;
    }

    public int getLandlordCardCount() {
        return LANDLORD_CARD_COUNT;
    }

    public boolean shouldStartGame(List<GameUserMO> gameUserMOList) {
        if (gameUserMOList.size() < MAX_USER_COUNT) {
            return Boolean.FALSE;
        }

        for (GameUserMO gameUserMO : gameUserMOList) {
            if (!gameUserMO.isPrepared()) {
                return Boolean.FALSE;
            }
        }

        return Boolean.TRUE;
    }

    public boolean shouldNotStartGame(List<GameUserMO> gameUserMOList) {
        return !this.shouldStartGame(gameUserMOList);
    }

    public void startGame(List<GameUserMO> gameUserMOList) {
        List<CardEnumeration> shuffledCardList = this.shuffleCard();
        List<List<CardEnumeration>> dealCardList = this.dealCard(shuffledCardList);
        for (int i = 0; i < gameUserMOList.size(); i++) {
            Map<CardEnumeration, Integer> cardMap = CardUtil.convertCardListToCardMap(dealCardList.remove(0));
            gameUserMOList.get(i).setCardMap(cardMap);
        }

        this.landlordCardList = dealCardList.get(dealCardList.size() - 1);
    }

    private List<CardEnumeration> shuffleCard() {
        List<CardEnumeration> aDeckCardList = getADeckCardList();
        List<CardEnumeration> aShuffledCardList = new LinkedList<>();
        while (!aDeckCardList.isEmpty()) {
            int random = (int) (Math.random() * aDeckCardList.size());
            CardEnumeration randomCard = aDeckCardList.remove(random);
            aShuffledCardList.add(randomCard);
        }
        return aShuffledCardList;
    }

    private List<CardEnumeration> getADeckCardList() {
        List<CardEnumeration> cardEnumerationList = new LinkedList<>();
        CardEnumeration[] cardEnumerationArray = CardEnumeration.values();
        for (int i = 0; i < COLOR_CARD_COUNT; i++) {
            cardEnumerationList.add(cardEnumerationArray[i]);
        }
        cardEnumerationList.add(CardEnumeration.CARD_516);
        cardEnumerationList.add(CardEnumeration.CARD_517);

        return cardEnumerationList;
    }

    private List<List<CardEnumeration>> dealCard(List<CardEnumeration> shuffledCardList) {
        List<List<CardEnumeration>> dealCardList = new LinkedList<>();

        for (int i = 0; i < MAX_USER_COUNT; i++) {
            List<CardEnumeration> userCardList = new LinkedList<>();
            for (int j = 0; j < NORMAL_USER_CARD_COUNT; j++) {
                userCardList.add(shuffledCardList.remove(0));
            }
            dealCardList.add(userCardList);
        }

        dealCardList.add(shuffledCardList);

        return dealCardList;
    }

    public String chooseOneUserToRobLandlord(List<GameUserMO> gameUserMOList) {
        int random  = (int) (Math.random() * MAX_USER_COUNT);
        String userId = gameUserMOList.get(random).getUserId();
        this.robLandlordRoundMO = new RobLandlordRoundMO(random, userId);
        return userId;
    }

    public void doRob(String userId) {
        UserRobLandlordTurnMO userRobLandlordTurnMO = this.thisGuyTurnForRobRound(userId);
        userRobLandlordTurnMO.setDoRob(Boolean.TRUE);
    }

    public void doNotRob(String userId) {
        UserRobLandlordTurnMO userRobLandlordTurnMO = this.thisGuyTurnForRobRound(userId);
        userRobLandlordTurnMO.setDoRob(Boolean.FALSE);
    }

    private UserRobLandlordTurnMO thisGuyTurnForRobRound(String userId) {
        if (null == this.robLandlordRoundMO) {
            throw new RobLandlordException(RobLandlordEnumeration.NOT_TIME_TO_ROB);
        }

        List<UserRobLandlordTurnMO> userRobLandlordTurnMOList = this.robLandlordRoundMO.getUserRobLandlordTurnMOList();
        UserRobLandlordTurnMO userRobLandlordTurnMO = userRobLandlordTurnMOList.get(userRobLandlordTurnMOList.size() - 1);
        if (!userId.equals(userRobLandlordTurnMO.getUserId())) {
            throw new RobLandlordException(RobLandlordEnumeration.NOT_YOUR_TURN);
        }

        return userRobLandlordTurnMO;
    }

    private int getUserIndexInUserListByUserId(String userId, List<GameUserMO> gameUserMOList) {
        for (int i = 0; i < gameUserMOList.size(); i++) {
            if (userId.equals(gameUserMOList.get(i).getUserId())) {
                return i;
            }
        }
        return 0;
    }

    private GameUserMO getUserInUserListByUserId(String userId, List<GameUserMO> gameUserMOList) {
        for (int i = 0; i < gameUserMOList.size(); i++) {
            if (userId.equals(gameUserMOList.get(i).getUserId())) {
                return gameUserMOList.get(i);
            }
        }
        return null;
    }

    public void giveLandlordCardToThisGuy(String userId, List<GameUserMO> gameUserMOList) {
        int userIndex = this.getUserIndexInUserListByUserId(userId, gameUserMOList);

        GameUserMO gameUserMO = gameUserMOList.get(userIndex);

        Map<CardEnumeration, Integer> cardMap = gameUserMO.getCardMap();
        for (CardEnumeration cardEnumeration : this.landlordCardList) {
            if (cardMap.containsKey(cardEnumeration)) {
                Integer count = cardMap.get(cardEnumeration);
                cardMap.put(cardEnumeration, count + 1);
            }
            else {
                cardMap.put(cardEnumeration, 1);
            }
        }

        this.playCardRoundMO = new PlayCardRoundMO();
        this.playCardRoundMO.addNewUserToStartPlayCard(userIndex, userId);
    }

    public boolean hasNextOneToStartRob() {
        if (null == this.robLandlordRoundMO) {
            throw new RobLandlordException(RobLandlordEnumeration.NOT_TIME_TO_ROB);
        }

        return this.robLandlordRoundMO.getUserRobLandlordTurnMOList().size() < MAX_USER_COUNT - 1;
    }

    public String makeNextUserToStartRob(List<GameUserMO> gameUserMOList) {
        int index = robLandlordRoundMO.getCurrentTurnUserIndex();

        int nextIndex = (index + 1) % this.getMaxUserCount();
        String nextUserId = gameUserMOList.get(nextIndex).getUserId();

        this.robLandlordRoundMO.addNewUserToStartRob(nextIndex, nextUserId);

        return nextUserId;
    }

    public String makeLastUserRobLandlordCard(List<GameUserMO> gameUserMOList) {
        int index = robLandlordRoundMO.getCurrentTurnUserIndex();

        int nextIndex = (index + 1) % this.getMaxUserCount();
        String nextUserId = gameUserMOList.get(nextIndex).getUserId();

        this.robLandlordRoundMO.addNewUserToDoRob(nextIndex, nextUserId);

        return nextUserId;
    }

    public void doPlayCard(String userId, List<CardEnumeration> cardEnumerationList, PlayCardTypeEnumeration playCardTypeEnumeration, List<GameUserMO> gameUserMOList) {
        if (CardUtil.playCardNotMatchPlayCardType(cardEnumerationList, playCardTypeEnumeration)) {
            throw new PlayCardException(PlayCardEnumeration.PLAY_CARD_DO_NOT_MATCH_ITS_TYPE);
        }

        UserPlayCardTurnMO userPlayCardTurnMO = this.thisGuyTurnForPlayCardRound(userId);

        GameUserMO gameUserMO = gameUserMOList.get(userPlayCardTurnMO.getUserIndex());
        Map<CardEnumeration, Integer> cardEnumerationMap = CardUtil.convertCardListToCardMap(cardEnumerationList);
        if (CardUtil.handCardMapNotContainsPlayCardMap(gameUserMO.getCardMap(), cardEnumerationMap)) {
            throw new PlayCardException(PlayCardEnumeration.HAND_CARD_DO_NOT_CONTAINS_PLAY_CARD);
        }

        UserPlayCardTurnMO lastUserPlayCardTurnMO = this.playCardRoundMO.getLastUserPlayCardTurnMO();
        if (CardUtil.currentPlayCardListNotBetterThanLastPlayCardList(lastUserPlayCardTurnMO, cardEnumerationList, playCardTypeEnumeration)) {
           throw new PlayCardException(PlayCardEnumeration.PLAY_CARD_DO_NOT_BETTER_THAN_LAST_PLAY_CARD);
        }

        this.removeUserCardList(gameUserMO, cardEnumerationList);

        userPlayCardTurnMO.setCardList(cardEnumerationList);
        userPlayCardTurnMO.setPlayCardTypeEnumeration(playCardTypeEnumeration);
    }

    private UserPlayCardTurnMO thisGuyTurnForPlayCardRound(String userId) {
        if (null == this.playCardRoundMO) {
            throw new PlayCardException(PlayCardEnumeration.NOT_TIME_TO_PLAY_CARD);
        }

        List<UserPlayCardTurnMO> userPlayCardTurnMOList = this.playCardRoundMO.getUserPlayCardTurnMOList();
        UserPlayCardTurnMO userPlayCardTurnMO = userPlayCardTurnMOList.get(userPlayCardTurnMOList.size() - 1);
        if (!userId.equals(userPlayCardTurnMO.getUserId())) {
            throw new PlayCardException(PlayCardEnumeration.NOT_YOUR_TURN);
        }

        return userPlayCardTurnMO;
    }

    private void removeUserCardList(GameUserMO gameUserMO, List<CardEnumeration> cardEnumerationList) {
        Map<CardEnumeration, Integer> cardMap = gameUserMO.getCardMap();
        for (CardEnumeration cardEnumeration : cardEnumerationList) {
            Integer cardCount = cardMap.get(cardEnumeration);
            if (1 == cardCount) {
                cardMap.remove(cardEnumeration);
            }
            else {
                cardMap.put(cardEnumeration, cardCount - 1);
            }
        }
    }

    public boolean thisGuyWin(String userId, List<GameUserMO> gameUserMOList) {
        GameUserMO gameUserMO = this.getUserInUserListByUserId(userId, gameUserMOList);

        return CollectionUtils.isEmpty(gameUserMO.getCardMap());
    }

    public String makeNextUserToStartPlayCard(List<GameUserMO> gameUserMOList) {
        int index = playCardRoundMO.getCurrentTurnUserIndex();

        int nextIndex = (index + 1) % this.getMaxUserCount();
        String nextUserId = gameUserMOList.get(nextIndex).getUserId();

        this.playCardRoundMO.addNewUserToStartPlayCard(nextIndex, nextUserId);

        return nextUserId;
    }

    public void doNotPlayCard(String userId) {
        this.thisGuyTurnForPlayCardRound(userId);

        UserPlayCardTurnMO lastUserPlayCardTurnMO = this.playCardRoundMO.getLastUserPlayCardTurnMO();
        if (null == lastUserPlayCardTurnMO) {
            throw new PlayCardException(PlayCardEnumeration.MUST_PLAY_CARD_WHEN_ROUND_START);
        }
    }

    public boolean hasNextOneToStartPlayCard() {
        return !this.playCardRoundMO.thisRoundFinish(this.getMaxUserCount());
    }

    public String makeLastPlayCardUserToStartPlayCard() {
        List<UserPlayCardTurnMO> userPlayCardTurnMOList = playCardRoundMO.getUserPlayCardTurnMOList();
        UserPlayCardTurnMO lastUserPlayCardTurnMO = userPlayCardTurnMOList.get(userPlayCardTurnMOList.size() - 1 - (MAX_USER_COUNT - 1));

        this.playCardRoundMO = new PlayCardRoundMO();
        this.playCardRoundMO.addNewUserToStartPlayCard(lastUserPlayCardTurnMO.getUserIndex(), lastUserPlayCardTurnMO.getUserId());

        return lastUserPlayCardTurnMO.getUserId();
    }
}

package com.nero.hua.model.user;

import lombok.Getter;
import lombok.Setter;
import org.springframework.util.CollectionUtils;

import java.util.LinkedList;
import java.util.List;

@Getter
@Setter
public class PlayCardRoundMO {

    private int currentTurnUserIndex;

    List<UserPlayCardTurnMO> userPlayCardTurnMOList = new LinkedList<>();

    public void addNewUserToStartPlayCard(int userIndex, String userId) {
        this.currentTurnUserIndex = userIndex;
        userPlayCardTurnMOList.add(new UserPlayCardTurnMO(userIndex, userId));
    }

    public boolean thisRoundFinish(int maxUserCount) {
        if (this.userPlayCardTurnMOList.size() < maxUserCount) {
            return Boolean.FALSE;
        }

        for (int i = 0; i < maxUserCount - 1; i++) {
            if (this.userPlayCardTurnMOList.get(this.userPlayCardTurnMOList.size() - 1 - i).userDoPlayCard()) {
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    public UserPlayCardTurnMO getLastUserPlayCardTurnMO() {
        if (CollectionUtils.isEmpty(userPlayCardTurnMOList)) {
            return null;
        }

        for (int i = this.userPlayCardTurnMOList.size() - 1; i >= 0; i--) {
            if (!CollectionUtils.isEmpty(this.userPlayCardTurnMOList.get(i).getCardList())) {
                return this.userPlayCardTurnMOList.get(i);
            }
        }

        return null;
    }

}

前端0号座椅总是为自己的设计

跟后端的回合流转类似,只不过多了位置。
前端房间的userList跟后端的userList保持一直,通过一个计算座椅位置来达到目的

import Card from "../../../enumeration/CardEnumeration";
import PlayCardType from "../../../enumeration/PlayCardTypeEnumeration";

export default class RoundMO {

  private currentTurnUserId : string ;

  private palyCardList : Array<Array<Card>> = new Array();

  private palyCardTypeList : Array<PlayCardType> = new Array();

  constructor(currentTurnUserId : string) {
    this.currentTurnUserId = currentTurnUserId;
  }

  public getCurrentTurnUserId() : string {
    return this.currentTurnUserId;
  }

  public setCurrentTurnUserId(currentTurnUserId : string) : void {
    this.currentTurnUserId = currentTurnUserId;
  }

  public doPlayCard(cardList : Array<Card>, playCardType : PlayCardType) : void {
    this.palyCardList.push(cardList);
    this.palyCardTypeList.push(playCardType);
  }

  public doNotPlayCard() : void {
    this.palyCardList.push(null);
    this.palyCardTypeList.push(null);
  }

  public thisRoundFinish(maxUserCount : number) : boolean {
    if (this.palyCardList.length < maxUserCount) {
      return false;
    }

    for (let i : number = 0, j : number = this.palyCardList.length - 1; i < maxUserCount - 1; i++, j--) {
      if (null !== this.palyCardList[j]) {
          return false;
      }
    }

    return true;
  }

  public getLastPlayCard() : Array<Card> {
    if (null == this.palyCardList) {
      return null;
    }

    for (let i = this.palyCardList.length; i >= 0; i--) {
      if (null != this.palyCardList[i]) {
        return this.palyCardList[i];
      }
    }

    return null;
  }

  public getLastPlayCardType() : PlayCardType {
    if (null == this.palyCardTypeList) {
      return null;
    }

    for (let i = this.palyCardTypeList.length; i >= 0; i--) {
      if (null != this.palyCardTypeList[i]) {
        return this.palyCardTypeList[i];
      }
    }

    return null;
  }

}

    private calculateSeatIndexByMeIndex(userIndex : number, meIndex : number) : number {
      return (userIndex - meIndex + 3) % 3;
    }

效果图

在这里插入图片描述
在这里插入图片描述

  • 13
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值