package puzzle; import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzert; import net.gnehzr.tnoodle.svglite.Color; import net.gnehzr.tnoodle.svglite.Dimension; import net.gnehzr.tnoodle.svglite.Circle; import net.gnehzr.tnoodle.svglite.Path; import net.gnehzr.tnoodle.svglite.Svg; 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 net.gnehzr.tnoodle.scrambles.InvalidScrambleException; import net.gnehzr.tnoodle.scrambles.Puzzle; import net.gnehzr.tnoodle.scrambles.PuzzleStateAndGenerator; import org.timepedia.exporter.client.Export; @Export public class ClockPuzzle extends Puzzle { private static final Logger l = Logger.getLogger(ClockPuzzle.class.getName()); private static final String[] turns={"UR","DR","DL","UL","U","R","D","L","ALL"}; private static final int STROKE_WIDTH = 2; private static final int radius = 70; private static final int clockRadius = 14; private static final int clockOuterRadius = 20; private static final int pointRadius = (clockRadius + clockOuterRadius) / 2; private static final int tickMarkRadius = 1; private static final int arrowHeight = 10; private static final int arrowRadius = 2; private static final int pinRadius = 4; private static final double arrowAngle = Math.PI / 2 - Math.acos( (double)arrowRadius / (double)arrowHeight ); private static final int gap = 5; @Override public String getLongName() { return "Clock"; } @Override public String getShortName() { return "clock"; } private static final int[][] moves = { {0,1,1,0,1,1,0,0,0, -1, 0, 0, 0, 0, 0, 0, 0, 0},// UR {0,0,0,0,1,1,0,1,1, 0, 0, 0, 0, 0, 0,-1, 0, 0},// DR {0,0,0,1,1,0,1,1,0, 0, 0, 0, 0, 0, 0, 0, 0,-1},// DL {1,1,0,1,1,0,0,0,0, 0, 0,-1, 0, 0, 0, 0, 0, 0},// UL {1,1,1,1,1,1,0,0,0, -1, 0,-1, 0, 0, 0, 0, 0, 0},// U {0,1,1,0,1,1,0,1,1, -1, 0, 0, 0, 0, 0,-1, 0, 0},// R {0,0,0,1,1,1,1,1,1, 0, 0, 0, 0, 0, 0,-1, 0,-1},// D {1,1,0,1,1,0,1,1,0, 0, 0,-1, 0, 0, 0, 0, 0,-1},// L {1,1,1,1,1,1,1,1,1, -1, 0,-1, 0, 0, 0,-1, 0,-1},// A }; private static HashMap<String, Color> defaultColorScheme = new HashMap<String, Color>(); static { defaultColorScheme.put("Front", new Color(0x3375b2)); defaultColorScheme.put("Back", new Color(0x55ccff)); defaultColorScheme.put("FrontClock", new Color(0x55ccff)); defaultColorScheme.put("BackClock", new Color(0x3375b2)); defaultColorScheme.put("Hand", Color.YELLOW); defaultColorScheme.put("HandBorder", Color.RED); defaultColorScheme.put("PinUp", Color.YELLOW); defaultColorScheme.put("PinDown", new Color(0x885500)); } @Override public HashMap<String, Color> getDefaultColorScheme() { return new HashMap<String, Color>(defaultColorScheme); } @Override public Dimension getPreferredSize() { return new Dimension(4*(radius+gap), 2*(radius+gap)); } @Override public PuzzleState getSolvedState() { return new ClockState(); } @Override protected int getRandomMoveCount() { return 19; } @Override public PuzzleStateAndGenerator generateRandomMoves(Random r) { StringBuilder scramble = new StringBuilder(); for(int x=0; x<9; x++) { int turn = r.nextInt(12)-5; boolean clockwise = ( turn >= 0 ); turn = Math.abs(turn); scramble.append( turns[x] + turn + (clockwise?"+":"-") + " "); } scramble.append( "y2 "); for(int x=4; x<9; x++) { int turn = r.nextInt(12)-5; boolean clockwise = ( turn >= 0 ); turn = Math.abs(turn); scramble.append( turns[x] + turn + (clockwise?"+":"-") + " "); } boolean isFirst = true; for(int x=0;x<4;x++) { if (r.nextInt(2) == 1) { scramble.append((isFirst?"":" ")+turns[x]); isFirst = false; } } String scrambleStr = scramble.toString().trim(); PuzzleState state = getSolvedState(); try { state = state.applyAlgorithm(scrambleStr); } catch(InvalidScrambleException e) { azzert(false, e); return null; } return new PuzzleStateAndGenerator(state, scrambleStr); } public class ClockState extends PuzzleState { private boolean[] pins; private int[] posit; private boolean rightSideUp; public ClockState() { pins = new boolean[] {false, false, false, false}; posit = new int[] {0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0}; rightSideUp = true; } public ClockState(boolean[] pins, int[] posit, boolean rightSideUp) { this.pins = pins; this.posit = posit; this.rightSideUp = rightSideUp; } @Override public LinkedHashMap<String, PuzzleState> getSuccessorsByName() { LinkedHashMap<String, PuzzleState> successors = new LinkedHashMap<String, PuzzleState>(); for(int turn = 0; turn < turns.length; turn++) { for(int rot = 0; rot < 12; rot++) { // Apply the move int[] positCopy = new int[18]; boolean[] pinsCopy = new boolean[4]; for( int p=0; p<18; p++) { positCopy[p] = (posit[p] + rot*moves[turn][p] + 12)%12; } System.arraycopy(pins, 0, pinsCopy, 0, 4); // Build the move string boolean clockwise = ( rot < 7 ); String move = turns[turn] + (clockwise?(rot+"+"):((12-rot)+"-")); successors.put(move, new ClockState(pinsCopy, positCopy, rightSideUp)); } } // Still y2 to implement int[] positCopy = new int[18]; boolean[] pinsCopy = new boolean[4]; System.arraycopy(posit, 0, positCopy, 9, 9); System.arraycopy(posit, 9, positCopy, 0, 9); System.arraycopy(pins, 0, pinsCopy, 0, 4); successors.put("y2", new ClockState(pinsCopy, positCopy, !rightSideUp)); // Pins position moves for(int pin = 0; pin < 4; pin++) { int[] positC = new int[18]; boolean[] pinsC = new boolean[4]; System.arraycopy(posit, 0, positC, 0, 18); System.arraycopy(pins, 0, pinsC, 0, 4); int pinI = (pin==0?1:(pin==1?3:(pin==2?2:0))); pinsC[pinI] = true; successors.put(turns[pin], new ClockState(pinsC, positC, rightSideUp)); } return successors; } @Override public boolean equals(Object other) { ClockState o = ((ClockState) other); return Arrays.equals(posit, o.posit); } @Override public int hashCode() { return Arrays.hashCode(posit); } @Override protected Svg drawScramble(HashMap<String, Color> colorScheme) { Svg svg = new Svg(getPreferredSize()); svg.setStroke(STROKE_WIDTH, 10, "round"); drawBackground(svg, colorScheme); for(int i = 0; i < 18; i++) { drawClock(svg, i, posit[i], colorScheme); } drawPins(svg, pins, colorScheme); return svg; } protected void drawBackground(Svg g, HashMap<String, Color> colorScheme) { String[] colorString; if(rightSideUp) { colorString = new String[]{"Front", "Back"}; } else { colorString = new String[]{"Back", "Front"}; } for(int s = 0; s < 2; s++) { Transform t = Transform.getTranslateInstance((s*2+1)*(radius + gap), radius + gap); // Draw puzzle for(int centerX : new int[] { -2*clockOuterRadius, 2*clockOuterRadius }) { for(int centerY : new int[] { -2*clockOuterRadius, 2*clockOuterRadius }) { Circle c = new Circle(centerX, centerY, clockOuterRadius); c.setTransform(t); c.setStroke(Color.BLACK); g.appendChild(c); } } Circle outerCircle = new Circle(0, 0, radius); outerCircle.setTransform(t); outerCircle.setStroke(Color.BLACK); outerCircle.setFill(colorScheme.get(colorString[s])); g.appendChild(outerCircle); for(int centerX : new int[] { -2*clockOuterRadius, 2*clockOuterRadius }) { for(int centerY : new int[] { -2*clockOuterRadius, 2*clockOuterRadius }) { // We don't want to clobber part of our nice // thick outer border. int innerClockOuterRadius = clockOuterRadius - STROKE_WIDTH/2; Circle c = new Circle(centerX, centerY, innerClockOuterRadius); c.setTransform(t); c.setFill(colorScheme.get(colorString[s])); g.appendChild(c); } } // Draw clocks for(int i = -1; i <= 1; i++) { for(int j = -1; j <= 1; j++) { Transform tCopy = new Transform(t); tCopy.translate(2*i*clockOuterRadius, 2*j*clockOuterRadius); Circle clockFace = new Circle(0, 0, clockRadius); clockFace.setStroke(Color.BLACK); clockFace.setFill(colorScheme.get(colorString[s]+ "Clock")); clockFace.setTransform(tCopy); g.appendChild(clockFace); for(int k = 0; k < 12; k++) { Circle tickMark = new Circle(0, -pointRadius, tickMarkRadius); tickMark.setFill(colorScheme.get(colorString[s] + "Clock")); tickMark.rotate(Math.toRadians(30*k)); tickMark.transform(tCopy); g.appendChild(tickMark); } } } } } protected void drawClock(Svg g, int clock, int position, HashMap<String, Color> colorScheme) { Transform t = new Transform(); t.rotate(Math.toRadians(position*30)); int netX = 0; int netY = 0; int deltaX, deltaY; if(clock < 9) { deltaX = radius + gap; deltaY = radius + gap; t.translate(deltaX, deltaY); netX += deltaX; netY += deltaY; } else { deltaX = 3*(radius + gap); deltaY = radius + gap; t.translate(deltaX, deltaY); netX += deltaX; netY += deltaY; clock -= 9; } deltaX = 2*((clock%3) - 1)*clockOuterRadius; deltaY = 2*((clock/3) - 1)*clockOuterRadius; t.translate(deltaX, deltaY); netX += deltaX; netY += deltaY; Path arrow = new Path(); arrow.moveTo(0, 0); arrow.lineTo(arrowRadius*Math.cos(arrowAngle), -arrowRadius*Math.sin(arrowAngle)); arrow.lineTo(0, -arrowHeight); arrow.lineTo(-arrowRadius*Math.cos( arrowAngle ), -arrowRadius*Math.sin(arrowAngle)); arrow.closePath(); arrow.setStroke(colorScheme.get("HandBorder")); arrow.setTransform(t); g.appendChild(arrow); Circle handBase = new Circle(0, 0, arrowRadius); handBase.setStroke(colorScheme.get("HandBorder")); handBase.setTransform(t); g.appendChild(handBase); arrow = new Path(arrow); arrow.setFill(colorScheme.get("Hand")); arrow.setStroke(null); arrow.setTransform(t); g.appendChild(arrow); handBase = new Circle(handBase); handBase.setFill(colorScheme.get("Hand")); handBase.setStroke(null); handBase.setTransform(t); g.appendChild(handBase); } protected void drawPins(Svg g, boolean[] pins, HashMap<String, Color> colorScheme) { Transform t = new Transform(); t.translate(radius + gap, radius + gap); int k = 0; for(int i = -1; i <= 1; i += 2) { for(int j = -1; j <= 1; j += 2) { Transform tt = new Transform(t); tt.translate(j*clockOuterRadius, i*clockOuterRadius); drawPin(g, tt, pins[k++], colorScheme); } } t.translate(2*(radius + gap), 0); k = 1; for(int i = -1; i <= 1; i += 2) { for(int j = -1; j <= 1; j += 2) { Transform tt = new Transform(t); tt.translate(j*clockOuterRadius, i*clockOuterRadius); drawPin(g, tt, !pins[k--], colorScheme); } k = 3; } } protected void drawPin(Svg g, Transform t, boolean pinUp, HashMap<String, Color> colorScheme) { Circle pin = new Circle(0, 0, pinRadius); pin.setTransform(t); pin.setStroke(Color.BLACK); pin.setFill(colorScheme.get( pinUp ? "PinUp" : "PinDown" )); g.appendChild(pin); } } }