Compare commits

...

10 Commits

Author SHA1 Message Date
FyloZ 48f2b72c40
Version finale 2023-04-11 23:18:34 -04:00
FyloZ 7416df8a17
Limite la longueur d'un coup sous les 5 secondes 2023-04-11 14:43:51 -04:00
FyloZ eb0146f10a
Gitignore 2023-04-11 14:22:02 -04:00
william c3cd91b199 Meilleures heuristiques 2023-04-11 14:11:19 -04:00
william 648c325649 Beaucoup de changements 2023-04-09 13:26:32 -04:00
william 5f7d74e0ae Plus d'heuristiques 2023-04-03 23:48:52 -04:00
FyloZ abf7f5bfd8
Séparation de minimax et de l'évaluation 2023-03-29 23:10:43 -04:00
william dbc494042b Fixes? 2023-03-25 20:22:24 -04:00
william 02b49c9437 Fixes? 2023-03-25 19:28:12 -04:00
william bd0fc7edfd Fixes? 2023-03-24 14:36:13 -04:00
36 changed files with 1846 additions and 491 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -8,7 +8,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

View File

@ -4,11 +4,20 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="41395b4b-3252-492c-a869-5f4dab107186" name="Changes" comment="Small fixes"> <list default="true" id="41395b4b-3252-492c-a869-5f4dab107186" name="Changes" comment="Limite la longueur d'un coup sous les 5 secondes">
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/Client.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/Client.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MiniMax.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MiniMax.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/game/PusherGame.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/game/PusherGame.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/PusherBoard.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/PusherBoard.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/PawnUtils.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/PawnUtils.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/AttackStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/AttackStrategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/DefenseStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/DefenseStrategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/ImmediateDefenseStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/ImmediateDefenseStrategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/MasterStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/MasterStrategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/MiniMaxStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/MiniMaxStrategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/RandomStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/RandomStrategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/Strategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/Strategy.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/WinningStrategy.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/WinningStrategy.java" afterDir="false" />
</list> </list>
<list id="98b8a79f-2f53-42bf-96da-7af322697a0d" name="Changes by acastonguay" comment="" /> <list id="98b8a79f-2f53-42bf-96da-7af322697a0d" name="Changes by acastonguay" comment="" />
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@ -19,6 +28,7 @@
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="Interface" />
<option value="Class" /> <option value="Class" />
</list> </list>
</option> </option>
@ -26,9 +36,10 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="alexis" /> <entry key="$PROJECT_DIR$" value="master" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="MacroExpansionManager"> <component name="MacroExpansionManager">
<option name="directoryName" value="x0367gi2" /> <option name="directoryName" value="x0367gi2" />
@ -36,12 +47,8 @@
<component name="MarkdownSettingsMigration"> <component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" /> <option name="stateVersion" value="1" />
</component> </component>
<component name="MavenImportPreferences"> <component name="ProblemsViewState">
<option name="importingSettings"> <option name="selectedTabId" value="CurrentFile" />
<MavenImportingSettings>
<option name="workspaceImportEnabled" value="true" />
</MavenImportingSettings>
</option>
</component> </component>
<component name="ProjectId" id="2NFN0RGJNNJqiDmt34ixVwkIwEm" /> <component name="ProjectId" id="2NFN0RGJNNJqiDmt34ixVwkIwEm" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true"> <component name="ProjectLevelVcsManager" settingsEditedManually="true">
@ -57,12 +64,17 @@
"RunOnceActivity.ShowReadmeOnStart": "true", "RunOnceActivity.ShowReadmeOnStart": "true",
"SHARE_PROJECT_CONFIGURATION_FILES": "true", "SHARE_PROJECT_CONFIGURATION_FILES": "true",
"codeWithMe.voiceChat.enabledByDefault": "false", "codeWithMe.voiceChat.enabledByDefault": "false",
"git-widget-placeholder": "william",
"last_opened_file_path": "/home/william/Dev/Projects", "last_opened_file_path": "/home/william/Dev/Projects",
"node.js.detected.package.eslint": "true", "node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true", "node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)", "node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)", "node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm", "nodejs_package_manager_path": "npm",
"project.structure.last.edited": "Libraries",
"project.structure.proportion": "0.15",
"project.structure.side.proportion": "0.2",
"settings.editor.selected.configurable": "reference.projectsettings.compiler.javacompiler",
"vue.rearranger.settings.migration": "true" "vue.rearranger.settings.migration": "true"
} }
}]]></component> }]]></component>
@ -72,7 +84,7 @@
<module name="Lab4" /> <module name="Lab4" />
<extension name="coverage"> <extension name="coverage">
<pattern> <pattern>
<option name="PATTERN" value="laboratoire4.*" /> <option name="PATTERN" value="laboratoire4.pawns.*" />
<option name="ENABLED" value="true" /> <option name="ENABLED" value="true" />
</pattern> </pattern>
</extension> </extension>
@ -85,7 +97,7 @@
<module name="Lab4" /> <module name="Lab4" />
<extension name="coverage"> <extension name="coverage">
<pattern> <pattern>
<option name="PATTERN" value="laboratoire4.*" /> <option name="PATTERN" value="laboratoire4.pawns.*" />
<option name="ENABLED" value="true" /> <option name="ENABLED" value="true" />
</pattern> </pattern>
</extension> </extension>
@ -98,7 +110,7 @@
<module name="Lab4" /> <module name="Lab4" />
<extension name="coverage"> <extension name="coverage">
<pattern> <pattern>
<option name="PATTERN" value="laboratoire4.*" /> <option name="PATTERN" value="laboratoire4.pawns.*" />
<option name="ENABLED" value="true" /> <option name="ENABLED" value="true" />
</pattern> </pattern>
</extension> </extension>
@ -128,6 +140,19 @@
<workItem from="1679344371350" duration="4138000" /> <workItem from="1679344371350" duration="4138000" />
<workItem from="1679365557789" duration="21000" /> <workItem from="1679365557789" duration="21000" />
<workItem from="1679424430928" duration="11764000" /> <workItem from="1679424430928" duration="11764000" />
<workItem from="1679593788411" duration="10753000" />
<workItem from="1679672719638" duration="8340000" />
<workItem from="1679779362814" duration="5912000" />
<workItem from="1679787823160" duration="2189000" />
<workItem from="1680198283785" duration="8256000" />
<workItem from="1680580123067" duration="326000" />
<workItem from="1680750346503" duration="2118000" />
<workItem from="1680821970713" duration="10363000" />
<workItem from="1680914341116" duration="19656000" />
<workItem from="1681078101864" duration="10646000" />
<workItem from="1681099446755" duration="28000" />
<workItem from="1681099484843" duration="9035000" />
<workItem from="1681145055287" duration="4569000" />
</task> </task>
<task id="LOCAL-00001" summary="MiniMax"> <task id="LOCAL-00001" summary="MiniMax">
<created>1679263366439</created> <created>1679263366439</created>
@ -150,7 +175,49 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1679490100944</updated> <updated>1679490100944</updated>
</task> </task>
<option name="localTasksCounter" value="4" /> <task id="LOCAL-00004" summary="Fixes?">
<created>1679682974611</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1679682974611</updated>
</task>
<task id="LOCAL-00005" summary="Fixes?">
<created>1679786894920</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1679786894920</updated>
</task>
<task id="LOCAL-00006" summary="Plus d'heuristiques">
<created>1680580135038</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1680580135038</updated>
</task>
<task id="LOCAL-00007" summary="Beaucoup de changements">
<created>1681061194018</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1681061194019</updated>
</task>
<task id="LOCAL-00008" summary="Gitignore">
<created>1681237323963</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1681237323963</updated>
</task>
<task id="LOCAL-00009" summary="Limite la longueur d'un coup sous les 5 secondes">
<created>1681238632769</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1681238632769</updated>
</task>
<option name="localTasksCounter" value="10" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -160,17 +227,11 @@
<MESSAGE value="MiniMax" /> <MESSAGE value="MiniMax" />
<MESSAGE value="Movement logic" /> <MESSAGE value="Movement logic" />
<MESSAGE value="Small fixes" /> <MESSAGE value="Small fixes" />
<option name="LAST_COMMIT_MESSAGE" value="Small fixes" /> <MESSAGE value="Fixes?" />
</component> <MESSAGE value="Plus d'heuristiques" />
<component name="XDebuggerManager"> <MESSAGE value="Beaucoup de changements" />
<breakpoint-manager> <MESSAGE value="Gitignore" />
<breakpoints> <MESSAGE value="Limite la longueur d'un coup sous les 5 secondes" />
<line-breakpoint enabled="true" type="java-line"> <option name="LAST_COMMIT_MESSAGE" value="Limite la longueur d'un coup sous les 5 secondes" />
<url>file://$PROJECT_DIR$/src/main/java/laboratoire4/PusherBoard.java</url>
<line>60</line>
<option name="timeStamp" value="7" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component> </component>
</project> </project>

