add Same/Same Wall/Plus rules logic.

This commit is contained in:
trunksbomb
2026-03-23 17:58:44 -04:00
parent b845d76cd0
commit 95d98fb721
4 changed files with 172 additions and 19 deletions

View File

@@ -372,12 +372,14 @@ 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<BoardCell> captureWave : moveResult.captureWaves()) {
int[] captureSlots = captureWave.stream()
.mapToInt(BoardCell::index)
.toArray();
if (captureSlots.length > 0) {
pendingAnimations.addLast(new AnimationStep(AnimationType.CAPTURE, captureSlots, owner));
}
}
advanceAnimationStep();
}

View File

@@ -7,21 +7,29 @@ import net.minecraft.resources.ResourceLocation;
public record MoveResult(
boolean valid,
String errorMessage,
List<List<BoardCell>> captureWaves,
List<BoardCell> capturedCells,
List<String> battleLog,
String playedCardName,
ResourceLocation playedCardId,
BoardCell playedCell) {
public static MoveResult success(
List<BoardCell> capturedCells,
List<List<BoardCell>> captureWaves,
List<String> battleLog,
String playedCardName,
ResourceLocation playedCardId,
BoardCell playedCell) {
return new MoveResult(true, "", List.copyOf(capturedCells), List.copyOf(battleLog), playedCardName, playedCardId, playedCell);
List<List<BoardCell>> immutableWaves = captureWaves.stream()
.map(List::copyOf)
.toList();
List<BoardCell> 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);
}
}

View File

@@ -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<String> battleLog = new ArrayList<>();
List<BoardCell> 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<BoardCell> resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
List<BoardCell> captured = new ArrayList<>();
private CaptureResolution resolveCaptures(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
List<SideComparison> comparisons = buildComparisons(origin, owner, playedCard, battleLog);
LinkedHashSet<BoardCell> firstWave = new LinkedHashSet<>();
LinkedHashSet<BoardCell> 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<SameCandidate> sameCandidates = buildSameCandidates(origin, playedCard, battleLog);
long sameMatches = sameCandidates.stream().filter(SameCandidate::matches).count();
if (sameMatches >= 2) {
List<BoardCell> 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<Integer, List<SideComparison>> plusGroups = new LinkedHashMap<>();
for (SideComparison comparison : comparisons) {
int sum = comparison.attackValue() + comparison.defendValue();
plusGroups.computeIfAbsent(sum, ignored -> new ArrayList<>()).add(comparison);
}
for (Map.Entry<Integer, List<SideComparison>> entry : plusGroups.entrySet()) {
if (entry.getValue().size() < 2) {
continue;
}
List<BoardCell> 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<List<BoardCell>> waves = new ArrayList<>();
if (!firstWave.isEmpty()) {
applyOwnership(owner, firstWave);
waves.add(List.copyOf(firstWave));
}
if (!comboSeeds.isEmpty()) {
LinkedHashSet<BoardCell> 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<SideComparison> buildComparisons(BoardCell origin, MatchParticipant owner, GameCard playedCard, List<String> battleLog) {
List<SideComparison> 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;
}
return captured;
private List<SameCandidate> buildSameCandidates(BoardCell origin, GameCard playedCard, List<String> battleLog) {
List<SameCandidate> 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<BoardCell> capturedCells) {
for (BoardCell cell : capturedCells) {
PlacedCard placedCard = cardAt(cell);
if (placedCard != null) {
board[cell.index()] = new PlacedCard(owner, placedCard.card());
}
}
}
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<List<BoardCell>> captureWaves) {
}
}

View File

@@ -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);
}