feat: 实现补杠和抢杠胡功能
新增 MeldType 和 MeldGroup 领域模型,支持碰、明杠、补杠、暗杠四种副露类型 GameSeat 新增副露管理方法,支持将碰升级为补杠 ResponseActionWindowBuilder 新增补杠响应窗口构建逻辑 SettlementService 新增补杠和抢杠胡结算规则 前端新增副露展示区域,支持显示各类副露标签
This commit is contained in:
@@ -12,6 +12,7 @@ public class GameSeat {
|
||||
private final String nickname;
|
||||
private final List<Tile> handTiles = new ArrayList<>();
|
||||
private final List<Tile> discardTiles = new ArrayList<>();
|
||||
private final List<MeldGroup> meldGroups = new ArrayList<>();
|
||||
private TileSuit lackSuit;
|
||||
private boolean won;
|
||||
private int score;
|
||||
@@ -51,6 +52,10 @@ public class GameSeat {
|
||||
return discardTiles;
|
||||
}
|
||||
|
||||
public List<MeldGroup> getMeldGroups() {
|
||||
return meldGroups;
|
||||
}
|
||||
|
||||
public TileSuit getLackSuit() {
|
||||
return lackSuit;
|
||||
}
|
||||
@@ -107,6 +112,34 @@ public class GameSeat {
|
||||
throw new IllegalStateException("弃牌区不存在指定牌");
|
||||
}
|
||||
|
||||
public void addPengMeld(Tile tile, int sourceSeatNo) {
|
||||
meldGroups.add(new MeldGroup(MeldType.PENG, tile, sourceSeatNo));
|
||||
}
|
||||
|
||||
public void addMingGangMeld(Tile tile, int sourceSeatNo) {
|
||||
meldGroups.add(new MeldGroup(MeldType.MING_GANG, tile, sourceSeatNo));
|
||||
}
|
||||
|
||||
public void upgradePengToBuGang(Tile tile) {
|
||||
for (int i = 0; i < meldGroups.size(); i++) {
|
||||
MeldGroup meldGroup = meldGroups.get(i);
|
||||
if (meldGroup.type() == MeldType.PENG && meldGroup.tile().equals(tile)) {
|
||||
meldGroups.set(i, new MeldGroup(MeldType.BU_GANG, tile, meldGroup.sourceSeatNo()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("当前没有可升级为补杠的碰副露");
|
||||
}
|
||||
|
||||
public boolean hasPengMeld(Tile tile) {
|
||||
return meldGroups.stream()
|
||||
.anyMatch(meldGroup -> meldGroup.type() == MeldType.PENG && meldGroup.tile().equals(tile));
|
||||
}
|
||||
|
||||
public void addAnGangMeld(Tile tile) {
|
||||
meldGroups.add(new MeldGroup(MeldType.AN_GANG, tile, null));
|
||||
}
|
||||
|
||||
public void declareHu() {
|
||||
this.won = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xuezhanmaster.game.domain;
|
||||
|
||||
public record MeldGroup(
|
||||
MeldType type,
|
||||
Tile tile,
|
||||
Integer sourceSeatNo
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xuezhanmaster.game.domain;
|
||||
|
||||
public enum MeldType {
|
||||
PENG,
|
||||
MING_GANG,
|
||||
BU_GANG,
|
||||
AN_GANG
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package com.xuezhanmaster.game.domain;
|
||||
|
||||
public enum SettlementType {
|
||||
DIAN_PAO_HU,
|
||||
QIANG_GANG_HU,
|
||||
ZI_MO_HU,
|
||||
MING_GANG
|
||||
BU_GANG,
|
||||
MING_GANG,
|
||||
AN_GANG
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ public record PublicSeatView(
|
||||
String lackSuit,
|
||||
int score,
|
||||
int handCount,
|
||||
List<String> discardTiles
|
||||
List<String> discardTiles,
|
||||
List<String> melds
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public record SelfSeatView(
|
||||
String lackSuit,
|
||||
int score,
|
||||
List<String> handTiles,
|
||||
List<String> discardTiles
|
||||
List<String> discardTiles,
|
||||
List<String> melds
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -50,6 +50,10 @@ public class GameActionProcessor {
|
||||
private List<GameEvent> gang(GameSession session, String userId, String tileDisplayName, Integer sourceSeatNo) {
|
||||
GameTable table = session.getTable();
|
||||
GameSeat seat = findSeatByUserId(table, userId);
|
||||
if (sourceSeatNo == null) {
|
||||
validateSelfGang(session, table, seat, tileDisplayName);
|
||||
return List.of();
|
||||
}
|
||||
validateResponseAction(session, table, seat, sourceSeatNo, tileDisplayName, "杠");
|
||||
return List.of();
|
||||
}
|
||||
@@ -219,6 +223,33 @@ public class GameActionProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSelfGang(GameSession session, GameTable table, GameSeat actorSeat, String tileDisplayName) {
|
||||
if (table.getPhase() != GamePhase.PLAYING) {
|
||||
throw new BusinessException("GAME_PHASE_INVALID", "当前阶段不允许暗杠");
|
||||
}
|
||||
if (actorSeat.isWon()) {
|
||||
throw new BusinessException("GAME_SEAT_ALREADY_WON", "已胡玩家不能继续杠牌");
|
||||
}
|
||||
if (actorSeat.getSeatNo() != table.getCurrentSeatNo()) {
|
||||
throw new BusinessException("GAME_TURN_INVALID", "当前不是该玩家的回合,不能暗杠");
|
||||
}
|
||||
if (session.getPendingResponseActionWindow() != null) {
|
||||
throw new BusinessException("GAME_ACTION_WINDOW_INVALID", "响应窗口期间不能执行暗杠");
|
||||
}
|
||||
if (isBlank(tileDisplayName)) {
|
||||
throw new BusinessException("GAME_ACTION_PARAM_INVALID", "暗杠动作缺少目标牌");
|
||||
}
|
||||
Tile tile = findTileInHand(actorSeat, tileDisplayName);
|
||||
long sameTileCount = actorSeat.getHandTiles().stream()
|
||||
.filter(handTile -> handTile.equals(tile))
|
||||
.count();
|
||||
boolean canConcealedGang = sameTileCount >= 4;
|
||||
boolean canSupplementalGang = sameTileCount >= 1 && actorSeat.hasPengMeld(tile);
|
||||
if (!canConcealedGang && !canSupplementalGang) {
|
||||
throw new BusinessException("GAME_ACTION_WINDOW_INVALID", "当前手牌不满足暗杠条件");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.isBlank();
|
||||
}
|
||||
|
||||
@@ -147,7 +147,8 @@ public class GameSessionService {
|
||||
self.getLackSuit() == null ? null : self.getLackSuit().name(),
|
||||
self.getScore(),
|
||||
self.getHandTiles().stream().map(Tile::getDisplayName).toList(),
|
||||
self.getDiscardTiles().stream().map(Tile::getDisplayName).toList()
|
||||
self.getDiscardTiles().stream().map(Tile::getDisplayName).toList(),
|
||||
toMeldLabels(self)
|
||||
),
|
||||
buildPublicSeats(table)
|
||||
);
|
||||
@@ -210,6 +211,30 @@ public class GameSessionService {
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<String> selfGangCandidate = findSelfGangCandidate(currentSeat);
|
||||
if (selfGangCandidate.isPresent()) {
|
||||
List<GameEvent> gangEvents = gameActionProcessor.process(
|
||||
session,
|
||||
new GameActionRequest(
|
||||
currentSeat.getPlayerId(),
|
||||
"GANG",
|
||||
selfGangCandidate.get(),
|
||||
null
|
||||
)
|
||||
);
|
||||
appendAndPublish(session, gangEvents);
|
||||
handlePostActionEffects(
|
||||
session,
|
||||
ActionType.GANG,
|
||||
new GameActionRequest(currentSeat.getPlayerId(), "GANG", selfGangCandidate.get(), null),
|
||||
gangEvents
|
||||
);
|
||||
if (table.getPhase() == GamePhase.FINISHED) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerVisibleGameState visibleState = playerVisibilityService.buildVisibleState(table, currentSeat);
|
||||
StrategyDecision decision = strategyService.evaluateDiscard(visibleState);
|
||||
List<GameEvent> events = gameActionProcessor.process(
|
||||
@@ -345,7 +370,8 @@ public class GameSessionService {
|
||||
|
||||
private void handleGangPostAction(GameSession session, GameActionRequest request) {
|
||||
if (request.sourceSeatNo() == null) {
|
||||
throw new BusinessException("GAME_ACTION_UNSUPPORTED", "当前仅支持响应明杠,主动杠将在后续迭代补充");
|
||||
handleSelfGangPostAction(session, request.userId(), request.tile());
|
||||
return;
|
||||
}
|
||||
handleResponseDeclarationPostAction(session, ActionType.GANG, request);
|
||||
}
|
||||
@@ -419,6 +445,10 @@ public class GameSessionService {
|
||||
}
|
||||
|
||||
closeResponseWindowAsPass(session, responseActionWindow);
|
||||
if (isSupplementalGangWindow(responseActionWindow)) {
|
||||
executeSupplementalGang(session, responseActionWindow.sourceSeatNo(), responseActionWindow.triggerTile());
|
||||
return;
|
||||
}
|
||||
continueAfterDiscardWithoutResponse(session);
|
||||
}
|
||||
|
||||
@@ -479,7 +509,7 @@ public class GameSessionService {
|
||||
switch (resolution.actionType()) {
|
||||
case PENG -> executePeng(session, resolution);
|
||||
case GANG -> executeGang(session, resolution);
|
||||
case HU -> executeHu(session, resolution);
|
||||
case HU -> executeHu(session, responseActionWindow, resolution);
|
||||
default -> throw new BusinessException("GAME_ACTION_INVALID", "未知的响应裁决结果");
|
||||
}
|
||||
}
|
||||
@@ -503,6 +533,7 @@ public class GameSessionService {
|
||||
|
||||
winnerSeat.removeMatchingHandTiles(claimedTile, 2);
|
||||
sourceSeat.removeMatchingDiscardTile(claimedTile);
|
||||
winnerSeat.addPengMeld(claimedTile, sourceSeat.getSeatNo());
|
||||
table.setCurrentSeatNo(winnerSeat.getSeatNo());
|
||||
|
||||
appendAndPublish(session, GameEvent.responseActionDeclared(
|
||||
@@ -524,6 +555,7 @@ public class GameSessionService {
|
||||
|
||||
winnerSeat.removeMatchingHandTiles(claimedTile, 3);
|
||||
sourceSeat.removeMatchingDiscardTile(claimedTile);
|
||||
winnerSeat.addMingGangMeld(claimedTile, sourceSeat.getSeatNo());
|
||||
table.setCurrentSeatNo(winnerSeat.getSeatNo());
|
||||
|
||||
appendAndPublish(session, GameEvent.responseActionDeclared(
|
||||
@@ -562,14 +594,22 @@ public class GameSessionService {
|
||||
notifyActionIfHumanTurn(session);
|
||||
}
|
||||
|
||||
private void executeHu(GameSession session, ResponseActionResolution resolution) {
|
||||
private void executeHu(
|
||||
GameSession session,
|
||||
ResponseActionWindow responseActionWindow,
|
||||
ResponseActionResolution resolution
|
||||
) {
|
||||
Tile claimedTile = parseTile(resolution.triggerTile());
|
||||
GameTable table = session.getTable();
|
||||
GameSeat winnerSeat = table.getSeats().get(resolution.winnerSeatNo());
|
||||
GameSeat sourceSeat = table.getSeats().get(resolution.sourceSeatNo());
|
||||
|
||||
winnerSeat.receiveTile(claimedTile);
|
||||
sourceSeat.removeMatchingDiscardTile(claimedTile);
|
||||
if (isSupplementalGangWindow(responseActionWindow)) {
|
||||
sourceSeat.removeMatchingHandTiles(claimedTile, 1);
|
||||
} else {
|
||||
sourceSeat.removeMatchingDiscardTile(claimedTile);
|
||||
}
|
||||
winnerSeat.declareHu();
|
||||
|
||||
appendAndPublish(session, GameEvent.responseActionDeclared(
|
||||
@@ -579,12 +619,21 @@ public class GameSessionService {
|
||||
sourceSeat.getSeatNo(),
|
||||
claimedTile.getDisplayName()
|
||||
));
|
||||
appendSettlementEvents(session, settlementService.settleDiscardHu(
|
||||
table,
|
||||
winnerSeat.getSeatNo(),
|
||||
sourceSeat.getSeatNo(),
|
||||
claimedTile.getDisplayName()
|
||||
));
|
||||
if (isSupplementalGangWindow(responseActionWindow)) {
|
||||
appendSettlementEvents(session, settlementService.settleRobbingGangHu(
|
||||
table,
|
||||
winnerSeat.getSeatNo(),
|
||||
sourceSeat.getSeatNo(),
|
||||
claimedTile.getDisplayName()
|
||||
));
|
||||
} else {
|
||||
appendSettlementEvents(session, settlementService.settleDiscardHu(
|
||||
table,
|
||||
winnerSeat.getSeatNo(),
|
||||
sourceSeat.getSeatNo(),
|
||||
claimedTile.getDisplayName()
|
||||
));
|
||||
}
|
||||
|
||||
if (shouldFinishTable(table)) {
|
||||
table.setPhase(GamePhase.FINISHED);
|
||||
@@ -624,6 +673,102 @@ public class GameSessionService {
|
||||
notifyActionIfHumanTurn(session);
|
||||
}
|
||||
|
||||
private void executeSelfGang(GameSession session, String userId, String tileDisplayName) {
|
||||
GameTable table = session.getTable();
|
||||
GameSeat winnerSeat = findSeatByUserId(table, userId);
|
||||
Tile gangTile = parseTile(tileDisplayName);
|
||||
|
||||
winnerSeat.removeMatchingHandTiles(gangTile, 4);
|
||||
winnerSeat.addAnGangMeld(gangTile);
|
||||
table.setCurrentSeatNo(winnerSeat.getSeatNo());
|
||||
|
||||
appendAndPublish(session, GameEvent.responseActionDeclared(
|
||||
session.getGameId(),
|
||||
GameEventType.GANG_DECLARED,
|
||||
winnerSeat.getSeatNo(),
|
||||
null,
|
||||
gangTile.getDisplayName()
|
||||
));
|
||||
appendSettlementEvents(session, settlementService.settleConcealedGang(
|
||||
table,
|
||||
winnerSeat.getSeatNo(),
|
||||
gangTile.getDisplayName()
|
||||
));
|
||||
|
||||
if (table.getWallTiles().isEmpty()) {
|
||||
table.setPhase(GamePhase.FINISHED);
|
||||
appendAndPublish(session, GameEvent.phaseChanged(session.getGameId(), table.getPhase().name()));
|
||||
return;
|
||||
}
|
||||
|
||||
Tile drawnTile = table.getWallTiles().remove(0);
|
||||
winnerSeat.receiveTile(drawnTile);
|
||||
appendAndPublish(session, GameEvent.tileDrawn(session.getGameId(), winnerSeat.getSeatNo(), table.getWallTiles().size()));
|
||||
appendAndPublish(session, GameEvent.turnSwitched(session.getGameId(), winnerSeat.getSeatNo()));
|
||||
continueFromResolvedActionTurn(session, winnerSeat);
|
||||
}
|
||||
|
||||
private void handleSelfGangPostAction(GameSession session, String userId, String tileDisplayName) {
|
||||
GameTable table = session.getTable();
|
||||
GameSeat actorSeat = findSeatByUserId(table, userId);
|
||||
Tile gangTile = parseTile(tileDisplayName);
|
||||
|
||||
if (actorSeat.hasPengMeld(gangTile)) {
|
||||
Optional<ResponseActionWindow> window = responseActionWindowBuilder.buildForSupplementalGang(
|
||||
session,
|
||||
actorSeat.getSeatNo(),
|
||||
gangTile
|
||||
);
|
||||
if (window.isPresent()) {
|
||||
openResponseWindow(session, window.get());
|
||||
autoPassAiCandidates(session);
|
||||
if (session.getPendingResponseActionWindow() == null) {
|
||||
executeSupplementalGang(session, actorSeat.getSeatNo(), tileDisplayName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
executeSupplementalGang(session, actorSeat.getSeatNo(), tileDisplayName);
|
||||
return;
|
||||
}
|
||||
|
||||
executeSelfGang(session, userId, tileDisplayName);
|
||||
}
|
||||
|
||||
private void executeSupplementalGang(GameSession session, int seatNo, String tileDisplayName) {
|
||||
GameTable table = session.getTable();
|
||||
GameSeat winnerSeat = table.getSeats().get(seatNo);
|
||||
Tile gangTile = parseTile(tileDisplayName);
|
||||
|
||||
winnerSeat.removeMatchingHandTiles(gangTile, 1);
|
||||
winnerSeat.upgradePengToBuGang(gangTile);
|
||||
table.setCurrentSeatNo(winnerSeat.getSeatNo());
|
||||
|
||||
appendAndPublish(session, GameEvent.responseActionDeclared(
|
||||
session.getGameId(),
|
||||
GameEventType.GANG_DECLARED,
|
||||
winnerSeat.getSeatNo(),
|
||||
null,
|
||||
gangTile.getDisplayName()
|
||||
));
|
||||
appendSettlementEvents(session, settlementService.settleSupplementalGang(
|
||||
table,
|
||||
winnerSeat.getSeatNo(),
|
||||
gangTile.getDisplayName()
|
||||
));
|
||||
|
||||
if (table.getWallTiles().isEmpty()) {
|
||||
table.setPhase(GamePhase.FINISHED);
|
||||
appendAndPublish(session, GameEvent.phaseChanged(session.getGameId(), table.getPhase().name()));
|
||||
return;
|
||||
}
|
||||
|
||||
Tile drawnTile = table.getWallTiles().remove(0);
|
||||
winnerSeat.receiveTile(drawnTile);
|
||||
appendAndPublish(session, GameEvent.tileDrawn(session.getGameId(), winnerSeat.getSeatNo(), table.getWallTiles().size()));
|
||||
appendAndPublish(session, GameEvent.turnSwitched(session.getGameId(), winnerSeat.getSeatNo()));
|
||||
continueFromResolvedActionTurn(session, winnerSeat);
|
||||
}
|
||||
|
||||
private ActionType parseActionType(String actionType) {
|
||||
try {
|
||||
return ActionType.valueOf(actionType.toUpperCase(Locale.ROOT));
|
||||
@@ -643,7 +788,8 @@ public class GameSessionService {
|
||||
seat.getLackSuit() == null ? null : seat.getLackSuit().name(),
|
||||
seat.getScore(),
|
||||
seat.getHandTiles().size(),
|
||||
seat.getDiscardTiles().stream().map(Tile::getDisplayName).toList()
|
||||
seat.getDiscardTiles().stream().map(Tile::getDisplayName).toList(),
|
||||
toMeldLabels(seat)
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
@@ -669,6 +815,10 @@ public class GameSessionService {
|
||||
return huEvaluator.canHu(seat.getHandTiles());
|
||||
}
|
||||
|
||||
private boolean isSupplementalGangWindow(ResponseActionWindow responseActionWindow) {
|
||||
return "SUPPLEMENTAL_GANG_DECLARED".equals(responseActionWindow.triggerEventType());
|
||||
}
|
||||
|
||||
private long countActiveSeats(GameTable table) {
|
||||
return table.getSeats().stream()
|
||||
.filter(seat -> !seat.isWon())
|
||||
@@ -691,7 +841,39 @@ public class GameSessionService {
|
||||
if (canSelfDrawHu(currentSeat)) {
|
||||
candidates.add(new PrivateActionCandidate(ActionType.HU.name(), null));
|
||||
}
|
||||
for (String gangTile : findSelfGangCandidates(currentSeat)) {
|
||||
candidates.add(new PrivateActionCandidate(ActionType.GANG.name(), gangTile));
|
||||
}
|
||||
candidates.add(new PrivateActionCandidate(ActionType.DISCARD.name(), null));
|
||||
return List.copyOf(candidates);
|
||||
}
|
||||
|
||||
private Optional<String> findSelfGangCandidate(GameSeat seat) {
|
||||
List<String> candidates = findSelfGangCandidates(seat);
|
||||
if (candidates.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(candidates.get(0));
|
||||
}
|
||||
|
||||
private List<String> findSelfGangCandidates(GameSeat seat) {
|
||||
return seat.getHandTiles().stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(Tile::getDisplayName, java.util.stream.Collectors.counting()))
|
||||
.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() >= 4 || seat.hasPengMeld(parseTile(entry.getKey())))
|
||||
.map(java.util.Map.Entry::getKey)
|
||||
.sorted()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<String> toMeldLabels(GameSeat seat) {
|
||||
return seat.getMeldGroups().stream()
|
||||
.map(meld -> switch (meld.type()) {
|
||||
case PENG -> "碰:" + meld.tile().getDisplayName();
|
||||
case MING_GANG -> "明杠:" + meld.tile().getDisplayName();
|
||||
case BU_GANG -> "补杠:" + meld.tile().getDisplayName();
|
||||
case AN_GANG -> "暗杠:" + meld.tile().getDisplayName();
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,40 @@ public class ResponseActionWindowBuilder {
|
||||
));
|
||||
}
|
||||
|
||||
public Optional<ResponseActionWindow> buildForSupplementalGang(GameSession session, int sourceSeatNo, Tile gangTile) {
|
||||
GameTable table = session.getTable();
|
||||
List<ResponseActionSeatCandidate> seatCandidates = new ArrayList<>();
|
||||
|
||||
for (GameSeat seat : table.getSeats()) {
|
||||
if (seat.getSeatNo() == sourceSeatNo || seat.isWon()) {
|
||||
continue;
|
||||
}
|
||||
if (!huEvaluator.canHuWithClaimedTile(seat.getHandTiles(), gangTile)) {
|
||||
continue;
|
||||
}
|
||||
seatCandidates.add(new ResponseActionSeatCandidate(
|
||||
seat.getSeatNo(),
|
||||
seat.getPlayerId(),
|
||||
List.of(
|
||||
new ResponseActionOption(ActionType.HU, gangTile.getDisplayName()),
|
||||
new ResponseActionOption(ActionType.PASS, null)
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
if (seatCandidates.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(ResponseActionWindow.create(
|
||||
session.getGameId(),
|
||||
"SUPPLEMENTAL_GANG_DECLARED",
|
||||
sourceSeatNo,
|
||||
gangTile.getDisplayName(),
|
||||
seatCandidates
|
||||
));
|
||||
}
|
||||
|
||||
private List<ResponseActionOption> buildSeatOptions(GameSeat seat, Tile discardedTile) {
|
||||
int sameTileCount = countSameTileInHand(seat, discardedTile);
|
||||
boolean canHu = huEvaluator.canHuWithClaimedTile(seat.getHandTiles(), discardedTile);
|
||||
|
||||
@@ -18,7 +18,9 @@ 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;
|
||||
|
||||
public SettlementResult settleDiscardHu(
|
||||
GameTable table,
|
||||
@@ -80,6 +82,75 @@ public class SettlementService {
|
||||
);
|
||||
}
|
||||
|
||||
public SettlementResult settleRobbingGangHu(
|
||||
GameTable table,
|
||||
int winnerSeatNo,
|
||||
int sourceSeatNo,
|
||||
String triggerTile
|
||||
) {
|
||||
return applySettlement(
|
||||
table,
|
||||
SettlementType.QIANG_GANG_HU,
|
||||
ActionType.HU,
|
||||
winnerSeatNo,
|
||||
sourceSeatNo,
|
||||
triggerTile,
|
||||
orderedDeltas(winnerSeatNo, DIAN_PAO_HU_SCORE, sourceSeatNo, -DIAN_PAO_HU_SCORE)
|
||||
);
|
||||
}
|
||||
|
||||
public SettlementResult settleSupplementalGang(
|
||||
GameTable table,
|
||||
int winnerSeatNo,
|
||||
String triggerTile
|
||||
) {
|
||||
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(winnerSeatNo, totalWinScore);
|
||||
return applySettlement(
|
||||
table,
|
||||
SettlementType.BU_GANG,
|
||||
ActionType.GANG,
|
||||
winnerSeatNo,
|
||||
winnerSeatNo,
|
||||
triggerTile,
|
||||
scoreDeltas
|
||||
);
|
||||
}
|
||||
|
||||
public SettlementResult settleConcealedGang(
|
||||
GameTable table,
|
||||
int winnerSeatNo,
|
||||
String triggerTile
|
||||
) {
|
||||
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(winnerSeatNo, totalWinScore);
|
||||
return applySettlement(
|
||||
table,
|
||||
SettlementType.AN_GANG,
|
||||
ActionType.GANG,
|
||||
winnerSeatNo,
|
||||
winnerSeatNo,
|
||||
triggerTile,
|
||||
scoreDeltas
|
||||
);
|
||||
}
|
||||
|
||||
private SettlementResult applySettlement(
|
||||
GameTable table,
|
||||
SettlementType settlementType,
|
||||
|
||||
@@ -14,10 +14,14 @@ public class PlayerVisibilityService {
|
||||
|
||||
public PlayerVisibleGameState buildVisibleState(GameTable table, GameSeat seat) {
|
||||
List<String> publicDiscards = new ArrayList<>();
|
||||
List<String> publicMelds = new ArrayList<>();
|
||||
for (GameSeat gameSeat : table.getSeats()) {
|
||||
for (Tile tile : gameSeat.getDiscardTiles()) {
|
||||
publicDiscards.add(gameSeat.getSeatNo() + ":" + tile.getDisplayName());
|
||||
}
|
||||
gameSeat.getMeldGroups().forEach(meld ->
|
||||
publicMelds.add(gameSeat.getSeatNo() + ":" + meld.type().name() + ":" + meld.tile().getDisplayName())
|
||||
);
|
||||
}
|
||||
|
||||
return new PlayerVisibleGameState(
|
||||
@@ -26,8 +30,7 @@ public class PlayerVisibilityService {
|
||||
seat.getLackSuit(),
|
||||
List.copyOf(seat.getHandTiles()),
|
||||
publicDiscards,
|
||||
List.of()
|
||||
publicMelds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user