Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clock changes: remove pin state and switch colors to high-contrast #49

Merged
merged 3 commits into from
Dec 15, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading