From 03dc6130e2b7981501f36079816c35498d5db602 Mon Sep 17 00:00:00 2001 From: Ticho Hidding Date: Mon, 8 Dec 2025 14:55:32 +0100 Subject: [PATCH] merge commit --- app/src/main/java/org/toop/Main.java | 3 - app/src/main/java/org/toop/app/App.java | 7 + .../java/org/toop/app/game/ReversiGame.java | 334 ------------------ framework/pom.xml | 6 - .../game/{ => games}/reversi/ReversiAIML.java | 45 ++- .../{ => games}/reversi/ReversiAISimple.java | 47 ++- .../org/toop/game/games/reversi/ReversiR.java | 17 + .../game}/machinelearning/NeuralNetwork.java | 65 ++-- .../game}/machinelearning/StateAction.java | 2 +- .../java/org/toop/game/reversi/Reversi.java | 301 ---------------- .../java/org/toop/game/reversi/ReversiAI.java | 15 - .../org/toop/game/tictactoe/ReversiTest.java | 31 +- 12 files changed, 120 insertions(+), 753 deletions(-) delete mode 100644 app/src/main/java/org/toop/app/game/ReversiGame.java rename game/src/main/java/org/toop/game/{ => games}/reversi/ReversiAIML.java (58%) rename game/src/main/java/org/toop/game/{ => games}/reversi/ReversiAISimple.java (57%) rename {framework/src/main/java/org/toop/framework => game/src/main/java/org/toop/game}/machinelearning/NeuralNetwork.java (75%) rename {framework/src/main/java/org/toop/framework => game/src/main/java/org/toop/game}/machinelearning/StateAction.java (80%) delete mode 100644 game/src/main/java/org/toop/game/reversi/Reversi.java delete mode 100644 game/src/main/java/org/toop/game/reversi/ReversiAI.java diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 16ae194..3b4fef3 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -1,12 +1,9 @@ package org.toop; import org.toop.app.App; -import org.toop.framework.machinelearning.NeuralNetwork; public final class Main { static void main(String[] args) { App.run(args); - NeuralNetwork nn = new NeuralNetwork(); - nn.init(); } } diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 8809c26..4448c37 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -15,6 +15,7 @@ import org.toop.framework.audio.*; import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.GlobalEventBus; +import org.toop.game.machinelearning.NeuralNetwork; import org.toop.framework.networking.NetworkingClientEventListener; import org.toop.framework.networking.NetworkingClientManager; import org.toop.framework.resource.ResourceLoader; @@ -138,8 +139,14 @@ public final class App extends Application { stage.show(); + //startML(); } + private void startML() { + NeuralNetwork nn = new NeuralNetwork(); + nn.init(); + } + private void setKeybinds(StackPane root) { root.addEventHandler(KeyEvent.KEY_PRESSED,event -> { if (event.getCode() == KeyCode.ESCAPE) { diff --git a/app/src/main/java/org/toop/app/game/ReversiGame.java b/app/src/main/java/org/toop/app/game/ReversiGame.java deleted file mode 100644 index f274f5a..0000000 --- a/app/src/main/java/org/toop/app/game/ReversiGame.java +++ /dev/null @@ -1,334 +0,0 @@ -package org.toop.app.game; - -import javafx.animation.SequentialTransition; -import org.toop.app.App; -import org.toop.app.GameInformation; -import org.toop.app.canvas.ReversiCanvas; -import org.toop.app.widget.WidgetContainer; -import org.toop.app.widget.view.GameView; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.game.enumerators.GameState; -import org.toop.game.records.Move; -import org.toop.game.reversi.Reversi; -import org.toop.game.reversi.ReversiAI; - -import javafx.geometry.Pos; -import javafx.scene.paint.Color; -import org.toop.game.reversi.ReversiAIML; - -import java.awt.*; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -public final class ReversiGame { - private final GameInformation information; - - private final int myTurn; - private final Runnable onGameOver; - private final BlockingQueue moveQueue; - - private final Reversi game; - private final ReversiAIML ai; - - private final GameView primary; - private final ReversiCanvas canvas; - - private final AtomicBoolean isRunning; - private final AtomicBoolean isPaused; - - public ReversiGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { - this.information = information; - - this.myTurn = myTurn; - this.onGameOver = onGameOver; - moveQueue = new LinkedBlockingQueue<>(); - - game = new Reversi(); - ai = new ReversiAIML(); - - isRunning = new AtomicBoolean(true); - isPaused = new AtomicBoolean(false); - - if (onForfeit == null || onExit == null) { - primary = new GameView(null, () -> { - isRunning.set(false); - WidgetContainer.getCurrentView().transitionPrevious(); - }, null, "Reversi"); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, "Reversi"); - } - - canvas = new ReversiCanvas(Color.BLACK, - (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, - (cell) -> { - if (onForfeit == null || onExit == null) { - if (information.players[game.getCurrentTurn()].isHuman) { - final char value = game.getCurrentTurn() == 0? 'B' : 'W'; - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - } else { - if (information.players[0].isHuman) { - final char value = myTurn == 0? 'B' : 'W'; - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - } - },this::highlightCells); - - - - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary); - - if (onForfeit == null || onExit == null) { - new Thread(this::localGameThread).start(); - setGameLabels(information.players[0].isHuman); - } else { - new EventFlow() - .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse) - .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse); - - setGameLabels(myTurn == 0); - } - - updateCanvas(false); - } - - public ReversiGame(GameInformation information) { - this(information, 0, null, null, null,null); - } - - private void localGameThread() { - while (isRunning.get()) { - if (isPaused.get()) { - try { - Thread.sleep(200); - } catch (InterruptedException _) {} - - continue; - } - - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "BLACK" : "WHITE"; - final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); - - primary.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - currentValue, - information.players[nextTurn].name); - - Move move = null; - - if (information.players[currentTurn].isHuman) { - try { - final Move wants = moveQueue.take(); - final Move[] legalMoves = game.getLegalMoves(); - - for (final Move legalMove : legalMoves) { - if (legalMove.position() == wants.position() && - legalMove.value() == wants.value()) { - move = wants; - break; - } - } - } catch (InterruptedException _) {} - } else { - final long start = System.currentTimeMillis(); - - move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty); - - if (information.players[currentTurn].computerThinkTime > 0) { - final long elapsedTime = System.currentTimeMillis() - start; - final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime; - - try { - Thread.sleep((long) (sleepTime * Math.random())); - } catch (InterruptedException _) {} - } - } - - if (move == null) { - continue; - } - - canvas.setCurrentlyHighlightedMovesNull(); - final GameState state = game.play(move); - updateCanvas(true); - - if (state != GameState.NORMAL) { - if (state == GameState.TURN_SKIPPED){ - continue; - } - int winningPLayerNumber = getPlayerNumberWithHighestScore(); - if (state == GameState.WIN && winningPLayerNumber > -1) { - primary.gameOver(true, information.players[winningPLayerNumber].name); - } else if (state == GameState.DRAW || winningPLayerNumber == -1) { - primary.gameOver(false, ""); - } - - isRunning.set(false); - } - } - } - - private int getPlayerNumberWithHighestScore(){ - Reversi.Score score = game.getScore(); - if (score.player1Score() > score.player2Score()) return 0; - if (score.player1Score() < score.player2Score()) return 1; - return -1; - } - - private void onMoveResponse(NetworkEvents.GameMoveResponse response) { - if (!isRunning.get()) { - return; - } - - char playerChar; - - if (response.player().equalsIgnoreCase(information.players[0].name)) { - playerChar = myTurn == 0? 'B' : 'W'; - } else { - playerChar = myTurn == 0? 'W' : 'B'; - } - - final Move move = new Move(Integer.parseInt(response.move()), playerChar); - final GameState state = game.play(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - if (response.player().equalsIgnoreCase(information.players[0].name)) { - primary.gameOver(true, information.players[0].name); - gameOver(); - } else { - primary.gameOver(false, information.players[1].name); - gameOver(); - } - } else if (state == GameState.DRAW) { - primary.gameOver(false, ""); - game.play(move); - } - } - - updateCanvas(false); - setGameLabels(game.getCurrentTurn() == myTurn); - } - - private void gameOver() { - if (onGameOver == null){ - return; - } - isRunning.set(false); - onGameOver.run(); - } - - private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) { - if (!isRunning.get()) { - return; - } - - moveQueue.clear(); - - int position = -1; - - if (information.players[0].isHuman) { - try { - position = moveQueue.take().position(); - } catch (InterruptedException _) {} - } else { - final Move move = ai.findBestMove(game, information.players[0].computerDifficulty); - - assert move != null; - position = move.position(); - } - - new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position)) - .postEvent(); - } - - private void updateCanvas(boolean animate) { - // Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve. - canvas.clearAll(); - - for (int i = 0; i < game.getBoard().length; i++) { - if (game.getBoard()[i] == 'B') { - canvas.drawDot(Color.BLACK, i); - } else if (game.getBoard()[i] == 'W') { - canvas.drawDot(Color.WHITE, i); - } - } - - final Move[] flipped = game.getMostRecentlyFlippedPieces(); - - final SequentialTransition animation = new SequentialTransition(); - isPaused.set(true); - - final Color fromColor = game.getCurrentPlayer() == 'W'? Color.WHITE : Color.BLACK; - final Color toColor = game.getCurrentPlayer() == 'W'? Color.BLACK : Color.WHITE; - - if (animate && flipped != null) { - for (final Move flip : flipped) { - canvas.clear(flip.position()); - canvas.drawDot(fromColor, flip.position()); - animation.getChildren().addFirst(canvas.flipDot(fromColor, toColor, flip.position())); - } - } - - animation.setOnFinished(_ -> { - isPaused.set(false); - - if (information.players[game.getCurrentTurn()].isHuman) { - final Move[] legalMoves = game.getLegalMoves(); - - for (final Move legalMove : legalMoves) { - canvas.drawLegalPosition(legalMove.position(), game.getCurrentPlayer()); - } - } - }); - - animation.play(); - } - - private void setGameLabels(boolean isMe) { - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "BLACK" : "WHITE"; - - primary.nextPlayer(isMe, - information.players[isMe? 0 : 1].name, - currentValue, - information.players[isMe? 1 : 0].name); - } - - private void highlightCells(int cellEntered) { - if (information.players[game.getCurrentTurn()].isHuman) { - Move[] legalMoves = game.getLegalMoves(); - boolean isLegalMove = false; - for (Move move : legalMoves) { - if (move.position() == cellEntered){ - isLegalMove = true; - break; - } - } - - if (cellEntered >= 0){ - Move[] moves = null; - if (isLegalMove) { - moves = game.getFlipsForPotentialMove( - new Point(cellEntered%game.getColumnSize(),cellEntered/game.getRowSize()), - game.getCurrentPlayer(), game.getCurrentPlayer() == 'B'?'W':'B',game.makeBoardAGrid()); - } - canvas.drawHighlightDots(moves); - } - } - } -} \ No newline at end of file diff --git a/framework/pom.xml b/framework/pom.xml index d215eb6..e319f49 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -152,12 +152,6 @@ 1.0.0-M2.1 compile - - org.toop - game - 0.1 - compile - diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAIML.java b/game/src/main/java/org/toop/game/games/reversi/ReversiAIML.java similarity index 58% rename from game/src/main/java/org/toop/game/reversi/ReversiAIML.java rename to game/src/main/java/org/toop/game/games/reversi/ReversiAIML.java index 6874f03..6c46905 100644 --- a/game/src/main/java/org/toop/game/reversi/ReversiAIML.java +++ b/game/src/main/java/org/toop/game/games/reversi/ReversiAIML.java @@ -1,18 +1,17 @@ -package org.toop.game.reversi; +package org.toop.game.games.reversi; import org.deeplearning4j.nn.multilayer.MultiLayerNetwork; import org.deeplearning4j.util.ModelSerializer; import org.nd4j.linalg.api.ndarray.INDArray; import org.nd4j.linalg.factory.Nd4j; -import org.toop.game.AI; -import org.toop.game.records.Move; +import org.toop.framework.gameFramework.model.player.AbstractAI; import java.io.IOException; import java.io.InputStream; import static java.lang.Math.random; -public class ReversiAIML extends AI{ +public class ReversiAIML extends AbstractAI { MultiLayerNetwork model; @@ -24,35 +23,35 @@ public class ReversiAIML extends AI{ } catch (IOException e) {} } - public Move findBestMove(Reversi reversi, int depth){ - int[] input = reversi.getBoardInt(); - - INDArray boardInput = Nd4j.create(new int[][] { input }); - INDArray prediction = model.output(boardInput); - - int move = pickLegalMove(prediction,reversi); - return new Move(move, reversi.getCurrentPlayer()); - } - - private int pickLegalMove(INDArray prediction, Reversi reversi) { + private int pickLegalMove(INDArray prediction, ReversiR reversi) { double[] logits = prediction.toDoubleVector(); - Move[] legalMoves = reversi.getLegalMoves(); + int[] legalMoves = reversi.getLegalMoves(); if (legalMoves.length == 0) return -1; - int bestMove = legalMoves[0].position(); + int bestMove = legalMoves[0]; double bestVal = logits[bestMove]; if (random() < 0.01){ - return legalMoves[(int)(random()*legalMoves.length-.5)].position(); + return legalMoves[(int)(random()*legalMoves.length-.5)]; } - for (Move move : legalMoves) { - int pos = move.position(); - if (logits[pos] > bestVal) { - bestMove = pos; - bestVal = logits[pos]; + for (int move : legalMoves) { + if (logits[move] > bestVal) { + bestMove = move; + bestVal = logits[move]; } } return bestMove; } + + @Override + public int getMove(ReversiR game) { + int[] input = game.getBoard(); + + INDArray boardInput = Nd4j.create(new int[][] { input }); + INDArray prediction = model.output(boardInput); + + int move = pickLegalMove(prediction,game); + return move; + } } diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAISimple.java b/game/src/main/java/org/toop/game/games/reversi/ReversiAISimple.java similarity index 57% rename from game/src/main/java/org/toop/game/reversi/ReversiAISimple.java rename to game/src/main/java/org/toop/game/games/reversi/ReversiAISimple.java index d5b9fe2..d744b45 100644 --- a/game/src/main/java/org/toop/game/reversi/ReversiAISimple.java +++ b/game/src/main/java/org/toop/game/games/reversi/ReversiAISimple.java @@ -1,28 +1,33 @@ -package org.toop.game.reversi; +package org.toop.game.games.reversi; -import org.toop.game.AI; -import org.toop.game.records.Move; +import org.toop.framework.gameFramework.model.player.AbstractAI; -import java.util.Arrays; +import java.awt.*; -public class ReversiAISimple extends AI { +public class ReversiAISimple extends AbstractAI { + + + private int getNumberOfOptions(ReversiR game, int move){ + ReversiR copy = game.deepCopy(); + copy.play(move); + return copy.getLegalMoves().length; + } + + private int getScore(ReversiR game, int move){ + return game.getFlipsForPotentialMove(new Point(move%game.getColumnSize(),move/game.getRowSize()),game.getCurrentTurn()).length; + } @Override - public Move findBestMove(Reversi game, int depth) { - //IO.println("****START FIND BEST MOVE****"); + public int getMove(ReversiR game) { - Move[] moves = game.getLegalMoves(); + int[] moves = game.getLegalMoves(); - - //game.printBoard(); - //IO.println("Legal moves: " + Arrays.toString(moves)); - - Move bestMove; - Move bestMoveScore = moves[0]; - Move bestMoveOptions = moves[0]; + int bestMove; + int bestMoveScore = moves[0]; + int bestMoveOptions = moves[0]; int bestScore = -1; int bestOptions = -1; - for (Move move : moves){ + for (int move : moves){ int numOpt = getNumberOfOptions(game, move); if (numOpt > bestOptions) { bestOptions = numOpt; @@ -50,14 +55,4 @@ public class ReversiAISimple extends AI { } return bestMove; } - - private int getNumberOfOptions(Reversi game, Move move){ - Reversi copy = new Reversi(game); - copy.play(move); - return copy.getLegalMoves().length; - } - - private int getScore(Reversi game, Move move){ - return game.getFlipsForPotentialMove(move).length; - } } diff --git a/game/src/main/java/org/toop/game/games/reversi/ReversiR.java b/game/src/main/java/org/toop/game/games/reversi/ReversiR.java index 85d6334..537e349 100644 --- a/game/src/main/java/org/toop/game/games/reversi/ReversiR.java +++ b/game/src/main/java/org/toop/game/games/reversi/ReversiR.java @@ -254,7 +254,24 @@ public final class ReversiR extends AbstractGame { }); return Arrays.stream(moves).mapToInt(Integer::intValue).toArray(); } + public int[] getMostRecentlyFlippedPieces() { return mostRecentlyFlippedPieces; } + + public Score getScore() { + int[] board = getBoard(); + int p1 = 0; + int p2 = 0; + for (int i = 0; i < this.getColumnSize() * this.getRowSize(); i++) { + if (board[i] == 1) { + p1 += 1; + } + if (board[i] == 2) { + p2 += 1; + } + + } + return new Score(p1, p2); + } } \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/machinelearning/NeuralNetwork.java b/game/src/main/java/org/toop/game/machinelearning/NeuralNetwork.java similarity index 75% rename from framework/src/main/java/org/toop/framework/machinelearning/NeuralNetwork.java rename to game/src/main/java/org/toop/game/machinelearning/NeuralNetwork.java index 9a7af90..e00c88e 100644 --- a/framework/src/main/java/org/toop/framework/machinelearning/NeuralNetwork.java +++ b/game/src/main/java/org/toop/game/machinelearning/NeuralNetwork.java @@ -1,4 +1,4 @@ -package org.toop.framework.machinelearning; +package org.toop.game.machinelearning; import org.deeplearning4j.nn.conf.MultiLayerConfiguration; import org.deeplearning4j.nn.conf.NeuralNetConfiguration; @@ -13,13 +13,14 @@ import org.nd4j.linalg.dataset.DataSet; import org.nd4j.linalg.factory.Nd4j; import org.nd4j.linalg.learning.config.Adam; import org.nd4j.linalg.lossfunctions.LossFunctions; -import org.toop.game.AI; -import org.toop.game.enumerators.GameState; -import org.toop.game.records.Move; -import org.toop.game.reversi.Reversi; -import org.toop.game.reversi.ReversiAI; -import org.toop.game.reversi.ReversiAIML; -import org.toop.game.reversi.ReversiAISimple; +import org.toop.framework.gameFramework.GameState; +import org.toop.framework.gameFramework.model.game.PlayResult; +import org.toop.framework.gameFramework.model.player.AbstractAI; +import org.toop.framework.gameFramework.model.player.Player; +import org.toop.game.games.reversi.ReversiAIR; +import org.toop.game.games.reversi.ReversiR; +import org.toop.game.games.reversi.ReversiAIML; +import org.toop.game.games.reversi.ReversiAISimple; import java.io.File; import java.io.IOException; @@ -33,11 +34,10 @@ public class NeuralNetwork { private MultiLayerConfiguration conf; private MultiLayerNetwork model; - private AI opponentAI; - private ReversiAI reversiAI = new ReversiAI(); - private AI opponentRand = new ReversiAI(); - private AI opponentSimple = new ReversiAISimple(); - private AI opponentAIML = new ReversiAIML(); + private AbstractAI opponentAI; + private AbstractAI opponentRand = new ReversiAIR(); + private AbstractAI opponentSimple = new ReversiAISimple(); + private AbstractAI opponentAIML = new ReversiAIML(); public NeuralNetwork() {} @@ -89,27 +89,27 @@ public class NeuralNetwork { } public void trainingLoop(){ - int totalGames = 50000; + int totalGames = 5000; double epsilon = 0.05; long start = System.nanoTime(); for (int game = 0; game gameHistory = new ArrayList<>(); - GameState state = GameState.NORMAL; + PlayResult state = new PlayResult(GameState.NORMAL,reversi.getCurrentTurn()); double reward = 0; - while (state != GameState.DRAW && state != GameState.WIN){ - char curr = reversi.getCurrentPlayer(); - Move move; + while (state.state() != GameState.DRAW && state.state() != GameState.WIN){ + int curr = reversi.getCurrentTurn(); + int move; if (curr == modelPlayer) { - int[] input = reversi.getBoardInt(); + int[] input = reversi.getBoard(); if (Math.random() < epsilon) { - Move[] moves = reversi.getLegalMoves(); + int[] moves = reversi.getLegalMoves(); move = moves[(int) (Math.random() * moves.length - .5f)]; } else { INDArray boardInput = Nd4j.create(new int[][]{input}); @@ -117,16 +117,16 @@ public class NeuralNetwork { int location = pickLegalMove(prediction, reversi); gameHistory.add(new StateAction(input, location)); - move = new Move(location, reversi.getCurrentPlayer()); + move = location; } }else{ - move = opponentAI.findBestMove(reversi,5); + move = opponentAI.getMove(reversi); } state = reversi.play(move); } //IO.println(model.params()); - Reversi.Score score = reversi.getScore(); + ReversiR.Score score = reversi.getScore(); int scoreDif = abs(score.player1Score() - score.player2Score()); if (score.player1Score() > score.player2Score()){ reward = 1 + ((scoreDif / 64.0) * 0.5); @@ -155,29 +155,26 @@ public class NeuralNetwork { IO.println((end-start)); } - private boolean isInCorner(Move move){ - return move.position() == 0 || move.position() == 7 || move.position() == 56 || move.position() == 63; - } - private int pickLegalMove(INDArray prediction, Reversi reversi){ + private int pickLegalMove(INDArray prediction, ReversiR reversi){ double[] probs = prediction.toDoubleVector(); - Move[] legalMoves = reversi.getLegalMoves(); + int[] legalMoves = reversi.getLegalMoves(); if (legalMoves.length == 0) return -1; - int bestMove = legalMoves[0].position(); + int bestMove = legalMoves[0]; double bestVal = probs[bestMove]; - for (Move move : legalMoves){ - if (probs[move.position()] > bestVal){ - bestMove = move.position(); + for (int move : legalMoves){ + if (probs[move] > bestVal){ + bestMove = move; bestVal = probs[bestMove]; } } return bestMove; } - private AI getOpponentAI(){ + private AbstractAI getOpponentAI(){ return switch ((int) (Math.random() * 4)) { case 0 -> opponentRand; case 1 -> opponentSimple; diff --git a/framework/src/main/java/org/toop/framework/machinelearning/StateAction.java b/game/src/main/java/org/toop/game/machinelearning/StateAction.java similarity index 80% rename from framework/src/main/java/org/toop/framework/machinelearning/StateAction.java rename to game/src/main/java/org/toop/game/machinelearning/StateAction.java index 58a7ffb..561230b 100644 --- a/framework/src/main/java/org/toop/framework/machinelearning/StateAction.java +++ b/game/src/main/java/org/toop/game/machinelearning/StateAction.java @@ -1,4 +1,4 @@ -package org.toop.framework.machinelearning; +package org.toop.game.machinelearning; public class StateAction { int[] state; diff --git a/game/src/main/java/org/toop/game/reversi/Reversi.java b/game/src/main/java/org/toop/game/reversi/Reversi.java deleted file mode 100644 index 42e0485..0000000 --- a/game/src/main/java/org/toop/game/reversi/Reversi.java +++ /dev/null @@ -1,301 +0,0 @@ -package org.toop.game.reversi; - -import org.toop.game.TurnBasedGame; -import org.toop.game.enumerators.GameState; -import org.toop.game.records.Move; - -import java.awt.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - - -public final class Reversi extends TurnBasedGame { - private int movesTaken; - private Set filledCells = new HashSet<>(); - private Move[] mostRecentlyFlippedPieces; - private char[][] cachedBoard; - - public record Score(int player1Score, int player2Score) {} - - public Reversi() { - super(8, 8, 2); - addStartPieces(); - } - - public Reversi(Reversi other) { - super(other); - this.movesTaken = other.movesTaken; - this.filledCells = other.filledCells; - this.mostRecentlyFlippedPieces = other.mostRecentlyFlippedPieces; - } - - - private void addStartPieces() { - this.setBoard(new Move(27, 'W')); - this.setBoard(new Move(28, 'B')); - this.setBoard(new Move(35, 'B')); - this.setBoard(new Move(36, 'W')); - updateFilledCellsSet(); - cachedBoard = makeBoardAGrid(); - } - private void updateFilledCellsSet() { - for (int i = 0; i < 64; i++) { - if (this.getBoard()[i] == 'W' || this.getBoard()[i] == 'B') { - filledCells.add(new Point(i % this.getColumnSize(), i / this.getRowSize())); - } - } - } - - @Override - public Move[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList<>(); - char[][] boardGrid = cachedBoard; - char currentPlayer = (this.getCurrentTurn()==0) ? 'B' : 'W'; - char opponent = (currentPlayer=='W') ? 'B' : 'W'; - - Set adjCell = getAdjacentCells(boardGrid, opponent); - for (Point point : adjCell){ - Move[] moves = getFlipsForPotentialMove(point, currentPlayer, opponent, boardGrid); - int score = moves.length; - if (score > 0){ - legalMoves.add(new Move(point.x + point.y * this.getRowSize(), currentPlayer)); - } - } - return legalMoves.toArray(new Move[0]); - } - - private Set getAdjacentCells(char[][] boardGrid, char opponent) { - Set possibleCells = new HashSet<>(); - for (Point point : filledCells) { //for every filled cell - if (boardGrid[point.x][point.y] == opponent) { - for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) { //check adjacent cells - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { //orthogonally and diagonally - int newX = point.x + deltaColumn, newY = point.y + deltaRow; - if (deltaColumn == 0 && deltaRow == 0 //continue if out of bounds - || !isOnBoard(newX, newY)) { - continue; - } - if (boardGrid[newY][newX] == EMPTY) { //check if the cell is empty - possibleCells.add(new Point(newX, newY)); //and then add it to the set of possible moves - } - } - } - } - } - return possibleCells; - } - - public Move[] getFlipsForPotentialMove(Point point, char currentPlayer, char opponent, char[][] boardGrid) { - final ArrayList movesToFlip = new ArrayList<>(); - for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) { //for all directions - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { - if (deltaColumn == 0 && deltaRow == 0){ - continue; - } - Move[] moves = getFlipsInDirection(point, boardGrid, currentPlayer, opponent, deltaColumn, deltaRow); - if (moves != null) { //getFlipsInDirection - movesToFlip.addAll(Arrays.asList(moves)); - } - } - } - return movesToFlip.toArray(new Move[0]); - } - - public Move[] getFlipsForPotentialMove(Move move) { - char curr = getCurrentPlayer(); - char opp = getOpponent(curr); - Point point = new Point(move.position() % this.getRowSize(), move.position() / this.getColumnSize()); - return getFlipsForPotentialMove(point, curr, opp, cachedBoard); - } - - private Move[] getFlipsInDirection(Point point, char[][] boardGrid, char currentPlayer, char opponent, int dirX, int dirY) { - final ArrayList movesToFlip = new ArrayList<>(); - int x = point.x + dirX; - int y = point.y + dirY; - - if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) { //there must first be an opponents tile - return null; - } - - while (isOnBoard(x, y) && boardGrid[y][x] == opponent) { //count the opponents tiles in this direction - - movesToFlip.add(new Move(x+y*this.getRowSize(), currentPlayer)); - x += dirX; - y += dirY; - } - if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) { - return movesToFlip.toArray(new Move[0]); //only return the count if last tile is ours - } - return null; - } - - private boolean isOnBoard(int x, int y) { - return x >= 0 && x < this.getColumnSize() && y >= 0 && y < this.getRowSize(); - } - - public char[][] makeBoardAGrid() { - char[][] boardGrid = new char[this.getRowSize()][this.getColumnSize()]; - for (int i = 0; i < 64; i++) { - boardGrid[i / this.getRowSize()][i % this.getColumnSize()] = this.getBoard()[i]; //boardGrid[y -> row] [x -> column] - } - return boardGrid; - } - - @Override - public GameState play(Move move) { - if (cachedBoard == null) { - cachedBoard = makeBoardAGrid(); - } - Move[] legalMoves = getLegalMoves(); - boolean moveIsLegal = false; - for (Move legalMove : legalMoves) { //check if the move is legal - if (move.equals(legalMove)) { - moveIsLegal = true; - break; - } - } - if (!moveIsLegal) { - return null; - } - - Move[] moves = sortMovesFromCenter(getFlipsForPotentialMove(new Point(move.position()%this.getColumnSize(),move.position()/this.getRowSize()), move.value(),move.value() == 'B'? 'W': 'B',makeBoardAGrid()),move); - mostRecentlyFlippedPieces = moves; - this.setBoard(move); //place the move on the board - for (Move m : moves) { - this.setBoard(m); //flip the correct pieces on the board - } - filledCells.add(new Point(move.position() % this.getRowSize(), move.position() / this.getColumnSize())); - cachedBoard = makeBoardAGrid(); - nextTurn(); - if (getLegalMoves().length == 0) { //skip the players turn when there are no legal moves - skipMyTurn(); - if (getLegalMoves().length > 0) { - return GameState.TURN_SKIPPED; - } - else { //end the game when neither player has a legal move - Score score = getScore(); - if (score.player1Score() == score.player2Score()) { - return GameState.DRAW; - } - else { - return GameState.WIN; - } - } - } - return GameState.NORMAL; - } - - private void skipMyTurn(){ - //IO.println("TURN " + getCurrentPlayer() + " SKIPPED"); - //TODO: notify user that a turn has been skipped - nextTurn(); - } - - public char getCurrentPlayer() { - if (this.getCurrentTurn() == 0){ - return 'B'; - } - else { - return 'W'; - } - } - - private char getOpponent(char currentPlayer){ - if (currentPlayer == 'B') { - return 'W'; - } - else { - return 'B'; - } - } - - public Score getScore(){ - int player1Score = 0, player2Score = 0; - for (int count = 0; count < this.getRowSize() * this.getColumnSize(); count++) { - if (this.getBoard()[count] == 'B') { - player1Score += 1; - } - if (this.getBoard()[count] == 'W') { - player2Score += 1; - } - } - return new Score(player1Score, player2Score); - } - - public boolean isGameOver(){ - Move[] legalMovesW = getLegalMoves(); - nextTurn(); - Move[] legalMovesB = getLegalMoves(); - nextTurn(); - if (legalMovesW.length + legalMovesB.length == 0) { - return true; - } - return false; - } - - public int getWinner(){ - if (!isGameOver()) { - return 0; - } - Score score = getScore(); - if (score.player1Score() > score.player2Score()) { - return 1; - } - else if (score.player1Score() < score.player2Score()) { - return 2; - } - return 0; - } - - private Move[] sortMovesFromCenter(Move[] moves, Move center) { //sorts the pieces to be flipped for animation purposes - int centerX = center.position()%this.getColumnSize(); - int centerY = center.position()/this.getRowSize(); - Arrays.sort(moves, (a, b) -> { - int dxA = a.position()%this.getColumnSize() - centerX; - int dyA = a.position()/this.getRowSize() - centerY; - int dxB = b.position()%this.getColumnSize() - centerX; - int dyB = b.position()/this.getRowSize() - centerY; - - int distA = dxA * dxA + dyA * dyA; - int distB = dxB * dxB + dyB * dyB; - - return Integer.compare(distA, distB); - }); - return moves; - } - public Move[] getMostRecentlyFlippedPieces() { - return mostRecentlyFlippedPieces; - } - - public int[] getBoardInt(){ - char[] input = getBoard(); - int[] result = new int[input.length]; - for (int i = 0; i < input.length; i++) { - switch (input[i]) { - case 'W': - result[i] = -1; - break; - case 'B': - result[i] = 1; - break; - case ' ': - default: - result[i] = 0; - break; - } - } - return result; - } - - public Point moveToPoint(Move move){ - return new Point(move.position()%this.getColumnSize(),move.position()/this.getRowSize()); - } - - public void printBoard(){ - for (int row = 0; row < this.getRowSize(); row++) { - IO.println(Arrays.toString(cachedBoard[row])); - } - } -} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAI.java b/game/src/main/java/org/toop/game/reversi/ReversiAI.java deleted file mode 100644 index 8e64509..0000000 --- a/game/src/main/java/org/toop/game/reversi/ReversiAI.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.toop.game.reversi; - -import org.toop.game.AI; -import org.toop.game.records.Move; - -public final class ReversiAI extends AI { - - @Override - public Move findBestMove(Reversi game, int depth) { - Move[] moves = game.getLegalMoves(); - int inty = (int)(Math.random() * moves.length-.5f); - if (moves.length == 0) return null; - return moves[inty]; - } -} diff --git a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java index 25daa26..cd83b63 100644 --- a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java +++ b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java @@ -1,31 +1,40 @@ +/*//todo fix this mess + + + package org.toop.game.tictactoe; import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.toop.framework.gameFramework.model.player.AbstractAI; +import org.toop.framework.gameFramework.model.player.Player; import org.toop.game.AI; import org.toop.game.enumerators.GameState; +import org.toop.game.games.reversi.ReversiAIR; +import org.toop.game.games.reversi.ReversiR; import org.toop.game.records.Move; import org.toop.game.reversi.Reversi; import org.toop.game.reversi.ReversiAI; -import org.toop.game.reversi.ReversiAIML; -import org.toop.game.reversi.ReversiAISimple; +import org.toop.game.games.reversi.ReversiAIML; +import org.toop.game.games.reversi.ReversiAISimple; import static org.junit.jupiter.api.Assertions.*; class ReversiTest { - private Reversi game; - private ReversiAI ai; + private ReversiR game; + private ReversiAIR ai; private ReversiAIML aiml; private ReversiAISimple aiSimple; - private AI player1; - private AI player2; + private AbstractAI player1; + private AbstractAI player2; + private Player[] players = new Player[2]; @BeforeEach void setup() { - game = new Reversi(); - ai = new ReversiAI(); + game = new ReversiR(players); + ai = new ReversiAIR(); aiml = new ReversiAIML(); aiSimple = new ReversiAISimple(); @@ -231,7 +240,7 @@ class ReversiTest { int draws = 0; List moves = new ArrayList<>(); for (int i = 0; i < totalGames; i++) { - game = new Reversi(); + game = new ReversiR(); while (!game.isGameOver()) { char curr = game.getCurrentPlayer(); Move move; @@ -258,4 +267,6 @@ class ReversiTest { } IO.println("p1 winrate: " + p1wins + "/" + totalGames + " = " + (double) p1wins / totalGames + "\np2wins: " + p2wins + " draws: " + draws); } -} \ No newline at end of file +} + + */ \ No newline at end of file