Plus d'heuristiques

This commit is contained in:
william 2023-04-03 23:48:52 -04:00
parent abf7f5bfd8
commit 5f7d74e0ae
8 changed files with 224 additions and 65 deletions

View File

@ -5,12 +5,14 @@
</component> </component>
<component name="ChangeListManager"> <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="Fixes?">
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/BoardEvaluator.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MiniMaxResult.java" 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/BoardEvaluator.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/BoardEvaluator.java" 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$/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/MiniMax.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/MiniMax.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/laboratoire4/Player.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/Player.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/PusherBoard.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/laboratoire4/PusherBoard.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" />
</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" />
@ -50,22 +52,22 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"RunOnceActivity.OpenProjectViewOnStart": "true", &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"SHARE_PROJECT_CONFIGURATION_FILES": "true", &quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
"codeWithMe.voiceChat.enabledByDefault": "false", &quot;codeWithMe.voiceChat.enabledByDefault&quot;: &quot;false&quot;,
"git-widget-placeholder": "william", &quot;git-widget-placeholder&quot;: &quot;william&quot;,
"last_opened_file_path": "/home/william/Dev/Projects", &quot;last_opened_file_path&quot;: &quot;/home/william/Dev/Projects&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}]]></component> }</component>
<component name="RunManager" selected="Application.Main"> <component name="RunManager" selected="Application.Main">
<configuration name="Client" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true"> <configuration name="Client" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="laboratoire4.Client" /> <option name="MAIN_CLASS_NAME" value="laboratoire4.Client" />
@ -132,6 +134,7 @@
<workItem from="1679672719638" duration="8340000" /> <workItem from="1679672719638" duration="8340000" />
<workItem from="1679779362814" duration="5912000" /> <workItem from="1679779362814" duration="5912000" />
<workItem from="1679787823160" duration="2189000" /> <workItem from="1679787823160" duration="2189000" />
<workItem from="1680198283785" duration="8256000" />
</task> </task>
<task id="LOCAL-00001" summary="MiniMax"> <task id="LOCAL-00001" summary="MiniMax">
<created>1679263366439</created> <created>1679263366439</created>

View File

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

@ -40,6 +40,7 @@ public class Client {
if (cmd == '5') { if (cmd == '5') {
stopGame(); stopGame();
break;
} }
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -6,7 +6,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
public class MiniMax { public class MiniMax {
private static final int MAX_DEPTH = 4; private static final int MAX_MAX_DEPTH = 6;
private static int MAX_DEPTH = 2;
public static MiniMaxResult miniMax(PusherBoard board) { public static MiniMaxResult miniMax(PusherBoard board) {
long startMillis = System.currentTimeMillis(); long startMillis = System.currentTimeMillis();
@ -15,6 +16,8 @@ public class MiniMax {
MiniMaxResult maxResult = null; MiniMaxResult maxResult = null;
for (int i = 0; i <= MAX_MAX_DEPTH; i += 2) {
MAX_DEPTH = i;
for (Action action : actions) { for (Action action : actions) {
int score = min(game, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); int score = min(game, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
@ -23,6 +26,7 @@ public class MiniMax {
maxResult = new MiniMaxResult(score, pawn.getRow(), pawn.getCol(), action.getMovement()); maxResult = new MiniMaxResult(score, pawn.getRow(), pawn.getCol(), action.getMovement());
} }
} }
}
long endMillis = System.currentTimeMillis(); long endMillis = System.currentTimeMillis();
System.out.printf("%d ms\n", (endMillis - startMillis)); System.out.printf("%d ms\n", (endMillis - startMillis));
@ -89,6 +93,11 @@ public class MiniMax {
Collection<MovingPawn> pawns = max ? game.getMaxPawns() : game.getMinPawns(); Collection<MovingPawn> pawns = max ? game.getMaxPawns() : game.getMinPawns();
Pawn.PawnMovement[] movements = Pawn.PawnMovement.values(); 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 (MovingPawn pawn : pawns) {
for (Pawn.PawnMovement movement : movements) { for (Pawn.PawnMovement movement : movements) {
if (pawn.isMoveValid(game.getBoard(), movement)) { if (pawn.isMoveValid(game.getBoard(), movement)) {

View File

@ -41,6 +41,17 @@ public class MovingBoard {
board[pawn.getRow()][pawn.getCol()] = pawn; board[pawn.getRow()][pawn.getCol()] = pawn;
} }
public MovingPawn getNextPawn(IPawn pawn, 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 MovingPawn[][] getBoard() { public MovingPawn[][] getBoard() {
return board; return board;
} }

View File

@ -2,6 +2,7 @@ package laboratoire4;
public interface MovingPawn extends IPawn { public interface MovingPawn extends IPawn {
void revertMove(); void revertMove();
int getMoveCount();
static MovingPawn from(Pawn pawn) { static MovingPawn from(Pawn pawn) {
if (pawn instanceof Pusher) { if (pawn instanceof Pusher) {
@ -10,5 +11,9 @@ public interface MovingPawn extends IPawn {
return new MovingPushed(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

@ -4,6 +4,7 @@ import java.util.Stack;
public class MovingPushed extends Pushed implements MovingPawn { public class MovingPushed extends Pushed implements MovingPawn {
private final Stack<PawnMovement> previousMoves = new Stack<>(); private final Stack<PawnMovement> previousMoves = new Stack<>();
private int moveCount = 0;
public MovingPushed(Player player, int row, int col) { public MovingPushed(Player player, int row, int col) {
super(player, row, col); super(player, row, col);
@ -13,6 +14,7 @@ public class MovingPushed extends Pushed implements MovingPawn {
public void move(PawnMovement movement) { public void move(PawnMovement movement) {
super.move(movement); super.move(movement);
previousMoves.push(movement); previousMoves.push(movement);
moveCount++;
} }
@Override @Override
@ -20,6 +22,11 @@ public class MovingPushed extends Pushed implements MovingPawn {
PawnMovement move = previousMoves.pop(); PawnMovement move = previousMoves.pop();
setRow(row - getDirection()); setRow(row - getDirection());
setCol(col - move.getMove()); setCol(col - move.getMove());
moveCount--;
} }
@Override
public int getMoveCount() {
return moveCount;
}
} }

View File

@ -4,6 +4,7 @@ import java.util.Stack;
public class MovingPusher extends Pusher implements MovingPawn { public class MovingPusher extends Pusher implements MovingPawn {
private final Stack<PawnMovement> previousMoves = new Stack<>(); private final Stack<PawnMovement> previousMoves = new Stack<>();
private int moveCount = 0;
public MovingPusher(Player player, int row, int col) { public MovingPusher(Player player, int row, int col) {
super(player, row, col); super(player, row, col);
@ -13,6 +14,7 @@ public class MovingPusher extends Pusher implements MovingPawn {
public void move(PawnMovement movement) { public void move(PawnMovement movement) {
previousMoves.push(movement); previousMoves.push(movement);
super.move(movement); super.move(movement);
moveCount++;
} }
@Override @Override
@ -20,5 +22,11 @@ public class MovingPusher extends Pusher implements MovingPawn {
PawnMovement move = previousMoves.pop(); PawnMovement move = previousMoves.pop();
setRow(row - getDirection()); setRow(row - getDirection());
setCol(col - move.getMove()); setCol(col - move.getMove());
moveCount--;
}
@Override
public int getMoveCount() {
return moveCount;
} }
} }