Files
xzmaster/backend/src/main/java/com/xuezhanmaster/game/service/ResponseActionResolver.java
hujun b84d0e8980 feat(结算): 实现血战麻将查叫与退税功能
新增局终处理逻辑,当牌墙耗尽且有多家未胡时:
1. 退税:未听牌玩家需退还此前杠分收入
2. 查叫:未听牌玩家向听牌玩家赔付最大理论点炮值
新增 SettlementType.TUI_SHUI 和 SettlementType.CHA_JIAO 结算类型
新增 ReadyHandOption 记录最优听牌选项

支持一炮多响裁决,新增 ResponseActionResolutionBatch 承载多赢家结果
在 GameSession 中新增 settlementHistory 保留结算记录供复用
更新开发文档要求加强关键区域的中文注释
2026-03-20 15:05:00 +08:00

68 lines
2.7 KiB
Java

package com.xuezhanmaster.game.service;
import com.xuezhanmaster.game.domain.ActionType;
import com.xuezhanmaster.game.domain.ResponseActionResolution;
import com.xuezhanmaster.game.domain.ResponseActionResolutionBatch;
import com.xuezhanmaster.game.domain.ResponseActionWindow;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Component
public class ResponseActionResolver {
public Optional<ResponseActionResolutionBatch> resolve(
ResponseActionWindow window,
Map<Integer, ActionType> selections,
int seatCount
) {
// 只要有胡牌声明,就按一炮多响返回全部胡牌赢家,其它动作全部失效。
List<ResponseActionResolution> huResolutions = selections.entrySet().stream()
.filter(entry -> entry.getValue() == ActionType.HU)
.sorted(Comparator.comparingInt(entry -> seatDistance(window.sourceSeatNo(), entry.getKey(), seatCount)))
.map(entry -> new ResponseActionResolution(
entry.getValue(),
entry.getKey(),
window.sourceSeatNo(),
window.triggerTile()
))
.toList();
if (!huResolutions.isEmpty()) {
// 一炮多响时保留所有胡牌赢家,后续由执行层逐个结算。
return Optional.of(new ResponseActionResolutionBatch(ActionType.HU, huResolutions));
}
return selections.entrySet().stream()
.filter(entry -> entry.getValue() != ActionType.PASS)
.min(Comparator
.comparingInt((Map.Entry<Integer, ActionType> entry) -> priority(entry.getValue()))
.thenComparingInt(entry -> seatDistance(window.sourceSeatNo(), entry.getKey(), seatCount)))
.map(entry -> new ResponseActionResolutionBatch(
entry.getValue(),
List.of(new ResponseActionResolution(
entry.getValue(),
entry.getKey(),
window.sourceSeatNo(),
window.triggerTile()
))
));
}
private int priority(ActionType actionType) {
return switch (actionType) {
case HU -> 0;
case GANG -> 1;
case PENG -> 2;
case PASS -> 3;
default -> 99;
};
}
private int seatDistance(int sourceSeatNo, int targetSeatNo, int seatCount) {
return (targetSeatNo - sourceSeatNo + seatCount) % seatCount;
}
}