From 4fe027a2bb3587906b4a40ac87c827cbf21a9ac3 Mon Sep 17 00:00:00 2001 From: Dukantic Date: Tue, 6 May 2025 16:56:03 +0200 Subject: [PATCH] main game --- "\\" | 371 ++++++++++++++++++++++++++++++++++++++++++++ src/GomokuAI.java | 3 +- src/GomokuGame.java | 147 ++++++++---------- 3 files changed, 440 insertions(+), 81 deletions(-) create mode 100644 "\\" diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..286e162 --- /dev/null +++ "b/\\" @@ -0,0 +1,371 @@ + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Random; +import java.io.BufferedWriter; + +public class GomokuGame { + + public static final int DEFAULT_BOARD_WIDTH = 15; // largeur du plateau + public static final int DEFAULT_BOARD_HEIGHT = 15; // hauteur du plateau + public static final int DEFAULT_TOKENS_COUNT = 60; // nb de jetons + public int NB_CELL_PLAY = 5; // nb de jetons + + private Player player1; + private Player player2; + private Player currentPlayer; + + private GomokuBoard board; + + private GomokuRenderer renderer; + private boolean playRenderer = true; + + Color colorP1; + int currentPlayerInt; + + Coordinate cellCoor = null; + + public static void main(String[] args) { + + int sizeX = 15; + int sizeY = 15; + int nbToken = 50; + int nbJetonsAligne = 5; + boolean renderer = false; + String path_a_load = ""; + + for (int i = 0; i < args.length; i++) { + switch (args[i]) { + case "--save": + if (i + 1 < args.length) { + path_a_load = args[++i]; + } + break; + + case "--size": + if (i + 2 < args.length) { + sizeX = Integer.parseInt(args[++i]); + sizeY = Integer.parseInt(args[++i]); + } + break; + + case "--nbToken": + if (i + 1 < args.length) { + nbToken = Integer.parseInt(args[++i]); + } + break; + + case "--nbTokenToWin": + if (i + 1 < args.length) { + nbJetonsAligne = Integer.parseInt(args[++i]); + } + break; + + case "--renderer": + if (i + 1 < args.length) { + String bool = args[++i]; + if (bool == "true") { + renderer = true; + } else { + renderer = false; + } + } + break; + + default: + System.out.println("Invalid option : " + args[i]); + break; + } + } + if (sizeY < 3 || sizeX < 3) { + System.out.println("Board is too small !"); + return; + } + GomokuGame g = new GomokuGame(false);// metre true ou fals si in veut l'affichage ou non + g.renderer = new ConsoleRenderer(); + g.NB_CELL_PLAY = nbJetonsAligne; + g.renderer.init(g); + g.board = new GomokuBoard(sizeX, sizeY); + g.board.get(sizeX / 2, sizeY / 2).setState(Color.BLACK); + g.player1 = new Human("un", Color.WHITE, nbToken); + g.player2 = new GomokuAI("deux", Color.BLACK, nbToken - 1); + // System.out.println(g.board); + // g.renderer.update(); + g.startGame(); + + } + + /** + * This method init the game with these parameters. + * + * @param bot If the player want to play with a bot it's true. + * @param name1 Name of player one. + * @param name2 Name of player two. + * @param tokens Number of tokens for each player. + */ + + public void newGame(boolean bot, String name1, String name2, int tokens) { + Color[] possible = { Color.WHITE, Color.BLACK }; + int rnd = new Random().nextInt(possible.length); + Color colorPlayer1 = possible[rnd]; + Color colorPlayer2 = colorPlayer1.inverse(); + + this.player1 = new Human(name1, colorPlayer1, tokens); + + if (bot) { + this.player2 = new GomokuAI(name2, colorPlayer2, tokens); + + } else { + this.player2 = new Human(name2, colorPlayer2, tokens); + } + + this.board = new GomokuBoard(15, 15); + this.colorP1 = colorPlayer1; + currentPlayer = this.player1.color == Color.WHITE ? this.player1 : this.player2; + this.currentPlayerInt = this.player1.color == Color.WHITE ? 1 : 2; + + } + + /** + * This method is the main loop of the game + */ + public void startGame() { + /* + * GomokuCell actualPlayedCell = play(player1); + * if( NB_CELL_PLAY <= + * board.countMax(board.countAlignedCells(actualPlayedCell))) + * { + * System.out.println("c'est gangée !"); + * } + */ + this.currentPlayer = player1; + renderer.update(); + while (this.player1.tokens > 0 || this.player2.tokens > 0) { + GomokuCell currentPlay = null; + while (currentPlay == null) { + currentPlay = this.play(this.currentPlayer); + } + // Expand the board if one of the neighbours of the cell is null + // Only check for North, South, East and West neighbours + for (Cardinal cardinal : Cardinal.NS_EW) { + GomokuCell neighbour = currentPlay.getNeighbour(cardinal); + if (neighbour == null) { + this.board.expandBoard(cardinal); + } + } + if (NB_CELL_PLAY <= board.countMax(board.countAlignedCells(currentPlay))) { + this.renderer.updateStatus("Le joueur " + this.currentPlayer + "a gagné !"); + return; + } + this.currentPlayer.tokens -= 1; + this.currentPlayer = this.nextPlayer(); + renderer.update(); + } + + this.renderer.updateStatus("Match nul, il ne reste plus de jetons."); + return; + } + + /** + * Place the token on the cell where the player play. + * + * @param Player get player to play the cell. + */ + public GomokuCell play(Player player) { + GomokuCell cellToPlay = null; + if (this.playRenderer) { // If we play the game with the renderer. + while (this.cellCoor == null) { + try { + wait(16); + } catch (InterruptedException e) { + this.save(null); + System.out.println(e); + } + } + cellToPlay = this.board.get(this.cellCoor); + if (cellToPlay == null || !cellToPlay.isPlayable()) { // If the cell is not playable we return null to not + // play. + return null; + } + cellToPlay.setState(player.color); + this.cellCoor = null; + } else { + cellToPlay = player.chooseMove(this.board); + cellToPlay.setState(player.color); + } + return cellToPlay; + + } + + /** + * This method save the game on a file. + * + * @param filename The file to save. + * @return True if successful and false if not. + */ + public boolean save(Path filename) { + // save the game state to the file + // 1. Open the file + // 2. Write the first line with: + // w h colorP1 currentPlayerInt nbTokens1 nbTokens2 + // 3. Write the next h lines with the board + // 4. Close the file + // 5. Return true if successful, false otherwise + + // if filename is null, save to default file in ".cache/save.dat" + if (filename == null) { + filename = Path.of(".cache/save.dat"); + } + + try (BufferedWriter writer = Files.newBufferedWriter(filename)) { + // Write the first line + writer.write(String.format("%d %d %s %d %d %d%n", + this.getBoard().getWidth(), + this.getBoard().getHeight(), + colorP1, + currentPlayerInt, + this.player1.tokens, + this.player2.tokens)); + + // Write the board + for (int i = 0; i < this.getBoard().getHeight(); ++i) { + for (int j = 0; j < this.getBoard().getWidth(); ++j) { + char c; + switch (board.get(i, j).getState()) { + case BLACK: + c = 'X'; + break; + case WHITE: + c = 'O'; + break; + case NIL: + c = '.'; + break; + default: + throw new IllegalStateException( + String.format("Unexpected value at cell (%d, %d): %s", i, j, + board.get(i, j).getState())); + } + writer.write(c); + } + writer.newLine(); + } + } catch (IOException e) { + System.err.println("Error writing file: " + e.getMessage()); + return false; + } catch (Exception e) { + System.err.println("Unexpected error: " + e.getMessage()); + return false; + } + return true; + } + + /** + * This method load a game on the file. + * + * @param filename The file to load. + * @return True if successful and False if not. + */ + public boolean load(Path filename) { + // load the game state from the file + // 1. Open the file + // 2. Read the first line to get: + // w h colorP1 currentPlayerInt nbTokens1 nbTokens2 + // 3. Read the next h lines to get the board + // 4. Close the file + // 5. Initialize the board with the read values + + int w, h; + Color[][] colors; + + try (BufferedReader reader = Files.newBufferedReader(filename)) { + String line = reader.readLine(); + if (line == null) { + throw new IOException("Empty save file"); + } + + String[] parts = line.split(" "); + if (parts.length != 6) { + throw new IOException("Invalid save file format"); + } + w = Integer.parseInt(parts[0]); + h = Integer.parseInt(parts[1]); + colorP1 = Color.valueOf(parts[2]); + currentPlayerInt = Integer.parseInt(parts[3]); + this.player1.tokens = Integer.parseInt(parts[4]); + this.player2.tokens = Integer.parseInt(parts[5]); + + if (w <= 0 || h <= 0) { + throw new IllegalArgumentException("Invalid board dimensions"); + } + + colors = new Color[h][w]; + + for (int i = 0; i < h; ++i) { + line = reader.readLine(); + if (line == null) { + throw new IOException("Unexpected end of file"); + } + + if (line.length() != w) { + throw new IOException("Invalid board line format"); + } + + for (int j = 0; j < w; ++j) { + switch (line.charAt(j)) { + case 'X': + colors[i][j] = Color.BLACK; + break; + case 'O': + colors[i][j] = Color.WHITE; + break; + case '.': + colors[i][j] = Color.NIL; + break; + default: + throw new IllegalArgumentException("Invalid color: " + line.charAt(j)); + } + } + } + + } catch (IOException e) { + System.err.println("Error reading file: " + e.getMessage()); + return false; + } catch (NumberFormatException e) { + System.err.println("Invalid number format: " + e.getMessage()); + return false; + } catch (IllegalArgumentException e) { + System.err.println("Invalid color format: " + e.getMessage()); + return false; + } catch (Exception e) { + System.err.println("Unexpected error: " + e.getMessage()); + return false; + } + + // Initialize the board with the read values + this.board = new GomokuBoard(w, h, colors); + + return true; + } + + /** + * This method return the next player to play. + * + * @return The next player. + */ + private Player nextPlayer() { + if (this.currentPlayer == this.player1) { + return this.player2; + } + return player1; + } + + /** + * Get the board. + */ + public GomokuBoard getBoard() { + return this.board; + } +} diff --git a/src/GomokuAI.java b/src/GomokuAI.java index 4dace93..a275b16 100644 --- a/src/GomokuAI.java +++ b/src/GomokuAI.java @@ -13,8 +13,9 @@ public class GomokuAI extends Player { * @param name The name of the player. * @param color The color of the player. */ - public GomokuAI(String name, Color color, int tokens) { + public GomokuAI(String name, Color color, int tokens, int difficulty) { super(name, color, tokens); + this.difficulty = difficulty; } /** diff --git a/src/GomokuGame.java b/src/GomokuGame.java index ca20648..3e89f44 100644 --- a/src/GomokuGame.java +++ b/src/GomokuGame.java @@ -1,10 +1,11 @@ +import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Random; -import java.io.BufferedReader; +import java.util.Random; public class GomokuGame { @@ -20,15 +21,36 @@ public class GomokuGame { private GomokuBoard board; private GomokuRenderer renderer; - private boolean playRenderer = true; Color colorP1; int currentPlayerInt; Coordinate cellCoor = null; - GomokuGame(boolean renderer) { - this.playRenderer = renderer; + GomokuGame(int nbToken, int jtToWin, int x, int y, boolean pvp, int difficulty) { + Random r = new Random(); + this.renderer = new ConsoleRenderer(); + this.NB_CELL_PLAY = jtToWin; + this.renderer.init(this); + this.board = new GomokuBoard(x, y); + this.board.get(x / 2, y / 2).setState(Color.BLACK); + + if (r.nextInt() % 2 == 0) { + this.player1 = new Human("Premier joueur", Color.WHITE, nbToken); + if (pvp) { + this.player2 = new Human("Deuxieme joueur", Color.WHITE, nbToken); + + } + this.player2 = new GomokuAI("deux", Color.BLACK, nbToken - 1, difficulty); + } else { + this.player2 = new Human("Premier joueur", Color.WHITE, nbToken); + if (pvp) { + this.player1 = new Human("Deuxieme joueur", Color.WHITE, nbToken); + + } + this.player1 = new GomokuAI("deux", Color.BLACK, nbToken - 1, difficulty); + } + } public static void main(String[] args) { @@ -37,7 +59,8 @@ public class GomokuGame { int sizeY = 15; int nbToken = 50; int nbJetonsAligne = 5; - boolean renderer = false; + int difficulty = 2; + boolean pvp = true; String path_a_load = ""; for (int i = 0; i < args.length; i++) { @@ -47,33 +70,55 @@ public class GomokuGame { path_a_load = args[++i]; } break; + case "--pvp": + pvp = true; + break; case "--size": if (i + 2 < args.length) { - sizeX = Integer.parseInt(args[++i]); - sizeY = Integer.parseInt(args[++i]); + try { + sizeX = Integer.parseInt(args[++i]); + sizeY = Integer.parseInt(args[++i]); + + } catch (Exception e) { + System.out.println("Arguments de --size invalides, config par défaut a été appliquée."); + } } break; case "--nbToken": if (i + 1 < args.length) { - nbToken = Integer.parseInt(args[++i]); + try { + nbToken = Integer.parseInt(args[++i]); + } catch (Exception e) { + System.out.println("Argument de --nbToken invalide, config par défaut a été appliquée."); + + } + } + break; + case "--difficulty": + if (i + 1 < args.length) { + try { + difficulty = Integer.parseInt(args[++i]); + if (difficulty > 3 || difficulty < 1) { + System.out.println( + "Argument de --difficluty invalide, config par défaut a été appliquée."); + difficulty = 2; + } + } catch (Exception e) { + System.out.println("Argument de --difficluty invalide, config par défaut a été appliquée."); + + } } break; case "--nbTokenToWin": if (i + 1 < args.length) { - nbJetonsAligne = Integer.parseInt(args[++i]); - } - break; - - case "--renderer": - if (i + 1 < args.length) { - String bool = args[++i]; - if (bool == "true") { - renderer = true; - } else { - renderer = false; + try { + nbJetonsAligne = Integer.parseInt(args[++i]); + } catch (Exception e) { + System.out + .println("Argument de --nbTokenToWin invalide, config par défaut a été appliquée."); } } break; @@ -87,51 +132,11 @@ public class GomokuGame { System.out.println("Board is too small !"); return; } - GomokuGame g = new GomokuGame(false);// metre true ou fals si in veut l'affichage ou non - g.renderer = new ConsoleRenderer(); - g.NB_CELL_PLAY = nbJetonsAligne; - g.renderer.init(g); - g.board = new GomokuBoard(sizeX, sizeY); - g.board.get(sizeX / 2, sizeY / 2).setState(Color.BLACK); - g.player1 = new Human("un", Color.WHITE, nbToken); - g.player2 = new GomokuAI("deux", Color.BLACK, nbToken - 1); - // System.out.println(g.board); - // g.renderer.update(); + GomokuGame g = new GomokuGame(nbToken, nbJetonsAligne, sizeX, sizeY, pvp, difficulty); g.startGame(); } - /** - * This method init the game with these parameters. - * - * @param bot If the player want to play with a bot it's true. - * @param name1 Name of player one. - * @param name2 Name of player two. - * @param tokens Number of tokens for each player. - */ - - public void newGame(boolean bot, String name1, String name2, int tokens) { - Color[] possible = { Color.WHITE, Color.BLACK }; - int rnd = new Random().nextInt(possible.length); - Color colorPlayer1 = possible[rnd]; - Color colorPlayer2 = colorPlayer1.inverse(); - - this.player1 = new Human(name1, colorPlayer1, tokens); - - if (bot) { - this.player2 = new GomokuAI(name2, colorPlayer2, tokens); - - } else { - this.player2 = new Human(name2, colorPlayer2, tokens); - } - - this.board = new GomokuBoard(15, 15); - this.colorP1 = colorPlayer1; - currentPlayer = this.player1.color == Color.WHITE ? this.player1 : this.player2; - this.currentPlayerInt = this.player1.color == Color.WHITE ? 1 : 2; - - } - /** * This method is the main loop of the game */ @@ -179,26 +184,8 @@ public class GomokuGame { */ public GomokuCell play(Player player) { GomokuCell cellToPlay = null; - if (this.playRenderer) { // If we play the game with the renderer. - while (this.cellCoor == null) { - try { - wait(16); - } catch (InterruptedException e) { - this.save(null); - System.out.println(e); - } - } - cellToPlay = this.board.get(this.cellCoor); - if (cellToPlay == null || !cellToPlay.isPlayable()) { // If the cell is not playable we return null to not - // play. - return null; - } - cellToPlay.setState(player.color); - this.cellCoor = null; - } else { - cellToPlay = player.chooseMove(this.board); - cellToPlay.setState(player.color); - } + cellToPlay = player.chooseMove(this.board); + cellToPlay.setState(player.color); return cellToPlay; }