Skip to content

Commit

Permalink
Merge pull request #63 from Ultraviolet-Ninja/colored-switches-fix
Browse files Browse the repository at this point in the history
Bug Fix
  • Loading branch information
Ultraviolet-Ninja authored Feb 23, 2023
2 parents 18152cf + b8a6e61 commit c34460b
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- Boolean Venn Diagram
- Chess
- Chord Qualities
- Colored Switches (Not technically part of the bomb, but was a fun ~~recursion~~ graph problem using A*)
- Colored Switches (Not technically part of the bomb, but was a fun ~~recursion~~ graph problem using ~~A*~~ Dijkstra's)
- Emoji Math
- Fast Math
- Forget Me Not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,67 +12,83 @@
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;

import static bomb.modules.c.colored.switches.ColoredSwitches.canFollowPath;

@SuppressWarnings("ConstantConditions")
public class ColoredSwitchGraphFactory {
private static final byte OUTGOING_STATE = 1, COLOR_CONDITIONS = 2, SWITCH_TO_FLIP = 3;
private static final String FILENAME = "graph.csv";

public static @NotNull Graph<ColoredSwitchNode, DefaultEdge> makeGraph() throws IllegalStateException {
return buildGraph(createFromFile());
}

private static List<ColoredSwitchNode> createFromFile() throws IllegalStateException {
static List<ColoredSwitchNode> createFromFile() throws IllegalStateException {
List<ColoredSwitchNode> output = new ArrayList<>(32);
Regex connectionFinder = new Regex("\\[(\\d{1,2})\\((\\d{1,3})\\)([1-5])]");
Regex connectionRegex = new Regex("\\[(\\d{1,2})\\((\\d{1,3})\\)([1-5])]");
InputStream in = ColoredSwitchGraphFactory.class.getResourceAsStream(FILENAME);

try (CSVReader csvReader = new CSVReader(new InputStreamReader(in))) {
csvReader.forEach(record -> output.add(buildNode(record, connectionFinder)));
csvReader.forEach(record -> output.add(buildNode(record, connectionRegex)));
return output;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

private static Graph<ColoredSwitchNode, DefaultEdge> buildGraph(List<ColoredSwitchNode> nodeList) {
Graph<ColoredSwitchNode, DefaultEdge> output = new SimpleDirectedGraph<>(DefaultEdge.class);
static @NotNull Graph<ColoredSwitchNode, DefaultEdge> makeGraphFromSwitchColors(
@NotNull SwitchColor[] startingColors) throws IllegalStateException {
return buildGraph(createFromFile(), startingColors);
}

private static Graph<ColoredSwitchNode, DefaultEdge> buildGraph(List<ColoredSwitchNode> nodeList,
SwitchColor[] startingSwitchColors) {
Graph<ColoredSwitchNode, DefaultEdge> createdGraph = new SimpleDirectedGraph<>(DefaultEdge.class);

for (ColoredSwitchNode node : nodeList)
output.addVertex(node);
nodeList.forEach(createdGraph::addVertex);

for (ColoredSwitchNode node : nodeList) {
for (byte connection : node.getOutgoingConnections()) {
output.addEdge(node, nodeList.get(connection));
if (isTraversableEdge(startingSwitchColors, node, nodeList.get(connection))) {
createdGraph.addEdge(node, nodeList.get(connection));
}
}
}

return output;
return createdGraph;
}

private static boolean isTraversableEdge(SwitchColor[] startingColors, ColoredSwitchNode startNode,
ColoredSwitchNode outgoingNode) {
var edgeData = startNode.getEdgeData(outgoingNode.getState());
SwitchColor switchColor = startingColors[edgeData.getValue1() - 1];

return canFollowPath(edgeData.getValue0(), switchColor);
}

private static ColoredSwitchNode buildNode(String[] record, Regex connectionFinder) {
private static ColoredSwitchNode buildNode(String[] record, Regex connectionRegex) {
ColoredSwitchNode node = new ColoredSwitchNode(Byte.parseByte(record[0]));

for (int i = 1; i < record.length; i++) {
if (record[i].isEmpty()) return node;
connectionFinder.loadText(record[i]);
connectionFinder.hasMatch();
connectionRegex.loadText(record[i]);
connectionRegex.hasMatch();

byte outgoingConnection = Byte.parseByte(connectionFinder.captureGroup(OUTGOING_STATE));
SwitchColor[] colorConditions = createConditions(connectionFinder.captureGroup(COLOR_CONDITIONS));
byte switchToFlip = Byte.parseByte(connectionFinder.captureGroup(SWITCH_TO_FLIP));
byte outgoingConnection = Byte.parseByte(connectionRegex.captureGroup(OUTGOING_STATE));
EnumSet<SwitchColor> edgeColors = createConditions(connectionRegex.captureGroup(COLOR_CONDITIONS));
byte switchToFlip = Byte.parseByte(connectionRegex.captureGroup(SWITCH_TO_FLIP));

node.addConnection(outgoingConnection, colorConditions, switchToFlip);
node.addConnection(outgoingConnection, edgeColors, switchToFlip);
}

return node;
}

private static SwitchColor[] createConditions(String ordinals) {
return Arrays.stream(ordinals.split(""))
private static EnumSet<SwitchColor> createConditions(String ordinals) {
return EnumSet.copyOf(
Arrays.stream(ordinals.split(""))
.mapToInt(Integer::parseInt)
.mapToObj(SwitchColor::getByIndex)
.toArray(SwitchColor[]::new);
.toList()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.javatuples.Pair;

import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -10,7 +11,7 @@ public class ColoredSwitchNode {
private static final int SIZE_LIMIT = 3;

private final byte state;
private final Map<Byte, Pair<SwitchColor[], Byte>> outgoingConnections;
private final Map<Byte, Pair<EnumSet<SwitchColor>, Byte>> outgoingConnections;

public ColoredSwitchNode(byte state) {
this.state = state;
Expand All @@ -21,15 +22,15 @@ public byte getState() {
return state;
}

public void addConnection(byte outgoingState, SwitchColor[] colorRestrictions, byte switchToFlip) {
public void addConnection(byte outgoingState, EnumSet<SwitchColor> colorRestrictions, byte switchToFlip) {
outgoingConnections.put(outgoingState, new Pair<>(colorRestrictions, switchToFlip));
}

public Set<Byte> getOutgoingConnections() {
return outgoingConnections.keySet();
}

public Pair<SwitchColor[], Byte> getEdgeData(byte targetState) {
public Pair<EnumSet<SwitchColor>, Byte> getEdgeData(byte targetState) {
return outgoingConnections.get(targetState);
}

Expand Down
126 changes: 52 additions & 74 deletions src/main/java/bomb/modules/c/colored/switches/ColoredSwitches.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,69 @@
import bomb.modules.s.switches.Switches;
import org.javatuples.Pair;
import org.jetbrains.annotations.NotNull;
import org.jgrapht.Graph;
import org.jgrapht.alg.interfaces.AStarAdmissibleHeuristic;
import org.jgrapht.alg.shortestpath.AStarShortestPath;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.graph.DefaultEdge;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.function.BiFunction;

import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static bomb.modules.c.colored.switches.ColoredSwitchGraphFactory.createFromFile;
import static bomb.modules.c.colored.switches.SwitchColor.NEUTRAL;
import static java.util.function.UnaryOperator.identity;

@DisplayComponent(resource = "colored_switches.fxml", buttonLinkerName = "Colored Switches")
public final class ColoredSwitches extends Switches {
private static final double WRONG_PATH_VALUE;
private static final Graph<ColoredSwitchNode, DefaultEdge> INTERNAL_GRAPH;
private static final BiFunction<SwitchColor[], Byte, AStarAdmissibleHeuristic<ColoredSwitchNode>> HEURISTIC_FUNCTION;
private static final Map<Byte, ColoredSwitchNode> NUMBER_TO_STATE_MAP;

private static byte secondaryStartLocation = -1;

static {
WRONG_PATH_VALUE = Double.MAX_VALUE;
INTERNAL_GRAPH = ColoredSwitchGraphFactory.makeGraph();
NUMBER_TO_STATE_MAP = createFromFile()
.stream()
.collect(Collectors.toMap(ColoredSwitchNode::getState, identity()));
}

public static @NotNull List<String> producePreemptiveMoveList(byte startingState) throws IllegalArgumentException {
validateByte(startingState);

List<String> outputList = new ArrayList<>();
secondaryStartLocation = makePreemptiveMove(startingState, outputList);

for (int i = 0; i < 2; i++)
secondaryStartLocation = makePreemptiveMove(secondaryStartLocation, outputList);
secondaryStartLocation = makePreemptiveMove(startingState, outputList);
secondaryStartLocation = makePreemptiveMove(secondaryStartLocation, outputList);
secondaryStartLocation = makePreemptiveMove(secondaryStartLocation, outputList);

return outputList;
}

private static byte makePreemptiveMove(byte currentState, List<String> outputList) throws IllegalStateException {
ColoredSwitchNode currentNode = getNodeByState(currentState);
ColoredSwitchNode currentNode = NUMBER_TO_STATE_MAP.get(currentState);

for (Byte connection : currentNode.getOutgoingConnections()) {
Pair<SwitchColor[], Byte> edgeData = currentNode.getEdgeData(connection);
Predicate<Byte> filterToBlackPath = connectionState -> {
Pair<EnumSet<SwitchColor>, Byte> edgeData = currentNode.getEdgeData(connectionState);
return isBlackPath(edgeData.getValue0());
};

if (isBlackPath(edgeData.getValue0())) {
outputList.add(String.valueOf(edgeData.getValue1()));
return connection;
}
}
Consumer<Byte> appendBlackPath = connectionState -> {
Pair<EnumSet<SwitchColor>, Byte> edgeData = currentNode.getEdgeData(connectionState);
outputList.add(String.valueOf(edgeData.getValue1()));
};

throw new IllegalStateException("This should be an unreachable state");
}
Optional<Byte> first = currentNode.getOutgoingConnections()
.stream()
.filter(filterToBlackPath)
.findFirst();

private static ColoredSwitchNode getNodeByState(byte startingState) throws IllegalStateException {
for (ColoredSwitchNode coloredSwitchNode : INTERNAL_GRAPH.vertexSet()) {
if (coloredSwitchNode.getState() == startingState)
return coloredSwitchNode;
}
throw new IllegalStateException();
first.ifPresent(appendBlackPath);

return first.orElseThrow(() -> new IllegalStateException("No black paths found"));
}

public static @NotNull List<String> produceFinalMoveList(@NotNull SwitchColor[] startingColors, byte desiredState)
Expand All @@ -73,49 +79,41 @@ private static ColoredSwitchNode getNodeByState(byte startingState) throws Illeg
if (!isFirstStepDone())
throw new IllegalStateException("Must flip 3 switches before producing the final list");

AStarShortestPath<ColoredSwitchNode, DefaultEdge> aStarShortestPath = new AStarShortestPath<>(
INTERNAL_GRAPH,
HEURISTIC_FUNCTION.apply(startingColors, desiredState)
DijkstraShortestPath<ColoredSwitchNode, DefaultEdge> shortestPath = new DijkstraShortestPath<>(
ColoredSwitchGraphFactory.makeGraphFromSwitchColors(startingColors)
);

ColoredSwitchNode startNode = getNodeByState(secondaryStartLocation);
ColoredSwitchNode destination = getNodeByState(desiredState);
ColoredSwitchNode startNode = NUMBER_TO_STATE_MAP.get(secondaryStartLocation);
ColoredSwitchNode destination = NUMBER_TO_STATE_MAP.get(desiredState);

List<ColoredSwitchNode> nodeList = aStarShortestPath.getPath(startNode, destination).getVertexList();
List<ColoredSwitchNode> nodeList = shortestPath.getPath(startNode, destination)
.getVertexList();

return createSwitchToFlipList(nodeList);
}

private static List<String> createSwitchToFlipList(List<ColoredSwitchNode> path) {
List<String> output = new ArrayList<>();

for (int i = 0; i < path.size() - 1; i++) {
Pair<SwitchColor[], Byte> edgeData = path.get(i).getEdgeData(path.get(i + 1).getState());
output.add(String.valueOf(edgeData.getValue1()));
}

return output;
return IntStream.range(0, path.size() - 1)
.mapToObj(i -> path.get(i).getEdgeData(path.get(i + 1).getState()))
.map(Pair::getValue1)
.map(String::valueOf)
.toList();
}

private static boolean canFollowPath(SwitchColor[] connectionConditions, SwitchColor switchColor) {
static boolean canFollowPath(EnumSet<SwitchColor> connectionConditions, SwitchColor switchColor) {
if (isBlackPath(connectionConditions)) return true;

for (SwitchColor possibleConnection : connectionConditions) {
if (switchColor == possibleConnection)
return true;
}
return false;
return connectionConditions.contains(switchColor);
}

private static void validateSwitchColors(SwitchColor[] startingColors) {
for (SwitchColor switchColor : startingColors) {
if (switchColor == NEUTRAL)
throw new IllegalArgumentException("All switches must have a color");
if (EnumSet.copyOf(Arrays.asList(startingColors)).contains(NEUTRAL)) {
throw new IllegalArgumentException("All switches must have a color. No neutral/black switches allowed");
}
}

private static boolean isBlackPath(SwitchColor[] connectionConditions) {
return connectionConditions.length == 1 && connectionConditions[0] == NEUTRAL;
private static boolean isBlackPath(EnumSet<SwitchColor> connectionConditions) {
return connectionConditions.size() == 1 && connectionConditions.contains(NEUTRAL);
}

private static void validateByte(byte state) throws IllegalArgumentException {
Expand All @@ -130,24 +128,4 @@ public static boolean isFirstStepDone() {
public static void reset() {
secondaryStartLocation = -1;
}

static {
HEURISTIC_FUNCTION = (startingColors, desiredState) ->
((sourceVertex, targetVertex) -> {
//Weight from source to target
double gX = Math.abs(sourceVertex.getState() - targetVertex.getState());
//Weight from target to desired state
double hX = Math.abs(desiredState - targetVertex.getState());

Pair<SwitchColor[], Byte> edgeData = sourceVertex.getEdgeData(targetVertex.getState());
if (edgeData == null)
return WRONG_PATH_VALUE;
SwitchColor switchToFlip = startingColors[edgeData.getValue1()];

if (!canFollowPath(edgeData.getValue0(), switchToFlip))
return WRONG_PATH_VALUE;

return gX + hX;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<GridPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="800.0" prefWidth="1200.0"
stylesheets="@../../../../css/c/colored_switches.css" xmlns="http://javafx.com/javafx/15.0.1"
stylesheets="@../../../../css/c/colored_switches.css" xmlns="http://javafx.com/javafx/16"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="bomb.modules.c.colored.switches.ColoredSwitchController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
Expand Down Expand Up @@ -114,8 +114,10 @@
</GridPane>
<GridPane GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="595.000032285256" minWidth="10.0"
prefWidth="503.94845757822475"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="695.9484575782247" minWidth="10.0"
prefWidth="695.9484575782247"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/>
Expand All @@ -130,7 +132,7 @@
</MFXButton>
</VBox>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" GridPane.columnIndex="1">
<MFXTextField fx:id="finalMoveOutputField" prefHeight="55.0" prefWidth="353.0"
<MFXTextField fx:id="finalMoveOutputField" prefHeight="55.0" prefWidth="648.0"
promptText="Final Switches To Flip"/>
<MFXButton id="submit-button" fx:id="finalMoveSubmitButton" disable="true" onAction="#getFinalMoveList"
text="Submit">
Expand Down
Loading

0 comments on commit c34460b

Please sign in to comment.