Skip to content

Commit

Permalink
Clock changes: remove pin state and switch colors to high-contrast (#49)
Browse files Browse the repository at this point in the history
* Remove pins moves (and pin up drawing) from clock.

* Switch clock colors to high-contrast.

* Apply suggestions from code review

Co-authored-by: Jeremy Fleischman <[email protected]>

---------

Co-authored-by: Jeremy Fleischman <[email protected]>
  • Loading branch information
lgarron and jfly authored Dec 15, 2023
1 parent 88cff09 commit ebd3622
Showing 1 changed file with 37 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,14 +55,21 @@ public String getShortName() {

private static final Map<String, Color> 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<String, Color> getDefaultColorScheme() {
Expand Down Expand Up @@ -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();
Expand All @@ -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;
}
Expand All @@ -145,39 +142,23 @@ public Map<String, PuzzleState> 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;
}
Expand All @@ -203,7 +184,7 @@ protected Svg drawScramble(Map<String, Color> colorScheme) {
drawClock(svg, i, posit[i], colorScheme);
}

drawPins(svg, pins, colorScheme);
drawPins(svg, colorScheme);
return svg;
}

Expand Down Expand Up @@ -238,7 +219,7 @@ protected void drawBackground(Svg g, Map<String, Color> 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]));
Expand All @@ -252,15 +233,17 @@ protected void drawBackground(Svg g, Map<String, Color> 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);
Expand All @@ -277,6 +260,7 @@ protected void drawClock(Svg g, int clock, int position, Map<String, Color> 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;
Expand Down Expand Up @@ -304,102 +288,54 @@ protected void drawClock(Svg g, int clock, int position, Map<String, Color> 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<String, Color> colorScheme) {
protected void drawPins(Svg g, Map<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);
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<String, Color> 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);
}
}

}
Expand Down

0 comments on commit ebd3622

Please sign in to comment.