/* This file is part of Eternity II Editor.
*
* Eternity II Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Eternity II Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Eternity II Editor. If not, see <http://www.gnu.org/licenses/>.
*
* Eternity II Editor project is hosted on SourceForge:
* http://sourceforge.net/projects/eternityii/
* and maintained by Yannick Kirschhoffer <alcibiade@alcibiade.org>
*/
package org.alcibiade.eternity.editor.solver.swap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.alcibiade.eternity.editor.model.GridModel;
import org.alcibiade.eternity.editor.model.QuadModel;
import org.alcibiade.eternity.editor.solver.ClusterListener;
import org.alcibiade.eternity.editor.solver.ClusterManager;
import org.alcibiade.eternity.editor.solver.EternitySolver;
import org.alcibiade.eternity.editor.solver.RandomFactory;
public class AStarSolverMkI extends EternitySolver implements ClusterListener {
private GridModel solutionGrid;
// This is set to problemGrid.getSize()^2 for convenience.
private int positions;
// This is set to problemGrid.getSize() for convenience.
private int gsize;
private GridModel problemGrid;
private long iterations = 0;
private Random random;
public AStarSolverMkI(GridModel problemGrid, GridModel solutionGrid,
ClusterManager clusterManager) {
super(clusterManager);
this.solutionGrid = solutionGrid;
this.problemGrid = problemGrid;
solutionGrid.reset();
solutionGrid.setSize(problemGrid.getSize());
this.gsize = problemGrid.getSize();
this.positions = gsize * gsize;
this.random = RandomFactory.getRandom();
}
@Override
public String getSolverName() {
return "AStar Solver MkI $Revision: 245 $";
}
@Override
public long getIterations() {
return iterations;
}
@Override
public void run() {
notifyStart();
clusterManager.showStartMessage();
moveLockedPieces();
boolean solved = solve();
if (solved) {
clusterManager.submitSolution(solutionGrid);
clusterManager.showStats(iterations);
}
notifyEnd(solved);
}
private void moveLockedPieces() {
int gsize = solutionGrid.getSize();
int positions = gsize * gsize;
for (int i = 0; i < positions; i++) {
QuadModel piece = problemGrid.getQuad(i);
if (piece.isLocked()) {
piece.copyTo(solutionGrid.getQuad(i));
piece.clear();
}
}
for (int i = 0; i < positions; i++) {
QuadModel piece = problemGrid.getQuad(i);
if (!piece.isLocked()) {
for (int j = 0; j < positions; j++) {
QuadModel solPiece = solutionGrid.getQuad(j);
if (solPiece.isClear()
&& solutionGrid.countSides(j) == piece.countDefaultPattern()) {
piece.copyTo(solPiece);
solutionGrid.optimizeQuadRotation(j);
break;
}
}
}
}
}
@Override
public void bestSolutionUpdated(int bestScore) {
// Do nothing
}
private boolean solve() {
// http://en.wikipedia.org/wiki/A*
// System.out.println(solutionGrid.toQuadString());
boolean result = false;
SortedSet<Path> openSet = new TreeSet<Path>();
openSet.add(new Path());
Set<Path> closedSet = new HashSet<Path>();
while (!openSet.isEmpty()) {
if (interrupted) {
return false;
}
if (slowmotion) {
try {
Thread.sleep(SLOWMOTION_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Lowest f_score
Path x = openSet.first();
if (x.distance() == 0) {
for (Swap s : x) {
s.apply();
}
return true;
}
openSet.remove(x);
closedSet.add(x);
// System.out.println("" + openSet.size() + ", " + closedSet.size()
// + ", " + x);
// Find neighbors
Set<Path> neighbors = computeNeighbors(x);
for (Path y : neighbors) {
if (closedSet.contains(y)) {
continue;
}
if (!openSet.contains(y)) {
openSet.add(y);
}
}
iterations++;
}
return result;
}
private Set<Path> computeNeighbors(Path p) {
Set<Path> result = new HashSet<Path>();
if (p.size() < solutionGrid.getPositions() - 1) {
for (int i = 0; i < positions; i++) {
p.apply();
QuadModel quad = solutionGrid.getQuad(i);
List<Integer> possibleIndices = new ArrayList<Integer>();
for (int j = 0; j < positions; j++) {
if (i != j && solutionGrid.countSides(j) == quad.countDefaultPattern()) {
possibleIndices.add(j);
}
}
p.revert();
if (!possibleIndices.isEmpty()) {
int randomIndex = random.nextInt(possibleIndices.size());
int dest = possibleIndices.get(randomIndex);
result.add(new Path(p, new Swap(i, dest)));
}
}
}
return result;
}
protected class Path extends ArrayList<Swap> implements Comparable<Path> {
private static final long serialVersionUID = 1L;
private int score = 0;
public Path() {
// Do nothing
score = _distance();
}
public Path(Path p, Swap swap) {
addAll(p);
add(swap);
score = _distance();
}
public int distance() {
return score;
}
private int _distance() {
apply();
score = 0;
score += solutionGrid.countConnections();
score -= solutionGrid.countPairs();
score *= score;
if (score > 0) {
score += size();
}
revert();
return score;
}
public void revert() {
for (int i = size() - 1; i >= 0; i--) {
get(i).revert();
}
}
public void apply() {
for (int i = 0; i < size(); i++) {
get(i).apply();
}
}
@Override
public String toString() {
return super.toString() + " (score: " + score + ")";
}
@Override
public boolean equals(Object o) {
boolean result = super.equals(o);
return result;
}
@Override
public int compareTo(Path o) {
int result = score - o.score;
if (result == 0) {
result = size() - o.size();
}
for (int i = 0; result == 0 && i < size(); i++) {
Swap ms = get(i);
Swap os = o.get(i);
result = ms.getSrcIndex() - os.getSrcIndex();
if (result == 0) {
result = ms.getDstIndex() - os.getDstIndex();
}
}
return result;
}
}
protected class Swap {
private int srcIndex;
private int dstIndex;
private int srcOrientation;
private int dstOrientation;
public Swap(int src, int dst) {
this.srcIndex = src;
this.dstIndex = dst;
}
public int getSrcIndex() {
return srcIndex;
}
public int getDstIndex() {
return dstIndex;
}
public void apply() {
// System.out.println(solutionGrid.toQuadString());
//
// System.out
// .println("Swapping " + this + " // " + srcOrientation + "|" +
// dstOrientation);
solutionGrid.swap(srcIndex, dstIndex);
srcOrientation = solutionGrid.optimizeQuadRotation(srcIndex);
dstOrientation = solutionGrid.optimizeQuadRotation(dstIndex);
// System.out.println(solutionGrid.toQuadString());
}
public void revert() {
// System.out.println(solutionGrid.toQuadString());
//
// System.out.println("Reverting " + this + " // " + srcOrientation
// + "|"
// + dstOrientation);
solutionGrid.swap(srcIndex, dstIndex);
solutionGrid.getQuad(srcIndex).rotateCounterclockwise(dstOrientation);
solutionGrid.getQuad(dstIndex).rotateCounterclockwise(srcOrientation);
// System.out.println(solutionGrid.toQuadString());
}
@Override
public boolean equals(Object obj) {
boolean result = false;
if (obj instanceof Swap) {
Swap os = (Swap) obj;
result = (srcIndex == os.srcIndex && dstIndex == os.dstIndex)
|| (srcIndex == os.dstIndex && dstIndex == os.srcIndex);
}
return result;
}
@Override
public int hashCode() {
return srcIndex + dstIndex;
}
@Override
public String toString() {
return "" + srcIndex + "<->" + dstIndex;
}
}
}