diff --git a/src/main/java/jchess/common/BaseChessGame.java b/src/main/java/jchess/common/BaseChessGame.java index 45094b9..487cc87 100644 --- a/src/main/java/jchess/common/BaseChessGame.java +++ b/src/main/java/jchess/common/BaseChessGame.java @@ -3,6 +3,8 @@ import dx.schema.types.MarkerType; import dx.schema.types.PieceType; import jchess.common.components.MarkerComponent; +import jchess.common.components.PieceComponent; +import jchess.common.components.PieceIdentifier; import jchess.common.components.TileComponent; import jchess.common.events.BoardClickedEvent; import jchess.common.events.BoardInitializedEvent; @@ -73,6 +75,8 @@ public void revertState() { protected abstract Entity getEntityAtPosition(int x, int y); + protected abstract int getDirectionFromOwnerId(int ownerId); + protected void onBoardClicked(int x, int y) { Entity clickedEntity = getEntityAtPosition(x, y); if (clickedEntity == null) { @@ -90,7 +94,11 @@ protected void onBoardClicked(int x, int y) { if (clickedMarker.onMarkerClicked != null) { clickedMarker.onMarkerClicked.run(); } - } else if (clickedEntity.piece != null) { + eventManager.getEvent(RenderEvent.class).fire(null); + return; + } + + if (clickedEntity.piece != null) { long startTime = System.currentTimeMillis(); // show the tiles this piece can move to @@ -100,7 +108,9 @@ protected void onBoardClicked(int x, int y) { long endTime = System.currentTimeMillis(); logger.info("Computing valid moves with kingCheck took {} ms", endTime - startTime); - } else if (clickedEntity.tile != null) { + } + + if (clickedEntity.tile != null) { // show which pieces can move to the selected tile for (Entity attacker : clickedEntity.tile.attackingPieces) { createMoveFromMarker(attacker); @@ -212,6 +222,20 @@ protected MateResult computeMateResult() { } } + protected void placePiece(Entity tile, int ownerId, int direction, PieceStore.IPieceDefinitionProvider pieceProvider) { + PieceStore.PieceDefinition pieceDefinition = pieceProvider.getPieceDefinition(); + PieceIdentifier pieceIdentifier = new PieceIdentifier( + pieceProvider.getPieceType(), + pieceDefinition.shortName(), + ownerId, + direction + ); + + PieceComponent pieceComp = new PieceComponent(this, pieceIdentifier, pieceDefinition.baseMoves()); + pieceComp.addSpecialMoves(pieceDefinition.specialRules()); + tile.piece = pieceComp; + } + @Override public void start() { generateBoard(); @@ -241,6 +265,18 @@ public int getActivePlayerId() { return activePlayerId; } + @Override + public void createPiece(Entity targetTile, PieceType pieceType, int ownerId) { + PieceStore.IPieceDefinitionProvider pieceProvider = pieceStore.getPiece(pieceType); + if (pieceProvider == null) { + logger.error("unable to place piece with pieceType '" + pieceType + "'. PieceType does not exist."); + return; + } + + int direction = getDirectionFromOwnerId(ownerId); + placePiece(targetTile, ownerId, direction, pieceProvider); + } + @Override public int getNumPlayers() { return numPlayers; diff --git a/src/main/java/jchess/common/moveset/special/ShapeShifting.java b/src/main/java/jchess/common/moveset/special/ShapeShifting.java index 1dce915..619bf43 100644 --- a/src/main/java/jchess/common/moveset/special/ShapeShifting.java +++ b/src/main/java/jchess/common/moveset/special/ShapeShifting.java @@ -8,6 +8,7 @@ import jchess.ecs.Entity; import jchess.el.CompiledTileExpression; import jchess.el.v2.ExpressionCompiler; + import java.util.Set; import java.util.stream.Stream; diff --git a/src/main/java/jchess/gamemode/GameModeStore.java b/src/main/java/jchess/gamemode/GameModeStore.java index 7bd06ed..5e02936 100644 --- a/src/main/java/jchess/gamemode/GameModeStore.java +++ b/src/main/java/jchess/gamemode/GameModeStore.java @@ -2,6 +2,9 @@ import dx.schema.types.LayoutId; import jchess.common.IChessGame; +import jchess.gamemode.hex2p.Hex2pPieceLayouts; +import jchess.gamemode.hex2p.Hex2pPieces; +import jchess.gamemode.hex2p.Hex2PlayerGame; import jchess.gamemode.hex3p.Hex3PlayerGame; import jchess.gamemode.hex3p.Hex3pPieceLayouts; import jchess.gamemode.hex3p.Hex3pPieces; @@ -25,6 +28,13 @@ public class GameModeStore { )); } + for (Hex2pPieceLayouts pieceLayout : Hex2pPieceLayouts.values()) { + gameModeProviders.put(LayoutId.HEX_2_P.name() + "." + pieceLayout.name(), new GameModeProvider( + Hex2PlayerGame::new, LayoutId.HEX_2_P, "2 Player Hexagonal Chess - " + pieceLayout.name(), 2, + new PieceStore(Hex2pPieces.values()), pieceLayout + )); + } + for (Square2pPieceLayouts pieceLayout : Square2pPieceLayouts.values()) { gameModeProviders.put(LayoutId.SQUARE_2_P.name() + "." + pieceLayout.name(), new GameModeProvider( Square2PlayerGame::new, LayoutId.SQUARE_2_P, "2 Player Classic Chess - " + pieceLayout.name(), 2, diff --git a/src/main/java/jchess/gamemode/hex2p/Hex2PlayerGame.java b/src/main/java/jchess/gamemode/hex2p/Hex2PlayerGame.java new file mode 100644 index 0000000..14c773d --- /dev/null +++ b/src/main/java/jchess/gamemode/hex2p/Hex2PlayerGame.java @@ -0,0 +1,100 @@ +package jchess.gamemode.hex2p; + +import dx.schema.types.Vector2I; +import jchess.common.BaseChessGame; +import jchess.common.components.TileComponent; +import jchess.ecs.Entity; +import jchess.gamemode.IPieceLayoutProvider; +import jchess.gamemode.PieceStore; + +import java.awt.Point; +import java.util.Arrays; +import java.util.Objects; + +public class Hex2PlayerGame extends BaseChessGame { + + private static final int numTilesHorizontal = 6 + 5; + + private static final int numTilesVertical = 21; + + private final Entity[][] tiles = new Entity[numTilesVertical][numTilesHorizontal]; + + public Hex2PlayerGame(PieceStore pieceStore, IPieceLayoutProvider layoutProvider) { + super(2, pieceStore, layoutProvider); + } + + @Override + public dx.schema.types.Entity applyPerspective(dx.schema.types.Entity tile, int playerIndex) { + if (playerIndex < 0 || playerIndex > 1) { + throw new IllegalArgumentException("playerIndex must be 0 or 1, but was " + playerIndex); + } + if (playerIndex == 0) return tile; + if (tile == null || tile.getTile() == null || tile.getTile().getDisplayPos() == null) return tile; + + Vector2I displayPos = tile.getTile().getDisplayPos(); + displayPos.setX(numTilesHorizontal - displayPos.getX()); + displayPos.setY(numTilesVertical - displayPos.getY()); + return tile; + } + + @Override + protected Entity getEntityAtPosition(int x, int y) { + if (x < 0 || x >= numTilesHorizontal) return null; + if (y < 0 || y >= numTilesVertical) return null; + + return tiles[y][x]; + } + + @Override + protected int getDirectionFromOwnerId(int ownerId) { + return ownerId == 0 ? 0 : 180; + } + + @Override + protected void generateBoard() { + for (int y = 0; y < numTilesVertical; y++) { + Entity[] tileRow = tiles[y]; + final int x0; + final int x1; + + if (y < 5) { // top part + x0 = 5 - y; + x1 = x0 + 2 * (y + 1); + } else if (y < 16) { // middle part + x0 = (y % 2 == 0) ? 1 : 0; + x1 = numTilesHorizontal; + } else { // bottom part + x0 = y - 15; + x1 = x0 + (6 - x0) * 2; + } + + for (int x = x0; x < x1; x += 2) { + tileRow[x] = entityManager.createEntity(); + tileRow[x].tile = new TileComponent(new Point(x, y), y % 3); + } + } + + // second pass: generate neighbors + Arrays.stream(tiles).flatMap(Arrays::stream) + .filter(Objects::nonNull) + .forEach(entity -> { + TileComponent tile = entity.tile; + assert tile != null; + int x = tile.position.x; + int y = tile.position.y; + tile.neighborsByDirection.put(0, getEntityAtPosition(x, y - 2)); + tile.neighborsByDirection.put(30, getEntityAtPosition(x + 1, y - 3)); + tile.neighborsByDirection.put(60, getEntityAtPosition(x + 1, y - 1)); + tile.neighborsByDirection.put(90, getEntityAtPosition(x + 2, y)); + tile.neighborsByDirection.put(120, getEntityAtPosition(x + 1, y + 1)); + tile.neighborsByDirection.put(150, getEntityAtPosition(x + 1, y + 3)); + tile.neighborsByDirection.put(180, getEntityAtPosition(x, y + 2)); + tile.neighborsByDirection.put(210, getEntityAtPosition(x - 1, y + 3)); + tile.neighborsByDirection.put(240, getEntityAtPosition(x - 1, y + 1)); + tile.neighborsByDirection.put(270, getEntityAtPosition(x - 2, y)); + tile.neighborsByDirection.put(300, getEntityAtPosition(x - 1, y - 1)); + tile.neighborsByDirection.put(330, getEntityAtPosition(x - 1, y - 3)); + }); + } + +} diff --git a/src/main/java/jchess/gamemode/hex2p/Hex2pPieceLayouts.java b/src/main/java/jchess/gamemode/hex2p/Hex2pPieceLayouts.java new file mode 100644 index 0000000..4e9abe2 --- /dev/null +++ b/src/main/java/jchess/gamemode/hex2p/Hex2pPieceLayouts.java @@ -0,0 +1,149 @@ +package jchess.gamemode.hex2p; + +import jchess.common.IChessGame; +import jchess.ecs.Entity; +import jchess.gamemode.IPieceLayoutProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.BiFunction; + +public enum Hex2pPieceLayouts implements IPieceLayoutProvider { + Standard((game, tileProvider) -> populateStandardBoard(new BoardController(game, tileProvider))), + Custom((game, tileProvider) -> populateCustomBoard(new BoardController(game, tileProvider))), + ; + + private static final int PLAYER_LIGHT = 0; + private static final int PLAYER_DARK = 1; + + private static final Logger logger = LoggerFactory.getLogger(Hex2pPieceLayouts.class); + + public final IPieceLayoutProvider layoutProvider; + + Hex2pPieceLayouts(IPieceLayoutProvider layoutProvider) { + this.layoutProvider = layoutProvider; + } + + @Override + public void placePieces(IChessGame game, BiFunction tileProvider) { + layoutProvider.placePieces(game, tileProvider); + } + + private static void populateStandardBoard(BoardController boardController) { + // place kings + boardController.placeKing(6, 19, PLAYER_LIGHT); + boardController.placeKing(6, 1, PLAYER_DARK); + + // place queens + boardController.placeQueen(4, 19, PLAYER_LIGHT); + boardController.placeQueen(4, 1, PLAYER_DARK); + + + // place bishops + for (int i = 0; i <= 4; i+=2) { + boardController.placeBishop(5, i, PLAYER_DARK); + boardController.placeBishop(5, 20 - i, PLAYER_LIGHT); + } + + // place knights + for (int x : new int[]{3, 7}) { + boardController.placeKnight(x,18, PLAYER_LIGHT); + boardController.placeKnight(x,2, PLAYER_DARK); + } + + // place rooks + for (int x : new int[] {2, 8}) { + boardController.placeRook(x,17, PLAYER_LIGHT); + boardController.placeRook(x,3, PLAYER_DARK); + } + + + // place pawns + for (int i = 0; i < 5; i++) { + for (int x : new int[] {1+i, 9-i}) { + boardController.placePawn(x, 16 - i, PLAYER_LIGHT); + boardController.placePawn(x, 4 + i, PLAYER_DARK); + } + } + } + + private static void populateCustomBoard(BoardController boardController) { + populateStandardBoard(boardController); + + boardController.placeArcher(4,15, PLAYER_LIGHT); + boardController.placeArcher(6,15, PLAYER_LIGHT); + boardController.placeArcher(4,5, PLAYER_DARK); + boardController.placeArcher(6,5, PLAYER_DARK); + + boardController.placePegasus(5,14, PLAYER_LIGHT); + boardController.placePegasus(5,6, PLAYER_DARK); + + boardController.placeSkrull(3,16, PLAYER_LIGHT); + boardController.placeSkrull(7,16, PLAYER_LIGHT); + boardController.placeSkrull(3,4, PLAYER_DARK); + boardController.placeSkrull(7,4, PLAYER_DARK); + + boardController.placeCatapult(4,17, PLAYER_LIGHT); + boardController.placeCatapult(6,17, PLAYER_LIGHT); + boardController.placeCatapult(4,3, PLAYER_DARK); + boardController.placeCatapult(6,3, PLAYER_DARK); + + } + + @SuppressWarnings("SameParameterValue") + private record BoardController(IChessGame game, BiFunction tileProvider) { + + private Entity getEntityAtPosition(int x, int y) { + return tileProvider.apply(x, y); + } + + private void placeRook(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Rook); + } + + private void placeKnight(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Knight); + } + + private void placeBishop(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Bishop); + } + + private void placeQueen(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Queen); + } + + private void placeKing(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.King); + } + + private void placePawn(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Pawn); + } + + private void placeArcher(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Archer); + } + + private void placePegasus(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Pegasus); + } + + private void placeSkrull(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Skrull); + } + + private void placeCatapult(int x, int y, int playerColor) { + placePiece(x, y, playerColor, Hex2pPieces.Catapult); + } + + private void placePiece(int x, int y, int playerColor, Hex2pPieces piece) { + Entity tile = getEntityAtPosition(x, y); + if (tile == null) { + logger.error("cannot place piece on tile ({}, {}). No tile found.", x, y); + return; + } + game.createPiece(tile, piece.getPieceType(), playerColor); + } + } +} diff --git a/src/main/java/jchess/gamemode/hex2p/Hex2pPieces.java b/src/main/java/jchess/gamemode/hex2p/Hex2pPieces.java new file mode 100644 index 0000000..55e1a74 --- /dev/null +++ b/src/main/java/jchess/gamemode/hex2p/Hex2pPieces.java @@ -0,0 +1,124 @@ +package jchess.gamemode.hex2p; + +import dx.schema.types.PieceType; +import jchess.common.components.PieceIdentifier; +import jchess.common.moveset.special.EnPassant; +import jchess.common.moveset.special.PawnPromotion; +import jchess.common.moveset.special.RangedAttack; +import jchess.common.moveset.special.ShapeShifting; +import jchess.ecs.Entity; +import jchess.el.CompiledTileExpression; +import jchess.el.v2.ExpressionCompiler; +import jchess.el.v2.TileExpression; +import jchess.gamemode.PieceStore; + +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static jchess.el.v2.TileExpression.neighbor; +import static jchess.el.v2.TileExpression.regex; +import static jchess.el.v2.TileExpression.rotations; + +public enum Hex2pPieces implements PieceStore.IPieceDefinitionProvider { + Rook(PieceType.ROOK, new PieceStore.PieceDefinition( + "R", + rotations(regex("0+", false), 6) + )), + Knight(PieceType.KNIGHT, new PieceStore.PieceDefinition( + "N", + rotations(regex("30.0 30.60", true), 6) + )), + Bishop(PieceType.BISHOP, new PieceStore.PieceDefinition( + "B", + rotations(regex("30+", false), 6) + )), + Queen(PieceType.QUEEN, new PieceStore.PieceDefinition( + "Q", + TileExpression.or(Rook.pieceDefinition.baseMoves(), Bishop.pieceDefinition.baseMoves()) + )), + King(PieceType.KING, new PieceStore.PieceDefinition( + "K", + rotations(regex("0", false), 12) + )), + Pawn(PieceType.PAWN, new PieceStore.PieceDefinition( + "", + TileExpression.or( + TileExpression.filter(neighbor(0), TileExpression.FILTER_EMPTY_TILE), + TileExpression.filter2(neighbor(300, 60), TileExpression.FILTER_CAPTURE) + ), + (game, pawnId) -> new EnPassant(game, pawnId, PieceType.PAWN, new int[]{0}, new int[]{330, 30}), + (game, pawnId) -> { + int owner = pawnId.ownerId(); + return new PawnPromotion( + game, getPromotionTilePredicate(neighbor(330, 30), pawnId), + Stream.of(Rook, Knight, Bishop, Queen).map(type -> getPiece(type, owner)).toArray(dx.schema.message.Piece[]::new) + ); + } + )), + Archer(PieceType.ARCHER, new PieceStore.PieceDefinition( + "A", + TileExpression.filter( + rotations(regex("0{1,2}", false), 6), + TileExpression.FILTER_EMPTY_TILE + ), + (game, archerIdentifier) -> new RangedAttack( + game, archerIdentifier, + rotations(regex("(30 60 90){1,2}", true), 6) + ) + )), + + Pegasus(PieceType.PEGASUS, new PieceStore.PieceDefinition( + "PE", + rotations(regex("(0 60){3}", true), 6) + )), + Catapult(PieceType.CATAPULT, new PieceStore.PieceDefinition( + "C", + TileExpression.filter(rotations(neighbor(0), 6), TileExpression.FILTER_EMPTY_TILE), + (game, catapultId) -> new RangedAttack( + game, catapultId, + rotations(regex("(0 60){3,4}", true), 6) + ) + )), + Skrull(PieceType.SKRULL, new PieceStore.PieceDefinition( + "S", + Pawn.pieceDefinition.baseMoves(), + (game, skrullId) -> new ShapeShifting(game, skrullId, rotations(regex("(0 30 60){1,2}", true), 6), + PieceType.ROOK, PieceType.KNIGHT, PieceType.BISHOP, PieceType.QUEEN, PieceType.ARCHER, PieceType.CATAPULT, PieceType.PAWN + ) + )); + + private final PieceType pieceType; + private final PieceStore.PieceDefinition pieceDefinition; + + Hex2pPieces(PieceType pieceType, PieceStore.PieceDefinition pieceDefinition) { + this.pieceType = pieceType; + this.pieceDefinition = pieceDefinition; + } + + private static Predicate getPromotionTilePredicate(ExpressionCompiler forwardTiles, PieceIdentifier pawnId) { + CompiledTileExpression forwardExpression = forwardTiles.toV1(pawnId); + return tile -> { + if (tile.tile == null) return false; + + // if both forward tiles exit the board bounds (= empty result) -> pawn can promote + return forwardExpression.findTiles(tile).findAny().isEmpty(); + }; + } + + private static dx.schema.message.Piece getPiece(Hex2pPieces pieceType, int ownerId) { + dx.schema.message.Piece result = new dx.schema.message.Piece(); + result.setPieceTypeId(pieceType.pieceType); + result.setPlayerIdx(ownerId); + return result; + } + + @Override + public PieceType getPieceType() { + return pieceType; + } + + @Override + public PieceStore.PieceDefinition getPieceDefinition() { + return pieceDefinition; + } +} diff --git a/src/main/java/jchess/gamemode/hex3p/Hex3PlayerGame.java b/src/main/java/jchess/gamemode/hex3p/Hex3PlayerGame.java index 3a2e7cc..47c32c4 100644 --- a/src/main/java/jchess/gamemode/hex3p/Hex3PlayerGame.java +++ b/src/main/java/jchess/gamemode/hex3p/Hex3PlayerGame.java @@ -1,22 +1,17 @@ package jchess.gamemode.hex3p; -import dx.schema.types.PieceType; import dx.schema.types.Vector2I; import jchess.common.BaseChessGame; -import jchess.common.components.PieceComponent; -import jchess.common.components.PieceIdentifier; import jchess.common.components.TileComponent; import jchess.ecs.Entity; import jchess.gamemode.IPieceLayoutProvider; import jchess.gamemode.PieceStore; -import jchess.gamemode.PieceStore.IPieceDefinitionProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.awt.Point; +import java.util.Arrays; +import java.util.Objects; public class Hex3PlayerGame extends BaseChessGame { - private static final Logger logger = LoggerFactory.getLogger(Hex3PlayerGame.class); private static final int numTilesHorizontal = 17 + 16; private static final int numTilesVertical = 17; @@ -58,15 +53,8 @@ protected Entity getEntityAtPosition(int x, int y) { } @Override - public void createPiece(Entity targetTile, PieceType pieceType, int ownerId) { - IPieceDefinitionProvider pieceProvider = pieceStore.getPiece(pieceType); - if (pieceProvider == null) { - logger.error("unable to place piece with pieceType '" + pieceType + "'. PieceType does not exist."); - return; - } - - int direction = ((ownerId - 3) * (-120)) % 360; // [0, 240, 120] - placePiece(targetTile, ownerId, direction, pieceProvider); + protected int getDirectionFromOwnerId(int ownerId) { + return ((ownerId - 3) * (-120)) % 360; // [0, 240, 120] } @Override @@ -78,46 +66,30 @@ protected void generateBoard() { int x1 = 32 - x0; for (int x = x0; x <= x1; x += 2) { tileRow[x] = entityManager.createEntity(); + tileRow[x].tile = new TileComponent(new Point(x, y), x % 3); } } - // second pass: fill entities with components - for (int y = 0; y < numTilesVertical; y++) { - Entity[] tileRow = tiles[y]; - int x0 = Math.abs(8 - y); - int x1 = 32 - x0; - for (int x = x0; x <= x1; x += 2) { - TileComponent tile = new TileComponent(new Point(x, y), x % 3); - - tile.neighborsByDirection.put(0, getEntityAtPosition(x, y - 2)); - tile.neighborsByDirection.put(30, getEntityAtPosition(x + 1, y - 1)); - tile.neighborsByDirection.put(60, getEntityAtPosition(x + 3, y - 1)); - tile.neighborsByDirection.put(90, getEntityAtPosition(x + 2, y)); - tile.neighborsByDirection.put(120, getEntityAtPosition(x + 3, y + 1)); - tile.neighborsByDirection.put(150, getEntityAtPosition(x + 1, y + 1)); - tile.neighborsByDirection.put(180, getEntityAtPosition(x, y + 2)); - tile.neighborsByDirection.put(210, getEntityAtPosition(x - 1, y + 1)); - tile.neighborsByDirection.put(240, getEntityAtPosition(x - 3, y + 1)); - tile.neighborsByDirection.put(270, getEntityAtPosition(x - 2, y)); - tile.neighborsByDirection.put(300, getEntityAtPosition(x - 3, y - 1)); - tile.neighborsByDirection.put(330, getEntityAtPosition(x - 1, y - 1)); - - tileRow[x].tile = tile; - } - } - } - - private void placePiece(Entity tile, int ownerId, int direction, IPieceDefinitionProvider pieceProvider) { - PieceStore.PieceDefinition pieceDefinition = pieceProvider.getPieceDefinition(); - PieceIdentifier pieceIdentifier = new PieceIdentifier( - pieceProvider.getPieceType(), - pieceDefinition.shortName(), - ownerId, - direction - ); - - PieceComponent pieceComp = new PieceComponent(this, pieceIdentifier, pieceDefinition.baseMoves()); - pieceComp.addSpecialMoves(pieceDefinition.specialRules()); - tile.piece = pieceComp; + // second pass: generate neighbors + Arrays.stream(tiles).flatMap(Arrays::stream) + .filter(Objects::nonNull) + .forEach(entity -> { + TileComponent tile = entity.tile; + assert tile != null; + int x = tile.position.x; + int y = tile.position.y; + tile.neighborsByDirection.put(0, getEntityAtPosition(x, y - 2)); + tile.neighborsByDirection.put(30, getEntityAtPosition(x + 1, y - 1)); + tile.neighborsByDirection.put(60, getEntityAtPosition(x + 3, y - 1)); + tile.neighborsByDirection.put(90, getEntityAtPosition(x + 2, y)); + tile.neighborsByDirection.put(120, getEntityAtPosition(x + 3, y + 1)); + tile.neighborsByDirection.put(150, getEntityAtPosition(x + 1, y + 1)); + tile.neighborsByDirection.put(180, getEntityAtPosition(x, y + 2)); + tile.neighborsByDirection.put(210, getEntityAtPosition(x - 1, y + 1)); + tile.neighborsByDirection.put(240, getEntityAtPosition(x - 3, y + 1)); + tile.neighborsByDirection.put(270, getEntityAtPosition(x - 2, y)); + tile.neighborsByDirection.put(300, getEntityAtPosition(x - 3, y - 1)); + tile.neighborsByDirection.put(330, getEntityAtPosition(x - 1, y - 1)); + }); } } diff --git a/src/main/java/jchess/gamemode/hex3p/Hex3pPieces.java b/src/main/java/jchess/gamemode/hex3p/Hex3pPieces.java index 01b0299..b649fc6 100644 --- a/src/main/java/jchess/gamemode/hex3p/Hex3pPieces.java +++ b/src/main/java/jchess/gamemode/hex3p/Hex3pPieces.java @@ -91,7 +91,7 @@ game, getPromotionTilePredicate(neighbor(330, 30), pawnId), "S", Pawn.pieceDefinition.baseMoves(), (game, skrullId) -> new ShapeShifting(game, skrullId, - rotations(regex("(0 30 60){1,2}", true),6), + rotations(regex("(0 30 60){1,2}", true), 6), PieceType.ROOK, PieceType.KNIGHT, PieceType.BISHOP, PieceType.QUEEN, PieceType.ARCHER, PieceType.CATAPULT, PieceType.PAWN ) )); diff --git a/src/main/java/jchess/gamemode/square2p/Square2PlayerGame.java b/src/main/java/jchess/gamemode/square2p/Square2PlayerGame.java index 73849c3..dce1faa 100644 --- a/src/main/java/jchess/gamemode/square2p/Square2PlayerGame.java +++ b/src/main/java/jchess/gamemode/square2p/Square2PlayerGame.java @@ -2,19 +2,14 @@ import dx.schema.types.Vector2I; import jchess.common.BaseChessGame; -import jchess.common.components.PieceComponent; -import jchess.common.components.PieceIdentifier; import jchess.common.components.TileComponent; import jchess.ecs.Entity; import jchess.gamemode.IPieceLayoutProvider; import jchess.gamemode.PieceStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.awt.Point; public class Square2PlayerGame extends BaseChessGame { - private static final Logger logger = LoggerFactory.getLogger(Square2PlayerGame.class); private static final int numTiles = 8; private final Entity[][] tiles = new Entity[numTiles][numTiles]; @@ -47,18 +42,8 @@ protected Entity getEntityAtPosition(int x, int y) { } @Override - public void createPiece(Entity targetTile, dx.schema.types.PieceType pieceType, int ownerId) { - for (Square2pPieces piece : Square2pPieces.values()) { - if (piece.getPieceType() == pieceType) { - placePiece( - targetTile, ownerId, - ownerId == 0 ? 0 : 180, - piece - ); - return; - } - } - logger.error("unable to place piece with pieceType '" + pieceType + "'. PieceType does not exist."); + protected int getDirectionFromOwnerId(int ownerId) { + return ownerId == 0 ? 0 : 180; } @Override @@ -90,17 +75,4 @@ protected void generateBoard() { } } - private void placePiece(Entity tile, int ownerId, int direction, Square2pPieces pieceType) { - PieceIdentifier pieceIdentifier = new PieceIdentifier( - pieceType.getPieceType(), - pieceType.getPieceDefinition().shortName(), - ownerId, - direction - ); - - PieceComponent piece = new PieceComponent(this, pieceIdentifier, pieceType.getPieceDefinition().baseMoves()); - piece.addSpecialMoves(pieceType.getPieceDefinition().specialRules()); - tile.piece = piece; - } - } diff --git a/src/main/jchess-web/bun.lockb b/src/main/jchess-web/bun.lockb index f16f516..22d3e10 100755 Binary files a/src/main/jchess-web/bun.lockb and b/src/main/jchess-web/bun.lockb differ diff --git a/src/main/jchess-web/package.json b/src/main/jchess-web/package.json index 741b4a6..3953a4e 100644 --- a/src/main/jchess-web/package.json +++ b/src/main/jchess-web/package.json @@ -30,6 +30,7 @@ "next": "14.0.3", "react": "^18", "react-dom": "^18", + "react-resizable-panels": "^1.0.10", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" @@ -47,4 +48,3 @@ "json-schema-to-typescript": "9.1.1" } } - diff --git a/src/main/jchess-web/src/components/GameComponent/GameComponent.tsx b/src/main/jchess-web/src/components/GameComponent/GameComponent.tsx index d852224..e36265e 100644 --- a/src/main/jchess-web/src/components/GameComponent/GameComponent.tsx +++ b/src/main/jchess-web/src/components/GameComponent/GameComponent.tsx @@ -1,14 +1,14 @@ "use client"; -import React, { ReactElement, useCallback, useEffect, useRef, useState } from "react"; -import { useGameContext } from "@/src/app/context/game_context"; -import { useBoardUpdateContext } from "@/src/app/context/board_update_context"; -import { Entity } from "@/models/BoardUpdate.schema"; +import React, {ReactElement, useCallback, useEffect, useRef, useState} from "react"; +import {useGameContext} from "@/src/app/context/game_context"; +import {useBoardUpdateContext} from "@/src/app/context/board_update_context"; +import {Entity} from "@/models/BoardUpdate.schema"; import Config from "@/src/utils/config"; -import { postClick } from "@/src/services/rest_api_service"; +import {postClick} from "@/src/services/rest_api_service"; import PlayerOverviewComponent from "./PlayerOverviewComponent"; import ChatComponent from "./ChatComponent"; import PieceSelectionComponent from "./PieceSelectionComponent"; -import { useThemeHelperContext } from "@/src/app/context/theme_helper_context"; +import {useThemeHelperContext} from "@/src/app/context/theme_helper_context"; import GameOverComponent from "@/src/components/GameComponent/GameOverComponent"; export default function GameComponent(): ReactElement { @@ -22,8 +22,8 @@ export default function GameComponent(): ReactElement { // Contexts const gameContext = useGameContext(); - const { boardUpdate } = useBoardUpdateContext(); // this is the current game state coming from the server - const { themeHelper } = useThemeHelperContext(); + const {boardUpdate} = useBoardUpdateContext(); // this is the current game state coming from the server + const {themeHelper} = useThemeHelperContext(); // State const canvasRef = useRef(null); @@ -51,7 +51,7 @@ export default function GameComponent(): ReactElement { minTilePos = [minX, minY]; maxTilePos = [maxX, maxY]; } - return { maxTilePos, minTilePos }; + return {maxTilePos, minTilePos}; } /** @@ -81,7 +81,11 @@ export default function GameComponent(): ReactElement { const tileStride = themeHelper.getTileStride(); const rawBoardWidth = tileSize!.x + tileStride.x * (maxTilePos[0] - minTilePos[0]); const rawBoardHeight = tileSize!.y + tileStride.y * (maxTilePos[1] - minTilePos[1]); + let scaleFactor = offsetWidthFromCanvasRef / rawBoardWidth; + if (rawBoardHeight * scaleFactor > offsetHeightFromCanvasRef) { + scaleFactor = offsetHeightFromCanvasRef / rawBoardHeight; + } const centerX = offsetWidthFromCanvasRef / 2; const centerY = offsetHeightFromCanvasRef / 2; @@ -226,7 +230,7 @@ export default function GameComponent(): ReactElement { } // calculate the min and max tile positions - const { maxTilePos, minTilePos } = calculateMinMaxTilePosition(boardUpdate.boardState); + const {maxTilePos, minTilePos} = calculateMinMaxTilePosition(boardUpdate.boardState); // calculate the piece size adjustment and translation offset to ensure clickability of the pieces const pieceSizeAdjustment = 0.7; // this is needed so the bounding box of the piece is not overlapping with other pieces @@ -272,14 +276,14 @@ export default function GameComponent(): ReactElement { ref={canvasRef} className="w-[80vw] h-[80vw] xl:w-[50vw] xl:h-[50vw] md:w-[65vw] md:h-[65vw] min-w-[20px] min-h-[200px] max-w-[80vh] max-h-[80vh] justify-self-center relative" > - - + +
{board}
- - + +
); diff --git a/src/main/resources/dx/schema/types/LayoutId.schema.json b/src/main/resources/dx/schema/types/LayoutId.schema.json index 921595b..3ab6c9c 100644 --- a/src/main/resources/dx/schema/types/LayoutId.schema.json +++ b/src/main/resources/dx/schema/types/LayoutId.schema.json @@ -4,6 +4,7 @@ "javaType": "dx.schema.types.LayoutId", "enum": [ "hex3p", + "hex2p", "square2p" ], "additionalProperties": false diff --git a/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_noAction.png b/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_noAction.png new file mode 100644 index 0000000..f41e71d Binary files /dev/null and b/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_noAction.png differ diff --git a/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_selected.png b/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_selected.png new file mode 100644 index 0000000..995fc77 Binary files /dev/null and b/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_selected.png differ diff --git a/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_yesAction.png b/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_yesAction.png new file mode 100644 index 0000000..3db09b9 Binary files /dev/null and b/src/main/resources/jchess/themeV2/default/board_hex2/hexMarker_yesAction.png differ diff --git a/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_noAction.png b/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_noAction.png new file mode 100644 index 0000000..0ec62e3 Binary files /dev/null and b/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_noAction.png differ diff --git a/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_selected.png b/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_selected.png new file mode 100644 index 0000000..bd380e5 Binary files /dev/null and b/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_selected.png differ diff --git a/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_yesAction.png b/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_yesAction.png new file mode 100644 index 0000000..33b3bb6 Binary files /dev/null and b/src/main/resources/jchess/themeV2/hunter/board_hex2/hexMarker_yesAction.png differ diff --git a/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_noAction.png b/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_noAction.png new file mode 100644 index 0000000..74c71fa Binary files /dev/null and b/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_noAction.png differ diff --git a/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_selected.png b/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_selected.png new file mode 100644 index 0000000..a80ac01 Binary files /dev/null and b/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_selected.png differ diff --git a/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_yesAction.png b/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_yesAction.png new file mode 100644 index 0000000..8fd6a6e Binary files /dev/null and b/src/main/resources/jchess/themeV2/matlak/board_hex2/hexMarker_yesAction.png differ diff --git a/src/main/resources/jchess/themeV2/theme_default.json b/src/main/resources/jchess/themeV2/theme_default.json index 00d465b..e19c2e0 100644 --- a/src/main/resources/jchess/themeV2/theme_default.json +++ b/src/main/resources/jchess/themeV2/theme_default.json @@ -32,6 +32,36 @@ } ] }, + { + "layoutId": "hex2p", + "tileSize": { + "x": 32, + "y": 30 + }, + "tileStride": { + "x": 24, + "y": 15 + }, + "tiles": [ + "./default/board_hex2/hex-Light.png", + "./default/board_hex2/hex-Medium.png", + "./default/board_hex2/hex-Dark.png" + ], + "markers": [ + { + "markerType": "yesAction", + "icon": "./default/board_hex2/hexMarker_yesAction.png" + }, + { + "markerType": "noAction", + "icon": "./default/board_hex2/hexMarker_noAction.png" + }, + { + "markerType": "selected", + "icon": "./default/board_hex2/hexMarker_selected.png" + } + ] + }, { "layoutId": "square2p", "tileSize": { diff --git a/src/main/resources/jchess/themeV2/theme_hunter.json b/src/main/resources/jchess/themeV2/theme_hunter.json index 4290bc6..f9f3a8c 100644 --- a/src/main/resources/jchess/themeV2/theme_hunter.json +++ b/src/main/resources/jchess/themeV2/theme_hunter.json @@ -32,6 +32,36 @@ } ] }, + { + "layoutId": "hex2p", + "tileSize": { + "x": 32, + "y": 30 + }, + "tileStride": { + "x": 24, + "y": 15 + }, + "tiles": [ + "./hunter/board_hex2/hex-Light.png", + "./hunter/board_hex2/hex-Medium.png", + "./hunter/board_hex2/hex-Dark.png" + ], + "markers": [ + { + "markerType": "yesAction", + "icon": "./hunter/board_hex2/hexMarker_yesAction.png" + }, + { + "markerType": "noAction", + "icon": "./hunter/board_hex2/hexMarker_noAction.png" + }, + { + "markerType": "selected", + "icon": "./hunter/board_hex2/hexMarker_selected.png" + } + ] + }, { "layoutId": "square2p", "tileSize": { diff --git a/src/main/resources/jchess/themeV2/theme_matlak.json b/src/main/resources/jchess/themeV2/theme_matlak.json index 066d71b..5f594fe 100644 --- a/src/main/resources/jchess/themeV2/theme_matlak.json +++ b/src/main/resources/jchess/themeV2/theme_matlak.json @@ -32,6 +32,36 @@ } ] }, + { + "layoutId": "hex2p", + "tileSize": { + "x": 32, + "y": 30 + }, + "tileStride": { + "x": 24, + "y": 15 + }, + "tiles": [ + "./matlak/board_hex2/hex-Light.png", + "./matlak/board_hex2/hex-Medium.png", + "./matlak/board_hex2/hex-Dark.png" + ], + "markers": [ + { + "markerType": "yesAction", + "icon": "./matlak/board_hex2/hexMarker_yesAction.png" + }, + { + "markerType": "noAction", + "icon": "./matlak/board_hex2/hexMarker_noAction.png" + }, + { + "markerType": "selected", + "icon": "./matlak/board_hex2/hexMarker_selected.png" + } + ] + }, { "layoutId": "square2p", "tileSize": { diff --git a/src/test/java/jchess/el/v2/RotationsTest.java b/src/test/java/jchess/el/v2/RotationsTest.java index a97ed18..4416dae 100644 --- a/src/test/java/jchess/el/v2/RotationsTest.java +++ b/src/test/java/jchess/el/v2/RotationsTest.java @@ -5,6 +5,7 @@ import jchess.gamemode.IPieceLayoutProvider; import jchess.gamemode.PieceStore; import jchess.gamemode.square2p.Square2PlayerGame; +import jchess.gamemode.square2p.Square2pPieces; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -25,7 +26,7 @@ void test_rotations() { TileExpression.repeat(TileExpression.neighbor(270), 1, 2, true) ); - Square2pBoardProvider square2pProvider = new Square2pBoardProvider(null, null); + Square2pBoardProvider square2pProvider = new Square2pBoardProvider(new PieceStore(Square2pPieces.values()), null); square2pProvider.generateBoard(); Entity originTile = square2pProvider.getEntityAtPosition(3, 3); square2pProvider.createPiece(originTile, PieceType.PAWN, 0);