Skip to content

Commit 99a37f1

Browse files
committed
cost, equality
1 parent 937b74a commit 99a37f1

File tree

5 files changed

+160
-71
lines changed

5 files changed

+160
-71
lines changed

src/main/java/org/teachingkidsprogramming/section08tdd/PuzzleBoard.java

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,26 @@
1313
public class PuzzleBoard extends JPanel {
1414
private static final String completed = "Batgirl.png";
1515
private static final long serialVersionUID = -3592444274530147326L;
16-
private final List<Tile> tiles;
17-
private final Point[] positions;
18-
private boolean done;
16+
private final List<Tile> tiles;
17+
private final Point[] positions;
18+
private final List<PuzzleBoard> history;
19+
private boolean done;
1920

2021
public PuzzleBoard() {
2122
this.positions = createPositions();
2223
this.tiles = createTiles(shuffled(this.positions));
23-
24+
this.history = new ArrayList<>();
2425
}
2526

2627
public PuzzleBoard(Point[] positions, List<Tile> tiles) {
28+
this(positions, tiles, new ArrayList<PuzzleBoard>());
29+
}
30+
31+
public PuzzleBoard(PuzzleBoard puzzle) {
32+
this(puzzle.positions, puzzle.tiles, puzzle.history);
33+
}
34+
35+
public PuzzleBoard(Point[] positions, List<Tile> tiles, List<PuzzleBoard> history) {
2736
this.positions = new Point[positions.length];
2837
for (int i = 0; i < positions.length; i++) {
2938
this.positions[i] = new Point(positions[i]);
@@ -34,14 +43,18 @@ public PuzzleBoard(Point[] positions, List<Tile> tiles) {
3443
Tile t = tiles.get(i);
3544
this.tiles.add(i, new Tile(t));
3645
}
37-
}
3846

39-
public PuzzleBoard(PuzzleBoard puzzle) {
40-
this(puzzle.positions, puzzle.tiles);
47+
if (history == null) history = new ArrayList<>();
48+
49+
this.history = new ArrayList<>(history.size());
50+
for (int i = 0; i < history.size(); i++) {
51+
PuzzleBoard b = history.get(i);
52+
this.history.add(i, new PuzzleBoard(b));
53+
}
4154
}
4255

43-
private static List<Point> shuffled(Point[] positions) {
44-
List<Point> s = Arrays.asList(positions);
56+
public static List<Point> shuffled(Point[] positions) {
57+
List<Point> s = new ArrayList<>(Arrays.asList(positions));
4558
Collections.shuffle(s);
4659
return s;
4760
}
@@ -189,42 +202,11 @@ public PuzzleBoard useMove(TileMove move) {
189202
Tile s = c.getPieceFromPosition(move.getSource());
190203
s.moveTo(c.positions[move.getTarget()]);
191204
s.teleport();
205+
c.history.add(new PuzzleBoard(this));
192206

193207
return c;
194208
}
195209

196-
/**
197-
* Estimate the cost of solving the puzzle if this is the next step in the
198-
* solution.
199-
*
200-
* @param history
201-
* All the steps we have already visited.
202-
* @return The estimated cost
203-
*/
204-
public int estimateCost(List<PuzzleBoard> history) {
205-
206-
// see
207-
// https://jdanger.com/solving-8-puzzle-with-artificial-intelligence.html
208-
// for ruby implementation
209-
210-
// We know the actual cost will be at least 1, because we must
211-
// make at least one move to get to this board.
212-
int cost = 1;
213-
214-
// Each tile that is in the wrong position will require at least
215-
// one move to get it in the right position (and possibly more than
216-
// 1). So add 1 to the estimate for every misplaced tile.
217-
cost += this.countMisplaced();
218-
219-
// If we have visited this board before, then we are actually
220-
// going backward. We will need at least one more move after
221-
// this one to move forward to the solution. So add one more move
222-
// to the estimate for every time we have visited this board.
223-
cost += this.timesVisited(history);
224-
225-
return cost;
226-
}
227-
228210
@Override
229211
public boolean equals(Object o) {
230212
if (this == o)
@@ -234,7 +216,7 @@ public boolean equals(Object o) {
234216

235217
PuzzleBoard that = (PuzzleBoard) o;
236218

237-
return tiles.equals(that.tiles);
219+
return this.toString().equals(that.toString());
238220

239221
}
240222

@@ -243,17 +225,6 @@ public int hashCode() {
243225
return tiles.hashCode();
244226
}
245227

246-
private int timesVisited(List<PuzzleBoard> history) {
247-
int visited = 0;
248-
for (PuzzleBoard puzzleBoard : history) {
249-
if (this.equals(puzzleBoard)) {
250-
visited++;
251-
}
252-
}
253-
254-
return visited;
255-
}
256-
257228
public Tile getPieceFromPosition(int source) {
258229
Point position = this.positions[source];
259230
for (Tile tile : this.tiles) {
@@ -264,4 +235,24 @@ public Tile getPieceFromPosition(int source) {
264235

265236
return null;
266237
}
238+
239+
public List<PuzzleBoard> getHistory() {
240+
return new ArrayList<>(this.history);
241+
}
242+
243+
@Override
244+
public String toString() {
245+
StringBuilder builder = new StringBuilder();
246+
builder.append("[ ");
247+
for(Tile t : this.tiles){
248+
builder.append(t.toString()).append(", ");
249+
}
250+
builder.append("]");
251+
252+
return builder.toString();
253+
}
254+
255+
public boolean isVisited() {
256+
return this.history.contains(this);
257+
}
267258
}

src/main/java/org/teachingkidsprogramming/section08tdd/PuzzleSolver.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
public class PuzzleSolver implements Runnable {
99
private final PuzzleBoard board;
10-
private final List<PuzzleBoard> history = new ArrayList<>();
1110

1211
public PuzzleSolver(PuzzleBoard board) {
1312
this.board = board;
@@ -50,7 +49,6 @@ private void update(final PuzzleBoard puzzle) {
5049
}
5150

5251
private void solve(PuzzleBoard puzzle) {
53-
this.copyToHistory(puzzle);
5452

5553
int minimumCost = Integer.MAX_VALUE;
5654
TileMove min = null;
@@ -64,8 +62,14 @@ private void solve(PuzzleBoard puzzle) {
6462

6563
// otherwise apply the move to generate a new board
6664
PuzzleBoard next = puzzle.useMove(tileMove);
65+
66+
// see if we have visited this board before:
67+
if (next.isVisited()){
68+
continue;
69+
}
70+
6771
// estimate the cost to reach the goal by going through the new board
68-
int estimate = estimateSolvingCost(next, this.history);
72+
int estimate = estimateCost(next);
6973

7074
// if the cost is the smallest we've seen so far
7175
if (estimate < minimumCost) {
@@ -87,10 +91,6 @@ private void solve(PuzzleBoard puzzle) {
8791
}
8892
}
8993

90-
private int estimateSolvingCost(PuzzleBoard next, List<PuzzleBoard> history) {
91-
return next.estimateCost(new ArrayList<>(history));
92-
}
93-
9494
// 0: can move down (to #3) or right (to #1)
9595
// 1: can move left (to #0), down (to #4), or right (to #2)
9696
// 2: can move left (to #1) or down (to #5)
@@ -129,10 +129,6 @@ private List<TileMove> createMoves() {
129129
return moves;
130130
}
131131

132-
private void copyToHistory(PuzzleBoard puzzle) {
133-
this.history.add(new PuzzleBoard(puzzle));
134-
}
135-
136132
private boolean animate(PuzzleBoard puzzle) {
137133
List<Tile> tiles = puzzle.getTiles();
138134
for (Tile tile : tiles) {
@@ -160,7 +156,7 @@ public static int distance(Point start, Point end) {
160156
return Math.abs(start.x - end.x) + Math.abs(start.y - end.y);
161157
}
162158

163-
public static int distance(PuzzleBoard start) {
159+
public static int estimateRemainingMoves(PuzzleBoard start) {
164160
int result = 0;
165161
for (Tile tile : start.getTiles()) {
166162
int goalIndex = tile.getCorrectPositionIndex();
@@ -170,4 +166,16 @@ public static int distance(PuzzleBoard start) {
170166
}
171167
return result;
172168
}
169+
170+
public static int estimateCost(PuzzleBoard board) {
171+
// The cost of this solving the puzzle using this is at least the number of moves it took to get to this
172+
// board
173+
int cost = board.getHistory().size();
174+
175+
// We estimate that the cost of solving the puzzle from here will be related to the number of moves needed
176+
// to get the tiles into the right positions, so we add the estimate to the actual cost
177+
cost += estimateRemainingMoves(board);
178+
179+
return cost;
180+
}
173181
}

src/main/java/org/teachingkidsprogramming/section08tdd/Tile.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ public void step() {
5555
this.position = new Point(x, y);
5656
}
5757

58+
@Override
59+
public boolean equals(Object o) {
60+
if (this == o) return true;
61+
if (o == null || getClass() != o.getClass()) return false;
62+
63+
Tile tile = (Tile) o;
64+
65+
return correctPositionIndex == tile.correctPositionIndex && position.equals(tile.position);
66+
67+
}
68+
69+
@Override
70+
public int hashCode() {
71+
int result = position.hashCode();
72+
result = 31 * result + correctPositionIndex;
73+
return result;
74+
}
75+
5876
private int stepVertical(int size) {
5977
if (this.position.y < this.target.y) {
6078
return this.position.y + size;
@@ -63,6 +81,7 @@ private int stepVertical(int size) {
6381
return this.position.y - size;
6482
}
6583
return this.position.y;
84+
6685
}
6786

6887
private int stepHorizontal(int size) {
@@ -94,4 +113,9 @@ public int getCorrectPositionIndex() {
94113
public Point getPosition() {
95114
return position;
96115
}
116+
117+
@Override
118+
public String toString() {
119+
return "{ " + this.position.x + ", " + this.position.y + " }";
120+
}
97121
}

src/test/java/org/teachingextensions/logo/tests/PuzzleBoardTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.teachingextensions.logo.tests;
22

3+
import static org.junit.Assert.assertEquals;
34
import static org.junit.Assert.assertFalse;
45
import static org.junit.Assert.assertTrue;
56

67
import java.awt.Point;
8+
import java.util.ArrayList;
9+
import java.util.Arrays;
10+
import java.util.Collections;
711
import java.util.List;
812

913
import org.junit.Test;
@@ -59,4 +63,38 @@ public void invalidMoveTest() {
5963
assertFalse(b.isValidMove(move));
6064
}
6165

66+
@Test
67+
public void toStringTest(){
68+
assertEquals("[ { 35, 35 }, { 35, 162 }, { 35, 289 }, { 162, 35 }, { 162, 162 }, { 162, 289 }, { 289, 35 }, { 289, 162 }, ]", getSolvedBoard().toString());
69+
}
70+
71+
@Test
72+
public void equalityTest(){
73+
Point[] positions = PuzzleBoard.createPositions();
74+
List<Tile> tiles = PuzzleBoard.createTiles(positions);
75+
PuzzleBoard a = new PuzzleBoard(positions, tiles);
76+
List<PuzzleBoard> history = new ArrayList<>();
77+
history.add(a);
78+
PuzzleBoard b = new PuzzleBoard(positions, tiles, history);
79+
assertTrue(a.equals(b));
80+
}
81+
82+
@Test
83+
public void inequalityTest(){
84+
Point[] positions = PuzzleBoard.createPositions();
85+
List<Tile> tiles = PuzzleBoard.createTiles(positions);
86+
PuzzleBoard a = new PuzzleBoard(positions, tiles);
87+
88+
List<PuzzleBoard> history = new ArrayList<>();
89+
history.add(a);
90+
91+
Point[] locations = PuzzleBoard.createPositions();
92+
Point[] array = new Point[locations.length];
93+
List<Point> l = PuzzleBoard.shuffled(locations);
94+
l.toArray(array);
95+
List<Tile> t = PuzzleBoard.createTiles(l);
96+
PuzzleBoard b = new PuzzleBoard(PuzzleBoard.createPositions(), t, history);
97+
assertFalse(a.equals(b));
98+
}
99+
62100
}

src/test/java/org/teachingextensions/logo/tests/PuzzleSolverTest.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.teachingkidsprogramming.section08tdd.Tile;
77

88
import java.awt.*;
9+
import java.util.ArrayList;
910
import java.util.List;
1011

1112
import static org.junit.Assert.assertEquals;
@@ -56,14 +57,14 @@ public void manhattanDistanceFromEightToZeroTest() {
5657

5758
@Test
5859
public void distanceFromSolvedToSolved() {
59-
PuzzleBoard solved = getSolvedPuzzle();
60-
assertEquals(0, PuzzleSolver.distance(solved));
60+
PuzzleBoard solved = getSolvedPuzzle(null);
61+
assertEquals(0, PuzzleSolver.estimateRemainingMoves(solved));
6162
}
6263

63-
private PuzzleBoard getSolvedPuzzle() {
64+
private PuzzleBoard getSolvedPuzzle(List<PuzzleBoard> history) {
6465
Point[] positions = PuzzleBoard.createPositions();
6566
List<Tile> tiles = PuzzleBoard.createTiles(positions);
66-
return new PuzzleBoard(positions, tiles);
67+
return new PuzzleBoard(positions, tiles, history);
6768
}
6869

6970
@Test
@@ -78,7 +79,7 @@ public void distanceForPuzzleWithOneMisplacedTile() {
7879
List<Tile> tiles = PuzzleBoard.createTiles(positions);
7980
PuzzleBoard b = new PuzzleBoard(PuzzleBoard.createPositions(), tiles);
8081
int expected = PuzzleSolver.distance(positions[8], positions[7]);
81-
assertEquals(expected, PuzzleSolver.distance(b));
82+
assertEquals(expected, PuzzleSolver.estimateRemainingMoves(b));
8283
}
8384

8485
@Test
@@ -104,7 +105,34 @@ public void distanceForPuzzleWithSeveralMisplacedTiles() {
104105

105106

106107
PuzzleBoard b = new PuzzleBoard(PuzzleBoard.createPositions(), tiles);
107-
assertEquals(expected, PuzzleSolver.distance(b));
108+
assertEquals(expected, PuzzleSolver.estimateRemainingMoves(b));
109+
}
110+
111+
@Test
112+
public void solutionHasNoCostTest() {
113+
PuzzleBoard b = getSolvedPuzzle(null);
114+
assertEquals(0, PuzzleSolver.estimateCost(b));
115+
}
116+
117+
@Test
118+
public void solutionHasHistoryCost() {
119+
PuzzleBoard b = getSolvedPuzzle(null);
120+
List<PuzzleBoard> history = new ArrayList<>();
121+
history.add(b);
122+
history.add(b);
123+
history.add(b);
124+
PuzzleBoard c = getSolvedPuzzle(history);
125+
assertEquals(3, PuzzleSolver.estimateCost(c));
126+
}
127+
128+
@Test
129+
public void unsolvedPuzzleHasMovementCost() {
130+
Point[] positions = PuzzleBoard.createPositions();
131+
positions = swap(positions, 7, 8);
132+
List<Tile> tiles = PuzzleBoard.createTiles(positions);
133+
134+
PuzzleBoard board = new PuzzleBoard(PuzzleBoard.createPositions(), tiles);
135+
assertEquals(127, PuzzleSolver.estimateCost(board));
108136
}
109137

110138
private Point[] swap(Point[] positions, int i, int j) {

0 commit comments

Comments
 (0)