19
pom.xml
View File

@ -9,9 +9,24 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>laboratoire4.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -0,0 +1,21 @@
package laboratoire4;
import laboratoire4.pawns.PawnMovement;
public class Action<T extends IPawn> {
private final T pawn;
private final PawnMovement movement;
public Action(T pawn, PawnMovement movement) {
this.pawn = pawn;
this.movement = movement;
}
public T getPawn() {
return pawn;
}
public PawnMovement getMovement() {
return movement;
}
}

View File

@ -1,5 +1,7 @@
package laboratoire4; package laboratoire4;
import laboratoire4.game.PusherGame;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
@ -9,7 +11,7 @@ public class Client {
private final Socket socket; private final Socket socket;
private final BufferedInputStream input; private final BufferedInputStream input;
private final BufferedOutputStream output; private final BufferedOutputStream output;
private PusherBoard board; private PusherGame board;
public Client(String host, int port) throws IOException { public Client(String host, int port) throws IOException {
socket = new Socket(host, port); socket = new Socket(host, port);
@ -60,7 +62,7 @@ public class Client {
String s = new String(aBuffer).trim(); String s = new String(aBuffer).trim();
String[] boardValues = s.split(" "); String[] boardValues = s.split(" ");
board = new PusherBoard(player, boardValues); board = new PusherGame(player, boardValues);
return player == Player.RED; return player == Player.RED;
} }
@ -76,24 +78,27 @@ public class Client {
board.move(previousMove); board.move(previousMove);
} }
// Thread.sleep(1000); Thread.sleep(200);
String nextMove = board.runNextMove(); String nextMove = board.runNextMove();
System.out.println("Prochain mouvement: " + nextMove); System.out.printf("Prochain mouvement: %s\n\n", nextMove);
output.write(nextMove.getBytes(), 0, nextMove.length()); output.write(nextMove.getBytes(), 0, nextMove.length());
output.flush(); output.flush();
} }
private void handleInvalidMove() throws InterruptedException { private void handleInvalidMove() throws InterruptedException, IOException {
System.err.println("Mouvement invalide!"); System.err.println("Mouvement invalide!");
Thread.sleep(500); Thread.sleep(500);
board.printBoard();
PusherGame.printBoard(board.getBoard());
// play();
} }
private void stopGame() throws IOException { private void stopGame() throws IOException {
System.out.println("GG well played!"); System.out.println("GG well played!");
socket.close(); // socket.close();
} }
} }

View File

