Beaucoup de changements

This commit is contained in:
william 2023-04-09 13:26:32 -04:00
parent 5f7d74e0ae
commit 648c325649
30 changed files with 948 additions and 537 deletions

View File

@ -4,15 +4,36 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="41395b4b-3252-492c-a869-5f4dab107186" name="Changes" comment="Fixes?">
<list default="true" id="41395b4b-3252-492c-a869-5f4dab107186" name="Changes" comment="Plus d'heuristiques">
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/Action.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/game/Game.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/game/GameUtils.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/PawnMovement.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/PawnUtils.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/AttackStrategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/DefenseStrategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/EvaluationResult.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/ImmediateDefenseStrategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/MasterStrategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/MiniMaxStrategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/StartingStrategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/Strategy.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/strategies/WinningStrategy.java" 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/BoardEvaluator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/BoardEvaluator.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/BoardEvaluator.java" beforeDir="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$/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/MovingBoard.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingBoard.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPawn.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPawn.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPushed.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPushed.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPusher.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPusher.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/GameTree.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/GameTree.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/IPawn.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/IPawn.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MiniMax.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MiniMaxResult.java" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingBoard.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/game/SimulatedGame.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPawn.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/SimulatedPawn.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPushed.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/SimulatedPushed.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/MovingPusher.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/SimulatedPusher.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/Pawn.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/Pawn.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/Pushed.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/Pushed.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/Pusher.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/pawns/Pusher.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/PusherBoard.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/game/PusherGame.java" afterDir="false" />
</list>
<list id="98b8a79f-2f53-42bf-96da-7af322697a0d" name="Changes by acastonguay" comment="" />
<option name="SHOW_DIALOG" value="false" />
@ -74,7 +95,7 @@
<module name="Lab4" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="laboratoire4.*" />
<option name="PATTERN" value="laboratoire4.pawns.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
@ -87,7 +108,7 @@
<module name="Lab4" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="laboratoire4.*" />
<option name="PATTERN" value="laboratoire4.pawns.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
@ -100,7 +121,7 @@
<module name="Lab4" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="laboratoire4.*" />
<option name="PATTERN" value="laboratoire4.pawns.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
@ -135,6 +156,10 @@
<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="16558000" />
</task>
<task id="LOCAL-00001" summary="MiniMax">
<created>1679263366439</created>
@ -171,7 +196,14 @@
<option name="project" value="LOCAL" />
<updated>1679786894920</updated>
</task>
<option name="localTasksCounter" value="6" />
<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>
<option name="localTasksCounter" value="7" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@ -182,7 +214,8 @@
<MESSAGE value="Movement logic" />
<MESSAGE value="Small fixes" />
<MESSAGE value="Fixes?" />
<option name="LAST_COMMIT_MESSAGE" value="Fixes?" />
<MESSAGE value="Plus d'heuristiques" />
<option name="LAST_COMMIT_MESSAGE" value="Plus d'heuristiques" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>

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,230 +0,0 @@
package laboratoire4;
import java.util.Collection;
public class BoardEvaluator {
public static int evaluate(MovingBoard game) {
Collection<MovingPawn> maxPawns = game.getMaxPawns();
Collection<MovingPawn> minPawns = game.getMinPawns();
return evaluatePlayer(game, maxPawns, minPawns, true) - evaluatePlayer(game, minPawns, maxPawns, false);
}
//region Full Board
private static int evaluatePlayer(MovingBoard game, Collection<MovingPawn> maxPawns, Collection<MovingPawn> minPawns, boolean max) {
if (minPawns.stream().noneMatch(IPawn::isPusher)) {
return 50;
}
int score = evaluateMaxPawnCountScore(maxPawns, minPawns);
for (MovingPawn pawn : maxPawns) {
score += evaluatePawn(game, pawn);
if (max) {
score += strategy(game, pawn);
}
}
return score;
}
private static int evaluateMaxPawnCountScore(Collection<MovingPawn> maxPawns, Collection<MovingPawn> minPawns) {
return evaluatePawnCountScore(minPawns) - evaluatePawnCountScore(maxPawns);
}
// Augmente beaucoup le score à partir de 10 pions capturés
// f(x) = 0.001x^4, f(1) = 0, f(10) = 10, f(16) = 65.54
private static int evaluatePawnCountScore(Collection<MovingPawn> pawns) {
int capturedPusherCount = (int) (8 - pawns.stream().filter(IPawn::isPusher).count());
return (int) (Math.pow(capturedPusherCount, 2));
}
//endregion
//region Board
private static int evaluatePawn(MovingBoard game, MovingPawn pawn) {
if (pawn.getRow() == pawn.getPlayer().getGoal()) {
return 50;
}
// Favorise les pièces qui peuvent gagner au prochain tour
if (pawn.getRow() + pawn.getDirection() == pawn.getPlayer().getGoal()) {
return 25;
}
int score = 0;
score += evaluateHomePawnScore(pawn);
// score += evaluatePushedScore(pawn);
score += evaluatePusherScore(game, pawn);
score += evaluateValidMovesScore(game, pawn);
score += evaluateAttackScore(game, pawn);
score += evaluateDefenseScore(game, pawn);
if (Math.abs(pawn.getPlayer().getGoal() - pawn.getRow()) < 4) {
score += hasEasyWinPath(game, pawn, pawn.getRow(), pawn.getCol(), 0);
}
return score;
}
private static int evaluateHomePawnScore(MovingPawn pawn) {
return pawn.getRow() == pawn.getPlayer().getHome() ? 1 : 0;
}
private static int evaluatePushedScore(MovingPawn pawn) {
return pawn.isPusher() ? 0 : 1;
}
private static int evaluatePusherScore(MovingBoard board, MovingPawn pawn) {
if (!pawn.isPusher()) return 0;
int score = 0;
for (Pawn.PawnMovement movement : Pawn.PawnMovement.values()) {
MovingPawn nearPawn = board.getNextPawn(pawn, movement);
if (MovingPawn.areSamePlayers(pawn, nearPawn) && !nearPawn.isPusher()) {
score++;
}
}
return score;
}
private static int evaluateValidMovesScore(MovingBoard game, MovingPawn pawn) {
int score = 0;
for (Pawn.PawnMovement movement : Pawn.PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
score++;
}
}
return score * pawn.getMoveCount();
}
private static int evaluateAttackScore(MovingBoard game, MovingPawn pawn) {
int score = 0;
Pawn.PawnMovement[] validAttackMoves = new Pawn.PawnMovement[]{Pawn.PawnMovement.LEFT_DIAGONAL, Pawn.PawnMovement.RIGHT_DIAGONAL};
for (Pawn.PawnMovement movement : validAttackMoves) {
MovingPawn nearPawn = game.getNextPawn(pawn, movement);
if (nearPawn != null && pawn.isMoveValid(game.getBoard(), movement) && !MovingPawn.areSamePlayers(pawn, nearPawn)) {
score++;
}
}
// Favorise les attaques de notre côté
if ((pawn.getPlayer() == Player.RED && pawn.getRow() < 4) ||
(pawn.getPlayer() == Player.BLACK && pawn.getRow() >= 4)) {
score++;
}
return score;
}
private static int evaluateDefenseScore(MovingBoard game, MovingPawn pawn) {
int score = 0;
Pawn.PawnMovement[] validAttackMoves = new Pawn.PawnMovement[]{Pawn.PawnMovement.LEFT_DIAGONAL, Pawn.PawnMovement.RIGHT_DIAGONAL};
for (Pawn.PawnMovement movement : validAttackMoves) {
MovingPawn nearPawn = game.getNextPawn(pawn, movement);
Pawn.PawnMovement oppositeMovement = Pawn.PawnMovement.from(movement.getMove() * -1);
if (nearPawn != null && !MovingPawn.areSamePlayers(pawn, nearPawn) && nearPawn.isMoveValid(game.getBoard(), oppositeMovement)) {
score -= 50;
}
}
return score;
}
private static int hasEasyWinPath(MovingBoard game, MovingPawn pawn, int row, int col, int depth) {
if (row == pawn.getPlayer().getGoal()) {
return 10;
}
int score = 0;
int nextRow = row + pawn.getDirection();
Pawn.PawnMovement[] preferredMoves = new Pawn.PawnMovement[]{Pawn.PawnMovement.LEFT_DIAGONAL, Pawn.PawnMovement.RIGHT_DIAGONAL};
for (Pawn.PawnMovement movement : preferredMoves) {
int nextCol = col + movement.getMove();
if (nextCol < 0 || nextCol > 7) continue;
MovingPawn nearPawn = game.getBoard()[nextRow][nextCol];
if (nearPawn == null) {
score += 2;
} else if (nearPawn.getPlayer() != pawn.getPlayer()) {
score++;
} else {
continue;
}
if (depth < 3) {
score += hasEasyWinPath(game, pawn, nextRow, pawn.getCol() + movement.getMove(), depth + 1);
}
}
return score;
}
//endregion
//region Strategy
private static int strategy(MovingBoard game, MovingPawn pawn) {
int score = 0;
score += defensiveStrategy(game, pawn);
return score;
}
private static int defensiveStrategy(MovingBoard game, MovingPawn pawn) {
int col = pawn.getCol();
int row = pawn.getRow();
if (col < 2 || col > 5) {
return 0;
}
MovingPawn leftPawn = game.getBoard()[row][col - 2];
MovingPawn rightPawn = game.getBoard()[row][col - 2];
int score = 0;
if (MovingPawn.areSamePlayers(leftPawn, pawn)) {
score++;
}
if (MovingPawn.areSamePlayers(rightPawn, pawn)) {
score++;
}
if (hasEnemyNear(game, pawn)) {
score += 100;
}
return score;
}
private static boolean hasEnemyNear(MovingBoard game, MovingPawn pawn) {
int row = pawn.getRow();
int col = pawn.getCol();
for (int i = 1; i <= 2; i++) {
int nextRow = row + i * pawn.getDirection();
if (nextRow < 0 || nextRow > 7) {
break;
}
for (int j = -1; j <= 1; j++) {
int nextCol = col + j;
if (nextCol < 0 || nextCol > 7) {
continue;
}
MovingPawn nearPawn = game.getBoard()[nextRow][nextCol];
if (nearPawn != null && !MovingPawn.areSamePlayers(pawn, nearPawn)) {
return true;
}
}
}
return false;
}
//endregion
}

