diff --git a/scrambles/src/main/java/org/worldcubeassociation/tnoodle/puzzle/ClockPuzzle.java b/scrambles/src/main/java/org/worldcubeassociation/tnoodle/puzzle/ClockPuzzle.java index 0c22bd38..aeec6357 100644 --- a/scrambles/src/main/java/org/worldcubeassociation/tnoodle/puzzle/ClockPuzzle.java +++ b/scrambles/src/main/java/org/worldcubeassociation/tnoodle/puzzle/ClockPuzzle.java @@ -17,15 +17,16 @@ public class ClockPuzzle extends Puzzle { 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 FACE_STROKE_WIDTH = 1; private static final int radius = 70; private static final int clockRadius = 14; - private static final int clockOuterRadius = 20; + private static final int clockOuterRadius = 21; private static final int pointRadius = (clockRadius + clockOuterRadius) / 2; private static final int tickMarkRadius = 1; + private static final int topTickMarkRadius = 2; private static final int arrowHeight = 10; private static final int arrowRadius = 2; private static final int pinRadius = 4; - private static final int pinUpOffset = 6; private static final double arrowAngle = Math.PI / 2 - Math.acos( (double)arrowRadius / (double)arrowHeight ); private static final int gap = 5; @@ -54,14 +55,21 @@ public String getShortName() { private static final Map defaultColorScheme = new HashMap<>(); 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)); + Color bright = new Color(0xccddee); + Color dark = new Color(0x113366); + + defaultColorScheme.put("Front", dark); + defaultColorScheme.put("FrontClock", bright); + defaultColorScheme.put("FrontTopClock", new Color(0xffcc44)); + defaultColorScheme.put("FrontHand", dark); + defaultColorScheme.put("FrontHandBorder", dark); + defaultColorScheme.put("FrontPin", new Color(0x88aacc)); + defaultColorScheme.put("Back", bright); + defaultColorScheme.put("BackClock", dark); + defaultColorScheme.put("BackTopClock", new Color(0xcc6600)); + defaultColorScheme.put("BackHand", bright); + defaultColorScheme.put("BackHandBorder", bright); + defaultColorScheme.put("BackPin", new Color(0x446699)); } @Override public Map getDefaultColorScheme() { @@ -101,14 +109,6 @@ public PuzzleStateAndGenerator generateRandomMoves(Random r) { scramble.append(turns[x]).append(turn).append(clockwise ? "+" : "-").append(" "); } - boolean isFirst = true; - for(int x=0;x<4;x++) { - if (r.nextInt(2) == 1) { - scramble.append(isFirst ? "" : " ").append(turns[x]); - isFirst = false; - } - } - String scrambleStr = scramble.toString().trim(); PuzzleState state = getSolvedState(); @@ -122,17 +122,14 @@ public PuzzleStateAndGenerator generateRandomMoves(Random r) { public class ClockState extends PuzzleState { - private final boolean[] pins; private final int[] posit; private final 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; + public ClockState(int[] posit, boolean rightSideUp) { this.posit = posit; this.rightSideUp = rightSideUp; } @@ -145,39 +142,23 @@ public Map getSuccessorsByName() { 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)); + successors.put(move, new ClockState(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)); - } + successors.put("y2", new ClockState(positCopy, !rightSideUp)); return successors; } @@ -203,7 +184,7 @@ protected Svg drawScramble(Map colorScheme) { drawClock(svg, i, posit[i], colorScheme); } - drawPins(svg, pins, colorScheme); + drawPins(svg, colorScheme); return svg; } @@ -238,7 +219,7 @@ protected void drawBackground(Svg g, Map colorScheme) { 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; + float innerClockOuterRadius = clockOuterRadius - STROKE_WIDTH/2f; Circle c = new Circle(centerX, centerY, innerClockOuterRadius); c.setTransform(t); c.setFill(colorScheme.get(colorString[s])); @@ -252,15 +233,17 @@ protected void drawBackground(Svg g, Map colorScheme) { Transform tCopy = new Transform(t); tCopy.translate(2*i*clockOuterRadius, 2*j*clockOuterRadius); + Circle clockFace = new Circle(0, 0, clockRadius); + clockFace.setStroke(FACE_STROKE_WIDTH, 10, "round"); 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")); + Circle tickMark = new Circle(0, -pointRadius, k == 0 ? topTickMarkRadius : tickMarkRadius); + tickMark.setFill(colorScheme.get(colorString[s] + (k == 0 ? "Top" : "") + "Clock")); tickMark.rotate(Math.toRadians(30*k)); tickMark.transform(tCopy); g.appendChild(tickMark); @@ -277,6 +260,7 @@ protected void drawClock(Svg g, int clock, int position, Map colo int netX = 0; int netY = 0; int deltaX, deltaY; + String sidePrefix = ((clock < 9) ^ (rightSideUp)) ? "Back" : "Front"; if(clock < 9) { deltaX = radius + gap; deltaY = radius + gap; @@ -304,102 +288,54 @@ protected void drawClock(Svg g, int clock, int position, Map colo arrow.lineTo(0, -arrowHeight); arrow.lineTo(-arrowRadius*Math.cos( arrowAngle ), -arrowRadius*Math.sin(arrowAngle)); arrow.closePath(); - arrow.setStroke(colorScheme.get("HandBorder")); + arrow.setStroke(colorScheme.get(sidePrefix + "HandBorder")); arrow.setTransform(t); g.appendChild(arrow); Circle handBase = new Circle(0, 0, arrowRadius); - handBase.setStroke(colorScheme.get("HandBorder")); + handBase.setStroke(colorScheme.get(sidePrefix + "HandBorder")); handBase.setTransform(t); g.appendChild(handBase); arrow = new Path(arrow); - arrow.setFill(colorScheme.get("Hand")); + arrow.setFill(colorScheme.get(sidePrefix + "Hand")); arrow.setStroke(null); arrow.setTransform(t); g.appendChild(arrow); handBase = new Circle(handBase); - handBase.setFill(colorScheme.get("Hand")); + handBase.setFill(colorScheme.get(sidePrefix + "Hand")); handBase.setStroke(null); handBase.setTransform(t); g.appendChild(handBase); } - protected void drawPins(Svg g, boolean[] pins, Map colorScheme) { + protected void drawPins(Svg g, Map 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); + drawPin(g, tt, colorScheme.get(rightSideUp ? "BackPin" : "FrontPin")); } } 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); + drawPin(g, tt, colorScheme.get(rightSideUp ? "FrontPin" : "BackPin" )); } - k = 3; } } - protected void drawPin(Svg g, Transform t, boolean pinUp, Map colorScheme) { + protected void drawPin(Svg g, Transform t, Color color) { Circle pin = new Circle(0, 0, pinRadius); pin.setTransform(t); - pin.setStroke(Color.BLACK); - pin.setFill(colorScheme.get( pinUp ? "PinUp" : "PinDown" )); + pin.setFill(color); g.appendChild(pin); - - // there have been problems in the past with clock pin states being "inverted", - // see https://github.com/thewca/tnoodle/issues/423 for details. - if (pinUp) { - Transform bodyTransform = new Transform(t); - // pin circle transform relates to the circle *center*. Since it is two - // radii wide, we only move *one* radius to the right. - bodyTransform.translate(-pinRadius, -pinUpOffset); - - Rectangle cylinderBody = new Rectangle(0, 0, 2 * pinRadius, pinUpOffset); - cylinderBody.setTransform(bodyTransform); - cylinderBody.setStroke(null); - cylinderBody.setFill(colorScheme.get( "PinUp" )); - g.appendChild(cylinderBody); - - // We are NOT using the rectangle stroke, because those border strokes would cross through - // the bottom circle (ie cylinder "foot"). Drawing paths left and right is less cumbersome - // than drawing a stroked rectangle and overlaying it yet again with a stroke-less circle - Path cylinderWalls = new Path(); - - // left border - cylinderWalls.moveTo(0, 0); - cylinderWalls.lineTo(0, pinUpOffset); - - // right border - cylinderWalls.moveTo(2 * pinRadius, 0); - cylinderWalls.lineTo(2 * pinRadius, pinUpOffset); - - cylinderWalls.closePath(); - cylinderWalls.setStroke(Color.BLACK); - cylinderWalls.setTransform(bodyTransform); - g.appendChild(cylinderWalls); - - // Cylinder top "lid". Basically just a second pin circle - // that is lifted `pinRadius` pixels high. - Transform headTransform = new Transform(t); - headTransform.translate(0, -pinUpOffset); - - Circle cylinderHead = new Circle(0, 0, pinRadius); - cylinderHead.setTransform(headTransform); - cylinderHead.setStroke(Color.BLACK); - cylinderHead.setFill(colorScheme.get( "PinUp" )); - g.appendChild(cylinderHead); - } } }