@ -1,11 +1,15 @@
package laboratoire4; package laboratoire4;
import laboratoire4.game.PusherGame;
import laboratoire4.pawns.Pawn;
import laboratoire4.pawns.PawnMovement;
import java.util.Collection; import java.util.Collection;
public class GameTree { public class GameTree {
private Node root; private Node root;
public Node buildTree(PusherBoard board){ public Node buildTree(PusherGame board){
return null; return null;
} }
@ -16,9 +20,9 @@ public class GameTree {
static class Node { static class Node {
private final Collection<Node> childs; private final Collection<Node> childs;
private final Pawn pawn; private final Pawn pawn;
private final Pawn.PawnMovement movement; private final PawnMovement movement;
Node(Collection<Node> childs, Pawn pawn, Pawn.PawnMovement movement) { Node(Collection<Node> childs, Pawn pawn, PawnMovement movement) {
this.childs = childs; this.childs = childs;
this.pawn = pawn; this.pawn = pawn;
this.movement = movement; this.movement = movement;
@ -32,7 +36,7 @@ public class GameTree {
return pawn; return pawn;
} }
public Pawn.PawnMovement getMovement() { public PawnMovement getMovement() {
return movement; return movement;
} }
} }

View File

@ -0,0 +1,18 @@
package laboratoire4;
import laboratoire4.pawns.PawnMovement;
public interface IPawn {
boolean isMoveValid(IPawn[][] board, PawnMovement movement);
boolean isMoveValid(IPawn[][] board, PawnMovement movement, boolean ignorePlayers);
boolean isMoveValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol, boolean ignorePlayer);
boolean isMoveValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol);
void move(PawnMovement movement);
boolean isPusher();
int getDirection();
Player getPlayer();
int getRow();
int getCol();
void setRow(int row);
void setCol(int col);
}

View File

@ -1,9 +1,27 @@
package laboratoire4; package laboratoire4;
import java.io.IOException; import java.util.Scanner;
public class Main { public class Main {
public static void main(String[] args) throws IOException { public static void main(String[] args) {
new Client("localhost", 8888).listen(); Scanner scanner = new Scanner(System.in);
while (true) {
try {
new Client("localhost", 8888).listen();
} catch (Exception e) {
e.printStackTrace();
}
System.out.print("Recommencer? (Y/n) ");
if (scanner.hasNextLine()) {
String nextLine = scanner.nextLine();
if (nextLine.equals("n")) {
System.exit(0);
}
}
}
} }
} }

View File

@ -1,147 +0,0 @@
package laboratoire4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class MiniMax {
private static final int MAX_DEPTH = 2;
private static Random random = new Random();
public static MiniMaxResult miniMax(PusherBoard board) {
return miniMax(board, true, 0, 0);
}
private static MiniMaxResult miniMax(PusherBoard board, boolean max, int depth, int alphaBeta) {
int limScore = max ? Integer.MIN_VALUE : Integer.MAX_VALUE;
List<Pawn> pawns = max ?
board.getMaxPawns() :
board.getMinPawns();
// Collections.shuffle(pawns);
List<MiniMaxResult> results = new ArrayList<>();
for (Pawn pawn : pawns) {
int originalRow = pawn.getRow();
int originalCol = pawn.getCol();
for (Pawn.PawnMovement movement : Pawn.PawnMovement.values()) {
if (pawn.isMoveValid(board, movement)) {
pawn.move(movement);
int nextAlphaBeta = max ? Math.max(limScore, alphaBeta) : Math.min(limScore, alphaBeta);
int score = evaluate(pawn, board, max);
if (depth < MAX_DEPTH) {
score = miniMax(board, !max, depth + 1, nextAlphaBeta).getScore();
}
if ((max && score > limScore) ||
(!max && score < limScore)) {
limScore = score;
}
MiniMaxResult result = new MiniMaxResult(limScore, pawn, movement);
//elagage alphaBeta
// if ((max && limScore > alphaBeta) ||
// (!max && limScore < alphaBeta)) {
// pawn.setRow(originalRow);
// pawn.setCol(originalCol);
//
// return result;
// }
results.add(result);
}
pawn.setRow(originalRow);
pawn.setCol(originalCol);
}
}
// Choisir aléatoirement
int index = random.nextInt(results.size());
return results.get(index);
}
private static int evaluate(Pawn pawn, PusherBoard board, boolean max) {
int score = didWin(pawn, max) + didCapture(pawn, board, max) + isPushed(pawn);
int homeRow = pawn.getPlayer() == Player.RED ? 0 : 7;
if (pawn.getRow() != homeRow) {
score += 50;
}
int direction = max ? 1 : -1;
return score * direction;
}
private static int isPushed(Pawn pawn) {
if (pawn instanceof Pushed) {
return 5;
}
return 0;
}
private static int didWin(Pawn pawn, boolean max) {
int goal = pawn.getPlayer() == Player.RED ? 7 : 0;
if (pawn.getRow() == goal) {
return max ? 1000 : 100000;
}
return 0;
}
private static int didCapture(Pawn pawn, PusherBoard board, boolean max) {
Pawn capturedPawn = board.getBoard()[pawn.getRow()][pawn.getCol()];
if (capturedPawn != null) {
if (capturedPawn.getPlayer() != pawn.getPlayer()) {
if (max) {
return capturedPawn instanceof Pusher ? 100 : 50;
} else {
return capturedPawn instanceof Pusher ? 10000 : 5000;
}
}
}
return 0;
}
static class MiniMaxResult {
private final int score;
private final Pawn pawn;
private final Pawn.PawnMovement movement;
public MiniMaxResult(int score, Pawn pawn, Pawn.PawnMovement movement) {
this.score = score;
this.pawn = pawn;
this.movement = movement;
}
public int getScore() {
return score;
}
public Pawn getPawn() {
return pawn;
}
public Pawn.PawnMovement getMovement() {
return movement;
}
@Override
public String toString() {
return "MiniMaxResult{" +
"score=" + score +
", pawn=" + pawn +
", movement=" + movement +
'}';
}
}
}

View File

@ -1,93 +0,0 @@
package laboratoire4;
public abstract class Pawn {
protected final Player player;
protected int row;
protected int col;
protected int direction;
public Pawn(Player player, int row, int col) {
this.player = player;
this.row = row;
this.col = col;
this.direction = player == Player.RED ? 1 : -1;
}
public void move(PawnMovement movement) {
setRow(row + direction);
setCol(col + movement.move);
}
public String getPosition() {
char colStr = (char)(col + 65);
return String.format("%s%d", colStr, row + 1);
}
public Player getPlayer() {
return player;
}
public int getCol() {
return col;
}
public int getRow() {
return row;
}
public void setCol(int col) {
this.col = col;
}
public void setRow(int row) {
this.row = row;
}
public int getDirection() {
return direction;
}
public boolean isMoveValid(PusherBoard game, PawnMovement movement) {
Pawn[][] board = game.getBoard();
int nextRow = row + getDirection();
if (nextRow < 0 || nextRow >= board.length) {
return false;
}
int nextCol = col + movement.move;
if (nextCol < 0 || nextCol >= board.length) {
return false;
}
return isMoveValid(board, movement);
}
protected abstract boolean isMoveValid(Pawn[][] board, PawnMovement movement);
enum PawnMovement {
STRAIGHT(0),
LEFT_DIAGONAL(-1),
RIGHT_DIAGONAL(1);
private final int move;
PawnMovement(int move) {
this.move = move;
}
public int getMove() {
return move;
}
public static PawnMovement from(int move) {
if (move == 0) {
return STRAIGHT;
}
if (move == 1) {
return RIGHT_DIAGONAL;
}
return LEFT_DIAGONAL;
}
}
}

View File

@ -1,6 +1,28 @@
package laboratoire4; package laboratoire4;
public enum Player { public enum Player {
BLACK, BLACK(-1, 0, 7),
RED RED(1, 7, 0);
private final int direction;
private final int goal;
private final int home;
Player(int direction, int goal, int home) {
this.direction = direction;
this.goal = goal;
this.home = home;
}
public int getDirection() {
return direction;
}
public int getGoal() {
return goal;
}
public int getHome() {
return home;
}
} }

View File

@ -1,34 +0,0 @@
package laboratoire4;
public class Pushed extends Pawn {
public Pushed(Player player, int row, int col) {
super(player, row, col);
}
@Override
public boolean isMoveValid(Pawn[][] board, PawnMovement movement) {
Pawn pusher = null;
Pawn to = board[row + direction][col + movement.getMove()];
if (col > 0 && movement == PawnMovement.RIGHT_DIAGONAL) {
pusher = board[row - direction][col - 1];
} else if (col < board.length - 1 && movement == PawnMovement.LEFT_DIAGONAL) {
pusher = board[row - direction][col + 1];
} else if (movement == PawnMovement.STRAIGHT) {
pusher = board[row - direction][col];
}
boolean pusherValid = pusher instanceof Pusher && pusher.player == player;
boolean destinationValid = to == null || (movement != PawnMovement.STRAIGHT && to.player != this.player);
return pusherValid && destinationValid;
}
@Override
public String toString() {
return "Pushed{" +
player +
", " + col +
", " + row +
"} ";
}
}

View File

@ -1,27 +0,0 @@
package laboratoire4;
public class Pusher extends Pawn {
public Pusher(Player player, int row, int col) {
super(player, row, col);
}
@Override
public boolean isMoveValid(Pawn[][] board, PawnMovement movement) {
Pawn to = board[row + direction][col + movement.getMove()];
if (to == null) {
return true;
}
return to.player != this.player && movement != PawnMovement.STRAIGHT;
}
@Override
public String toString() {
return "Pusher{" +
player +
", " + col +
", " + row +
"} ";
}
}

View File

@ -1,144 +0,0 @@
package laboratoire4;
import java.util.ArrayList;
import java.util.List;
public class PusherBoard {
private final Player player;
private Pawn[][] board;
private final List<Pawn> maxPawns = new ArrayList<>();
private final List<Pawn> minPawns = new ArrayList<>();
public PusherBoard(Player player, String[] boardValues) {
this.player = player;
newGame(boardValues);
}
public void newGame(String[] boardValues) {
this.board = new Pawn[8][8];
int col = 0, row = 0;
for (String boardValue : boardValues) {
int v = Integer.parseInt(boardValue);
if (v != 0) {
Player pawnPlayer = (v == 1 || v == 2) ? Player.RED : Player.BLACK;
Pawn pawn;
if (v % 2 == 0) { // 2 et 4 sont les pushers
pawn = new Pusher(pawnPlayer, row, col);
} else {
pawn = new Pushed(pawnPlayer, row, col);
}
if (pawnPlayer == player) {
maxPawns.add(pawn);
} else {
minPawns.add(pawn);
}
board[row][col] = pawn;
}
col++;
if (col == board.length) {
col = 0;
row++;
}
}
}
public String runNextMove() {
MiniMax.MiniMaxResult result = MiniMax.miniMax(this);
Pawn pawn = result.getPawn();
String initialPosition = pawn.getPosition();
System.out.println(result.getScore());
move(pawn, result.getMovement());
return initialPosition + "-" + pawn.getPosition();
}
public void move(String move) {
//FORMAT ex : D2-D3
String[] split = move.trim().split(" - ");
move(split[0], split[1]);
}
private void move(Pawn pawn, Pawn.PawnMovement movement) {
move(pawn.getCol(), pawn.getRow(), pawn.getCol() + movement.getMove(), pawn.getRow() + pawn.getDirection());
}
public void move(String from, String to) {
//FORMAT ex : from {D2}, to {D3}
int from_col = (int) from.charAt(0) - 65;
int from_row = Integer.parseInt(String.valueOf(from.charAt(1))) - 1;
int to_col = (int) to.charAt(0) - 65;
int to_row = Integer.parseInt(String.valueOf(to.charAt(1))) - 1;
move(from_col, from_row, to_col, to_row);
}
public void move(int from_col, int from_row, int to_col, int to_row) {
if (!isValid(from_col, from_row, to_col)) {
return;
}
Pawn pawn = board[from_row][from_col];
Pawn destPawn = board[to_row][to_col];
if (destPawn != null) {
if (destPawn.getPlayer() == player) {
maxPawns.remove(destPawn);
} else {
minPawns.remove(destPawn);
}
}
board[from_row][from_col] = null;
board[to_row][to_col] = pawn;
pawn.setRow(to_row);
pawn.setCol(to_col);
}
private boolean isValid(int from_col, int from_row, int to_col) {
Pawn pawn = getBoard()[from_row][from_col];
Pawn.PawnMovement move = Pawn.PawnMovement.from(to_col - from_col);
if (pawn == null) {
return false;
}
return pawn.isMoveValid(this, move);
}
public Pawn[][] getBoard() {
return board;
}
public List<Pawn> getMaxPawns() {
return maxPawns;
}
public List<Pawn> getMinPawns() {
return minPawns;
}
public void printBoard() {
for (int i = 7; i >= 0; i--) {
for (int j = 0; j < this.board.length; j++) {
if (this.board[i][j] != null) {
System.out.print(this.board[i][j] + " | ");
} else {
System.out.print(" | ");
}
}
System.out.println();
System.out.println("----------------------------------------------------------------------------------------");
}
}
}

View File

@ -0,0 +1,13 @@
package laboratoire4.game;
import laboratoire4.IPawn;
import laboratoire4.Player;
import laboratoire4.pawns.PawnMovement;
public interface Game {
void move(IPawn pawn, PawnMovement movement);
IPawn[][] getBoard();
Player getPlayer();
}

View File

@ -0,0 +1,38 @@
package laboratoire4.game;
import laboratoire4.IPawn;
import laboratoire4.Player;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class GameUtils {
public static <T extends IPawn> Stream<T> getPawnsAsStream(T[][] board) {
return Arrays.stream(board)
.flatMap(Arrays::stream)
.filter(Objects::nonNull);
}
public static <T extends IPawn> Collection<IPawn> getPawns(T[][] board) {
return getPawnsAsStream(board).collect(Collectors.toList());
}
public static <T extends IPawn> Stream<T> getMaxPawnsAsStream(T[][] board, Player player) {
return getPawnsAsStream(board).filter(p -> p.getPlayer() == player);
}
public static <T extends IPawn> Collection<T> getMaxPawns(T[][] board, Player player) {
return getMaxPawnsAsStream(board, player).collect(Collectors.toList());
}
public static <T extends IPawn> Stream<T> getMinPawnsAsStream(T[][] board, Player player) {
return getPawnsAsStream(board).filter(p -> p.getPlayer() != player);
}
public static <T extends IPawn> Collection<T> getMinPawns(T[][] board, Player player) {
return getMinPawnsAsStream(board, player).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,117 @@
package laboratoire4.game;
import laboratoire4.IPawn;
import laboratoire4.Player;
import laboratoire4.pawns.Pawn;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.Pushed;
import laboratoire4.pawns.Pusher;
import laboratoire4.strategies.EvaluationResult;
import laboratoire4.strategies.MasterStrategy;
public class PusherGame implements Game {
private final Player player;
private Pawn[][] board;
public PusherGame(Player player, String[] boardValues) {
this.player = player;
newGame(boardValues);
}
public void newGame(String[] boardValues) {
this.board = new Pawn[8][8];
int col = 0, row = 0;
for (String boardValue : boardValues) {
int v = Integer.parseInt(boardValue);
if (v != 0) {
Player pawnPlayer = (v == 1 || v == 2) ? Player.RED : Player.BLACK;
Pawn pawn;
if (v % 2 == 0) { // 2 et 4 sont les pushers
pawn = new Pusher(pawnPlayer, row, col);
} else {
pawn = new Pushed(pawnPlayer, row, col);
}
board[row][col] = pawn;
}
col++;
if (col == board.length) {
col = 0;
row++;
}
}
}
public String runNextMove() {
EvaluationResult result = MasterStrategy.getInstance(this).getNextMove();
if (result == null) {
// Ce n'est pas censé arriver, on réessaie
System.err.println("Aucune résultat n'a été retourné!");
result = MasterStrategy.getInstance(this).getNextMove();
}
Pawn pawn = board[result.getRow()][result.getCol()];
String initialPosition = pawn.getPosition();
move(pawn, result.getMovement());
String nextPosition = pawn.getPosition();
return initialPosition + "-" + nextPosition;
}
public void move(String move) {
String[] split = move.trim().split(" - ");
String from = split[0];
String to = split[1];
int fromCol = (int) from.charAt(0) - 65;
int fromRow = Integer.parseInt(String.valueOf(from.charAt(1))) - 1;
int toCol = (int) to.charAt(0) - 65;
PawnMovement movement = PawnMovement.from(toCol - fromCol);
move(fromRow, fromCol, movement);
}
public void move(int row, int col, PawnMovement movement) {
Pawn pawn = board[row][col];
if (pawn == null) {
return;
}
move(pawn, movement);
}
public void move(IPawn pawn, PawnMovement movement) {
int toRow = pawn.getRow() + pawn.getDirection();
int toCol = pawn.getCol() + movement.getMove();
board[pawn.getRow()][pawn.getCol()] = null;
board[toRow][toCol] = (Pawn) pawn;
pawn.move(movement);
}
public Player getPlayer() {
return player;
}
public Pawn[][] getBoard() {
return board;
}
public static void printBoard(Pawn[][] board) {
for (int i = 7; i >= 0; i--) {
for (int j = 0; j < board.length; j++) {
if (board[i][j] != null) {
System.out.print(board[i][j] + " | ");
} else {
System.out.print(" | ");
}
}
System.out.println();
System.out.println("----------------------------------------------------------------------------------------");
}
}
}

View File

@ -0,0 +1,99 @@
package laboratoire4.game;
import laboratoire4.IPawn;
import laboratoire4.Player;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.SimulatedPawn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
import java.util.function.Predicate;
public class SimulatedGame implements Game {
private final SimulatedPawn[][] board;
private final Player player;
private final Stack<SimulatedPawn> removedPawns = new Stack<>();
private final Stack<SimulatedPawn> previousMovedPawns = new Stack<>();
public SimulatedGame(IPawn[][] board, Player player) {
this.board = asMovingPawns(board);
this.player = player;
}
public void move(IPawn pawn, PawnMovement movement) {
int toRow = pawn.getRow() + pawn.getDirection();
int toCol = pawn.getCol() + movement.getMove();
SimulatedPawn capturedPawn = board[toRow][toCol];
previousMovedPawns.push((SimulatedPawn) pawn);
removedPawns.push(capturedPawn);
board[pawn.getRow()][pawn.getCol()] = null;
board[toRow][toCol] = (SimulatedPawn) pawn;
pawn.move(movement);
}
public void revertMove() {
SimulatedPawn pawn = previousMovedPawns.pop();
SimulatedPawn capturedPawn = removedPawns.pop();
board[pawn.getRow()][pawn.getCol()] = capturedPawn;
pawn.revertMove();
board[pawn.getRow()][pawn.getCol()] = pawn;
}
public SimulatedPawn getNextPawn(IPawn pawn, PawnMovement move) {
int nextRow = pawn.getRow() + pawn.getDirection();
int nextCol = pawn.getCol() + move.getMove();
if (nextCol < 0 || nextCol > 7) {
return null;
}
return board[nextRow][nextCol];
}
public SimulatedPawn[][] getBoard() {
return board;
}
public Player getPlayer() {
return player;
}
public boolean hasCaptured() {
return removedPawns.peek() != null;
}
private Collection<SimulatedPawn> getPawns(Predicate<SimulatedPawn> predicate) {
List<SimulatedPawn> pawns = new ArrayList<>();
for (SimulatedPawn[] row : board) {
for (SimulatedPawn pawn : row) {
if (pawn != null && predicate.test(pawn)) {
pawns.add(pawn);
}
}
}
return pawns;
}
public static SimulatedPawn[][] asMovingPawns(IPawn[][] board) {
SimulatedPawn[][] to = new SimulatedPawn[board.length][board.length];
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board.length; col++) {
if (board[row][col] == null) {
continue;
}
SimulatedPawn pawn = SimulatedPawn.from(board[row][col]);
to[row][col] = pawn;
}
}
return to;
}
}

View File

@ -0,0 +1,79 @@
package laboratoire4.pawns;
import laboratoire4.IPawn;
import laboratoire4.Player;
public abstract class Pawn implements IPawn {
protected final Player player;
protected int row;
protected int col;
public Pawn(Player player, int row, int col) {
this.player = player;
this.row = row;
this.col = col;
}
public void move(PawnMovement movement) {
setRow(row + player.getDirection());
setCol(col + movement.getMove());
}
public String getPosition() {
char colStr = (char) (col + 65);
return String.format("%s%d", colStr, row + 1);
}
public Player getPlayer() {
return player;
}
public int getCol() {
return col;
}
public int getRow() {
return row;
}
public void setCol(int col) {
this.col = col;
}
public void setRow(int row) {
this.row = row;
}
public int getDirection() {
return player.getDirection();
}
public boolean isMoveValid(IPawn[][] board, PawnMovement movement) {
return isMoveValid(board, movement, false);
}
public boolean isMoveValid(IPawn[][] board, PawnMovement movement, boolean ignorePlayers) {
return isMoveValid(board, movement, row, col, ignorePlayers);
}
public boolean isMoveValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol) {
return isMoveValid(board, movement, fromRow, fromCol, false);
}
@Override
public boolean isMoveValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol, boolean ignorePlayers) {
int nextRow = fromRow + getDirection();
if (nextRow < 0 || nextRow >= board.length) {
return false;
}
int nextCol = fromCol + movement.getMove();
if (nextCol < 0 || nextCol >= board.length) {
return false;
}
return isMoveReallyValid(board, movement, fromRow, fromCol, ignorePlayers);
}
protected abstract boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol, boolean ignorePlayers);
}

