feat(计分): 实现血战计分V1核心逻辑

- 新增血战计分服务,支持七对、清一色等基础番型及杠上花等特殊加番
- 扩展结算结果结构,包含番型明细与支付分数计算
- 新增PostGangContext记录杠后补摸窗口,用于判断杠上花/杠上炮
- 完善胡牌判定器,新增七对和对对胡识别方法
- 更新开发计划文档,补充注释规范要求
- 添加计分相关单元测试,确保核心逻辑正确性
This commit is contained in:
hujun
2026-03-20 14:50:19 +08:00
parent d038a8732d
commit 34809fd0f3
17 changed files with 804 additions and 29 deletions

View File

@@ -17,6 +17,8 @@ public class GameSession {
private final GameTable table;
private final List<GameEvent> events;
private ResponseActionWindow pendingResponseActionWindow;
// 记录最近一次杠后补摸窗口,用于判断杠上花与杠上炮。
private PostGangContext postGangContext;
private final Map<Integer, ActionType> responseActionSelections;
public GameSession(String roomId, GameTable table) {
@@ -56,6 +58,14 @@ public class GameSession {
this.pendingResponseActionWindow = pendingResponseActionWindow;
}
public PostGangContext getPostGangContext() {
return postGangContext;
}
public void setPostGangContext(PostGangContext postGangContext) {
this.postGangContext = postGangContext;
}
public Map<Integer, ActionType> getResponseActionSelections() {
return responseActionSelections;
}
@@ -63,4 +73,8 @@ public class GameSession {
public void clearResponseActionSelections() {
responseActionSelections.clear();
}
public void clearPostGangContext() {
this.postGangContext = null;
}
}

View File

@@ -0,0 +1,7 @@
package com.xuezhanmaster.game.domain;
public record PostGangContext(
int seatNo,
String tileDisplayName
) {
}

View File

@@ -0,0 +1,11 @@
package com.xuezhanmaster.game.domain;
import java.util.List;
public record SettlementDetail(
int baseScore,
int totalFan,
int paymentScore,
List<SettlementFan> fans
) {
}

View File

@@ -0,0 +1,8 @@
package com.xuezhanmaster.game.domain;
public record SettlementFan(
String code,
String label,
int fan
) {
}

View File

@@ -8,6 +8,7 @@ public record SettlementResult(
int actorSeatNo,
int sourceSeatNo,
String triggerTile,
SettlementDetail settlementDetail,
List<ScoreChange> scoreChanges
) {
}

View File

@@ -1,6 +1,8 @@
package com.xuezhanmaster.game.event;
import com.xuezhanmaster.game.domain.ScoreChange;
import com.xuezhanmaster.game.domain.SettlementDetail;
import com.xuezhanmaster.game.domain.SettlementFan;
import com.xuezhanmaster.game.domain.SettlementResult;
import java.time.Instant;
@@ -92,6 +94,7 @@ public record GameEvent(
payload.put("actorSeatNo", settlementResult.actorSeatNo());
payload.put("sourceSeatNo", settlementResult.sourceSeatNo());
payload.put("triggerTile", settlementResult.triggerTile());
payload.put("settlementDetail", toSettlementDetailPayload(settlementResult.settlementDetail()));
payload.put("scoreChanges", toScoreChangePayload(settlementResult.scoreChanges()));
return of(gameId, GameEventType.SETTLEMENT_APPLIED, settlementResult.actorSeatNo(), payload);
}
@@ -143,4 +146,21 @@ public record GameEvent(
}
return payload;
}
private static Map<String, Object> toSettlementDetailPayload(SettlementDetail settlementDetail) {
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("baseScore", settlementDetail.baseScore());
payload.put("totalFan", settlementDetail.totalFan());
payload.put("paymentScore", settlementDetail.paymentScore());
List<Map<String, Object>> fans = new ArrayList<>();
for (SettlementFan settlementFan : settlementDetail.fans()) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("code", settlementFan.code());
item.put("label", settlementFan.label());
item.put("fan", settlementFan.fan());
fans.add(item);
}
payload.put("fans", fans);
return payload;
}
}

View File

