From 8e5d643b8d8218e412d3d34ae9fb13f684f26579 Mon Sep 17 00:00:00 2001 From: stanolacko Date: Fri, 9 Sep 2022 13:55:47 +0200 Subject: [PATCH 1/5] 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/5] 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/5] 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; + } +} From 5296f41eb26d82eef99b93d8abf98cd2c9981a2a Mon Sep 17 00:00:00 2001 From: stanolacko Date: Fri, 9 Sep 2022 13:55:47 +0200 Subject: [PATCH 4/5] BlackDragon + NeutralFigureReturned/UndeployNeutralFigure --- .../action/MoveBlackDragonAction.java | 19 ++++ .../java/com/jcloisterzone/engine/Engine.java | 1 + .../engine/StateGsonBuilder.java | 63 +++++++++++ .../event/NeutralFigureReturned.java | 16 ++- .../java/com/jcloisterzone/figure/Barn.java | 5 + .../java/com/jcloisterzone/figure/Figure.java | 4 + .../com/jcloisterzone/figure/Follower.java | 4 + .../jcloisterzone/figure/neutral/BigTop.java | 6 + .../figure/neutral/BlackDragon.java | 15 +++ .../jcloisterzone/figure/neutral/Count.java | 6 + .../figure/neutral/NeutralFigure.java | 12 -- .../game/GameStatePhaseReducer.java | 2 + .../capability/BlackDragonCapability.java | 100 +++++++++++++++++ .../game/phase/BlackDragonMovePhase.java | 106 ++++++++++++++++++ .../game/phase/BlackDragonPlacePhase.java | 32 ++++++ .../game/phase/CleanUpTurnPartPhase.java | 1 + .../game/phase/DragonMovePhase.java | 2 + .../game/phase/ScoringPhase.java | 21 +++- .../com/jcloisterzone/game/state/Flag.java | 2 +- .../game/state/NeutralFiguresState.java | 37 ++++-- .../reducers/AbstractUndeploy.java | 19 +++- .../reducers/ReturnNeutralFigure.java | 2 +- .../reducers/UndeployNeutralFigure.java | 34 ++++++ 23 files changed, 480 insertions(+), 29 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 create mode 100644 src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java create mode 100644 src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.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..368bc47b0 100644 --- a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java +++ b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java @@ -11,18 +11,21 @@ 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; 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; @@ -70,6 +73,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 +292,19 @@ public JsonElement serializeNeutralFigures(GameState root, JsonSerializationCont data.add("placement", context.serialize(pos)); neutral.add("bigtop", data); } + pos = state.getBlackDragonDeployment(); + if (pos != null) { + Tuple3,Integer,Array> 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; } @@ -374,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(); @@ -387,6 +405,7 @@ public JsonArray serializePlayEvents(GameState root, JsonSerializationContext co events.add(item); // clean-up dragonPath = null; + blackdragonPath = null; continue; } if (item == null) { @@ -529,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"); @@ -539,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(); @@ -897,4 +944,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/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/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/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/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/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/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/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/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 new file mode 100644 index 000000000..5432d829b --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java @@ -0,0 +1,100 @@ +package com.jcloisterzone.game.capability; + +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; +import io.vavr.Tuple2; +import io.vavr.Tuple3; + +/** + * @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,Array>> { + + private static final long serialVersionUID = 1L; + + public static final Vector EMPTY_VISITED = Vector.empty(); + + @Override + public GameState onStartGame(GameState state) { + state = state.mapNeutralFigures(nf -> nf.setBlackDragon(new BlackDragon("blackdragon.1"))); + state = setInitialTurnState(state); + 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) { + 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) + ).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); + } + } + 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/BlackDragonMovePhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java new file mode 100644 index 000000000..e9a1992d9 --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java @@ -0,0 +1,106 @@ +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.Tuple3; +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); + } + + @Override + public StepResult enter(GameState state) { + 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(); + 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.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(); + + 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"); + } + + BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + + Vector visited = blackdragonCap.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 Tuple3<>(blackdragon._1.append(blackdragonPosition),blackdragon._2,blackdragon._3); + }); + + 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/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/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/game/phase/ScoringPhase.java b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java index 6f40969dd..c9ae3df78 100644 --- a/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java @@ -13,24 +13,29 @@ 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; 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 { 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 +104,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 +135,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 Tuple3<>(blackdragonCap.EMPTY_VISITED,completedMutable.keySet().size(),blackdragonCap.getScore(state))); + return next(state, blackdragonMovePhase); + } + + Map deployedWagonsBefore = getDeployedWagons(state); for (Capability cap : state.getCapabilities().toSeq()) { state = cap.beforeCompletableScore(state, completedMutable.keySet()); @@ -182,7 +195,7 @@ public StepResult enter(GameState state) { for (Capability cap : state.getCapabilities().toSeq()) { state = cap.onTurnScoring(state, scored); } - + if (!deployedWagonsBefore.isEmpty()) { Set deployedWagonsAfter = getDeployedWagons(state).keySet(); Set returnedVagons = deployedWagonsBefore.keySet().diff(deployedWagonsAfter); 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; + } + } 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; + } +} From 34c48276d9c59d694d91b869dacba0134c6293bf Mon Sep 17 00:00:00 2001 From: Roman Krejcik Date: Wed, 12 Oct 2022 22:33:16 +0200 Subject: [PATCH 5/5] code review WIP --- .../action/MoveBlackDragonAction.java | 19 ------- .../engine/StateGsonBuilder.java | 38 +------------ .../event/NeutralFigureReturned.java | 16 +----- .../java/com/jcloisterzone/figure/Barn.java | 5 -- .../java/com/jcloisterzone/figure/Figure.java | 4 -- .../com/jcloisterzone/figure/Follower.java | 4 -- .../jcloisterzone/figure/neutral/BigTop.java | 5 -- .../jcloisterzone/figure/neutral/Count.java | 5 -- .../game/GameStatePhaseReducer.java | 1 - .../capability/BlackDragonCapability.java | 55 ++++--------------- .../BlackDragonCapabilityModel.java | 31 +++++++++++ .../game/phase/BlackDragonMovePhase.java | 34 +++++------- .../game/phase/BlackDragonPhase.java | 25 +++++++++ .../game/phase/BlackDragonPlacePhase.java | 42 ++++++++++---- .../game/phase/ScoringPhase.java | 9 --- .../reducers/AbstractUndeploy.java | 12 ---- .../reducers/ReturnNeutralFigure.java | 2 +- .../reducers/UndeployNeutralFigure.java | 34 ------------ 18 files changed, 115 insertions(+), 226 deletions(-) delete mode 100644 src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java create mode 100644 src/main/java/com/jcloisterzone/game/capability/BlackDragonCapabilityModel.java create mode 100644 src/main/java/com/jcloisterzone/game/phase/BlackDragonPhase.java delete mode 100644 src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java diff --git a/src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java b/src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java deleted file mode 100644 index b0c16dfc5..000000000 --- a/src/main/java/com/jcloisterzone/action/MoveBlackDragonAction.java +++ /dev/null @@ -1,19 +0,0 @@ -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/StateGsonBuilder.java b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java index 368bc47b0..0df526faf 100644 --- a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java +++ b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java @@ -73,7 +73,6 @@ 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(); } @@ -294,14 +293,14 @@ public JsonElement serializeNeutralFigures(GameState root, JsonSerializationCont } pos = state.getBlackDragonDeployment(); if (pos != null) { - Tuple3,Integer,Array> blackdragonmoves = root.getCapabilityModel(BlackDragonCapability.class); + BlackDragonCapabilityModel model = 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))); + model.getVisited().forEach(p -> visitedData.add(context.serialize(p))); data.add("visited", visitedData); - data.addProperty("remaining", blackdragonmoves._2 - blackdragonmoves._1.length()); + data.addProperty("remaining", model.getMoves() - model.getVisited().length()); } neutral.add("blackdragon", data); } @@ -571,21 +570,6 @@ 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(); @@ -944,20 +928,4 @@ 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/event/NeutralFigureReturned.java b/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java index f30911343..8197206b6 100644 --- a/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java +++ b/src/main/java/com/jcloisterzone/event/NeutralFigureReturned.java @@ -2,22 +2,16 @@ 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, Boolean forced, Player player) { + public NeutralFigureReturned(PlayEventMeta metadata, NeutralFigure neutralFigure, BoardPointer from) { super(metadata); this.neutralFigure = neutralFigure; this.from = from; - this.forced = forced; - this.player = player; } public BoardPointer getFrom() { @@ -27,12 +21,4 @@ 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/Barn.java b/src/main/java/com/jcloisterzone/figure/Barn.java index c04bd89c2..4ec68ed2b 100644 --- a/src/main/java/com/jcloisterzone/figure/Barn.java +++ b/src/main/java/com/jcloisterzone/figure/Barn.java @@ -19,11 +19,6 @@ 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/Figure.java b/src/main/java/com/jcloisterzone/figure/Figure.java index 6e9d9c280..b4dd1ef3e 100644 --- a/src/main/java/com/jcloisterzone/figure/Figure.java +++ b/src/main/java/com/jcloisterzone/figure/Figure.java @@ -74,10 +74,6 @@ 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/Follower.java b/src/main/java/com/jcloisterzone/figure/Follower.java index 92a85afbc..4cf781517 100644 --- a/src/main/java/com/jcloisterzone/figure/Follower.java +++ b/src/main/java/com/jcloisterzone/figure/Follower.java @@ -28,10 +28,6 @@ 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/neutral/BigTop.java b/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java index 2e43a4678..a587d4a24 100644 --- a/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java +++ b/src/main/java/com/jcloisterzone/figure/neutral/BigTop.java @@ -12,9 +12,4 @@ 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 413164cb6..bb1939363 100644 --- a/src/main/java/com/jcloisterzone/figure/neutral/Count.java +++ b/src/main/java/com/jcloisterzone/figure/neutral/Count.java @@ -12,9 +12,4 @@ 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/game/GameStatePhaseReducer.java b/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java index 3546ffbe9..0ee44e4d5 100644 --- a/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java +++ b/src/main/java/com/jcloisterzone/game/GameStatePhaseReducer.java @@ -47,7 +47,6 @@ public GameStatePhaseReducer(GameSetup setup, double initialRandom) { 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 5432d829b..fc2a803c0 100644 --- a/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java +++ b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapability.java @@ -2,28 +2,21 @@ 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; import io.vavr.Tuple2; -import io.vavr.Tuple3; -/** - * @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,Array>> { +public class BlackDragonCapability extends Capability { private static final long serialVersionUID = 1L; @@ -32,7 +25,7 @@ public class BlackDragonCapability extends Capability,In @Override public GameState onStartGame(GameState state) { state = state.mapNeutralFigures(nf -> nf.setBlackDragon(new BlackDragon("blackdragon.1"))); - state = setInitialTurnState(state); + state = setModel(state, new BlackDragonCapabilityModel(EMPTY_VISITED, 0)); return state; } @@ -43,58 +36,30 @@ public boolean isMeepleDeploymentAllowed(GameState state, Position pos) { @Override public GameState beforeCompletableScore(GameState state, java.util.Set features) { - Tuple3,Integer,Array> model = getModel(state); - state = setModel(state, new Tuple3<>(EMPTY_VISITED,features.size(),model._3)); + if (features.size() > 0) { + state = setModel(state, new BlackDragonCapabilityModel(EMPTY_VISITED, features.size())); + } 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) ).apply(state); - state = blackDragonOnTile(state, pos); + state = clearTile(state, pos); return state; } - public GameState blackDragonOnTile(GameState state, Position pos) { + public GameState clearTile(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)) { + if (pos.equals(fp.getPosition()) && m.canBeEatenByDragon(state)) { 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/capability/BlackDragonCapabilityModel.java b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapabilityModel.java new file mode 100644 index 000000000..aacf32ecd --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/capability/BlackDragonCapabilityModel.java @@ -0,0 +1,31 @@ +package com.jcloisterzone.game.capability; + +import com.jcloisterzone.board.Position; +import io.vavr.collection.Vector; + +public class BlackDragonCapabilityModel { + + private final Vector visited; + private final int moves; + + public BlackDragonCapabilityModel(Vector visited, int moves) { + this.visited = visited; + this.moves = moves; + } + + public Vector getVisited() { + return visited; + } + + public BlackDragonCapabilityModel setVisited(Vector visited) { + return new BlackDragonCapabilityModel(visited, moves); + } + + public int getMoves() { + return moves; + } + + public BlackDragonCapabilityModel setMoves(int moves) { + return new BlackDragonCapabilityModel(visited, moves); + } +} diff --git a/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java index e9a1992d9..6b081fcc0 100644 --- a/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonMovePhase.java @@ -1,11 +1,11 @@ package com.jcloisterzone.game.phase; -import com.jcloisterzone.Player; -import com.jcloisterzone.action.MoveBlackDragonAction; +import com.jcloisterzone.action.MoveDragonAction; 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.BlackDragonCapabilityModel; import com.jcloisterzone.game.capability.CountCapability; import com.jcloisterzone.game.capability.BlackDragonCapability; import com.jcloisterzone.game.state.ActionsState; @@ -15,7 +15,6 @@ import com.jcloisterzone.io.message.MoveNeutralFigureMessage; import com.jcloisterzone.random.RandomGenerator; import com.jcloisterzone.reducers.MoveNeutralFigure; -import io.vavr.Tuple3; import io.vavr.collection.HashSet; import io.vavr.collection.Set; import io.vavr.collection.Vector; @@ -28,27 +27,23 @@ public BlackDragonMovePhase(RandomGenerator random, Phase defaultNext) { @Override public StepResult enter(GameState state) { - BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); - Vector visited = blackdragonCap.getVisitedPositions(state); - Integer moves = blackdragonCap.getMoves(state); - if (visited.size() == moves) { + BlackDragonCapabilityModel model = state.getCapabilityModel(BlackDragonCapability.class); + if (model.getVisited().size() == model.getMoves()) { return next(endBlackDragonMove(state)); } - Set availMoves = getAvailBlackDragonMoves(state, visited); + Set availMoves = getAvailBlackDragonMoves(state, model.getVisited()); if (availMoves.isEmpty()) { return next(endBlackDragonMove(state)); } BlackDragon blackdragon = state.getNeutralFigures().getBlackDragon(); return promote(state.setPlayerActions( - new ActionsState(state.getTurnPlayer(), new MoveBlackDragonAction(blackdragon.getId(), availMoves), false) + new ActionsState(state.getTurnPlayer(), new MoveDragonAction(blackdragon.getId(), availMoves), false) )); } private GameState endBlackDragonMove(GameState state) { state = state.addFlag(Flag.BLACK_DRAGON_MOVED); - state = state.mapCapabilityModel(BlackDragonCapability.class, blackdragon -> { - return new Tuple3<>(BlackDragonCapability.EMPTY_VISITED,0,blackdragon._3); - }); + state = state.setCapabilityModel(BlackDragonCapability.class, new BlackDragonCapabilityModel(BlackDragonCapability.EMPTY_VISITED, 0)); state = clearActions(state); return state; } @@ -80,27 +75,26 @@ public StepResult handleMoveNeutralFigure(GameState state, MoveNeutralFigureMess throw new IllegalArgumentException("Illegal neutral figure move"); } - BlackDragonCapability blackdragonCap = state.getCapabilities().get(BlackDragonCapability.class); + BlackDragonCapability cap = state.getCapabilities().get(BlackDragonCapability.class); + BlackDragonCapabilityModel model = state.getCapabilityModel(BlackDragonCapability.class); - Vector visited = blackdragonCap.getVisitedPositions(state); + Vector visited = model.getVisited(); Set availMoves = getAvailBlackDragonMoves(state, visited); - Position pos = ptr.getPosition(); + final 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 Tuple3<>(blackdragon._1.append(blackdragonPosition),blackdragon._2,blackdragon._3); + state = state.mapCapabilityModel(BlackDragonCapability.class, m -> { + return m.setVisited(m.getVisited().append(pos)); }); - state = blackdragonCap.blackDragonOnTile(state, pos); + state = cap.clearTile(state, pos); return enter(state); } } diff --git a/src/main/java/com/jcloisterzone/game/phase/BlackDragonPhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonPhase.java new file mode 100644 index 000000000..2d31cc3b6 --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonPhase.java @@ -0,0 +1,25 @@ +package com.jcloisterzone.game.phase; + +import com.jcloisterzone.game.capability.BlackDragonCapability; +import com.jcloisterzone.game.capability.BlackDragonCapabilityModel; +import com.jcloisterzone.game.state.GameState; +import com.jcloisterzone.random.RandomGenerator; + +public class BlackDragonPhase extends Phase { + + private BlackDragonMovePhase blackDragonMovePhase; + + public BlackDragonPhase(RandomGenerator random, Phase defaultNext) { + super(random, defaultNext); + blackDragonMovePhase = new BlackDragonMovePhase(random, defaultNext); + } + + @Override + public StepResult enter(GameState state) { + BlackDragonCapabilityModel model = state.getCapabilityModel(BlackDragonCapability.class); + if (model.getMoves() > 0) { + return next(state, blackDragonMovePhase); + } + return next(state); + } +} diff --git a/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java b/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java index 3fc324db5..4f17d2a9f 100644 --- a/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/BlackDragonPlacePhase.java @@ -1,10 +1,14 @@ package com.jcloisterzone.game.phase; import com.jcloisterzone.Player; +import com.jcloisterzone.event.ScoreEvent; import com.jcloisterzone.game.capability.BlackDragonCapability; import com.jcloisterzone.game.state.GameState; import com.jcloisterzone.random.RandomGenerator; -import io.vavr.collection.Array; +import io.vavr.Predicates; +import io.vavr.collection.Stream; + +import java.util.HashMap; public class BlackDragonPlacePhase extends Phase { @@ -14,17 +18,31 @@ public BlackDragonPlacePhase(RandomGenerator random, Phase 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; - } + BlackDragonCapability cap = state.getCapabilities().get(BlackDragonCapability.class); + + java.util.Map receivedScore = new HashMap<>(); + for (Player player : state.getPlayers().getPlayers() ) { + receivedScore.put(player, 0); + } + + Stream.ofAll(state.getCurrentTurnPartEvents()) + .filter(Predicates.instanceOf(ScoreEvent.class)) + .map(ev -> (ScoreEvent) ev) + .forEach(ev -> { + for (ScoreEvent.ReceivedPoints rp : ev.getPoints()) { + receivedScore.put(rp.getPlayer(), receivedScore.get(rp.getPlayer()) + rp.getPoints()); + } + }); + + for (Player player : state.getPlayers().getPlayers() ) { + int playerReceivedPoints = receivedScore.get(player); + if (playerReceivedPoints > 0) { + int scoreCurrent = state.getPlayers().getScore().get(player.getIndex()); + if (scoreCurrent / 50 > (scoreCurrent - playerReceivedPoints) / 50) { + state = cap.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 c9ae3df78..75f17f4b0 100644 --- a/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/ScoringPhase.java @@ -135,15 +135,6 @@ 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 Tuple3<>(blackdragonCap.EMPTY_VISITED,completedMutable.keySet().size(),blackdragonCap.getScore(state))); - return next(state, blackdragonMovePhase); - } - Map deployedWagonsBefore = getDeployedWagons(state); for (Capability cap : state.getCapabilities().toSeq()) { diff --git a/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java b/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java index 1dbed1bcc..8414968be 100644 --- a/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java +++ b/src/main/java/com/jcloisterzone/reducers/AbstractUndeploy.java @@ -46,16 +46,4 @@ 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 7e0896705..8d68d24ba 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, true, triggeringPlayer) + new NeutralFigureReturned(PlayEventMeta.createWithPlayer(triggeringPlayer), figure, from) ); return state; } diff --git a/src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java b/src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java deleted file mode 100644 index f1db09905..000000000 --- a/src/main/java/com/jcloisterzone/reducers/UndeployNeutralFigure.java +++ /dev/null @@ -1,34 +0,0 @@ -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; - } -}