View File

@ -0,0 +1,31 @@
package laboratoire4.pawns;
public enum PawnMovement {
STRAIGHT(0),
LEFT_DIAGONAL(-1),
RIGHT_DIAGONAL(1);
private final int move;
PawnMovement(int move) {
this.move = move;
}
public int getMove() {
return move;
}
public PawnMovement getOpposite() {
return from(move * -1);
}
public static PawnMovement from(int move) {
if (move == 0) {
return STRAIGHT;
}
if (move == 1) {
return RIGHT_DIAGONAL;
}
return LEFT_DIAGONAL;
}
}

View File

@ -0,0 +1,185 @@
package laboratoire4.pawns;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.Player;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import java.awt.*;
import java.util.List;
import java.util.Queue;
import java.util.*;
public class PawnUtils {
public static int distanceFromGoal(IPawn pawn) {
return Math.abs(pawn.getPlayer().getGoal() - pawn.getRow());
}
public static int distanceFromHome(IPawn pawn) {
return Math.abs(pawn.getRow() - pawn.getPlayer().getHome());
}
public static boolean areSamePlayers(IPawn a, IPawn b) {
return a != null && b != null && a.getPlayer() == b.getPlayer();
}
public static boolean hasEasyWinPath(IPawn[][] board, IPawn initialPawn) {
Queue<Point> positionsToVisit = new ArrayDeque<>();
positionsToVisit.add(new Point(initialPawn.getRow(), initialPawn.getCol()));
while (!positionsToVisit.isEmpty()) {
Point pos = positionsToVisit.poll();
for (PawnMovement movement : PawnMovement.values()) {
if (!initialPawn.isMoveValid(board, movement, pos.x, pos.y, false)) {
continue;
}
int nextRow = pos.x + initialPawn.getDirection();
int nextCol = pos.y + movement.getMove();
IPawn pawn = board[nextRow][nextCol];
if (pawn != null && pawn.getPlayer() != initialPawn.getPlayer() && pawn.isMoveValid(board, movement.getOpposite())) {
// On peut se faire capturer en allant !
continue;
}
if (nextRow == initialPawn.getPlayer().getGoal()) {
return true;
}
positionsToVisit.add(new Point(nextRow, nextCol));
}
}
return false;
}
public static boolean canBeCaptured(Game game, IPawn pawn) {
for (PawnMovement movement : PawnMovement.values()) {
int nextRow = pawn.getRow() + pawn.getDirection();
int nextCol = pawn.getCol() + movement.getMove();
if (nextRow < 0 || nextRow > 7 || nextCol < 0 || nextCol > 7) {
continue;
}
IPawn nearPawn = game.getBoard()[nextRow][nextCol];
if (nearPawn == null || PawnUtils.areSamePlayers(pawn, nearPawn)) {
continue;
}
if (nearPawn.isMoveValid(game.getBoard(), movement.getOpposite())) {
return true;
}
}
return false;
}
public static boolean canBeCaptured(Game game, int row, int col, Player player) {
for (PawnMovement movement : PawnMovement.values()) {
int nextRow = row + player.getDirection();
int nextCol = col + movement.getMove();
if (nextRow < 0 || nextRow > 7 || nextCol < 0 || nextCol > 7) {
continue;
}
IPawn nearPawn = game.getBoard()[nextRow][nextCol];
if (nearPawn == null || player == nearPawn.getPlayer()) {
continue;
}
if (nearPawn.isMoveValid(game.getBoard(), movement.getOpposite(), true)) {
return true;
}
}
return false;
}
public static boolean canCapture(Game game, IPawn max, IPawn min) {
int rowDistance = Math.abs(min.getRow() - max.getRow());
int colDistance = Math.abs(min.getCol() - max.getCol());
if (colDistance > rowDistance) {
// Le pion ne pourra jamais être atteint
return false;
}
return PawnUtils.canCapture(game, max, min, max.getRow(), max.getCol(), 0, rowDistance);
}
public static boolean canCapture(Game game, IPawn max, IPawn min, int row, int col, int depth, int maxDepth) {
if (min.getRow() == row && min.getCol() == col) {
return true;
}
if (depth >= maxDepth) {
return false;
}
for (PawnMovement movement : PawnMovement.values()) {
if (max.isMoveValid(game.getBoard(), movement, row, col, false)) {
int nextRow = row + max.getDirection();
int nextCol = col + movement.getMove();
if (canCapture(game, max, min, nextRow, nextCol, depth + 1, maxDepth)) {
return true;
}
}
}
return false;
}
public static <T extends IPawn> Collection<Action<T>> getActions(Game game, boolean max) {
return getActions(game, max, true, false);
}
public static <T extends IPawn> Collection<Action<T>> getActions(Game game, boolean max, boolean excludeDefense, boolean excludeDanger) {
List<Action<T>> actions = new ArrayList<>();
PawnMovement[] movements = PawnMovement.values();
//noinspection unchecked
Collection<T> pawns = (Collection<T>) (max ?
GameUtils.getMaxPawns(game.getBoard(), game.getPlayer()) :
GameUtils.getMinPawns(game.getBoard(), game.getPlayer()));
for (T pawn : pawns) {
if (excludeDefense && pawn.getRow() == pawn.getPlayer().getHome()) {
int col = pawn.getCol();
// Si possible, on ne bouge pas ces pushers, comme ça on a une défense d'urgence totale
if (col == 1 || col == 2 || col == 5 || col == 6) {
continue;
}
}
for (PawnMovement movement : movements) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
if (excludeDanger && PawnUtils.canBeCaptured(game, pawn.getRow() + pawn.getDirection(), pawn.getCol() + movement.getMove(), pawn.getPlayer())) {
continue;
}
actions.add(new Action<>(pawn, movement));
}
}
}
if (actions.isEmpty()) {
if (excludeDanger && excludeDefense) {
return getActions(game, max, true, false);
}
if (excludeDefense) {
return getActions(game, max, false, false);
}
}
Collections.shuffle(actions);
return actions;
}
}

