Skip to content

Commit b79d03b

Browse files
committed
Add TicTacToe test
1 parent 6efb42c commit b79d03b

File tree

5 files changed

+83
-14
lines changed

5 files changed

+83
-14
lines changed

aima-core/src/main/java/aima/core/environment/tictactoe/TicTacToeState.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,22 @@ public class TicTacToeState implements Cloneable {
1919
public static final String X = "X";
2020
public static final String EMPTY = "-";
2121
//
22-
private String[] board = new String[] { EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
23-
EMPTY, EMPTY, EMPTY, EMPTY };
22+
private String[] board;
2423

25-
private String playerToMove = X;
24+
private String playerToMove;
2625
private double utility = -1; // 1: win for X, 0: win for O, 0.5: draw
26+
27+
public TicTacToeState(){
28+
this.board = new String[] { EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY };
29+
playerToMove = X;
30+
}
31+
32+
public TicTacToeState(String[] board, String playerToMove){
33+
this.board = board;
34+
this.playerToMove = (Objects.equals(playerToMove, X) ? O : X);
35+
analyzeUtility();
36+
this.playerToMove = playerToMove;
37+
}
2738

2839
public String getPlayerToMove() {
2940
return playerToMove;

aima-core/src/main/java/aima/core/search/adversarial/MonteCarloTreeSearch.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public A makeDecision(S state) {
4343
}
4444

4545
private Node<S, A> select(GameTree gameTree) {
46-
Node node = gameTree.getRoot();
47-
while (isNodeFullyExpanded(node)) {
46+
Node<S, A> node = gameTree.getRoot();
47+
while (!game.isTerminal(node.getState()) && isNodeFullyExpanded(node)) {
4848
node = gameTree.getChildWithMaxUCT(node);
4949
}
5050
return node;
@@ -79,15 +79,16 @@ private A bestAction(Node<S, A> root) {
7979
Node<S, A> bestChild = tree.getChildWithMaxPlayouts(root);
8080
for (A a : game.getActions(root.getState())) {
8181
S result = game.getResult(root.getState(), a);
82-
if (result == bestChild.getState()) return a;
82+
if (result.equals(bestChild.getState())) return a;
8383
}
8484
return null;
8585
}
8686

8787
private boolean isNodeFullyExpanded(Node<S, A> node) {
88+
List<S> visitedChildren = tree.getVisitedChildren(node);
8889
for (A a : game.getActions(node.getState())) {
8990
S result = game.getResult(node.getState(), a);
90-
if (!tree.contains(result)) {
91+
if (!visitedChildren.contains(result)) {
9192
return false;
9293
}
9394
}
@@ -97,9 +98,10 @@ private boolean isNodeFullyExpanded(Node<S, A> node) {
9798

9899
private Node<S, A> randomlySelectUnvisitedChild(Node<S, A> node) {
99100
List<S> unvisitedChildren = new ArrayList<>();
101+
List<S> visitedChildren = tree.getVisitedChildren(node);
100102
for (A a : game.getActions(node.getState())) {
101103
S result = game.getResult(node.getState(), a);
102-
if (!tree.contains(result)) unvisitedChildren.add(result);
104+
if (!visitedChildren.contains(result)) unvisitedChildren.add(result);
103105
}
104106
Random rand = new Random();
105107
Node<S, A> newChild = tree.addChild(node, unvisitedChildren.get(rand.nextInt(unvisitedChildren.size())));

aima-core/src/main/java/aima/core/search/framework/GameTree.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ public Node<S, A> getRoot() {
3131
return root;
3232
}
3333

34+
public List<S> getVisitedChildren(Node<S, A> parent) {
35+
List<S> visitedChildren = new ArrayList<>();
36+
if (gameTree.containsKey(parent)) {
37+
for (Node<S, A> child : gameTree.get(parent)) {
38+
visitedChildren.add(child.getState());
39+
}
40+
}
41+
return visitedChildren;
42+
}
43+
3444
public Node<S, A> addChild(Node<S, A> parent, S child) {
3545
Node<S, A> newChild = nodeFactory.createNode(child);
3646
List<Node<S, A>> children = successors(parent);
@@ -57,11 +67,8 @@ public Node<S, A> getParent(Node<S, A> node) {
5767
}
5868

5969
public List<Node<S, A>> successors(Node<S, A> node) {
60-
return gameTree.get(node);
61-
}
62-
63-
public boolean contains(S state) {
64-
return Ni.containsKey(state);
70+
if (gameTree.containsKey(node)) return gameTree.get(node);
71+
else return new ArrayList<>();
6572
}
6673

6774
public void updateStats(boolean result, Node<S, A> node) {
@@ -82,6 +89,7 @@ public Node<S, A> getChildWithMaxUCT(Node<S, A> node) {
8289
best_children.add(child);
8390
}
8491
}
92+
8593
Random rand = new Random();
8694
return best_children.get(rand.nextInt(best_children.size()));
8795
}

aima-core/src/test/java/aima/test/core/unit/search/SearchTestSuite.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import aima.test.core.unit.search.adversarial.AlphaBetaSearchTest;
44
import aima.test.core.unit.search.adversarial.MinimaxSearchTest;
5+
import aima.test.core.unit.search.adversarial.MonteCarloTreeSearchTest;
56
import aima.test.core.unit.search.csp.AssignmentTest;
67
import aima.test.core.unit.search.csp.CSPTest;
78
import aima.test.core.unit.search.csp.MapCSPTest;
@@ -20,7 +21,7 @@
2021
import org.junit.runners.Suite;
2122

2223
@RunWith(Suite.class)
23-
@Suite.SuiteClasses({AlphaBetaSearchTest.class, MinimaxSearchTest.class, AssignmentTest.class, CSPTest.class, MapCSPTest.class,
24+
@Suite.SuiteClasses({AlphaBetaSearchTest.class, MinimaxSearchTest.class, MonteCarloTreeSearchTest.class, AssignmentTest.class, CSPTest.class, MapCSPTest.class,
2425
MetricsTest.class, TreeCspSolverTest.class, AStarSearchTest.class, GreedyBestFirstSearchTest.class, RecursiveBestFirstSearchTest.class,
2526
AndOrSearchTest.class, LRTAStarAgentTest.class, OnlineDFSAgentTest.class,
2627
BidirectionalSearchTest.class, BreadthFirstSearchTest.class, DepthFirstSearchTest.class,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package aima.test.core.unit.search.adversarial;
2+
3+
import aima.core.environment.tictactoe.TicTacToeGame;
4+
import aima.core.environment.tictactoe.TicTacToeState;
5+
import aima.core.search.adversarial.Game;
6+
import aima.core.search.adversarial.MonteCarloTreeSearch;
7+
import org.junit.Assert;
8+
import org.junit.Before;
9+
import org.junit.Test;
10+
11+
public class MonteCarloTreeSearchTest {
12+
Game game;
13+
MonteCarloTreeSearch monteCarloTreeSearch;
14+
15+
@Before
16+
public void setup() {
17+
this.game = new TicTacToeGame();
18+
this.monteCarloTreeSearch = new MonteCarloTreeSearch(game, 100);
19+
}
20+
21+
@Test
22+
public void testTicTacToeBoard() {
23+
String[] board = new String[]{TicTacToeState.X, TicTacToeState.O, TicTacToeState.O, TicTacToeState.O, TicTacToeState.X, TicTacToeState.X, TicTacToeState.X, TicTacToeState.O, TicTacToeState.X};
24+
TicTacToeState state = new TicTacToeState(board, TicTacToeState.O);
25+
Assert.assertTrue(game.isTerminal(state));
26+
Assert.assertEquals(1, game.getUtility(state, TicTacToeState.X), 0);
27+
28+
board = new String[]{TicTacToeState.X, TicTacToeState.O, TicTacToeState.O, TicTacToeState.O, TicTacToeState.X, TicTacToeState.X, TicTacToeState.X, TicTacToeState.X, TicTacToeState.O};
29+
state = new TicTacToeState(board, TicTacToeState.O);
30+
Assert.assertTrue(game.isTerminal(state));
31+
Assert.assertEquals(0.5, game.getUtility(state, TicTacToeState.X), 0);
32+
33+
board = new String[]{TicTacToeState.X, TicTacToeState.O, TicTacToeState.EMPTY, TicTacToeState.X, TicTacToeState.EMPTY, TicTacToeState.O, TicTacToeState.X, TicTacToeState.EMPTY, TicTacToeState.EMPTY};
34+
state = new TicTacToeState(board, TicTacToeState.O);
35+
Assert.assertTrue(game.isTerminal(state));
36+
Assert.assertEquals(1, game.getUtility(state, TicTacToeState.X), 0);
37+
}
38+
39+
@Test
40+
public void testMonteCarloTreeDecision() {
41+
String[] board = new String[]{TicTacToeState.O, TicTacToeState.X, TicTacToeState.O, TicTacToeState.EMPTY, TicTacToeState.X, TicTacToeState.X, TicTacToeState.EMPTY, TicTacToeState.EMPTY, TicTacToeState.EMPTY};
42+
TicTacToeState state = new TicTacToeState(board, TicTacToeState.O);
43+
String[] expectedBoard = new String[]{TicTacToeState.O, TicTacToeState.X, TicTacToeState.O, TicTacToeState.O, TicTacToeState.X, TicTacToeState.X, TicTacToeState.EMPTY, TicTacToeState.EMPTY, TicTacToeState.EMPTY};
44+
TicTacToeState expectedState = new TicTacToeState(expectedBoard, TicTacToeState.X);
45+
Assert.assertEquals(expectedState, game.getResult(state, monteCarloTreeSearch.makeDecision(state)));
46+
}
47+
}

0 commit comments

Comments
 (0)