package puzzle; import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzert; import net.gnehzr.tnoodle.svglite.Color; import net.gnehzr.tnoodle.svglite.Svg; import net.gnehzr.tnoodle.svglite.Dimension; import net.gnehzr.tnoodle.svglite.Path; import net.gnehzr.tnoodle.svglite.Transform; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Random; import java.util.logging.Logger; import puzzle.SkewbSolver.SkewbSolverState; import net.gnehzr.tnoodle.scrambles.InvalidScrambleException; import net.gnehzr.tnoodle.scrambles.Puzzle; import net.gnehzr.tnoodle.scrambles.PuzzleStateAndGenerator; import net.gnehzr.tnoodle.utils.GwtSafeUtils; import org.timepedia.exporter.client.Export; @Export public class SkewbPuzzle extends Puzzle { private static final int MIN_SCRAMBLE_LENGTH = 11; private static final Logger l = Logger.getLogger(SkewbPuzzle.class.getName()); private SkewbSolver skewbSolver = null; private static final int pieceSize = 30; private static final int gap = 3; private static final double sq3d2 = Math.sqrt(3) / 2; public SkewbPuzzle() { skewbSolver = new SkewbSolver(); wcaMinScrambleDistance = 7; } @Override public PuzzleStateAndGenerator generateRandomMoves(Random r) { SkewbSolverState state = skewbSolver.randomState(r); String scramble = skewbSolver.generateExactly(state, MIN_SCRAMBLE_LENGTH, r); PuzzleState pState; try { pState = getSolvedState().applyAlgorithm(scramble); } catch (InvalidScrambleException e) { azzert(false, e); return null; } return new PuzzleStateAndGenerator(pState, scramble); } /************************************************************* * Functions to display the puzzle */ private static final HashMap<String, Color> defaultColorScheme = new HashMap<String, Color>(); static { defaultColorScheme.put("U", Color.WHITE); defaultColorScheme.put("R", Color.BLUE); defaultColorScheme.put("F", Color.RED); defaultColorScheme.put("D", Color.YELLOW); defaultColorScheme.put("L", Color.GREEN); defaultColorScheme.put("B", new Color(0xFF8000)); } @Override public HashMap<String, Color> getDefaultColorScheme() { return new HashMap<String, Color>(defaultColorScheme); } private Transform[] getFaceTrans() { Transform[] position = { new Transform(pieceSize*sq3d2, -pieceSize/2, pieceSize*sq3d2, pieceSize/2, (pieceSize*4+gap*1.5)*sq3d2, pieceSize), new Transform(pieceSize*sq3d2, -pieceSize/2, 0, pieceSize, (pieceSize*7+gap*3)*sq3d2, pieceSize * 1.5), new Transform(pieceSize*sq3d2, -pieceSize/2, 0, pieceSize, (pieceSize*5+gap*2)*sq3d2, pieceSize * 2.5 + 0.5 * gap), new Transform(0, pieceSize, -pieceSize*sq3d2, -pieceSize/2, (pieceSize*3+gap*1)*sq3d2, pieceSize * 4.5 + 1.5 * gap), new Transform(pieceSize*sq3d2, pieceSize/2, 0, pieceSize, (pieceSize*3+gap*1)*sq3d2, pieceSize * 2.5 + 0.5 * gap), new Transform(pieceSize*sq3d2, pieceSize/2, 0, pieceSize, pieceSize*sq3d2, pieceSize * 1.5), }; return position; } @Override public Dimension getPreferredSize() { return new Dimension( (int) Math.ceil((3 * gap + 8 * pieceSize + 1) * sq3d2), (int) Math.ceil(2 * gap + 6 * pieceSize + 1)); } @Override public String getLongName() { return "Skewb"; } @Override public String getShortName() { return "skewb"; } @Override public PuzzleState getSolvedState() { return new SkewbState(); } @Override protected int getRandomMoveCount() { return 15; } public class SkewbState extends PuzzleState { /** * +---------+ * | 1 2 | * U > | 0-0 | * | 3 4 | * +---------+---------+---------+---------+ * | 1 2 | 1 2 | 1 2 | 1 2 | * | 4-0 | 2-0 | 1-0 | 5-0 | * | 3 4 | 3 4 | 3 4 | 3 4 | * +---------+---------+---------+---------+ * ^ | 1 2 | * FL | 3-0 | * | 3 4 | * +---------+ */ private int[][] image = new int[6][5]; SkewbState() { for (int i=0; i<6; i++) { for (int j=0; j<5; j++) { image[i][j] = i; } } } SkewbState(int[][] _image) { for (int i=0; i<6; i++) { for (int j=0; j<5; j++) { image[i][j] = _image[i][j]; } } } private void turn(int axis, int pow, int[][] image) { //axis:0-R 1-U 2-L 3-B for (int p=0; p<pow; p++) { switch (axis) { case 0: swap(2, 0, 3, 0, 1, 0, image); swap(2, 4, 3, 2, 1, 3, image); swap(2, 2, 3, 1, 1, 4, image); swap(2, 3, 3, 4, 1, 1, image); swap(4, 4, 5, 3, 0, 4, image); break; case 1: swap(0, 0, 1, 0, 5, 0, image); swap(0, 2, 1, 2, 5, 1, image); swap(0, 4, 1, 4, 5, 2, image); swap(0, 1, 1, 1, 5, 3, image); swap(4, 1, 2, 2, 3, 4, image); break; case 2: swap(4, 0, 5, 0, 3, 0, image); swap(4, 3, 5, 4, 3, 3, image); swap(4, 1, 5, 3, 3, 1, image); swap(4, 4, 5, 2, 3, 4, image); swap(2, 3, 0, 1, 1, 4, image); break; case 3: swap(1, 0, 3, 0, 5, 0, image); swap(1, 4, 3, 4, 5, 3, image); swap(1, 3, 3, 3, 5, 1, image); swap(1, 2, 3, 2, 5, 4, image); swap(0, 2, 2, 4, 4, 3, image); break; default: azzert(false); } } } private void swap(int f1, int s1, int f2, int s2, int f3, int s3, int[][] image) { int temp = image[f1][s1]; image[f1][s1] = image[f2][s2]; image[f2][s2] = image[f3][s3]; image[f3][s3] = temp; } /** * return a square skewb face. whose 4 corners are (-1, -1), (1, -1), (1, 1), (-1, 1). It will be transformed later. */ private Path[] getFacePaths() { Path[] p = new Path[5]; for (int i=0; i<5; i++) { p[i] = new Path(); // In svg, by default, borders are scaled along with shapes. // Setting vector-effect to non-scaling-stroke disables that. // Unfortunately, batik doesn't support it, so we have // to do something hacky by explicitly setting the // stroke-width to something teeny. // If Batik ever changes to support vector-effect, we // can clean this up. //p[i].setAttribute("vector-effect", "non-scaling-stroke"); p[i].setAttribute("stroke-width", 1.0/pieceSize + "px"); } p[0].moveTo(-1, 0); p[0].lineTo( 0, 1); p[0].lineTo( 1, 0); p[0].lineTo(0,-1); p[0].closePath(); p[1].moveTo(-1, 0); p[1].lineTo(-1,-1); p[1].lineTo( 0,-1); p[1].closePath(); p[2].moveTo( 0,-1); p[2].lineTo( 1,-1); p[2].lineTo( 1, 0); p[2].closePath(); p[3].moveTo(-1, 0); p[3].lineTo(-1, 1); p[3].lineTo( 0, 1); p[3].closePath(); p[4].moveTo( 0, 1); p[4].lineTo( 1, 1); p[4].lineTo( 1, 0); p[4].closePath(); return p; } protected Svg drawScramble(HashMap<String, Color> colorScheme) { Svg g = new Svg(getPreferredSize()); Color[] scheme = new Color[6]; for(int i = 0; i < scheme.length; i++) { scheme[i] = colorScheme.get("URFDLB".charAt(i)+""); } Transform[] position = getFaceTrans(); for (int face=0; face<6; face++) { Path[] p = getFacePaths(); for (int i=0; i<5; i++) { p[i].transform(position[face]); p[i].setFill(scheme[image[face][i]]); p[i].setStroke(Color.BLACK); g.appendChild(p[i]); } } return g; } public LinkedHashMap<String, PuzzleState> getSuccessorsByName() { LinkedHashMap<String, PuzzleState> successors = new LinkedHashMap<String, PuzzleState>(); String axes = "RULB"; for(int axis = 0; axis < axes.length(); axis++) { char face = axes.charAt(axis); for(int pow = 1; pow <= 2; pow++) { String turn = "" + face; if(pow == 2) { turn += "'"; } int[][] imageCopy = new int[image.length][image[0].length]; GwtSafeUtils.deepCopy(image, imageCopy); turn(axis, pow, imageCopy); successors.put(turn, new SkewbState(imageCopy)); } } return successors; } @Override public boolean equals(Object other) { // Sure this could blow up with a cast exception, but shouldn't it? =) return Arrays.deepEquals(image, ((SkewbState) other).image); } @Override public int hashCode() { return Arrays.deepHashCode(image); } } }