View File

@ -0,0 +1,39 @@
package laboratoire4.pawns;
import laboratoire4.IPawn;
import laboratoire4.Player;
public class Pushed extends Pawn {
public Pushed(Player player, int row, int col) {
super(player, row, col);
}
@Override
public boolean isPusher() {
return false;
}
@Override
public boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol, boolean ignorePlayers) {
int direction = getDirection();
IPawn pusher = null;
IPawn to = board[fromRow + direction][fromCol + movement.getMove()];
if (fromCol > 0 && movement == PawnMovement.RIGHT_DIAGONAL) {
pusher = board[fromRow - direction][fromCol - movement.getMove()];
} else if (fromCol < board.length - 1 && movement == PawnMovement.LEFT_DIAGONAL) {
pusher = board[fromRow - direction][fromCol - movement.getMove()];
} else if (movement == PawnMovement.STRAIGHT) {
pusher = board[fromRow - direction][fromCol];
}
boolean pusherValid = pusher != null && pusher.isPusher() && pusher.getPlayer() == player;
boolean destinationValid = to == null || (movement != PawnMovement.STRAIGHT && (ignorePlayers || to.getPlayer() != this.player));
return pusherValid && destinationValid;
}
@Override
public String toString() {
return String.format("Pushed{%s, %s}", player, getPosition());
}
}

View File

@ -0,0 +1,35 @@
package laboratoire4.pawns;
import laboratoire4.IPawn;
import laboratoire4.Player;
public class Pusher extends Pawn {
public Pusher(Player player, int row, int col) {
super(player, row, col);
}
@Override
public boolean isPusher() {
return true;
}
@Override
public boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol, boolean ignorePlayers) {
IPawn to = board[fromRow + getDirection()][fromCol + movement.getMove()];
if (to == null) {
return true;
}
if (!ignorePlayers && to.getPlayer() == player) {
return false;
}
return movement != PawnMovement.STRAIGHT;
}
@Override
public String toString() {
return String.format("Pusher{%s, %s}", player, getPosition());
}
}

View File

@ -0,0 +1,19 @@
package laboratoire4.pawns;
import laboratoire4.IPawn;
public interface SimulatedPawn extends IPawn {
void revertMove();
int getMoveCount();
int getOriginalRow();
int getOriginalCol();
static SimulatedPawn from(IPawn pawn) {
if (pawn instanceof Pusher) {
return new SimulatedPusher(pawn.getPlayer(), pawn.getRow(), pawn.getCol());
}
return new SimulatedPushed(pawn.getPlayer(), pawn.getRow(), pawn.getCol());
}
}

View File

