独自一人开发斗地主游戏(h5 + 安卓 + 苹果)
开篇感言
不枉我深入学习基础知识,算法与数据结构,编译原理,java并发编程,这些东西都有用得到的时候。
再极端的时间内学习了cocos creator引擎,并学以致用,有极大的增加了自己的自信心。
在公司总是拧螺丝,一个人开发一个完成的作品,我又觉得自己彷佛回到了色彩斑斓的世界中。
前端代码地址:https://github.com/neroHua/neroGameClient 后端代码地址:https://github.com/neroHua/neroGameServer
背景
本人使用java工作已经7年之多了,js和ts基本懂一些。 打算开发一款逮狗腿子的棋牌游戏(合同学们玩),先仿制一款斗地主游戏,试一下。
涉及到的知识点技能
编码功底:
- 算法和数据结构
- 设计模式(策略,责任链)
- 敏捷开发。
游戏引擎: 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
架构设计及其思路
- 服务器整体为单机结构(以后有需要,再扩容好了),提供 h5,安卓,苹果 三种客户端接入服务器,且前端由Cocos 引擎处理,直接打包成三个端 。
- 客户端和服务器端的通信协议选择为http,和 websocket (三端都支持的通信方式)。用户点击等等事件由客户端发送http请求。通过websocket,服务器向客户端推送消息。服务器端不接收用户通过websocket推送到服务器的消息。只用用户进入房间以后,才会开启websocket通信。
- 游戏的主要模型/功能设计。用户,房间,游戏,游戏控制器,游戏回合。房间是核心: 用户再房间里面玩游戏。每一个房间都有一个游戏控制器用以控制游戏的进程。用户有手牌。游戏控制器,通过游戏回合控制每回合的进行。
详细设计
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.为了便于比较牌型,先把牌按照一定格式格式化, 比如下面的。(对于有变种的牌型可以让客户端选择)
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();
}
- 牌型的格式化算法
整体式为: 把牌按照相同单牌权值的数量的大小进行分割成小块,再根据数量的大小把小块合并。把原来的大块按照单牌权值的大小进行排序,可以方便上诉操作
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++;
}
}
- 对格式化的牌进行牌型的识别
可以使用责任链模式,对于变种的可以可以处理。(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;
}
效果图
|