From 47e5ce2b1561920ccce85d323258b21405a429a2 Mon Sep 17 00:00:00 2001 From: Roman Krejcik Date: Sun, 22 Aug 2021 21:55:15 +0200 Subject: [PATCH] multiple shepherds closes #403 --- .../engine/StateGsonBuilder.java | 4 +- .../game/capability/SheepCapability.java | 24 +--- .../game/capability/SheepCapabilityModel.java | 33 ++++++ .../game/capability/SheepToken.java | 18 +++ .../game/phase/ShepherdPhase.java | 112 ++++++++++++------ .../reducers/UndeployMeeple.java | 2 +- 6 files changed, 134 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/jcloisterzone/game/capability/SheepCapabilityModel.java create mode 100644 src/main/java/com/jcloisterzone/game/capability/SheepToken.java diff --git a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java index 9f6a81b9d..a21344b15 100644 --- a/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java +++ b/src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java @@ -16,7 +16,7 @@ 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.SheepCapability.SheepToken; +import com.jcloisterzone.game.capability.SheepToken; import com.jcloisterzone.game.phase.DragonMovePhase; import com.jcloisterzone.game.phase.Phase; import com.jcloisterzone.game.phase.RussianPromosTrapPhase; @@ -124,7 +124,7 @@ public JsonElement serialize(Game game, Type typeOfSrc, JsonSerializationContext SheepCapability sheepCap = state.getCapabilities().get(SheepCapability.class); if (sheepCap != null) { - Map> sheepModel = sheepCap.getModel(state); + Map> sheepModel = sheepCap.getModel(state).getPlacedTokens(); JsonObject jsonItem = new JsonObject(); JsonArray jsonFlocks = new JsonArray(); sheepModel.forEach((fp, tokens) -> { diff --git a/src/main/java/com/jcloisterzone/game/capability/SheepCapability.java b/src/main/java/com/jcloisterzone/game/capability/SheepCapability.java index e673e7bc7..10a8445bb 100644 --- a/src/main/java/com/jcloisterzone/game/capability/SheepCapability.java +++ b/src/main/java/com/jcloisterzone/game/capability/SheepCapability.java @@ -1,9 +1,6 @@ package com.jcloisterzone.game.capability; -import com.jcloisterzone.board.pointer.FeaturePointer; import com.jcloisterzone.game.Capability; -import com.jcloisterzone.game.Token; -import com.jcloisterzone.game.capability.SheepCapability.SheepToken; import com.jcloisterzone.game.state.GameState; import io.vavr.collection.*; @@ -12,22 +9,7 @@ /** * @model Map> - list of placed sheep tokens */ -public class SheepCapability extends Capability>> { - - public enum SheepToken implements Token { - SHEEP_1X, - SHEEP_2X, - SHEEP_3X, - SHEEP_4X, - WOLF; - - public int sheepCount() { - if (this == WOLF) { - throw new IllegalStateException(); - } - return this.ordinal() + 1; - } - } +public class SheepCapability extends Capability { private static final long serialVersionUID = 1L; @@ -44,11 +26,11 @@ public int sheepCount() { @Override public GameState onStartGame(GameState state) { - return setModel(state, HashMap.empty()); + return setModel(state, new SheepCapabilityModel(HashMap.empty(), List.empty())); } public Vector getBagConent(GameState state) { - Map bagCount = getModel(state) + Map bagCount = getModel(state).getPlacedTokens() .values() .flatMap(Function.identity()) .foldLeft(SHEEP_TOKEN_COUNT, (tokenCount, token) -> { diff --git a/src/main/java/com/jcloisterzone/game/capability/SheepCapabilityModel.java b/src/main/java/com/jcloisterzone/game/capability/SheepCapabilityModel.java new file mode 100644 index 000000000..a41c837d4 --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/capability/SheepCapabilityModel.java @@ -0,0 +1,33 @@ +package com.jcloisterzone.game.capability; + +import com.jcloisterzone.board.pointer.FeaturePointer; +import com.jcloisterzone.board.pointer.MeeplePointer; +import io.vavr.collection.List; +import io.vavr.collection.Map; + +public class SheepCapabilityModel { + + private final Map> placedTokens; + private final List unresolvedFlocks; + + public SheepCapabilityModel(Map> placedTokens, List unresolvedFlocks) { + this.placedTokens = placedTokens; + this.unresolvedFlocks = unresolvedFlocks; + } + + public Map> getPlacedTokens() { + return placedTokens; + } + + public SheepCapabilityModel setPlacedTokens(Map> placedTokens) { + return new SheepCapabilityModel(placedTokens, this.unresolvedFlocks); + } + + public List getUnresolvedFlocks() { + return unresolvedFlocks; + } + + public SheepCapabilityModel setUnresolvedFlocks(List unresolvedFlocks) { + return new SheepCapabilityModel(this.placedTokens, unresolvedFlocks); + } +} diff --git a/src/main/java/com/jcloisterzone/game/capability/SheepToken.java b/src/main/java/com/jcloisterzone/game/capability/SheepToken.java new file mode 100644 index 000000000..21b0cc4cb --- /dev/null +++ b/src/main/java/com/jcloisterzone/game/capability/SheepToken.java @@ -0,0 +1,18 @@ +package com.jcloisterzone.game.capability; + +import com.jcloisterzone.game.Token; + +public enum SheepToken implements Token { + SHEEP_1X, + SHEEP_2X, + SHEEP_3X, + SHEEP_4X, + WOLF; + + public int sheepCount() { + if (this == WOLF) { + throw new IllegalStateException(); + } + return this.ordinal() + 1; + } +} diff --git a/src/main/java/com/jcloisterzone/game/phase/ShepherdPhase.java b/src/main/java/com/jcloisterzone/game/phase/ShepherdPhase.java index b4e69110c..c4aacdab5 100644 --- a/src/main/java/com/jcloisterzone/game/phase/ShepherdPhase.java +++ b/src/main/java/com/jcloisterzone/game/phase/ShepherdPhase.java @@ -1,5 +1,6 @@ package com.jcloisterzone.game.phase; +import com.jcloisterzone.Player; import com.jcloisterzone.action.FlockAction; import com.jcloisterzone.board.pointer.FeaturePointer; import com.jcloisterzone.board.pointer.MeeplePointer; @@ -12,7 +13,8 @@ import com.jcloisterzone.figure.Meeple; import com.jcloisterzone.figure.Shepherd; import com.jcloisterzone.game.capability.SheepCapability; -import com.jcloisterzone.game.capability.SheepCapability.SheepToken; +import com.jcloisterzone.game.capability.SheepCapabilityModel; +import com.jcloisterzone.game.capability.SheepToken; import com.jcloisterzone.game.state.ActionsState; import com.jcloisterzone.game.state.GameState; import com.jcloisterzone.game.state.PlacedTile; @@ -50,42 +52,60 @@ private Seq getClosedFieldsWithShepherd(GameState state) { @Override public StepResult enter(GameState state) { PlacedTile lastPlaced = state.getLastPlaced(); - Seq closedFieldsWithShepherd = getClosedFieldsWithShepherd(state); - Shepherd shepherd = (Shepherd) state.getTurnPlayer().getSpecialMeeples(state).find(m -> m instanceof Shepherd).getOrNull(); - FeaturePointer shepherdFp = shepherd.getDeployment(state); + Seq shepherds = state.getTurnPlayer().getSpecialMeeples(state).filter(m -> m instanceof Shepherd).map(m -> (Shepherd) m); + List unresolvedFlocks = List.empty(); + java.util.Set unresolvedFields = new java.util.HashSet<>(); - boolean isFieldExtended = false; - boolean alreadyExpanded = false; + for (var shepherd : shepherds) { + FeaturePointer shepherdFp = shepherd.getDeployment(state); - if (shepherdFp != null) { - boolean isJustPlaced = lastPlaced.getPosition().equals(shepherdFp.getPosition()); + boolean isFieldExtended = false; + boolean alreadyExpanded = false; - Field field = (Field) state.getFeature(shepherdFp); - isFieldExtended = state.getTileFeatures2(lastPlaced.getPosition()).map(Tuple2::_2).contains(field); + if (shepherdFp != null) { + boolean isJustPlaced = lastPlaced.getPosition().equals(shepherdFp.getPosition()); + + Field field = (Field) state.getFeature(shepherdFp); + isFieldExtended = state.getTileFeatures2(lastPlaced.getPosition()).map(Tuple2::_2).contains(field); - if (isJustPlaced) { - state = expandFlock(state, shepherdFp); - alreadyExpanded = true; + if (isJustPlaced) { + state = expandFlock(state, shepherdFp); + alreadyExpanded = true; + } + + if (!alreadyExpanded && isFieldExtended && !unresolvedFields.contains(field)) { + unresolvedFlocks = unresolvedFlocks.append(new MeeplePointer(shepherdFp, shepherd.getId())); + unresolvedFields.add(field); + } } } - if (shepherdFp == null || !isFieldExtended || alreadyExpanded) { - for (Field field : closedFieldsWithShepherd) { + for (Field field : getClosedFieldsWithShepherd(state)) { + if (!unresolvedFields.contains(field)) { state = scoreFlock(state, field); } + } + + if (unresolvedFlocks.isEmpty()) { return next(state); } - FlockAction action = new FlockAction(new MeeplePointer(shepherdFp, shepherd.getId())); - ActionsState as = new ActionsState(state.getTurnPlayer(), action, false); - return promote(state.setPlayerActions(as)); + SheepCapability cap = state.getCapabilities().get(SheepCapability.class); + List _unresolvedFlocks = unresolvedFlocks; + state = cap.updateModel(state, model -> model.setUnresolvedFlocks(_unresolvedFlocks)); + return prepareAction(state); } @PhaseMessageHandler public StepResult handleFlockMessage(GameState state, FlockMessage msg) { - Shepherd shepherd = (Shepherd) state.getTurnPlayer().getSpecialMeeples(state).find(m -> m instanceof Shepherd).getOrNull(); - FeaturePointer shepherdFp = shepherd.getDeployment(state); + SheepCapability cap = state.getCapabilities().get(SheepCapability.class); + List unresolvedFlocks = cap.getModel(state).getUnresolvedFlocks(); + + MeeplePointer mp = unresolvedFlocks.get(); + FeaturePointer shepherdFp = mp.asFeaturePointer(); + var deployment = state.getDeployedMeeples().find(t -> t._1 instanceof Shepherd && t._2.equals(shepherdFp)).get(); + Shepherd shepherd = (Shepherd) deployment._1; if (msg.getValue() == FlockOption.EXPAND) { state = expandFlock(state, shepherdFp); @@ -93,30 +113,45 @@ public StepResult handleFlockMessage(GameState state, FlockMessage msg) { for (Field field : closedFieldsWithShepherd) { state = scoreFlock(state, field); } - return next(state); } else { - return scoreFlock(state, shepherdFp); + state = scoreFlock(state, shepherdFp); } + + state = cap.updateModel(state, model -> model.setUnresolvedFlocks(unresolvedFlocks.pop())); + return prepareAction(state); } + private StepResult prepareAction(GameState state) { + SheepCapability cap = state.getCapabilities().get(SheepCapability.class); + List unresolvedFlocks = cap.getModel(state).getUnresolvedFlocks(); + + if (unresolvedFlocks.isEmpty()) { + return next(state); + } + + FlockAction action = new FlockAction(unresolvedFlocks.get()); + ActionsState as = new ActionsState(state.getTurnPlayer(), action, false); + return promote(state.setPlayerActions(as)); + } + private Map getShepherdsOnField(GameState state, Field field) { return state.getDeployedMeeples().filter((m, fp) -> m instanceof Shepherd && field.getPlaces().contains(fp)); } - private StepResult scoreFlock(GameState state, FeaturePointer shepherdFp) { + private GameState scoreFlock(GameState state, FeaturePointer shepherdFp) { Field field = (Field) state.getFeature(shepherdFp); state = scoreFlock(state, field); Seq closedFieldsWithShepherd = getClosedFieldsWithShepherd(state); for (Field closedField : closedFieldsWithShepherd) { state = scoreFlock(state, closedField); } - state = clearActions(state); - return next(state); + return clearActions(state); } private GameState scoreFlock(GameState state, Field field) { SheepCapability cap = state.getCapabilities().get(SheepCapability.class); - Map> placedTokens = cap.getModel(state); + SheepCapabilityModel model = cap.getModel(state); + Map> placedTokens = model.getPlacedTokens(); Map shepherdsOnField = getShepherdsOnField(state, field); Seq tokens = shepherdsOnField.values().flatMap(fp -> placedTokens.get(fp).get()); int points = tokens.map(SheepToken::sheepCount).sum().intValue(); @@ -130,16 +165,21 @@ private GameState scoreFlock(GameState state, Field field) { return new ExprItem(size, "sheep." + t._1.name(), t._1.sheepCount() * size); }); PointsExpression expr = new PointsExpression("flock", exprs); + java.util.Set scoredPlayers = new java.util.HashSet<>(); // when multiple shepherds of one player is placed on the field, then score it only once for (Tuple2 t : shepherdsOnField) { - Shepherd m = (Shepherd) t._1; - receivedPoints = receivedPoints.append(new ReceivedPoints(expr, m.getPlayer(), m.getDeployment(state))); - state = (new UndeployMeeple(m, false)).apply(state); + Shepherd shepherd = (Shepherd) t._1; + Player player = shepherd.getPlayer(); + if (!scoredPlayers.contains(player)) { + receivedPoints = receivedPoints.append(new ReceivedPoints(expr, player, shepherd.getDeployment(state))); + scoredPlayers.add(player); + } + state = (new UndeployMeeple(shepherd, false)).apply(state); } state = (new AddPoints(receivedPoints, false)).apply(state); return cap.setModel( state, - shepherdsOnField.values().foldLeft(placedTokens, (acc, fp) -> acc.remove(fp)) + model.setPlacedTokens(shepherdsOnField.values().foldLeft(placedTokens, (acc, fp) -> acc.remove(fp))) ); } @@ -158,15 +198,17 @@ private GameState expandFlock(GameState state, FeaturePointer shepherdFp) { } // remove all placed tokens from capability model - state = cap.updateModel(state, placedTokens -> - shepherdsOnField.values().foldLeft(placedTokens, (acc, fp) -> acc.remove(fp)) - ); + state = cap.updateModel(state, model -> { + var placedTokens = model.getPlacedTokens(); + return model.setPlacedTokens(shepherdsOnField.values().foldLeft(placedTokens, (acc, fp) -> acc.remove(fp))); + }); return state; } - return state.getCapabilities().get(SheepCapability.class).updateModel(state, placedTokens -> { - return placedTokens.put(shepherdFp, List.of(drawnToken), (l1, l2) -> l1.appendAll(l2)); + return state.getCapabilities().get(SheepCapability.class).updateModel(state, model -> { + var placedTokens = model.getPlacedTokens(); + return model.setPlacedTokens(placedTokens.put(shepherdFp, List.of(drawnToken), (l1, l2) -> l1.appendAll(l2))); }); } diff --git a/src/main/java/com/jcloisterzone/reducers/UndeployMeeple.java b/src/main/java/com/jcloisterzone/reducers/UndeployMeeple.java index b3aece550..a7878404b 100644 --- a/src/main/java/com/jcloisterzone/reducers/UndeployMeeple.java +++ b/src/main/java/com/jcloisterzone/reducers/UndeployMeeple.java @@ -36,7 +36,7 @@ public GameState apply(GameState state) { } if (meeple instanceof Shepherd) { - state = state.mapCapabilityModel(SheepCapability.class, tokens -> tokens.remove(source)); + state = state.mapCapabilityModel(SheepCapability.class, model -> model.setPlacedTokens(model.getPlacedTokens().remove(source))); } NeutralFiguresState nfState = state.getNeutralFigures();