@ -0,0 +1,49 @@
package laboratoire4.pawns;
import laboratoire4.Player;
import java.util.Stack;
public class SimulatedPushed extends Pushed implements SimulatedPawn {
private final int originalRow;
private final int originalCol;
private final Stack<PawnMovement> previousMoves = new Stack<>();
private int moveCount = 0;
public SimulatedPushed(Player player, int row, int col) {
super(player, row, col);
originalRow = row;
originalCol = col;
}
@Override
public void move(PawnMovement movement) {
super.move(movement);
previousMoves.push(movement);
moveCount++;
}
@Override
public void revertMove() {
PawnMovement move = previousMoves.pop();
setRow(row - getDirection());
setCol(col - move.getMove());
moveCount--;
}
@Override
public int getOriginalRow() {
return originalRow;
}
@Override
public int getOriginalCol() {
return originalCol;
}
@Override
public int getMoveCount() {
return moveCount;
}
}

View File

@ -0,0 +1,49 @@
package laboratoire4.pawns;
import laboratoire4.Player;
import java.util.Stack;
public class SimulatedPusher extends Pusher implements SimulatedPawn {
private final int originalRow;
private final int originalCol;
private final Stack<PawnMovement> previousMoves = new Stack<>();
private int moveCount = 0;
public SimulatedPusher(Player player, int row, int col) {
super(player, row, col);
originalRow = row;
originalCol = col;
}
@Override
public void move(PawnMovement movement) {
previousMoves.push(movement);
super.move(movement);
moveCount++;
}
@Override
public void revertMove() {
PawnMovement move = previousMoves.pop();
setRow(row - getDirection());
setCol(col - move.getMove());
moveCount--;
}
@Override
public int getOriginalRow() {
return originalRow;
}
@Override
public int getOriginalCol() {
return originalCol;
}
@Override
public int getMoveCount() {
return moveCount;
}
}

View File

@ -0,0 +1,136 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.PawnUtils;
import laboratoire4.pawns.SimulatedPawn;
import java.util.Collection;
public class AttackStrategy extends MiniMaxStrategy {
private static final int ATTACK_DISTANCE_FROM_GOAL = 4;
private long originalMinPushedCount;
private long originalMinPusherCount;
private long originalMaxPusherCount;
protected AttackStrategy(Game game) {
super(game);
}
@Override
public EvaluationResult getNextMove() {
originalMinPushedCount = GameUtils.getMinPawnsAsStream(game.getBoard(), game.getPlayer()).filter(p -> !p.isPusher()).count();
originalMinPusherCount = GameUtils.getMinPawnsAsStream(game.getBoard(), game.getPlayer()).filter(IPawn::isPusher).count();
originalMaxPusherCount = GameUtils.getMaxPawnsAsStream(game.getBoard(), game.getPlayer())
.filter(IPawn::isPusher)
.count();
return super.getNextMove();
}
@Override
protected int evaluateSimulation() {
int score = 0;
if (getMinPawns().stream().anyMatch(p -> p.getRow() == p.getPlayer().getGoal())) {
return Integer.MIN_VALUE;
}
// region Pions max perdus
Collection<SimulatedPawn> maxPawns = getMaxPawns();
long pusherCount = maxPawns.stream().filter(IPawn::isPusher).count();
score -= Math.pow((originalMaxPusherCount - pusherCount) * 10, 2);
// endregion
// region Pions min capturés
long minPusherCount = getMinPawns().stream().filter(IPawn::isPusher).count();
long minPushedCount = getMinPawns().stream().filter(p -> !p.isPusher()).count();
score += (originalMinPushedCount - minPushedCount) * 5;
score += (originalMinPusherCount - minPusherCount) * 10;
// endregion
for (SimulatedPawn pawn : maxPawns) {
int distanceFromGoal = PawnUtils.distanceFromGoal(pawn);
if (distanceFromGoal <= ATTACK_DISTANCE_FROM_GOAL) {
score += ATTACK_DISTANCE_FROM_GOAL - distanceFromGoal + 1;
if (PawnUtils.hasEasyWinPath(game.getBoard(), pawn)) {
score += Integer.MAX_VALUE / distanceFromGoal;
}
}
int row = pawn.getRow();
int col = pawn.getCol();
if (row > 0 && row < 7) {
IPawn facingPawn = game.getBoard()[row + pawn.getDirection()][col];
if (facingPawn != null && !PawnUtils.areSamePlayers(pawn, facingPawn)) {
// On bloque potentiellement un pion adverse, qui ne peut pas nous attaquer
score += 10;
}
}
// if (col > 0) {
// IPawn leftPawn = game.getBoard()[row][col - 1];
// if (leftPawn != null && !PawnUtils.areSamePlayers(pawn, leftPawn)) {
// score += 5;
// }
// }
// if (col < 7) {
// IPawn rightPawn = game.getBoard()[row][col + 1];
// if (rightPawn != null && !PawnUtils.areSamePlayers(pawn, rightPawn)) {
// score += 5;
// }
// }
}
return score;
}
@Override
public int getWeight() {
Collection<IPawn> maxPawns = GameUtils.getMaxPawns(game.getBoard(), game.getPlayer());
int maxWeight = 0;
for (IPawn pawn : maxPawns) {
int weight = 0;
int distanceFromGoal = PawnUtils.distanceFromGoal(pawn);
if (distanceFromGoal <= 1) {
return WEIGHT_MAX;
}
if (distanceFromGoal == 2) {
if (PawnUtils.hasEasyWinPath(game.getBoard(), pawn)) {
weight = 8;
} else {
weight = 5;
}
}
if (distanceFromGoal > 2 && distanceFromGoal <= ATTACK_DISTANCE_FROM_GOAL) {
weight = 2;
}
if (weight > maxWeight) {
maxWeight = weight;
}
}
return maxWeight;
}
private boolean cantBeCaptured(Action<IPawn> action) {
IPawn pawn = action.getPawn();
PawnMovement movement = action.getMovement();
int row = pawn.getRow() + pawn.getDirection();
int col = pawn.getCol() + movement.getMove();
return !PawnUtils.canBeCaptured(game, row, col, pawn.getPlayer());
}
}

View File

