diff --git a/game/src/main/java/org/toop/game/BitboardGame.java b/game/src/main/java/org/toop/game/BitboardGame.java new file mode 100644 index 0000000..a0a3b69 --- /dev/null +++ b/game/src/main/java/org/toop/game/BitboardGame.java @@ -0,0 +1,71 @@ +package org.toop.game; + +import org.toop.framework.gameFramework.GameState; + +import java.util.Arrays; + +public abstract class BitboardGame { + private final int columnSize; + private final int rowSize; + + // long is 64 bits. Every game has a limit of 64 cells maximum. + private final long[] playerBitboard; + private int currentTurn; + + public BitboardGame(int columnSize, int rowSize, int playerCount) { + this.columnSize = columnSize; + this.rowSize = rowSize; + + this.playerBitboard = new long[playerCount]; + this.currentTurn = 0; + + Arrays.fill(playerBitboard, 0L); + } + + public BitboardGame(BitboardGame other) { + this.columnSize = other.columnSize; + this.rowSize = other.rowSize; + + this.playerBitboard = Arrays.copyOf(other.playerBitboard, other.playerBitboard.length); + this.currentTurn = other.currentTurn; + } + + public int getColumnSize() { + return this.columnSize; + } + + public int getRowSize() { + return this.rowSize; + } + + public long getPlayerBitboard(int player) { + return this.playerBitboard[player]; + } + + public void setPlayerBitboard(int player, long bitboard) { + this.playerBitboard[player] = bitboard; + } + + public int getPlayerCount() { + return playerBitboard.length; + } + + public int getCurrentTurn() { + return currentTurn; + } + + public int getCurrentPlayer() { + return currentTurn % playerBitboard.length; + } + + public int getNextPlayer() { + return (currentTurn + 1) % playerBitboard.length; + } + + public void nextTurn() { + currentTurn++; + } + + public abstract long getLegalMoves(); + public abstract GameState play(long move); +} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/reversi/BitboardReversi.java b/game/src/main/java/org/toop/game/reversi/BitboardReversi.java new file mode 100644 index 0000000..381a9c5 --- /dev/null +++ b/game/src/main/java/org/toop/game/reversi/BitboardReversi.java @@ -0,0 +1,137 @@ +package org.toop.game.reversi; + +import org.toop.framework.gameFramework.GameState; +import org.toop.game.BitboardGame; + +public class BitboardReversi extends BitboardGame { + public record Score(int black, int white) {} + + private final long notAFile = 0xfefefefefefefefeL; + private final long notHFile = 0x7f7f7f7f7f7f7f7fL; + + public BitboardReversi() { + super(8, 8, 2); + + // Black (player 0) + setPlayerBitboard(0, (1L << (3 + 4 * 8)) | (1L << (4 + 3 * 8))); + + // White (player 1) + setPlayerBitboard(1, (1L << (3 + 3 * 8)) | (1L << (4 + 4 * 8))); } + + @Override + public long getLegalMoves() { + final long player = getPlayerBitboard(getCurrentPlayer()); + final long opponent = getPlayerBitboard(getNextPlayer()); + + long legalMoves = 0L; + legalMoves |= computeMoves(player, opponent, 8, -1L); + legalMoves |= computeMoves(player, opponent, -8, -1L); + legalMoves |= computeMoves(player, opponent, 1, notAFile); + legalMoves |= computeMoves(player, opponent, -1, notHFile); + legalMoves |= computeMoves(player, opponent, 9, notAFile); + legalMoves |= computeMoves(player, opponent, 7, notHFile); + legalMoves |= computeMoves(player, opponent, -7, notHFile); + legalMoves |= computeMoves(player, opponent, -9, notAFile); + + return legalMoves; + } + + public long getFlips(long move) { + final long player = getPlayerBitboard(getCurrentPlayer()); + final long opponent = getPlayerBitboard(getNextPlayer()); + + long flips = 0L; + flips |= computeFlips(move, player, opponent, 8, -1L); + flips |= computeFlips(move, player, opponent, -8, -1L); + flips |= computeFlips(move, player, opponent, 1, notAFile); + flips |= computeFlips(move, player, opponent, -1, notHFile); + flips |= computeFlips(move, player, opponent, 9, notAFile); + flips |= computeFlips(move, player, opponent, 7, notHFile); + flips |= computeFlips(move, player, opponent, -7, notHFile); + flips |= computeFlips(move, player, opponent, -9, notAFile); + + return flips; + } + + @Override + public GameState play(long move) { + final long flips = getFlips(move); + + long player = getPlayerBitboard(getCurrentPlayer()); + long opponent = getPlayerBitboard(getNextPlayer()); + + player |= move | flips; + opponent &= ~flips; + + setPlayerBitboard(getCurrentPlayer(), player); + setPlayerBitboard(getNextPlayer(), opponent); + + nextTurn(); + + final long nextLegalMoves = getLegalMoves(); + + if (nextLegalMoves <= 0) { + nextTurn(); + + final long skippedLegalMoves = getLegalMoves(); + + if (skippedLegalMoves <= 0) { + final long black = getPlayerBitboard(0); + final long white = getPlayerBitboard(1); + + final int blackCount = Long.bitCount(black); + final int whiteCount = Long.bitCount(white); + + if (blackCount == whiteCount) { + return GameState.DRAW; + } + + return GameState.WIN; + } + + return GameState.TURN_SKIPPED; + } + + return GameState.NORMAL; + } + + public Score getScore() { + return new Score( + Long.bitCount(getPlayerBitboard(0)), + Long.bitCount(getPlayerBitboard(1)) + ); + } + + private long computeMoves(long player, long opponent, int shift, long mask) { + long moves = player; + + while (true) { + if (shift > 0) moves = (moves << shift) & mask; + else moves = (moves >>> -shift) & mask; + + long newMoves = moves & opponent; + if (newMoves == 0) break; + moves = newMoves; + } + + if (shift > 0) moves = (moves << shift) & mask; + else moves = (moves >>> -shift) & mask; + + return moves & ~(player | opponent); + } + + private long computeFlips(long move, long player, long opponent, int shift, long mask) { + long flips = 0L; + + long moves = move; + + while (true) { + if (shift > 0) moves = (moves << shift) & mask; + else moves = (moves >>> -shift) & mask; + + if ((moves & opponent) != 0) flips |= moves; + else if ((moves & player) != 0) return flips; + else return 0L; + } + } +} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/tictactoe/BitboardTicTacToe.java b/game/src/main/java/org/toop/game/tictactoe/BitboardTicTacToe.java new file mode 100644 index 0000000..26578f7 --- /dev/null +++ b/game/src/main/java/org/toop/game/tictactoe/BitboardTicTacToe.java @@ -0,0 +1,80 @@ +package org.toop.game.tictactoe; + +import org.toop.framework.gameFramework.GameState; +import org.toop.game.BitboardGame; + +public class BitboardTicTacToe extends BitboardGame { + private final long[] winningLines = { + 0b111000000L, // top row + 0b000111000L, // middle row + 0b000000111L, // bottom row + 0b100100100L, // left column + 0b010010010L, // middle column + 0b001001001L, // right column + 0b100010001L, // diagonal + 0b001010100L // anti-diagonal + }; + + public BitboardTicTacToe() { + super(3, 3, 2); + } + + @Override + public long getLegalMoves() { + final long xBitboard = getPlayerBitboard(0); + final long oBitboard = getPlayerBitboard(1); + + final long taken = (xBitboard | oBitboard); + return (~taken) & 0x1ffL; + } + + @Override + public GameState play(long move) { + long playerBitboard = getPlayerBitboard(getCurrentPlayer()); + playerBitboard |= move; + + setPlayerBitboard(getCurrentPlayer(), playerBitboard); + + if (checkWin(playerBitboard)) { + return GameState.WIN; + } + + if (getLegalMoves() <= 0L || checkEarlyDraw()) { + return GameState.DRAW; + } + + nextTurn(); + + return GameState.NORMAL; + } + + private boolean checkWin(long board) { + for (final long line : winningLines) { + if ((board & line) == line) { + return true; + } + } + + return false; + } + + private boolean checkEarlyDraw() { + final long xBitboard = getPlayerBitboard(0); + final long oBitboard = getPlayerBitboard(1); + + final long taken = (xBitboard | oBitboard); + final long empty = (~taken) & 0x1FFL; + + for (final long line : winningLines) { + if (((line & xBitboard) != 0 && (line & oBitboard) != 0)) { + continue; + } + + if ((line & empty) != 0) { + return false; + } + } + + return true; + } +} \ No newline at end of file