1. 简介
这个idea插件呢就是 xechat-idea当然也有vscode版本的,vscode可以搜索xechat-vscode,博主本人有相关的笔记链接
说明一下,vscode版本的可以直接在vscode插件市场下载,但是登录的话原有的官方服务器不可用了。现存的有两个地址可用。可用服务器列表的前两个。
其中第二个服务器需要前方qq群754126966找邪恶鼓励师
申请token ,下边的登录命令以第一个服务器为例
- 查询服务器列表
#showServer
- 打开麻将
#play 12
可以邀请同服务器好友同玩,也可直接开始和ai玩
点击free之后会将游戏页面独立出来一个窗口,该窗口可以调节透明度,摸鱼的时候不容易被发现
2. 开发过程
1. 什么是责任链
我的理解就是每个处理器只处理自己该处理的东西,除非遇到特殊条件,否则就交给下一个处理器来处理
2. 麻将游戏开发。
首先我这里现在只实现了最基本的
平胡
和七小对
胡牌判断,其他的胡牌类型都写在枚举里边了,有时间的话可以再写
然后 麻将氛围1-9万
1-9饼
1-9条
东南西北
中发白
判断顺子的时候需要判断牌的数值是否连续,前边三种万饼条只要数字连续就可以,后边的两种只能组成刻子或者是杠。所以它们的id要隔开。于是就有了下方的这个类
1. 麻将枚举类
package cn.xeblog.plugin.game.mahjong.enums;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import java.util.Arrays;
/**
* @author eleven
* @date 2024/3/20 8:38
* @apiNote
*/
@Getter
public enum MahjongEnum {
//🀇
YI_WAN(1, "\uD83C\uDC07", "一万"),
//🀈
ER_WAN(2, "\uD83C\uDC08", "二万"),
//🀉
SAN_WAN(3, "\uD83C\uDC09", "三万"),
//🀊
SI_WAN(4, "\uD83C\uDC0A", "四万"),
//🀋
WU_WAN(5, "\uD83C\uDC0B", "五万"),
//🀌
LIU_WAN(6, "\uD83C\uDC0C","六万"),
//🀍
QI_WAN(7, "\uD83C\uDC0D", "七万"),
//🀎
BA_WAN(8, "\uD83C\uDC0E", "八万"),
//🀏
JIU_WAN(9, "\uD83C\uDC0F", "九万"),
//🀐
YI_TIAO(11, "\uD83C\uDC10", "一条"),
//🀑
ER_TIAO(12, "\uD83C\uDC11", "二条"),
//🀒
SAN_TIAO(13, "\uD83C\uDC12", "三条"),
//🀓
SI_TIAO(14, "\uD83C\uDC13", "四条"),
//🀔
WU_TIAO(15, "\uD83C\uDC14", "五条"),
//🀕
LIU_TIAO(16, "\uD83C\uDC15", "六条"),
//🀖
QI_TIAO(17, "\uD83C\uDC16", "七条"),
//🀗
BA_TIAO(18, "\uD83C\uDC17", "八条"),
//🀘
JIU_TIAO(19, "\uD83C\uDC18", "九条"),
// 🀙
YI_BING(21, "\uD83C\uDC19", "一饼"),
//🀚
ER_BING(22, "\uD83C\uDC1A", "二饼"),
//🀛
SAN_BING(23, "\uD83C\uDC1B", "三饼"),
//🀜
SI_BING(24, "\uD83C\uDC1C", "四饼"),
//🀝
WU_BING(25, "\uD83C\uDC1D", "五饼"),
//🀞
LIU_BING(26, "\uD83C\uDC1E", "六饼"),
//🀟
QI_BING(27, "\uD83C\uDC1F", "七饼"),
//🀠
BA_BING(28, "\uD83C\uDC20", "八饼"),
//🀡
JIU_BING(29, "\uD83C\uDC21", "九饼"),
//🀀
DONG_FENG(31, "\uD83C\uDC00", "东风"),
//🀁
NAN_FENG(33, "\uD83C\uDC01", "南风"),
//🀂
XI_FENG(35, "\uD83C\uDC02", "西风"),
//🀃
BEI_FENG(37, "\uD83C\uDC03", "北风"),
//🀄
HONG_ZHONG(41, "\uD83C\uDC04", "中"),
//🀅
FA_CAI(43, "\uD83C\uDC05", "发"),
//🀆
BAI_BAN(45, "\uD83C\uDC06", "白"),
UNKNOWN(-9527, "\uD83C\uDC22", "暗杠")
;
private final Integer id;
private final String value;
private final String tipsText;
MahjongEnum(Integer id, String value, String tipsText) {
this.id = id;
this.value =value;
this.tipsText =tipsText;
}
public static MahjongEnum getById(Integer searchId) {
return Arrays.stream(MahjongEnum.values())
.filter(item -> item.getId() != null && item.getId().equals(searchId))
.findFirst()
.orElse(UNKNOWN);
}
@Override
public String toString() {
return StrUtil.format("[{}:{}:{}]", id, value, tipsText);
}
}
2. 接着我们写一个判断刻子,顺子,杠,的工具类
这里边的
刻子
就是有三个一样的,杠
就是四个一样的,顺子
就是三个连着的。
一开始的时候没想好怎么写判断顺子,但是后来给枚举加上id之后,只需要判断有没有三个id连续在一起就行了
判断是否能听牌就更加简单粗暴了,如果当前玩家的手牌不能赢,那么就把摸得牌替换完其他所有牌,如果能够胡的话那就是能够听牌。
例如你摸了一个发财
没胡,那我就把这个牌替换成1-9万
、1-9条
、1-9饼
、东南西北
、中白
,只要有一个能胡,那就是听牌了。
- 代码太多了放个链接吧,MahjongUtils.java
3. 胡牌类型枚举
从腾讯麻将里找的规则 完整代码地址
@Getter
public enum HuType {
TIAN_HU(0, 88, "天胡", "", "庄家发完手牌后直接胡牌"),
DI_HU(1, 88, "地胡", "", "发完手牌后,非庄家第一次摸牌就自摸胡牌。如在第一次摸牌前有任意玩家吃碰杠则不算地胡"),
SHO_SAN_YAO(2, 88, "十三幺",
values(DONG_FENG, NAN_FENG, XI_FENG, BEI_FENG) +
values(HONG_ZHONG, FA_CAI, BAI_BAN) +
values(YI_WAN, JIU_WAN) +
values(YI_TIAO, JIU_TIAO) +
values(YI_BING, JIU_BING) +
values(YI_WAN),
"由3种序数牌的一、九牌,7种字牌及其中一对作将组成的胡牌。不计五门齐、不求人、单钓将、门前清、全带么");
}
4. 摸牌类型枚举
package cn.xeblog.plugin.game.mahjong.enums;
/**
* @author eleven
* @date 2024/3/20 11:24
* @apiNote
*/
public enum TouchType {
// 摸
TOUCH,
// 吃
EAT,
// 碰
BUMP,
// 杠
BAR,
// 测试听牌
TEST
;
}
3. 责任链开发
1. 创建一个责任链的处理对象
package cn.xeblog.plugin.game.mahjong.model.dto;
import cn.hutool.core.collection.CollUtil;
import cn.xeblog.plugin.game.mahjong.enums.MahjongEnum;
import cn.xeblog.plugin.game.mahjong.enums.TouchType;
import cn.xeblog.plugin.game.mahjong.utils.MahjongUtils;
import groovy.transform.builder.Builder;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @author eleven
* @date 2024/3/20 9:06
* @apiNote
*/
@Data
@Builder
public class MahjongRequest {
// 摸得牌, 可能是 吃碰杠,或者自己摸的
private MahjongEnum touchMahjong;
// 玩家的手牌
private List<MahjongEnum> handMahjong;
// 当前轮次,判断天胡,地胡,人胡
private Integer round;
/**
* 0 摸排 1吃牌 2碰牌 3杠牌
*/
private TouchType touchType;
// 是否胡牌
private Boolean win;
// 输出最后的胡牌类型和番数
private StringBuilder stringBuilder;
/**
* 庄家
*/
private Boolean banker;
private Long score;
// 玩家的刻子,顺子,杠,暗杠
private MahjongGroup group;
// 当前玩家手牌可以听牌的列表
private List<MahjongEnum> tingList;
public StringBuilder getStringBuilder() {
if (stringBuilder == null) {
stringBuilder = new StringBuilder();
}
return stringBuilder;
}
public void addScore(Long addScore) {
Long defaultScore = Optional.ofNullable(score).orElse(0L);
this.score = defaultScore + addScore;
}
public Boolean getWin() {
return win != null && win;
}
public Boolean getBanker() {
return banker != null && banker;
}
public List<MahjongEnum> getAllHandMahjong() {
List<MahjongEnum> result = new ArrayList<>();
List<MahjongEnum> handMahjong = getHandMahjong();
if (CollUtil.isNotEmpty(handMahjong)) {
result.addAll(handMahjong);
}
if (null != touchMahjong) {
result.add(touchMahjong);
}
return MahjongUtils.sortById(result);
}
public Integer getRound() {
return Optional.ofNullable(round).orElse(0);
}
public MahjongGroup getGroup() {
return Optional.ofNullable(group).orElse(new MahjongGroup());
}
}
2. 首先创建一个抽象类
这个抽象类定义了下一个处理器是谁,公共的处理方法
handler
,定义了一个抽象方法isValidWin
用来给让每一个实现来判断是否符合自己的胡牌逻辑
package cn.xeblog.plugin.game.mahjong.handler;
import cn.xeblog.plugin.game.mahjong.model.dto.MahjongRequest;
import lombok.Data;
/**
* @author eleven
* @date 2024/3/20 9:04
* @apiNote
*/
@Data
public abstract class HuHandler {
private HuHandler nextHandler;
public void handle(MahjongRequest request) {
if (nextHandler != null) {
nextHandler.handle(request);
}
}
public abstract boolean isValidWin(MahjongRequest request);
}
3. 创建一个责任链的构造器
抄的别人的,好像是固定写法
package cn.xeblog.plugin.game.mahjong.handler;
import cn.xeblog.plugin.game.mahjong.handler.common.*;
import java.util.Arrays;
/**
* @author eleven
* @date 2024/3/20 13:40
* @apiNote
*/
public class HuHandlerBuilder {
private HuHandler head;
private HuHandler tail;
public HuHandlerBuilder addHandler(HuHandler handler) {
if (this.head == null) {
this.head = this.tail = handler;
return this;
}
this.tail.setNextHandler(handler);
this.tail = handler;
return this;
}
public HuHandlerBuilder addHandler(HuHandler... handlers) {
if (handlers.length != 0) {
Arrays.stream(handlers).forEach(this::addHandler);
}
return this;
}
public HuHandler build() {
return this.head;
}
public static HuHandler fastCommonBuild() {
return new HuHandlerBuilder().addHandler(
new PingHuHandler(),
new SevenPairsHuHandler(),
new SelfTouchHuHandler(),
new ShiSanYaoHandler(),
new TianHuHandler(),
new DiHuHandler(),
new RenHuHandler()
).build();
}
}
4. 创建一个平胡处理类
具体逻辑自己看看吧,反正就那么回事
package cn.xeblog.plugin.game.mahjong.handler.common;
import cn.hutool.core.collection.CollUtil;
import cn.xeblog.plugin.game.mahjong.enums.MahjongEnum;
import cn.xeblog.plugin.game.mahjong.handler.HuHandler;
import cn.xeblog.plugin.game.mahjong.model.dto.MahjongGroup;
import cn.xeblog.plugin.game.mahjong.model.dto.MahjongRequest;
import cn.xeblog.plugin.game.mahjong.utils.MahjongUtils;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author eleven
* @date 2024/3/20 9:07
* @apiNote
*/
public class PingHuHandler extends HuHandler {
@Override
public void handle(MahjongRequest request) {
boolean validWin = isValidWin(request);
if (validWin) {
request.setWin(true);
request.addScore(1L);
request.getStringBuilder().append(" 平胡(1)");
}
super.handle(request);
}
@Override
public boolean isValidWin(MahjongRequest request) {
List<MahjongEnum> handMahjong = request.getAllHandMahjong();
Map<Integer, List<MahjongEnum>> map = MahjongUtils.getCountMap(handMahjong);
return valid4442(map, request) || valid33332(map, request) || validCommon(handMahjong, request);
}
private boolean validCommon(List<MahjongEnum> handMahjong, MahjongRequest request) {
Set<MahjongEnum> gang;
//MahjongGroup group = request.getGroup();
MahjongGroup group = new MahjongGroup();
//List<MahjongEnum> pairs = getGroupAndPairsV1(handMahjong, group);
List<MahjongEnum> pairs = getGroupAndPairsV2(handMahjong, group);
if (pairs == null) return false;
request.setGroup(group);
int aaaOrAbcNum = group.getGang().size() +
group.getKeZi().size() +
group.getShunZi().size();
return aaaOrAbcNum == 4 && (CollUtil.size(pairs) == 1 || CollUtil.isEmpty(handMahjong));
}
private List<MahjongEnum> getGroupAndPairsV2(List<MahjongEnum> handMahjong, MahjongGroup group) {
List<MahjongEnum> pairs = getGroupAndPairsV1(handMahjong, group);
Map<Integer, List<MahjongEnum>> map = handMahjong.stream()
.collect(Collectors.groupingBy(MahjongEnum::getId));
if (CollUtil.isNotEmpty(pairs)) {
boolean doWhileFlag = pairs.stream().anyMatch(item -> map.get(item.getId()).size() > 2);
while (pairs.size() > 1 && doWhileFlag) {
getGroupAndPairsV1(handMahjong, group);
}
}
return pairs;
}
@Nullable
private static List<MahjongEnum> getGroupAndPairsV1(List<MahjongEnum> handMahjong, MahjongGroup group) {
Set<MahjongEnum> gang;
while (CollUtil.isNotEmpty(gang = MahjongUtils.getGang(handMahjong))) {
group.addGang(gang);
handMahjong.removeAll(gang);
}
List<MahjongEnum> pairs = MahjongUtils.getPairs(handMahjong);
if (CollUtil.isEmpty(pairs)) {
return null;
}
Set<MahjongEnum> keZi;
while (CollUtil.isNotEmpty(keZi = MahjongUtils.getAAA(handMahjong))) {
group.addKeZi(keZi);
handMahjong.removeAll(keZi);
}
List<MahjongEnum> shunZi;
while (CollUtil.isNotEmpty(shunZi = MahjongUtils.getABC(handMahjong))) {
group.addShunZi(shunZi);
shunZi.forEach(handMahjong::remove);
}
pairs = MahjongUtils.getPairs(handMahjong);
if (CollUtil.isNotEmpty(pairs)) {
group.setPairs(pairs.get(0));
}
return pairs;
}
/**
* 校验三组四个一样的和一个对子
*
* @param map 分组数据
* @return boolean
*/
public boolean valid4442(Map<Integer, List<MahjongEnum>> map, MahjongRequest request) {
return validSame(map, request);
}
/**
* 校验四组三个一样的和一个对子
*
* @param map
* @return
*/
public boolean valid33332(Map<Integer, List<MahjongEnum>> map, MahjongRequest request) {
// todo 现在只校验了四组三个一样
return validSame(map, request);
}
public boolean validSame(Map<Integer, List<MahjongEnum>> map, MahjongRequest request) {
MahjongGroup group = request.getGroup();
for (List<MahjongEnum> value : map.values()) {
int size = value.size();
if (size == 4) {
group.addGang(Set.copyOf(value));
}
if (size == 3) {
group.addKeZi(Set.copyOf(value));
}
if (size == 2) {
group.setPairs(value.get(0));
}
}
return group.getPairs() != null &&
(group.getGang().size() == 4 ||
group.getKeZi().size() == 4);
}
}
5. 创建一个七小对处理器
package cn.xeblog.plugin.game.mahjong.handler.common;
import cn.xeblog.plugin.game.mahjong.enums.MahjongEnum;
import cn.xeblog.plugin.game.mahjong.handler.HuHandler;
import cn.xeblog.plugin.game.mahjong.model.dto.MahjongRequest;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author eleven
* @date 2024/3/20 10:12
* @apiNote 七小对
*/
public class SevenPairsHuHandler extends HuHandler {
@Override
public void handle(MahjongRequest request) {
boolean validWin = isValidWin(request);
if (validWin) {
request.setWin(true);
request.getStringBuilder().append("七小对");
request.addScore(2L);
}
super.handle(request);
}
@Override
public boolean isValidWin(MahjongRequest request) {
List<MahjongEnum> handMahjong = request.getAllHandMahjong();
Map<Integer, List<MahjongEnum>> map = handMahjong.stream()
.collect(Collectors.groupingBy(MahjongEnum::getId));
AtomicInteger count = new AtomicInteger();
map.forEach((k, v) -> {
if (v.size() % 2 == 0) {
count.getAndIncrement();
}
});
return count.get() == 7;
}
}
6. 更多类型可以参考HuType
这个枚举类自己写
7. 测试
/**
* @author eleven
* @date 2024/3/20 11:08
* @apiNote
*/
public class MahjongTest {
/**
* 七小对
*/
@Test
public void sevenPairs() {
List<MahjongEnum> mahjongEnums = MahjongUtils.wan();
mahjongEnums.remove(BA_WAN);
mahjongEnums.remove(JIU_WAN);
List<MahjongEnum> handMahjong = new ArrayList<>(13);
handMahjong.addAll(mahjongEnums);
handMahjong.addAll(mahjongEnums);
handMahjong.remove(0);
MahjongRequest request = new MahjongRequest();
request.setHandMahjong(handMahjong);
request.setTouchMahjong(MahjongEnum.YI_WAN);
request.setTouchType(TouchType.TOUCH);
request.setWin(false);
printMahjong(request.getAllHandMahjong());
HuHandler build = HuHandlerBuilder.fastCommonBuild();
build.handle(request);
printResponse(request);
}
/**
* 前两种情况能够正常判断,后边两种情况判断出错。
* 所以需要对特殊牌型进行特殊处理
*/
@Test
public void getPairs() {
List<MahjongEnum> handMahjong = new ArrayList<>();
Collections.addAll(handMahjong,
YI_BING, YI_BING,
YI_BING, ER_BING, SAN_BING,
YI_WAN, ER_WAN, SAN_WAN,
SI_WAN, WU_WAN, LIU_WAN,
QI_WAN, BA_WAN, JIU_WAN
);
printMahjong(handMahjong);
MahjongUtils.getPairs(handMahjong)
.forEach(System.out::println);
handMahjong.clear();
Collections.addAll(handMahjong,
YI_BING, YI_BING,
YI_TIAO, ER_TIAO, SAN_TIAO,
YI_WAN, ER_WAN, SAN_WAN,
SI_WAN, WU_WAN, LIU_WAN,
QI_WAN, BA_WAN, JIU_WAN
);
printMahjong(handMahjong);
MahjongUtils.getPairs(handMahjong)
.forEach(System.out::println);
handMahjong.clear();
Collections.addAll(handMahjong,
YI_TIAO, YI_TIAO, YI_TIAO, YI_TIAO,
YI_WAN, YI_WAN, YI_WAN, YI_WAN,
YI_TIAO, YI_TIAO, YI_TIAO, YI_TIAO,
FA_CAI, FA_CAI
);
printMahjong(handMahjong);
// todo 判断错误应该为 FA_CAI
MahjongUtils.getPairs(handMahjong)
.forEach(System.out::println);
handMahjong.clear();
Collections.addAll(handMahjong,
YI_TIAO, YI_TIAO, YI_TIAO,
YI_WAN, YI_WAN, YI_WAN,
YI_TIAO, YI_TIAO, YI_TIAO,
DONG_FENG, DONG_FENG, DONG_FENG,
FA_CAI, FA_CAI
);
printMahjong(handMahjong);
MahjongUtils.getPairs(handMahjong)
.forEach(System.out::println);
}
@Test
public void shiSanYao() {
MahjongRequest request = new MahjongRequest();
List<MahjongEnum> handMahjong = new ArrayList<>(14);
Collections.addAll(handMahjong,
DONG_FENG, NAN_FENG, XI_FENG, BEI_FENG,
HONG_ZHONG, FA_CAI, BAI_BAN,
YI_WAN, JIU_WAN,
YI_TIAO, JIU_TIAO,
YI_BING, JIU_BING);
request.setHandMahjong(handMahjong);
request.setTouchMahjong(JIU_BING);
request.setRound(0);
request.setBanker(true);
request.setTouchType(TouchType.TOUCH);
printMahjong(request.getAllHandMahjong());
HuHandlerBuilder.fastCommonBuild().handle(request);
printResponse(request);
}
@Test
public void pingHuTest() {
List<MahjongEnum> handMahjong = new ArrayList<>();
Collections.addAll(handMahjong,
YI_WAN, YI_WAN, YI_WAN, YI_WAN,
YI_BING, YI_BING,
YI_BING, ER_BING, SAN_BING,
SI_WAN, SI_WAN, SI_WAN,
BAI_BAN, BAI_BAN, BAI_BAN
);
MahjongRequest request = new MahjongRequest();
request.setHandMahjong(handMahjong);
HuHandler huHandler = HuHandlerBuilder.fastCommonBuild();
huHandler.handle(request);
printMahjong(request.getAllHandMahjong());
printResponse(request);
}
@Test
public void tingTest() {
List<MahjongEnum> list = List.of(SAN_WAN, SI_WAN, WU_WAN,
SAN_TIAO, ER_TIAO,
WU_WAN, LIU_WAN, QI_WAN,
SAN_BING, SI_BING, WU_BING,
BAI_BAN, BAI_BAN);
System.out.println(MahjongUtils.getTingList(list));
}
public MahjongRequest getReq(MahjongEnum... enums) {
MahjongRequest request = new MahjongRequest();
request.setHandMahjong(List.of(enums));
return request;
}
private void printResponse(MahjongRequest request) {
System.out.println(StrUtil.format("当前回合{},身份:{},得分{}\n胡牌类型{}\n组合{}\n",
request.getRound(),
request.getBanker() ? "庄" : "闲",
request.getScore(),
request.getStringBuilder(),
request.getGroup()));
}
private void printMahjong(List<MahjongEnum> list) {
System.out.println("===============当前牌型============");
List<List<MahjongEnum>> partition = Lists.partition(list, 4);
partition.forEach(item -> {
item.forEach(it -> System.out.print(it + "\t"));
System.out.println();
}
);
System.out.println("==================================");
}
}