Skip to content

Commit

Permalink
multiple shepherds
Browse files Browse the repository at this point in the history
closes #403
  • Loading branch information
farin committed Aug 22, 2021
1 parent b46021e commit 47e5ce2
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 59 deletions.
4 changes: 2 additions & 2 deletions src/main/java/com/jcloisterzone/engine/StateGsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,7 +124,7 @@ public JsonElement serialize(Game game, Type typeOfSrc, JsonSerializationContext

SheepCapability sheepCap = state.getCapabilities().get(SheepCapability.class);
if (sheepCap != null) {
Map<FeaturePointer, List<SheepToken>> sheepModel = sheepCap.getModel(state);
Map<FeaturePointer, List<SheepToken>> sheepModel = sheepCap.getModel(state).getPlacedTokens();
JsonObject jsonItem = new JsonObject();
JsonArray jsonFlocks = new JsonArray();
sheepModel.forEach((fp, tokens) -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;

Expand All @@ -12,22 +9,7 @@
/**
* @model Map<Position, List<SheepToken>> - list of placed sheep tokens
*/
public class SheepCapability extends Capability<Map<FeaturePointer, List<SheepToken>>> {

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<SheepCapabilityModel> {

private static final long serialVersionUID = 1L;

Expand All @@ -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<SheepToken> getBagConent(GameState state) {
Map<SheepToken, Integer> bagCount = getModel(state)
Map<SheepToken, Integer> bagCount = getModel(state).getPlacedTokens()
.values()
.flatMap(Function.identity())
.foldLeft(SHEEP_TOKEN_COUNT, (tokenCount, token) -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<FeaturePointer, List<SheepToken>> placedTokens;
private final List<MeeplePointer> unresolvedFlocks;

public SheepCapabilityModel(Map<FeaturePointer, List<SheepToken>> placedTokens, List<MeeplePointer> unresolvedFlocks) {
this.placedTokens = placedTokens;
this.unresolvedFlocks = unresolvedFlocks;
}

public Map<FeaturePointer, List<SheepToken>> getPlacedTokens() {
return placedTokens;
}

public SheepCapabilityModel setPlacedTokens(Map<FeaturePointer, List<SheepToken>> placedTokens) {
return new SheepCapabilityModel(placedTokens, this.unresolvedFlocks);
}

public List<MeeplePointer> getUnresolvedFlocks() {
return unresolvedFlocks;
}

public SheepCapabilityModel setUnresolvedFlocks(List<MeeplePointer> unresolvedFlocks) {
return new SheepCapabilityModel(this.placedTokens, unresolvedFlocks);
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/jcloisterzone/game/capability/SheepToken.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
112 changes: 77 additions & 35 deletions src/main/java/com/jcloisterzone/game/phase/ShepherdPhase.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -50,73 +52,106 @@ private Seq<Field> getClosedFieldsWithShepherd(GameState state) {
@Override
public StepResult enter(GameState state) {
PlacedTile lastPlaced = state.getLastPlaced();
Seq<Field> closedFieldsWithShepherd = getClosedFieldsWithShepherd(state);

Shepherd shepherd = (Shepherd) state.getTurnPlayer().getSpecialMeeples(state).find(m -> m instanceof Shepherd).getOrNull();
FeaturePointer shepherdFp = shepherd.getDeployment(state);
Seq<Shepherd> shepherds = state.getTurnPlayer().getSpecialMeeples(state).filter(m -> m instanceof Shepherd).map(m -> (Shepherd) m);
List<MeeplePointer> unresolvedFlocks = List.empty();
java.util.Set<Field> 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<MeeplePointer> _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<MeeplePointer> 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);
Seq<Field> closedFieldsWithShepherd = getClosedFieldsWithShepherd(state);
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<MeeplePointer> 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<Meeple, FeaturePointer> 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<Field> 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<FeaturePointer, List<SheepToken>> placedTokens = cap.getModel(state);
SheepCapabilityModel model = cap.getModel(state);
Map<FeaturePointer, List<SheepToken>> placedTokens = model.getPlacedTokens();
Map<Meeple, FeaturePointer> shepherdsOnField = getShepherdsOnField(state, field);
Seq<SheepToken> tokens = shepherdsOnField.values().flatMap(fp -> placedTokens.get(fp).get());
int points = tokens.map(SheepToken::sheepCount).sum().intValue();
Expand All @@ -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<Player> scoredPlayers = new java.util.HashSet<>(); // when multiple shepherds of one player is placed on the field, then score it only once
for (Tuple2<Meeple, FeaturePointer> 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)))
);
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 47e5ce2

Please sign in to comment.