Skip to content

Commit

Permalink
Merge pull request #59 from BlazingTwist/27-fix-move-simulator
Browse files Browse the repository at this point in the history
27 fix move simulator
  • Loading branch information
BlazingTwist authored Jan 28, 2024
2 parents 6a1fda8 + 2078657 commit 413b281
Show file tree
Hide file tree
Showing 22 changed files with 374 additions and 226 deletions.
51 changes: 31 additions & 20 deletions src/main/java/jchess/common/BaseChessGame.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import jchess.common.components.TileComponent;
import jchess.common.events.BoardClickedEvent;
import jchess.common.events.BoardInitializedEvent;
import jchess.common.events.ComputeAttackInfoEvent;
import jchess.common.events.GameOverEvent;
import jchess.common.events.OfferPieceSelectionEvent;
import jchess.common.events.PieceMoveEvent;
import jchess.common.events.PieceOfferSelectedEvent;
import jchess.common.events.RenderEvent;
import jchess.common.moveset.MoveIntention;
import jchess.common.state.IRevertibleState;
import jchess.common.state.StateManager;
import jchess.ecs.EcsEventManager;
import jchess.ecs.Entity;
import jchess.ecs.EntityManager;
Expand All @@ -27,6 +28,7 @@ public abstract class BaseChessGame implements IChessGame {
private static final Logger logger = LoggerFactory.getLogger(BaseChessGame.class);
protected final EntityManager entityManager;
protected final EcsEventManager eventManager;
protected final StateManager stateManager;
protected final int numPlayers;
protected final PieceStore pieceStore;
protected final IPieceLayoutProvider pieceLayout;
Expand All @@ -36,6 +38,7 @@ public abstract class BaseChessGame implements IChessGame {
public BaseChessGame(int numPlayers, PieceStore pieceStore, IPieceLayoutProvider pieceLayout) {
this.entityManager = new EntityManager();
this.eventManager = new EcsEventManager();
this.stateManager = new StateManager();
this.numPlayers = numPlayers;
this.pieceStore = pieceStore;
this.pieceLayout = pieceLayout;
Expand All @@ -45,16 +48,25 @@ public BaseChessGame(int numPlayers, PieceStore pieceStore, IPieceLayoutProvider
eventManager.registerEvent(new BoardInitializedEvent());
eventManager.registerEvent(new PieceMoveEvent());

ComputeAttackInfoEvent computeAttackInfoEvent = new ComputeAttackInfoEvent();
eventManager.registerEvent(computeAttackInfoEvent);
computeAttackInfoEvent.addListener(_void -> TileComponent.updateAttackInfo(this));

BoardClickedEvent boardClickedEvent = new BoardClickedEvent();
eventManager.registerEvent(boardClickedEvent);
boardClickedEvent.addListener(point -> onBoardClicked(point.x, point.y));

eventManager.registerEvent(new OfferPieceSelectionEvent());
eventManager.registerEvent(new PieceOfferSelectedEvent());

stateManager.registerState(new IRevertibleState() {
@Override
public void saveState() {
// attackInfo is temporary data, no need to save it
}

@Override
public void revertState() {
// on revert, "simply" recompute the attackInfo
TileComponent.updateAttackInfo(BaseChessGame.this);
}
});
}

protected abstract void generateBoard();
Expand Down Expand Up @@ -83,7 +95,7 @@ protected void onBoardClicked(int x, int y) {

// show the tiles this piece can move to
boolean isActivePiece = clickedEntity.piece.identifier.ownerId() == activePlayerId;
clickedEntity.findValidMoves(true).forEach(move -> createMoveToMarker(move, isActivePiece));
clickedEntity.findValidMoves(this, true).forEach(move -> createMoveToMarker(move, isActivePiece));
createSelectionMarker(clickedEntity);

long endTime = System.currentTimeMillis();
Expand Down Expand Up @@ -154,7 +166,7 @@ protected void checkGameOver() {
// Game is over if the current player is unable to make any moves.
boolean gameOver = entityManager.getEntities().stream()
.filter(entity -> entity.piece != null && entity.piece.identifier.ownerId() == activePlayerId)
.allMatch(entity -> entity.findValidMoves(true).findAny().isEmpty());
.allMatch(entity -> entity.findValidMoves(this, true).findAny().isEmpty());

if (gameOver) {
logger.info("Game Over! Losing Player: {}", activePlayerId);
Expand Down Expand Up @@ -219,30 +231,29 @@ public EcsEventManager getEventManager() {
return eventManager;
}

@Override
public StateManager getStateManager() {
return stateManager;
}

@Override
public int getActivePlayerId() {
return activePlayerId;
}

@Override
public void movePiece(Entity fromTile, Entity toTile, Class<?> moveType) {
toTile.piece = fromTile.piece;
fromTile.piece = null;
public int getNumPlayers() {
return numPlayers;
}

@Override
public void notifyPieceMove(Entity fromTile, Entity toTile, Class<?> moveType) {
eventManager.getEvent(PieceMoveEvent.class).fire(new PieceMoveEvent.PieceMove(fromTile, toTile, moveType));
eventManager.getEvent(ComputeAttackInfoEvent.class).fire(null);

// end turn
activePlayerId = (activePlayerId + 1) % numPlayers;
checkGameOver();
TileComponent.updateAttackInfo(this);
}

@Override
public void movePieceStationary(Entity tile, Class<?> moveType) {
eventManager.getEvent(PieceMoveEvent.class).fire(new PieceMoveEvent.PieceMove(tile, tile, moveType));
eventManager.getEvent(ComputeAttackInfoEvent.class).fire(null);

// end turn
public void endTurn() {
activePlayerId = (activePlayerId + 1) % numPlayers;
checkGameOver();
}
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/jchess/common/IChessGame.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jchess.common;

import dx.schema.types.PieceType;
import jchess.common.state.StateManager;
import jchess.ecs.EcsEventManager;
import jchess.ecs.Entity;
import jchess.ecs.EntityManager;
Expand All @@ -10,15 +11,19 @@ public interface IChessGame {

EcsEventManager getEventManager();

StateManager getStateManager();

int getActivePlayerId();

int getNumPlayers();

void start();

void createPiece(Entity targetTile, PieceType pieceType, int ownerId);

void movePiece(Entity fromTile, Entity toTile, Class<?> moveType);
void notifyPieceMove(Entity fromTile, Entity toTile, Class<?> moveType);

void movePieceStationary(Entity Tile, Class<?> moveType);
void endTurn();

dx.schema.types.Entity applyPerspective(dx.schema.types.Entity tile, int playerIndex);
}
15 changes: 10 additions & 5 deletions src/main/java/jchess/common/components/PieceComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import jchess.common.moveset.ISpecialRuleProvider;
import jchess.common.moveset.MoveIntention;
import jchess.common.moveset.NormalMove;
import jchess.common.state.StateManager;
import jchess.ecs.Entity;
import jchess.el.CompiledTileExpression;
import jchess.el.v2.ExpressionCompiler;
Expand Down Expand Up @@ -42,30 +43,33 @@ public void addSpecialMoves(ISpecialRuleProvider... specialRuleProviders) {
);
}

public Stream<MoveIntention> findValidMoves(Entity thisTile, boolean verifyKingSafe) {
public Stream<MoveIntention> findValidMoves(IChessGame game, Entity thisTile, boolean verifyKingSafe) {
if (thisTile.tile == null) return Stream.empty();

Stream<MoveIntention> moves = Stream.empty();
if (baseMoveSet != null) {
moves = Stream.concat(
moves,
baseMoveSet.findTiles(thisTile).map(toTile -> NormalMove.getMove(game, thisTile, toTile))
baseMoveSet.findTiles(thisTile).map(toTile -> NormalMove.getMove(this.game, thisTile, toTile))
);
}
for (ISpecialRule specialRule : specialMoveSet) {
moves = specialRule.getSpecialMoves(thisTile, moves);
}

if (verifyKingSafe) {
moves = verifyKingSafe(moves);
moves = verifyKingSafe(game, moves);
}

return moves;
}

private Stream<MoveIntention> verifyKingSafe(Stream<MoveIntention> allMoves) {
private Stream<MoveIntention> verifyKingSafe(IChessGame game, Stream<MoveIntention> allMoves) {
int ownPlayerId = identifier.ownerId();

StateManager stateManager = game.getStateManager();
stateManager.saveState();

return allMoves.filter(move -> {
MoveIntention.IMoveSimulator simulator = move.moveSimulator();
simulator.simulate();
Expand All @@ -79,11 +83,12 @@ private Stream<MoveIntention> verifyKingSafe(Stream<MoveIntention> allMoves) {
boolean kingInCheckAfterMove = game.getEntityManager().getEntities().parallelStream()
.filter(entity -> entity.piece != null && entity.piece.identifier.ownerId() != ownPlayerId)
.anyMatch(entity -> entity
.findValidMoves(false)
.findValidMoves(game, false)
.anyMatch(moveTo -> moveTo.displayTile() == ownKing)
);

simulator.revert();
stateManager.revertState();

return !kingInCheckAfterMove;
});
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/jchess/common/components/TileComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static void updateAttackInfo(IChessGame game) {
game.getEntityManager().getEntities().parallelStream()
.filter(entity -> entity.tile != null && entity.piece != null)
.forEach(entity -> entity
.findValidMoves(false)
.findValidMoves(game, false)
.forEach(move -> {
assert move.displayTile().tile != null;
move.displayTile().tile.attackingPieces.add(entity);
Expand Down

This file was deleted.

16 changes: 16 additions & 0 deletions src/main/java/jchess/common/moveset/MoveIntention.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
package jchess.common.moveset;

import jchess.common.IChessGame;
import jchess.ecs.Entity;

public record MoveIntention(Entity displayTile, Runnable onClick, IMoveSimulator moveSimulator) {

/**
* This is a utility Method for constructing a MoveIntention in the specific use-case where the moveSimulator matches the actual move.
*/
public static MoveIntention fromMoveSimulator(IChessGame game, Entity displayTile, IMoveSimulator moveSimulator) {
return new MoveIntention(
displayTile,
() -> {
moveSimulator.simulate();
game.endTurn();
},
moveSimulator
);
}

public interface IMoveSimulator {
void simulate();

void revert();
}
}
25 changes: 17 additions & 8 deletions src/main/java/jchess/common/moveset/NormalMove.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@

public class NormalMove {
public static MoveIntention getMove(IChessGame game, Entity fromTile, Entity toTile) {
return new MoveIntention(
toTile,
() -> game.movePiece(fromTile, toTile, NormalMove.class),
new NormalMoveSimulator(toTile.piece, fromTile, toTile)
);
NormalMoveSimulator moveSimulator = new NormalMoveSimulator(game, fromTile, toTile, NormalMove.class);
return MoveIntention.fromMoveSimulator(game, toTile, moveSimulator);
}

private record NormalMoveSimulator(
PieceComponent capturedPiece, Entity fromTile, Entity toTile
) implements MoveIntention.IMoveSimulator {
public static class NormalMoveSimulator implements MoveIntention.IMoveSimulator {
private final IChessGame game;
private final Entity fromTile;
private final Entity toTile;
private final Class<?> moveType;
private final PieceComponent capturedPiece;

public NormalMoveSimulator(IChessGame game, Entity fromTile, Entity toTile, Class<?> moveType) {
this.game = game;
this.fromTile = fromTile;
this.toTile = toTile;
this.moveType = moveType;
this.capturedPiece = toTile.piece;
}

@Override
public void simulate() {
toTile.piece = fromTile.piece;
fromTile.piece = null;
game.notifyPieceMove(fromTile, toTile, moveType);
}

@Override
Expand Down
Loading

0 comments on commit 413b281

Please sign in to comment.