hanze/game-client

tournament idk (85940005630c4ab95769fb3b688e23e28cb88f2e)
Repositories

commit 85940005630c4ab95769fb3b688e23e28cb88f2e
parent 344c921054e2cf4bf965e3766a766e895f6082fc
Author: Friedel Schon <[email protected]>
Date:   Fri,  3 Feb 2023 00:53:29 +0100

tournament idk

Diffstat:
Adata/temp/NBLDDMO.csv2++
Msrc/main/java/nl/isygameclient/Headless.java283+++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/main/java/nl/isygameclient/Tournament.java109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main/java/nl/isygameclient/models/Ai.java232+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/main/java/nl/isygameclient/models/PlayerManager.java97+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/main/java/nl/isygameclient/models/board/Board.java97+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/main/java/nl/isygameclient/models/board/HistoryBoard.java169+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/main/java/nl/isygameclient/models/games/othello/Othello.java329+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/main/java/nl/isygameclient/network/GameClient.java5-----
9 files changed, 700 insertions(+), 623 deletions(-)

diff --git a/data/temp/NBLDDMO.csv b/data/temp/NBLDDMO.csv @@ -0,0 +1,2 @@ +Name Ai1, Name Ai2, Game Turns, Draw, Winner Name, Winner Playing As, Total Game Time, Average Time Per Move Ai1, Average Time Per Move Ai2, Moves Ai1, Moves Ai2 +Johs, H1Linear,59,true,H1Linear,white,12617,247,222,(x2 y2); (x4 y2); (x2 y0); (x6 y1); (x2 y5); (x0 y3); (x5 y5); (x1 y1); (x1 y4); (x4 y0); (x0 y1); (x7 y0); (x7 y3); (x1 y5); (x3 y5); (x6 y5); (x7 y5); (x6 y7); (x2 y6); (x0 y7); (x0 y6); (x2 y7); (x4 y7); (x7 y6),(x3 y2); (x5 y4); (x3 y1); (x5 y1); (x3 y0); (x1 y3); (x2 y4); (x0 y2); (x0 y4); (x1 y0); (x5 y0); (x6 y0); (x2 y1); (x0 y0); (x7 y1); (x7 y2); (x6 y2); (x6 y3); (x6 y4); (x0 y5); (x4 y5); (x7 y4); (x5 y6); (x3 y6); (x1 y6); (x6 y6); (x1 y7); (x3 y7); (x5 y7); (x7 y7), diff --git a/src/main/java/nl/isygameclient/Headless.java b/src/main/java/nl/isygameclient/Headless.java @@ -2,14 +2,6 @@ package nl.isygameclient; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import nl.isygameclient.models.Ai; -import nl.isygameclient.models.Game; -import nl.isygameclient.models.Player; -import nl.isygameclient.models.PlayerManager; -import nl.isygameclient.models.games.othello.Othello; -import nl.isygameclient.util.DataSaver; -import nl.isygameclient.util.Vector2D; - import java.io.IOException; import java.lang.reflect.Type; import java.nio.file.Files; @@ -20,141 +12,148 @@ import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import nl.isygameclient.models.Ai; +import nl.isygameclient.models.Game; +import nl.isygameclient.models.Player; +import nl.isygameclient.models.PlayerManager; +import nl.isygameclient.models.games.othello.Othello; +import nl.isygameclient.util.DataSaver; +import nl.isygameclient.util.Vector2D; public class Headless { - private static final String JSON_FILENAME = "heuristics.json"; - private static final int THREAD_POOL_SIZE = 10; - private static final int ROUNDS_PER_GAME = 100; - - private static long calculateAverage(List<Long> values) { - long sum = values.stream().mapToLong(Long::longValue).sum(); - return sum / values.size(); - } - - private static Map<String, Game> createGames(Map<String, int[][]> heuristics) { - Map<String, Game> games = new HashMap<>(); - for (Map.Entry<String, int[][]> heuristic1 : heuristics.entrySet()) { - for (Map.Entry<String, int[][]> heuristic2 : heuristics.entrySet()) { - - ArrayList<Player> players = new ArrayList<>(); - players.add(new Ai(heuristic1.getKey(), "black", heuristic1.getValue())); - players.add(new Ai(heuristic2.getKey(), "white", heuristic2.getValue())); - - var playerManager = new PlayerManager(0, players); - Othello othello = new Othello(playerManager); - games.put(heuristic1.getKey() + ", " + heuristic2.getKey(), othello); - } - } - return games; - } - - private static String buildDataString( - String gameName, int gameTurns, boolean isDraw, Player winner, long totalGameTime, - Map<Player, ArrayList<Long>> playersTimePerMoves, - Map<Player, ArrayList<Vector2D<Integer, Integer>>> playersMovesMade) { - StringBuilder builder = new StringBuilder(); - - String joined = String.join(",", gameName, String.valueOf(gameTurns), String.valueOf(isDraw), - winner.getPlayerName(), winner.getPlayingAs(), String.valueOf(totalGameTime)) + ","; - builder.append(joined); - - playersTimePerMoves.forEach((k, v) -> { - var average = calculateAverage(v); - builder.append(average).append(","); - }); - playersMovesMade.forEach((k, v) -> { - String listString = v.stream().map(Object::toString) - .collect(Collectors.joining("; ")); - builder.append(listString).append(","); - }); - return builder.toString(); - } - - private static Map<String, int[][]> loadHeuristics() { - try { - Type mapType = new TypeToken<Map<String, int[][]>>() { - }.getType(); - String inFile = new String(Files.readAllBytes(Paths.get(JSON_FILENAME))); - return new Gson().fromJson(inFile, mapType); - } catch (NoSuchFileException e) { - System.err.println("NO HEURISTICS JSON HAS BEEN PROVIDED."); - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - public static void main(String[] args) { - var heuristics = loadHeuristics(); - if (heuristics == null) { - System.err.println("No heuristics in file."); - return; - } - - var executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE); - var games = createGames(heuristics); - var dataSaver = new DataSaver(); - dataSaver.saveData("Name Ai1, Name Ai2, Game Turns, Draw, Winner Name, Winner Playing As, Total Game Time, Average Time Per Move Ai1, Average Time Per Move Ai2, Moves Ai1, Moves Ai2"); - for (Map.Entry<String, Game> game : games.entrySet()) { - Runnable simulation = new Simulation(game.getKey(), game.getValue(), ROUNDS_PER_GAME, new ReentrantLock(), dataSaver); - executor.execute(simulation); - } - executor.shutdown(); - - while (!executor.isTerminated()) { - } - - System.out.println("All simulations completed successfully."); - } - - private record Simulation(String gameName, Game game, int rounds, Lock lock, DataSaver dataSaver) implements Runnable { - @Override - public void run() { - System.out.println("Starting simulation: " + gameName); - - var playerManager = game.getPlayerManager(); - for (int i = 0; i < rounds; i++) { - System.out.println("Start round" + i + " " + gameName); - - Map<Player, ArrayList<Long>> playersTimePerMoves = new HashMap<>(); - Map<Player, ArrayList<Vector2D<Integer, Integer>>> PlayersMovesMade = new HashMap<>(); - - long totalGameTime = 0; - int gameTurns = 0; - while (!game.isGameOver()) { - gameTurns++; - var currentPlayer = playerManager.getCurrentPlayer(); - if (game.getValidMoves(currentPlayer).isEmpty()) { - playerManager.nextPlayer(); - continue; - } - - final long startTime = System.currentTimeMillis(); - var move = currentPlayer.onPlayerTurn(); - game.move(currentPlayer, move); - final long endTime = System.currentTimeMillis(); - final long deltaTime = endTime - startTime; - totalGameTime += deltaTime; - - PlayersMovesMade.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add(move); - playersTimePerMoves.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add(deltaTime); - } - - var winner = game.getWinners().get(0); - var data = buildDataString(gameName, gameTurns, game.isDraw(), winner, totalGameTime, playersTimePerMoves, PlayersMovesMade); - - lock.lock(); - try { - dataSaver.saveData(data); - } finally { - lock.unlock(); - } - game.restart(); - System.out.println("end round" + i + " " + gameName); - } - System.out.println(gameName + " has finished"); - } - } + private static final String JSON_FILENAME = "heuristics.json"; + private static final int THREAD_POOL_SIZE = 10; + private static final int ROUNDS_PER_GAME = 100; + + private static long calculateAverage(List<Long> values) { + long sum = values.stream().mapToLong(Long::longValue).sum(); + return sum / values.size(); + } + + private static Map<String, Game> createGames(Map<String, int[][]> heuristics) { + Map<String, Game> games = new HashMap<>(); + for (Map.Entry<String, int[][]> heuristic1 : heuristics.entrySet()) { + for (Map.Entry<String, int[][]> heuristic2 : heuristics.entrySet()) { + + ArrayList<Player> players = new ArrayList<>(); + players.add(new Ai(heuristic1.getKey(), "black", heuristic1.getValue())); + players.add(new Ai(heuristic2.getKey(), "white", heuristic2.getValue())); + + var playerManager = new PlayerManager(0, players); + Othello othello = new Othello(playerManager); + games.put(heuristic1.getKey() + ", " + heuristic2.getKey(), othello); + } + } + return games; + } + + private static String buildDataString( + String gameName, int gameTurns, boolean isDraw, Player winner, long totalGameTime, + Map<Player, ArrayList<Long>> playersTimePerMoves, + Map<Player, ArrayList<Vector2D<Integer, Integer>>> playersMovesMade) { + StringBuilder builder = new StringBuilder(); + + String joined = String.join(",", gameName, String.valueOf(gameTurns), String.valueOf(isDraw), + winner.getPlayerName(), winner.getPlayingAs(), String.valueOf(totalGameTime)) + + ","; + builder.append(joined); + + playersTimePerMoves.forEach((k, v) -> { + var average = calculateAverage(v); + builder.append(average).append(","); + }); + playersMovesMade.forEach((k, v) -> { + String listString = v.stream().map(Object::toString).collect(Collectors.joining("; ")); + builder.append(listString).append(","); + }); + return builder.toString(); + } + + private static Map<String, int[][]> loadHeuristics() { + try { + Type mapType = new TypeToken<Map<String, int[][]>>() { + }.getType(); + String inFile = new String(Files.readAllBytes(Paths.get(JSON_FILENAME))); + return new Gson().fromJson(inFile, mapType); + } catch (NoSuchFileException e) { + System.err.println("NO HEURISTICS JSON HAS BEEN PROVIDED."); + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static void main(String[] args) { + var heuristics = loadHeuristics(); + if (heuristics == null) { + System.err.println("No heuristics in file."); + return; + } + + var executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE); + var games = createGames(heuristics); + var dataSaver = new DataSaver(); + dataSaver.saveData("Name Ai1, Name Ai2, Game Turns, Draw, Winner Name, Winner Playing As, Total Game Time, Average Time Per Move Ai1, Average Time Per Move Ai2, Moves Ai1, Moves Ai2"); + for (Map.Entry<String, Game> game : games.entrySet()) { + Runnable simulation = new Simulation(game.getKey(), game.getValue(), ROUNDS_PER_GAME, new ReentrantLock(), dataSaver); + executor.execute(simulation); + } + executor.shutdown(); + + while (!executor.isTerminated()) { + } + + System.out.println("All simulations completed successfully."); + } + + private record Simulation(String gameName, Game game, int rounds, Lock lock, DataSaver dataSaver) implements Runnable { + @Override + public void run() { + System.out.println("Starting simulation: " + gameName); + + var playerManager = game.getPlayerManager(); + for (int i = 0; i < rounds; i++) { + System.out.println("Start round" + i + " " + gameName); + + Map<Player, ArrayList<Long>> playersTimePerMoves = new HashMap<>(); + Map<Player, ArrayList<Vector2D<Integer, Integer>>> PlayersMovesMade = new HashMap<>(); + + long totalGameTime = 0; + int gameTurns = 0; + while (!game.isGameOver()) { + gameTurns++; + var currentPlayer = playerManager.getCurrentPlayer(); + if (game.getValidMoves(currentPlayer).isEmpty()) { + playerManager.nextPlayer(); + continue; + } + + final long startTime = System.currentTimeMillis(); + var move = currentPlayer.onPlayerTurn(); + game.move(currentPlayer, move); + final long endTime = System.currentTimeMillis(); + final long deltaTime = endTime - startTime; + totalGameTime += deltaTime; + + PlayersMovesMade.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add(move); + playersTimePerMoves.computeIfAbsent(currentPlayer, k -> new ArrayList<>()).add(deltaTime); + } + + var winner = game.getWinners().get(0); + var data = buildDataString(gameName, gameTurns, game.isDraw(), winner, totalGameTime, playersTimePerMoves, PlayersMovesMade); + + lock.lock(); + try { + dataSaver.saveData(data); + } finally { + lock.unlock(); + } + game.restart(); + System.out.println("end round" + i + " " + gameName); + } + System.out.println(gameName + " has finished"); + } + } } diff --git a/src/main/java/nl/isygameclient/Tournament.java b/src/main/java/nl/isygameclient/Tournament.java @@ -0,0 +1,109 @@ +package nl.isygameclient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import nl.isygameclient.models.Ai; +import nl.isygameclient.models.Game; +import nl.isygameclient.models.Player; +import nl.isygameclient.models.PlayerManager; +import nl.isygameclient.models.games.othello.Othello; +import nl.isygameclient.network.Event; +import nl.isygameclient.network.EventType; +import nl.isygameclient.network.GameClient; +import nl.isygameclient.util.Vector2D; + +public class Tournament { + public static final int[][] HEURISTIC = { + { 10, 2, 5, 5, 5, 5, 2, 10 }, + { 2, 2, 1, 1, 1, 1, 2, 2 }, + { 5, 1, 1, 1, 1, 1, 1, 5 }, + { 5, 1, 1, 1, 1, 1, 1, 5 }, + { 5, 1, 1, 1, 1, 1, 1, 5 }, + { 5, 1, 1, 1, 1, 1, 1, 5 }, + { 2, 2, 1, 1, 1, 1, 2, 2 }, + { 10, 2, 5, 5, 5, 5, 2, 10 } + }; + + public static void main(String[] args) throws IOException { + GameClient client = new GameClient("localhost", 7789); + + client.login("deeznuts" + new Random().nextInt(0, 9)); + + Player dummyPlayer = new Player("network", "black") { + public Vector2D<Integer, Integer> onPlayerTurn() { return null; } + }; + + Player ai = new Ai("yannick", "white", HEURISTIC); + + PlayerManager playerManager = new PlayerManager(0, List.of(ai, dummyPlayer)); + Othello othello = new Othello(playerManager); + + String firstPlayer = null; + + while (true) { + Event event = client.event(-1); + + switch (event.type) { + case MATCH: + @SuppressWarnings("unchecked") + var data = (Map<String, String>) event.data; + + String opponent = data.get("opponent"); + System.out.println("playing against " + opponent); + + firstPlayer = null; + playerManager.restart(); + othello.getBoard().clear(); + + break; + case YOURTURN: + if (firstPlayer == null) { + firstPlayer = "self"; + othello.initializeBoard(); + } + var turn = ai.onPlayerTurn(); + System.out.println(turn); + client.send("move " + (turn.getY() * 8 + turn.getX())); + // othello.move(ai, turn); + + break; + case MOVE: + if (firstPlayer == null) { + firstPlayer = "other"; + playerManager.nextPlayer(); + othello.initializeBoard(); + } + @SuppressWarnings("unchecked") + var moveMap = (Map<String, String>) event.data; + + int move = Integer.parseInt(moveMap.get("move")); + System.out.printf("%s did move %dx%d\n", moveMap.get("player"), move / 8, move % 8); + othello.move(moveMap.get("player").equals(client.getName()) ? ai : dummyPlayer, new Vector2D<>(move % 8, move / 8)); + + break; + case WIN: + case LOSS: + default: + for (int y = 0; y < 8; y++) { + System.out.print("|"); + for (int x = 0; x < 8; x++) { + Player p = othello.getBoard().get(new Vector2D<Integer, Integer>(x, y)); + if (p == dummyPlayer) { + System.out.print("N|"); + } else if (p == ai) { + System.out.print("A|"); + } else { + System.out.print(" |"); + } + } + System.out.println(); + } // outcome = event; + // return false; + } + } + // client.close(); + } +} diff --git a/src/main/java/nl/isygameclient/models/Ai.java b/src/main/java/nl/isygameclient/models/Ai.java @@ -1,133 +1,109 @@ package nl.isygameclient.models; -import nl.isygameclient.util.Vector2D; - +import java.security.SecureRandom; import java.util.*; +import nl.isygameclient.util.Vector2D; -public class Ai extends Player{ - - public static final int DEPTH = 5; - - private int[][] heuristics; - - public Ai(String name, String playingAs, int[][] heuristics) { - super(name, playingAs); - this.heuristics = heuristics; - } - - public static <T> T randomMove(Collection<T> possibleMoves) { - int size = possibleMoves.size(); - int item = new Random().nextInt(size); // In real life, the Random object should be rather more shared than this - int i = 0; - for (T obj : possibleMoves) - if (i == item) - return obj; - else - i++; - return null; - } - - private double miniMax(Integer depth, boolean isMaximizing) { - var playerManager = game.getPlayerManager(); - var currentPlayer = playerManager.getCurrentPlayer(); - var possibleMoves = game.getValidMoves(currentPlayer); - - if (depth == 0 || game.isGameOver()) { - return heuristicScore(currentPlayer); - } - - double bestValue = isMaximizing ? 0 : Double.MAX_VALUE; - for (Vector2D<Integer, Integer> move : possibleMoves) { - game.move(currentPlayer, move); - double value = miniMax(depth - 1, !isMaximizing); - bestValue = isMaximizing ? Math.max(bestValue, value) : Math.min(bestValue, value); - game.undo(); - } - return bestValue; - } - - private double miniMaxAlphaBeta(double alpha, double beta, Integer depth, boolean isMaximizing) { - var playerManager = game.getPlayerManager(); - var currentPlayer = playerManager.getCurrentPlayer(); - var possibleMoves = game.getValidMoves(currentPlayer); - - if (depth == 0 || game.isGameOver()) { - return heuristicScore(currentPlayer); - } - - double bestValue; - if (isMaximizing) { - bestValue = Double.MIN_VALUE; - for (Vector2D<Integer, Integer> move : possibleMoves) { - game.move(currentPlayer, move); - double value = miniMaxAlphaBeta(alpha, beta, depth - 1, false); - game.undo(); - - bestValue = Math.max(bestValue, value); - alpha = Math.max(alpha, bestValue); - if (beta <= alpha) { - break; - } - } - - } else { - bestValue = Double.MAX_VALUE; - for (Vector2D<Integer, Integer> move : possibleMoves) { - game.move(currentPlayer, move); - double value = miniMaxAlphaBeta(alpha, beta, depth - 1, true); - game.undo(); - - bestValue = Math.min(bestValue, value); - beta = Math.min(beta, bestValue); - if (beta <= alpha) { - break; - } - } - } - return bestValue; - } - - @Override - public Vector2D<Integer, Integer> onPlayerTurn() { - var manager = game.getPlayerManager(); - var currentPlayer = manager.getCurrentPlayer(); - var possibleMoves = game.getValidMoves(currentPlayer); - - double bestValue = Integer.MIN_VALUE; - var bestMove = randomMove(possibleMoves); - - for (Vector2D<Integer, Integer> move : possibleMoves) { - game.move(currentPlayer, move); - double moveValue = miniMaxAlphaBeta(Double.MIN_VALUE, Double.MAX_VALUE, DEPTH, false); - if (moveValue > bestValue) { - bestValue = moveValue; - bestMove = move; - } - game.undo(); - } - return bestMove; - } - - public int heuristicScore(Player player) { - var board = game.getBoard(); - int score = 0; - for (int y = 0; y < board.getHeight(); y++) { - for (int x = 0; x < board.getWidth(); x++) { - if (Objects.equals(player, board.get(new Vector2D<>(x, y)))) { - score += heuristics[y][x]; - } - } - } - return score; - } - - public void setGame(Game game) { - this.game = game; - } - - public void setHeuristics(int[][] heuristics) { - this.heuristics = heuristics; - } - - +public class Ai extends Player { + + public static final int DEPTH = 5; + public static final Random AI_RANDOM = new Random(); + + private final int[][] heuristics; + + public Ai(String name, String playingAs, int[][] heuristics) { + super(name, playingAs); + this.heuristics = heuristics; + } + + public static <T> T randomMove(Collection<T> possibleMoves) { + int size = possibleMoves.size(); + int item = AI_RANDOM.nextInt(size); // In real life, the Random object should be rather more shared than this + int i = 0; + for (T obj : possibleMoves) + if (i == item) + return obj; + else + i++; + return null; + } + + private double miniMaxAlphaBeta(double alpha, double beta, Integer depth, boolean isMaximizing) { + var playerManager = game.getPlayerManager(); + var currentPlayer = playerManager.getCurrentPlayer(); + var possibleMoves = game.getValidMoves(currentPlayer); + + if (depth == 0 || game.isGameOver()) { + return heuristicScore(currentPlayer); + } + + double bestValue; + if (isMaximizing) { + bestValue = Double.MIN_VALUE; + for (Vector2D<Integer, Integer> move : possibleMoves) { + game.move(currentPlayer, move); + double value = miniMaxAlphaBeta(alpha, beta, depth - 1, false); + game.undo(); + + bestValue = Math.max(bestValue, value); + alpha = Math.max(alpha, bestValue); + if (beta <= alpha) { + break; + } + } + + } else { + bestValue = Double.MAX_VALUE; + for (Vector2D<Integer, Integer> move : possibleMoves) { + game.move(currentPlayer, move); + double value = miniMaxAlphaBeta(alpha, beta, depth - 1, true); + game.undo(); + + bestValue = Math.min(bestValue, value); + beta = Math.min(beta, bestValue); + if (beta <= alpha) { + break; + } + } + } + return bestValue; + } + + @Override + public Vector2D<Integer, Integer> onPlayerTurn() { + var manager = game.getPlayerManager(); + var currentPlayer = manager.getCurrentPlayer(); + var possibleMoves = game.getValidMoves(currentPlayer); + + double bestValue = Integer.MIN_VALUE; + var bestMove = randomMove(possibleMoves); + + for (Vector2D<Integer, Integer> move : possibleMoves) { + game.move(currentPlayer, move); + double moveValue = miniMaxAlphaBeta(Double.MIN_VALUE, Double.MAX_VALUE, DEPTH, false); + if (moveValue > bestValue) { + bestValue = moveValue; + bestMove = move; + } + game.undo(); + } + return bestMove; + } + + public int heuristicScore(Player player) { + var board = game.getBoard(); + int score = 0; + for (int y = 0; y < board.getHeight(); y++) { + for (int x = 0; x < board.getWidth(); x++) { + if (Objects.equals(player, board.get(new Vector2D<>(x, y)))) { + score += heuristics[y][x]; + } + } + } + return score; + } + + public void setGame(Game game) { + this.game = game; + } } diff --git a/src/main/java/nl/isygameclient/models/PlayerManager.java b/src/main/java/nl/isygameclient/models/PlayerManager.java @@ -1,67 +1,66 @@ package nl.isygameclient.models; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class PlayerManager { - protected int startingPlayerIndex; - protected int currentPlayerIndex; - protected final ArrayList<Player> players; + protected int startingPlayerIndex; + protected int currentPlayerIndex; + protected final List<Player> players; - public PlayerManager(int startingPlayerIndex, ArrayList<Player> players) { - this.startingPlayerIndex = startingPlayerIndex; - this.currentPlayerIndex = startingPlayerIndex; - this.players = players; - } + public PlayerManager(int startingPlayerIndex, List<Player> players) { + this.startingPlayerIndex = startingPlayerIndex; + this.currentPlayerIndex = startingPlayerIndex; + this.players = players; + } - public void restart() { - currentPlayerIndex = startingPlayerIndex; - } + public void restart() { + currentPlayerIndex = startingPlayerIndex; + } - public void nextPlayer() { - currentPlayerIndex += 1; - if (currentPlayerIndex >= players.size()) { - currentPlayerIndex = 0; - } - } - public void previousPlayer() { - currentPlayerIndex -= 1; - if (currentPlayerIndex < 0) { - currentPlayerIndex = players.size() - 1; - } - } + public void nextPlayer() { + currentPlayerIndex += 1; + if (currentPlayerIndex >= players.size()) { + currentPlayerIndex = 0; + } + } + public void previousPlayer() { + currentPlayerIndex -= 1; + if (currentPlayerIndex < 0) { + currentPlayerIndex = players.size() - 1; + } + } - public boolean isCurrentPlayer(Player player) { - return Objects.equals(player, getCurrentPlayer()); - } + public boolean isCurrentPlayer(Player player) { + return Objects.equals(player, getCurrentPlayer()); + } - public Player getCurrentPlayer(){ - return players.get(currentPlayerIndex); - } + public Player getCurrentPlayer() { + return players.get(currentPlayerIndex); + } - public Player getStartingPlayer() { - return players.get(startingPlayerIndex); - } + public Player getStartingPlayer() { + return players.get(startingPlayerIndex); + } - public ArrayList<Player> getPlayers(){ - return players; - } + public List<Player> getPlayers() { + return players; + } - public int getStartingPlayerIndex() { - return startingPlayerIndex; - } + public int getStartingPlayerIndex() { + return startingPlayerIndex; + } - public int getCurrentPlayerIndex() { - return currentPlayerIndex; - } - - public void setStartingPlayerIndex(int startingPlayerIndex) { - this.startingPlayerIndex = startingPlayerIndex; - } - - public void setCurrentPlayerIndex(int currentPlayerIndex) { - this.currentPlayerIndex = currentPlayerIndex; - } + public int getCurrentPlayerIndex() { + return currentPlayerIndex; + } + public void setStartingPlayerIndex(int startingPlayerIndex) { + this.startingPlayerIndex = startingPlayerIndex; + } + public void setCurrentPlayerIndex(int currentPlayerIndex) { + this.currentPlayerIndex = currentPlayerIndex; + } } diff --git a/src/main/java/nl/isygameclient/models/board/Board.java b/src/main/java/nl/isygameclient/models/board/Board.java @@ -1,65 +1,64 @@ package nl.isygameclient.models.board; -import nl.isygameclient.models.Player; -import nl.isygameclient.util.Vector2D; - import java.util.Arrays; -import java.util.List; - -public abstract class Board<T> { +import nl.isygameclient.util.Vector2D; - protected final int width; - protected final int height; +public class Board<T> { + protected final int width; + protected final int height; - protected final T[][] field; + protected final T[][] field; - @SuppressWarnings("unchecked") - public Board(int width, int height) { - this.width = width; - this.height = height; - this.field = (T[][]) new Player[height][width]; - } + @SuppressWarnings("unchecked") + public Board(int width, int height) { + this.width = width; + this.height = height; + this.field = (T[][]) new Object[height][width]; + } - public void set(T value, Vector2D<Integer, Integer> pos) { - field[pos.getY()][pos.getX()] = value; - } + public void set(T value, Vector2D<Integer, Integer> pos) { + field[pos.getY()][pos.getX()] = value; + } - public T get(Vector2D<Integer, Integer> pos) { - return field[pos.getY()][pos.getX()]; - } + public T get(Vector2D<Integer, Integer> pos) { + return field[pos.getY()][pos.getX()]; + } - public int size() { - return width * height; - } + public int size() { + return width * height; + } - public void clear() { - Arrays.fill(field, null); - } + public void clear() { + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + field[y][x] = null; + } - public int getWidth() { - return width; - } + public int getWidth() { + return width; + } - public int getHeight() { - return height; - } + public int getHeight() { + return height; + } - public T[][] getField() { - return field; - } + public T[][] getField() { + return field; + } - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - for (T[] row : field) { - builder.append(Arrays.toString(row)) - .append(", \n"); - } + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (T[] row : field) { + builder.append(Arrays.toString(row)) + .append(", \n"); + } - return "Board{" + - "width=" + width + - ", height=" + height + - ", field=\n" + builder + - '}'; - } + return "Board{" + + + "width=" + width + + ", height=" + height + + ", field=\n" + builder + + '}'; + } } diff --git a/src/main/java/nl/isygameclient/models/board/HistoryBoard.java b/src/main/java/nl/isygameclient/models/board/HistoryBoard.java @@ -1,91 +1,90 @@ package nl.isygameclient.models.board; -import nl.isygameclient.util.Vector2D; - import java.util.*; +import nl.isygameclient.util.Vector2D; -public abstract class HistoryBoard<T> extends Board<T> { - public record Move<T>(T current, T previous) { - } - - public record Change<T>(T player, Map<Vector2D<Integer, Integer>, Move<T>> moves) { - } - - private int index; - private final Stack<Change<T>> stack; - - public HistoryBoard(int width, int height) { - super(width, height); - this.stack = new Stack<>(); - this.index = 0; - } - - public List<Change<T>> getHistory() { - return stack.subList(0, index); - } - - public List<Change<T>> getFuture() { - return stack.subList(index, stack.size()); - } - - public void add(T player, Map<Vector2D<Integer, Integer>, T> changes) { - for (int i = stack.size(); i > index; i--) - stack.pop(); - - Map<Vector2D<Integer, Integer>, Move<T>> moves = new HashMap<>(); - for (Map.Entry<Vector2D<Integer, Integer>, T> entry : changes.entrySet()) { - var vector = entry.getKey(); - moves.put(entry.getKey(), new Move<>(entry.getValue(), get(vector))); - } - - stack.push(new Change<>(player, moves)); - index++; - - for (var move : changes.entrySet()) { - var vector = move.getKey(); - set(move.getValue(), vector); - } - } - - public boolean canUndo() { - return index > 0; - } - - public boolean canRedo() { - return index < stack.size(); - } - - public boolean undo() { - if (!canUndo()) - return false; - - index--; - - for (var move : stack.get(index).moves.entrySet()) { - var vector = move.getKey(); - set(move.getValue().previous, vector); - } - - return true; - } - - public boolean redo() { - if (!canRedo()) - return false; - - for (var move : stack.get(index).moves.entrySet()) { - var vector = move.getKey(); - set(move.getValue().current, vector); - } - - index++; - return true; - } - - public void clear() { - super.clear(); - stack.clear(); - index = 0; - } +public class HistoryBoard<T> extends Board<T> { + public record Move<T>(T current, T previous) { + } + + public record Change<T>(T player, Map<Vector2D<Integer, Integer>, Move<T>> moves) { + } + + private int index; + private final Stack<Change<T>> stack; + + public HistoryBoard(int width, int height) { + super(width, height); + this.stack = new Stack<>(); + this.index = 0; + } + + public List<Change<T>> getHistory() { + return stack.subList(0, index); + } + + public List<Change<T>> getFuture() { + return stack.subList(index, stack.size()); + } + + public void add(T player, Map<Vector2D<Integer, Integer>, T> changes) { + for (int i = stack.size(); i > index; i--) + stack.pop(); + + Map<Vector2D<Integer, Integer>, Move<T>> moves = new HashMap<>(); + for (Map.Entry<Vector2D<Integer, Integer>, T> entry : changes.entrySet()) { + var vector = entry.getKey(); + moves.put(entry.getKey(), new Move<>(entry.getValue(), get(vector))); + } + + stack.push(new Change<>(player, moves)); + index++; + + for (var move : changes.entrySet()) { + var vector = move.getKey(); + set(move.getValue(), vector); + } + } + + public boolean canUndo() { + return index > 0; + } + + public boolean canRedo() { + return index < stack.size(); + } + + public boolean undo() { + if (!canUndo()) + return false; + + index--; + + for (var move : stack.get(index).moves.entrySet()) { + var vector = move.getKey(); + set(move.getValue().previous, vector); + } + + return true; + } + + public boolean redo() { + if (!canRedo()) + return false; + + for (var move : stack.get(index).moves.entrySet()) { + var vector = move.getKey(); + set(move.getValue().current, vector); + } + + index++; + return true; + } + + public void clear() { + super.clear(); + stack.clear(); + index = 0; + } } diff --git a/src/main/java/nl/isygameclient/models/games/othello/Othello.java b/src/main/java/nl/isygameclient/models/games/othello/Othello.java @@ -1,176 +1,175 @@ package nl.isygameclient.models.games.othello; +import java.util.*; +import java.util.stream.Collectors; import nl.isygameclient.models.Game; import nl.isygameclient.models.Player; import nl.isygameclient.models.PlayerManager; import nl.isygameclient.models.board.HistoryBoard; import nl.isygameclient.util.Vector2D; -import java.util.*; -import java.util.stream.Collectors; - public class Othello extends Game { - public Othello(PlayerManager playerManager) { - super(playerManager, new HistoryBoard<>(8, 8) {}); - initializeBoard(); - } - - @Override - public void restart() { - playerManager.restart(); - board = new HistoryBoard<>(8, 8) {}; - initializeBoard(); - } - - private void initializeBoard() { - var players = playerManager.getPlayers(); - board.set(players.get(1), new Vector2D<>(3, 3)); - board.set(players.get(0), new Vector2D<>(3, 4)); - board.set(players.get(0), new Vector2D<>(4, 3)); - board.set(players.get(1), new Vector2D<>(4, 4)); - - } - - @Override - public boolean isDraw() { - var scores = getScores(); - return scores.values().stream().distinct().count() == 1; - } - - @Override - public boolean isGameOver() { - for (Player player : playerManager.getPlayers()) { - if (getValidMoves(player).size() > 0){ - return false; - } - } - return true; - } - - @Override - public boolean isWinner(Player player) { - Player winner = null; - int score = 0; - for (Map.Entry<Player, Integer> entry : getScores().entrySet()) { - if (entry.getValue() > score) { - winner = entry.getKey(); - score = entry.getValue(); - } - } - return Objects.equals(player, winner); - } - - - @Override - public boolean move(Player player, Vector2D<Integer, Integer> pos) { - if (!isMoveValid(player, pos)) { - return false; - } - - var flippable = checkNeighbours(player, pos); - - Map<Vector2D<Integer, Integer>, Player> changes = flippable.stream().collect(Collectors.toMap(e -> e, e -> player)); - board.add(player, changes); - playerManager.nextPlayer(); - return true; - } - - - @Override - public boolean isMoveValid(Player player, Vector2D<Integer, Integer> pos) { - if (board.get(pos) != null) - return false; - - var flippable = checkNeighbours(player, pos); - return !flippable.isEmpty(); - } - - - private List<Vector2D<Integer, Integer>> rangeContains(Player player, Vector2D<Integer, Integer> pos, Vector2D<Integer, Integer> dir) { - List<Vector2D<Integer, Integer>> flippable = new ArrayList<>(); - int x = pos.getX(); - int y = pos.getY(); - while (x >= 0 && x < board.getWidth() && y >= 0 && y < board.getHeight()) { - var newPos = new Vector2D<>(x, y); - flippable.add(newPos); - if (Objects.equals(board.get(newPos), player)) { - return flippable; - } - x += dir.getX(); - y += dir.getY(); - } - return null; - } - - - private Set<Vector2D<Integer, Integer>> checkNeighbours(Player player, Vector2D<Integer, Integer> pos) { - Set<Vector2D<Integer, Integer>> flippable = new HashSet<>(); - for (int dx = -1; dx <= 1; dx++) { - if ((pos.getX() + dx) < 0 || (pos.getX() + dx) >= board.getWidth()) - continue; - - for (int dy = -1; dy <= 1; dy++) { - if ((pos.getY() + dy) < 0 || (pos.getY() + dy) >= board.getHeight()) - continue; - - if (dx == 0 && dy == 0) - continue; - - var checkPos = board.get(new Vector2D<>(pos.getX() + dx, pos.getY() + dy)); - if (checkPos != null && checkPos != player) { - List<Vector2D<Integer, Integer>> flip = rangeContains(player, pos, new Vector2D<>(dx, dy)); - if (flip != null) - flippable.addAll(flip); - } - } - } - return flippable; - } - - @Override - public List<Vector2D<Integer, Integer>> getValidMoves(Player player) { - List<Vector2D<Integer, Integer>> valid = new ArrayList<>(); - for (int y = 0; y < board.getHeight(); y++) { - for (int x = 0; x < board.getWidth(); x++) { - if (isMoveValid(player, new Vector2D<>(x, y))) { - valid.add(new Vector2D<>(x, y)); - } - } - } - return valid; - } - - @Override - public List<Player> getWinners() { - ArrayList<Player> winners = new ArrayList<>(); - for (Player player : playerManager.getPlayers()) { - if (isWinner(player)) { - winners.add(player); - } - } - return winners; - } - - @Override - public int getPlayerScore(Player player) { - int score = 0; - for (int y = 0; y < board.getHeight(); y++) { - for (int x = 0; x < board.getWidth(); x++) { - if (Objects.equals(player, board.get(new Vector2D<>(x, y)))) { - score += 1; - } - } - } - return score; - } - - private HashMap<Player, Integer> getScores() { - HashMap<Player, Integer> scores = new HashMap<>(); - for (Player player : playerManager.getPlayers()) { - int score = getPlayerScore(player); - scores.put(player, score); - } - return scores; - } + public Othello(PlayerManager playerManager) { + super(playerManager, new HistoryBoard<>(8, 8)); + initializeBoard(); + } + + @Override + public void restart() { + playerManager.restart(); + board.clear(); + initializeBoard(); + } + + public void initializeBoard() { + board.set(playerManager.getCurrentPlayer(), new Vector2D<>(3, 4)); + board.set(playerManager.getCurrentPlayer(), new Vector2D<>(4, 3)); + playerManager.nextPlayer(); + board.set(playerManager.getCurrentPlayer(), new Vector2D<>(3, 3)); + board.set(playerManager.getCurrentPlayer(), new Vector2D<>(4, 4)); + playerManager.nextPlayer(); + } + + @Override + public boolean isDraw() { + var scores = getScores(); + return scores.values().stream().distinct().count() == 1; + } + + @Override + public boolean isGameOver() { + for (Player player : playerManager.getPlayers()) { + if (getValidMoves(player).size() > 0) { + return false; + } + } + return true; + } + + @Override + public boolean isWinner(Player player) { + Player winner = null; + int score = 0; + for (Map.Entry<Player, Integer> entry : getScores().entrySet()) { + if (entry.getValue() > score) { + winner = entry.getKey(); + score = entry.getValue(); + } + } + return Objects.equals(player, winner); + } + + + @Override + public boolean move(Player player, Vector2D<Integer, Integer> pos) { + if (!isMoveValid(player, pos)) { + return false; + } + + var flippable = checkNeighbours(player, pos); + + Map<Vector2D<Integer, Integer>, Player> changes = flippable.stream().collect(Collectors.toMap(e -> e, e -> player)); + board.add(player, changes); + playerManager.nextPlayer(); + return true; + } + + + @Override + public boolean isMoveValid(Player player, Vector2D<Integer, Integer> pos) { + if (board.get(pos) != null) + return false; + + var flippable = checkNeighbours(player, pos); + return !flippable.isEmpty(); + } + + + private List<Vector2D<Integer, Integer>> rangeContains(Player player, Vector2D<Integer, Integer> pos, Vector2D<Integer, Integer> dir) { + List<Vector2D<Integer, Integer>> flippable = new ArrayList<>(); + int x = pos.getX(); + int y = pos.getY(); + while (x >= 0 && x < board.getWidth() && y >= 0 && y < board.getHeight()) { + var newPos = new Vector2D<>(x, y); + flippable.add(newPos); + if (Objects.equals(board.get(newPos), player)) { + return flippable; + } + x += dir.getX(); + y += dir.getY(); + } + return null; + } + + + private Set<Vector2D<Integer, Integer>> checkNeighbours(Player player, Vector2D<Integer, Integer> pos) { + Set<Vector2D<Integer, Integer>> flippable = new HashSet<>(); + for (int dx = -1; dx <= 1; dx++) { + if ((pos.getX() + dx) < 0 || (pos.getX() + dx) >= board.getWidth()) + continue; + + for (int dy = -1; dy <= 1; dy++) { + if ((pos.getY() + dy) < 0 || (pos.getY() + dy) >= board.getHeight()) + continue; + + if (dx == 0 && dy == 0) + continue; + + var checkPos = board.get(new Vector2D<>(pos.getX() + dx, pos.getY() + dy)); + if (checkPos != null && checkPos != player) { + List<Vector2D<Integer, Integer>> flip = rangeContains(player, pos, new Vector2D<>(dx, dy)); + if (flip != null) + flippable.addAll(flip); + } + } + } + return flippable; + } + + @Override + public List<Vector2D<Integer, Integer>> getValidMoves(Player player) { + List<Vector2D<Integer, Integer>> valid = new ArrayList<>(); + for (int y = 0; y < board.getHeight(); y++) { + for (int x = 0; x < board.getWidth(); x++) { + if (isMoveValid(player, new Vector2D<>(x, y))) { + valid.add(new Vector2D<>(x, y)); + } + } + } + return valid; + } + + @Override + public List<Player> getWinners() { + ArrayList<Player> winners = new ArrayList<>(); + for (Player player : playerManager.getPlayers()) { + if (isWinner(player)) { + winners.add(player); + } + } + return winners; + } + + @Override + public int getPlayerScore(Player player) { + int score = 0; + for (int y = 0; y < board.getHeight(); y++) { + for (int x = 0; x < board.getWidth(); x++) { + if (Objects.equals(player, board.get(new Vector2D<>(x, y)))) { + score += 1; + } + } + } + return score; + } + + private HashMap<Player, Integer> getScores() { + HashMap<Player, Integer> scores = new HashMap<>(); + for (Player player : playerManager.getPlayers()) { + int score = getPlayerScore(player); + scores.put(player, score); + } + return scores; + } } diff --git a/src/main/java/nl/isygameclient/network/GameClient.java b/src/main/java/nl/isygameclient/network/GameClient.java @@ -1,11 +1,6 @@ package nl.isygameclient.network; import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Random;