From 95d98fb7215c240e54ce51ade0aed7b1b7e49880 Mon Sep 17 00:00:00 2001 From: trunksbomb Date: Mon, 23 Mar 2026 17:58:44 -0400 Subject: [PATCH] add Same/Same Wall/Plus rules logic. --- .../blockentity/DuelTableBlockEntity.java | 8 +- .../trunksbomb/minetriad/game/MoveResult.java | 14 +- .../trunksbomb/minetriad/game/TriadMatch.java | 166 ++++++++++++++++-- .../minetriad/game/TriadRuleSet.java | 3 +- 4 files changed, 172 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/trunksbomb/minetriad/blockentity/DuelTableBlockEntity.java b/src/main/java/com/trunksbomb/minetriad/blockentity/DuelTableBlockEntity.java index a2fa219..1e612d2 100644 --- a/src/main/java/com/trunksbomb/minetriad/blockentity/DuelTableBlockEntity.java +++ b/src/main/java/com/trunksbomb/minetriad/blockentity/DuelTableBlockEntity.java @@ -372,11 +372,13 @@ public class DuelTableBlockEntity extends BlockEntity { } pendingAnimations.addLast(new AnimationStep(AnimationType.PLACEMENT, new int[] {moveResult.playedCell().index()}, owner)); - if (!moveResult.capturedCells().isEmpty()) { - int[] captureSlots = moveResult.capturedCells().stream() + for (List captureWave : moveResult.captureWaves()) { + int[] captureSlots = captureWave.stream() .mapToInt(BoardCell::index) .toArray(); - pendingAnimations.addLast(new AnimationStep(AnimationType.CAPTURE, captureSlots, owner)); + if (captureSlots.length > 0) { + pendingAnimations.addLast(new AnimationStep(AnimationType.CAPTURE, captureSlots, owner)); + } } advanceAnimationStep(); } diff --git a/src/main/java/com/trunksbomb/minetriad/game/MoveResult.java b/src/main/java/com/trunksbomb/minetriad/game/MoveResult.java index 21e01ab..b959119 100644 --- a/src/main/java/com/trunksbomb/minetriad/game/MoveResult.java +++ b/src/main/java/com/trunksbomb/minetriad/game/MoveResult.java @@ -7,21 +7,29 @@ import net.minecraft.resources.ResourceLocation; public record MoveResult( boolean valid, String errorMessage, + List> captureWaves, List capturedCells, List battleLog, String playedCardName, ResourceLocation playedCardId, BoardCell playedCell) { public static MoveResult success( - List capturedCells, + List> captureWaves, List battleLog, String playedCardName, ResourceLocation playedCardId, BoardCell playedCell) { - return new MoveResult(true, "", List.copyOf(capturedCells), List.copyOf(battleLog), playedCardName, playedCardId, playedCell); + List> immutableWaves = captureWaves.stream() + .map(List::copyOf) + .toList(); + List flattenedCaptures = immutableWaves.stream() + .flatMap(List::stream) + .distinct() + .toList(); + return new MoveResult(true, "", immutableWaves, flattenedCaptures, List.copyOf(battleLog), playedCardName, playedCardId, playedCell); } public static MoveResult failure(String errorMessage) { - return new MoveResult(false, errorMessage, List.of(), List.of(), "", null, null); + return new MoveResult(false, errorMessage, List.of(), List.of(), List.of(), "", null, null); } } diff --git a/src/main/java/com/trunksbomb/minetriad/game/TriadMatch.java b/src/main/java/com/trunksbomb/minetriad/game/TriadMatch.java index 6add61b..11fe1b3 100644 --- a/src/main/java/com/trunksbomb/minetriad/game/TriadMatch.java +++ b/src/main/java/com/trunksbomb/minetriad/game/TriadMatch.java @@ -2,7 +2,11 @@ package com.trunksbomb.minetriad.game; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; public final class TriadMatch { private final MatchParticipant firstParticipant; @@ -75,14 +79,111 @@ public final class TriadMatch { board[move.targetCell().index()] = new PlacedCard(move.participant(), playedCard); List battleLog = new ArrayList<>(); - List capturedCells = resolveCaptures(move.targetCell(), move.participant(), playedCard, battleLog); + CaptureResolution captureResolution = resolveCaptures(move.targetCell(), move.participant(), playedCard, battleLog); activeParticipant = otherParticipant(move.participant()); - return MoveResult.success(capturedCells, battleLog, playedCard.definition().name().getString(), playedCard.definition().id(), move.targetCell()); + return MoveResult.success(captureResolution.captureWaves(), battleLog, playedCard.definition().name().getString(), playedCard.definition().id(), move.targetCell()); } - private List resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List battleLog) { - List captured = new ArrayList<>(); + private CaptureResolution resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List battleLog) { + List comparisons = buildComparisons(origin, owner, playedCard, battleLog); + LinkedHashSet firstWave = new LinkedHashSet<>(); + LinkedHashSet comboSeeds = new LinkedHashSet<>(); + for (SideComparison comparison : comparisons) { + if (!comparison.neighbor().owner().equals(owner) && comparison.attackValue() > comparison.defendValue()) { + firstWave.add(comparison.cell()); + } + } + + if (ruleSet.sameRule()) { + List sameCandidates = buildSameCandidates(origin, playedCard, battleLog); + long sameMatches = sameCandidates.stream().filter(SameCandidate::matches).count(); + if (sameMatches >= 2) { + List sameCaptures = sameCandidates.stream() + .filter(SameCandidate::matches) + .map(SameCandidate::cell) + .filter(cell -> cell != null) + .distinct() + .filter(cell -> { + PlacedCard placedCard = cardAt(cell); + return placedCard != null && !placedCard.owner().equals(owner); + }) + .toList(); + if (!sameCaptures.isEmpty()) { + battleLog.add("Same triggers."); + firstWave.addAll(sameCaptures); + comboSeeds.addAll(sameCaptures); + } + } + } + + if (ruleSet.plusRule()) { + Map> plusGroups = new LinkedHashMap<>(); + for (SideComparison comparison : comparisons) { + int sum = comparison.attackValue() + comparison.defendValue(); + plusGroups.computeIfAbsent(sum, ignored -> new ArrayList<>()).add(comparison); + } + for (Map.Entry> entry : plusGroups.entrySet()) { + if (entry.getValue().size() < 2) { + continue; + } + List plusCaptures = entry.getValue().stream() + .map(SideComparison::cell) + .distinct() + .filter(cell -> { + PlacedCard placedCard = cardAt(cell); + return placedCard != null && !placedCard.owner().equals(owner); + }) + .toList(); + if (!plusCaptures.isEmpty()) { + battleLog.add("Plus triggers on sum " + entry.getKey() + "."); + firstWave.addAll(plusCaptures); + comboSeeds.addAll(plusCaptures); + } + } + } + + List> waves = new ArrayList<>(); + if (!firstWave.isEmpty()) { + applyOwnership(owner, firstWave); + waves.add(List.copyOf(firstWave)); + } + + if (!comboSeeds.isEmpty()) { + LinkedHashSet comboWave = new LinkedHashSet<>(); + for (BoardCell comboCell : comboSeeds) { + PlacedCard comboCard = cardAt(comboCell); + if (comboCard == null || !comboCard.owner().equals(owner)) { + continue; + } + for (CardSide side : CardSide.values()) { + BoardCell neighborCell = neighbor(comboCell, side); + if (neighborCell == null) { + continue; + } + PlacedCard neighbor = cardAt(neighborCell); + if (neighbor == null || neighbor.owner().equals(owner)) { + continue; + } + int attackValue = comboCard.card().value(side); + int defendValue = neighbor.card().value(side.opposite()); + if (attackValue > defendValue) { + comboWave.add(neighborCell); + } + } + } + if (!comboWave.isEmpty()) { + battleLog.add("Combo triggers."); + applyOwnership(owner, comboWave); + waves.add(List.copyOf(comboWave)); + } + } + + return new CaptureResolution(waves); + } + + private List buildComparisons(BoardCell origin, MatchParticipant owner, GameCard playedCard, List battleLog) { + List comparisons = new ArrayList<>(); for (CardSide side : CardSide.values()) { BoardCell neighborCell = neighbor(origin, side); if (neighborCell == null) { @@ -90,13 +191,12 @@ public final class TriadMatch { } PlacedCard neighbor = cardAt(neighborCell); - if (neighbor == null || neighbor.owner().equals(owner)) { + if (neighbor == null) { continue; } int attackValue = playedCard.value(side); int defendValue = neighbor.card().value(side.opposite()); - boolean capturedNeighbor = attackValue > defendValue; battleLog.add(String.format( "%s %s=%d vs %s %s=%d at [%d,%d] -> %s", playedCard.definition().name().getString(), @@ -107,14 +207,47 @@ public final class TriadMatch { defendValue, neighborCell.row(), neighborCell.column(), - capturedNeighbor ? "flip" : "hold")); - if (capturedNeighbor) { - board[neighborCell.index()] = new PlacedCard(owner, neighbor.card()); - captured.add(neighborCell); + !neighbor.owner().equals(owner) && attackValue > defendValue ? "flip" : "hold")); + comparisons.add(new SideComparison(side, neighborCell, neighbor, attackValue, defendValue)); + } + return comparisons; + } + + private List buildSameCandidates(BoardCell origin, GameCard playedCard, List battleLog) { + List candidates = new ArrayList<>(); + for (CardSide side : CardSide.values()) { + BoardCell neighborCell = neighbor(origin, side); + if (neighborCell == null) { + boolean wallMatch = ruleSet.sameWallRule() && playedCard.value(side) == 10; + battleLog.add(String.format( + "%s %s=%d vs WALL=%d -> %s", + playedCard.definition().name().getString(), + side.name(), + playedCard.value(side), + 10, + wallMatch ? "same" : "ignore")); + candidates.add(new SameCandidate(side, null, wallMatch)); + continue; + } + + PlacedCard neighbor = cardAt(neighborCell); + if (neighbor == null) { + continue; + } + + boolean sameMatch = playedCard.value(side) == neighbor.card().value(side.opposite()); + candidates.add(new SameCandidate(side, neighborCell, sameMatch)); + } + return candidates; + } + + private void applyOwnership(MatchParticipant owner, Set capturedCells) { + for (BoardCell cell : capturedCells) { + PlacedCard placedCard = cardAt(cell); + if (placedCard != null) { + board[cell.index()] = new PlacedCard(owner, placedCard.card()); } } - - return captured; } private BoardCell neighbor(BoardCell cell, CardSide side) { @@ -167,4 +300,13 @@ public final class TriadMatch { void forceActiveParticipant(MatchParticipant participant) { this.activeParticipant = participant; } + + private record SideComparison(CardSide side, BoardCell cell, PlacedCard neighbor, int attackValue, int defendValue) { + } + + private record SameCandidate(CardSide side, BoardCell cell, boolean matches) { + } + + private record CaptureResolution(List> captureWaves) { + } } diff --git a/src/main/java/com/trunksbomb/minetriad/game/TriadRuleSet.java b/src/main/java/com/trunksbomb/minetriad/game/TriadRuleSet.java index 14e3859..b8bc611 100644 --- a/src/main/java/com/trunksbomb/minetriad/game/TriadRuleSet.java +++ b/src/main/java/com/trunksbomb/minetriad/game/TriadRuleSet.java @@ -3,7 +3,8 @@ package com.trunksbomb.minetriad.game; public record TriadRuleSet( boolean openHands, boolean sameRule, + boolean sameWallRule, boolean plusRule, boolean elementalRule) { - public static final TriadRuleSet CLASSIC_OPEN = new TriadRuleSet(true, false, false, false); + public static final TriadRuleSet CLASSIC_OPEN = new TriadRuleSet(true, true, true, true, false); }