-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from sandorw/feature/hexheuristics
Feature/hexheuristics
- Loading branch information
Showing
8 changed files
with
488 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
src/main/java/com/github/sandorw/mocabogaso/games/hex/OneSpaceHopHeuristic.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package com.github.sandorw.mocabogaso.games.hex; | ||
|
||
import com.github.sandorw.mocabogaso.ai.mcts.Heuristic; | ||
import com.github.sandorw.mocabogaso.games.GameState; | ||
import com.github.sandorw.mocabogaso.games.defaults.DefaultGameMove; | ||
import com.github.sandorw.mocabogaso.games.defaults.DefaultGameResult; | ||
import com.github.sandorw.mocabogaso.games.hex.HexGameState.BoardStatus; | ||
|
||
/** | ||
* Heuristic to encourage one space jump moves that can still be securely connected if needed. | ||
* | ||
* @author sandorw | ||
*/ | ||
public class OneSpaceHopHeuristic implements Heuristic<DefaultGameMove, DefaultGameResult> { | ||
int weight; | ||
|
||
public OneSpaceHopHeuristic(int weight) { | ||
this.weight = weight; | ||
} | ||
|
||
@Override | ||
public int getWeight() { | ||
return weight; | ||
} | ||
|
||
@Override | ||
public <GS extends GameState<DefaultGameMove, DefaultGameResult>> DefaultGameResult | ||
evaluateMove(DefaultGameMove move, GS initialGameState) { | ||
if (move == null) { | ||
return null; | ||
} | ||
HexGameState hexGameState = (HexGameState) initialGameState; | ||
int rowIndex = hexGameState.getRowNumber(move.getLocation()); | ||
int colIndex = hexGameState.getColNumber(move.getLocation()); | ||
int boardSize = hexGameState.boardSize; | ||
BoardStatus movingPlayer = (move.getPlayerName().equals("X") ? BoardStatus.X : BoardStatus.O); | ||
if ((rowIndex < boardSize-2) && (colIndex < boardSize-1) | ||
&& (hexGameState.boardLocation[rowIndex+2][colIndex+1] == movingPlayer) | ||
&& (hexGameState.boardLocation[rowIndex+1][colIndex] == BoardStatus.EMPTY) | ||
&& (hexGameState.boardLocation[rowIndex+1][colIndex+1] == BoardStatus.EMPTY)) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
if (rowIndex < boardSize-1) { | ||
if ((colIndex < boardSize-2) | ||
&& (hexGameState.boardLocation[rowIndex+1][colIndex+2] == movingPlayer) | ||
&& (hexGameState.boardLocation[rowIndex+1][colIndex+1] == BoardStatus.EMPTY) | ||
&& (hexGameState.boardLocation[rowIndex][colIndex+1] == BoardStatus.EMPTY)) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
if ((colIndex > 0) | ||
&& (hexGameState.boardLocation[rowIndex+1][colIndex-1] == movingPlayer) | ||
&& (hexGameState.boardLocation[rowIndex][colIndex-1] == BoardStatus.EMPTY) | ||
&& (hexGameState.boardLocation[rowIndex+1][colIndex] == BoardStatus.EMPTY)) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
} | ||
if (rowIndex > 0) { | ||
if ((colIndex > 1) | ||
&& (hexGameState.boardLocation[rowIndex-1][colIndex-2] == movingPlayer) | ||
&& (hexGameState.boardLocation[rowIndex-1][colIndex-1] == BoardStatus.EMPTY) | ||
&& (hexGameState.boardLocation[rowIndex][colIndex-1] == BoardStatus.EMPTY)) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
if ((colIndex < boardSize-1) | ||
&& (hexGameState.boardLocation[rowIndex-1][colIndex+1] == movingPlayer) | ||
&& (hexGameState.boardLocation[rowIndex][colIndex+1] == BoardStatus.EMPTY) | ||
&& (hexGameState.boardLocation[rowIndex-1][colIndex] == BoardStatus.EMPTY)) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
} | ||
if ((rowIndex > 1) && (colIndex > 0) | ||
&& (hexGameState.boardLocation[rowIndex-2][colIndex-1] == movingPlayer) | ||
&& (hexGameState.boardLocation[rowIndex-1][colIndex] == BoardStatus.EMPTY) | ||
&& (hexGameState.boardLocation[rowIndex-1][colIndex-1] == BoardStatus.EMPTY)) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public <GS extends GameState<DefaultGameMove, DefaultGameResult>> DefaultGameMove suggestPlayoutMove(GS gameState) { | ||
return null; | ||
} | ||
|
||
} |
101 changes: 101 additions & 0 deletions
101
src/main/java/com/github/sandorw/mocabogaso/games/hex/SecureConnectionHeuristic.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package com.github.sandorw.mocabogaso.games.hex; | ||
|
||
import com.github.sandorw.mocabogaso.ai.mcts.Heuristic; | ||
import com.github.sandorw.mocabogaso.games.GameState; | ||
import com.github.sandorw.mocabogaso.games.defaults.DefaultGameMove; | ||
import com.github.sandorw.mocabogaso.games.defaults.DefaultGameResult; | ||
import com.github.sandorw.mocabogaso.games.hex.HexGameState.BoardStatus; | ||
|
||
/** | ||
* Heuristic to incentivize linking groups together when threatened. | ||
* | ||
* @author sandorw | ||
*/ | ||
public class SecureConnectionHeuristic implements Heuristic<DefaultGameMove, DefaultGameResult> { | ||
private int weight; | ||
|
||
public SecureConnectionHeuristic(int weight) { | ||
this.weight = weight; | ||
} | ||
|
||
@Override | ||
public int getWeight() { | ||
return weight; | ||
} | ||
|
||
@Override | ||
public <GS extends GameState<DefaultGameMove, DefaultGameResult>> DefaultGameResult | ||
evaluateMove(DefaultGameMove move, GS initialGameState) { | ||
if (move == null) { | ||
return null; | ||
} | ||
HexGameState hexGameState = (HexGameState) initialGameState; | ||
int rowIndex = hexGameState.getRowNumber(move.getLocation()); | ||
int colIndex = hexGameState.getColNumber(move.getLocation()); | ||
BoardStatus movingPlayer = (move.getPlayerName().equals("X") ? BoardStatus.X : BoardStatus.O); | ||
boolean nonUrgentConnectionExists = false; | ||
boolean directOppositeAllies = false; | ||
int numAlliedGroups = 0; | ||
int numAlliedPieces = 0; | ||
int firstAllyIndex = -4; | ||
HexGameState.Group[] alliedGroups = {null, null, null}; | ||
for (int i=0; i < 6; ++i) { | ||
int neighborRow = rowIndex + HexGameState.neighborRowDelta.get(i); | ||
int neighborCol = colIndex + HexGameState.neighborColDelta.get(i); | ||
if (hexGameState.isIndexInBounds(neighborRow) && hexGameState.isIndexInBounds(neighborCol)) { | ||
BoardStatus neighborStatus = hexGameState.boardLocation[neighborRow][neighborCol]; | ||
if (neighborStatus == movingPlayer) { | ||
++numAlliedPieces; | ||
HexGameState.Group neighborGroup = hexGameState.groups[neighborRow][neighborCol]; | ||
boolean matchingGroup = false; | ||
for (int j=0; j < numAlliedGroups; ++j) { | ||
if (neighborGroup == alliedGroups[j]) { | ||
matchingGroup = true; | ||
break; | ||
} | ||
} | ||
if (!matchingGroup) { | ||
alliedGroups[numAlliedGroups] = neighborGroup; | ||
++numAlliedGroups; | ||
if (numAlliedGroups == 1) { | ||
firstAllyIndex = i; | ||
} else if (firstAllyIndex == i-3) { | ||
directOppositeAllies = true; | ||
} | ||
} | ||
} else if (neighborStatus == BoardStatus.EMPTY) { | ||
int prevIndex = (i == 0 ? 5 : i-1); | ||
int nextIndex = (i == 5 ? 0 : i+1); | ||
int prevRow = rowIndex + HexGameState.neighborRowDelta.get(prevIndex); | ||
int prevCol = colIndex + HexGameState.neighborColDelta.get(prevIndex); | ||
int nextRow = rowIndex + HexGameState.neighborRowDelta.get(nextIndex); | ||
int nextCol = colIndex + HexGameState.neighborColDelta.get(nextIndex); | ||
if (hexGameState.isIndexInBounds(prevRow) | ||
&& hexGameState.isIndexInBounds(prevCol) | ||
&& hexGameState.isIndexInBounds(nextRow) | ||
&& hexGameState.isIndexInBounds(nextCol)) { | ||
BoardStatus prevStatus = hexGameState.boardLocation[prevRow][prevCol]; | ||
BoardStatus nextStatus = hexGameState.boardLocation[nextRow][nextCol]; | ||
HexGameState.Group prevGroup = hexGameState.groups[prevRow][prevCol]; | ||
HexGameState.Group nextGroup = hexGameState.groups[nextRow][nextCol]; | ||
if ((prevStatus == movingPlayer) && (nextStatus == movingPlayer) | ||
&& (prevGroup != nextGroup)) { | ||
nonUrgentConnectionExists = true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if ((numAlliedGroups == 3) || ((numAlliedGroups == 2) | ||
&& ((directOppositeAllies && (numAlliedPieces == 2)) | ||
|| !nonUrgentConnectionExists))) { | ||
return new DefaultGameResult(move.getPlayerName(), false); | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public <GS extends GameState<DefaultGameMove, DefaultGameResult>> DefaultGameMove suggestPlayoutMove(GS gameState) { | ||
return null; | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
src/main/java/com/github/sandorw/mocabogaso/players/AIBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.github.sandorw.mocabogaso.players; | ||
|
||
import com.github.sandorw.mocabogaso.ai.AIService; | ||
import com.github.sandorw.mocabogaso.ai.mcts.MonteCarloSearchService; | ||
import com.github.sandorw.mocabogaso.ai.mcts.NodeResultsFactory; | ||
import com.github.sandorw.mocabogaso.ai.mcts.NodeResultsService; | ||
import com.github.sandorw.mocabogaso.ai.mcts.PlayoutPolicy; | ||
import com.github.sandorw.mocabogaso.ai.mcts.amaf.AMAFHeuristicNodeResults; | ||
import com.github.sandorw.mocabogaso.ai.mcts.amaf.AMAFHeuristicNodeResultsFactory; | ||
import com.github.sandorw.mocabogaso.ai.mcts.amaf.AMAFMonteCarloSearchService; | ||
import com.github.sandorw.mocabogaso.ai.mcts.amaf.AMAFNodeResultsService; | ||
import com.github.sandorw.mocabogaso.ai.mcts.amaf.DefaultAMAFNodeResults; | ||
import com.github.sandorw.mocabogaso.ai.mcts.amaf.DefaultAMAFNodeResultsFactory; | ||
import com.github.sandorw.mocabogaso.ai.mcts.defaults.DefaultNodeResults; | ||
import com.github.sandorw.mocabogaso.ai.mcts.defaults.DefaultNodeResultsFactory; | ||
import com.github.sandorw.mocabogaso.ai.mcts.defaults.DefaultNodeResultsService; | ||
import com.github.sandorw.mocabogaso.ai.mcts.policies.RandomMovePlayoutPolicy; | ||
import com.github.sandorw.mocabogaso.games.GameMove; | ||
import com.github.sandorw.mocabogaso.games.GameResult; | ||
import com.github.sandorw.mocabogaso.games.GameState; | ||
|
||
public class AIBuilder<GM extends GameMove, GR extends GameResult, GS extends GameState<GM,GR>> { | ||
private GS initialGameState; | ||
private boolean withAMAF; | ||
private boolean withHeuristics; | ||
private int timePerMoveMs; | ||
private int numThreads; | ||
|
||
public AIBuilder(GS initialGameState) { | ||
this.initialGameState = initialGameState; | ||
withAMAF = false; | ||
withHeuristics = false; | ||
timePerMoveMs = 1000; | ||
numThreads = 1; | ||
} | ||
|
||
public AIBuilder<GM,GR,GS> withAMAF() { | ||
withAMAF = true; | ||
return this; | ||
} | ||
|
||
public AIBuilder<GM,GR,GS> withHeuristics() { | ||
withHeuristics = true; | ||
return this; | ||
} | ||
|
||
public AIBuilder<GM,GR,GS> withTimePerMove(int timePerMoveMs) { | ||
this.timePerMoveMs = timePerMoveMs; | ||
return this; | ||
} | ||
|
||
public AIBuilder<GM,GR,GS> multithreaded(int numThreads) { | ||
this.numThreads = numThreads; | ||
return this; | ||
} | ||
|
||
public Player<GM> build() { | ||
PlayoutPolicy policy = new RandomMovePlayoutPolicy(); | ||
AIService<GM> aiService = null; | ||
if (withHeuristics) { | ||
NodeResultsFactory<AMAFHeuristicNodeResults> nodeResultsFactory = new AMAFHeuristicNodeResultsFactory(); | ||
if (withAMAF) { | ||
AMAFNodeResultsService<AMAFHeuristicNodeResults> nodeResultsService | ||
= new AMAFNodeResultsService<>(nodeResultsFactory); | ||
aiService = new AMAFMonteCarloSearchService<>(nodeResultsService, policy, initialGameState); | ||
} else { | ||
DefaultNodeResultsService<AMAFHeuristicNodeResults> nodeResultsService | ||
= new DefaultNodeResultsService<>(nodeResultsFactory); | ||
aiService = new MonteCarloSearchService<>(nodeResultsService, policy, initialGameState); | ||
} | ||
} else if (withAMAF) { | ||
NodeResultsFactory<DefaultAMAFNodeResults> nodeResultsFactory = new DefaultAMAFNodeResultsFactory(); | ||
AMAFNodeResultsService<DefaultAMAFNodeResults> nodeResultsService | ||
= new AMAFNodeResultsService<>(nodeResultsFactory); | ||
aiService = new AMAFMonteCarloSearchService<>(nodeResultsService, policy, initialGameState); | ||
} else { | ||
NodeResultsFactory<DefaultNodeResults> nodeResultsFactory = new DefaultNodeResultsFactory(); | ||
NodeResultsService<DefaultNodeResults> nodeResultsService | ||
= new DefaultNodeResultsService<>(nodeResultsFactory); | ||
aiService = new MonteCarloSearchService<>(nodeResultsService, policy, initialGameState); | ||
} | ||
if (numThreads > 1) { | ||
return new MultiThreadedAIPlayer<>(aiService, timePerMoveMs, numThreads); | ||
} | ||
return new AIPlayer<>(aiService, timePerMoveMs); | ||
} | ||
} |
Oops, something went wrong.