feat: 添加局后复盘服务与页面容器组件
新增复盘服务相关DTO、Controller和Service 实现复盘页面容器组件ReviewPageContainer 更新前端页面架构文档与开发计划 移除DemoGameController中的演示复盘接口 补充复盘服务单元测试
This commit is contained in:
@@ -28,4 +28,3 @@ public class DemoGameController {
|
||||
return ApiResponse.success(demoGameService.createDemoTeachingAdvice());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,14 @@ public class GameSessionService {
|
||||
return toStateResponse(session, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复盘服务当前仍直接消费内存态对局与结算历史,
|
||||
* 这里先提供只读查询入口,避免 review 包直接接触会话仓储细节。
|
||||
*/
|
||||
public GameSession getSessionForReview(String gameId) {
|
||||
return getRequiredSession(gameId);
|
||||
}
|
||||
|
||||
public GameStateResponse performAction(String gameId, GameActionRequest request) {
|
||||
GameSession session = getRequiredSession(gameId);
|
||||
ActionType actionType = parseActionType(request.actionType());
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.xuezhanmaster.review.controller;
|
||||
|
||||
import com.xuezhanmaster.common.api.ApiResponse;
|
||||
import com.xuezhanmaster.review.dto.ReviewSummaryResponse;
|
||||
import com.xuezhanmaster.review.service.ReviewService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class ReviewController {
|
||||
|
||||
private final ReviewService reviewService;
|
||||
|
||||
public ReviewController(ReviewService reviewService) {
|
||||
this.reviewService = reviewService;
|
||||
}
|
||||
|
||||
@GetMapping("/games/{gameId}/review")
|
||||
public ApiResponse<ReviewSummaryResponse> review(
|
||||
@PathVariable String gameId,
|
||||
@RequestParam String userId
|
||||
) {
|
||||
return ApiResponse.success(reviewService.createGameReviewSummary(gameId, userId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.xuezhanmaster.review.dto;
|
||||
|
||||
/**
|
||||
* 复盘页里的“关键失误”条目。
|
||||
* 字段口径优先服务 H5 展示与后续训练题沉淀,不先做复杂评分模型。
|
||||
*/
|
||||
public record ReviewMistakeItem(
|
||||
String severity,
|
||||
String title,
|
||||
String issue,
|
||||
String suggestion
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.xuezhanmaster.review.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 复盘页里的“关键结算节点”条目。
|
||||
* 当前先用扁平结构表达,后续接真实局后数据时再决定是否拆更细的事件层级。
|
||||
*/
|
||||
public record ReviewSettlementItem(
|
||||
String title,
|
||||
String summary,
|
||||
int scoreDelta,
|
||||
List<String> fanLabels
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.xuezhanmaster.review.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 局后复盘页的最小协议骨架。
|
||||
* 当前先覆盖“总览、关键失误、训练方向”三块正式页面必需信息,后续再按真实复盘算法逐步扩展。
|
||||
*/
|
||||
public record ReviewSummaryResponse(
|
||||
String gameId,
|
||||
String userId,
|
||||
String playerNickname,
|
||||
int seatNo,
|
||||
int finalScore,
|
||||
String resultLabel,
|
||||
String conclusion,
|
||||
List<ReviewSettlementItem> settlementTimeline,
|
||||
List<ReviewMistakeItem> mistakeInsights,
|
||||
List<ReviewTrainingFocusItem> trainingFocuses
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.xuezhanmaster.review.dto;
|
||||
|
||||
/**
|
||||
* 复盘页里的“后续训练方向”条目。
|
||||
* 当前先保留最小训练类型标签,避免过早引入大而全训练体系。
|
||||
*/
|
||||
public record ReviewTrainingFocusItem(
|
||||
String drillType,
|
||||
String title,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
package com.xuezhanmaster.review.service;
|
||||
|
||||
import com.xuezhanmaster.common.exception.BusinessException;
|
||||
import com.xuezhanmaster.game.domain.GameSeat;
|
||||
import com.xuezhanmaster.game.domain.GameSession;
|
||||
import com.xuezhanmaster.game.domain.ScoreChange;
|
||||
import com.xuezhanmaster.game.domain.SettlementFan;
|
||||
import com.xuezhanmaster.game.domain.SettlementResult;
|
||||
import com.xuezhanmaster.game.domain.SettlementType;
|
||||
import com.xuezhanmaster.game.service.GameSessionService;
|
||||
import com.xuezhanmaster.review.dto.ReviewMistakeItem;
|
||||
import com.xuezhanmaster.review.dto.ReviewSettlementItem;
|
||||
import com.xuezhanmaster.review.dto.ReviewSummaryResponse;
|
||||
import com.xuezhanmaster.review.dto.ReviewTrainingFocusItem;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class ReviewService {
|
||||
|
||||
private final GameSessionService gameSessionService;
|
||||
|
||||
public ReviewService(GameSessionService gameSessionService) {
|
||||
this.gameSessionService = gameSessionService;
|
||||
}
|
||||
|
||||
public ReviewSummaryResponse createGameReviewSummary(String gameId, String userId) {
|
||||
GameSession session = gameSessionService.getSessionForReview(gameId);
|
||||
GameSeat reviewSeat = session.getTable().getSeats().stream()
|
||||
.filter(seat -> seat.getPlayerId().equals(userId))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BusinessException("GAME_SEAT_NOT_FOUND", "当前玩家不在对局中"));
|
||||
|
||||
List<SettlementResult> settlementHistory = List.copyOf(session.getSettlementHistory());
|
||||
List<ReviewSettlementItem> settlementTimeline = buildSettlementTimeline(settlementHistory, reviewSeat.getSeatNo());
|
||||
List<ReviewMistakeItem> mistakeInsights = buildMistakeInsights(settlementHistory, reviewSeat.getSeatNo());
|
||||
List<ReviewTrainingFocusItem> trainingFocuses = buildTrainingFocuses(
|
||||
settlementHistory,
|
||||
reviewSeat.getSeatNo(),
|
||||
reviewSeat.getScore(),
|
||||
mistakeInsights
|
||||
);
|
||||
|
||||
return new ReviewSummaryResponse(
|
||||
session.getGameId(),
|
||||
reviewSeat.getPlayerId(),
|
||||
reviewSeat.getNickname(),
|
||||
reviewSeat.getSeatNo(),
|
||||
reviewSeat.getScore(),
|
||||
buildResultLabel(reviewSeat.getScore()),
|
||||
buildConclusion(reviewSeat, settlementTimeline, mistakeInsights),
|
||||
settlementTimeline,
|
||||
mistakeInsights,
|
||||
trainingFocuses
|
||||
);
|
||||
}
|
||||
|
||||
private List<ReviewSettlementItem> buildSettlementTimeline(List<SettlementResult> settlementHistory, int seatNo) {
|
||||
List<ReviewSettlementItem> items = new ArrayList<>();
|
||||
for (SettlementResult settlementResult : settlementHistory) {
|
||||
Integer scoreDelta = resolveSeatDelta(settlementResult, seatNo);
|
||||
if (scoreDelta == null) {
|
||||
continue;
|
||||
}
|
||||
List<String> fanLabels = buildFanLabels(settlementResult);
|
||||
items.add(new ReviewSettlementItem(
|
||||
buildSettlementTitle(settlementResult, scoreDelta),
|
||||
buildSettlementSummary(settlementResult, scoreDelta, fanLabels),
|
||||
scoreDelta,
|
||||
fanLabels
|
||||
));
|
||||
}
|
||||
if (items.isEmpty()) {
|
||||
items.add(new ReviewSettlementItem(
|
||||
"暂无个人结算",
|
||||
"当前对局还没有产生与你直接相关的结算变化,复盘摘要会在出现真实得失分后逐步丰富。",
|
||||
0,
|
||||
List.of("等待结算")
|
||||
));
|
||||
}
|
||||
return List.copyOf(items);
|
||||
}
|
||||
|
||||
private List<ReviewMistakeItem> buildMistakeInsights(List<SettlementResult> settlementHistory, int seatNo) {
|
||||
List<SettlementResult> negativeSettlements = settlementHistory.stream()
|
||||
.filter(result -> {
|
||||
Integer delta = resolveSeatDelta(result, seatNo);
|
||||
return delta != null && delta < 0;
|
||||
})
|
||||
.sorted(Comparator.comparingInt((SettlementResult result) -> Math.abs(resolveSeatDelta(result, seatNo))).reversed())
|
||||
.limit(3)
|
||||
.toList();
|
||||
|
||||
List<ReviewMistakeItem> items = new ArrayList<>();
|
||||
for (SettlementResult settlementResult : negativeSettlements) {
|
||||
int scoreDelta = resolveSeatDelta(settlementResult, seatNo);
|
||||
items.add(switch (settlementResult.settlementType()) {
|
||||
case DIAN_PAO_HU -> new ReviewMistakeItem(
|
||||
"HIGH",
|
||||
"点炮导致失分",
|
||||
"你在他人弃牌胡结算中承担了 " + formatScore(scoreDelta) + ",说明中后盘危险张控制仍有明显缺口。",
|
||||
"当对手已经副露成型或番型抬高时,优先保留现成安全张,避免继续压高危中张。"
|
||||
);
|
||||
case QIANG_GANG_HU -> new ReviewMistakeItem(
|
||||
"HIGH",
|
||||
"补杠时机过激",
|
||||
"你在补杠后被对手抢杠胡,直接产生 " + formatScore(scoreDelta) + " 的回撤。",
|
||||
"补杠前先检查场上是否已出现高压听牌信号,必要时放弃补杠收益,优先确保不放大失分。"
|
||||
);
|
||||
case ZI_MO_HU -> new ReviewMistakeItem(
|
||||
"MEDIUM",
|
||||
"未能压住对手自摸",
|
||||
"对手自摸时你被扣除 " + formatScore(scoreDelta) + ",说明本局在速度或成叫质量上没有形成足够压制。",
|
||||
"后续训练应强化中盘成型速度判断,避免在明显落后牌速时仍维持松散手型。"
|
||||
);
|
||||
case TUI_SHUI -> new ReviewMistakeItem(
|
||||
"MEDIUM",
|
||||
"杠分未守住",
|
||||
"本局出现退税,导致你回吐 " + formatScore(scoreDelta) + ",说明前序杠牌收益没有稳定兑现。",
|
||||
"练习在流局风险升高时复盘杠牌收益与听牌质量,避免为短期税分牺牲整体成叫。"
|
||||
);
|
||||
case CHA_JIAO -> new ReviewMistakeItem(
|
||||
"HIGH",
|
||||
"流局未成叫",
|
||||
"流局查叫阶段你承担了 " + formatScore(scoreDelta) + ",反映出收尾阶段的成叫效率不足。",
|
||||
"进入残局后要更早切换到保叫路线,优先确保最小可支付听牌,而不是继续追求高番远型。"
|
||||
);
|
||||
case MING_GANG -> new ReviewMistakeItem(
|
||||
"MEDIUM",
|
||||
"放杠失分",
|
||||
"你在他人明杠结算里承担了 " + formatScore(scoreDelta) + ",说明对明显碰后加杠的防范还不够。",
|
||||
"看到对手碰后,应更早记录其可能补强的牌张,减少继续喂牌或协助做大的情况。"
|
||||
);
|
||||
case BU_GANG, AN_GANG -> new ReviewMistakeItem(
|
||||
"LOW",
|
||||
"杠分压制不足",
|
||||
"对手通过杠牌从你这里拿走了 " + formatScore(scoreDelta) + ",本局资源交换略偏被动。",
|
||||
"后续可专项训练杠前后局势判断,明确什么时候该抢速度,什么时候该减少被动付分。"
|
||||
);
|
||||
});
|
||||
}
|
||||
return List.copyOf(items);
|
||||
}
|
||||
|
||||
private List<ReviewTrainingFocusItem> buildTrainingFocuses(
|
||||
List<SettlementResult> settlementHistory,
|
||||
int seatNo,
|
||||
int finalScore,
|
||||
List<ReviewMistakeItem> mistakeInsights
|
||||
) {
|
||||
List<ReviewTrainingFocusItem> items = new ArrayList<>();
|
||||
Set<String> addedDrillTypes = new LinkedHashSet<>();
|
||||
|
||||
if (hasNegativeSettlement(settlementHistory, seatNo, SettlementType.DIAN_PAO_HU, SettlementType.QIANG_GANG_HU, SettlementType.ZI_MO_HU)) {
|
||||
addTrainingFocus(
|
||||
items,
|
||||
addedDrillTypes,
|
||||
"RISK_CONTROL",
|
||||
"危险张与守备节奏训练",
|
||||
"围绕真实失分节点复盘中后盘危险张排序,建立“副露压力上升时先保底不放炮”的判断习惯。"
|
||||
);
|
||||
}
|
||||
|
||||
if (hasNegativeSettlement(settlementHistory, seatNo, SettlementType.CHA_JIAO) || finalScore <= 0) {
|
||||
addTrainingFocus(
|
||||
items,
|
||||
addedDrillTypes,
|
||||
"READY_HAND",
|
||||
"成叫效率训练",
|
||||
"重点训练残局从做大切换到保叫的时机,避免在查叫阶段继续承担被动失分。"
|
||||
);
|
||||
}
|
||||
|
||||
if (hasSettlement(settlementHistory, seatNo, SettlementType.MING_GANG, SettlementType.BU_GANG, SettlementType.AN_GANG, SettlementType.TUI_SHUI)) {
|
||||
addTrainingFocus(
|
||||
items,
|
||||
addedDrillTypes,
|
||||
"GANG_TIMING",
|
||||
"杠牌收益判断训练",
|
||||
"把本局杠牌、退税与抢杠相关节点串起来看,训练“能不能杠”和“杠完是否值得”的双重判断。"
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasPositiveHu(settlementHistory, seatNo)) {
|
||||
addTrainingFocus(
|
||||
items,
|
||||
addedDrillTypes,
|
||||
"PATTERN_BUILDING",
|
||||
"基础番型成型训练",
|
||||
"当前结算里你的直接胡牌样本不足,建议先从常见番型与听牌速度训练入手,提升稳定得分能力。"
|
||||
);
|
||||
}
|
||||
|
||||
if (items.isEmpty()) {
|
||||
addTrainingFocus(
|
||||
items,
|
||||
addedDrillTypes,
|
||||
"ADVANTAGE_CONVERT",
|
||||
"优势局扩大训练",
|
||||
"本局没有明显失误结算,后续可把重点放在如何把已有领先进一步转化为更稳定的收尾优势。"
|
||||
);
|
||||
}
|
||||
|
||||
if (items.size() == 1 && !mistakeInsights.isEmpty()) {
|
||||
addTrainingFocus(
|
||||
items,
|
||||
addedDrillTypes,
|
||||
"REVIEW_REPEAT",
|
||||
"关键失分复现训练",
|
||||
"把本局最高代价的 1 到 2 个结算节点单独复现,形成固定复盘模板,提升下一局的可迁移性。"
|
||||
);
|
||||
}
|
||||
|
||||
return List.copyOf(items);
|
||||
}
|
||||
|
||||
private void addTrainingFocus(
|
||||
List<ReviewTrainingFocusItem> items,
|
||||
Set<String> addedDrillTypes,
|
||||
String drillType,
|
||||
String title,
|
||||
String description
|
||||
) {
|
||||
if (addedDrillTypes.add(drillType)) {
|
||||
items.add(new ReviewTrainingFocusItem(drillType, title, description));
|
||||
}
|
||||
}
|
||||
|
||||
private String buildResultLabel(int finalScore) {
|
||||
if (finalScore > 0) {
|
||||
return "本局净胜";
|
||||
}
|
||||
if (finalScore < 0) {
|
||||
return "本局失分";
|
||||
}
|
||||
return "本局持平";
|
||||
}
|
||||
|
||||
private String buildConclusion(
|
||||
GameSeat reviewSeat,
|
||||
List<ReviewSettlementItem> settlementTimeline,
|
||||
List<ReviewMistakeItem> mistakeInsights
|
||||
) {
|
||||
long positiveSettlements = settlementTimeline.stream()
|
||||
.filter(item -> item.scoreDelta() > 0)
|
||||
.count();
|
||||
long negativeSettlements = settlementTimeline.stream()
|
||||
.filter(item -> item.scoreDelta() < 0)
|
||||
.count();
|
||||
|
||||
if (reviewSeat.getScore() > 0) {
|
||||
if (negativeSettlements > 0 && !mistakeInsights.isEmpty()) {
|
||||
return "本局净胜 " + formatScore(reviewSeat.getScore())
|
||||
+ ",虽然通过 " + positiveSettlements + " 次真实得分节点建立优势,但仍暴露出“"
|
||||
+ mistakeInsights.get(0).title() + "”这类回撤点。";
|
||||
}
|
||||
return "本局净胜 " + formatScore(reviewSeat.getScore())
|
||||
+ ",主要依靠 " + positiveSettlements + " 次真实结算建立优势,整体节奏相对稳定。";
|
||||
}
|
||||
if (reviewSeat.getScore() < 0) {
|
||||
if (!mistakeInsights.isEmpty()) {
|
||||
return "本局净失 " + formatScore(reviewSeat.getScore())
|
||||
+ ",关键回撤集中在“" + mistakeInsights.get(0).title()
|
||||
+ "”,建议优先围绕该节点做针对性训练。";
|
||||
}
|
||||
return "本局净失 " + formatScore(reviewSeat.getScore())
|
||||
+ ",当前直接失分样本不多,但结算收益不足,后续需要优先提升稳定成叫与基础得分能力。";
|
||||
}
|
||||
return "本局最终持平,期间共有 " + positiveSettlements + " 次得分与 " + negativeSettlements
|
||||
+ " 次失分节点,后续应重点复盘收益转化是否足够稳定。";
|
||||
}
|
||||
|
||||
private boolean hasNegativeSettlement(List<SettlementResult> settlementHistory, int seatNo, SettlementType... settlementTypes) {
|
||||
Set<SettlementType> acceptedTypes = Set.of(settlementTypes);
|
||||
return settlementHistory.stream()
|
||||
.filter(result -> acceptedTypes.contains(result.settlementType()))
|
||||
.anyMatch(result -> {
|
||||
Integer delta = resolveSeatDelta(result, seatNo);
|
||||
return delta != null && delta < 0;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean hasSettlement(List<SettlementResult> settlementHistory, int seatNo, SettlementType... settlementTypes) {
|
||||
Set<SettlementType> acceptedTypes = Set.of(settlementTypes);
|
||||
return settlementHistory.stream()
|
||||
.filter(result -> acceptedTypes.contains(result.settlementType()))
|
||||
.anyMatch(result -> resolveSeatDelta(result, seatNo) != null);
|
||||
}
|
||||
|
||||
private boolean hasPositiveHu(List<SettlementResult> settlementHistory, int seatNo) {
|
||||
return settlementHistory.stream()
|
||||
.filter(result -> result.settlementType() == SettlementType.DIAN_PAO_HU
|
||||
|| result.settlementType() == SettlementType.ZI_MO_HU
|
||||
|| result.settlementType() == SettlementType.QIANG_GANG_HU)
|
||||
.anyMatch(result -> {
|
||||
Integer delta = resolveSeatDelta(result, seatNo);
|
||||
return delta != null && delta > 0;
|
||||
});
|
||||
}
|
||||
|
||||
private Integer resolveSeatDelta(SettlementResult settlementResult, int seatNo) {
|
||||
for (ScoreChange scoreChange : settlementResult.scoreChanges()) {
|
||||
if (scoreChange.seatNo() == seatNo) {
|
||||
return scoreChange.delta();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<String> buildFanLabels(SettlementResult settlementResult) {
|
||||
if (!settlementResult.settlementDetail().fans().isEmpty()) {
|
||||
return settlementResult.settlementDetail().fans().stream()
|
||||
.map(SettlementFan::label)
|
||||
.toList();
|
||||
}
|
||||
return List.of(settlementTypeLabel(settlementResult.settlementType()));
|
||||
}
|
||||
|
||||
private String buildSettlementTitle(SettlementResult settlementResult, int scoreDelta) {
|
||||
return switch (settlementResult.settlementType()) {
|
||||
case DIAN_PAO_HU -> scoreDelta > 0 ? "点炮胡得分" : "点炮失分";
|
||||
case ZI_MO_HU -> scoreDelta > 0 ? "自摸胡得分" : "对手自摸失分";
|
||||
case QIANG_GANG_HU -> scoreDelta > 0 ? "抢杠胡得分" : "补杠被抢";
|
||||
case MING_GANG -> scoreDelta > 0 ? "明杠收益" : "放杠失分";
|
||||
case BU_GANG -> scoreDelta > 0 ? "补杠收益" : "对手补杠失分";
|
||||
case AN_GANG -> scoreDelta > 0 ? "暗杠收益" : "对手暗杠失分";
|
||||
case TUI_SHUI -> scoreDelta > 0 ? "退税回补" : "杠分被退税";
|
||||
case CHA_JIAO -> scoreDelta > 0 ? "查叫收益" : "流局查叫失分";
|
||||
};
|
||||
}
|
||||
|
||||
private String buildSettlementSummary(
|
||||
SettlementResult settlementResult,
|
||||
int scoreDelta,
|
||||
List<String> fanLabels
|
||||
) {
|
||||
String fanSummary = fanLabels.isEmpty() ? "" : ",标签:" + String.join(" / ", fanLabels);
|
||||
return switch (settlementResult.settlementType()) {
|
||||
case DIAN_PAO_HU -> scoreDelta > 0
|
||||
? "你通过他人弃牌完成胡牌,获得 " + formatScore(scoreDelta)
|
||||
+ ",来源座位 " + settlementResult.sourceSeatNo() + fanSummary + "。"
|
||||
: "你向座位 " + settlementResult.actorSeatNo() + " 点炮,产生 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
case ZI_MO_HU -> scoreDelta > 0
|
||||
? "你完成自摸,从仍未胡牌对手处累计获得 " + formatScore(scoreDelta) + fanSummary + "。"
|
||||
: "座位 " + settlementResult.actorSeatNo() + " 自摸时,你承担了 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
case QIANG_GANG_HU -> scoreDelta > 0
|
||||
? "你在抢杠窗口完成胡牌,获得 " + formatScore(scoreDelta)
|
||||
+ ",目标来自座位 " + settlementResult.sourceSeatNo() + fanSummary + "。"
|
||||
: "你补杠时被座位 " + settlementResult.actorSeatNo() + " 抢杠胡,损失 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
case MING_GANG -> scoreDelta > 0
|
||||
? "你通过明杠从座位 " + settlementResult.sourceSeatNo() + " 取得 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。"
|
||||
: "你为座位 " + settlementResult.actorSeatNo() + " 的明杠支付了 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
case BU_GANG -> scoreDelta > 0
|
||||
? "你完成补杠,从其余未胡玩家处累计获得 " + formatScore(scoreDelta) + fanSummary + "。"
|
||||
: "座位 " + settlementResult.actorSeatNo() + " 补杠时,你承担了 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
case AN_GANG -> scoreDelta > 0
|
||||
? "你完成暗杠,从其余未胡玩家处累计获得 " + formatScore(scoreDelta) + fanSummary + "。"
|
||||
: "座位 " + settlementResult.actorSeatNo() + " 暗杠时,你承担了 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
case TUI_SHUI -> scoreDelta > 0
|
||||
? "你在局末获得退税回补,拿回 " + formatScore(scoreDelta)
|
||||
+ ",来源座位 " + settlementResult.sourceSeatNo() + "。"
|
||||
: "你被执行退税,向座位 " + settlementResult.actorSeatNo() + " 回吐了 "
|
||||
+ formatScore(scoreDelta) + "。";
|
||||
case CHA_JIAO -> scoreDelta > 0
|
||||
? "流局查叫阶段,你从座位 " + settlementResult.sourceSeatNo() + " 获得 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。"
|
||||
: "流局查叫阶段,你向座位 " + settlementResult.actorSeatNo() + " 支付了 "
|
||||
+ formatScore(scoreDelta) + fanSummary + "。";
|
||||
};
|
||||
}
|
||||
|
||||
private String settlementTypeLabel(SettlementType settlementType) {
|
||||
return switch (settlementType) {
|
||||
case DIAN_PAO_HU -> "点炮胡";
|
||||
case QIANG_GANG_HU -> "抢杠胡";
|
||||
case ZI_MO_HU -> "自摸胡";
|
||||
case BU_GANG -> "补杠";
|
||||
case MING_GANG -> "明杠";
|
||||
case AN_GANG -> "暗杠";
|
||||
case TUI_SHUI -> "退税";
|
||||
case CHA_JIAO -> "查叫";
|
||||
};
|
||||
}
|
||||
|
||||
private String formatScore(int scoreDelta) {
|
||||
return scoreDelta > 0 ? "+" + scoreDelta : String.valueOf(scoreDelta);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user