/* 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.backtracking;
import org.alcibiade.eternity.editor.model.GridModel;
import org.alcibiade.eternity.editor.model.Pattern;
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.path.PathProvider;
public class IterativePathSolverMkIII extends EternitySolver implements ClusterListener {
protected static Pattern defaultpat = Pattern.getDefaultPattern();
protected GridModel solutionGrid;
// This is set to problemGrid.getSize()^2 for convenience.
protected int positions;
// This is set to problemGrid.getSize() for convenience.
protected int gsize;
// The number of quads that aren't locked in the initial position. This will
// represent our maximal search depth.
private int openPositions;
protected int record = 0;
protected PathProvider path;
protected GridModel pieces;
protected long iterations = 0;
public IterativePathSolverMkIII(GridModel grid, GridModel solutionGrid,
ClusterManager clusterManager, PathProvider pathProvider) {
super(clusterManager);
this.solutionGrid = solutionGrid;
pieces = grid.clone();
solutionGrid.reset();
solutionGrid.setSize(grid.getSize());
this.gsize = grid.getSize();
this.positions = gsize * gsize;
this.path = pathProvider;
// Compute the number of open positions to search
this.openPositions = 0;
for (QuadModel quad : grid) {
if (!quad.isLocked()) {
openPositions += 1;
}
}
}
@Override
public String getSolverName() {
return "Iterative Path Solver MkIII $Revision: 245 $";
}
@Override
public long getIterations() {
return iterations;
}
@Override
public void run() {
notifyStart();
clusterManager.showStartMessage();
moveLockedPieces();
boolean solved = solve(0);
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 = pieces.getQuad(i);
if (piece.isLocked()) {
piece.copyTo(solutionGrid.getQuad(i));
piece.clear();
}
}
}
private boolean solve(int destOffset) {
boolean result = false;
if (interrupted) {
return false;
}
if (slowmotion) {
try {
Thread.sleep(SLOWMOTION_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
iterations++;
if (destOffset == openPositions) {
result = true;
} else {
updateRecordData(destOffset);
int destIndex = path.getNextPathIndex(pieces, solutionGrid);
QuadModel dest = solutionGrid.getQuad(destIndex);
result = explore(destOffset, destIndex);
if (!result) {
dest.clear();
}
}
return result;
}
private void updateRecordData(int destOffset) {
if (destOffset > record) {
GridModel remaining = pieces.clone();
GridModel solutionGrid = this.solutionGrid.clone();
for (int i = destOffset; i < openPositions; i++) {
int offs = path.getNextPathIndex(pieces, solutionGrid);
QuadModel target = solutionGrid.getQuad(offs);
// Target may not be clear if there are locked quads already in
// place.
if (target.isClear()) {
QuadModel missing = solutionGrid.getMissingQuad(offs);
int bestFitness = Integer.MIN_VALUE;
QuadModel bestQuad = null;
for (QuadModel q : remaining) {
if (!q.isClear()
&& q.countDefaultPattern() == solutionGrid
.countExternalBorders(offs)) {
int fitness = q.matchDegrees(missing);
if (fitness > bestFitness) {
bestFitness = fitness;
bestQuad = q;
}
}
}
bestQuad.copyTo(target);
bestQuad.clear();
solutionGrid.optimizeQuadRotation(offs);
}
}
clusterManager.submitSolution(solutionGrid);
record = destOffset;
}
}
private boolean explore(int destOffset, int destIndex) {
boolean result = false;
QuadModel dest = solutionGrid.getQuad(destIndex);
for (int srcIndex = 0; !result && srcIndex < positions; srcIndex++) {
QuadModel src = pieces.getQuad(srcIndex);
if (src.isClear()) {
continue;
}
QuadModel missing = solutionGrid.getMissingQuad(destIndex);
if (missing.countDefaultPattern() != src.countDefaultPattern()) {
continue;
}
src.copyTo(dest);
src.clear();
for (int rot = 0; !result && rot < 4; rot++) {
dest.rotateClockwise();
if (dest.matches(missing)) {
result = solve(destOffset + 1);
}
}
dest.copyTo(src);
if (!result) {
dest.clear();
}
}
return result;
}
public void bestSolutionUpdated(int bestScore) {
if (clusterManager.isSolutionFound()) {
this.interrupt();
}
}
}