Skip to content

Commit bd1693f

Browse files
committed
A Star Player
1 parent 3d7912c commit bd1693f

14 files changed

+301
-25
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.teachingextensions.logo;
2+
3+
import java.util.Comparator;
4+
5+
public class AStarEstimator implements Comparator<PuzzleState> {
6+
7+
@Override
8+
public int compare(PuzzleState left, PuzzleState right) {
9+
return costOf(left) - costOf(right);
10+
}
11+
12+
private int costOf(PuzzleState state) {
13+
return state.getActualCost() + state.getEstimatedCost();
14+
}
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.teachingextensions.logo;
2+
3+
import java.util.PriorityQueue;
4+
5+
/**
6+
* A player who solves puzzles using the A* strategy will not only take into account the known cost of a reaching
7+
* a puzzle state, but also estimates the remaining cost to the goal.
8+
*/
9+
public class AStarPlayer extends PuzzlePlayer {
10+
public AStarPlayer(Puzzle puzzle) {
11+
super(puzzle, new PriorityQueue<>(1, new AStarEstimator()));
12+
}
13+
}

src/main/java/org/teachingextensions/logo/Puzzle.java

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.teachingextensions.logo;
22

3+
import java.awt.*;
34
import java.util.Arrays;
45

56
/**
@@ -8,14 +9,50 @@
89
* @see <a href="http://en.wikipedia.org/wiki/15_puzzle">Wikipedia</a>
910
*/
1011
public class Puzzle {
11-
private static final int[] solution = {0, 1, 2, 3, 4, 5, 6, 7, 8};
12-
private static final int blank = 8;
12+
private static final int blank = 8;
1313
private final int[] cells;
1414

1515
public Puzzle(int[] cells) {
1616
this.cells = cells;
1717
}
1818

19+
/**
20+
* Gives the position of the cell as it would appear on a 3x3 board.
21+
*
22+
* @param cell
23+
* The cell to get the position for.
24+
* @return The position of the cell.
25+
*/
26+
public static Point getPosition(int cell) {
27+
return new Point(cell % 3, cell / 3);
28+
}
29+
30+
/**
31+
* Calculate the <a href="http://en.wikipedia.org/wiki/Taxicab_geometry">Manhattan Distance</a> between two positions.
32+
*
33+
* @param start
34+
* The starting position.
35+
* @param end
36+
* The ending position.
37+
* @return The distance between the two positions.
38+
*/
39+
public static int getDistance(Point start, Point end) {
40+
return Math.abs(start.x - end.x) + Math.abs(start.y - end.y);
41+
}
42+
43+
/**
44+
* Calculate the <a href="http://en.wikipedia.org/wiki/Taxicab_geometry">Manhattan Distance</a> between two cells by first converting them to positions.
45+
*
46+
* @param start
47+
* The starting cell
48+
* @param end
49+
* The ending cell
50+
* @return The distance between the cells.
51+
*/
52+
public static int getDistance(int start, int end) {
53+
return getDistance(getPosition(start), getPosition(end));
54+
}
55+
1956
@Override
2057
public int hashCode() {
2158
return Arrays.hashCode(cells);
@@ -38,12 +75,7 @@ public String toString() {
3875
}
3976

4077
public boolean isSolved() {
41-
for (int i = 0; i < solution.length; i++) {
42-
if (solution[i] != cells[i]) {
43-
return false;
44-
}
45-
}
46-
return true;
78+
return getDistanceToGoal() == 0;
4779
}
4880

4981
public int getBlankIndex() {
@@ -69,4 +101,17 @@ public Puzzle swapBlank(int target) {
69101
copy[target] = 8;
70102
return new Puzzle(copy);
71103
}
104+
105+
/**
106+
* Calculate the distance between the goal by summing the distance between each cell and its goal.
107+
*
108+
* @return The distance to the goal.
109+
*/
110+
public int getDistanceToGoal() {
111+
int distance = 0;
112+
for (int i = 0; i < cells.length; i++) {
113+
distance += getDistance(i, cells[i]);
114+
}
115+
return distance;
116+
}
72117
}

src/main/java/org/teachingextensions/logo/PuzzleState.java

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* Represents a node in the puzzle-solving graph. Keeps track of the current puzzle arrangement and the actions
1010
* required to arrive at the current arrangement from the starting arrangement.
1111
*/
12-
public class PuzzleState implements Comparator<PuzzleState> , Comparable<PuzzleState>{
12+
public class PuzzleState implements Comparator<PuzzleState>, Comparable<PuzzleState> {
1313
private final Puzzle puzzle;
1414
private final Stack<Direction> history;
1515

@@ -26,19 +26,6 @@ public boolean isSolution() {
2626
return puzzle.isSolved();
2727
}
2828

29-
@Override
30-
public String toString() {
31-
StringBuilder b = new StringBuilder();
32-
if (!history.isEmpty()) {
33-
b.append(history.peek());
34-
b.append(" to ");
35-
}
36-
37-
b.append(puzzle);
38-
return b.toString();
39-
}
40-
41-
4229
public List<PuzzleState> getBranches() {
4330
List<PuzzleState> branches = new ArrayList<>(4);
4431
int blank = puzzle.getBlankIndex();
@@ -88,10 +75,15 @@ public int compare(PuzzleState o1, PuzzleState o2) {
8875
}
8976

9077
@Override
91-
public int compareTo( PuzzleState o) {
78+
public int compareTo(PuzzleState o) {
9279
return compare(this, o);
9380
}
9481

82+
@Override
83+
public int hashCode() {
84+
return puzzle.hashCode();
85+
}
86+
9587
@Override
9688
public boolean equals(Object o) {
9789
if (this == o) return true;
@@ -104,8 +96,19 @@ public boolean equals(Object o) {
10496
}
10597

10698
@Override
107-
public int hashCode() {
108-
return puzzle.hashCode();
99+
public String toString() {
100+
StringBuilder b = new StringBuilder();
101+
if (!history.isEmpty()) {
102+
b.append(history.peek());
103+
b.append(" to ");
104+
}
105+
106+
b.append(puzzle);
107+
return b.toString();
108+
}
109+
110+
public int getEstimatedCost() {
111+
return puzzle.getDistanceToGoal();
109112
}
110113

111114
public enum Direction {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.teachingextensions.logo;
2+
3+
import org.junit.Test;
4+
import org.teachingextensions.approvals.lite.Approvals;
5+
import org.teachingextensions.approvals.lite.reporters.UseReporter;
6+
import org.teachingextensions.approvals.lite.reporters.macosx.BeyondCompareReporter;
7+
8+
@UseReporter(BeyondCompareReporter.class)
9+
public class AStarPlayerTest extends PuzzlePlayerTest {
10+
11+
@Override
12+
protected PuzzlePlayer getPlayer(Puzzle puzzle) {
13+
return new AStarPlayer(puzzle);
14+
}
15+
16+
/**
17+
* Produces a puzzle solution
18+
*/
19+
@Test
20+
public void solve_puzzle() throws Exception {
21+
verifySolution();
22+
}
23+
24+
/**
25+
* Solve longer puzzle
26+
*/
27+
@Test
28+
public void solve_longer_puzzle() throws Exception {
29+
verifyLongSolution();
30+
}
31+
32+
/**
33+
* Solve jumbo puzzle
34+
*/
35+
@Test
36+
public void solve_jumbo_puzzle() throws Exception {
37+
int[] cells = {8, 0, 1, 2, 3, 4, 5, 6, 7};
38+
Puzzle p = new Puzzle(cells);
39+
PuzzlePlayer b = getPlayer(p);
40+
41+
PuzzleState s = b.solve();
42+
Approvals.verifyAll("From " + p + " to " + s.getPuzzle(), s.getHistory());
43+
}
44+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
From [8, 0, 1, 2, 3, 4, 5, 6, 7] to [0, 1, 2, 3, 4, 5, 6, 7, 8]
2+
3+
4+
{Right = 1}
5+
{Down = 3}
6+
{Left = -1}
7+
{Down = 3}
8+
{Right = 1}
9+
{Right = 1}
10+
{Up = -3}
11+
{Left = -1}
12+
{Left = -1}
13+
{Down = 3}
14+
{Right = 1}
15+
{Up = -3}
16+
{Up = -3}
17+
{Right = 1}
18+
{Down = 3}
19+
{Down = 3}
20+
{Left = -1}
21+
{Left = -1}
22+
{Up = -3}
23+
{Right = 1}
24+
{Right = 1}
25+
{Down = 3}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
From [0, 1, 2, 3, 4, 8, 5, 6, 7] to [0, 1, 2, 3, 4, 5, 6, 7, 8]
2+
3+
4+
{Down = 3}
5+
{Left = -1}
6+
{Left = -1}
7+
{Up = -3}
8+
{Right = 1}
9+
{Down = 3}
10+
{Right = 1}
11+
{Up = -3}
12+
{Left = -1}
13+
{Left = -1}
14+
{Down = 3}
15+
{Right = 1}
16+
{Right = 1}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Solve [0, 1, 2, 3, 4, 5, 6, 8, 7]
2+
3+
4+
{Right = 1}

src/main/java/org/teachingextensions/logo/PuzzlePlayerTest.java renamed to src/test/java/org/teachingextensions/logo/PuzzlePlayerTest.java

File renamed without changes.

src/test/java/org/teachingextensions/logo/PuzzleStateTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,26 @@ public void not_equal_to_state_with_different_puzzle() {
108108
assertNotEquals(a, b);
109109
}
110110

111+
/**
112+
* The estimated cost is zero when puzzle is solved.
113+
*/
114+
@Test
115+
public void estimated_cost_is_zero_when_solved() {
116+
PuzzleState s = new PuzzleState(getSolvedPuzzle());
117+
assertEquals(0, s.getEstimatedCost());
118+
}
119+
120+
/**
121+
* The estimated cost is the distance to goal when the puzzle is not solved.
122+
*/
123+
@Test
124+
public void estimated_cost_is_goal_distance_when_unsolved() {
125+
Puzzle p = getPuzzle(7);
126+
p = p.swapBlank(4);
127+
PuzzleState s = new PuzzleState(p);
128+
assertEquals(p.getDistanceToGoal(), s.getEstimatedCost());
129+
}
130+
111131
/**
112132
* Is equal to another state when the puzzles are the same
113133
*/

0 commit comments

Comments
 (0)