25 Commits

Author SHA1 Message Date
Bas de Jong
a1f0d48477 Refactored Tournament to use matchExecutor and ResultBroadcaster. Added turnTime and players are now added through Tournament creation instead of on MatchMaker/ScoreSystem creation 2026-01-12 08:33:54 +01:00
lieght
5caf6900d1 Removed input mistake, removed print 2026-01-11 10:50:53 +01:00
lieght
94d85bf78d Null handling 2026-01-11 09:07:30 +01:00
lieght
c9ea8f5e5b Tournament now uses propper builder pattern 2026-01-11 09:03:49 +01:00
lieght
cc7acf9f0c Moved scoring calculation into scoring system 2026-01-11 07:45:55 +01:00
Bas de Jong
955cb6109c Added back ability to shuffle matchmaker 2026-01-11 01:54:40 +01:00
Bas de Jong
013dd90705 Async tournament runner 2026-01-11 01:42:59 +01:00
Bas de Jong
c77499c36d Added result comeback with a draw 2026-01-11 00:53:31 +01:00
Bas de Jong
28791fcc8a Tournament is now without admins 2026-01-10 22:47:53 +01:00
Bas de Jong
97657b01c9 Added admins to games 2026-01-10 22:28:41 +01:00
Bas de Jong
a5bf6ca9fb Request admin list 2026-01-10 21:22:15 +01:00
Bas de Jong
fc25c15736 Starting a tournament now requires to be admin 2026-01-10 21:19:04 +01:00
Bas de Jong
d4cad3311e Tournament refactor for better naming and easier to understand code 2026-01-10 20:38:26 +01:00
Bas de Jong
96afc9543a Removed unnecessary imports 2026-01-10 04:29:38 +01:00
Bas de Jong
0c1b106da5 Refactored tournament to use interfaces and builders 2026-01-10 04:28:12 +01:00
Bas de Jong
aca0b2dcc0 Tournament now returns result to clients 2026-01-10 02:44:04 +01:00
Bas de Jong
75963a891b Tournament results are now send back to the clients connected to the server 2026-01-10 02:14:23 +01:00
Bas de Jong
6b644ed8fa Shuffle now changeable, host can now switch tournament gametype 2026-01-10 00:06:52 +01:00
Bas de Jong
6a395cc40b GlobalEventBus is now async instead 2026-01-09 23:34:29 +01:00
Bas de Jong
5e5948d1fe Working tournament 2026-01-09 22:28:15 +01:00
Bas de Jong
9c01aabbe1 Fixed merge mistakes 2026-01-07 23:46:21 +01:00
Bas Antonius de Jong
0cb52b042f Merge branch 'Development' into 289-server 2026-01-07 23:44:13 +01:00
Bas de Jong
56a8d12e46 Logging and fixed user input getting stuck 2026-01-07 23:38:53 +01:00
65220d9649 Hotfix for stuff 2026-01-07 17:16:45 +01:00
Bas Antonius de Jong
c64a2e2c65 Server update with new dev changes (#305)
* merge widgets with development

* readd previous game thread code

* Revert "readd previous game thread code"

This reverts commit d24feef73e.

* Revert "Merge remote-tracking branch 'origin/Development' into Development"

This reverts commit 59d46cb73c, reversing
changes made to 38681c5db0.

* Revert "merge widgets with development"

This reverts commit 38681c5db0.

* Merge 292 into development (#293)

Applied template method pattern to abstract player

* Added documentation to player classes and improved method names (#295)

* mcts v1

* bitboard optimization

* bitboard fix & mcts v2 & mcts v3. v3 still in progress and v4 coming soon

* main

---------

Co-authored-by: ramollia <>
Co-authored-by: Stef <stbuwalda@gmail.com>
Co-authored-by: Stef <48526421+StefBuwalda@users.noreply.github.com>
2026-01-07 16:15:49 +01:00
40 changed files with 282 additions and 1757 deletions

View File

@@ -5,5 +5,49 @@ import org.toop.app.App;
public final class Main { public final class Main {
static void main(String[] args) { static void main(String[] args) {
App.run(args); App.run(args);
// testMCTS(10);
} }
// Voor onderzoek
// private static void testMCTS(int games) {
// var random = new ArtificialPlayer<>(new RandomAI<BitboardReversi>(), "Random AI");
// var v1 = new ArtificialPlayer<>(new MCTSAI<BitboardTicTacToe>(10), "MCTS V1 AI");
// var v2 = new ArtificialPlayer<>(new MCTSAI2<BitboardTicTacToe>(10), "MCTS V2 AI");
// var v2_2 = new ArtificialPlayer<>(new MCTSAI2<BitboardTicTacToe>(100), "MCTS V2_2 AI");
// var v3 = new ArtificialPlayer<>(new MCTSAI3<BitboardTicTacToe>(10), "MCTS V3 AI");
// testAI(games, new Player[]{ v1, v2 });
// // testAI(games, new Player[]{ v1, v3 });
// // testAI(games, new Player[]{ random, v3 });
// // testAI(games, new Player[]{ v2, v3 });
// testAI(games, new Player[]{ v2, v3 });
// // testAI(games, new Player[]{ v3, v2 });
// }
// private static void testAI(int games, Player<BitboardReversi>[] ais) {
// int wins = 0;
// int ties = 0;
// for (int i = 0; i < games; i++) {
// final BitboardReversi match = new BitboardReversi(ais);
// while (!match.isTerminal()) {
// final int currentAI = match.getCurrentTurn();
// final long move = ais[currentAI].getMove(match);
// match.play(move);
// }
// if (match.getWinner() < 0) {
// ties++;
// continue;
// }
// wins += match.getWinner() == 0? 1 : 0;
// }
// System.out.printf("Out of %d games, %s won %d -- tied %d -- lost %d, games against %s\n", games, ais[0].getName(), wins, ties, games - wins - ties, ais[1].getName());
// System.out.printf("Average win rate was: %.2f\n\n", wins / (float)games);
// }
} }

View File

@@ -11,7 +11,6 @@ import org.toop.app.widget.popup.ErrorPopup;
import org.toop.app.widget.popup.SendChallengePopup; import org.toop.app.widget.popup.SendChallengePopup;
import org.toop.app.widget.view.ServerView; import org.toop.app.widget.view.ServerView;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.game.players.ArtificialPlayer;
import org.toop.framework.game.players.OnlinePlayer; import org.toop.framework.game.players.OnlinePlayer;
import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.controller.GameController;
import org.toop.framework.eventbus.GlobalEventBus; import org.toop.framework.eventbus.GlobalEventBus;
@@ -20,7 +19,7 @@ import org.toop.framework.networking.connection.clients.TournamentNetworkingClie
import org.toop.framework.networking.connection.events.NetworkEvents; import org.toop.framework.networking.connection.events.NetworkEvents;
import org.toop.framework.networking.connection.types.NetworkingConnector; import org.toop.framework.networking.connection.types.NetworkingConnector;
import org.toop.framework.networking.server.gateway.NettyGatewayServer; import org.toop.framework.networking.server.gateway.NettyGatewayServer;
import org.toop.game.players.ai.mcts.MCTSAI3; import org.toop.framework.game.players.LocalPlayer;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import java.util.Arrays; import java.util.Arrays;
@@ -209,8 +208,7 @@ public final class Server {
information.players[opponentStartingTurn].name = response.opponent(); information.players[opponentStartingTurn].name = response.opponent();
Player[] players = new Player[2]; Player[] players = new Player[2];
players[userStartingTurn] = new LocalPlayer(user);
players[userStartingTurn] = new ArtificialPlayer(new MCTSAI3(1000, 8), user);
players[opponentStartingTurn] = new OnlinePlayer(response.opponent()); players[opponentStartingTurn] = new OnlinePlayer(response.opponent());
switch (type) { switch (type) {
@@ -246,8 +244,7 @@ public final class Server {
private void handleTournamentResult(NetworkEvents.TournamentResultResponse response) { private void handleTournamentResult(NetworkEvents.TournamentResultResponse response) {
IO.println(response.gameType()); IO.println(response.gameType());
IO.println(Arrays.toString(response.names())); IO.println(Arrays.toString(response.names()));
IO.println(Arrays.toString(response.scoreTypes())); IO.println(Arrays.toString(response.scores()));
IO.println(Arrays.toString(response.scores().toArray()));
} }
private void handleReceivedMove(NetworkEvents.GameMoveResponse response) { private void handleReceivedMove(NetworkEvents.GameMoveResponse response) {

View File

@@ -2,13 +2,9 @@ package org.toop.app.canvas;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.toop.app.App; import org.toop.app.App;
import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.game.players.LocalPlayer;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
public class ReversiBitCanvas extends BitGameCanvas { public class ReversiBitCanvas extends BitGameCanvas {
private TurnBasedGame gameCopy;
private int previousCell;
public ReversiBitCanvas() { public ReversiBitCanvas() {
super(Color.GRAY, new Color(0f, 0.4f, 0.2f, 1f), (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, 8, 8, 5, true); super(Color.GRAY, new Color(0f, 0.4f, 0.2f, 1f), (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, 8, 8, 5, true);
canvas.setOnMouseMoved(event -> { canvas.setOnMouseMoved(event -> {
@@ -24,9 +20,6 @@ public class ReversiBitCanvas extends BitGameCanvas {
break; break;
} }
} }
if (hovered != null) {
checkHoverDots(hovered, cellId);
}
}); });
} }
@@ -38,31 +31,9 @@ public class ReversiBitCanvas extends BitGameCanvas {
@Override @Override
public void redraw(TurnBasedGame gameCopy) { public void redraw(TurnBasedGame gameCopy) {
this.gameCopy = gameCopy;
clearAll(); clearAll();
long[] board = gameCopy.getBoard(); long[] board = gameCopy.getBoard();
loopOverBoard(board[0], (i) -> drawDot(Color.WHITE, i)); loopOverBoard(board[0], (i) -> drawDot(Color.WHITE, i));
loopOverBoard(board[1], (i) -> drawDot(Color.BLACK, i)); loopOverBoard(board[1], (i) -> drawDot(Color.BLACK, i));
} }
public void drawLegalDots(TurnBasedGame gameCopy){
long legal = gameCopy.getLegalMoves();
loopOverBoard(legal, (i) -> drawInnerDot(gameCopy.getCurrentTurn()==0?new Color(1f,1f,1f,0.65f) :new Color(0f,0f,0f,0.65f), i,false));
}
private void checkHoverDots(BitGameCanvas.Cell hovered, int cellId){
if (previousCell == cellId){
return;
}
long backflips = ((BitboardReversi)gameCopy).getFlips(1L << previousCell);
loopOverBoard(backflips, (i) -> drawInnerDot(gameCopy.getCurrentTurn()==1?Color.WHITE:Color.BLACK, i,true));
previousCell = cellId;
if (gameCopy.getPlayer(gameCopy.getCurrentTurn()) instanceof LocalPlayer) {
long legal = gameCopy.getLegalMoves();
if ((legal & (1L << cellId)) != 0) {
long flips = ((BitboardReversi) gameCopy).getFlips(1L << cellId);
loopOverBoard(flips, (i) -> drawInnerDot(gameCopy.getCurrentTurn() == 0 ? Color.WHITE : Color.BLACK, i, false));
}
}
}
} }

View File

@@ -5,12 +5,10 @@ import javafx.geometry.Pos;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.app.canvas.GameCanvas; import org.toop.app.canvas.GameCanvas;
import org.toop.app.canvas.ReversiBitCanvas;
import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.view.GameView; import org.toop.app.widget.view.GameView;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.eventbus.GlobalEventBus; import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.controller.GameController;
import org.toop.framework.gameFramework.model.game.threadBehaviour.SupportsOnlinePlay; import org.toop.framework.gameFramework.model.game.threadBehaviour.SupportsOnlinePlay;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
@@ -155,18 +153,6 @@ public class GenericGameController implements GameController {
@Override @Override
public void updateUI() { public void updateUI() {
TurnBasedGame gameCopy = game.deepCopy(); canvas.redraw(game.deepCopy());
canvas.redraw(gameCopy);
String gameType = game.getClass().getSimpleName().replace("Bitboard","");
gameView.nextPlayer(true, getCurrentPlayer().getName(), game.getPlayer(1-getCurrentPlayerIndex()).getName(),gameType);
if (gameType.equals("Reversi")) {
BitboardReversi reversiGame = (BitboardReversi) game;
BitboardReversi.Score reversiScore = reversiGame.getScore();
gameView.setPlayer1Score(reversiScore.black());
gameView.setPlayer2Score(reversiScore.white());
if (getCurrentPlayer() instanceof LocalPlayer) {
((ReversiBitCanvas)canvas).drawLegalDots(gameCopy);
}
}
} }
} }

View File

@@ -11,19 +11,13 @@ import org.toop.framework.game.players.OnlinePlayer;
import java.util.Arrays; import java.util.Arrays;
public class ReversiBitController extends GenericGameController { public class ReversiBitController extends GenericGameController {
private BitboardReversi game;
public ReversiBitController(Player[] players) { public ReversiBitController(Player[] players) {
BitboardReversi game = new BitboardReversi(); BitboardReversi game = new BitboardReversi();
game.init(players); game.init(players);
ThreadBehaviour thread = Arrays.stream(players).anyMatch(e -> e instanceof OnlinePlayer) ? ThreadBehaviour thread = Arrays.stream(players).anyMatch(e -> e instanceof OnlinePlayer) ?
new OnlineThreadBehaviour(game) : new LocalThreadBehaviour(game); new OnlineThreadBehaviour(game) : new LocalThreadBehaviour(game);
super(new ReversiBitCanvas(), game, thread, "Reversi"); super(new ReversiBitCanvas(), game, thread, "Reversi");
} }
public BitboardReversi.Score getScore() {
return game.getScore();
}
} }

View File

@@ -4,7 +4,6 @@ import org.toop.app.widget.complex.ConfirmWidget;
import org.toop.app.widget.complex.PopupWidget; import org.toop.app.widget.complex.PopupWidget;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import org.toop.framework.game.games.reversi.BitboardReversi;
public final class GameOverPopup extends PopupWidget { public final class GameOverPopup extends PopupWidget {
public GameOverPopup(boolean winOrTie, String winner) { public GameOverPopup(boolean winOrTie, String winner) {
@@ -16,6 +15,7 @@ public final class GameOverPopup extends PopupWidget {
else{ else{
confirmWidget.setMessage("It was a tie!"); confirmWidget.setMessage("It was a tie!");
} }
confirmWidget.addButton("ok", this::hide); confirmWidget.addButton("ok", this::hide);
add(Pos.CENTER, confirmWidget); add(Pos.CENTER, confirmWidget);

View File

@@ -6,8 +6,6 @@ import javafx.scene.text.Font;
import org.toop.app.widget.Primitive; import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.popup.GameOverPopup; import org.toop.app.widget.popup.GameOverPopup;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@@ -26,8 +24,6 @@ public final class GameView extends ViewWidget {
private final Text player2Header; private final Text player2Header;
private Circle player1Icon; private Circle player1Icon;
private Circle player2Icon; private Circle player2Icon;
private final Text player1Score;
private final Text player2Score;
private final Button forfeitButton; private final Button forfeitButton;
private final Button exitButton; private final Button exitButton;
private final TextField chatInput; private final TextField chatInput;
@@ -42,8 +38,6 @@ public final class GameView extends ViewWidget {
player2Header = Primitive.header(""); player2Header = Primitive.header("");
player1Icon = new Circle(); player1Icon = new Circle();
player2Icon = new Circle(); player2Icon = new Circle();
player1Score = Primitive.header("");
player2Score = Primitive.header("");
if (onForfeit != null) { if (onForfeit != null) {
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run(), false); forfeitButton = Primitive.button("forfeit", () -> onForfeit.run(), false);
@@ -100,7 +94,7 @@ public final class GameView extends ViewWidget {
} }
} }
public void nextPlayer(boolean isMe, String currentPlayer, String nextPlayer, String GameType) { public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer, char GameType) {
Platform.runLater(() -> { Platform.runLater(() -> {
if (!(hasSet)) { if (!(hasSet)) {
playerHeader.setText(currentPlayer + " vs. " + nextPlayer); playerHeader.setText(currentPlayer + " vs. " + nextPlayer);
@@ -118,8 +112,8 @@ public final class GameView extends ViewWidget {
new GameOverPopup(iWon, winner).show(Pos.CENTER); new GameOverPopup(iWon, winner).show(Pos.CENTER);
} }
private void setPlayerHeaders(boolean isMe, String currentPlayer, String nextPlayer, String GameType) { private void setPlayerHeaders(boolean isMe, String currentPlayer, String nextPlayer, char GameType) {
if (Objects.equals(GameType, "TicTacToe")) { if (GameType == 'T') {
if (isMe) { if (isMe) {
player1Header.setText("X: " + currentPlayer); player1Header.setText("X: " + currentPlayer);
player2Header.setText("O: " + nextPlayer); player2Header.setText("O: " + nextPlayer);
@@ -130,7 +124,7 @@ public final class GameView extends ViewWidget {
} }
setPlayerInfoTTT(); setPlayerInfoTTT();
} }
else if (Objects.equals(GameType, "Reversi")) { else if (GameType == 'R') {
if (isMe) { if (isMe) {
player1Header.setText(currentPlayer); player1Header.setText(currentPlayer);
player2Header.setText(nextPlayer); player2Header.setText(nextPlayer);
@@ -157,16 +151,14 @@ public final class GameView extends ViewWidget {
private void setPlayerInfoReversi() { private void setPlayerInfoReversi() {
var player1box = Primitive.hbox( var player1box = Primitive.hbox(
player1Icon, player1Icon,
player1Header, player1Header
player1Score
); );
player1box.getStyleClass().add("hboxspacing"); player1box.getStyleClass().add("hboxspacing");
var player2box = Primitive.hbox( var player2box = Primitive.hbox(
player2Icon, player2Icon,
player2Header, player2Header
player2Score
); );
player2box.getStyleClass().add("hboxspacing"); player2box.getStyleClass().add("hboxspacing");
@@ -180,16 +172,8 @@ public final class GameView extends ViewWidget {
player1Icon.setRadius(player1Header.fontProperty().map(Font::getSize).getValue()); player1Icon.setRadius(player1Header.fontProperty().map(Font::getSize).getValue());
player2Icon.setRadius(player2Header.fontProperty().map(Font::getSize).getValue()); player2Icon.setRadius(player2Header.fontProperty().map(Font::getSize).getValue());
player1Icon.setFill(Color.WHITE); player1Icon.setFill(Color.BLACK);
player2Icon.setFill(Color.BLACK); player2Icon.setFill(Color.WHITE);
add(Pos.TOP_RIGHT, playerInfo); add(Pos.TOP_RIGHT, playerInfo);
} }
public void setPlayer1Score(int score) {
player1Score.setText("(" + Integer.toString(score) + ")");
}
public void setPlayer2Score(int score) {
player2Score.setText("(" + Integer.toString(score) + ")");
}
} }

View File

@@ -4,7 +4,6 @@ import javafx.application.Platform;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.gameControllers.ReversiBitController; import org.toop.app.gameControllers.ReversiBitController;
import org.toop.app.gameControllers.TicTacToeBitController; import org.toop.app.gameControllers.TicTacToeBitController;
import org.toop.framework.game.players.LocalPlayer;
import org.toop.framework.gameFramework.controller.GameController; import org.toop.framework.gameFramework.controller.GameController;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.players.ArtificialPlayer; import org.toop.framework.game.players.ArtificialPlayer;
@@ -13,10 +12,11 @@ import org.toop.app.widget.complex.PlayerInfoWidget;
import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.popup.ErrorPopup; import org.toop.app.widget.popup.ErrorPopup;
import org.toop.app.widget.tutorial.*; import org.toop.app.widget.tutorial.*;
import org.toop.framework.game.players.LocalPlayer;
import org.toop.game.players.ai.MCTSAI;
import org.toop.game.players.ai.MCTSAI2;
import org.toop.game.players.ai.MCTSAI3;
import org.toop.game.players.ai.MiniMaxAI; import org.toop.game.players.ai.MiniMaxAI;
import org.toop.game.players.ai.mcts.MCTSAI1;
import org.toop.game.players.ai.mcts.MCTSAI3;
import org.toop.game.players.ai.mcts.MCTSAI4;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@@ -54,7 +54,7 @@ public class LocalMultiplayerView extends ViewWidget {
if (information.players[0].isHuman) { if (information.players[0].isHuman) {
players[0] = new LocalPlayer(information.players[0].name); players[0] = new LocalPlayer(information.players[0].name);
} else { } else {
players[0] = new ArtificialPlayer(new MCTSAI1(100), "MCTS AI"); players[0] = new ArtificialPlayer(new MCTSAI(100), "MCTS AI");
} }
if (information.players[1].isHuman) { if (information.players[1].isHuman) {
players[1] = new LocalPlayer(information.players[1].name); players[1] = new LocalPlayer(information.players[1].name);
@@ -82,13 +82,13 @@ public class LocalMultiplayerView extends ViewWidget {
if (information.players[0].isHuman) { if (information.players[0].isHuman) {
players[0] = new LocalPlayer(information.players[0].name); players[0] = new LocalPlayer(information.players[0].name);
} else { } else {
// players[0] = new ArtificialPlayer(new RandomAI(), "Random AI"); // players[0] = new ArtificialPlayer(new RandomAI<BitboardReversi>(), "Random AI");
players[0] = new ArtificialPlayer(new MCTSAI1(100), "MCTS V1 AI"); players[0] = new ArtificialPlayer(new MCTSAI3(50), "MCTS V3 AI");
} }
if (information.players[1].isHuman) { if (information.players[1].isHuman) {
players[1] = new LocalPlayer(information.players[1].name); players[1] = new LocalPlayer(information.players[1].name);
} else { } else {
players[1] = new ArtificialPlayer(new MCTSAI4(100, 8), "MCTS V4 AI"); players[1] = new ArtificialPlayer(new MCTSAI2(50), "MCTS V2 AI");
} }
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) { if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) {
new ShowEnableTutorialWidget( new ShowEnableTutorialWidget(

View File

@@ -17,8 +17,8 @@ public abstract class BitboardGame implements TurnBasedGame {
private Player[] players; private Player[] players;
// long is 64 bits. Every game has a limit of 64 cells maximum. // long is 64 bits. Every game has a limit of 64 cells maximum.
protected final long[] playerBitboard; private final long[] playerBitboard;
protected int currentTurn = 0; private int currentTurn = 0;
private final int playerCount; private final int playerCount;
public BitboardGame(int columnSize, int rowSize, int playerCount) { public BitboardGame(int columnSize, int rowSize, int playerCount) {
@@ -74,8 +74,6 @@ public abstract class BitboardGame implements TurnBasedGame {
return playerBitboard.length; return playerBitboard.length;
} }
public int getAmountOfTurns() { return currentTurn; }
public int getCurrentTurn() { public int getCurrentTurn() {
return getCurrentPlayerIndex(); return getCurrentPlayerIndex();
} }

View File

@@ -1,39 +1,31 @@
package org.toop.framework.game.gameThreads; package org.toop.framework.game.gameThreads;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour; import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.gameFramework.view.GUIEvents;
import org.toop.framework.utils.ImmutablePair; import org.toop.framework.utils.ImmutablePair;
import org.toop.framework.utils.Pair; import org.toop.framework.utils.Pair;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import static org.toop.framework.gameFramework.GameState.TURN_SKIPPED;
import static org.toop.framework.gameFramework.GameState.WIN;
public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable { public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Runnable {
private final Consumer<ImmutablePair<String, Integer>> onPlayerMove; private final Consumer<ImmutablePair<String, Integer>> onPlayerMove;
private final Consumer<Pair<GameState, Integer>> onGameEnd; private final Consumer<Pair<GameState, Integer>> onGameEnd;
private final ExecutorService moveExecutor = Executors.newSingleThreadExecutor();
private final Duration timeOut;
/** /**
* Creates a new base behaviour for the specified game. * Creates a new base behaviour for the specified game.
* *
* @param game the turn-based game to control * @param game the turn-based game to control
*/ */
public ServerThreadBehaviour( public ServerThreadBehaviour(TurnBasedGame game, Consumer<ImmutablePair<String, Integer>> onPlayerMove, Consumer<Pair<GameState, Integer>> onGameEnd) {
TurnBasedGame game,
Consumer<ImmutablePair<String,
Integer>> onPlayerMove,
Consumer<Pair<GameState, Integer>> onGameEnd,
Duration timeOut
) {
this.onPlayerMove = onPlayerMove; this.onPlayerMove = onPlayerMove;
this.onGameEnd = onGameEnd; this.onGameEnd = onGameEnd;
this.timeOut = timeOut;
super(game); super(game);
} }
@@ -67,43 +59,24 @@ public class ServerThreadBehaviour extends AbstractThreadBehaviour implements Ru
public void run() { public void run() {
while (isRunning.get()) { while (isRunning.get()) {
Player currentPlayer = game.getPlayer(game.getCurrentTurn()); Player currentPlayer = game.getPlayer(game.getCurrentTurn());
long move = currentPlayer.getMove(game.deepCopy());
Future<Long> move = moveExecutor.submit(() -> currentPlayer.getMove(game.deepCopy())); PlayResult result = game.play(move);
PlayResult result;
try {
long moveResult = move.get(timeOut.toMillis(), TimeUnit.MILLISECONDS);
result = game.play(moveResult);
GameState state = result.state(); GameState state = result.state();
notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(moveResult))); notifyPlayerMove(new ImmutablePair<>(currentPlayer.getName(), Long.numberOfTrailingZeros(move)));
switch (state) { switch (state) {
case WIN, DRAW -> { case WIN, DRAW -> {
isRunning.set(false); isRunning.set(false);
moveExecutor.shutdown();
notifyGameEnd(new ImmutablePair<>(state, game.getWinner())); notifyGameEnd(new ImmutablePair<>(state, game.getWinner()));
} }
case NORMAL, TURN_SKIPPED -> { /* continue normally */ } case NORMAL, TURN_SKIPPED -> { /* continue normally */ }
default -> { default -> {
logger.error("Unexpected state {}", state); logger.error("Unexpected state {}", state);
isRunning.set(false); isRunning.set(false);
moveExecutor.shutdown();
throw new RuntimeException("Unknown state: " + state); throw new RuntimeException("Unknown state: " + state);
} }
} }
} catch (InterruptedException | ExecutionException e) {
isRunning.set(false);
notifyGameEnd(new ImmutablePair<>(GameState.DRAW, 0));
moveExecutor.shutdown();
return;
} catch (TimeoutException e) {
isRunning.set(false);
notifyGameEnd(new ImmutablePair<>(GameState.WIN, 1+game.getWinner()%2));
moveExecutor.shutdown();
return;
}
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import org.toop.framework.game.BitboardGame;
import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.BitboardGame;
public class BitboardReversi extends BitboardGame { public class BitboardReversi extends BitboardGame {
@@ -320,58 +321,8 @@ public class BitboardReversi extends BitboardGame {
else if (blackCount > whiteCount){ else if (blackCount > whiteCount){
return 0; return 0;
} }
else { else{
return 1; return 1;
} }
} }
@Override
public float rateMove(long move) {
final long corners = 0x8100000000000081L;
if ((move & corners) != 0L) {
return 0.4f;
}
final long xSquares = 0x0042000000004200L;
if ((move & xSquares) != 0) {
return -0.4f;
}
final long cSquares = 0x4281000000008142L;
if ((move & cSquares) != 0) {
return -0.1f;
}
return 0.0f;
}
@Override
public long heuristicMove(long legalMoves) {
long bestMove = 0L;
float bestMoveRate = Float.NEGATIVE_INFINITY;
while (legalMoves != 0L) {
final long move = legalMoves & -legalMoves;
final float moveRate = rateMove(move);
if (moveRate > bestMoveRate) {
bestMove = move;
bestMoveRate = moveRate;
}
legalMoves &= ~move;
}
return bestMove;
}
@Override
public void setFrom(long player1, long player2, int turn) {
this.playerBitboard[0] = player1;
this.playerBitboard[1] = player2;
this.currentTurn = turn;
}
} }

View File

@@ -2,7 +2,6 @@ package org.toop.framework.game.games.tictactoe;
import org.toop.framework.gameFramework.GameState; import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.gameFramework.model.player.Player;
import org.toop.framework.game.BitboardGame; import org.toop.framework.game.BitboardGame;
@@ -111,18 +110,4 @@ public class BitboardTicTacToe extends BitboardGame {
public BitboardTicTacToe deepCopy() { public BitboardTicTacToe deepCopy() {
return new BitboardTicTacToe(this); return new BitboardTicTacToe(this);
} }
@Override
public float rateMove(long move) {
return 0.0f;
}
@Override
public long heuristicMove(long legalMoves) {
return legalMoves;
}
@Override
public void setFrom(long player1, long player2, int turn) {
}
} }

View File

@@ -14,9 +14,7 @@ import org.toop.framework.gameFramework.model.game.TurnBasedGame;
*/ */
public class ArtificialPlayer extends AbstractPlayer { public class ArtificialPlayer extends AbstractPlayer {
/** /** The AI instance used to calculate moves. */
* The AI instance used to calculate moves.
*/
private final AI ai; private final AI ai;
/** /**
@@ -59,8 +57,4 @@ public class ArtificialPlayer extends AbstractPlayer {
public ArtificialPlayer deepCopy() { public ArtificialPlayer deepCopy() {
return new ArtificialPlayer(this); return new ArtificialPlayer(this);
} }
public AI getAi() {
return ai;
}
} }

View File

@@ -29,8 +29,13 @@ public class ServerPlayer extends AbstractPlayer {
@Override @Override
public long determineMove(TurnBasedGame game) { public long determineMove(TurnBasedGame game) {
lastMove = new CompletableFuture<>(); lastMove = new CompletableFuture<>();
System.out.println("Sending yourturn");
client.send("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}"); client.send("SVR GAME YOURTURN {TURNMESSAGE: \"<bericht voor deze beurt>\"}\n");
return lastMove.join(); try {
return lastMove.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return 0;
}
} }
} }

View File

@@ -13,9 +13,4 @@ public interface TurnBasedGame extends DeepCopyable<TurnBasedGame> {
PlayResult play(long move); PlayResult play(long move);
PlayResult getState(); PlayResult getState();
boolean isTerminal(); boolean isTerminal();
float rateMove(long move);
long heuristicMove(long legalMoves);
void setFrom(long player1, long player2, int turn);
} }

View File

@@ -65,7 +65,7 @@ public class NetworkEvents extends EventsBase {
public record GameResultResponse(long clientId, String condition) public record GameResultResponse(long clientId, String condition)
implements GenericEvent {} implements GenericEvent {}
public record TournamentResultResponse(long clientId, String gameType, String[] names, String[] scoreTypes, List<Integer[]> scores) public record TournamentResultResponse(long clientId, String gameType, String[] names, Integer[] scores)
implements GenericEvent {} implements GenericEvent {}
/** Indicates that a game move has been processed or received. */ /** Indicates that a game move has been processed or received. */

View File

@@ -3,9 +3,7 @@ package org.toop.framework.networking.connection.handlers;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -119,13 +117,10 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
} }
private void resultsHandler(String rec) { private void resultsHandler(String rec) {
// TODO all of this
IO.println(rec); IO.println(rec);
String gameTypeRaw = extract(rec, "GAMETYPE"); String gameTypeRaw = extract(rec, "GAMETYPE");
String usersRaw = extract(rec, "USERS"); String usersRaw = extract(rec, "USERS");
String scoreTypesRaw = extract(rec, "SCORETYPES");
String scoresRaw = extract(rec, "SCORES"); String scoresRaw = extract(rec, "SCORES");
if (usersRaw == null) return; if (usersRaw == null) return;
@@ -139,32 +134,16 @@ public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
users = new String[]{}; users = new String[]{};
} }
String[] scoreTypes;
if (scoreTypesRaw.length() > 2) {
scoreTypes = Arrays.stream(scoreTypesRaw.substring(1, usersRaw.length() - 1).split(","))
.map(s -> s.trim().replace("\"", ""))
.toArray(String[]::new);
} else {
scoreTypes = new String[]{};
}
if (scoresRaw == null) return; if (scoresRaw == null) return;
if (scoresRaw.length() > 2) { if (scoresRaw.length() > 2) {
List<Integer[]> scores = Arrays.stream( Integer[] scores = Arrays.stream(scoresRaw.substring(1, scoresRaw.length() - 1).split(","))
scoresRaw.substring(1, scoresRaw.length() - 1) // remove outer []
.split("\\],\\[")
)
.map(part -> part.replace("[", "").replace("]", ""))
.map(part -> Arrays.stream(part.split(","))
.map(String::trim) .map(String::trim)
.map(Integer::parseInt) .map(Integer::parseInt)
.toArray(Integer[]::new) .toArray(Integer[]::new);
)
.toList();
eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scoreTypes, scores)); eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scores));
} else { } else {
eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scoreTypes, new ArrayList<>())); eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, new Integer[]{}));
} }
} }

View File

@@ -5,7 +5,7 @@ import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.client.NettyClient;
import java.time.Duration; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -19,13 +19,12 @@ public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
private final CompletableFuture<Integer> resultFuture; private final CompletableFuture<Integer> resultFuture;
public OnlineTurnBasedGame(NettyClient[] admins, TurnBasedGame game, CompletableFuture<Integer> resultFuture, Duration timeOut, NettyClient... clients) { public OnlineTurnBasedGame(NettyClient[] admins, TurnBasedGame game, CompletableFuture<Integer> resultFuture, NettyClient... clients) {
this.game = game; this.game = game;
this.gameThread = new ServerThreadBehaviour( this.gameThread = new ServerThreadBehaviour(
game, game,
(pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()), (pair) -> notifyMoveMade(pair.getLeft(), pair.getRight()),
(pair) -> notifyGameEnd(pair.getLeft(), pair.getRight()), (pair) -> notifyGameEnd(pair.getLeft(), pair.getRight())
timeOut
); );
this.resultFuture = resultFuture; this.resultFuture = resultFuture;
this.clients = clients; this.clients = clients;
@@ -43,15 +42,17 @@ public class OnlineTurnBasedGame implements OnlineGame<TurnBasedGame> {
private void notifyGameEnd(GameState state, int winner) { private void notifyGameEnd(GameState state, int winner) {
if (state == GameState.DRAW) { if (state == GameState.DRAW) {
Arrays.stream(admins).forEach(a -> a.send("SVR GAME END")); Arrays.stream(admins).forEach(a -> a.send(
String.format("SVR GAME END")
));
for (NettyClient client : clients) { for (NettyClient client : clients) {
client.send("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"); client.send(String.format("SVR GAME DRAW {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"));
} }
} else { } else {
Arrays.stream(admins).forEach(a -> a.send("SVR GAME END")); Arrays.stream(admins).forEach(a -> a.send("SVR GAME END"));
clients[winner].send("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"); clients[winner].send(String.format("SVR GAME WIN {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"));
clients[(winner+1)%2].send("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"); clients[(winner+1)%2].send(String.format("SVR GAME LOSS {PLAYERONESCORE: \"<score speler1>\", PLAYERTWOSCORE: \"<score speler2>\", COMMENT: \"<comment>\"}"));
} }
// Remove game from clients // Remove game from clients

View File

@@ -11,8 +11,9 @@ import org.toop.framework.networking.server.stores.SubscriptionStore;
import org.toop.framework.networking.server.stores.TurnBasedGameStore; import org.toop.framework.networking.server.stores.TurnBasedGameStore;
import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore; import org.toop.framework.networking.server.stores.TurnBasedGameTypeStore;
import org.toop.framework.networking.server.tournaments.*; import org.toop.framework.networking.server.tournaments.*;
import org.toop.framework.networking.server.tournaments.matchmakers.DoubleRoundRobinMatchMaker; import org.toop.framework.networking.server.tournaments.matchmakers.RoundRobinMatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.*; import org.toop.framework.networking.server.tournaments.scoresystems.BasicScoreSystem;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import org.toop.framework.utils.ImmutablePair; import org.toop.framework.utils.ImmutablePair;
import java.util.*; import java.util.*;
@@ -145,7 +146,6 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
getAdmins().toArray(NettyClient[]::new), getAdmins().toArray(NettyClient[]::new),
gameTypesStore.create(gameType), gameTypesStore.create(gameType),
gameResult, gameResult,
turnTime,
clients clients
); );
@@ -167,7 +167,6 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
clients[0].name(), clients[0].name(),
gameType, gameType,
clients[0].name())); clients[0].name()));
game.start(); game.start();
return grfReturn; return grfReturn;
} catch (Exception e) { } catch (Exception e) {
@@ -298,13 +297,10 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
Tournament tournament = new Tournament.Builder() Tournament tournament = new Tournament.Builder()
.matchExecutor(this::startGame) .matchExecutor(this::startGame)
.tournamentRunner(new AsyncTournamentRunner()) .tournamentRunner(new AsyncTournamentRunner())
.matchMaker(new DoubleRoundRobinMatchMaker()) .matchMaker(new RoundRobinMatchMaker())
.addScoreSystem(new MatchCountScoreSystem()) .scoreSystem(new BasicScoreSystem())
.addScoreSystem(new WinCountScoreSystem())
.addScoreSystem(new DrawCountScoreSystem())
.addScoreSystem(new LoseCountScoreSystem())
.resultBroadcaster(this::endTournament) .resultBroadcaster(this::endTournament)
.turnTimeout(Duration.ofSeconds(10)) .turnTimeout(Duration.ofSeconds(5))
.addPlayers(tournamentUsers.toArray(NettyClient[]::new)) .addPlayers(tournamentUsers.toArray(NettyClient[]::new))
.addAdmins(admins.toArray(NettyClient[]::new)) .addAdmins(admins.toArray(NettyClient[]::new))
.build(); .build();
@@ -312,43 +308,26 @@ public class Server implements GameServer<TurnBasedGame, NettyClient, Long> {
new Thread(() -> tournament.run(gameType)).start(); new Thread(() -> tournament.run(gameType)).start();
} }
public void endTournament(List<IntegerScoreSystem> systems) { public void endTournament(IntegerScoreSystem score) {
if (systems.isEmpty()) return;
Map<String, List<ImmutablePair<String, Integer>>> combined = new HashMap<>(); List<String> u = new ArrayList<>();
List<Integer> s = new ArrayList<>();
for (var system : systems) { for (var entry : score.getScore().entrySet()) {
for (var player : system.getScore().keySet()) { u.add(entry.getKey().name());
combined.putIfAbsent(player.name(), new ArrayList<>()); s.add(entry.getValue());
combined.get(player.name()).addLast(new ImmutablePair<>(system.scoreName(), system.getScore().get(player)));
}
}
List<String> names = new ArrayList<>();
List<String> systemNames = new ArrayList<>();
List<List<Integer>> scores = new ArrayList<>();
for (var player : combined.entrySet()) {
names.addLast(player.getKey());
scores.addLast(new ArrayList<>());
for (var system : player.getValue()) {
if (!systemNames.contains(system.getLeft())) systemNames.addLast(system.getLeft());
scores.getLast().addLast(system.getRight());
}
} }
Gson gson = new Gson(); Gson gson = new Gson();
String namesJson = gson.toJson(names); String users = gson.toJson(u);
String systemNamesJson = gson.toJson(systemNames); String scores = gson.toJson(s);
String scoresJson = gson.toJson(scores);
String msg = String.format( String msg = String.format(
"SVR RESULTS {GAMETYPE: \"%s\", USERS: %s, SCORETYPES: %s, SCORES: %s, TOURNAMENT: 1}", "SVR RESULTS {GAMETYPE: \"%s\", USERS: %s, SCORES: %s, TOURNAMENT: 1}",
"none", // TODO gametype "none", // TODO gametype
namesJson, users,
systemNamesJson, scores
scoresJson
); );
for (var user : onlineUsers()) { for (var user : onlineUsers()) {

View File

@@ -16,14 +16,16 @@ public class AsyncTournamentRunner implements TournamentRunner {
public void run( public void run(
MatchExecutor matchRunner, MatchExecutor matchRunner,
MatchMaker matchMaker, MatchMaker matchMaker,
List<IntegerScoreSystem> scoreSystems, IntegerScoreSystem scoreSystem,
ResultBroadcaster<IntegerScoreSystem> broadcaster, ResultBroadcaster<IntegerScoreSystem> broadcaster,
Duration turnTime, Duration turnTime,
String gameType String gameType
) { ) {
ExecutorService matchExecutor = Executors.newVirtualThreadPerTaskExecutor(); ExecutorService matchExecutor =
ExecutorService scoringExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
Queue<TournamentMatch> pendingMatches = new ConcurrentLinkedQueue<>(); Queue<TournamentMatch> pendingMatches = new ConcurrentLinkedQueue<>();
matchMaker.forEach(pendingMatches::add); matchMaker.forEach(pendingMatches::add);
@@ -54,11 +56,7 @@ public class AsyncTournamentRunner implements TournamentRunner {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
try { try {
GameResultFuture game = matchRunner.submit(gameType, turnTime, a, b); GameResultFuture game = matchRunner.submit(gameType, turnTime, a, b);
scoreSystem.result(match, game.result().join());
CompletableFuture.runAsync(
() -> scoreSystems.forEach(s -> s.result(match, game.result().join())),
scoringExecutor
).join();
} finally { } finally {
a.clearGame(); a.clearGame();
b.clearGame(); b.clearGame();
@@ -75,7 +73,7 @@ public class AsyncTournamentRunner implements TournamentRunner {
Thread.sleep(10); // Safety Thread.sleep(10); // Safety
} }
broadcaster.broadcast(scoreSystems); broadcaster.broadcast(scoreSystem);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View File

@@ -6,7 +6,6 @@ import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import java.time.Duration; import java.time.Duration;
import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
public class BasicTournamentRunner implements TournamentRunner { public class BasicTournamentRunner implements TournamentRunner {
@@ -14,7 +13,7 @@ public class BasicTournamentRunner implements TournamentRunner {
public void run( public void run(
MatchExecutor matchExecutor, MatchExecutor matchExecutor,
MatchMaker matchMaker, MatchMaker matchMaker,
List<IntegerScoreSystem> scoreSystems, IntegerScoreSystem scoreSystem,
ResultBroadcaster<IntegerScoreSystem> broadcaster, ResultBroadcaster<IntegerScoreSystem> broadcaster,
Duration turnTime, Duration turnTime,
String gameType String gameType
@@ -25,13 +24,13 @@ public class BasicTournamentRunner implements TournamentRunner {
for (TournamentMatch match : matchMaker) { for (TournamentMatch match : matchMaker) {
// Play game and await the results // Play game and await the results
GameResultFuture game = matchExecutor.submit(gameType, turnTime, match.getClient0(), match.getClient1()); GameResultFuture game = matchExecutor.submit(gameType, turnTime, match.getClient0(), match.getClient1());
scoreSystems.forEach(e -> e.result(match, game.result().join())); scoreSystem.result(match, game.result().join());
match.getClient0().clearGame(); match.getClient0().clearGame();
match.getClient1().clearGame(); match.getClient1().clearGame();
} }
broadcaster.broadcast(scoreSystems); broadcaster.broadcast(scoreSystem);
}); });
} finally { } finally {
threadPool.shutdown(); threadPool.shutdown();

View File

@@ -2,9 +2,7 @@ package org.toop.framework.networking.server.tournaments;
import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem; import org.toop.framework.networking.server.tournaments.scoresystems.ScoreSystem;
import java.util.List;
@FunctionalInterface @FunctionalInterface
public interface ResultBroadcaster<T extends ScoreSystem<?, ?, ?>> { public interface ResultBroadcaster<T extends ScoreSystem<?, ?, ?>> {
void broadcast(List<T> scoreSystem); void broadcast(T scoreSystem);
} }

View File

@@ -4,52 +4,45 @@ import org.toop.framework.networking.server.MatchExecutor;
import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker; import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Objects; import java.util.Objects;
public class Tournament { public class Tournament {
private final MatchExecutor matchExecutor; private final MatchExecutor matchExecutor;
private final List<IntegerScoreSystem> scoreSystems; private final IntegerScoreSystem scoreSystem;
private final TournamentRunner tournamentRunner; private final TournamentRunner tournamentRunner;
private final MatchMaker matchMaker; private final MatchMaker matchMaker;
private final ResultBroadcaster<IntegerScoreSystem> broadcaster; private final ResultBroadcaster<IntegerScoreSystem> broadcaster;
private final NettyClient[] players; private final NettyClient[] players;
private final Duration turnTime; private final Duration turnTime;
private final Shuffler shuffler;
private Tournament(Tournament.Builder builder) { private Tournament(Tournament.Builder builder) {
matchExecutor = builder.matchExecutor; matchExecutor = builder.matchExecutor;
scoreSystems = builder.scoreSystems; scoreSystem = builder.scoreSystem;
tournamentRunner = builder.tournamentRunner; tournamentRunner = builder.tournamentRunner;
matchMaker = builder.matchMaker; matchMaker = builder.matchMaker;
broadcaster = builder.broadcaster; broadcaster = builder.broadcaster;
players = builder.players; players = builder.players;
turnTime = builder.turnTime; turnTime = builder.turnTime;
shuffler = builder.shuffler;
} }
public void run(String gameType) { public void run(String gameType) throws IllegalArgumentException {
Arrays.stream(players).forEach(e -> { Arrays.stream(players).forEach(e -> {
matchMaker.addPlayer(e); matchMaker.addPlayer(e);
scoreSystems.forEach(k -> k.addPlayer(e)); scoreSystem.addPlayer(e);
}); });
if (shuffler != null) matchMaker.shuffle(shuffler); tournamentRunner.run(matchExecutor, matchMaker, scoreSystem, broadcaster, turnTime, gameType);
tournamentRunner.run(matchExecutor, matchMaker, scoreSystems, broadcaster, turnTime, gameType);
} }
public static class Builder { public static class Builder {
private MatchExecutor matchExecutor; private MatchExecutor matchExecutor;
private List<IntegerScoreSystem> scoreSystems = new ArrayList<>(); private IntegerScoreSystem scoreSystem;
private TournamentRunner tournamentRunner; private TournamentRunner tournamentRunner;
private MatchMaker matchMaker; private MatchMaker matchMaker;
private ResultBroadcaster<IntegerScoreSystem> broadcaster; private ResultBroadcaster<IntegerScoreSystem> broadcaster;
@@ -57,15 +50,14 @@ public class Tournament {
private NettyClient[] observors; private NettyClient[] observors;
private NettyClient[] admins; private NettyClient[] admins;
private Duration turnTime = Duration.ofSeconds(10); private Duration turnTime = Duration.ofSeconds(10);
private Shuffler shuffler;
public Builder matchExecutor(MatchExecutor matchExecutor) { public Builder matchExecutor(MatchExecutor matchExecutor) {
this.matchExecutor = matchExecutor; this.matchExecutor = matchExecutor;
return this; return this;
} }
public Builder addScoreSystem(IntegerScoreSystem scoreSystem) { public Builder scoreSystem(IntegerScoreSystem scoreSystem) {
this.scoreSystems.addLast(scoreSystem); this.scoreSystem = scoreSystem;
return this; return this;
} }
@@ -104,13 +96,9 @@ public class Tournament {
return this; return this;
} }
public Builder addMatchShuffler(Shuffler shuffler) {
this.shuffler = shuffler;
return this;
}
public Tournament build() { public Tournament build() {
Objects.requireNonNull(matchExecutor, "matchExecutor"); Objects.requireNonNull(matchExecutor, "matchExecutor");
Objects.requireNonNull(scoreSystem, "scoreSystem");
Objects.requireNonNull(tournamentRunner, "tournamentRunner"); Objects.requireNonNull(tournamentRunner, "tournamentRunner");
Objects.requireNonNull(matchMaker, "matchMaker"); Objects.requireNonNull(matchMaker, "matchMaker");
Objects.requireNonNull(broadcaster, "resultBroadcaster"); // TODO is not always necessary and needs to be more generic, not just at the end Objects.requireNonNull(broadcaster, "resultBroadcaster"); // TODO is not always necessary and needs to be more generic, not just at the end

View File

@@ -5,9 +5,8 @@ import org.toop.framework.networking.server.tournaments.matchmakers.MatchMaker;
import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem; import org.toop.framework.networking.server.tournaments.scoresystems.IntegerScoreSystem;
import java.time.Duration; import java.time.Duration;
import java.util.List;
public interface TournamentRunner { public interface TournamentRunner {
void run(MatchExecutor matchExecutor, MatchMaker matchMaker, List<IntegerScoreSystem> scoreSystems, void run(MatchExecutor matchExecutor, MatchMaker matchMaker, IntegerScoreSystem scoreSystem,
ResultBroadcaster<IntegerScoreSystem> broadcaster, Duration turnTime, String gameType); ResultBroadcaster<IntegerScoreSystem> broadcaster, Duration turnTime, String gameType);
} }

View File

@@ -1,81 +0,0 @@
package org.toop.framework.networking.server.tournaments.matchmakers;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import org.toop.framework.networking.server.tournaments.shufflers.Shuffler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class DoubleRoundRobinMatchMaker implements MatchMaker {
private final List<NettyClient> players = new ArrayList<>();
public DoubleRoundRobinMatchMaker() {} // TODO let user decide store type
@Override
public void addPlayer(NettyClient player) {
players.addLast(player);
}
@Override
public void shuffle(Shuffler shuffler) {
if (players.size() < 2) return;
shuffler.shuffle(players);
}
@Override
public List<NettyClient> getPlayers() {
return players;
}
@Override
public Iterator<TournamentMatch> iterator() {
return new Iterator<>() {
private int i = 0;
private int j = 1;
private boolean reverse = false;
@Override
public boolean hasNext() {
return players.size() > 1
&& i < players.size() - 1
&& j < players.size();
}
@Override
public TournamentMatch next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
NettyClient home = players.get(i);
NettyClient away = players.get(j);
TournamentMatch match = reverse ? new TournamentMatch(away, home) : new TournamentMatch(home, away);
advance();
return match;
}
private void advance() {
j++;
if (j >= players.size()) {
i++;
j = i + 1;
if (i >= players.size() - 1) {
if (!reverse) {
reverse = true;
i = 0;
j = 1;
}
}
}
}
};
}
}

View File

@@ -37,6 +37,7 @@ public class RoundRobinMatchMaker implements MatchMaker {
private int i = 0; private int i = 0;
private int j = 1; private int j = 1;
private boolean reverse = false;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
@@ -54,13 +55,26 @@ public class RoundRobinMatchMaker implements MatchMaker {
NettyClient home = players.get(i); NettyClient home = players.get(i);
NettyClient away = players.get(j); NettyClient away = players.get(j);
TournamentMatch match = reverse ? new TournamentMatch(away, home) : new TournamentMatch(home, away);
advance();
return match;
}
private void advance() {
j++; j++;
if (j >= players.size()) { if (j >= players.size()) {
i++; i++;
j = i + 1; j = i + 1;
}
return new TournamentMatch(home, away); if (i >= players.size() - 1) {
if (!reverse) {
reverse = true;
i = 0;
j = 1;
}
}
}
} }
}; };
} }

View File

@@ -3,21 +3,17 @@ package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient; import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch; import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class WinCountScoreSystem implements IntegerScoreSystem { public class BasicScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>(); private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0; private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1; private final int WIN_POINTS = 1;
public WinCountScoreSystem() {} // TODO let user decide store type public BasicScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "wins";
}
@Override @Override
public void addPlayer(NettyClient user) { public void addPlayer(NettyClient user) {

View File

@@ -1,43 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DrawCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public DrawCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "draws";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
switch (result) {
case 0, 1 -> {}
case -1 -> {
scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
}
default -> throw new IllegalArgumentException("Unknown result: " + result);
}
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -1,41 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class LoseCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public LoseCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "loses";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
switch (result) {
case 0 -> scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
case 1 -> scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
case -1 -> {} // Draw
default -> throw new IllegalArgumentException("Unknown result: " + result);
}
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -1,37 +0,0 @@
package org.toop.framework.networking.server.tournaments.scoresystems;
import org.toop.framework.networking.server.client.NettyClient;
import org.toop.framework.networking.server.tournaments.TournamentMatch;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MatchCountScoreSystem implements IntegerScoreSystem {
private final Map<NettyClient, Integer> scores = new ConcurrentHashMap<>();
private final int INIT_SCORE = 0;
private final int WIN_POINTS = 1;
public MatchCountScoreSystem() {} // TODO let user decide store type
@Override
public String scoreName() {
return "matches";
}
@Override
public void addPlayer(NettyClient user) {
scores.putIfAbsent(user, INIT_SCORE);
}
@Override
public void result(TournamentMatch match, Integer result) {
scores.merge(match.getClient0(), WIN_POINTS, Integer::sum);
scores.merge(match.getClient1(), WIN_POINTS, Integer::sum);
}
@Override
public Map<NettyClient, Integer> getScore() {
return scores;
}
}

View File

@@ -3,7 +3,6 @@ package org.toop.framework.networking.server.tournaments.scoresystems;
import java.util.Map; import java.util.Map;
public interface ScoreSystem<MATCHTYPE, SCORETYPE, USERTYPE> { public interface ScoreSystem<MATCHTYPE, SCORETYPE, USERTYPE> {
String scoreName();
void addPlayer(USERTYPE user); void addPlayer(USERTYPE user);
void result(MATCHTYPE match, SCORETYPE result); void result(MATCHTYPE match, SCORETYPE result);
Map<USERTYPE, SCORETYPE> getScore(); Map<USERTYPE, SCORETYPE> getScore();

View File

@@ -1,298 +1,193 @@
package org.toop.game.players.ai; package org.toop.game.players.ai;
import org.toop.framework.gameFramework.GameState;
import org.toop.framework.gameFramework.model.game.PlayResult;
import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.framework.gameFramework.model.player.AbstractAI; import org.toop.framework.gameFramework.model.player.AbstractAI;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class MCTSAI extends AbstractAI {
protected static class Node {
public static final int VIRTUAL_LOSS = -1;
public class MCTSAI extends AbstractAI {
private static class Node {
public TurnBasedGame state; public TurnBasedGame state;
public long move; public long move;
public long unexpandedMoves;
public Node parent; public Node parent;
public int expanded;
public Node[] children; public Node[] children;
public AtomicInteger value; public int visits;
public AtomicInteger visits; public float value;
public float heuristic;
public float solved;
public Node(TurnBasedGame state, Node parent, long move) {
final long legalMoves = state.getLegalMoves();
public Node(TurnBasedGame state, long move, Node parent) {
this.state = state; this.state = state;
this.move = move; this.move = move;
this.unexpandedMoves = legalMoves;
this.parent = parent; this.parent = parent;
this.children = new Node[Long.bitCount(legalMoves)];
this.value = new AtomicInteger(0); this.expanded = 0;
this.visits = new AtomicInteger(0); this.children = new Node[Long.bitCount(state.getLegalMoves())];
this.heuristic = state.rateMove(move); this.visits = 0;
this.value = 0.0f;
this.solved = Float.NaN;
} }
public Node(TurnBasedGame state) { public Node(TurnBasedGame state) {
this(state, null, 0L); this(state, 0L, null);
}
public int getExpanded() {
return children.length - Long.bitCount(unexpandedMoves);
} }
public boolean isFullyExpanded() { public boolean isFullyExpanded() {
return unexpandedMoves == 0L; return expanded >= children.length;
} }
public float calculateUCT(float explorationFactor) { float calculateUCT() {
if (visits.get() == 0) { float exploitation = visits <= 0? 0 : value / visits;
return Float.POSITIVE_INFINITY; float exploration = 1.41f * (float)(Math.sqrt(Math.log(visits) / visits));
}
final float exploitation = (float) value.get() / visits.get(); return exploitation + exploration;
final float exploration = (float)(Math.sqrt(explorationFactor / visits.get()));
final float bias = heuristic * 10.0f / (visits.get() + 1);
return exploitation + exploration + bias;
} }
public Node bestUCTChild() { public Node bestUCTChild() {
final int expanded = getExpanded(); int bestChildIndex = -1;
float bestScore = Float.NEGATIVE_INFINITY;
Node highestUCTChild = null;
float highestUCT = Float.NEGATIVE_INFINITY;
for (int i = 0; i < expanded; i++) { for (int i = 0; i < expanded; i++) {
final float childUCT = children[i].calculateUCT(2.0f * (float)Math.log(visits.get())); final float score = calculateUCT();
if (childUCT > highestUCT) { if (score > bestScore) {
highestUCTChild = children[i]; bestChildIndex = i;
highestUCT = childUCT; bestScore = score;
} }
} }
return highestUCTChild; return bestChildIndex >= 0? children[bestChildIndex] : this;
} }
} }
protected static final ThreadLocal<Random> random = ThreadLocal.withInitial(Random::new); private final int milliseconds;
protected final int milliseconds;
protected int lastIterations;
public MCTSAI(int milliseconds) { public MCTSAI(int milliseconds) {
this.milliseconds = milliseconds; this.milliseconds = milliseconds;
this.lastIterations = 0;
} }
public MCTSAI(MCTSAI other) { public MCTSAI(MCTSAI other) {
this.milliseconds = other.milliseconds; this.milliseconds = other.milliseconds;
this.lastIterations = other.lastIterations;
} }
public int getLastIterations() { @Override
return lastIterations; public MCTSAI deepCopy() {
return new MCTSAI(this);
} }
protected Node selection(Node root) { @Override
while (Float.isNaN(root.solved) && root.isFullyExpanded() && !root.state.isTerminal()) { public long getMove(TurnBasedGame game) {
root.value.addAndGet(Node.VIRTUAL_LOSS); Node root = new Node(game.deepCopy());
root.visits.incrementAndGet();
root = root.bestUCTChild(); long endTime = System.currentTimeMillis() + milliseconds;
while (System.currentTimeMillis() <= endTime) {
Node node = selection(root);
long legalMoves = node.state.getLegalMoves();
if (legalMoves != 0) {
node = expansion(node, legalMoves);
} }
root.value.addAndGet(Node.VIRTUAL_LOSS); float result = 0.0f;
root.visits.incrementAndGet();
return root; if (node.state.getLegalMoves() != 0) {
result = simulation(node.state, game.getCurrentTurn());
} }
protected Node expansion(Node leaf) { backPropagation(node, result);
synchronized (leaf) {
if (leaf.unexpandedMoves == 0L) {
return leaf;
} }
final long unexpandedMove = leaf.unexpandedMoves & -leaf.unexpandedMoves; int mostVisitedIndex = -1;
int mostVisits = -1;
final TurnBasedGame copiedState = leaf.state.deepCopy(); for (int i = 0; i < root.expanded; i++) {
copiedState.play(unexpandedMove); if (root.children[i].visits > mostVisits) {
mostVisitedIndex = i;
final Node expandedChild = new Node(copiedState, leaf, unexpandedMove); mostVisits = root.children[i].visits;
leaf.children[leaf.getExpanded()] = expandedChild;
leaf.unexpandedMoves &= ~unexpandedMove;
return expandedChild;
} }
} }
protected int simulation(Node leaf) { return mostVisitedIndex != -1? root.children[mostVisitedIndex].move : randomSetBit(game.getLegalMoves());
final TurnBasedGame copiedState = leaf.state.deepCopy();
final int playerIndex = 1 - copiedState.getCurrentTurn();
while (!copiedState.isTerminal()) {
final long legalMoves = copiedState.getLegalMoves();
final long randomMove = randomSetBit(legalMoves);
copiedState.play(randomMove);
} }
if (copiedState.getWinner() == playerIndex) { private Node selection(Node node) {
return 1; while (node.state.getLegalMoves() != 0L && node.isFullyExpanded()) {
node = node.bestUCTChild();
} }
if (copiedState.getWinner() >= 0) { return node;
return -1;
} }
return 0; private Node expansion(Node node, long legalMoves) {
for (int i = 0; i < node.expanded; i++) {
legalMoves &= ~node.children[i].move;
} }
protected void backPropagation(Node leaf, int value) { if (legalMoves == 0L) {
while (leaf != null) { return node;
value -= Node.VIRTUAL_LOSS;
leaf.value.addAndGet(value);
if (Float.isNaN(leaf.solved)) {
updateSolvedStatus(leaf);
} }
value = -value; long move = randomSetBit(legalMoves);
leaf = leaf.parent;
TurnBasedGame copy = node.state.deepCopy();
copy.play(move);
Node newlyExpanded = new Node(copy, move, node);
node.children[node.expanded] = newlyExpanded;
node.expanded++;
return newlyExpanded;
}
private float simulation(TurnBasedGame state, int playerIndex) {
TurnBasedGame copy = state.deepCopy();
long legalMoves = copy.getLegalMoves();
PlayResult result = null;
while (legalMoves != 0) {
result = copy.play(randomSetBit(legalMoves));
legalMoves = copy.getLegalMoves();
}
if (result.state() == GameState.WIN) {
if (result.player() == playerIndex) {
return 1.0f;
}
return -1.0f;
}
return -0.2f;
}
private void backPropagation(Node node, float value) {
while (node != null) {
node.visits++;
node.value += value;
node = node.parent;
} }
} }
protected Node mostVisitedChild(Node root) { public static long randomSetBit(long value) {
final int expanded = root.getExpanded(); Random random = new Random();
Node mostVisitedChild = null; int count = Long.bitCount(value);
int mostVisited = -1; int target = random.nextInt(count);
for (int i = 0; i < expanded; i++) { while (true) {
if (root.children[i].visits.get() > mostVisited) { int bit = Long.numberOfTrailingZeros(value);
mostVisitedChild = root.children[i]; if (target == 0) {
mostVisited = root.children[i].visits.get(); return 1L << bit;
} }
}
return mostVisitedChild;
}
protected Node findOrResetRoot(Node root, TurnBasedGame game) {
if (root == null) {
return new Node(game.deepCopy());
}
if (areStatesEqual(root.state.getBoard(), game.getBoard())) {
return root;
}
final int expanded = root.getExpanded();
for (int i = 0; i < expanded; i++) {
if (areStatesEqual(root.children[i].state.getBoard(), game.getBoard())) {
root.children[i].parent = null;
return root.children[i];
}
}
return new Node(game.deepCopy());
}
protected Node findChildByMove(Node root, long move) {
final int expanded = root.getExpanded();
for (int i = 0; i < expanded; i++) {
if (root.children[i].move == move) {
root.children[i].parent = null;
return root.children[i];
}
}
return null;
}
protected boolean areStatesEqual(long[] state1, long[] state2) {
if (state1.length != state2.length) {
return false;
}
for (int i = 0; i < state1.length; i++) {
if (state1[i] != state2[i]) {
return false;
}
}
return true;
}
protected long randomSetBit(long value) {
if (0L == value) {
return 0;
}
final int bitCount = Long.bitCount(value);
final int randomBitCount = random.get().nextInt(bitCount);
for (int i = 0; i < randomBitCount; i++) {
value &= value - 1; value &= value - 1;
} target--;
return value & -value;
}
private void updateSolvedStatus(Node node) {
if (node.state.isTerminal()) {
final int winner = node.state.getWinner();
final int mover = 1 - node.state.getCurrentTurn();
node.solved = winner == mover? 1.0f : winner == -1? 0.0f : -1.0f;
return;
}
if (node.isFullyExpanded()) {
boolean allChildrenSolved = true;
boolean foundWinningMove = false;
boolean foundDrawMove = false;
for (final Node child : node.children) {
if (!Float.isNaN(child.solved)) {
if (child.solved == -1.0f) {
foundWinningMove = true;
break;
}
if (child.solved == 0.0f) {
foundDrawMove = true;
}
} else {
allChildrenSolved = false;
}
}
if (foundWinningMove) {
node.solved = 1.0f;
} else if (allChildrenSolved) {
node.solved = foundDrawMove? 0.0f : -1.0f;
}
} }
} }
} }

View File

@@ -1,38 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
public class MCTSAI1 extends MCTSAI {
public MCTSAI1(int milliseconds) {
super(milliseconds);
}
public MCTSAI1(MCTSAI1 other) {
super(other);
}
@Override
public MCTSAI1 deepCopy() {
return new MCTSAI1(this);
}
@Override
public long getMove(TurnBasedGame game) {
final Node root = new Node(game, null, 0L);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
return mostVisitedChild.move;
}
}

View File

@@ -1,48 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
public class MCTSAI2 extends MCTSAI {
private Node root;
public MCTSAI2(int milliseconds) {
super(milliseconds);
this.root = null;
}
public MCTSAI2(MCTSAI2 other) {
super(other);
this.root = other.root;
}
@Override
public MCTSAI2 deepCopy() {
return new MCTSAI2(this);
}
@Override
public long getMove(TurnBasedGame game) {
root = findOrResetRoot(root, game);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
final long move = mostVisitedChild.move;
root = findChildByMove(root, move);
return move;
}
}

View File

@@ -1,76 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MCTSAI3 extends MCTSAI {
private final int threads;
private final ExecutorService threadPool;
public MCTSAI3(int milliseconds, int threads) {
super(milliseconds);
this.threads = threads;
this.threadPool = Executors.newFixedThreadPool(threads);
}
public MCTSAI3(MCTSAI3 other) {
super(other);
this.threads = other.threads;
this.threadPool = other.threadPool;
}
@Override
public MCTSAI3 deepCopy() {
return new MCTSAI3(this);
}
@Override
public long getMove(TurnBasedGame game) {
final Node root = new Node(game.deepCopy(), null, 0L);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
final CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
threadPool.submit(() -> {
try {
iterate(root, endTime);
} finally {
latch.countDown();
}
});
}
try {
final long remaining = endTime - System.nanoTime();
latch.await(remaining, TimeUnit.NANOSECONDS);
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
return mostVisitedChild.move;
} catch (Exception _) {
lastIterations = 0;
final long legalMoves = game.getLegalMoves();
return randomSetBit(legalMoves);
}
}
private void iterate(Node root, long endTime) {
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
}
}

View File

@@ -1,82 +0,0 @@
package org.toop.game.players.ai.mcts;
import org.toop.framework.gameFramework.model.game.TurnBasedGame;
import org.toop.game.players.ai.MCTSAI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MCTSAI4 extends MCTSAI {
private final int threads;
private final ExecutorService threadPool;
private Node root;
public MCTSAI4(int milliseconds, int threads) {
super(milliseconds);
this.threads = threads;
this.threadPool = Executors.newFixedThreadPool(threads);
}
public MCTSAI4(MCTSAI4 other) {
super(other);
this.threads = other.threads;
this.threadPool = other.threadPool;
}
@Override
public MCTSAI4 deepCopy() {
return new MCTSAI4(this);
}
@Override
public long getMove(TurnBasedGame game) {
root = findOrResetRoot(root, game);
final long endTime = System.nanoTime() + milliseconds * 1_000_000L;
final CountDownLatch latch = new CountDownLatch(threads);
for (int i = 0; i < threads; i++) {
threadPool.submit(() -> {
try {
iterate(root, endTime);
} finally {
latch.countDown();
}
});
}
try {
final long remaining = endTime - System.nanoTime();
latch.await(remaining, TimeUnit.NANOSECONDS);
lastIterations = root.visits.get();
final Node mostVisitedChild = mostVisitedChild(root);
final long move = mostVisitedChild.move;
root = findChildByMove(root, move);
return move;
} catch (Exception _) {
lastIterations = 0;
final long legalMoves = game.getLegalMoves();
return randomSetBit(legalMoves);
}
}
private void iterate(Node root, long endTime) {
while (Float.isNaN(root.solved) && System.nanoTime() < endTime) {
Node leaf = selection(root);
leaf = expansion(leaf);
final int value = simulation(leaf);
backPropagation(leaf, value);
}
}
}

View File

@@ -1,77 +0,0 @@
package research;
public class AIData {
public String AI;
public long gamesPlayed;
public double winrate;
public double averageIterations;
public double averageIterations10;
public double averageIterations20;
public double averageIterations30;
public AIData(String AI, long gamesPlayed, double winrate, double averageIterations, double averageIterations10, double averageIterations20, double averageIterations30) {
this.AI = AI;
this.gamesPlayed = gamesPlayed;
this.winrate = winrate;
this.averageIterations = averageIterations;
this.averageIterations10 = averageIterations10;
this.averageIterations20 = averageIterations20;
this.averageIterations30 = averageIterations30;
}
public String getAI() {
return AI;
}
public void setAI(String AI) {
this.AI = AI;
}
public long getGamesPlayed() {
return gamesPlayed;
}
public void setGamesPlayed(long gamesPlayed) {
this.gamesPlayed = gamesPlayed;
}
public double getWinrate() {
return winrate;
}
public void setWinrate(double winrate) {
this.winrate = winrate;
}
public double getAverageIterations() {
return averageIterations;
}
public void setAverageIterations(double averageIterations) {
this.averageIterations = averageIterations;
}
public double getAverageIterations10() {
return averageIterations10;
}
public void setAverageIterations10(double averageIterations10) {
this.averageIterations10 = averageIterations10;
}
public double getAverageIterations20() {
return averageIterations20;
}
public void setAverageIterations20(double averageIterations20) {
this.averageIterations20 = averageIterations20;
}
public double getAverageIterations30() {
return averageIterations30;
}
public void setAverageIterations30(double averageIterations30) {
this.averageIterations30 = averageIterations30;
}
}

View File

@@ -1,612 +0,0 @@
package research;
import org.apache.maven.surefire.shared.io.FileDeleteStrategy;
import org.junit.jupiter.api.*;
import org.toop.framework.game.games.reversi.BitboardReversi;
import org.toop.framework.game.players.ArtificialPlayer;
import org.toop.game.players.ai.MCTSAI;
import org.toop.game.players.ai.mcts.MCTSAI1;
import org.toop.game.players.ai.mcts.MCTSAI2;
import org.toop.game.players.ai.mcts.MCTSAI3;
import org.toop.game.players.ai.mcts.MCTSAI4;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class AITest {
private static String fileName = "gameData.csv";
private static List<Matchup> matchupList = new ArrayList<Matchup>();
private static List<AIData> dataList = new ArrayList<AIData>();
private static List<GameData> gameDataList = new ArrayList<GameData>();
@BeforeAll
public static void init() {
var versions = new ArtificialPlayer[4];
versions[0] = new ArtificialPlayer(new MCTSAI1(10), "MCTS V1");
versions[1] = new ArtificialPlayer(new MCTSAI2(10), "MCTS V2");
versions[2] = new ArtificialPlayer(new MCTSAI3(10, 8), "MCTS V3");
versions[3] = new ArtificialPlayer(new MCTSAI4(10, 8), "MCTS V4");
for (int i = 0; i < versions.length; i++) {
for (int j = i + 1; j < versions.length; j++) {
final int playerIndex1 = i % versions.length;
final int playerIndex2 = j % versions.length;
addMatch(versions[playerIndex1], versions[playerIndex2]);
addMatch(versions[playerIndex2], versions[playerIndex1]); // home vs away system
}
}
}
// @BeforeAll
// public static void init() {
//
// var versions = new ArtificialPlayer[11];
// versions[0] = new ArtificialPlayer(new MCTSAI3(10, 1), "MCTS V3T1");
// versions[1] = new ArtificialPlayer(new MCTSAI3(10, 2), "MCTS V3T2");
// versions[2] = new ArtificialPlayer(new MCTSAI3(10, 4), "MCTS V3T4");
// versions[3] = new ArtificialPlayer(new MCTSAI3(10, 8), "MCTS V3T8");
// versions[4] = new ArtificialPlayer(new MCTSAI3(10, 16), "MCTS V3T16");
// versions[5] = new ArtificialPlayer(new MCTSAI3(10, 128), "MCTS V3T32");
// versions[6] = new ArtificialPlayer(new MCTSAI3(10, 256), "MCTS V3T64");
// versions[7] = new ArtificialPlayer(new MCTSAI3(10, 128), "MCTS V3T128");
// versions[8] = new ArtificialPlayer(new MCTSAI3(10, 256), "MCTS V3T256");
// versions[9] = new ArtificialPlayer(new MCTSAI3(10, 512), "MCTS V3T512");
// versions[10] = new ArtificialPlayer(new MCTSAI3(10, 1024), "MCTS V3T1024");
//
// for (int i = 0; i < versions.length; i++) {
// for (int j = i + 1; j < versions.length; j++) {
// final int playerIndex1 = i % versions.length;
// final int playerIndex2 = j % versions.length;
// addMatch(versions[playerIndex1], versions[playerIndex2]);
// addMatch(versions[playerIndex2], versions[playerIndex1]); // home vs away system
// }
// }
// }
public static void addMatch(ArtificialPlayer v1, ArtificialPlayer v2) {
matchupList.add(new Matchup(v1, v2));
}
public void addAIData(AIData data) {
dataList.add(data);
}
public void addGameData(GameData data) {
gameDataList.add(data);
}
@Test
public void testAIvsAI() {
while (true) {
for (Matchup m : matchupList) {
playGame(m);
}
}
}
public void playGame(Matchup m) {
long nanocounterAI1 = 0L;
long nanocounterAI2 = 0L;
List<Integer> iterationsAI1 = new ArrayList<>();
List<Integer> iterationsAI2 = new ArrayList<>();
final BitboardReversi match = new BitboardReversi();
ArtificialPlayer[] players = new ArtificialPlayer[2];
players[0] = m.getPlayer1();
players[1] = m.getPlayer2();
match.init(players);
while (!match.isTerminal()) {
final int currentAI = match.getCurrentTurn();
final long startTime = System.nanoTime();
final long move = players[currentAI].getMove(match);
final long endTime = System.nanoTime();
if (players[currentAI].getAi() instanceof MCTSAI) {
final int lastIterations = ((MCTSAI) players[currentAI].getAi()).getLastIterations();
if (currentAI == 0) {
iterationsAI1.add(lastIterations);
nanocounterAI1 += (endTime - startTime);
} else {
iterationsAI2.add(lastIterations);
nanocounterAI2 += (endTime - startTime);
}
}
match.play(move);
}
generateMatchData(m.getPlayer1().getName(), m.getPlayer2().getName(), match, iterationsAI1, iterationsAI2, nanocounterAI1, nanocounterAI2);
}
public void generateMatchData(
String AI1,
String AI2,
BitboardReversi match,
List<Integer> iterationsAI1,
List<Integer> iterationsAI2,
long nanocounterAI1,
long nanocounterAI2
) {
try {
var ai110 = iterationsAI1.subList(0, 10);
var ai120 = iterationsAI1.subList(10, 20);
var ai130 = iterationsAI1.subList(20, iterationsAI1.size());
var ai210 = iterationsAI2.subList(0, 10);
var ai220 = iterationsAI2.subList(10, 20);
var ai230 = iterationsAI2.subList(20, iterationsAI2.size());
writeGamesToCSV(fileName, new GameData(
AI1,
AI2,
getWinnerForMatch(AI1, AI2, match),
match.getAmountOfTurns(),
iterationsAI1.stream().mapToLong(Integer::longValue).sum(),
ai110.stream().mapToLong(Integer::longValue).sum(),
ai120.stream().mapToLong(Integer::longValue).sum(),
ai130.stream().mapToLong(Integer::longValue).sum(),
iterationsAI1.stream().mapToDouble(Integer::doubleValue).sum() / iterationsAI1.size(),
ai110.stream().mapToDouble(Integer::doubleValue).sum() / ai110.size(),
ai120.stream().mapToDouble(Integer::doubleValue).sum() / ai120.size(),
ai130.stream().mapToDouble(Integer::doubleValue).sum() / ai130.size(),
iterationsAI2.stream().mapToInt(Integer::intValue).sum(),
ai210.stream().mapToLong(Integer::longValue).sum(),
ai220.stream().mapToLong(Integer::longValue).sum(),
ai230.stream().mapToLong(Integer::longValue).sum(),
iterationsAI2.stream().mapToDouble(Integer::doubleValue).sum() / iterationsAI2.size(),
ai210.stream().mapToDouble(Integer::doubleValue).sum() / ai210.size(),
ai220.stream().mapToDouble(Integer::doubleValue).sum() / ai220.size(),
ai230.stream().mapToDouble(Integer::doubleValue).sum() / ai230.size(),
nanocounterAI1,
nanocounterAI2,
LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
));
} catch (IOException e) {
throw new RuntimeException(e);
} catch (IndexOutOfBoundsException e) {
return;
}
}
public String getWinnerForMatch(String AI1, String AI2, BitboardReversi match) {
if (match.getWinner() == 0) {
return AI1;
}
if (match.getWinner() == 1) {
return AI2;
} else {
return "TIE";
}
}
public void generateData(Matchup matchup, BitboardReversi match, List<Integer> iterationsAI1, List<Integer> iterationsAI2) {
boolean matchup1Found = false;
boolean matchup2Found = false;
for (AIData aiData : dataList) {
if (aiData.getAI().equals(matchup.getPlayer1().getName())) {
matchup1Found = true;
} if (aiData.getAI().equals(matchup.getPlayer2().getName())) {
matchup2Found = true;
}
}
if (!(matchup1Found)) {
addAIData(new AIData(matchup.getPlayer1().getName(), 0, 0, 0, 0, 0, 0));
}
if (!(matchup2Found)) {
addAIData(new AIData(matchup.getPlayer2().getName(), 0, 0, 0, 0, 0, 0));
}
for (AIData aiData : dataList) { // set data for player 1
if (aiData.getAI().equals(matchup.getPlayer1().getName())) {
aiData.setGamesPlayed(aiData.getGamesPlayed() + 1);
aiData.setWinrate(calculateWinrate(0, aiData.getWinrate(), aiData.getGamesPlayed(), match.getWinner()));
aiData.setAverageIterations(calculateAverageIterations(aiData.getAverageIterations(), iterationsAI1));
aiData.setAverageIterations10(calculateAverageIterationsStartEnd(0, 10, aiData.getAverageIterations10(), iterationsAI1));
aiData.setAverageIterations20(calculateAverageIterationsStartEnd(10, 20, aiData.getAverageIterations20(), iterationsAI1));
aiData.setAverageIterations30(calculateAverageIterationsStartEnd(20, iterationsAI1.size(), aiData.getAverageIterations30(), iterationsAI1));
}
}
for (AIData aiData : dataList) {
if (aiData.getAI().equals(matchup.getPlayer2().getName())) {
aiData.setGamesPlayed(aiData.getGamesPlayed() + 1);
aiData.setWinrate(calculateWinrate(1, aiData.getWinrate(), aiData.getGamesPlayed(), match.getWinner()));
aiData.setAverageIterations(calculateAverageIterations(aiData.getAverageIterations(), iterationsAI2));
aiData.setAverageIterations10(calculateAverageIterationsStartEnd(0, 10, aiData.getAverageIterations10(), iterationsAI2));
aiData.setAverageIterations20(calculateAverageIterationsStartEnd(10, 20, aiData.getAverageIterations20(), iterationsAI2));
aiData.setAverageIterations30(calculateAverageIterationsStartEnd(20, iterationsAI2.size(), aiData.getAverageIterations30(), iterationsAI2));
}
}
}
public double calculateWinrate(int player, double winrate, long gamesPlayed, int winner) {
double result;
if (winner == 0 && player == 0 || winner == 1 && player == 1) {
return (winrate * (gamesPlayed - 1) + 1) / gamesPlayed;
} else if (winner == 0 && player == 1 || winner == 1 && player == 0) {
return (winrate * (gamesPlayed - 1) + 0) / gamesPlayed;
}
return (winrate * (gamesPlayed - 1) + 0) / gamesPlayed;
}
public double calculateAverageIterations(double averageIterations, List<Integer> thisGameIterations) {
double thisGameIterationsAverage = 0;
for (int iterations = 0; iterations < thisGameIterations.size(); iterations += 1) {
thisGameIterationsAverage += thisGameIterations.get(iterations);
}
thisGameIterationsAverage /= thisGameIterations.size();
return (averageIterations + thisGameIterationsAverage) / 2;
}
public double calculateAverageIterationsStartEnd(int start, int end, double averageIterations, List<Integer> thisGameIterations) {
double thisGameIterationsAverage = 0;
for (int iterations = start; iterations < end; iterations += 1) {
thisGameIterationsAverage += thisGameIterations.get(iterations);
}
thisGameIterationsAverage /= (end - start);
return (averageIterations + thisGameIterationsAverage) / 2;
}
@AfterAll
public static void writeAfterTests() {
try {
writeAIToCsv("Data.csv", dataList);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void writeGamesToCSV(String filepath, GameData gameData) throws IOException {
try (
final BufferedWriter writer = Files.newBufferedWriter(
Paths.get(filepath),
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
);
final BufferedReader reader = new BufferedReader(new FileReader(filepath))
) {
if (reader.readLine() == null || reader.readLine().isBlank()) {
writer.write("Black,White,Winner,Turns Played,Black total iterations,Black total iterations 0-10,Black total iterations 11-20,Black total iterations 21-30,Black average iterations,Black average iterations 0-10,Black average iterations 11-20,Black average iterations 21-30,White total iterations,White total iterations 0-10,White total iterations 11-20,White total iterations 21-30,White average iterations,White average iterations 0-10,White average iterations 11-20,White average iterations 21-30,Total Time AI1,Total Time AI2,Time");
writer.newLine();
}
writer.write(
gameData.AI1() + "," +
gameData.AI2() + "," +
gameData.winner() + "," +
gameData.turns() + "," +
gameData.AI1totalIterations() + "," +
gameData.AI1totalIterations10() + "," +
gameData.AI1totalIterations20() + "," +
gameData.AI1totalIterations30() + "," +
BigDecimal.valueOf(gameData.AI1averageIterations()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI1averageIterations10()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI1averageIterations20()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI1averageIterations30()).setScale(2, RoundingMode.HALF_EVEN) + "," +
gameData.AI2totalIterations() + "," +
gameData.AI2totalIterations10() + "," +
gameData.AI2totalIterations20() + "," +
gameData.AI2totalIterations30() + "," +
BigDecimal.valueOf(gameData.AI2averageIterations()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI2averageIterations10()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI2averageIterations20()).setScale(2, RoundingMode.HALF_EVEN) + "," +
BigDecimal.valueOf(gameData.AI2averageIterations30()).setScale(2, RoundingMode.HALF_EVEN) + "," +
(gameData.nanoAI1() / 1_000_000L) + "," +
(gameData.nanoAI2() / 1_000_000L) + "," +
gameData.time());
writer.newLine();
}
}
public static void writeAIToCsv(String filepath, List<AIData> dataList) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
writer.write("AI Name,Games Played,Winrate,Average Iterations,Average Iterations 0-10, Average Iterations 11-20, Average Iterations 20-30");
writer.newLine();
for (AIData data : dataList) {
writer.write(
data.getAI() + "," +
data.getGamesPlayed() + "," +
data.getWinrate() + "," +
Math.round(data.getAverageIterations()) + "," +
Math.round(data.getAverageIterations10()) + "," +
Math.round(data.getAverageIterations20()) + "," +
Math.round(data.getAverageIterations30()));
writer.newLine();
}
}
}
}
//public class AITest {
// private static int games = 2;
//
// @BeforeAll
// public static void setUp() {
// var versions = new ArtificialPlayer[5];
// versions[0] = new ArtificialPlayer(new RandomAI(), "Random AI");
// versions[1] = new ArtificialPlayer(new MCTSAI1(20), "MCTS V1 AI");
// versions[2] = new ArtificialPlayer(new org.toop.game.players.ai.mcts.MCTSAI2(20), "MCTS V2 AI");
// versions[3] = new ArtificialPlayer(new org.toop.game.players.ai.mcts.MCTSAI3(20, 10), "MCTS V3 AI");
// versions[4] = new ArtificialPlayer(new MCTSAI4(20, 10), "MCTS V4 AI");
//
// for (int i = 0; i < versions.length; i++) {
// for (int j = i + 1; j < versions.length; j++) {
// final int playerIndex1 = i % versions.length;
// final int playerIndex2 = j % versions.length;
// addMatchup(versions[playerIndex1], versions[playerIndex2]);
// }
// }
//
// }
//
// @BeforeEach
// public void setUpEach() {
// matchupList = new ArrayList<>();
// }
//
// @Test
// public void testIterationsInRealGame() {
// for (int i = 0; i < matchups.size(); i++) {
// testAIVSAI(games, getMatchup(i));
// }
// }
//
//
// private void testAIVSAI(int games, ArtificialPlayer[] ais) {
//
// List<List<Integer>> gamesList = new ArrayList<>();
// for (int i = 0; i < games; i++) {
// final BitboardReversi match = new BitboardReversi();
// match.init(ais);
//
// List<Integer> iterations1 = new ArrayList<>();
// List<Integer> iterations2 = new ArrayList<>();
//
// while (!match.isTerminal()) {
// final int currentAI = match.getCurrentTurn();
// final long move = ais[currentAI].getMove(match);
// if (ais[currentAI].getAi() instanceof MCTSAI) {
// final int lastIterations = ((MCTSAI) ais[currentAI].getAi()).getLastIterations();
// if (currentAI == 0) {
// iterations1.add(lastIterations);
// } else if (currentAI == 1) {
// iterations2.add(lastIterations);
// }
// }
// match.play(move);
// }
// int winner = match.getWinner();
// iterations1.addFirst(winner);
//// iterations1.add(-999);
// iterations1.addAll(iterations2);
//
// gamesList.add(iterations1);
// }
// matchupList.add(gamesList);
// }
//
// @Test
// public void testIterationsAtFixedMove() {
// for (ArtificialPlayer[] matchup : matchups) {
// List<List<Integer>> gamesList = new ArrayList<>();
// for (int j = 0; j < games; j++) {
// final BitboardReversi match = new BitboardReversi();
// match.init(matchup);
//
// List<Integer> iterations = new ArrayList<>();
//
// for (Long move : fixedMoveSet) {
// match.play(move);
// if (move == 32L) {
// break;
// }
// }
//// iterations.add(-999);
// var player = matchup[match.getCurrentTurn()];
// for (int k = 0; k < 10; k++) {
// player.getMove(match);
// if (player.getAi() instanceof MCTSAI) {
// iterations.add(((MCTSAI) player.getAi()).getLastIterations());
// }
// }
// gamesList.add(iterations);
// }
// matchupList.add(gamesList);
// }
// }
//
//
// @Test
// public void testIterationsInFixedGame() {
// for (ArtificialPlayer[] matchup : matchups) {
// List<List<Integer>> gamesList = new ArrayList<>();
// for (int j = 0; j < games; j++) {
// final BitboardReversi match = new BitboardReversi();
// match.init(matchup);
//
// List<Integer> iterations = new ArrayList<>();
//
// iterations.add(-999);
//
// for (Long move : fixedMoveSet) {
// var player = matchup[match.getCurrentTurn()];
// player.getMove(match);
// if (player.getAi() instanceof MCTSAI) {
// iterations.add(((MCTSAI) player.getAi()).getLastIterations());
// }
// match.play(move);
// }
//
// gamesList.add(iterations);
// }
// matchupList.add(gamesList);
// }
// }
//
// @AfterEach
// public void tearDown() {
// data.add(matchupList);
// }
//
// @AfterAll
// public static void writeAfterTests() {
// try {
// writeToCsv("Data.csv", data);
// } catch (IOException e) {
//
// }
// }
//
//
// public static void writeToCsv(String filepath, List<List<List<List<Integer>>>> data) throws IOException {
// try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
//
// writer.write("TestID,Matchup,GameNr,Winner");
// for (int i = 0; i < data.size(); i++) {
// writer.write(",Iterations");
// }
//
// writer.newLine();
//
// for (int TestID = 0; TestID < data.size(); TestID++) {
// List<List<List<Integer>>> testCase = data.get(TestID);
//
// for (int matchupNr = 0; matchupNr < testCase.size(); matchupNr++) {
// List<List<Integer>> matchup = testCase.get(matchupNr);
//
// for (int gameNr = 0; gameNr < matchup.size(); gameNr++) {
// List<Integer> game = matchup.get(gameNr);
// writer.write((TestID + 1) + "," + (getMatchupName(matchupNr)) + "," + (gameNr + 1));
// for (int i = 0; i < game.size(); i++) {
// if (i == 0) {
// writer.write("," + getWinnerFromMatchup(game.get(i), matchupNr));
// } else {
// writer.write("," + game.get(i));
// }
// }
// writer.newLine();
// }
// }
// }
// }
//
// }
//
//
// private static final List<List<List<List<Integer>>>> data = new ArrayList<>();
// private List<List<List<Integer>>> matchupList = new ArrayList<>();
// private static final List<String> matchupNames = new ArrayList<>();
// private static final List<ArtificialPlayer[]> matchups = new ArrayList<>();
//
// private static String getMatchupName(int matchupNr) {
// return matchupNames.get(matchupNr);
// }
//
// private static ArtificialPlayer[] getMatchup(int matchupNr) {
// return matchups.get(matchupNr);
// }
//
// private static String getWinnerFromMatchup(Integer winner, int matchupNr) {
// String matchup = matchupNames.get(matchupNr);
//
// String[] parts = matchup.split(" vs ");
//
// if (parts.length != 2) {
// return "Invalid matchup formatting.";
// }
//
// return winner == 0 ? parts[0] : winner == 1 ? parts[1] : winner == -999 ? "NVT" : "Tie";
// }
//
// private static void addMatchup(ArtificialPlayer player1, ArtificialPlayer player2) {
// matchups.add(new ArtificialPlayer[]{player1, player2});
// matchupNames.add(player1.getName() + " vs " + player2.getName());
// }
//}
// private final Long[] fixedMoveSet = new Long[]{17592186044416L,
// 35184372088832L,
// 67108864L,
// 8796093022208L,
// 2251799813685248L,
// 288230376151711744L,
// 70368744177664L,
// 1125899906842624L,
// 137438953472L,
// 140737488355328L,
// 4503599627370496L,
// 2305843009213693952L,
// 18014398509481984L,
// 274877906944L,
// 576460752303423488L,
// -9223372036854775808L,
// 549755813888L,
// 1152921504606846976L,
// 144115188075855872L,
// 72057594037927936L,
// 36028797018963968L,
// 17179869184L,
// 2199023255552L,
// 1048576L,
// 4398046511104L,
// 281474976710656L,
// 9007199254740992L,
// 2147483648L,
// 1073741824L,
// 33554432L,
// 262144L,
// 8388608L,
// 8192L,
// 4611686018427387904L,
// 4294967296L,
// 524288L,
// 4096L,
// 16777216L,
// 65536L,
// 32L,
// 2048L,
// 8L,
// 4L,
// 8589934592L,
// 16L,
// 2097152L,
// 4194304L,
// 1024L,
// 512L,
// 16384L,
// 536870912L,
// 1099511627776L,
// 64L,
// 562949953421312L,
// 128L,
// 1L,
// 32768L,
// 2L,
// 256L,
// 131072L};
// }

View File

@@ -1,32 +0,0 @@
package research;
public record GameData(
String AI1,
String AI2,
String winner,
int turns,
long AI1totalIterations,
long AI1totalIterations10,
long AI1totalIterations20,
long AI1totalIterations30,
double AI1averageIterations,
double AI1averageIterations10,
double AI1averageIterations20,
double AI1averageIterations30,
long AI2totalIterations,
long AI2totalIterations10,
long AI2totalIterations20,
long AI2totalIterations30,
double AI2averageIterations,
double AI2averageIterations10,
double AI2averageIterations20,
double AI2averageIterations30,
long nanoAI1,
long nanoAI2,
String time
) {}

View File

@@ -1,30 +0,0 @@
package research;
import org.toop.framework.game.players.ArtificialPlayer;
import java.util.ArrayList;
import java.util.List;
public class Matchup {
public ArtificialPlayer player1;
public ArtificialPlayer player2;
public Matchup(ArtificialPlayer player1, ArtificialPlayer player2) {
this.player1 = player1;
this.player2 = player2;
}
public Matchup() {}
public String toString() {
return player1.toString() + " VS " + player2.toString();
}
public ArtificialPlayer getPlayer1() {
return player1;
}
public ArtificialPlayer getPlayer2() {
return player2;
}
}