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

View File

@ -4,23 +4,26 @@ import java.util.Collection;
public class BoardEvaluator {
public static int evaluate(MovingBoard game) {
MovingPawn[][] board = game.getBoard();
Collection<MovingPawn> maxPawns = game.getMaxPawns();
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
private static int evaluatePlayer(MovingPawn[][] board, Collection<MovingPawn> maxPawns, Collection<MovingPawn> minPawns) {
if (minPawns.size() == 0) {
return Integer.MAX_VALUE;
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(board, pawn);
score += evaluatePawn(game, pawn);
if (max) {
score += strategy(game, pawn);
}
}
return score;
@ -33,83 +36,195 @@ public class BoardEvaluator {
// 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) {
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
//region Board
private static int evaluatePawn(MovingPawn[][] board, MovingPawn pawn) {
private static int evaluatePawn(MovingBoard game, MovingPawn pawn) {
if (pawn.getRow() == pawn.getPlayer().getGoal()) {
return Integer.MAX_VALUE;
return 50;
}
// Favorise les pièces qui peuvent gagner au prochain tour
if (pawn.getRow() + pawn.getDirection() == pawn.getPlayer().getGoal()) {
return Integer.MAX_VALUE / 2;
return 25;
}
int score = 0;
score += evaluateHomePawnScore(pawn);
score += evaluatePushedScore(pawn);
score += evaluateValidMovesScore(board, pawn);
score += evaluateAttackScore(board, 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() ? 50 : 0;
return pawn.getRow() == pawn.getPlayer().getHome() ? 1 : 0;
}
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;
if (pawn.isMoveValid(board, Pawn.PawnMovement.LEFT_DIAGONAL)) {
score += 50;
for (Pawn.PawnMovement movement : Pawn.PawnMovement.values()) {
MovingPawn nearPawn = board.getNextPawn(pawn, movement);
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;
}
private static int evaluateAttackScore(MovingPawn[][] board, MovingPawn pawn) {
private static int evaluateValidMovesScore(MovingBoard game, MovingPawn pawn) {
int score = 0;
int row = pawn.getRow();
int col = pawn.getCol();
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;
for (Pawn.PawnMovement movement : Pawn.PawnMovement.values()) {
if (pawn.isMoveValid(game.getBoard(), movement)) {
score++;
}
}
return score * pawn.getMoveCount();
}
if (pawn.isMoveValid(board, Pawn.PawnMovement.RIGHT_DIAGONAL)) {
MovingPawn destPawn = board[row + pawn.getDirection()][col + 1];
private static int evaluateAttackScore(MovingBoard game, MovingPawn pawn) {
int score = 0;
if (destPawn != null && destPawn.getPlayer() != pawn.getPlayer()) {
score += 100;
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 *= 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;
}
//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') {
stopGame();
break;
}
}
} catch (IOException e) {

View File

@ -6,7 +6,8 @@ import java.util.Collections;
import java.util.List;
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) {
long startMillis = System.currentTimeMillis();
@ -15,6 +16,8 @@ public class MiniMax {
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);
@ -23,6 +26,7 @@ public class MiniMax {
maxResult = new MiniMaxResult(score, pawn.getRow(), pawn.getCol(), action.getMovement());
}
}
}
long endMillis = System.currentTimeMillis();
System.out.printf("%d ms\n", (endMillis - startMillis));
@ -89,6 +93,11 @@ public class MiniMax {
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)) {

View File

@ -41,6 +41,17 @@ public class MovingBoard {
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() {
return board;
}

View File

@ -2,6 +2,7 @@ package laboratoire4;
public interface MovingPawn extends IPawn {
void revertMove();
int getMoveCount();
static MovingPawn from(Pawn pawn) {
if (pawn instanceof Pusher) {
@ -10,5 +11,9 @@ public interface MovingPawn extends IPawn {
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 {
private final Stack<PawnMovement> previousMoves = new Stack<>();
private int moveCount = 0;
public MovingPushed(Player player, int row, int col) {
super(player, row, col);
@ -13,6 +14,7 @@ public class MovingPushed extends Pushed implements MovingPawn {
public void move(PawnMovement movement) {
super.move(movement);
previousMoves.push(movement);
moveCount++;
}
@Override
@ -20,6 +22,11 @@ public class MovingPushed extends Pushed implements MovingPawn {
PawnMovement move = previousMoves.pop();
setRow(row - getDirection());
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 {
private final Stack<PawnMovement> previousMoves = new Stack<>();
private int moveCount = 0;
public MovingPusher(Player player, int row, int col) {
super(player, row, col);
@ -13,6 +14,7 @@ public class MovingPusher extends Pusher implements MovingPawn {
public void move(PawnMovement movement) {
previousMoves.push(movement);
super.move(movement);
moveCount++;
}
@Override
@ -20,5 +22,11 @@ public class MovingPusher extends Pusher implements MovingPawn {
PawnMovement move = previousMoves.pop();
setRow(row - getDirection());
setCol(col - move.getMove());
moveCount--;
}
@Override
public int getMoveCount() {
return moveCount;
}
}