@ -0,0 +1,178 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.PawnUtils;
import laboratoire4.pawns.SimulatedPawn;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class DefenseStrategy extends MiniMaxStrategy {
private static final int DEFENSE_DISTANCE_FROM_HOME = 2;
private static final int PAWN_DEFENSE_DISTANCE = 3;
private final Map<IPawn, Integer> dangerousPawns;
public DefenseStrategy(Game game) {
super(game);
dangerousPawns = getDangerousPawns(game);
}
@Override
public EvaluationResult getNextMove() {
if (dangerousPawns.isEmpty()) {
return null;
}
return super.getNextMove();
}
@Override
public int getWeight() {
if (dangerousPawns.isEmpty()) {
return 0;
}
Collection<IPawn> minPawns = GameUtils.getMinPawns(game.getBoard(), game.getPlayer());
Collection<IPawn> maxPawns = GameUtils.getMaxPawns(game.getBoard(), game.getPlayer());
int maxWeight = 0;
for (IPawn pawn : minPawns) {
int weight = 0;
int distanceFromGoal = PawnUtils.distanceFromGoal(pawn);
if (distanceFromGoal <= 2) {
if (PawnUtils.hasEasyWinPath(game.getBoard(), pawn)) {
if (maxPawns.stream().noneMatch(p -> PawnUtils.canCapture(game, p, pawn))) {
// Le pion ne peut pas être arrêté, on doit attaquer !
return 0;
} else {
return WEIGHT_MAX;
}
} else {
weight = 5;
}
}
if (distanceFromGoal > 2 && distanceFromGoal <= DEFENSE_DISTANCE_FROM_HOME) {
weight = 2;
}
if (weight > maxWeight) {
maxWeight = weight;
}
}
return maxWeight;
}
@Override
protected int evaluateSimulation() {
int score = 0;
Collection<SimulatedPawn> maxPawns = getMaxPawns();
Collection<SimulatedPawn> minPawns = getMinPawns();
for (SimulatedPawn pawn : minPawns) {
for (PawnMovement movement : PawnMovement.values()) {
if (PawnUtils.canBeCaptured(game, pawn.getRow() + pawn.getDirection(), pawn.getCol() + movement.getMove(), pawn.getPlayer())) {
// Ce pion ennemi est aussi en danger s'il bouge !
score += 5;
}
}
}
for (IPawn dangerousPawn : dangerousPawns.keySet()) {
Optional<SimulatedPawn> simulated = GameUtils.getMinPawnsAsStream(game.getBoard(), game.getPlayer())
.filter(p -> p.getOriginalRow() == dangerousPawn.getRow() && p.getOriginalCol() == dangerousPawn.getCol())
.findFirst();
if (!simulated.isPresent()) {
// Le pion a été capturé !
score += Math.pow(dangerousPawns.get(dangerousPawn), 3);
} else {
// On est toujours en danger
int distance = PawnUtils.distanceFromGoal(simulated.get());
score -= Math.pow(DEFENSE_DISTANCE_FROM_HOME - distance + 1, 3);
for (SimulatedPawn pawn : maxPawns) {
if (PawnUtils.distanceFromHome(pawn) <= DEFENSE_DISTANCE_FROM_HOME) { // On ne veut pas que nos attaquants s'approchent des pions ennemis
score += PAWN_DEFENSE_DISTANCE - distanceFromCapture(pawn, simulated.get()) * 5;
}
}
}
}
for (SimulatedPawn pawn : maxPawns) {
if (PawnUtils.canBeCaptured(game, pawn)) {
score -= 5;
}
// On préfère les groupes de 2 pushers, puisqu'ils ont une meilleure surface de défense
if (!pawn.isPusher()) {
continue;
}
int col = pawn.getCol();
int row = pawn.getRow();
if (col > 0 && PawnUtils.areSamePlayers(pawn, game.getBoard()[row][col - 1])) {
score += 5;
}
if (col < 7 && PawnUtils.areSamePlayers(pawn, game.getBoard()[row][col + 1])) {
score += 5;
}
if (row > 0 && PawnUtils.areSamePlayers(pawn, game.getBoard()[row - 1][col])) {
score += 5;
}
if (row < 7 && PawnUtils.areSamePlayers(pawn, game.getBoard()[row + 1][col])) {
score += 5;
}
}
return score;
}
private int distanceFromCapture(IPawn max, IPawn min) {
int rowDistance = Math.abs(min.getRow() - max.getRow());
int colDistance = Math.abs(min.getCol() - max.getCol());
if (colDistance > rowDistance) {
// Le pion ne pourra jamais être atteint
return PAWN_DEFENSE_DISTANCE;
}
if (!PawnUtils.canCapture(game, max, min, max.getRow(), max.getCol(), 0, rowDistance)) {
return PAWN_DEFENSE_DISTANCE;
}
return rowDistance;
}
private static Map<IPawn, Integer> getDangerousPawns(Game game) {
Map<IPawn, Integer> dangerousPawns = new HashMap<>();
for (IPawn pawn : GameUtils.getMinPawns(game.getBoard(), game.getPlayer())) {
int distance = PawnUtils.distanceFromGoal(pawn);
if (distance <= DEFENSE_DISTANCE_FROM_HOME) {
// Les pions proches de nous qui ne peuvent pas bouger peuvent être ignorés
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
dangerousPawns.put(pawn, DEFENSE_DISTANCE_FROM_HOME - distance + 1);
break;
}
}
}
}
return dangerousPawns;
}
}

View File

@ -0,0 +1,27 @@
package laboratoire4.strategies;
import laboratoire4.pawns.PawnMovement;
public class EvaluationResult {
private final int row;
private final int col;
private final PawnMovement movement;
public EvaluationResult(int row, int col, PawnMovement movement) {
this.row = row;
this.col = col;
this.movement = movement;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public PawnMovement getMovement() {
return movement;
}
}

View File

@ -0,0 +1,68 @@
package laboratoire4.strategies;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.PawnUtils;
public class ImmediateDefenseStrategy implements Strategy {
private static final int IMM_DEFENSE_DISTANCE_FROM_HOME = 2;
private final Game game;
public ImmediateDefenseStrategy(Game game) {
this.game = game;
}
@Override
public EvaluationResult getNextMove() {
// On n'utilise pas la méthode utilitaire, car on veut bouger la ligne de défense si nécessaire
for (IPawn pawn : GameUtils.getMaxPawns(game.getBoard(), game.getPlayer())) {
if (PawnUtils.distanceFromHome(pawn) > IMM_DEFENSE_DISTANCE_FROM_HOME) {
continue;
}
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
int nextRow = pawn.getRow() + pawn.getDirection();
int nextCol = pawn.getCol() + movement.getMove();
IPawn nextPawn = game.getBoard()[nextRow][nextCol];
if (nextPawn == null || PawnUtils.areSamePlayers(pawn, nextPawn)) {
continue;
}
return new EvaluationResult(pawn.getRow(), pawn.getCol(), movement);
}
}
}
return null;
}
@Override
public int getWeight() {
for (IPawn pawn : GameUtils.getMaxPawns(game.getBoard(), game.getPlayer())) {
if (PawnUtils.distanceFromHome(pawn) > IMM_DEFENSE_DISTANCE_FROM_HOME) {
continue;
}
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
int nextRow = pawn.getRow() + pawn.getDirection();
int nextCol = pawn.getCol() + movement.getMove();
IPawn nextPawn = game.getBoard()[nextRow][nextCol];
if (nextPawn == null || PawnUtils.areSamePlayers(pawn, nextPawn)) {
continue;
}
return WEIGHT_MAX;
}
}
}
return 0;
}
}

View File

@ -0,0 +1,71 @@
package laboratoire4.strategies;
import laboratoire4.game.Game;
public class MasterStrategy implements Strategy {
private static Strategy instance;
public static Strategy getInstance(Game game) {
if (instance == null) {
instance = new MasterStrategy(game);
}
return instance;
}
private final Game game;
private MasterStrategy(Game game) {
this.game = game;
}
@Override
public EvaluationResult getNextMove() {
long startMs = System.currentTimeMillis();
Strategy strategy = getBestStrategy();
System.out.println(strategy.getClass().getSimpleName());
EvaluationResult result = strategy.getNextMove();
long endMs = System.currentTimeMillis();
System.out.printf("Temps: %d ms\n", endMs - startMs);
return result;
}
private Strategy getBestStrategy() {
Strategy[] strategies = new Strategy[]{
new ImmediateDefenseStrategy(game),
new WinningStrategy(game),
new DefenseStrategy(game),
new AttackStrategy(game)
};
int maxWeight = 0;
Strategy bestStrategy = null;
for (Strategy strategy : strategies) {
int weight = strategy.getWeight();
if (weight == WEIGHT_MAX) {
return strategy;
}
if (weight > maxWeight) {
maxWeight = weight;
bestStrategy = strategy;
}
}
if (maxWeight > 0) {
return bestStrategy;
}
return new RandomStrategy(game);
}
@Override
public int getWeight() {
return WEIGHT_MAX;
}
}

View File

