package net.gnehzr.tnoodle.test;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzert;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzertEquals;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzertNotEquals;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzertSame;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.choose;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Random;
import java.util.SortedMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.gnehzr.tnoodle.scrambles.AlgorithmBuilder;
import net.gnehzr.tnoodle.scrambles.AlgorithmBuilder.MergingMode;
import net.gnehzr.tnoodle.scrambles.InvalidMoveException;
import net.gnehzr.tnoodle.scrambles.InvalidScrambleException;
import net.gnehzr.tnoodle.scrambles.Puzzle;
import net.gnehzr.tnoodle.scrambles.PuzzlePlugins;
import net.gnehzr.tnoodle.scrambles.Puzzle.PuzzleState;
import net.gnehzr.tnoodle.scrambles.ScrambleCacher;
import net.gnehzr.tnoodle.scrambles.ScrambleCacherListener;
import net.gnehzr.tnoodle.utils.BadLazyClassDescriptionException;
import net.gnehzr.tnoodle.utils.LazyInstantiatorException;
import net.gnehzr.tnoodle.utils.LazyInstantiator;
import net.gnehzr.tnoodle.utils.TimedLogRecordStart;
import net.gnehzr.tnoodle.utils.TNoodleLogging;
import net.gnehzr.tnoodle.utils.Utils;
import puzzle.ClockPuzzle;
import puzzle.ClockPuzzle.ClockState;
import puzzle.SquareOnePuzzle;
import puzzle.CubePuzzle;
import puzzle.CubePuzzle.CubeState;
import puzzle.ThreeByThreeCubePuzzle;
import puzzle.PyraminxPuzzle;
import puzzle.PyraminxPuzzle.PyraminxState;
import puzzle.PyraminxSolver;
import puzzle.PyraminxSolver.PyraminxSolverState;
import puzzle.MegaminxPuzzle;
import puzzle.TwoByTwoSolver;
import puzzle.TwoByTwoSolver.TwoByTwoState;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.Exportable;
public class ScrambleTest {
private static final Logger l = Logger.getLogger(ScrambleTest.class.getName());
private static final Random r = Utils.getSeededRandom();
static class LockHolder extends Thread {
public LockHolder() {
setDaemon(true);
}
private Object o;
public void setObjectToLock(Object o) {
synchronized(this) {
this.o = o;
if(isAlive()) {
notify();
} else {
start();
}
}
try {
Thread.sleep(100); // give the locker thread a chance to grab the lock
} catch(InterruptedException e) {
e.printStackTrace();
}
}
@Override
public synchronized void run() {
while(o != null) {
synchronized(o) {
System.out.println("GOT LOCK " + o);
Object locked = o;
while(o == locked) {
try {
wait();
} catch (InterruptedException e) {}
}
}
}
}
}
public static void testScrambleFiltering() throws BadLazyClassDescriptionException, LazyInstantiatorException, InvalidScrambleException, IOException {
System.out.println("Testing scramble filtering");
int SCRAMBLE_COUNT = 10;
SortedMap<String, LazyInstantiator<Puzzle>> lazyScramblers = PuzzlePlugins.getScramblers();
for(String puzzle : lazyScramblers.keySet()) {
LazyInstantiator<Puzzle> lazyScrambler = lazyScramblers.get(puzzle);
final Puzzle scrambler = lazyScrambler.cachedInstance();
for(int count = 0; count < SCRAMBLE_COUNT; count++){
PuzzleState state = scrambler.getSolvedState().applyAlgorithm(scrambler.generateWcaScramble(r));
azzertSame(state.solveIn(scrambler.getWcaMinScrambleDistance() - 1), null);
}
}
}
private static void testSolveIn() throws InvalidScrambleException, BadLazyClassDescriptionException, LazyInstantiatorException, IOException {
int SCRAMBLE_COUNT = 10;
int SCRAMBLE_LENGTH = 4;
SortedMap<String, LazyInstantiator<Puzzle>> lazyScramblers = PuzzlePlugins.getScramblers();
for(String puzzle : lazyScramblers.keySet()) {
LazyInstantiator<Puzzle> lazyScrambler = lazyScramblers.get(puzzle);
final Puzzle scrambler = lazyScrambler.cachedInstance();
System.out.println("Testing " + puzzle);
// Test solving the solved state
String solution = scrambler.getSolvedState().solveIn(0);
azzertEquals("", solution);
for(int count = 0; count < SCRAMBLE_COUNT; count++) {
System.out.print("Scramble ["+(count+1)+"/"+SCRAMBLE_COUNT+"]: ");
PuzzleState state = scrambler.getSolvedState();
for(int i = 0; i < SCRAMBLE_LENGTH; i++){
HashMap<String, ? extends PuzzleState> successors = state.getSuccessorsByName();
String move = choose(r, successors.keySet());
System.out.print(" "+move);
state = successors.get(move);
}
System.out.print("...");
solution = state.solveIn(SCRAMBLE_LENGTH);
azzert(solution != null, "Puzzle "+scrambler.getShortName()+" solveIn method failed!");
System.out.println("Found: "+solution);
state = state.applyAlgorithm(solution);
azzert(state.isSolved(), "Solution was not correct");
}
}
}
private static void testThreads() throws LazyInstantiatorException, InvalidScrambleException, BadLazyClassDescriptionException, IOException {
LockHolder lh = new LockHolder();
int SCRAMBLE_COUNT = 10;
boolean drawScramble = true;
SortedMap<String, LazyInstantiator<Puzzle>> lazyScramblers = PuzzlePlugins.getScramblers();
for(String puzzle : lazyScramblers.keySet()) {
LazyInstantiator<Puzzle> lazyScrambler = lazyScramblers.get(puzzle);
final Puzzle scrambler = lazyScrambler.cachedInstance();
System.out.println("Testing " + puzzle);
// It's easy to get this wrong (read about Arrays.hashCode vs Arrays.deepHashCode).
// This is just a sanity check.
azzert(scrambler.getSolvedState().hashCode() == scrambler.getSolvedState().hashCode());
// Generating a scramble
System.out.println("Generating a " + puzzle + " scramble");
String scramble;
lh.setObjectToLock(scrambler);
scramble = scrambler.generateScramble();
// Drawing that scramble
System.out.println("Drawing " + scramble);
scrambler.drawScramble(scramble, null);
// Scramblers should support "null" as the empty scramble
scrambler.drawScramble(null, null);
System.out.println("Generating & drawing 2 sets of " + SCRAMBLE_COUNT + " scrambles simultaneously." +
" This is meant to shake out threading problems in scramblers.");
final Object[] o = new Object[0];
ScrambleCacherListener cacherStopper = new ScrambleCacherListener() {
@Override
public void scrambleCacheUpdated(ScrambleCacher src) {
System.out.println(Thread.currentThread() + " " + src.getAvailableCount() + " / " + src.getCacheSize());
if(src.getAvailableCount() == src.getCacheSize()) {
src.stop();
synchronized(o) {
o.notify();
}
}
}
};
ScrambleCacher c1 = new ScrambleCacher(scrambler, SCRAMBLE_COUNT, drawScramble, cacherStopper);
ScrambleCacher c2 = new ScrambleCacher(scrambler, SCRAMBLE_COUNT, drawScramble, cacherStopper);
while(c1.isRunning() || c2.isRunning()) {
synchronized(o) {
try {
o.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
lh.setObjectToLock(null);
System.out.println("\nTest passed!");
}
private static void testNames() throws BadLazyClassDescriptionException, LazyInstantiatorException, IOException {
SortedMap<String, LazyInstantiator<Puzzle>> lazyScramblers = PuzzlePlugins.getScramblers();
// Check that the names by which the scramblers refer to themselves
// is the same as the names by which we refer to them in the plugin definitions file.
for(String shortName : lazyScramblers.keySet()) {
String longName = PuzzlePlugins.getScramblerLongName(shortName);
LazyInstantiator<Puzzle> lazyScrambler = lazyScramblers.get(shortName);
Puzzle scrambler = lazyScrambler.cachedInstance();
azzertEquals(shortName, scrambler.getShortName());
azzertEquals(longName, scrambler.getLongName());
System.out.println(Exportable.class + " isAssignableFrom " + scrambler.getClass());
azzert(Exportable.class.isAssignableFrom(scrambler.getClass()));
Annotation[] annotations = scrambler.getClass().getAnnotations();
boolean foundExport = false;
for(Annotation annotation : annotations) {
if(Export.class.isAssignableFrom(annotation.annotationType())) {
foundExport = true;
break;
}
}
azzert(foundExport);
}
}
private static void testClockPuzzle() throws InvalidScrambleException, InvalidMoveException {
ClockPuzzle clock = new ClockPuzzle();
ClockState state = (ClockState)clock.getSolvedState();
state = (ClockState)state.applyAlgorithm("ALL2+ y2 ALL1-"); // This scramble is breaking the solveIn method...
String solution = state.solveIn(3);
if(solution == null) {
System.out.println("No solution");
} else {
System.out.println(solution);
}
}
private static void testCubePuzzle() throws InvalidScrambleException, InvalidMoveException {
testCubeNormalization();
testTwosConverter();
testTwosSolver();
}
private static void testCubeNormalization() throws InvalidScrambleException, InvalidMoveException {
CubePuzzle fours = new CubePuzzle(4);
CubeState solved = fours.getSolvedState();
CubeState state = (CubeState) solved.applyAlgorithm("Rw Lw'");
CubeState normalizedState = state.getNormalized();
CubeState normalizedSolvedState = solved.getNormalized();
azzertEquals(normalizedState, normalizedSolvedState);
azzertEquals(normalizedState.hashCode(), normalizedSolvedState.hashCode());
state = (CubeState) solved.applyAlgorithm("Uw Dw'");
normalizedState = state.getNormalized();
azzertEquals(normalizedState, normalizedSolvedState);
CubePuzzle threes = new ThreeByThreeCubePuzzle();
solved = threes.getSolvedState();
CubeState bDone = (CubeState) solved.apply("B");
CubeState fwDone = (CubeState) solved.apply("Fw");
azzert(bDone.equalsNormalized(fwDone));
AlgorithmBuilder ab3 = new AlgorithmBuilder(threes, MergingMode.CANONICALIZE_MOVES);
String alg = "D2 U' L2 B2 F2 D B2 U' B2 F D' F U' R F2 L2 D' B D F'";
ab3.appendAlgorithm(alg);
azzertEquals(ab3.toString(), alg);
for(int depth = 0; depth < 100; depth++) {
state = choose(r, state.getSuccessorsByName().values());
normalizedState = state.getNormalized();
PuzzleState rotatedState = state.applyAlgorithm("Uw Dw'").getNormalized();
azzertEquals(normalizedState, rotatedState);
}
}
private static void testAlgorithmBuilder() throws InvalidMoveException {
System.out.println("Testing algorithm builder");
CubePuzzle fours = new CubePuzzle(4);
AlgorithmBuilder ab4 = new AlgorithmBuilder(fours, MergingMode.CANONICALIZE_MOVES);
String ogAlg = "Rw Lw";
ab4.appendAlgorithm(ogAlg);
String shortenedAlg = ab4.toString();
System.out.println(ogAlg + " -> " + shortenedAlg);
String[] shortenedAlgSplit = AlgorithmBuilder.splitAlgorithm(shortenedAlg);
azzertEquals(shortenedAlgSplit.length, 1);
Puzzle sq1 = new SquareOnePuzzle();
AlgorithmBuilder abSq1;
abSq1 = new AlgorithmBuilder(sq1, MergingMode.CANONICALIZE_MOVES);
abSq1.appendAlgorithm("(1,0) (0,1)");
azzertEquals(abSq1.toString(), "(1,1)");
abSq1 = new AlgorithmBuilder(sq1, MergingMode.CANONICALIZE_MOVES);
abSq1.appendAlgorithm("(0,1) (1,1)");
azzertEquals(abSq1.toString(), "(1,2)");
CubePuzzle fives = new CubePuzzle(5);
AlgorithmBuilder ab5 = new AlgorithmBuilder(fives, MergingMode.NO_MERGING);
String alg = "U R 4Rw'";
ab5.appendAlgorithm(alg);
azzertEquals(alg, ab5.toString());
}
private static void testTwosConverter() throws InvalidMoveException {
int orient = 0;
int permute = 0;
int MOVE_R = 3;
orient = TwoByTwoSolver.moveOrient[orient][MOVE_R];
permute = TwoByTwoSolver.movePerm[permute][MOVE_R];
CubePuzzle twos = new CubePuzzle(2);
CubeState state = (CubeState) twos.getSolvedState().apply("R");
TwoByTwoState twoByTwoState = state.toTwoByTwoState();
azzertEquals(twoByTwoState.orientation, orient);
azzertEquals(twoByTwoState.permutation, permute);
TwoByTwoSolver twoByTwoSolver = new TwoByTwoSolver();
azzertEquals(twoByTwoSolver.solveIn(twoByTwoState, 1), "R'");
int MOVE_R_PRIME = 5;
orient = TwoByTwoSolver.moveOrient[orient][MOVE_R_PRIME];
permute = TwoByTwoSolver.movePerm[permute][MOVE_R_PRIME];
azzertEquals(orient, 0);
azzertEquals(permute, 0);
}
private static void testTwosSolver() throws InvalidScrambleException {
CubePuzzle twos = new CubePuzzle(2);
CubeState state = (CubeState) twos.getSolvedState();
String solution = state.solveIn(0);
azzertEquals(solution, "");
state = (CubeState) state.applyAlgorithm("R2 B2 F2");
solution = state.solveIn(1);
azzertNotEquals(solution, null);
state = (CubeState) state.applyAlgorithm(solution);
azzert(state.isSolved());
}
private static void testPyraConverter() throws InvalidMoveException {
int SCRAMBLE_COUNT = 1000;
int SCRAMBLE_LENGTH = 20;
int edgePerm = 0;
int edgeOrient = 0;
int cornerOrient = 0;
int tips = 0;
final String[] moveToString = {"U", "U'", "L", "L'", "R", "R'", "B", "B'"};
PyraminxPuzzle pyra = new PyraminxPuzzle();
PyraminxState state = (PyraminxState) pyra.getSolvedState();
PyraminxSolverState sstate = state.toPyraminxSolverState();
azzertEquals(sstate.edgePerm, edgePerm);
azzertEquals(sstate.edgeOrient, edgeOrient);
azzertEquals(sstate.cornerOrient, cornerOrient);
azzertEquals(sstate.tips, tips);
for (int i = 0; i < SCRAMBLE_COUNT; i++){
System.out.println(" Scramble ["+i+"/"+SCRAMBLE_COUNT+"]");
edgePerm = 0;
edgeOrient = 0;
cornerOrient = 0;
state = (PyraminxState) pyra.getSolvedState();
for (int j = 0; j < SCRAMBLE_LENGTH; j++){
int move = r.nextInt(moveToString.length);
edgePerm = PyraminxSolver.moveEdgePerm[edgePerm][move];
edgeOrient = PyraminxSolver.moveEdgeOrient[edgeOrient][move];
cornerOrient = PyraminxSolver.moveCornerOrient[cornerOrient][move];
state = (PyraminxState) state.apply(moveToString[move]);
}
sstate = state.toPyraminxSolverState();
azzertEquals(sstate.edgePerm, edgePerm);
azzertEquals(sstate.edgeOrient, edgeOrient);
azzertEquals(sstate.cornerOrient, cornerOrient);
}
System.out.println("");
}
public static void testMega() throws InvalidScrambleException {
MegaminxPuzzle megaminx = new MegaminxPuzzle();
PuzzleState solved = megaminx.getSolvedState();
String spinL = "R++ L2'";
String spinU = "D++ U2'";
PuzzleState state = solved.applyAlgorithm(spinL).applyAlgorithm(spinU).applyAlgorithm(spinU).applyAlgorithm(spinL).applyAlgorithm(spinL).applyAlgorithm(spinL);
state = state.applyAlgorithm(spinU);
azzert(state.equalsNormalized(solved));
}
private static void benchmarking() throws BadLazyClassDescriptionException, LazyInstantiatorException, InvalidScrambleException, IOException {
// Analyze the 3x3x3 solver.
int THREE_BY_THREE_SCRAMBLE_COUNT = 100;
int THREE_BY_THREE_MAX_SCRAMBLE_LENGTH = 21;
int THREE_BY_THREE_TIMEMIN = 0; //milliseconds
int THREE_BY_THREE_TIMEOUT = 5*1000; //milliseconds
cs.min2phase.Search threeSolver = new cs.min2phase.Search();
cs.min2phase.Tools.init();
TimedLogRecordStart start = new TimedLogRecordStart(Level.INFO, "Searching for " + THREE_BY_THREE_SCRAMBLE_COUNT + " random 3x3x3 cubes in less that " + THREE_BY_THREE_MAX_SCRAMBLE_LENGTH + " moves");
l.log(start);
for(int i = 0; i < THREE_BY_THREE_SCRAMBLE_COUNT; i++){
threeSolver.solution(cs.min2phase.Tools.randomCube(r), THREE_BY_THREE_MAX_SCRAMBLE_LENGTH, THREE_BY_THREE_TIMEOUT, THREE_BY_THREE_TIMEMIN, cs.min2phase.Search.INVERSE_SOLUTION);
}
l.log(start.finishedNow());
// How long does it takes to test if a puzzle is solvable in <= 1 move?
int SCRAMBLE_COUNT = 100;
SortedMap<String, LazyInstantiator<Puzzle>> lazyScramblers = PuzzlePlugins.getScramblers();
for(String puzzle : lazyScramblers.keySet()) {
LazyInstantiator<Puzzle> lazyScrambler = lazyScramblers.get(puzzle);
final Puzzle scrambler = lazyScrambler.cachedInstance();
start = new TimedLogRecordStart(Level.INFO, "Are " + THREE_BY_THREE_SCRAMBLE_COUNT + " " + puzzle + " more than one move away from solved?");
l.log(start);
PuzzleState solved = scrambler.getSolvedState();
for(int count = 0; count < SCRAMBLE_COUNT; count++){
String scramble = scrambler.generateWcaScramble(r);
System.out.println("Searching for solution in <= 1 move to " + scramble);
PuzzleState state = solved.applyAlgorithm(scramble);
String solution = state.solveIn(1);
azzertEquals(solution, null);
}
l.log(start.finishedNow());
}
}
public static void main(String[] args) throws BadLazyClassDescriptionException, LazyInstantiatorException, InvalidScrambleException, InvalidMoveException, IOException {
TNoodleLogging.initializeLogging();
System.out.println("Testing names.");
testNames();
testAlgorithmBuilder();
System.out.println("Testing specific Puzzle issues.");
testClockPuzzle();
testCubePuzzle();
testPyraConverter();
testMega();
benchmarking();
System.out.println("Testing solveIn method");
testSolveIn();
testScrambleFiltering();
testThreads();
}
}