From 8e5d643b8d8218e412d3d34ae9fb13f684f26579 Mon Sep 17 00:00:00 2001 From: stanolacko Date: Fri, 9 Sep 2022 13:55:47 +0200 Subject: [PATCH 1/3] BlackDragon missing - set scoring on begin of double turn, place black dragon at end of score phase (before phase 4), at end of turn --- .../action/MoveBlackDragonAction.java | 19 +++ .../java/com/jcloisterzone/engine/Engine.java | 1 + .../engine/StateGsonBuilder.java | 32 +++++ .../java/com/jcloisterzone/figure/Barn.java | 5 + .../com/jcloisterzone/figure/Follower.java | 4 + .../java/com/jcloisterzone/figure/Meeple.java | 4 + .../figure/neutral/BlackDragon.java | 15 +++ .../capability/BlackDragonCapability.java | 74 +++++++++++ .../game/phase/BlackDragonMovePhase.java | 123 ++++++++++++++++++ .../game/phase/CleanUpTurnPartPhase.java | 1 + .../game/phase/ScoringPhase.java | 29 ++++- .../com/jcloisterzone/game/state/Flag.java | 2 +- .../game/state/NeutralFiguresState.java | 37 ++++-- 13 files changed, 333 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java create mode 100644 src/main/java/com/jcloisterzone/figure/neutral/BlackDragon.java create mode 100644 src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java create mode 100644 src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java diff --git a/src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java b/src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java new file mode 100644 index 000000000..b0c16dfc5 --- /dev/null +++ b/src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java @@ -0,0 +1,19 @@ +package com.jcloisterzone.action; + +import com.jcloisterzone.board.Position; +import io.vavr.collection.Set; + +//TODO generic NeutralMeepleAction ? +public class MoveBlackDragonAction extends SelectTileAction { + + private final String figureId; + + public MoveBlackDragonAction(String figureId, Set options) { + super(options); + this.figureId = figureId; + } + + public String getFigureId() { + return figureId; + } +} \ No newline at end of file diff --git a/src/main/java/com/jcloisterzone/engine/Engine.java b/src/main/java/com/jcloisterzone/engine/Engine.java index b706da3ae..73eaae7fb 100644 --- a/src/main/java/com/jcloisterzone/engine/Engine.java +++ b/src/main/java/com/jcloisterzone/engine/Engine.java @@ -148,6 +148,7 @@ private GameSetup createSetupFromMessage(GameSetupMessage setupMsg) { capabilities = addCapabilities(capabilities, setupMsg,"watchtower", WatchtowerCapability.class); capabilities = addCapabilities(capabilities, setupMsg,"robbers-son", RobbersSonCapability.class); + capabilities = addCapabilities(capabilities, setupMsg,"black-dragon", BlackDragonCapability.class); Map rules = HashMap.empty(); if (setupMsg.getElements().containsKey("farmers")) { diff --git a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java index 738abb87b..76d0933c9 100644 --- a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java +++ b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java @@ -11,12 +11,14 @@ import com.jcloisterzone.feature.Tower; import com.jcloisterzone.figure.Follower; import com.jcloisterzone.figure.Meeple; +import com.jcloisterzone.figure.neutral.BlackDragon; import com.jcloisterzone.figure.neutral.Dragon; import com.jcloisterzone.game.capability.*; import com.jcloisterzone.game.capability.FerriesCapability.FerryToken; import com.jcloisterzone.game.capability.GoldminesCapability.GoldToken; import com.jcloisterzone.game.capability.LittleBuildingsCapability.LittleBuilding; import com.jcloisterzone.game.capability.SheepToken; +import com.jcloisterzone.game.phase.BlackDragonMovePhase; import com.jcloisterzone.game.phase.DragonMovePhase; import com.jcloisterzone.game.phase.Phase; import com.jcloisterzone.game.phase.RussianPromosTrapPhase; @@ -70,6 +72,7 @@ public Gson create() { builder.registerTypeAdapter(RemoveMageOrWitchAction.class, new ActionSerializer("RemoveMageOrWitch")); builder.registerTypeAdapter(LittleBuildingAction.class, new LittleBuildingActionSerializer()); builder.registerTypeAdapter(ScoreAcrobatsAction.class, new SelectFeatureActionSerializer()); + builder.registerTypeAdapter(MoveBlackDragonAction.class, new MoveBlackDragonActionSerializer()); return builder.create(); } @@ -288,6 +291,19 @@ public JsonElement serializeNeutralFigures(GameState root, JsonSerializationCont data.add("placement", context.serialize(pos)); neutral.add("bigtop", data); } + pos = state.getBlackDragonDeployment(); + if (pos != null) { + Tuple2,Integer> blackdragonmoves = root.getCapabilityModel(BlackDragonCapability.class); + JsonObject data = new JsonObject(); + data.add("position", context.serialize(pos)); + if (root.getPhase() instanceof BlackDragonMovePhase) { + JsonArray visitedData = new JsonArray(); + blackdragonmoves._1.forEach(p -> visitedData.add(context.serialize(p))); + data.add("visited", visitedData); + data.addProperty("remaining", blackdragonmoves._2 - blackdragonmoves._1.length()); + } + neutral.add("blackdragon", data); + } return neutral; } @@ -897,4 +913,20 @@ public JsonElement serialize(LittleBuildingAction action, Type type, JsonSeriali return json; } } + + private class MoveBlackDragonActionSerializer implements JsonSerializer { + @Override + public JsonElement serialize(MoveBlackDragonAction action, Type type, JsonSerializationContext context) { + JsonObject json = new JsonObject(); + json.addProperty("type", "MoveDragon"); + json.addProperty("figureId", action.getFigureId()); + JsonArray options = new JsonArray(); + action.getOptions().forEach(pos -> { + options.add(context.serialize(pos)); + }); + json.add("options", options); + return json; + } + } + } diff --git a/src/main/java/com/jcloisterzone/figure/Barn.java b/src/main/java/com/jcloisterzone/figure/Barn.java index 4ec68ed2b..c04bd89c2 100644 --- a/src/main/java/com/jcloisterzone/figure/Barn.java +++ b/src/main/java/com/jcloisterzone/figure/Barn.java @@ -19,6 +19,11 @@ public boolean canBeEatenByDragon(GameState state) { return false; } + @Override + public boolean canBeEatenByBlackDragon(GameState state) { + return false; + } + @Override public DeploymentCheckResult isDeploymentAllowed(GameState state, FeaturePointer fp, Structure feature) { if (!(feature instanceof Field)) { diff --git a/src/main/java/com/jcloisterzone/figure/Follower.java b/src/main/java/com/jcloisterzone/figure/Follower.java index 4cf781517..92a85afbc 100644 --- a/src/main/java/com/jcloisterzone/figure/Follower.java +++ b/src/main/java/com/jcloisterzone/figure/Follower.java @@ -28,6 +28,10 @@ public boolean canBeEatenByDragon(GameState state) { return !(getFeature(state) instanceof Castle); } + @Override + public boolean canBeEatenByBlackDragon(GameState state) { + return !(getFeature(state) instanceof Castle); + } public boolean isCaptured(GameState state) { Array> model = state.getCapabilityModel(TowerCapability.class); return model != null && Stream.concat(model).find(f -> f == this).isDefined(); diff --git a/src/main/java/com/jcloisterzone/figure/Meeple.java b/src/main/java/com/jcloisterzone/figure/Meeple.java index 748fd32cc..65cd1f613 100644 --- a/src/main/java/com/jcloisterzone/figure/Meeple.java +++ b/src/main/java/com/jcloisterzone/figure/Meeple.java @@ -41,6 +41,10 @@ public boolean canBeEatenByDragon(GameState state) { return true; } + public boolean canBeEatenByBlackDragon(GameState state) { + return true; + } + public DeploymentCheckResult isDeploymentAllowed(GameState state, FeaturePointer fp, Structure feature) { return DeploymentCheckResult.OK; } diff --git a/src/main/java/com/jcloisterzone/figure/neutral/BlackDragon.java b/src/main/java/com/jcloisterzone/figure/neutral/BlackDragon.java new file mode 100644 index 000000000..996d979f6 --- /dev/null +++ b/src/main/java/com/jcloisterzone/figure/neutral/BlackDragon.java @@ -0,0 +1,15 @@ +package com.jcloisterzone.figure.neutral; + +import com.jcloisterzone.Immutable; +import com.jcloisterzone.board.Position; + +@Immutable +public class BlackDragon extends NeutralFigure { + + private static final long serialVersionUID = 1L; + + public BlackDragon(String id) { + super(id); + } + +} diff --git a/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java new file mode 100644 index 000000000..19b94349d --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java @@ -0,0 +1,74 @@ +package com.jcloisterzone.game.capability; + +import com.jcloisterzone.Immutable; +import com.jcloisterzone.board.Position; +import com.jcloisterzone.board.pointer.FeaturePointer; +import com.jcloisterzone.feature.Completable; +import com.jcloisterzone.figure.Meeple; +import com.jcloisterzone.figure.neutral.BlackDragon; +import com.jcloisterzone.game.Capability; +import com.jcloisterzone.game.state.GameState; +import com.jcloisterzone.reducers.MoveNeutralFigure; +import com.jcloisterzone.reducers.UndeployMeeple; + +import io.vavr.collection.Vector; +import io.vavr.Tuple2; + +/** + * @model Tuple2,Integer> : visited tiles, finished features + */ +@Immutable +public class BlackDragonCapability extends Capability,Integer>> { + + private static final long serialVersionUID = 1L; + + @Override + public GameState onStartGame(GameState state) { + System.out.println("\n"); + System.out.println("BlackDragon is running"); + System.out.println("\n"); + state = state.mapNeutralFigures(nf -> nf.setBlackDragon(new BlackDragon("blackdragon.1"))); + state = setModel(state, new Tuple2<>(Vector.empty(),0)); + return state; + } + + @Override + public boolean isMeepleDeploymentAllowed(GameState state, Position pos) { + return !pos.equals(state.getNeutralFigures().getBlackDragonDeployment()); + } + + @Override + public GameState beforeCompletableScore(GameState state, java.util.Set features) { + System.out.println("\n"); + System.out.println("BeforeCompletable"); + System.out.println(features.size()); + System.out.println("\n"); + state = setModel(state, new Tuple2<>(Vector.empty(),features.size())); + +// Position pos = state.getLastPlaced().getPosition(); +// state = moveBlackDragon(state, pos); + return state; + } + + public GameState moveBlackDragon(GameState state, Position pos) { + state = ( + new MoveNeutralFigure<>(state.getNeutralFigures().getBlackDragon(), pos) + ).apply(state); + + state = blackDragonOnTile(state, pos); + return state; + + } + + public GameState blackDragonOnTile(GameState state, Position pos) { + for (Tuple2 t: state.getDeployedMeeples()) { + Meeple m = t._1; + FeaturePointer fp = t._2; + if (pos.equals(fp.getPosition()) && m.canBeEatenByBlackDragon(state)) { + state = (new UndeployMeeple(m, true)).apply(state); + } + } + + return state; + } +} diff --git a/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java new file mode 100644 index 000000000..fc24f70c7 --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java @@ -0,0 +1,123 @@ +package com.jcloisterzone.game.phase; + +import com.jcloisterzone.Player; +import com.jcloisterzone.action.MoveBlackDragonAction; +import com.jcloisterzone.board.Position; +import com.jcloisterzone.board.pointer.BoardPointer; +import com.jcloisterzone.figure.neutral.BlackDragon; +import com.jcloisterzone.figure.neutral.NeutralFigure; +import com.jcloisterzone.game.capability.CountCapability; +import com.jcloisterzone.game.capability.BlackDragonCapability; +import com.jcloisterzone.game.state.ActionsState; +import com.jcloisterzone.game.state.Flag; +import com.jcloisterzone.game.state.GameState; +import com.jcloisterzone.game.state.PlacedTile; +import com.jcloisterzone.io.message.MoveNeutralFigureMessage; +import com.jcloisterzone.random.RandomGenerator; +import com.jcloisterzone.reducers.MoveNeutralFigure; +import io.vavr.Tuple2; +import io.vavr.collection.HashSet; +import io.vavr.collection.Set; +import io.vavr.collection.Vector; + +public class BlackDragonMovePhase extends Phase { + + public BlackDragonMovePhase(RandomGenerator random, Phase defaultNext) { + super(random, defaultNext); + } + + private Vector getVisitedPositions(GameState state) { + Tuple2,Integer> blackdragonmoves = state.getCapabilityModel(BlackDragonCapability.class); + return blackdragonmoves._1 == null ? Vector.empty() : blackdragonmoves._1; + } + + private Integer getMoves(GameState state) { + Tuple2,Integer> blackdragonmoves = state.getCapabilityModel(BlackDragonCapability.class); + return blackdragonmoves._2 == null ? 0 : blackdragonmoves._2; + } + + @Override + public StepResult enter(GameState state) { + Vector visited = getVisitedPositions(state); + Integer moves = getMoves(state); + System.out.println("\n"); +System.out.println(visited); +System.out.println(moves); +System.out.println("\n"); + if (visited.size() == moves) { + return next(endBlackDragonMove(state)); + } + + Set availMoves = getAvailBlackDragonMoves(state, visited); + if (availMoves.isEmpty()) { + return next(endBlackDragonMove(state)); + } + + BlackDragon blackdragon = state.getNeutralFigures().getBlackDragon(); + + System.out.println("AvailMoves"); + System.out.println(availMoves); + System.out.println("\n"); + return promote(state.setPlayerActions( + new ActionsState(state.getTurnPlayer(), new MoveBlackDragonAction(blackdragon.getId(), availMoves), false) + )); + } + + private GameState endBlackDragonMove(GameState state) { + state = state.addFlag(Flag.BLACK_DRAGON_MOVED); + state = state.setCapabilityModel(BlackDragonCapability.class, new Tuple2<>(Vector.empty(),0)); + state = clearActions(state); + return state; + } + + + public Set getAvailBlackDragonMoves(GameState state, Vector visited) { + Set result = HashSet.empty(); + Position blackdragonPosition = state.getNeutralFigures().getBlackDragonDeployment(); + + if (blackdragonPosition != null) { + for (Position offset: Position.ADJACENT.values()) { + Position pos = blackdragonPosition.add(offset); + PlacedTile pt = state.getPlacedTile(pos); + + if (pt == null || CountCapability.isTileForbidden(pt.getTile())) continue; + if (visited.contains(pos)) continue; + + result = result.add(pos); + } + } + return result; + } + + @PhaseMessageHandler + public StepResult handleMoveNeutralFigure(GameState state, MoveNeutralFigureMessage msg) { + BoardPointer ptr = msg.getTo(); + NeutralFigure fig = state.getNeutralFigures().getById(msg.getFigureId()); + + if (!(fig instanceof BlackDragon)) { + throw new IllegalArgumentException("Illegal neutral figure move"); + } + + Vector visited = getVisitedPositions(state); + Set availMoves = getAvailBlackDragonMoves(state, visited); + + Position pos = ptr.getPosition(); + if (!availMoves.contains(pos)) { + throw new IllegalArgumentException("Invalid black dragon move."); + } + + Position blackdragonPosition = state.getNeutralFigures().getBlackDragonDeployment(); + + state = ( + new MoveNeutralFigure<>((BlackDragon) fig, pos, state.getActivePlayer()) + ).apply(state); + + state = state.mapCapabilityModel(BlackDragonCapability.class, blackdragon -> { + return new Tuple2<>(blackdragon._1.append(blackdragonPosition),blackdragon._2); + }); + + BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + state = blackdragonCap.blackDragonOnTile(state, pos); + return enter(state); + } +} diff --git a/src/main/java/com/jcloisterzone/game/phase/CleanUpTurnPartPhase.java b/src/main/java/com/jcloisterzone/game/phase/CleanUpTurnPartPhase.java index 70e10638e..673aaa596 100644 --- a/src/main/java/com/jcloisterzone/game/phase/CleanUpTurnPartPhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/CleanUpTurnPartPhase.java @@ -38,6 +38,7 @@ public StepResult enter(GameState state) { .remove(Flag.PORTAL_USED) .remove(Flag.NO_PHANTOM) .remove(Flag.FLYING_MACHINE_USED) + .remove(Flag.BLACK_DRAGON_MOVED) ); } diff --git a/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java index 6f40969dd..683c914ac 100644 --- a/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java @@ -13,9 +13,11 @@ import com.jcloisterzone.game.ScoreFeatureReducer; import com.jcloisterzone.game.capability.*; import com.jcloisterzone.game.capability.TunnelCapability.Tunnel; +import com.jcloisterzone.game.state.Flag; import com.jcloisterzone.game.state.GameState; import com.jcloisterzone.game.state.PlacedTile; import com.jcloisterzone.random.RandomGenerator; +import com.jcloisterzone.Player; import com.jcloisterzone.reducers.ScoreCompletable; import com.jcloisterzone.reducers.ScoreField; import com.jcloisterzone.reducers.ScoreFieldWhenBarnIsConnected; @@ -24,13 +26,15 @@ import io.vavr.Tuple2; import io.vavr.collection.*; - public class ScoringPhase extends Phase { private java.util.Map completedMutable = new java.util.HashMap<>(); + private BlackDragonMovePhase blackdragonMovePhase; + public ScoringPhase(RandomGenerator random, Phase defaultNext) { super(random, defaultNext); + blackdragonMovePhase = new BlackDragonMovePhase(random, this); } private void collectCompletedOnTile(GameState state, PlacedTile tile) { @@ -99,8 +103,6 @@ public StepResult enter(GameState state) { PlacedTile lastPlaced = state.getLastPlaced(); Position pos = lastPlaced.getPosition(); - Map deployedWagonsBefore = getDeployedWagons(state); - collectCompletedOnTile(state, lastPlaced); collectCompletedOnAdjacentEdges(state, pos); // closed by abbey, city gates ... etc @@ -132,6 +134,16 @@ public StepResult enter(GameState state) { } } + BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + + Array scoreOnStart = state.getPlayers().getScore(); + + if (blackdragonCap != null && completedMutable.keySet().size()>0 && !state.hasFlag(Flag.BLACK_DRAGON_MOVED)) { + state = blackdragonCap.setModel(state, new Tuple2<>(Vector.empty(),completedMutable.keySet().size())); + return next(state, blackdragonMovePhase); + } + + Map deployedWagonsBefore = getDeployedWagons(state); for (Capability cap : state.getCapabilities().toSeq()) { state = cap.beforeCompletableScore(state, completedMutable.keySet()); @@ -182,6 +194,17 @@ public StepResult enter(GameState state) { for (Capability cap : state.getCapabilities().toSeq()) { state = cap.onTurnScoring(state, scored); } + + if (blackdragonCap != null) { + for(Player player : state.getPlayers().getPlayers() ) { + Integer scoreBefore = scoreOnStart.get(player.getIndex()); + Integer scoreCurrent = state.getPlayers().getScore().get(player.getIndex()); + Integer diff = scoreCurrent - scoreBefore; + if (diff>0 && ((scoreBefore % 50) + diff > 50)) { + state = blackdragonCap.moveBlackDragon(state, state.getLastPlaced().getPosition()); + } + } + } if (!deployedWagonsBefore.isEmpty()) { Set deployedWagonsAfter = getDeployedWagons(state).keySet(); diff --git a/src/main/java/com/jcloisterzone/game/state/Flag.java b/src/main/java/com/jcloisterzone/game/state/Flag.java index 5f7336c7a..b73bf9538 100644 --- a/src/main/java/com/jcloisterzone/game/state/Flag.java +++ b/src/main/java/com/jcloisterzone/game/state/Flag.java @@ -5,5 +5,5 @@ public enum Flag { RANSOM_PAID, BAZAAR_AUCTION, TUNNEL_PLACED, // Cleared at the turn part end - PORTAL_USED, NO_PHANTOM, FLYING_MACHINE_USED + PORTAL_USED, NO_PHANTOM, FLYING_MACHINE_USED, BLACK_DRAGON_MOVED } \ No newline at end of file diff --git a/src/main/java/com/jcloisterzone/game/state/NeutralFiguresState.java b/src/main/java/com/jcloisterzone/game/state/NeutralFiguresState.java index 0538d157d..5541c6f31 100644 --- a/src/main/java/com/jcloisterzone/game/state/NeutralFiguresState.java +++ b/src/main/java/com/jcloisterzone/game/state/NeutralFiguresState.java @@ -20,15 +20,16 @@ public class NeutralFiguresState implements Serializable { private final Witch witch; private final Count count; private final BigTop bigtop; + private final BlackDragon blackdragon; private final LinkedHashMap, BoardPointer> deployedNeutralFigures; public NeutralFiguresState() { - this(null, null, null, null, null, null, LinkedHashMap.empty()); + this(null, null, null, null, null, null, null,LinkedHashMap.empty()); } public NeutralFiguresState( - Dragon dragon, Fairy fairy, Mage mage, Witch witch, Count count, BigTop bigtop, + Dragon dragon, Fairy fairy, Mage mage, Witch witch, Count count, BigTop bigtop, BlackDragon blackdragon, LinkedHashMap, BoardPointer> deployedNeutralFigures ) { this.dragon = dragon; @@ -37,35 +38,40 @@ public NeutralFiguresState( this.witch = witch; this.count = count; this.bigtop = bigtop; + this.blackdragon = blackdragon; this.deployedNeutralFigures = deployedNeutralFigures; } public NeutralFiguresState setDragon(Dragon dragon) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFiguresState setFairy(Fairy fairy) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFiguresState setMage(Mage mage) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFiguresState setWitch(Witch witch) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFiguresState setCount(Count count) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFiguresState setBigTop(BigTop bigtop) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); + } + + public NeutralFiguresState setBlackDragon(BlackDragon blackdragon) { + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFiguresState setDeployedNeutralFigures(LinkedHashMap, BoardPointer> deployedNeutralFigures) { - return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, deployedNeutralFigures); + return new NeutralFiguresState(dragon, fairy, mage, witch, count, bigtop, blackdragon, deployedNeutralFigures); } public NeutralFigure getById(String figureId) { @@ -75,6 +81,7 @@ public NeutralFigure getById(String figureId) { if (witch != null && figureId.equals(witch.getId())) return witch; if (count != null && figureId.equals(count.getId())) return count; if (bigtop != null && figureId.equals(bigtop.getId())) return bigtop; + if (blackdragon != null && figureId.equals(blackdragon.getId())) return blackdragon; return null; } @@ -134,4 +141,16 @@ public LinkedHashMap, BoardPointer> getDeployedNeutralFigures() return deployedNeutralFigures; } + public BlackDragon getBlackDragon() { + return blackdragon; + } + + public Position getBlackDragonDeployment() { + var bp = deployedNeutralFigures.get(blackdragon).getOrNull(); + if (bp != null) { + return bp.getPosition(); + } + return null; + } + } From b89dc4a0cd513ea9b227f93cc36dfda273a8b5fa Mon Sep 17 00:00:00 2001 From: stanolacko Date: Sun, 11 Sep 2022 20:21:55 +0200 Subject: [PATCH 2/3] Scoring situation on scoring board is stored on begin of turn. Included BlackDragon path. BlackDragon placement as separated phase and removed from ScoringPhase. --- .../engine/StateGsonBuilder.java | 18 ++++++- .../game/GameStatePhaseReducer.java | 2 + .../capability/BlackDragonCapability.java | 48 ++++++++++++------- .../game/phase/BlackDragonMovePhase.java | 39 +++++---------- .../game/phase/BlackDragonPlacePhase.java | 32 +++++++++++++ .../game/phase/ScoringPhase.java | 14 +----- 6 files changed, 96 insertions(+), 57 deletions(-) create mode 100644 src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java diff --git a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java index 76d0933c9..930ad8651 100644 --- a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java +++ b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java @@ -25,6 +25,7 @@ import com.jcloisterzone.game.state.*; import com.jcloisterzone.io.MessageParser; import io.vavr.Tuple2; +import io.vavr.Tuple3; import io.vavr.collection.*; import java.lang.reflect.Type; @@ -293,7 +294,7 @@ public JsonElement serializeNeutralFigures(GameState root, JsonSerializationCont } pos = state.getBlackDragonDeployment(); if (pos != null) { - Tuple2,Integer> blackdragonmoves = root.getCapabilityModel(BlackDragonCapability.class); + Tuple3,Integer,Array> blackdragonmoves = root.getCapabilityModel(BlackDragonCapability.class); JsonObject data = new JsonObject(); data.add("position", context.serialize(pos)); if (root.getPhase() instanceof BlackDragonMovePhase) { @@ -390,6 +391,7 @@ public JsonArray serializePlayEvents(GameState root, JsonSerializationContext co JsonObject item = null; JsonArray turnEvents = null; JsonArray dragonPath = null; + JsonArray blackdragonPath = null; for (PlayEvent ev : root.getEvents()) { if (ev instanceof PlayerTurnEvent) { player = ((PlayerTurnEvent) ev).getPlayer(); @@ -403,6 +405,7 @@ public JsonArray serializePlayEvents(GameState root, JsonSerializationContext co events.add(item); // clean-up dragonPath = null; + blackdragonPath = null; continue; } if (item == null) { @@ -545,6 +548,19 @@ public JsonArray serializePlayEvents(GameState root, JsonSerializationContext co } else { dragonPath.add(context.serialize(nev.getTo())); } + } else if (nev.getNeutralFigure() instanceof BlackDragon) { + if (blackdragonPath == null) { + JsonObject data = new JsonObject(); + blackdragonPath = new JsonArray(); + blackdragonPath.add(context.serialize(nev.getFrom())); + blackdragonPath.add(context.serialize(nev.getTo())); + data.addProperty("type", "blackdragon-moved"); + data.addProperty("figure", nev.getNeutralFigure().getId()); + data.add("path", blackdragonPath); + turnEvents.add(data); + } else { + blackdragonPath.add(context.serialize(nev.getTo())); + } } else { JsonObject data = new JsonObject(); data.addProperty("type", "neutral-moved"); diff --git a/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java b/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java index 4677b2429..3546ffbe9 100644 --- a/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java +++ b/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java @@ -46,6 +46,8 @@ public GameStatePhaseReducer(GameSetup setup, double initialRandom) { next = cleanUpTurnPartPhase = new CleanUpTurnPartPhase(randomGenerator, next); if (setup.contains(CornCircleCapability.class)) next = new CornCirclePhase(randomGenerator, next); + if (setup.contains(BlackDragonCapability.class)) next = new BlackDragonPlacePhase(randomGenerator, next); + if (setup.contains(DragonCapability.class) && "after-scoring".equals(setup.getStringRule(Rule.DRAGON_MOVEMENT))) { next = new DragonPhase(randomGenerator, next); } diff --git a/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java index 19b94349d..22a219b5f 100644 --- a/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java +++ b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java @@ -11,24 +11,25 @@ import com.jcloisterzone.reducers.MoveNeutralFigure; import com.jcloisterzone.reducers.UndeployMeeple; +import io.vavr.collection.Array; import io.vavr.collection.Vector; import io.vavr.Tuple2; +import io.vavr.Tuple3; /** - * @model Tuple2,Integer> : visited tiles, finished features + * @model Tuple3,Integer,Array : visited tiles by black dragon, count of finished features, score on start of turn */ @Immutable -public class BlackDragonCapability extends Capability,Integer>> { +public class BlackDragonCapability extends Capability,Integer,Array>> { private static final long serialVersionUID = 1L; + public static final Vector EMPTY_VISITED = Vector.empty(); + @Override public GameState onStartGame(GameState state) { - System.out.println("\n"); - System.out.println("BlackDragon is running"); - System.out.println("\n"); state = state.mapNeutralFigures(nf -> nf.setBlackDragon(new BlackDragon("blackdragon.1"))); - state = setModel(state, new Tuple2<>(Vector.empty(),0)); + state = setInitialTurnState(state); return state; } @@ -39,17 +40,34 @@ public boolean isMeepleDeploymentAllowed(GameState state, Position pos) { @Override public GameState beforeCompletableScore(GameState state, java.util.Set features) { - System.out.println("\n"); - System.out.println("BeforeCompletable"); - System.out.println(features.size()); - System.out.println("\n"); - state = setModel(state, new Tuple2<>(Vector.empty(),features.size())); - -// Position pos = state.getLastPlaced().getPosition(); -// state = moveBlackDragon(state, pos); + Tuple3,Integer,Array> model = getModel(state); + state = setModel(state, new Tuple3<>(EMPTY_VISITED,features.size(),model._3)); return state; } + @Override + public GameState onTurnPartCleanUp(GameState state) { + return setInitialTurnState(state); + } + + public GameState setInitialTurnState(GameState state) { + return setModel(state, new Tuple3<>(EMPTY_VISITED,0,state.getPlayers().getScore())); + } + + public Vector getVisitedPositions(GameState state) { + Vector visitedpositions = getModel(state)._1; + return visitedpositions == null ? BlackDragonCapability.EMPTY_VISITED : visitedpositions; + } + + public Integer getMoves(GameState state) { + Integer finishedfeatures = getModel(state)._2; + return finishedfeatures == null ? 0 : finishedfeatures; + } + + public Array getScore(GameState state) { + return getModel(state)._3; + } + public GameState moveBlackDragon(GameState state, Position pos) { state = ( new MoveNeutralFigure<>(state.getNeutralFigures().getBlackDragon(), pos) @@ -57,7 +75,6 @@ public GameState moveBlackDragon(GameState state, Position pos) { state = blackDragonOnTile(state, pos); return state; - } public GameState blackDragonOnTile(GameState state, Position pos) { @@ -68,7 +85,6 @@ public GameState blackDragonOnTile(GameState state, Position pos) { state = (new UndeployMeeple(m, true)).apply(state); } } - return state; } } diff --git a/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java index fc24f70c7..e9a1992d9 100644 --- a/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java @@ -15,7 +15,7 @@ import com.jcloisterzone.io.message.MoveNeutralFigureMessage; import com.jcloisterzone.random.RandomGenerator; import com.jcloisterzone.reducers.MoveNeutralFigure; -import io.vavr.Tuple2; +import io.vavr.Tuple3; import io.vavr.collection.HashSet; import io.vavr.collection.Set; import io.vavr.collection.Vector; @@ -26,38 +26,19 @@ public BlackDragonMovePhase(RandomGenerator random, Phase defaultNext) { super(random, defaultNext); } - private Vector getVisitedPositions(GameState state) { - Tuple2,Integer> blackdragonmoves = state.getCapabilityModel(BlackDragonCapability.class); - return blackdragonmoves._1 == null ? Vector.empty() : blackdragonmoves._1; - } - - private Integer getMoves(GameState state) { - Tuple2,Integer> blackdragonmoves = state.getCapabilityModel(BlackDragonCapability.class); - return blackdragonmoves._2 == null ? 0 : blackdragonmoves._2; - } - @Override public StepResult enter(GameState state) { - Vector visited = getVisitedPositions(state); - Integer moves = getMoves(state); - System.out.println("\n"); -System.out.println(visited); -System.out.println(moves); -System.out.println("\n"); + BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + Vector visited = blackdragonCap.getVisitedPositions(state); + Integer moves = blackdragonCap.getMoves(state); if (visited.size() == moves) { return next(endBlackDragonMove(state)); } - Set availMoves = getAvailBlackDragonMoves(state, visited); if (availMoves.isEmpty()) { return next(endBlackDragonMove(state)); } - BlackDragon blackdragon = state.getNeutralFigures().getBlackDragon(); - - System.out.println("AvailMoves"); - System.out.println(availMoves); - System.out.println("\n"); return promote(state.setPlayerActions( new ActionsState(state.getTurnPlayer(), new MoveBlackDragonAction(blackdragon.getId(), availMoves), false) )); @@ -65,12 +46,13 @@ public StepResult enter(GameState state) { private GameState endBlackDragonMove(GameState state) { state = state.addFlag(Flag.BLACK_DRAGON_MOVED); - state = state.setCapabilityModel(BlackDragonCapability.class, new Tuple2<>(Vector.empty(),0)); + state = state.mapCapabilityModel(BlackDragonCapability.class, blackdragon -> { + return new Tuple3<>(BlackDragonCapability.EMPTY_VISITED,0,blackdragon._3); + }); state = clearActions(state); return state; } - public Set getAvailBlackDragonMoves(GameState state, Vector visited) { Set result = HashSet.empty(); Position blackdragonPosition = state.getNeutralFigures().getBlackDragonDeployment(); @@ -98,7 +80,9 @@ public StepResult handleMoveNeutralFigure(GameState state, MoveNeutralFigureMess throw new IllegalArgumentException("Illegal neutral figure move"); } - Vector visited = getVisitedPositions(state); + BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + + Vector visited = blackdragonCap.getVisitedPositions(state); Set availMoves = getAvailBlackDragonMoves(state, visited); Position pos = ptr.getPosition(); @@ -113,10 +97,9 @@ public StepResult handleMoveNeutralFigure(GameState state, MoveNeutralFigureMess ).apply(state); state = state.mapCapabilityModel(BlackDragonCapability.class, blackdragon -> { - return new Tuple2<>(blackdragon._1.append(blackdragonPosition),blackdragon._2); + return new Tuple3<>(blackdragon._1.append(blackdragonPosition),blackdragon._2,blackdragon._3); }); - BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); state = blackdragonCap.blackDragonOnTile(state, pos); return enter(state); } diff --git a/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java new file mode 100644 index 000000000..3fc324db5 --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java @@ -0,0 +1,32 @@ +package com.jcloisterzone.game.phase; + +import com.jcloisterzone.Player; +import com.jcloisterzone.game.capability.BlackDragonCapability; +import com.jcloisterzone.game.state.GameState; +import com.jcloisterzone.random.RandomGenerator; +import io.vavr.collection.Array; + +public class BlackDragonPlacePhase extends Phase { + + public BlackDragonPlacePhase(RandomGenerator random, Phase defaultNext) { + super(random, defaultNext); + } + + @Override + public StepResult enter(GameState state) { + Array scoreOnStart = state.getCapabilityModel(BlackDragonCapability.class)._3; + BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + + for(Player player : state.getPlayers().getPlayers() ) { + Integer scoreBefore = scoreOnStart.get(player.getIndex()); + Integer scoreCurrent = state.getPlayers().getScore().get(player.getIndex()); + Integer diff = scoreCurrent - scoreBefore; + if (diff>0 && ((scoreBefore % 50) + diff > 50)) { + state = blackdragonCap.moveBlackDragon(state, state.getLastPlaced().getPosition()); + break; + } + } + + return next(state); + } +} diff --git a/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java index 683c914ac..c9ae3df78 100644 --- a/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java @@ -24,6 +24,7 @@ import com.jcloisterzone.reducers.UndeployMeeples; import io.vavr.Predicates; import io.vavr.Tuple2; +import io.vavr.Tuple3; import io.vavr.collection.*; public class ScoringPhase extends Phase { @@ -139,7 +140,7 @@ public StepResult enter(GameState state) { Array scoreOnStart = state.getPlayers().getScore(); if (blackdragonCap != null && completedMutable.keySet().size()>0 && !state.hasFlag(Flag.BLACK_DRAGON_MOVED)) { - state = blackdragonCap.setModel(state, new Tuple2<>(Vector.empty(),completedMutable.keySet().size())); + state = blackdragonCap.setModel(state, new Tuple3<>(blackdragonCap.EMPTY_VISITED,completedMutable.keySet().size(),blackdragonCap.getScore(state))); return next(state, blackdragonMovePhase); } @@ -195,17 +196,6 @@ public StepResult enter(GameState state) { state = cap.onTurnScoring(state, scored); } - if (blackdragonCap != null) { - for(Player player : state.getPlayers().getPlayers() ) { - Integer scoreBefore = scoreOnStart.get(player.getIndex()); - Integer scoreCurrent = state.getPlayers().getScore().get(player.getIndex()); - Integer diff = scoreCurrent - scoreBefore; - if (diff>0 && ((scoreBefore % 50) + diff > 50)) { - state = blackdragonCap.moveBlackDragon(state, state.getLastPlaced().getPosition()); - } - } - } - if (!deployedWagonsBefore.isEmpty()) { Set deployedWagonsAfter = getDeployedWagons(state).keySet(); Set returnedVagons = deployedWagonsBefore.keySet().diff(deployedWagonsAfter); From c8c72850ea899204a4f85a549b00edd41abcd7b2 Mon Sep 17 00:00:00 2001 From: stanolacko Date: Fri, 16 Sep 2022 11:38:01 +0200 Subject: [PATCH 3/3] NeutralFigureReturned, BlackDragon eats neutral figures --- .../engine/StateGsonBuilder.java | 15 ++++++++ .../event/NeutralFigureReturned.java | 16 ++++++++- .../java/com/jcloisterzone/figure/Figure.java | 4 +++ .../java/com/jcloisterzone/figure/Meeple.java | 4 --- .../jcloisterzone/figure/neutral/BigTop.java | 6 ++++ .../jcloisterzone/figure/neutral/Count.java | 6 ++++ .../figure/neutral/NeutralFigure.java | 12 ------- .../capability/BlackDragonCapability.java | 10 ++++++ .../game/phase/DragonMovePhase.java | 2 ++ .../reducers/AbstractUndeploy.java | 19 ++++++++++- .../reducers/ReturnNeutralFigure.java | 2 +- .../reducers/UndeployNeutralFigure.java | 34 +++++++++++++++++++ 12 files changed, 111 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java diff --git a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java index 930ad8651..368bc47b0 100644 --- a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java +++ b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java @@ -571,6 +571,21 @@ public JsonArray serializePlayEvents(GameState root, JsonSerializationContext co } continue; } + if (ev instanceof NeutralFigureReturned) { + NeutralFigureReturned nfr = (NeutralFigureReturned) ev; + if (nfr.isForced()) { + JsonObject data = new JsonObject(); + data.addProperty("type", "neutralfigure-returned"); + data.addProperty("neutralfigure", nfr.getNeutralFigure().getClass().getSimpleName().toLowerCase()); + Player nfrp = nfr.getPlayer(); + if (nfrp != null) { + data.addProperty("player", nfr.getPlayer().getIndex()); + } + data.add("from", context.serialize(nfr.getFrom())); + turnEvents.add(data); + } + continue; + } if (ev instanceof RansomPaidEvent) { RansomPaidEvent rev = (RansomPaidEvent) ev; JsonObject data = new JsonObject(); diff --git a/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java b/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java index 8197206b6..f30911343 100644 --- a/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java +++ b/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java @@ -2,16 +2,22 @@ import com.jcloisterzone.board.pointer.BoardPointer; import com.jcloisterzone.figure.neutral.NeutralFigure; +import com.jcloisterzone.Player; public class NeutralFigureReturned extends PlayEvent { private final BoardPointer from; private final NeutralFigure neutralFigure; + private final boolean forced; + /** true if meeple is returned different way than scoring feature */ + private final Player player; - public NeutralFigureReturned(PlayEventMeta metadata, NeutralFigure neutralFigure, BoardPointer from) { + public NeutralFigureReturned(PlayEventMeta metadata, NeutralFigure neutralFigure, BoardPointer from, Boolean forced, Player player) { super(metadata); this.neutralFigure = neutralFigure; this.from = from; + this.forced = forced; + this.player = player; } public BoardPointer getFrom() { @@ -21,4 +27,12 @@ public BoardPointer getFrom() { public NeutralFigure getNeutralFigure() { return neutralFigure; } + + public boolean isForced() { + return forced; + } + + public Player getPlayer() { + return player; + } } diff --git a/src/main/java/com/jcloisterzone/figure/Figure.java b/src/main/java/com/jcloisterzone/figure/Figure.java index b4dd1ef3e..6e9d9c280 100644 --- a/src/main/java/com/jcloisterzone/figure/Figure.java +++ b/src/main/java/com/jcloisterzone/figure/Figure.java @@ -74,6 +74,10 @@ public boolean isInSupply(GameState state) { return !isDeployed(state); } + public boolean canBeEatenByBlackDragon(GameState state) { + return true; + } + @Override public int hashCode() { return id.hashCode(); diff --git a/src/main/java/com/jcloisterzone/figure/Meeple.java b/src/main/java/com/jcloisterzone/figure/Meeple.java index 65cd1f613..748fd32cc 100644 --- a/src/main/java/com/jcloisterzone/figure/Meeple.java +++ b/src/main/java/com/jcloisterzone/figure/Meeple.java @@ -41,10 +41,6 @@ public boolean canBeEatenByDragon(GameState state) { return true; } - public boolean canBeEatenByBlackDragon(GameState state) { - return true; - } - public DeploymentCheckResult isDeploymentAllowed(GameState state, FeaturePointer fp, Structure feature) { return DeploymentCheckResult.OK; } diff --git a/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java b/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java index 58342de95..2e43a4678 100644 --- a/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java +++ b/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java @@ -2,6 +2,7 @@ import com.jcloisterzone.Immutable; import com.jcloisterzone.board.Position; +import com.jcloisterzone.game.state.GameState; @Immutable public class BigTop extends NeutralFigure { @@ -11,4 +12,9 @@ public class BigTop extends NeutralFigure { public BigTop(String id) { super(id); } + + @Override + public boolean canBeEatenByBlackDragon(GameState state) { + return false; + } } diff --git a/src/main/java/com/jcloisterzone/figure/neutral/Count.java b/src/main/java/com/jcloisterzone/figure/neutral/Count.java index 9dfc26598..413164cb6 100644 --- a/src/main/java/com/jcloisterzone/figure/neutral/Count.java +++ b/src/main/java/com/jcloisterzone/figure/neutral/Count.java @@ -2,6 +2,7 @@ import com.jcloisterzone.Immutable; import com.jcloisterzone.board.pointer.FeaturePointer; +import com.jcloisterzone.game.state.GameState; @Immutable public class Count extends NeutralFigure { @@ -11,4 +12,9 @@ public class Count extends NeutralFigure { public Count(String id) { super(id); } + + @Override + public boolean canBeEatenByBlackDragon(GameState state) { + return false; + } } diff --git a/src/main/java/com/jcloisterzone/figure/neutral/NeutralFigure.java b/src/main/java/com/jcloisterzone/figure/neutral/NeutralFigure.java index 91ebc042a..0cb13bd54 100644 --- a/src/main/java/com/jcloisterzone/figure/neutral/NeutralFigure.java +++ b/src/main/java/com/jcloisterzone/figure/neutral/NeutralFigure.java @@ -28,16 +28,4 @@ public boolean at(GameState state, Structure feature) { FeaturePointer fp = ptr.asFeaturePointer(); return feature.getPlaces().contains(fp); } - - -// @Override -// public void deploy(T at) { -// T origin = getDeployment(); -// game.replaceState(state -> { -// LinkedHashMap, BoardPointer> deployedNeutralFigures = state.getDeployedNeutralFigures(); -// return state.setDeployedNeutralFigures(deployedNeutralFigures.put(this, at)); -// }); -// game.post(new NeutralFigureMoveEvent(game.getActivePlayer(), this, origin, at)); -// } - } diff --git a/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java index 22a219b5f..5432d829b 100644 --- a/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java +++ b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java @@ -2,14 +2,17 @@ import com.jcloisterzone.Immutable; import com.jcloisterzone.board.Position; +import com.jcloisterzone.board.pointer.BoardPointer; import com.jcloisterzone.board.pointer.FeaturePointer; import com.jcloisterzone.feature.Completable; import com.jcloisterzone.figure.Meeple; import com.jcloisterzone.figure.neutral.BlackDragon; +import com.jcloisterzone.figure.neutral.NeutralFigure; import com.jcloisterzone.game.Capability; import com.jcloisterzone.game.state.GameState; import com.jcloisterzone.reducers.MoveNeutralFigure; import com.jcloisterzone.reducers.UndeployMeeple; +import com.jcloisterzone.reducers.UndeployNeutralFigure; import io.vavr.collection.Array; import io.vavr.collection.Vector; @@ -85,6 +88,13 @@ public GameState blackDragonOnTile(GameState state, Position pos) { state = (new UndeployMeeple(m, true)).apply(state); } } + for (Tuple2, BoardPointer> t: state.getNeutralFigures().getDeployedNeutralFigures().toSet()) { + NeutralFigure nf = t._1; + BoardPointer bp = t._2; + if (pos.equals(bp.getPosition()) && state.getNeutralFigures().getBlackDragon() != nf && nf.canBeEatenByBlackDragon(state)) { + state = (new UndeployNeutralFigure(nf, true)).apply(state); + } + } return state; } } diff --git a/src/main/java/com/jcloisterzone/game/phase/DragonMovePhase.java b/src/main/java/com/jcloisterzone/game/phase/DragonMovePhase.java index 7c8ce3002..7e498bb14 100644 --- a/src/main/java/com/jcloisterzone/game/phase/DragonMovePhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/DragonMovePhase.java @@ -67,6 +67,7 @@ public Set getAvailDragonMoves(GameState state, Vector visit BoardPointer fairyPtr = state.getNeutralFigures().getFairyDeployment(); Position fairyPosition = fairyPtr == null ? null : fairyPtr.getPosition(); Position dragonPosition = state.getNeutralFigures().getDragonDeployment(); + Position blackdragonPosition = state.getNeutralFigures().getBlackDragonDeployment(); for (Position offset: Position.ADJACENT.values()) { Position pos = dragonPosition.add(offset); @@ -75,6 +76,7 @@ public Set getAvailDragonMoves(GameState state, Vector visit if (pt == null || CountCapability.isTileForbidden(pt.getTile())) continue; if (visited.contains(pos)) continue; if (pos.equals(fairyPosition)) continue; + if (pos.equals(blackdragonPosition)) continue; result = result.add(pos); } diff --git a/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java b/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java index aa6f1a84c..1dbed1bcc 100644 --- a/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java +++ b/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java @@ -1,15 +1,20 @@ package com.jcloisterzone.reducers; import com.jcloisterzone.Player; +import com.jcloisterzone.board.pointer.BoardPointer; import com.jcloisterzone.board.pointer.FeaturePointer; import com.jcloisterzone.event.MeepleReturned; +import com.jcloisterzone.event.NeutralFigureReturned; import com.jcloisterzone.event.PlayEvent; import com.jcloisterzone.feature.Structure; import com.jcloisterzone.figure.Builder; import com.jcloisterzone.figure.Follower; import com.jcloisterzone.figure.Meeple; import com.jcloisterzone.figure.Pig; +import com.jcloisterzone.figure.neutral.NeutralFigure; import com.jcloisterzone.game.state.GameState; +import com.jcloisterzone.game.state.NeutralFiguresState; + import io.vavr.Tuple2; import io.vavr.collection.LinkedHashMap; import io.vavr.collection.Stream; @@ -20,7 +25,7 @@ protected GameState undeploy(GameState state, PlayEvent.PlayEventMeta meta, Meep LinkedHashMap deployedMeeples = state.getDeployedMeeples(); state = state.setDeployedMeeples(deployedMeeples.remove(meeple)); state = state.appendEvent( - new MeepleReturned(meta, meeple, source, forced) + new MeepleReturned(meta, meeple, source, forced) ); return state; } @@ -41,4 +46,16 @@ protected GameState undeployLonelySpecials(GameState state, Follower meeple, Fea return state; } + + protected GameState undeploy(GameState state, PlayEvent.PlayEventMeta meta, NeutralFigure figure, BoardPointer source, boolean forced, Player player) { + + NeutralFiguresState nfState = state.getNeutralFigures(); + nfState = nfState.setDeployedNeutralFigures(nfState.getDeployedNeutralFigures().remove(figure)); + state = state.setNeutralFigures(nfState); + + state = state.appendEvent( + new NeutralFigureReturned(meta, figure, source, forced, player) + ); + return state; + } } diff --git a/src/main/java/com/jcloisterzone/reducers/ReturnNeutralFigure.java b/src/main/java/com/jcloisterzone/reducers/ReturnNeutralFigure.java index 8d68d24ba..7e0896705 100644 --- a/src/main/java/com/jcloisterzone/reducers/ReturnNeutralFigure.java +++ b/src/main/java/com/jcloisterzone/reducers/ReturnNeutralFigure.java @@ -32,7 +32,7 @@ public GameState apply(GameState state) { ); state = state.setNeutralFigures(nfState); state = state.appendEvent( - new NeutralFigureReturned(PlayEventMeta.createWithPlayer(triggeringPlayer), figure, from) + new NeutralFigureReturned(PlayEventMeta.createWithPlayer(triggeringPlayer), figure, from, true, triggeringPlayer) ); return state; } diff --git a/src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java b/src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java new file mode 100644 index 000000000..f1db09905 --- /dev/null +++ b/src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java @@ -0,0 +1,34 @@ +package com.jcloisterzone.reducers; + +import com.jcloisterzone.board.pointer.BoardPointer; +import com.jcloisterzone.board.pointer.FeaturePointer; +import com.jcloisterzone.event.PlayEvent.PlayEventMeta; +import com.jcloisterzone.figure.neutral.NeutralFigure; +import com.jcloisterzone.game.state.GameState; +import com.jcloisterzone.game.state.NeutralFiguresState; + +public class UndeployNeutralFigure extends AbstractUndeploy { + + private final NeutralFigure figure; + /** true if meep le is returned different way than scoring feature */ + private final boolean forced; + + public UndeployNeutralFigure(NeutralFigure figure, boolean forced) { + this.figure = figure; + this.forced = forced; + } + + @Override + public GameState apply(GameState state) { + BoardPointer source = figure.getDeployment(state); + + PlayEventMeta metaWithPlayer = PlayEventMeta.createWithActivePlayer(state); + state = undeploy(state, metaWithPlayer, figure, source, forced, state.getActivePlayer()); + + return state; + } + + public boolean isForced() { + return forced; + } +}