@@ -0,0 +1,167 @@
package com.xuezhanmaster.game.service;
import com.xuezhanmaster.game.domain.GameSeat;
import com.xuezhanmaster.game.domain.MeldGroup;
import com.xuezhanmaster.game.domain.MeldType;
import com.xuezhanmaster.game.domain.SettlementDetail;
import com.xuezhanmaster.game.domain.SettlementFan;
import com.xuezhanmaster.game.domain.Tile;
import com.xuezhanmaster.game.domain.TileSuit;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class BloodBattleScoringService {
private static final int BASE_SCORE = 1;
private static final int EXPOSED_GANG_SCORE = 2;
private static final int SUPPLEMENTAL_GANG_SCORE = 1;
private static final int CONCEALED_GANG_SCORE = 2;
private final HuEvaluator huEvaluator;
public BloodBattleScoringService(HuEvaluator huEvaluator) {
this.huEvaluator = huEvaluator;
}
public SettlementDetail buildDiscardHuDetail(
GameSeat winnerSeat,
boolean gangShangPao,
boolean haiDiPao
) {
return buildHuDetail(winnerSeat, false, false, gangShangPao, false, haiDiPao);
}
public SettlementDetail buildSelfDrawHuDetail(
GameSeat winnerSeat,
boolean gangShangHua,
boolean haiDiLaoYue
) {
// V1 先采用统一基础口径,不叠加地方“自摸加底/加番”变体。
return buildHuDetail(winnerSeat, false, gangShangHua, false, haiDiLaoYue, false);
}
public SettlementDetail buildRobbingGangHuDetail(GameSeat winnerSeat) {
return buildHuDetail(winnerSeat, true, false, false, false, false);
}
public SettlementDetail buildExposedGangDetail() {
return new SettlementDetail(BASE_SCORE, 0, EXPOSED_GANG_SCORE, List.of());
}
public SettlementDetail buildSupplementalGangDetail() {
return new SettlementDetail(BASE_SCORE, 0, SUPPLEMENTAL_GANG_SCORE, List.of());
}
public SettlementDetail buildConcealedGangDetail() {
return new SettlementDetail(BASE_SCORE, 0, CONCEALED_GANG_SCORE, List.of());
}
private SettlementDetail buildHuDetail(
GameSeat winnerSeat,
boolean robbingGang,
boolean gangShangHua,
boolean gangShangPao,
boolean haiDiLaoYue,
boolean haiDiPao
) {
List<SettlementFan> fans = new ArrayList<>();
if (huEvaluator.isSevenPairs(winnerSeat.getHandTiles())) {
fans.add(new SettlementFan("QI_DUI", "七对", 2));
} else {
if (huEvaluator.isPengPengHu(winnerSeat.getHandTiles())) {
fans.add(new SettlementFan("DUI_DUI_HU", "对对胡", 1));
}
if (isJinGouDiao(winnerSeat)) {
fans.add(new SettlementFan("JIN_GOU_DIAO", "金钩钓", 1));
}
}
if (isQingYiSe(winnerSeat)) {
fans.add(new SettlementFan("QING_YI_SE", "清一色", 2));
}
int genCount = countGen(winnerSeat);
for (int i = 0; i < genCount; i++) {
fans.add(new SettlementFan("GEN", "", 1));
}
if (gangShangHua) {
fans.add(new SettlementFan("GANG_SHANG_HUA", "杠上花", 1));
}
if (gangShangPao) {
fans.add(new SettlementFan("GANG_SHANG_PAO", "杠上炮", 1));
}
if (haiDiLaoYue) {
fans.add(new SettlementFan("HAI_DI_LAO_YUE", "海底捞月", 1));
}
if (haiDiPao) {
fans.add(new SettlementFan("HAI_DI_PAO", "海底炮", 1));
}
if (robbingGang) {
fans.add(new SettlementFan("QIANG_GANG_HU", "抢杠胡", 1));
}
int totalFan = fans.stream()
.mapToInt(SettlementFan::fan)
.sum();
int paymentScore = BASE_SCORE << totalFan;
return new SettlementDetail(BASE_SCORE, totalFan, paymentScore, List.copyOf(fans));
}
private boolean isJinGouDiao(GameSeat winnerSeat) {
return winnerSeat.getMeldGroups().size() == 4
&& winnerSeat.getHandTiles().size() == 2
&& winnerSeat.getHandTiles().get(0).equals(winnerSeat.getHandTiles().get(1));
}
private boolean isQingYiSe(GameSeat winnerSeat) {
TileSuit firstSuit = null;
for (Tile tile : winnerSeat.getHandTiles()) {
if (firstSuit == null) {
firstSuit = tile.getSuit();
continue;
}
if (tile.getSuit() != firstSuit) {
return false;
}
}
for (MeldGroup meldGroup : winnerSeat.getMeldGroups()) {
Tile meldTile = meldGroup.tile();
if (firstSuit == null) {
firstSuit = meldTile.getSuit();
continue;
}
if (meldTile.getSuit() != firstSuit) {
return false;
}
}
return firstSuit != null;
}
private int countGen(GameSeat winnerSeat) {
Map<Tile, Integer> tileCounts = new LinkedHashMap<>();
for (Tile tile : winnerSeat.getHandTiles()) {
tileCounts.merge(tile, 1, Integer::sum);
}
for (MeldGroup meldGroup : winnerSeat.getMeldGroups()) {
tileCounts.merge(meldGroup.tile(), tileCountForMeld(meldGroup.type()), Integer::sum);
}
int genCount = 0;
for (Integer count : tileCounts.values()) {
if (count == 4) {
genCount++;
}
}
return genCount;
}
private int tileCountForMeld(MeldType meldType) {
return switch (meldType) {
case PENG -> 3;
case MING_GANG, BU_GANG, AN_GANG -> 4;
};
}
}

