Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Obelisk special figure #432

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/com/jcloisterzone/engine/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ private GameSetup createSetupFromMessage(GameSetupMessage setupMsg) {
meeples = addMeeples(meeples, setupMsg, "shepherd", Shepherd.class);
meeples = addMeeples(meeples, setupMsg, "ringmaster", Ringmaster.class);

meeples = addMeeples(meeples, setupMsg, "obelisk", Obelisk.class);

Set<Class<? extends Capability<?>>> capabilities = HashSet.empty();
capabilities = addCapabilities(capabilities, setupMsg,"abbot", AbbotCapability.class);
capabilities = addCapabilities(capabilities, setupMsg,"barn", BarnCapability.class);
Expand Down Expand Up @@ -148,6 +150,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,"obelisk", ObeliskCapability.class);

Map<Rule, Object> rules = HashMap.empty();
if (setupMsg.getElements().containsKey("farmers")) {
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/com/jcloisterzone/figure/Obelisk.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.jcloisterzone.figure;

import com.jcloisterzone.Player;
import com.jcloisterzone.board.pointer.FeaturePointer;
import com.jcloisterzone.feature.Field;
import com.jcloisterzone.feature.Structure;
import com.jcloisterzone.game.state.GameState;

public class Obelisk extends Special {

private static final long serialVersionUID = 1L;

public Obelisk(String id, Player player) {
super(id, player);
}

@Override
public boolean canBeEatenByDragon(GameState state) {
return false;
}

@Override
public DeploymentCheckResult isDeploymentAllowed(GameState state, FeaturePointer fp, Structure feature) {
if (!(feature instanceof Field)) {
return new DeploymentCheckResult("The obelisk must be placed only on a field.");
}
return super.isDeploymentAllowed(state, fp, feature);
}
}
159 changes: 159 additions & 0 deletions src/main/java/com/jcloisterzone/game/capability/ObeliskCapability.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package com.jcloisterzone.game.capability;

import com.jcloisterzone.Player;
import com.jcloisterzone.action.MeepleAction;
import com.jcloisterzone.board.Corner;
import com.jcloisterzone.board.Location;
import com.jcloisterzone.board.Position;
import com.jcloisterzone.board.pointer.FeaturePointer;
import com.jcloisterzone.event.ExprItem;
import com.jcloisterzone.event.PointsExpression;
import com.jcloisterzone.event.ScoreEvent;
import com.jcloisterzone.event.ScoreEvent.ReceivedPoints;
import com.jcloisterzone.feature.Feature;
import com.jcloisterzone.feature.Field;
import com.jcloisterzone.feature.Scoreable;
import com.jcloisterzone.figure.Obelisk;
import com.jcloisterzone.game.Capability;
import com.jcloisterzone.game.ScoreFeatureReducer;
import com.jcloisterzone.game.state.GameState;
import com.jcloisterzone.game.state.PlacedTile;
import com.jcloisterzone.reducers.AddPoints;
import com.jcloisterzone.reducers.UndeployMeeple;

import io.vavr.Predicates;
import io.vavr.Tuple2;
import io.vavr.collection.HashMap;
import io.vavr.collection.Map;
import io.vavr.collection.Set;
import io.vavr.collection.HashSet;
import io.vavr.collection.Stream;

/**
* @model FeaturePointer: ptr to just placed Obelisk
*/
public final class ObeliskCapability extends Capability<FeaturePointer> {

private static final long serialVersionUID = 1L;

public static final Set<Position> TILES_ARROUND_OBELISK = HashSet.of(
new Position(-2, -2),
new Position(-2, -1),
new Position(-2, 0),
new Position(-2, 1),
new Position(-1, -2),
new Position(-1, -1),
new Position(-1, 0),
new Position(-1, 1),
new Position( 0, -2),
new Position( 0, -1),
new Position( 0, 0),
new Position( 0, 1),
new Position( 1, -2),
new Position( 1, -1),
new Position( 1, 0),
new Position( 1, 1)
).toSet();

@Override
public GameState onActionPhaseEntered(GameState state) {
Player player = state.getPlayerActions().getPlayer();

Obelisk obelisk = player.getMeepleFromSupply(state, Obelisk.class);
if (obelisk == null) {
return state;
}

Position pos = state.getLastPlaced().getPosition();

// By convention obelisk action contains feature pointer which points to
// left top corner of tile intersection
// |
// |
// ----+----
// | XX
// | XX
Set<FeaturePointer> options = Stream.of(
pos,
new Position(pos.x + 1, pos.y),
new Position(pos.x, pos.y + 1),
new Position(pos.x + 1, pos.y + 1)
)
.map(p -> getCornerFeature(state, p))
.filter(Predicates.isNotNull())
.map(Tuple2::_1)
.toSet();

if (options.isEmpty()) {
return state;
}

return state.appendAction(new MeepleAction(obelisk, options));
}

@Override
public GameState onTurnPartCleanUp(GameState state) {
return setModel(state, null);
}

private boolean containsCorner(Tuple2<FeaturePointer, Feature> t, Corner c) {
return t != null && t._1.getLocation().getCorners().contains(c);
}

private Tuple2<FeaturePointer, Feature> getCornerFeature(GameState state, Position pos) {
Tuple2<FeaturePointer, Feature> t;
t = state.getFeaturePartOf2(new FeaturePointer(new Position(pos.x - 1, pos.y - 1), Field.class, Location.SL));
if (!containsCorner(t, Corner.SE)) return null;
t = state.getFeaturePartOf2(new FeaturePointer(new Position(pos.x, pos.y - 1), Field.class, Location.WL));
if (!containsCorner(t, Corner.SW)) return null;
t = state.getFeaturePartOf2(new FeaturePointer(new Position(pos.x - 1, pos.y), Field.class, Location.EL));
if (!containsCorner(t, Corner.NE)) return null;
t = state.getFeaturePartOf2(new FeaturePointer(pos, Field.class, Location.NL));
if (!containsCorner(t, Corner.NW)) return null;
return t;
}

@Override
public GameState onFinalScoring(GameState state) {
state = scoreObelisks(state, true);
return state;
}

@Override
public GameState onTurnScoring(GameState state, HashMap<Scoreable, ScoreFeatureReducer> completed) {
state = scoreObelisks(state, false);
return state;
}

public GameState scoreObelisks(GameState state, Boolean isFinal) {
Set<Obelisk> deployedObelisks = getDeployedObelisks(state).keySet();

int tilesRequired = TILES_ARROUND_OBELISK.length();

for(Obelisk obelisk : deployedObelisks) {
Position position = obelisk.getPosition(state);
Set<PlacedTile> tiles = getObeliskTiles(state,position);
if (isFinal || (tiles.length() == tilesRequired)) {
ReceivedPoints rp = new ReceivedPoints(new PointsExpression(isFinal ? "obelisk.incomplete" : "obelisk", new ExprItem(tiles.length(), "tiles", tiles.length())), obelisk.getPlayer() , obelisk.getDeployment(state));
state = (new AddPoints(rp, true, true)).apply(state);
state = (new UndeployMeeple(obelisk, false)).apply(state);
}
}
return state;

}

private Map<Obelisk, FeaturePointer> getDeployedObelisks(GameState state) {
return state.getDeployedMeeples()
.filter((m, fp) -> m instanceof Obelisk)
.mapKeys(m -> (Obelisk) m);
}

private Set<PlacedTile> getObeliskTiles(GameState state, Position pos) {
return TILES_ARROUND_OBELISK
.map(
offset -> state.getPlacedTile(pos.add(offset))
)
.filter(locTile -> locTile != null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.jcloisterzone.game.Capability;
import com.jcloisterzone.game.Rule;
import com.jcloisterzone.game.capability.BarnCapability;
import com.jcloisterzone.game.capability.ObeliskCapability;
import com.jcloisterzone.game.capability.MonasteriesCapability;
import com.jcloisterzone.game.capability.PortalCapability;
import com.jcloisterzone.game.state.ActionsState;
Expand Down Expand Up @@ -109,6 +110,11 @@ private Set<FeaturePointer> getMeepleAvailableStructures(GameState state, Meeple
// no meeples except Shepherd is on feature
return true;
}
// Obelisk is not interacting with other meeples
if (meeples.find(m -> !(m instanceof Obelisk)).isEmpty()) {
// no meeples except Obelisk is on feature
return true;
}
if (struct instanceof Road) {
Road road = (Road) struct;
if (road.isLabyrinth(state)) {
Expand Down Expand Up @@ -215,6 +221,9 @@ public StepResult handleDeployMeeple(GameState state, DeployMeepleMessage msg) {
if (meeple instanceof Barn) {
state = state.setCapabilityModel(BarnCapability.class, fp);
}
if (meeple instanceof Obelisk) {
state = state.setCapabilityModel(ObeliskCapability.class, fp);
}

state = clearActions(state);
return next(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.jcloisterzone.feature.Feature;
import com.jcloisterzone.figure.Barn;
import com.jcloisterzone.figure.Meeple;
import com.jcloisterzone.figure.Obelisk;
import com.jcloisterzone.figure.Shepherd;
import com.jcloisterzone.game.state.GameState;
import com.jcloisterzone.game.state.NeutralFiguresState;
Expand Down Expand Up @@ -43,7 +44,7 @@ public GameState apply(GameState state) {
for (Tuple2<Meeple, FeaturePointer> t : state
.getDeployedMeeples()
.filter(t -> fps.contains(t._2))
.filter(t -> !(t._1 instanceof Barn) && !(t._1 instanceof Shepherd))
.filter(t -> !(t._1 instanceof Barn) && !(t._1 instanceof Shepherd) && !(t._1 instanceof Obelisk))
) {
meeples.add(t._1);
events.add(
Expand Down