@ -0,0 +1,172 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.game.SimulatedGame;
import laboratoire4.pawns.Pawn;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.PawnUtils;
import laboratoire4.pawns.SimulatedPawn;
import java.util.Collection;
import java.util.stream.Collectors;
public abstract class MiniMaxStrategy implements Strategy {
private static final int MAX_DEPTH = 6;
private static final long MAX_TIME_MS = 4800; // Moins de 5 secondes, car le tour n'est pas complètement fini
protected SimulatedGame game;
protected long startTimeMs;
protected MiniMaxStrategy(Game game) {
this.game = new SimulatedGame(game.getBoard(), game.getPlayer());
}
@Override
public EvaluationResult getNextMove() {
return miniMax();
}
private EvaluationResult miniMax() {
EvaluationResult maxResult = null;
int maxScore = Integer.MIN_VALUE;
startTimeMs = System.currentTimeMillis();
for (Action<IPawn> action : getActions(true)) {
int score = min(0, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (maxResult == null || score > maxScore) {
IPawn pawn = action.getPawn();
maxResult = new EvaluationResult(pawn.getRow(), pawn.getCol(), action.getMovement());
}
}
return maxResult;
}
private int max(int depth, int alpha, int beta) {
if (depth >= MAX_DEPTH) {
return evaluate();
}
if (System.currentTimeMillis() - startTimeMs > MAX_TIME_MS) {
return Integer.MIN_VALUE;
}
int maxScore = Integer.MIN_VALUE;
for (Action<IPawn> action : getActions(true)) {
IPawn pawn = action.getPawn();
PawnMovement movement = action.getMovement();
game.move(pawn, movement);
int score = min(depth + 1, Math.max(alpha, maxScore), beta);
game.revertMove();
if (score > maxScore) {
maxScore = score;
}
if (maxScore >= beta) {
return maxScore;
}
}
return maxScore;
}
private int min(int depth, int alpha, int beta) {
if (depth >= MAX_DEPTH) {
return evaluate();
}
if (System.currentTimeMillis() - startTimeMs > MAX_TIME_MS) {
return Integer.MAX_VALUE;
}
int minScore = Integer.MAX_VALUE;
for (Action<IPawn> action : getActions(false)) {
IPawn pawn = action.getPawn();
PawnMovement movement = action.getMovement();
game.move(pawn, movement);
int score = max(depth + 1, alpha, Math.min(beta, minScore));
game.revertMove();
if (score < minScore) {
minScore = score;
}
if (minScore <= alpha) {
return minScore;
}
}
return minScore;
}
protected Collection<SimulatedPawn> getMaxPawns() {
return GameUtils.getMaxPawns(game.getBoard(), game.getPlayer());
}
protected Collection<SimulatedPawn> getMinPawns() {
return GameUtils.getMinPawns(game.getBoard(), game.getPlayer());
}
protected Collection<Action<IPawn>> getActions(boolean max) {
return PawnUtils.getActions(game, max);
}
private int evaluate() {
int score = evaluateSimulation();
Collection<SimulatedPawn> maxPawns = getMaxPawns();
if (maxPawns.stream().noneMatch(IPawn::isPusher)) {
return Integer.MIN_VALUE;
}
if (getMinPawns().stream().anyMatch(p -> p.getRow() == p.getPlayer().getGoal())) {
return Integer.MIN_VALUE;
}
for (IPawn pawn : maxPawns) {
if (pawn.getRow() == pawn.getPlayer().getGoal()) {
return Integer.MAX_VALUE;
}
if (PawnUtils.canBeCaptured(game, pawn)) {
// On peut être capturé
score -= 10;
}
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement) && !PawnUtils.canBeCaptured(game, pawn.getRow() + pawn.getDirection(), pawn.getCol() + movement.getMove(), pawn.getPlayer())) {
// On ne peut pas être capturé en faisant ce mouvement
score += 5;
}
}
}
// region Lignes ennemies
for (IPawn pawn : getMinPawns()) {
int row = pawn.getRow();
int col = pawn.getCol();
if (row > 0 && PawnUtils.areSamePlayers(pawn, game.getBoard()[row - 1][col])) {
score -= 5;
}
if (row < 0 && PawnUtils.areSamePlayers(pawn, game.getBoard()[row + 1][col])) {
score -= 5;
}
}
// endregion
return score;
}
protected abstract int evaluateSimulation();
}

View File

@ -0,0 +1,58 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* Au début du jeu, il n'y a pas d'attaques, de défense ou de possibilités de gagner (pas besoin de minimax).
* Alors, on avance quelques pions (pas tous) aléatoirement.
*/
public class RandomStrategy implements Strategy {
private static final int PAWN_TO_MOVE = 2;
private final Random random = new Random();
private final Game game;
public RandomStrategy(Game game) {
this.game = game;
}
@Override
public EvaluationResult getNextMove() {
Collection<IPawn> outsideHomePushers = GameUtils.getMaxPawnsAsStream(game.getBoard(), game.getPlayer())
.filter(IPawn::isPusher)
.filter(p -> p.getRow() != p.getPlayer().getHome())
.filter(p -> p.getRow() != p.getPlayer().getHome() + p.getDirection())
.collect(Collectors.toList());
if (outsideHomePushers.size() >= PAWN_TO_MOVE) {
List<Action<IPawn>> validActions = Strategy.getValidActions(game.getBoard(), outsideHomePushers);
if (!validActions.isEmpty()) {
return getRandomMove(validActions);
}
}
List<Action<IPawn>> validActions = Strategy.getValidActions(game);
return getRandomMove(validActions);
}
@Override
public int getWeight() {
return 0;
}
private EvaluationResult getRandomMove(List<Action<IPawn>> actions) {
int randomIndex = random.nextInt(actions.size());
Action<IPawn> nextAction = actions.get(randomIndex);
IPawn nextPawn = nextAction.getPawn();
return new EvaluationResult(nextPawn.getRow(), nextPawn.getCol(), nextAction.getMovement());
}
}

View File

@ -0,0 +1,58 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.pawns.PawnMovement;
import java.util.*;
import java.util.stream.Collectors;
public interface Strategy {
int WEIGHT_MAX = 10;
EvaluationResult getNextMove();
int getWeight();
static List<Action<IPawn>> getValidActions(Game game) {
List<IPawn> maxPawns = Arrays.stream(game.getBoard())
.flatMap(Arrays::stream)
.filter(Objects::nonNull)
.filter(p -> p.getPlayer() == game.getPlayer())
.collect(Collectors.toList());
return getValidActions(game.getBoard(), maxPawns);
}
static List<Action<IPawn>> getValidActions(IPawn[][] board, Collection<IPawn> pawns) {
return getValidActions(board, pawns, true);
}
static List<Action<IPawn>> getValidActions(IPawn[][] board, Collection<IPawn> pawns, boolean excludeDefense) {
List<Action<IPawn>> validActions = new ArrayList<>();
for (IPawn pawn : pawns) {
if (excludeDefense && pawn.getRow() == pawn.getPlayer().getHome()) {
int col = pawn.getCol();
// Si possible, on ne bouge pas ces pushers, comme ça on a une défense d'urgence totale
if (col == 1 || col == 2 || col == 5 || col == 6) {
continue;
}
}
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(board, movement)) {
validActions.add(new Action<>(pawn, movement));
}
}
}
if (excludeDefense && validActions.isEmpty()) {
return getValidActions(board, pawns, false);
}
return validActions;
}
}

View File

@ -0,0 +1,107 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.PawnUtils;
import java.util.Collection;
import java.util.stream.Collectors;
public class WinningStrategy implements Strategy {
private Action<IPawn> winningAction;
public WinningStrategy(Game game) {
winningAction = findImmediateWinningAction(game);
if (winningAction == null) {
// On cherche un chemin on peut gagner quoi qu'il arrive
winningAction = findWinningPath(game);
}
}
@Override
public EvaluationResult getNextMove() {
if (winningAction == null) {
return null;
}
IPawn winningPawn = winningAction.getPawn();
return new EvaluationResult(winningPawn.getRow(), winningPawn.getCol(), winningAction.getMovement());
}
@Override
public int getWeight() {
if (winningAction != null) {
return WEIGHT_MAX;
}
return 0;
}
private static Action<IPawn> findImmediateWinningAction(Game game) {
Collection<IPawn> nearPawns = GameUtils.getMaxPawnsAsStream(game.getBoard(), game.getPlayer())
.filter(p -> p.getRow() + p.getDirection() == p.getPlayer().getGoal())
.collect(Collectors.toList());
for (IPawn pawn : nearPawns) {
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
return new Action<>(pawn, movement);
}
}
}
return null;
}
private Action<IPawn> findWinningPath(Game game) {
for (IPawn pawn : GameUtils.getMaxPawns(game.getBoard(), game.getPlayer())) {
if (PawnUtils.distanceFromGoal(pawn) > 4) {
continue;
}
for (PawnMovement movement : PawnMovement.values()) {
int nextRow = pawn.getRow() + pawn.getDirection();
int nextCol = pawn.getCol() + movement.getMove();
if (pawn.isMoveValid(game.getBoard(), movement) &&
!PawnUtils.canBeCaptured(game, nextRow, nextCol, game.getPlayer()) &&
hasWinningPath(game, pawn, nextRow, nextCol)) {
return new Action<>(pawn, movement);
}
}
}
return null;
}
private boolean hasWinningPath(Game game, IPawn initialPawn, int row, int col) {
if (row == initialPawn.getPlayer().getGoal()) {
return true;
}
for (PawnMovement movement : PawnMovement.values()) {
int nextRow = row + initialPawn.getDirection();
int nextCol = col + movement.getMove();
if (!initialPawn.isMoveValid(game.getBoard(), movement, row, col)) {
continue;
}
IPawn nextPawn = game.getBoard()[nextRow][nextCol];
if (nextPawn == null && hasWinningPath(game, initialPawn, nextRow, nextCol)) {
return true;
}
if (nextPawn != null && !PawnUtils.areSamePlayers(initialPawn, nextPawn) &&
!PawnUtils.canBeCaptured(game, row, col, initialPawn.getPlayer()) &&
hasWinningPath(game, initialPawn, nextRow, nextCol)) {
return true;
}
}
return false;
}
}