View File

@@ -6,6 +6,7 @@ import com.xuezhanmaster.game.domain.GamePhase;
import com.xuezhanmaster.game.domain.GameSeat;
import com.xuezhanmaster.game.domain.GameSession;
import com.xuezhanmaster.game.domain.GameTable;
import com.xuezhanmaster.game.domain.PostGangContext;
import com.xuezhanmaster.game.domain.ResponseActionResolution;
import com.xuezhanmaster.game.domain.ResponseActionSeatCandidate;
import com.xuezhanmaster.game.domain.ResponseActionWindow;
@@ -495,6 +496,7 @@ public class GameSessionService {
}
private void continueAfterDiscardWithoutResponse(GameSession session) {
session.clearPostGangContext();
moveToNextSeat(session.getTable(), session.getGameId());
autoPlayBots(session);
notifyActionIfHumanTurn(session);
@@ -526,6 +528,7 @@ public class GameSessionService {
}
private void executePeng(GameSession session, ResponseActionResolution resolution) {
session.clearPostGangContext();
Tile claimedTile = parseTile(resolution.triggerTile());
GameTable table = session.getTable();
GameSeat winnerSeat = table.getSeats().get(resolution.winnerSeatNo());
@@ -548,6 +551,7 @@ public class GameSessionService {
}
private void executeGang(GameSession session, ResponseActionResolution resolution) {
session.clearPostGangContext();
Tile claimedTile = parseTile(resolution.triggerTile());
GameTable table = session.getTable();
GameSeat winnerSeat = table.getSeats().get(resolution.winnerSeatNo());
@@ -580,6 +584,7 @@ public class GameSessionService {
Tile drawnTile = table.getWallTiles().remove(0);
winnerSeat.receiveTile(drawnTile);
markPostGangContext(session, winnerSeat.getSeatNo(), claimedTile.getDisplayName());
appendAndPublish(session, GameEvent.tileDrawn(session.getGameId(), winnerSeat.getSeatNo(), table.getWallTiles().size()));
appendAndPublish(session, GameEvent.turnSwitched(session.getGameId(), winnerSeat.getSeatNo()));
continueFromResolvedActionTurn(session, winnerSeat);
@@ -619,6 +624,11 @@ public class GameSessionService {
sourceSeat.getSeatNo(),
claimedTile.getDisplayName()
));
// 牌墙已空时,这次点炮对应“最后一张牌后的弃牌胡”,按海底炮加番。
boolean gangShangPao = !isSupplementalGangWindow(responseActionWindow)
&& isPostGangDiscard(session, sourceSeat.getSeatNo());
boolean haiDiPao = !isSupplementalGangWindow(responseActionWindow)
&& table.getWallTiles().isEmpty();
if (isSupplementalGangWindow(responseActionWindow)) {
appendSettlementEvents(session, settlementService.settleRobbingGangHu(
table,
@@ -628,12 +638,15 @@ public class GameSessionService {
));
} else {
appendSettlementEvents(session, settlementService.settleDiscardHu(
table,
winnerSeat.getSeatNo(),
sourceSeat.getSeatNo(),
claimedTile.getDisplayName()
table,
winnerSeat.getSeatNo(),
sourceSeat.getSeatNo(),
claimedTile.getDisplayName(),
gangShangPao,
haiDiPao
));
}
session.clearPostGangContext();
if (shouldFinishTable(table)) {
table.setPhase(GamePhase.FINISHED);
@@ -647,6 +660,9 @@ public class GameSessionService {
private void executeSelfDrawHu(GameSession session, String userId) {
GameTable table = session.getTable();
GameSeat winnerSeat = findSeatByUserId(table, userId);
// 自摸胡时牌墙已空,说明当前这张就是最后一张牌,按海底捞月加番。
boolean gangShangHua = isPostGangDraw(session, winnerSeat.getSeatNo());
boolean haiDiLaoYue = table.getWallTiles().isEmpty();
winnerSeat.declareHu();
appendAndPublish(session, GameEvent.responseActionDeclared(
@@ -659,8 +675,11 @@ public class GameSessionService {
appendSettlementEvents(session, settlementService.settleSelfDrawHu(
table,
winnerSeat.getSeatNo(),
null
null,
gangShangHua,
haiDiLaoYue
));
session.clearPostGangContext();
if (shouldFinishTable(table)) {
table.setPhase(GamePhase.FINISHED);
@@ -703,6 +722,7 @@ public class GameSessionService {
Tile drawnTile = table.getWallTiles().remove(0);
winnerSeat.receiveTile(drawnTile);
markPostGangContext(session, winnerSeat.getSeatNo(), gangTile.getDisplayName());
appendAndPublish(session, GameEvent.tileDrawn(session.getGameId(), winnerSeat.getSeatNo(), table.getWallTiles().size()));
appendAndPublish(session, GameEvent.turnSwitched(session.getGameId(), winnerSeat.getSeatNo()));
continueFromResolvedActionTurn(session, winnerSeat);
@@ -764,6 +784,7 @@ public class GameSessionService {
Tile drawnTile = table.getWallTiles().remove(0);
winnerSeat.receiveTile(drawnTile);
markPostGangContext(session, winnerSeat.getSeatNo(), gangTile.getDisplayName());
appendAndPublish(session, GameEvent.tileDrawn(session.getGameId(), winnerSeat.getSeatNo(), table.getWallTiles().size()));
appendAndPublish(session, GameEvent.turnSwitched(session.getGameId(), winnerSeat.getSeatNo()));
continueFromResolvedActionTurn(session, winnerSeat);
@@ -815,6 +836,21 @@ public class GameSessionService {
return huEvaluator.canHu(seat.getHandTiles());
}
private void markPostGangContext(GameSession session, int seatNo, String tileDisplayName) {
// 杠后补摸只对当前这名玩家的下一次自摸/弃牌裁决有效。
session.setPostGangContext(new PostGangContext(seatNo, tileDisplayName));
}
private boolean isPostGangDraw(GameSession session, int seatNo) {
PostGangContext postGangContext = session.getPostGangContext();
return postGangContext != null && postGangContext.seatNo() == seatNo;
}
private boolean isPostGangDiscard(GameSession session, int sourceSeatNo) {
PostGangContext postGangContext = session.getPostGangContext();
return postGangContext != null && postGangContext.seatNo() == sourceSeatNo;
}
private boolean isSupplementalGangWindow(ResponseActionWindow responseActionWindow) {
return "SUPPLEMENTAL_GANG_DECLARED".equals(responseActionWindow.triggerEventType());
}

View File

@@ -14,6 +14,9 @@ public class HuEvaluator {
if (tiles.size() % 3 != 2) {
return false;
}
if (isSevenPairs(tiles)) {
return true;
}
int[] counts = buildCounts(tiles);
for (int i = 0; i < counts.length; i++) {
@@ -29,6 +32,45 @@ public class HuEvaluator {
return false;
}
public boolean isSevenPairs(List<Tile> tiles) {
if (tiles.size() != 14) {
return false;
}
int[] counts = buildCounts(tiles);
int pairCount = 0;
for (int count : counts) {
if (count == 0) {
continue;
}
if (count != 2 && count != 4) {
return false;
}
pairCount += count / 2;
}
return pairCount == 7;
}
public boolean isPengPengHu(List<Tile> tiles) {
if (tiles.size() % 3 != 2) {
return false;
}
int[] counts = buildCounts(tiles);
for (int i = 0; i < counts.length; i++) {
if (counts[i] < 2) {
continue;
}
counts[i] -= 2;
if (canFormTripletsOnly(counts)) {
counts[i] += 2;
return true;
}
counts[i] += 2;
}
return false;
}
public boolean canHuWithClaimedTile(List<Tile> handTiles, Tile claimedTile) {
List<Tile> tiles = new ArrayList<>(handTiles);
tiles.add(claimedTile);
@@ -73,6 +115,20 @@ public class HuEvaluator {
return false;
}
private boolean canFormTripletsOnly(int[] counts) {
int firstIndex = firstNonZeroIndex(counts);
if (firstIndex == -1) {
return true;
}
if (counts[firstIndex] < 3) {
return false;
}
counts[firstIndex] -= 3;
boolean result = canFormTripletsOnly(counts);
counts[firstIndex] += 3;
return result;
}
private int firstNonZeroIndex(int[] counts) {
for (int i = 0; i < counts.length; i++) {
if (counts[i] > 0) {

View File

@@ -4,6 +4,7 @@ import com.xuezhanmaster.game.domain.ActionType;
import com.xuezhanmaster.game.domain.GameSeat;
import com.xuezhanmaster.game.domain.GameTable;
import com.xuezhanmaster.game.domain.ScoreChange;
import com.xuezhanmaster.game.domain.SettlementDetail;
import com.xuezhanmaster.game.domain.SettlementResult;
import com.xuezhanmaster.game.domain.SettlementType;
import org.springframework.stereotype.Service;
@@ -16,18 +17,25 @@ import java.util.Map;
@Service
public class SettlementService {
private static final int DIAN_PAO_HU_SCORE = 1;
private static final int ZI_MO_HU_SCORE = 1;
private static final int BU_GANG_SCORE = 1;
private static final int MING_GANG_SCORE = 1;
private static final int AN_GANG_SCORE = 2;
private final BloodBattleScoringService scoringService;
public SettlementService(BloodBattleScoringService scoringService) {
this.scoringService = scoringService;
}
public SettlementResult settleDiscardHu(
GameTable table,
int winnerSeatNo,
int sourceSeatNo,
String triggerTile
String triggerTile,
boolean gangShangPao,
boolean haiDiPao
) {
SettlementDetail settlementDetail = scoringService.buildDiscardHuDetail(
table.getSeats().get(winnerSeatNo),
gangShangPao,
haiDiPao
);
return applySettlement(
table,
SettlementType.DIAN_PAO_HU,
@@ -35,7 +43,13 @@ public class SettlementService {
winnerSeatNo,
sourceSeatNo,
triggerTile,
orderedDeltas(winnerSeatNo, DIAN_PAO_HU_SCORE, sourceSeatNo, -DIAN_PAO_HU_SCORE)
settlementDetail,
orderedDeltas(
winnerSeatNo,
settlementDetail.paymentScore(),
sourceSeatNo,
-settlementDetail.paymentScore()
)
);
}
@@ -45,6 +59,7 @@ public class SettlementService {
int sourceSeatNo,
String triggerTile
) {
SettlementDetail settlementDetail = scoringService.buildExposedGangDetail();
return applySettlement(
table,
SettlementType.MING_GANG,
@@ -52,23 +67,36 @@ public class SettlementService {
winnerSeatNo,
sourceSeatNo,
triggerTile,
orderedDeltas(winnerSeatNo, MING_GANG_SCORE, sourceSeatNo, -MING_GANG_SCORE)
settlementDetail,
orderedDeltas(
winnerSeatNo,
settlementDetail.paymentScore(),
sourceSeatNo,
-settlementDetail.paymentScore()
)
);
}
public SettlementResult settleSelfDrawHu(
GameTable table,
int winnerSeatNo,
String triggerTile
String triggerTile,
boolean gangShangHua,
boolean haiDiLaoYue
) {
SettlementDetail settlementDetail = scoringService.buildSelfDrawHuDetail(
table.getSeats().get(winnerSeatNo),
gangShangHua,
haiDiLaoYue
);
Map<Integer, Integer> scoreDeltas = new LinkedHashMap<>();
int totalWinScore = 0;
for (GameSeat seat : table.getSeats()) {
if (seat.getSeatNo() == winnerSeatNo || seat.isWon()) {
continue;
}
scoreDeltas.put(seat.getSeatNo(), -ZI_MO_HU_SCORE);
totalWinScore += ZI_MO_HU_SCORE;
scoreDeltas.put(seat.getSeatNo(), -settlementDetail.paymentScore());
totalWinScore += settlementDetail.paymentScore();
}
scoreDeltas.put(winnerSeatNo, totalWinScore);
return applySettlement(
@@ -78,6 +106,7 @@ public class SettlementService {
winnerSeatNo,
winnerSeatNo,
triggerTile,
settlementDetail,
scoreDeltas
);
}
@@ -88,6 +117,7 @@ public class SettlementService {
int sourceSeatNo,
String triggerTile
) {
SettlementDetail settlementDetail = scoringService.buildRobbingGangHuDetail(table.getSeats().get(winnerSeatNo));
return applySettlement(
table,
SettlementType.QIANG_GANG_HU,
@@ -95,7 +125,13 @@ public class SettlementService {
winnerSeatNo,
sourceSeatNo,
triggerTile,
orderedDeltas(winnerSeatNo, DIAN_PAO_HU_SCORE, sourceSeatNo, -DIAN_PAO_HU_SCORE)
settlementDetail,
orderedDeltas(
winnerSeatNo,
settlementDetail.paymentScore(),
sourceSeatNo,
-settlementDetail.paymentScore()
)
);
}
@@ -104,14 +140,15 @@ public class SettlementService {
int winnerSeatNo,
String triggerTile
) {
SettlementDetail settlementDetail = scoringService.buildSupplementalGangDetail();
Map<Integer, Integer> scoreDeltas = new LinkedHashMap<>();
int totalWinScore = 0;
for (GameSeat seat : table.getSeats()) {
if (seat.getSeatNo() == winnerSeatNo || seat.isWon()) {
continue;
}
scoreDeltas.put(seat.getSeatNo(), -BU_GANG_SCORE);
totalWinScore += BU_GANG_SCORE;
scoreDeltas.put(seat.getSeatNo(), -settlementDetail.paymentScore());
totalWinScore += settlementDetail.paymentScore();
}
scoreDeltas.put(winnerSeatNo, totalWinScore);
return applySettlement(
@@ -121,6 +158,7 @@ public class SettlementService {
winnerSeatNo,
winnerSeatNo,
triggerTile,
settlementDetail,
scoreDeltas
);
}
@@ -130,14 +168,15 @@ public class SettlementService {
int winnerSeatNo,
String triggerTile
) {
SettlementDetail settlementDetail = scoringService.buildConcealedGangDetail();
Map<Integer, Integer> scoreDeltas = new LinkedHashMap<>();
int totalWinScore = 0;
for (GameSeat seat : table.getSeats()) {
if (seat.getSeatNo() == winnerSeatNo || seat.isWon()) {
continue;
}
scoreDeltas.put(seat.getSeatNo(), -AN_GANG_SCORE);
totalWinScore += AN_GANG_SCORE;
scoreDeltas.put(seat.getSeatNo(), -settlementDetail.paymentScore());
totalWinScore += settlementDetail.paymentScore();
}
scoreDeltas.put(winnerSeatNo, totalWinScore);
return applySettlement(
@@ -147,6 +186,7 @@ public class SettlementService {
winnerSeatNo,
winnerSeatNo,
triggerTile,
settlementDetail,
scoreDeltas
);
}
@@ -158,6 +198,7 @@ public class SettlementService {
int actorSeatNo,
int sourceSeatNo,
String triggerTile,
SettlementDetail settlementDetail,
Map<Integer, Integer> scoreDeltas
) {
List<ScoreChange> scoreChanges = new ArrayList<>();
@@ -176,6 +217,7 @@ public class SettlementService {
actorSeatNo,
sourceSeatNo,
triggerTile,
settlementDetail,
List.copyOf(scoreChanges)
);
}