View File

@ -1,5 +1,7 @@
package laboratoire4;
import laboratoire4.game.PusherGame;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
@ -9,7 +11,7 @@ public class Client {
private final Socket socket;
private final BufferedInputStream input;
private final BufferedOutputStream output;
private PusherBoard board;
private PusherGame board;
public Client(String host, int port) throws IOException {
socket = new Socket(host, port);
@ -60,7 +62,7 @@ public class Client {
String s = new String(aBuffer).trim();
String[] boardValues = s.split(" ");
board = new PusherBoard(player, boardValues);
board = new PusherGame(player, boardValues);
return player == Player.RED;
}
@ -79,7 +81,7 @@ public class Client {
// Thread.sleep(500);
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.flush();
@ -89,7 +91,7 @@ public class Client {
System.err.println("Mouvement invalide!");
Thread.sleep(500);
PusherBoard.printBoard(board.getBoard());
PusherGame.printBoard(board.getBoard());
// play();
}

View File

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

View File

@ -1,8 +1,11 @@
package laboratoire4;
import laboratoire4.pawns.PawnMovement;
public interface IPawn {
boolean isMoveValid(IPawn[][] board, Pawn.PawnMovement movement);
void move(Pawn.PawnMovement movement);
boolean isMoveValid(IPawn[][] board, PawnMovement movement);
boolean isMoveValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol);
void move(PawnMovement movement);
boolean isPusher();
int getDirection();
Player getPlayer();

View File

@ -1,134 +0,0 @@
package laboratoire4;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class MiniMax {
private static final int MAX_MAX_DEPTH = 6;
private static int MAX_DEPTH = 2;
public static MiniMaxResult miniMax(PusherBoard board) {
long startMillis = System.currentTimeMillis();
MovingBoard game = new MovingBoard(board.getBoard(), board.getPlayer());
Collection<Action> actions = getActions(game, true);
MiniMaxResult maxResult = null;
for (int i = 0; i <= MAX_MAX_DEPTH; i += 2) {
MAX_DEPTH = i;
for (Action action : actions) {
int score = min(game, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (maxResult == null || score > maxResult.getScore()) {
MovingPawn pawn = action.getPawn();
maxResult = new MiniMaxResult(score, pawn.getRow(), pawn.getCol(), action.getMovement());
}
}
}
long endMillis = System.currentTimeMillis();
System.out.printf("%d ms\n", (endMillis - startMillis));
return maxResult;
}
private static int max(MovingBoard game, int depth, int alpha, int beta) {
if (depth >= MAX_DEPTH) {
return evaluate(game);
}
int maxScore = Integer.MIN_VALUE;
for (Action action : getActions(game, true)) {
MovingPawn pawn = action.getPawn();
Pawn.PawnMovement movement = action.getMovement();
game.move(pawn, movement);
int score = min(game, depth + 1, Math.max(alpha, maxScore), beta);
game.revertMove();
if (score > maxScore) {
maxScore = score;
}
if (maxScore >= beta) {
return maxScore;
}
}
return maxScore;
}
private static int min(MovingBoard game, int depth, int alpha, int beta) {
if (depth >= MAX_DEPTH) {
return evaluate(game);
}
int minScore = Integer.MAX_VALUE;
for (Action action : getActions(game, false)) {
MovingPawn pawn = action.getPawn();
Pawn.PawnMovement movement = action.getMovement();
game.move(pawn, movement);
int score = max(game, depth + 1, alpha, Math.min(beta, minScore)) * -1;
game.revertMove();
if (score < minScore) {
minScore = score;
}
if (minScore <= alpha) {
return minScore;
}
}
return minScore;
}
private static Collection<Action> getActions(MovingBoard game, boolean max) {
List<Action> actions = new ArrayList<>();
Collection<MovingPawn> pawns = max ? game.getMaxPawns() : game.getMinPawns();
Pawn.PawnMovement[] movements = Pawn.PawnMovement.values();
// Collection<MovingPawn> winningPawns = pawns.stream().filter(p -> p.getRow() + p.getDirection() == p.getPlayer().getGoal()).collect(Collectors.toList());
// if (winningPawns.size() > 0) {
// pawns = winningPawns;
// }
for (MovingPawn pawn : pawns) {
for (Pawn.PawnMovement movement : movements) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
actions.add(new Action(pawn, movement));
}
}
}
Collections.shuffle(actions);
return actions;
}
private static int evaluate(MovingBoard board) {
return BoardEvaluator.evaluate(board);
}
static class Action {
private final MovingPawn pawn;
private final Pawn.PawnMovement movement;
public Action(MovingPawn pawn, Pawn.PawnMovement movement) {
this.pawn = pawn;
this.movement = movement;
}
public MovingPawn getPawn() {
return pawn;
}
public Pawn.PawnMovement getMovement() {
return movement;
}
}
}

View File

@ -1,41 +0,0 @@
package laboratoire4;
public class MiniMaxResult {
private final int score;
private final int row;
private final int col;
private final Pawn.PawnMovement movement;
public MiniMaxResult(int score, int row, int col, Pawn.PawnMovement movement) {
this.score = score;
this.row = row;
this.col = col;
this.movement = movement;
}
public int getScore() {
return score;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
public Pawn.PawnMovement getMovement() {
return movement;
}
@Override
public String toString() {
return "MiniMaxResult{" +
"score=" + score +
", col=" + col +
", row=" + row +
", movement=" + movement +
'}';
}
}

View File

@ -1,19 +0,0 @@
package laboratoire4;
public interface MovingPawn extends IPawn {
void revertMove();
int getMoveCount();
static MovingPawn from(Pawn pawn) {
if (pawn instanceof Pusher) {
return new MovingPusher(pawn.getPlayer(), pawn.getRow(), pawn.getCol());
}
return new MovingPushed(pawn.getPlayer(), pawn.getRow(), pawn.getCol());
}
static boolean areSamePlayers(IPawn a, IPawn b) {
return a != null && b != null && a.getPlayer() == b.getPlayer();
}
}

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

@ -1,10 +1,20 @@
package laboratoire4;
package laboratoire4.game;
public class PusherBoard {
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.MasterStrategy;
import laboratoire4.strategies.Strategy;
import laboratoire4.strategies.EvaluationResult;
public class PusherGame implements Game {
private final Player player;
private Pawn[][] board;
public PusherBoard(Player player, String[] boardValues) {
public PusherGame(Player player, String[] boardValues) {
this.player = player;
newGame(boardValues);
@ -37,9 +47,11 @@ public class PusherBoard {
}
public String runNextMove() {
MiniMaxResult result = MiniMax.miniMax(this);
// MiniMaxResult result = MiniMax.miniMax(this);
EvaluationResult result = MasterStrategy.getInstance().getNextMove(this);
Pawn pawn = board[result.getRow()][result.getCol()];
System.out.println(result.getScore());
// System.out.println(result.getScore());
String initialPosition = pawn.getPosition();
move(pawn, result.getMovement());
@ -57,11 +69,11 @@ public class PusherBoard {
int fromRow = Integer.parseInt(String.valueOf(from.charAt(1))) - 1;
int toCol = (int) to.charAt(0) - 65;
Pawn.PawnMovement movement = Pawn.PawnMovement.from(toCol - fromCol);
PawnMovement movement = PawnMovement.from(toCol - fromCol);
move(fromRow, fromCol, movement);
}
public void move(int row, int col, Pawn.PawnMovement movement) {
public void move(int row, int col, PawnMovement movement) {
Pawn pawn = board[row][col];
if (pawn == null) {
return;
@ -70,16 +82,12 @@ public class PusherBoard {
move(pawn, movement);
}
private void move(Pawn pawn, Pawn.PawnMovement movement) {
// if (!pawn.isMoveValid(board, movement)) {
// return;
// }
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;
board[toRow][toCol] = (Pawn) pawn;
pawn.move(movement);
}

View File

@ -1,4 +1,9 @@
package laboratoire4;
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;
@ -6,34 +11,34 @@ import java.util.List;
import java.util.Stack;
import java.util.function.Predicate;
public class MovingBoard {
private final MovingPawn[][] board;
public class SimulatedGame implements Game {
private final SimulatedPawn[][] board;
private final Player player;
private final Stack<MovingPawn> removedPawns = new Stack<>();
private final Stack<MovingPawn> previousMovedPawns = new Stack<>();
private final Stack<SimulatedPawn> removedPawns = new Stack<>();
private final Stack<SimulatedPawn> previousMovedPawns = new Stack<>();
public MovingBoard(Pawn[][] board, Player player) {
public SimulatedGame(IPawn[][] board, Player player) {
this.board = asMovingPawns(board);
this.player = player;
}
public void move(MovingPawn pawn, Pawn.PawnMovement movement) {
public void move(IPawn pawn, PawnMovement movement) {
int toRow = pawn.getRow() + pawn.getDirection();
int toCol = pawn.getCol() + movement.getMove();
MovingPawn capturedPawn = board[toRow][toCol];
SimulatedPawn capturedPawn = board[toRow][toCol];
previousMovedPawns.push(pawn);
previousMovedPawns.push((SimulatedPawn) pawn);
removedPawns.push(capturedPawn);
board[pawn.getRow()][pawn.getCol()] = null;
board[toRow][toCol] = pawn;
board[toRow][toCol] = (SimulatedPawn) pawn;
pawn.move(movement);
}
public void revertMove() {
MovingPawn pawn = previousMovedPawns.pop();
MovingPawn capturedPawn = removedPawns.pop();
SimulatedPawn pawn = previousMovedPawns.pop();
SimulatedPawn capturedPawn = removedPawns.pop();
board[pawn.getRow()][pawn.getCol()] = capturedPawn;
@ -41,7 +46,7 @@ public class MovingBoard {
board[pawn.getRow()][pawn.getCol()] = pawn;
}
public MovingPawn getNextPawn(IPawn pawn, Pawn.PawnMovement move) {
public SimulatedPawn getNextPawn(IPawn pawn, PawnMovement move) {
int nextRow = pawn.getRow() + pawn.getDirection();
int nextCol = pawn.getCol() + move.getMove();
@ -52,7 +57,7 @@ public class MovingBoard {
return board[nextRow][nextCol];
}
public MovingPawn[][] getBoard() {
public SimulatedPawn[][] getBoard() {
return board;
}
@ -64,18 +69,11 @@ public class MovingBoard {
return removedPawns.peek() != null;
}
public Collection<MovingPawn> getMaxPawns() {
return getPawns(p -> p.getPlayer() == player);
}
public Collection<MovingPawn> getMinPawns() {
return getPawns(p -> p.getPlayer() != player);
}
private Collection<MovingPawn> getPawns(Predicate<MovingPawn> predicate) {
List<MovingPawn> pawns = new ArrayList<>();
for (MovingPawn[] row : board) {
for (MovingPawn pawn : row) {
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);
}
@ -84,15 +82,15 @@ public class MovingBoard {
return pawns;
}
public static MovingPawn[][] asMovingPawns(Pawn[][] board) {
MovingPawn[][] to = new MovingPawn[board.length][board.length];
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;
}
MovingPawn pawn = MovingPawn.from(board[row][col]);
SimulatedPawn pawn = SimulatedPawn.from(board[row][col]);
to[row][col] = pawn;
}
}

View File

@ -1,4 +1,7 @@
package laboratoire4;
package laboratoire4.pawns;
import laboratoire4.IPawn;
import laboratoire4.Player;
public abstract class Pawn implements IPawn {
protected final Player player;
@ -13,11 +16,11 @@ public abstract class Pawn implements IPawn {
public void move(PawnMovement movement) {
setRow(row + player.getDirection());
setCol(col + movement.move);
setCol(col + movement.getMove());
}
public String getPosition() {
char colStr = (char)(col + 65);
char colStr = (char) (col + 65);
return String.format("%s%d", colStr, row + 1);
}
@ -46,44 +49,23 @@ public abstract class Pawn implements IPawn {
}
public boolean isMoveValid(IPawn[][] board, PawnMovement movement) {
int nextRow = row + getDirection();
return isMoveValid(board, movement, row, col);
}
@Override
public boolean isMoveValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol) {
int nextRow = fromRow + getDirection();
if (nextRow < 0 || nextRow >= board.length) {
return false;
}
int nextCol = col + movement.move;
int nextCol = fromCol + movement.getMove();
if (nextCol < 0 || nextCol >= board.length) {
return false;
}
return isMoveReallyValid(board, movement);
return isMoveReallyValid(board, movement, fromRow, fromCol);
}
protected abstract boolean isMoveReallyValid(IPawn[][] 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;
}
}
protected abstract boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol);
}

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,53 @@
package laboratoire4.pawns;
import laboratoire4.IPawn;
import java.awt.*;
import java.util.ArrayDeque;
import java.util.Queue;
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)) {
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;
}
}

View File

@ -1,4 +1,7 @@
package laboratoire4;
package laboratoire4.pawns;
import laboratoire4.IPawn;
import laboratoire4.Player;
public class Pushed extends Pawn {
public Pushed(Player player, int row, int col) {
@ -11,17 +14,17 @@ public class Pushed extends Pawn {
}
@Override
public boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement) {
public boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol) {
int direction = getDirection();
IPawn pusher = null;
IPawn to = board[row + direction][col + movement.getMove()];
IPawn to = board[fromRow + direction][fromCol + movement.getMove()];
if (col > 0 && movement == PawnMovement.RIGHT_DIAGONAL) {
pusher = board[row - direction][col - movement.getMove()];
} else if (col < board.length - 1 && movement == PawnMovement.LEFT_DIAGONAL) {
pusher = board[row - direction][col - 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[row - direction][col];
pusher = board[fromRow - direction][fromCol];
}
boolean pusherValid = pusher != null && pusher.isPusher() && pusher.getPlayer() == player;

View File

@ -1,4 +1,7 @@
package laboratoire4;
package laboratoire4.pawns;
import laboratoire4.IPawn;
import laboratoire4.Player;
public class Pusher extends Pawn {
public Pusher(Player player, int row, int col) {
@ -11,8 +14,8 @@ public class Pusher extends Pawn {
}
@Override
public boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement) {
IPawn to = board[getRow() + getDirection()][getCol() + movement.getMove()];
public boolean isMoveReallyValid(IPawn[][] board, PawnMovement movement, int fromRow, int fromCol) {
IPawn to = board[fromRow + getDirection()][fromCol + movement.getMove()];
if (to == null) {
return true;

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

@ -1,13 +1,20 @@
package laboratoire4;
package laboratoire4.pawns;
import laboratoire4.Player;
import java.util.Stack;
public class MovingPushed extends Pushed implements MovingPawn {
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 MovingPushed(Player player, int row, int col) {
public SimulatedPushed(Player player, int row, int col) {
super(player, row, col);
originalRow = row;
originalCol = col;
}
@Override
@ -25,6 +32,16 @@ public class MovingPushed extends Pushed implements MovingPawn {
moveCount--;
}
@Override
public int getOriginalRow() {
return originalRow;
}
@Override
public int getOriginalCol() {
return originalCol;
}
@Override
public int getMoveCount() {
return moveCount;

View File

@ -1,13 +1,20 @@
package laboratoire4;
package laboratoire4.pawns;
import laboratoire4.Player;
import java.util.Stack;
public class MovingPusher extends Pusher implements MovingPawn {
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 MovingPusher(Player player, int row, int col) {
public SimulatedPusher(Player player, int row, int col) {
super(player, row, col);
originalRow = row;
originalCol = col;
}
@Override
@ -25,6 +32,16 @@ public class MovingPusher extends Pusher implements MovingPawn {
moveCount--;
}
@Override
public int getOriginalRow() {
return originalRow;
}
@Override
public int getOriginalCol() {
return originalCol;
}
@Override
public int getMoveCount() {
return moveCount;

View File

@ -0,0 +1,68 @@
package laboratoire4.strategies;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
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 originalMinPawnCount;
@Override
public EvaluationResult getNextMove(Game game) {
originalMinPawnCount = GameUtils.getMinPawnsAsStream(game.getBoard(), game.getPlayer()).count();
return super.getNextMove(game);
}
@Override
protected int evaluateSimulation() {
for (SimulatedPawn pawn : getMaxPawns()) {
if (pawn.getRow() == pawn.getPlayer().getGoal()) {
return Integer.MAX_VALUE;
}
}
int score = 0;
int minPawnCount = getMinPawns().size();
long capturedPawnCount = originalMinPawnCount - minPawnCount;
score += Math.pow(capturedPawnCount, 2);
return score;
}
@Override
public int getWeight(Game game) {
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) {
weight = PawnUtils.hasEasyWinPath(game.getBoard(), pawn) ? 8 : 5;
}
if (distanceFromGoal > 2 && distanceFromGoal <= ATTACK_DISTANCE_FROM_GOAL) {
weight = 2;
}
if (weight > maxWeight) {
maxWeight = weight;
}
}
return maxWeight;
}
}

View File

@ -0,0 +1,93 @@
package laboratoire4.strategies;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
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 = 3;
private final Map<IPawn, Integer> dangerousPawns = new HashMap<>();
@Override
public EvaluationResult getNextMove(Game game) {
for (IPawn pawn : GameUtils.getMinPawns(game.getBoard(), game.getPlayer())) {
int distance = PawnUtils.distanceFromGoal(pawn);
if (distance <= DEFENSE_DISTANCE_FROM_HOME) {
dangerousPawns.put(pawn, DEFENSE_DISTANCE_FROM_HOME - distance + 1);
}
}
if (dangerousPawns.isEmpty()) {
return null;
}
return super.getNextMove(game);
}
@Override
public int getWeight(Game game) {
Collection<IPawn> minPawns = GameUtils.getMinPawns(game.getBoard(), game.getPlayer());
int maxWeight = 0;
for (IPawn pawn : minPawns) {
int weight = 0;
int distanceFromGoal = PawnUtils.distanceFromGoal(pawn);
if (distanceFromGoal <= 1) {
return WEIGHT_MAX;
}
if (distanceFromGoal == 2) {
weight = PawnUtils.hasEasyWinPath(game.getBoard(), pawn) ? 8 : 5;
}
if (distanceFromGoal > 2 && distanceFromGoal <= DEFENSE_DISTANCE_FROM_HOME) {
weight = 2;
}
if (weight > maxWeight) {
maxWeight = weight;
}
}
return maxWeight;
}
@Override
protected int evaluateSimulation() {
for (SimulatedPawn pawn : getMinPawns()) {
if (pawn.getRow() == pawn.getPlayer().getGoal()) {
return Integer.MIN_VALUE;
}
}
int score = 0;
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.isEmpty()) {
// 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);
}
}
return score;
}
}

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,61 @@
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 = 1;
@Override
public EvaluationResult getNextMove(Game game) {
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(Game game) {
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,77 @@
package laboratoire4.strategies;
import laboratoire4.game.Game;
public class MasterStrategy implements Strategy {
private static Strategy instance;
public static Strategy getInstance() {
if (instance == null) {
instance = new MasterStrategy();
}
return instance;
}
private boolean starting = true;
private MasterStrategy() {
}
@Override
public EvaluationResult getNextMove(Game game) {
long startMs = System.currentTimeMillis();
Strategy strategy = getBestStrategy(game);
System.out.println(strategy.getClass().getSimpleName());
EvaluationResult result = strategy.getNextMove(game);
long endMs = System.currentTimeMillis();
System.out.printf("Temps: %d ms\n", endMs - startMs);
return result;
}
private Strategy getBestStrategy(Game game) {
Strategy[] strategies = new Strategy[]{
new WinningStrategy(),
new ImmediateDefenseStrategy(),
new DefenseStrategy(),
new AttackStrategy()
};
int maxWeight = 0;
Strategy bestStrategy = null;
for (Strategy strategy : strategies) {
int weight = strategy.getWeight(game);
if (weight == WEIGHT_MAX) {
return strategy;
}
if (weight > maxWeight) {
maxWeight = weight;
bestStrategy = strategy;
}
}
if (maxWeight > 0) {
starting = false;
return bestStrategy;
}
//noinspection IfStatementWithIdenticalBranches
if (starting) {
return new StartingStrategy();
}
// TODO WN: Default strategy
return new StartingStrategy();
}
@Override
public int getWeight(Game game) {
return WEIGHT_MAX;
}
}

View File

@ -0,0 +1,123 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.game.SimulatedGame;
import laboratoire4.pawns.PawnMovement;
import laboratoire4.pawns.SimulatedPawn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public abstract class MiniMaxStrategy implements Strategy {
private static final int MAX_DEPTH = 4;
protected SimulatedGame game;
@Override
public EvaluationResult getNextMove(Game game) {
return miniMax(game);
}
private EvaluationResult miniMax(Game game) {
this.game = new SimulatedGame(game.getBoard(), game.getPlayer());
EvaluationResult maxResult = null;
int maxScore = Integer.MIN_VALUE;
for (Action<SimulatedPawn> action : getActions(true)) {
int score = min(0, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (maxResult == null || score > maxScore) {
SimulatedPawn 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 evaluateSimulation();
}
int maxScore = Integer.MIN_VALUE;
for (Action<SimulatedPawn> action : getActions(true)) {
SimulatedPawn 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 evaluateSimulation();
}
int minScore = Integer.MAX_VALUE;
for (Action<SimulatedPawn> action : getActions(false)) {
SimulatedPawn pawn = action.getPawn();
PawnMovement movement = action.getMovement();
game.move(pawn, movement);
int score = max(depth + 1, alpha, Math.min(beta, minScore)) * -1;
game.revertMove();
if (score < minScore) {
minScore = score;
}
if (minScore <= alpha) {
return minScore;
}
}
return minScore;
}
private Collection<Action<SimulatedPawn>> getActions(boolean max) {
List<Action<SimulatedPawn>> actions = new ArrayList<>();
Collection<SimulatedPawn> pawns = max ? getMaxPawns() : getMinPawns();
PawnMovement[] movements = PawnMovement.values();
for (SimulatedPawn pawn : pawns) {
for (PawnMovement movement : movements) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
actions.add(new Action<>(pawn, movement));
}
}
}
Collections.shuffle(actions);
return actions;
}
protected Collection<SimulatedPawn> getMaxPawns() {
return GameUtils.getMaxPawns(game.getBoard(), game.getPlayer());
}
protected Collection<SimulatedPawn> getMinPawns() {
return GameUtils.getMinPawns(game.getBoard(), game.getPlayer());
}
protected abstract int evaluateSimulation();
}

View File

@ -0,0 +1,50 @@
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 StartingStrategy implements Strategy {
private static final int PAWN_TO_MOVE = 3;
private final Random random = new Random();
@Override
public EvaluationResult getNextMove(Game game) {
Collection<IPawn> outsideHomePawns = GameUtils.getMaxPawnsAsStream(game.getBoard(), game.getPlayer())
.filter(p -> p.getRow() != p.getPlayer().getHome() && p.getRow() != p.getPlayer().getHome() + p.getDirection())
.collect(Collectors.toList());
if (outsideHomePawns.size() > PAWN_TO_MOVE) {
List<Action<IPawn>> validActions = Strategy.getValidActions(game.getBoard(), outsideHomePawns);
if (!validActions.isEmpty()) {
return getRandomMove(validActions);
}
}
List<Action<IPawn>> validActions = Strategy.getValidActions(game);
return getRandomMove(validActions);
}
@Override
public int getWeight(Game game) {
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,40 @@
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 {
public static final int WEIGHT_MAX = 10;
EvaluationResult getNextMove(Game game);
int getWeight(Game game);
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) {
List<Action<IPawn>> validActions = new ArrayList<>();
for (IPawn pawn : pawns) {
for (PawnMovement movement : PawnMovement.values()) {
if (pawn.isMoveValid(board, movement)) {
validActions.add(new Action<>(pawn, movement));
}
}
}
return validActions;
}
}

View File

@ -0,0 +1,51 @@
package laboratoire4.strategies;
import laboratoire4.Action;
import laboratoire4.IPawn;
import laboratoire4.game.Game;
import laboratoire4.game.GameUtils;
import laboratoire4.pawns.PawnMovement;
import java.util.Collection;
import java.util.stream.Collectors;
public class WinningStrategy implements Strategy {
@Override
public EvaluationResult getNextMove(Game game) {
Action<IPawn> winningAction = getWinningAction(game);
if (winningAction == null) {
return null;
}
IPawn winningPawn = winningAction.getPawn();
return new EvaluationResult(winningPawn.getRow(), winningPawn.getCol(), winningAction.getMovement());
}
@Override
public int getWeight(Game game) {
Action<IPawn> winningAction = getWinningAction(game);
if (winningAction == null) {
return 0;
}
return WEIGHT_MAX + 1;
}
private static Action<IPawn> getWinningAction(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;
}
}