+
+
@@ -38,5 +39,5 @@
-
+
diff --git a/src/main/java/edu/rpi/legup/model/PuzzleImporter.java b/src/main/java/edu/rpi/legup/model/PuzzleImporter.java
index 0cc163200..0902478db 100644
--- a/src/main/java/edu/rpi/legup/model/PuzzleImporter.java
+++ b/src/main/java/edu/rpi/legup/model/PuzzleImporter.java
@@ -130,6 +130,20 @@ public void initializePuzzle(Node node) throws InvalidFileFormatException {
public abstract void initializeBoard(String[] statements)
throws UnsupportedOperationException, IllegalArgumentException;
+ /**
+ * Used to check that elements in the proof tree are saved properly.
+ *
+ * Make sure the list elements are lowercase
+ *
+ * @return A list of elements that will change when solving the puzzle * e.g. {"cell"}, {"cell",
+ * "line"}
+ */
+ public List getImporterElements() {
+ List elements = new ArrayList<>();
+ elements.add("cell");
+ return elements;
+ }
+
/**
* Creates the proof for building
*
@@ -379,7 +393,8 @@ protected void makeTransitionChanges(TreeTransition transition, Node transElemen
NodeList cellList = transElement.getChildNodes();
for (int i = 0; i < cellList.getLength(); i++) {
Node node = cellList.item(i);
- if (node.getNodeName().equalsIgnoreCase("cell")) {
+ List elements = getImporterElements();
+ if (elements.contains(node.getNodeName().toLowerCase())) {
Board board = transition.getBoard();
PuzzleElement cell = puzzle.getFactory().importCell(node, board);
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java b/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java
new file mode 100644
index 000000000..773513cda
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/Binary.java
@@ -0,0 +1,71 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.model.Puzzle;
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+
+public class Binary extends Puzzle {
+ public Binary() {
+ super();
+
+ this.name = "Binary";
+
+ this.importer = new BinaryImporter(this);
+ this.exporter = new BinaryExporter(this);
+
+ this.factory = new BinaryCellFactory();
+ }
+
+ /** Initializes the game board. Called by the invoker of the class */
+ @Override
+ public void initializeView() {
+ boardView = new BinaryView((BinaryBoard) currentBoard);
+ boardView.setBoard(currentBoard);
+ addBoardListener(boardView);
+ }
+
+ /**
+ * Generates a random edu.rpi.legup.puzzle based on the difficulty
+ *
+ * @param difficulty level of difficulty (1-10)
+ * @return board of the random edu.rpi.legup.puzzle
+ */
+ @Override
+ public Board generatePuzzle(int difficulty) {
+ return null;
+ }
+
+ // /**
+ // * Determines if the given dimensions are valid for Binary
+ // *
+ // * @param rows the number of rows
+ // * @param columns the number of columns
+ // * @return true if the given dimensions are valid for Binary, false otherwise
+ // */
+ // @Override
+ // public boolean isValidDimensions(int rows, int columns){
+ // return rows >= 2 && rows % 2 == 0 && columns >= 2 && columns % 2 == 0;
+ // }
+
+ @Override
+ public boolean isBoardComplete(Board board) {
+ BinaryBoard binaryBoard = (BinaryBoard) board;
+
+ for (ContradictionRule rule : contradictionRules) {
+ if (rule.checkContradiction(binaryBoard) == null) {
+ return false;
+ }
+ }
+ for (PuzzleElement data : binaryBoard.getPuzzleElements()) {
+ BinaryCell cell = (BinaryCell) data;
+ if (cell.getType() == BinaryType.UNKNOWN) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onBoardChange(Board board) {}
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java
new file mode 100644
index 000000000..35c37b1a1
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryBoard.java
@@ -0,0 +1,84 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.model.gameboard.GridBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+public class BinaryBoard extends GridBoard {
+ private int size;
+
+ public BinaryBoard(int width, int height) {
+ super(width, height);
+ this.size = width;
+ }
+
+ public BinaryBoard(int size) {
+ super(size, size);
+ this.size = size;
+ }
+
+ @Override
+ public BinaryCell getCell(int x, int y) {
+ if (y * dimension.width + x >= puzzleElements.size()
+ || x >= dimension.width
+ || y >= dimension.height
+ || x < 0
+ || y < 0) {
+ return null;
+ }
+ return (BinaryCell) super.getCell(x, y);
+ }
+
+ public Set getRowCells(int rowNum) {
+ Set row = new HashSet<>();
+ for (int i = 0; i < size; i++) {
+ BinaryCell cell = getCell(i, rowNum);
+ row.add(cell);
+ }
+ return row;
+ }
+
+ public ArrayList getRowTypes(int rowNum) {
+ ArrayList row = new ArrayList();
+ for (int i = 0; i < size; i++) {
+ BinaryCell cell = getCell(i, rowNum);
+ row.add(cell.getType());
+ }
+ return row;
+ }
+
+ public ArrayList getColTypes(int colNum) {
+ ArrayList col = new ArrayList();
+ for (int i = 0; i < size; i++) {
+ BinaryCell cell = getCell(colNum, i);
+ col.add(cell.getType());
+ }
+ return col;
+ }
+
+ public Set getCol(int colNum) {
+ Set col = new HashSet<>();
+ for (int i = 0; i < size; i++) {
+ col.add(getCell(colNum, i));
+ }
+ return col;
+ }
+
+ @Override
+ public BinaryBoard copy() {
+ System.out.println("BinaryBoard copy()");
+ BinaryBoard copy = new BinaryBoard(dimension.width, dimension.height);
+ for (int x = 0; x < this.dimension.width; x++) {
+ for (int y = 0; y < this.dimension.height; y++) {
+ copy.setCell(x, y, getCell(x, y).copy());
+ }
+ }
+ for (PuzzleElement e : modifiedData) {
+ copy.getPuzzleElement(e).setModifiable(false);
+ }
+ return copy;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java
new file mode 100644
index 000000000..9007215ad
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCell.java
@@ -0,0 +1,35 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.model.gameboard.GridCell;
+import java.awt.Point;
+
+public class BinaryCell extends GridCell {
+ public BinaryCell(int valueInt, Point location) {
+ super(valueInt, location);
+ }
+
+ public BinaryType getType() {
+ switch (data) {
+ case 0:
+ return BinaryType.ZERO;
+ case 1:
+ return BinaryType.ONE;
+ case 2:
+ return BinaryType.UNKNOWN;
+ default:
+ if (data > 1) {
+ return BinaryType.UNKNOWN;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public BinaryCell copy() {
+ BinaryCell copy = new BinaryCell(data, (Point) location.clone());
+ copy.setIndex(index);
+ copy.setModifiable(isModifiable);
+ copy.setGiven(isGiven);
+ return copy;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java
new file mode 100644
index 000000000..890c26656
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryCellFactory.java
@@ -0,0 +1,59 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.ElementFactory;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.awt.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class BinaryCellFactory extends ElementFactory {
+
+ public BinaryCell importCell(Node node, Board board) throws InvalidFileFormatException {
+ try {
+ if (!node.getNodeName().equalsIgnoreCase("cell")) {
+ throw new InvalidFileFormatException(
+ "binary Factory: unknown puzzleElement puzzleElement");
+ }
+
+ BinaryBoard binaryBoard = (BinaryBoard) board;
+ int width = binaryBoard.getWidth();
+ int height = binaryBoard.getHeight();
+
+ NamedNodeMap attributeList = node.getAttributes();
+ int value = Integer.valueOf(attributeList.getNamedItem("value").getNodeValue());
+ int x = Integer.valueOf(attributeList.getNamedItem("x").getNodeValue());
+ int y = Integer.valueOf(attributeList.getNamedItem("y").getNodeValue());
+
+ if (x >= width || y >= height) {
+ throw new InvalidFileFormatException("binary Factory: cell location out of bounds");
+ }
+ if (value < -2) {
+ throw new InvalidFileFormatException("binary Factory: cell unknown value");
+ }
+
+ BinaryCell cell = new BinaryCell(value, new Point(x, y));
+ cell.setIndex(y * height + x);
+ return cell;
+ } catch (NumberFormatException e) {
+ throw new InvalidFileFormatException(
+ "binary Factory: unknown value where integer expected");
+ } catch (NullPointerException e) {
+ throw new InvalidFileFormatException("binary Factory: could not find attribute(s)");
+ }
+ }
+
+ public org.w3c.dom.Element exportCell(Document document, PuzzleElement puzzleElement) {
+ org.w3c.dom.Element cellElement = document.createElement("cell");
+
+ BinaryCell cell = (BinaryCell) puzzleElement;
+ Point loc = cell.getLocation();
+ cellElement.setAttribute("value", String.valueOf(cell.getData()));
+ cellElement.setAttribute("x", String.valueOf(loc.x));
+ cellElement.setAttribute("y", String.valueOf(loc.y));
+
+ return cellElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java
new file mode 100644
index 000000000..0bad559d9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryController.java
@@ -0,0 +1,45 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.controller.ElementController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import java.awt.event.MouseEvent;
+
+public class BinaryController extends ElementController {
+
+ @Override
+ public void changeCell(MouseEvent e, PuzzleElement data) {
+ BinaryCell cell = (BinaryCell) data;
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ if (e.isControlDown()) {
+ this.boardView
+ .getSelectionPopupMenu()
+ .show(
+ boardView,
+ this.boardView.getCanvas().getX() + e.getX(),
+ this.boardView.getCanvas().getY() + e.getY());
+ } else {
+ if (cell.getData() == 0) {
+ data.setData(1);
+ } else {
+ if (cell.getData() == 1) {
+ data.setData(2);
+ } else {
+ data.setData(0);
+ }
+ }
+ }
+ } else {
+ if (e.getButton() == MouseEvent.BUTTON3) {
+ if (cell.getData() == 0) {
+ data.setData(1);
+ } else {
+ if (cell.getData() == 1) {
+ data.setData(2);
+ } else {
+ data.setData(0);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java
new file mode 100644
index 000000000..9ac99c958
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryElementView.java
@@ -0,0 +1,120 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.ui.boardview.GridElementView;
+import java.awt.*;
+
+public class BinaryElementView extends GridElementView {
+
+ private static final Font FONT = new Font("TimesRoman", Font.BOLD, 17);
+ private static final Color FONT_COLOR = Color.BLACK;
+
+ public BinaryElementView(BinaryCell cell) {
+ super(cell);
+ }
+
+ /**
+ * Gets the PuzzleElement associated with this view
+ *
+ * @return PuzzleElement associated with this view
+ */
+ @Override
+ public BinaryCell getPuzzleElement() {
+ return (BinaryCell) super.getPuzzleElement();
+ }
+
+ @Override
+ public void drawGiven(Graphics2D graphics2D) {
+ BinaryCell cell = (BinaryCell) puzzleElement;
+ BinaryType type = cell.getType();
+ if (type == BinaryType.ZERO) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.LIGHT_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(FONT_COLOR);
+ graphics2D.setFont(FONT);
+ FontMetrics metrics = graphics2D.getFontMetrics(FONT);
+ String value = String.valueOf(puzzleElement.getData());
+ int xText = location.x + (size.width - metrics.stringWidth(value)) / 2;
+ int yText =
+ location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent();
+ graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText);
+ } else {
+ if (type == BinaryType.ONE) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.LIGHT_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(FONT_COLOR);
+ graphics2D.setFont(FONT);
+ FontMetrics metrics = graphics2D.getFontMetrics(FONT);
+ String value = String.valueOf(puzzleElement.getData());
+ int xText = location.x + (size.width - metrics.stringWidth(value)) / 2;
+ int yText =
+ location.y
+ + ((size.height - metrics.getHeight()) / 2)
+ + metrics.getAscent();
+ graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText);
+
+ } else {
+ if (type == BinaryType.UNKNOWN) {
+ graphics2D.setStroke(new BasicStroke(0));
+ graphics2D.setColor(Color.WHITE);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void drawElement(Graphics2D graphics2D) {
+ BinaryCell cell = (BinaryCell) puzzleElement;
+ BinaryType type = cell.getType();
+ if (type == BinaryType.ZERO) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.WHITE);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(FONT_COLOR);
+ graphics2D.setFont(FONT);
+ FontMetrics metrics = graphics2D.getFontMetrics(FONT);
+ String value = String.valueOf(puzzleElement.getData());
+ int xText = location.x + (size.width - metrics.stringWidth(value)) / 2;
+ int yText =
+ location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent();
+ graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText);
+ } else {
+ if (type == BinaryType.ONE) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.WHITE);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(FONT_COLOR);
+ graphics2D.setFont(FONT);
+ FontMetrics metrics = graphics2D.getFontMetrics(FONT);
+ String value = String.valueOf(puzzleElement.getData());
+ int xText = location.x + (size.width - metrics.stringWidth(value)) / 2;
+ int yText =
+ location.y
+ + ((size.height - metrics.getHeight()) / 2)
+ + metrics.getAscent();
+ graphics2D.drawString(String.valueOf(puzzleElement.getData()), xText, yText);
+
+ } else {
+ if (type == BinaryType.UNKNOWN) {
+ graphics2D.setStroke(new BasicStroke(0));
+ graphics2D.setColor(Color.WHITE);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java
new file mode 100644
index 000000000..cd58314b6
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryExporter.java
@@ -0,0 +1,39 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.model.PuzzleExporter;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import org.w3c.dom.Document;
+
+public class BinaryExporter extends PuzzleExporter {
+
+ public BinaryExporter(Binary binary) {
+ super(binary);
+ }
+
+ @Override
+ protected org.w3c.dom.Element createBoardElement(Document newDocument) {
+ BinaryBoard board;
+ if (puzzle.getTree() != null) {
+ board = (BinaryBoard) puzzle.getTree().getRootNode().getBoard();
+ } else {
+ board = (BinaryBoard) puzzle.getBoardView().getBoard();
+ }
+
+ org.w3c.dom.Element boardElement = newDocument.createElement("board");
+ boardElement.setAttribute("width", String.valueOf(board.getWidth()));
+ boardElement.setAttribute("height", String.valueOf(board.getHeight()));
+
+ org.w3c.dom.Element cellsElement = newDocument.createElement("cells");
+ for (PuzzleElement puzzleElement : board.getPuzzleElements()) {
+ BinaryCell cell = (BinaryCell) puzzleElement;
+ if (cell.getData() != -2) {
+ org.w3c.dom.Element cellElement =
+ puzzle.getFactory().exportCell(newDocument, puzzleElement);
+ cellsElement.appendChild(cellElement);
+ }
+ }
+
+ boardElement.appendChild(cellsElement);
+ return boardElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java
new file mode 100644
index 000000000..2fc5b09ef
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryImporter.java
@@ -0,0 +1,117 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.model.PuzzleImporter;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.awt.*;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class BinaryImporter extends PuzzleImporter {
+ public BinaryImporter(Binary binary) {
+ super(binary);
+ }
+
+ @Override
+ public boolean acceptsRowsAndColumnsInput() {
+ return true;
+ }
+
+ @Override
+ public boolean acceptsTextInput() {
+ return false;
+ }
+
+ @Override
+ public void initializeBoard(int rows, int columns) {
+ BinaryBoard binaryBoard = new BinaryBoard(columns, rows);
+
+ for (int y = 0; y < rows; y++) {
+ for (int x = 0; x < columns; x++) {
+ BinaryCell cell = new BinaryCell(BinaryType.UNKNOWN.toValue(), new Point(x, y));
+ cell.setIndex(y * columns + x);
+ cell.setModifiable(true);
+ binaryBoard.setCell(x, y, cell);
+ }
+ }
+ puzzle.setCurrentBoard(binaryBoard);
+ }
+
+ /**
+ * Creates the board for building
+ *
+ * @param node xml document node
+ * @throws InvalidFileFormatException if file is invalid
+ */
+ @Override
+ public void initializeBoard(Node node) throws InvalidFileFormatException {
+ try {
+ if (!node.getNodeName().equalsIgnoreCase("board")) {
+ throw new InvalidFileFormatException(
+ "binary Importer: cannot find board puzzleElement");
+ }
+ Element boardElement = (Element) node;
+ if (boardElement.getElementsByTagName("cells").getLength() == 0) {
+ throw new InvalidFileFormatException(
+ "binary Importer: no puzzleElement found for board");
+ }
+
+ Element dataElement = (Element) boardElement.getElementsByTagName("cells").item(0);
+ NodeList elementDataList = dataElement.getElementsByTagName("cell");
+
+ BinaryBoard binaryBoard = null;
+ if (!boardElement.getAttribute("size").isEmpty()) {
+ int size = Integer.valueOf(boardElement.getAttribute("size"));
+ binaryBoard = new BinaryBoard(size);
+ } else {
+ if (!boardElement.getAttribute("width").isEmpty()
+ && !boardElement.getAttribute("height").isEmpty()) {
+ int width = Integer.valueOf(boardElement.getAttribute("width"));
+ int height = Integer.valueOf(boardElement.getAttribute("height"));
+ binaryBoard = new BinaryBoard(width, height);
+ }
+ }
+
+ int width = binaryBoard.getWidth();
+ int height = binaryBoard.getHeight();
+
+ if (binaryBoard == null || width % 2 != 0 || height % 2 != 0) {
+ throw new InvalidFileFormatException("binary Importer: invalid board dimensions");
+ }
+
+ for (int i = 0; i < elementDataList.getLength(); i++) {
+ BinaryCell cell =
+ (BinaryCell)
+ puzzle.getFactory()
+ .importCell(elementDataList.item(i), binaryBoard);
+ Point loc = cell.getLocation();
+ if (cell.getData() != BinaryType.UNKNOWN.toValue()) {
+ cell.setModifiable(false);
+ cell.setGiven(true);
+ }
+ binaryBoard.setCell(loc.x, loc.y, cell);
+ }
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ if (binaryBoard.getCell(x, y) == null) {
+ BinaryCell cell =
+ new BinaryCell(BinaryType.UNKNOWN.toValue(), new Point(x, y));
+ cell.setIndex(y * height + x);
+ cell.setModifiable(true);
+ binaryBoard.setCell(x, y, cell);
+ }
+ }
+ }
+ puzzle.setCurrentBoard(binaryBoard);
+ } catch (NumberFormatException e) {
+ throw new InvalidFileFormatException(
+ "binary Importer: unknown value where integer expected");
+ }
+ }
+
+ @Override
+ public void initializeBoard(String[] statements) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Binary cannot accept text input");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java
new file mode 100644
index 000000000..6e3413d7a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryType.java
@@ -0,0 +1,11 @@
+package edu.rpi.legup.puzzle.binary;
+
+public enum BinaryType {
+ ZERO,
+ ONE,
+ UNKNOWN;
+
+ public int toValue() {
+ return this.ordinal();
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java
new file mode 100644
index 000000000..b11554f28
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/BinaryView.java
@@ -0,0 +1,24 @@
+package edu.rpi.legup.puzzle.binary;
+
+import edu.rpi.legup.controller.BoardController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.ui.boardview.GridBoardView;
+import java.awt.*;
+
+public class BinaryView extends GridBoardView {
+
+ public BinaryView(BinaryBoard board) {
+ super(new BoardController(), new BinaryController(), board.getDimension());
+
+ for (PuzzleElement puzzleElement : board.getPuzzleElements()) {
+ BinaryCell cell = (BinaryCell) puzzleElement;
+ Point loc = cell.getLocation();
+ BinaryElementView elementView = new BinaryElementView(cell);
+ elementView.setIndex(cell.getIndex());
+ elementView.setSize(elementSize);
+ elementView.setLocation(
+ new Point(loc.x * elementSize.width, loc.y * elementSize.height));
+ elementViews.add(elementView);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/elements/BlankTile.java b/src/main/java/edu/rpi/legup/puzzle/binary/elements/BlankTile.java
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/elements/BlankTile.java
@@ -0,0 +1 @@
+
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/elements/NumberTile.java
@@ -0,0 +1 @@
+
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java
new file mode 100644
index 000000000..e38c6b78d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/CompleteRowColumnDirectRule.java
@@ -0,0 +1,55 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+
+public class CompleteRowColumnDirectRule extends DirectRule {
+
+ public CompleteRowColumnDirectRule() {
+ super(
+ "BINA-BASC-0003",
+ "Complete Row Column",
+ "If a row/column of length n contains n/2 of a single value, the remaining cells must contain the other value",
+ "edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard();
+ ContradictionRule contraRule = new UnbalancedRowOrColumnContradictionRule();
+ BinaryCell binaryCell = (BinaryCell) puzzleElement;
+ BinaryBoard modified = origBoard.copy();
+ BinaryCell c = (BinaryCell) modified.getPuzzleElement(puzzleElement);
+
+ // System.out.println("ORIG" + binaryCell.getData());
+ // System.out.println("AFTER" + Math.abs(binaryCell.getData() - 1));
+ modified.getPuzzleElement(puzzleElement).setData(binaryCell.getData());
+ System.out.println(contraRule.checkContradictionAt(modified, puzzleElement));
+
+ if (contraRule.checkContradictionAt(modified, puzzleElement) != null) {
+ return null;
+ }
+
+ return "Grouping of Three Ones or Zeros not found";
+ }
+
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/DuplicateRowsOrColumnsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/DuplicateRowsOrColumnsContradictionRule.java
new file mode 100644
index 000000000..8b0d88ae4
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/DuplicateRowsOrColumnsContradictionRule.java
@@ -0,0 +1,55 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+import edu.rpi.legup.puzzle.binary.BinaryType;
+import java.util.ArrayList;
+
+public class DuplicateRowsOrColumnsContradictionRule extends ContradictionRule {
+ private final String NO_CONTRADICTION_MESSAGE =
+ "Does not contain a contradiction at this index";
+ private final String INVALID_USE_MESSAGE = "Row or column must have a value in each cell";
+
+ public DuplicateRowsOrColumnsContradictionRule() {
+ super(
+ "BINA-CONT-0003",
+ "Duplicate Rows Or Columns",
+ "There must not be two rows or two columns that are duplicates",
+ "edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png");
+ }
+
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ BinaryBoard binaryBoard = (BinaryBoard) board;
+ BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement);
+
+ ArrayList row = binaryBoard.getRowTypes(cell.getLocation().y);
+
+ int size = row.size();
+
+ for (int i = 0; i < size; i++) {
+ if (i > cell.getLocation().y) {
+ ArrayList currRow = binaryBoard.getRowTypes(i);
+ if (currRow.equals(row)) {
+ return null;
+ }
+ }
+ }
+
+ ArrayList col = binaryBoard.getColTypes(cell.getLocation().x);
+
+ for (int i = 0; i < size; i++) {
+ if (i > cell.getLocation().x) {
+ ArrayList currCol = binaryBoard.getColTypes(i);
+ if (currCol.equals(col)) {
+ return null;
+ }
+ }
+ }
+
+ return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneOrZeroCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneOrZeroCaseRule.java
new file mode 100644
index 000000000..70549cd72
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneOrZeroCaseRule.java
@@ -0,0 +1,90 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+import edu.rpi.legup.puzzle.binary.BinaryType;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OneOrZeroCaseRule extends CaseRule {
+
+ public OneOrZeroCaseRule() {
+ super(
+ "BINA-CASE-0001",
+ "One or Zero",
+ "Each blank cell is either a one or a zero.",
+ "edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png");
+ }
+
+ @Override
+ public String checkRuleRaw(TreeTransition transition) {
+ List childTransitions = transition.getParents().get(0).getChildren();
+ if (childTransitions.size() != 2) {
+ return super.getInvalidUseOfRuleMessage() + ": This case rule must have 2 children.";
+ }
+
+ TreeTransition case1 = childTransitions.get(0);
+ TreeTransition case2 = childTransitions.get(1);
+ if (case1.getBoard().getModifiedData().size() != 1
+ || case2.getBoard().getModifiedData().size() != 1) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must have 1 modified cell for each case.";
+ }
+
+ BinaryCell mod1 = (BinaryCell) case1.getBoard().getModifiedData().iterator().next();
+ BinaryCell mod2 = (BinaryCell) case2.getBoard().getModifiedData().iterator().next();
+ if (!mod1.getLocation().equals(mod2.getLocation())) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must modify the same cell for each case.";
+ }
+
+ if (!((mod1.getType() == BinaryType.ZERO && mod2.getType() == BinaryType.ONE)
+ || (mod2.getType() == BinaryType.ZERO && mod1.getType() == BinaryType.ONE))) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must an empty white and black cell.";
+ }
+
+ return null;
+ }
+
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ BinaryBoard binaryBoard = (BinaryBoard) board.copy();
+ CaseBoard caseBoard = new CaseBoard(binaryBoard, this);
+ binaryBoard.setModifiable(false);
+ for (PuzzleElement element : binaryBoard.getPuzzleElements()) {
+ if (((BinaryCell) element).getType() == BinaryType.UNKNOWN) {
+ caseBoard.addPickableElement(element);
+ }
+ }
+ return caseBoard;
+ }
+
+ @Override
+ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
+ ArrayList cases = new ArrayList<>();
+ Board case1 = board.copy();
+ PuzzleElement data1 = case1.getPuzzleElement(puzzleElement);
+ data1.setData(BinaryType.ZERO.toValue());
+ case1.addModifiedData(data1);
+ cases.add(case1);
+
+ Board case2 = board.copy();
+ PuzzleElement data2 = case2.getPuzzleElement(puzzleElement);
+ data2.setData(BinaryType.ONE.toValue());
+ case2.addModifiedData(data2);
+ cases.add(case2);
+
+ return cases;
+ }
+
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneTileGapDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneTileGapDirectRule.java
new file mode 100644
index 000000000..2e1e96fa5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/OneTileGapDirectRule.java
@@ -0,0 +1,64 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+
+public class OneTileGapDirectRule extends DirectRule {
+ private final String INVALID_USE_MESSAGE = "Number at cell is incorrect";
+
+ public OneTileGapDirectRule() {
+ super(
+ "BINA-BASC-0002",
+ "One Tile Gap",
+ "If an empty tile is in between the same value, fill the gap with the other value.",
+ "edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png");
+ }
+
+ boolean checkLeftRight(BinaryCell c, BinaryBoard board) {
+ int x = c.getLocation().x;
+ int y = c.getLocation().y;
+ return board.getCell(x - 1, y).getType() != c.getType()
+ || board.getCell(x + 1, y).getType() != c.getType();
+ }
+
+ boolean checkUpDown(BinaryCell c, BinaryBoard board) {
+ int x = c.getLocation().x;
+ int y = c.getLocation().y;
+ return board.getCell(x, y - 1).getType() != c.getType()
+ || board.getCell(x, y + 1).getType() != c.getType();
+ }
+
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard();
+ ContradictionRule contraRule = new ThreeAdjacentContradictionRule();
+ BinaryCell binaryCell = (BinaryCell) puzzleElement;
+ BinaryBoard modified = origBoard.copy();
+
+ // System.out.println("ORIG" + binaryCell.getData());
+ // System.out.println("AFTER" + Math.abs(binaryCell.getData() - 1));
+ modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1));
+
+ PuzzleElement newP = binaryCell;
+
+ System.out.println(contraRule.checkContradictionAt(modified, newP));
+
+ if (contraRule.checkContradictionAt(modified, newP) == null) {
+ return null;
+ }
+ modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1));
+
+ return "Grouping of Three Ones or Zeros not found";
+ }
+
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/SurroundPairDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/SurroundPairDirectRule.java
new file mode 100644
index 000000000..dc2f07c8b
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/SurroundPairDirectRule.java
@@ -0,0 +1,48 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+
+public class SurroundPairDirectRule extends DirectRule {
+
+ public SurroundPairDirectRule() {
+ super(
+ "BINA-BASC-0001",
+ "Surround Pair",
+ "If two adjacent tiles have the same value, surround the tiles with the other value.",
+ "edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png");
+ }
+
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ BinaryBoard origBoard = (BinaryBoard) transition.getParents().get(0).getBoard();
+ ContradictionRule contraRule = new ThreeAdjacentContradictionRule();
+ BinaryCell binaryCell = (BinaryCell) puzzleElement;
+ BinaryBoard modified = origBoard.copy();
+
+ // System.out.println("ORIG" + binaryCell.getData());
+ // System.out.println("AFTER" + Math.abs(binaryCell.getData() - 1));
+ modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1));
+
+ PuzzleElement newP = binaryCell;
+
+ System.out.println(contraRule.checkContradictionAt(modified, newP));
+
+ if (contraRule.checkContradictionAt(modified, newP) == null) {
+ return null;
+ }
+ modified.getPuzzleElement(puzzleElement).setData(Math.abs(binaryCell.getData() - 1));
+
+ return "Grouping of Three Ones or Zeros not found";
+ }
+
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/ThreeAdjacentContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/ThreeAdjacentContradictionRule.java
new file mode 100644
index 000000000..075642246
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/ThreeAdjacentContradictionRule.java
@@ -0,0 +1,127 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+import edu.rpi.legup.puzzle.binary.BinaryType;
+
+public class ThreeAdjacentContradictionRule extends ContradictionRule {
+ private final String NO_CONTRADICTION_MESSAGE =
+ "Does not contain a contradiction at this index";
+ private final String INVALID_USE_MESSAGE = "Contradiction must be a zero or one";
+
+ public ThreeAdjacentContradictionRule() {
+ super(
+ "BINA-CONT-0001",
+ "Three Adjacent",
+ "There must not be three adjacent zeros or three adjacent ones in a row or column",
+ "edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png");
+ }
+
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ BinaryBoard binaryBoard = (BinaryBoard) board;
+ int height = binaryBoard.getHeight();
+ int width = binaryBoard.getWidth();
+
+ BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement);
+ System.out.println("THE CELL IS : " + cell.getType());
+ int cellX = cell.getLocation().x;
+ int cellY = cell.getLocation().y;
+ BinaryCell oneUp = null;
+ BinaryCell oneDown = null;
+ BinaryCell oneForward = null;
+ BinaryCell oneBackward = null;
+ BinaryCell twoUp = null;
+ BinaryCell twoDown = null;
+ BinaryCell twoForward = null;
+ BinaryCell twoBackward = null;
+ if (binaryBoard.getCell(cellX, cellY + 1) != null) {
+ oneUp = binaryBoard.getCell(cellX, cellY + 1);
+ }
+ if (binaryBoard.getCell(cellX, cellY - 1) != null) {
+ oneDown = binaryBoard.getCell(cellX, cellY - 1);
+ }
+ if (binaryBoard.getCell(cellX + 1, cellY) != null) {
+ oneForward = binaryBoard.getCell(cellX + 1, cellY);
+ }
+ if (binaryBoard.getCell(cellX - 1, cellY) != null) {
+ oneBackward = binaryBoard.getCell(cellX - 1, cellY);
+ }
+ if (binaryBoard.getCell(cellX, cellY + 2) != null) {
+ twoUp = binaryBoard.getCell(cellX, cellY + 2);
+ }
+ if (binaryBoard.getCell(cellX, cellY - 2) != null) {
+ twoDown = binaryBoard.getCell(cellX, cellY - 2);
+ }
+ if (binaryBoard.getCell(cellX + 2, cellY) != null) {
+ twoForward = binaryBoard.getCell(cellX + 2, cellY);
+ }
+ if (binaryBoard.getCell(cellX - 2, cellY) != null) {
+ twoBackward = binaryBoard.getCell(cellX - 2, cellY);
+ }
+
+ if (cell.getType() == BinaryType.ONE || cell.getType() == BinaryType.ZERO) {
+ if (twoBackward != null
+ && oneBackward != null
+ && twoBackward.getType() != BinaryType.UNKNOWN
+ && oneBackward.getType() != BinaryType.UNKNOWN) {
+ if (twoBackward.getType() == cell.getType()
+ && oneBackward.getType() == cell.getType()) {
+ System.out.println("1");
+ return null;
+ }
+ }
+ if (twoForward != null
+ && oneForward != null
+ && twoForward.getType() != BinaryType.UNKNOWN
+ && oneForward.getType() != BinaryType.UNKNOWN) {
+ if (twoForward.getType() == cell.getType()
+ && oneForward.getType() == cell.getType()) {
+ System.out.println("2");
+ return null;
+ }
+ }
+ if (twoDown != null
+ && oneDown != null
+ && twoDown.getType() != BinaryType.UNKNOWN
+ && oneDown.getType() != BinaryType.UNKNOWN) {
+ if (twoDown.getType() == cell.getType() && oneDown.getType() == cell.getType()) {
+ System.out.println("3");
+ return null;
+ }
+ }
+ if (twoUp != null
+ && oneUp != null
+ && twoUp.getType() != BinaryType.UNKNOWN
+ && oneUp.getType() != BinaryType.UNKNOWN) {
+ if (twoUp.getType() == cell.getType() && oneUp.getType() == cell.getType()) {
+ System.out.println("4");
+ return null;
+ }
+ }
+ if (oneBackward != null
+ && oneForward != null
+ && oneBackward.getType() != BinaryType.UNKNOWN
+ && oneForward.getType() != BinaryType.UNKNOWN) {
+ if (oneBackward.getType() == cell.getType()
+ && oneForward.getType() == cell.getType()) {
+ System.out.println("5");
+ return null;
+ }
+ }
+ if (oneUp != null
+ && oneDown != null
+ && oneUp.getType() != BinaryType.UNKNOWN
+ && oneDown.getType() != BinaryType.UNKNOWN) {
+ if (oneUp.getType() == cell.getType() && oneDown.getType() == cell.getType()) {
+ System.out.println("6");
+ return null;
+ }
+ }
+ }
+ return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowOrColumnContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowOrColumnContradictionRule.java
new file mode 100644
index 000000000..5089b3b5f
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/UnbalancedRowOrColumnContradictionRule.java
@@ -0,0 +1,68 @@
+package edu.rpi.legup.puzzle.binary.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.binary.BinaryBoard;
+import edu.rpi.legup.puzzle.binary.BinaryCell;
+import edu.rpi.legup.puzzle.binary.BinaryType;
+import java.util.Set;
+
+public class UnbalancedRowOrColumnContradictionRule extends ContradictionRule {
+
+ private final String NO_CONTRADICTION_MESSAGE =
+ "Does not contain a contradiction at this index";
+ private final String INVALID_USE_MESSAGE = "Row or column must have a value in each cell";
+
+ public UnbalancedRowOrColumnContradictionRule() {
+ super(
+ "BINA-CONT-0002",
+ "Unbalanced Row Or Column",
+ "Each row or column must contain an equal number of zeros and ones",
+ "edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png");
+ }
+
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ BinaryBoard binaryBoard = (BinaryBoard) board;
+
+ BinaryCell cell = (BinaryCell) binaryBoard.getPuzzleElement(puzzleElement);
+ Set row = binaryBoard.getRowCells(cell.getLocation().y);
+
+ int size = row.size();
+ int rowNumZeros = 0;
+ int rowNumOnes = 0;
+
+ for (BinaryCell item : row) {
+ if (item.getType() == BinaryType.ZERO) {
+ rowNumZeros++;
+ } else if (item.getType() == BinaryType.ONE) {
+ rowNumOnes++;
+ }
+ }
+
+ if (rowNumZeros == size / 2 && rowNumOnes == size / 2) {
+ return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE;
+ }
+
+ Set col = binaryBoard.getCol(cell.getLocation().x);
+
+ size = col.size();
+ int colNumZeros = 0;
+ int colNumOnes = 0;
+
+ for (BinaryCell item : col) {
+ if (item.getType() == BinaryType.ZERO) {
+ colNumZeros++;
+ } else if (item.getType() == BinaryType.ONE) {
+ colNumOnes++;
+ }
+ }
+
+ if (colNumZeros == size / 2 && colNumOnes == size / 2) {
+ return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE;
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt
new file mode 100644
index 000000000..c8cb0d1b9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/binary/rules/binary_reference_sheet.txt
@@ -0,0 +1,9 @@
+BINA-BASC-0001 : SurroundPairDirectRule
+BINA-BASC-0002 : OneTileGapDirectRule
+BINA-BASC-0003 : CompleteRowColumnDirectRule
+
+BINA-CONT-0001 : ThreeAdjacentContradictionRule
+BINA-CONT-0002 : UnbalancedRowOrColumnContradictionRule
+BINA-CONT-0003 : DuplicateRowsOrColumnsContradictionRule
+
+BINA-CASE-0001 : OneOrZeroCaseRule
\ No newline at end of file
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/Minesweeper.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/Minesweeper.java
new file mode 100644
index 000000000..ed8066f39
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/Minesweeper.java
@@ -0,0 +1,64 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.model.Puzzle;
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class Minesweeper extends Puzzle {
+
+ public static final String NAME = "Minesweeper";
+
+ public Minesweeper() {
+ this.name = NAME;
+ this.importer = new MinesweeperImporter(this);
+ this.exporter = new MinesweeperExporter(this);
+ this.factory = MinesweeperCellFactory.INSTANCE;
+ }
+
+ @Override
+ @Contract(pure = false)
+ public void initializeView() {
+ this.boardView = new MinesweeperView((MinesweeperBoard) this.currentBoard);
+ this.boardView.setBoard(this.currentBoard);
+ addBoardListener(boardView);
+ }
+
+ @Override
+ @Contract("_ -> null")
+ public @Nullable Board generatePuzzle(int difficulty) {
+ return null;
+ }
+
+ @Override
+ @Contract(pure = true)
+ public boolean isBoardComplete(@NotNull Board board) {
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board;
+
+ for (ContradictionRule rule : contradictionRules) {
+ if (rule.checkContradiction(minesweeperBoard) == null) {
+ return false;
+ }
+ }
+ for (PuzzleElement> data : minesweeperBoard.getPuzzleElements()) {
+ final MinesweeperCell cell = (MinesweeperCell) data;
+ if (cell.getData().equals(MinesweeperTileData.empty())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ @Contract(pure = true)
+ public void onBoardChange(@NotNull Board board) {}
+
+ @Override
+ @Contract(pure = true)
+ public boolean isValidDimensions(int rows, int columns) {
+ return rows >= 1 && columns >= 1;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperBoard.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperBoard.java
new file mode 100644
index 000000000..bef317ab5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperBoard.java
@@ -0,0 +1,36 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.model.gameboard.GridBoard;
+
+public class MinesweeperBoard extends GridBoard {
+
+ public MinesweeperBoard(int width, int height) {
+ super(width, height);
+ }
+
+ public MinesweeperBoard(int size) {
+ super(size);
+ }
+
+ @Override
+ public MinesweeperCell getCell(int x, int y) {
+ return (MinesweeperCell) super.getCell(x, y);
+ }
+
+ /**
+ * Performs a deep copy of the Board
+ *
+ * @return a new copy of the board that is independent of this one
+ */
+ @Override
+ public MinesweeperBoard copy() {
+ MinesweeperBoard newMinesweeperBoard =
+ new MinesweeperBoard(this.dimension.width, this.dimension.height);
+ for (int x = 0; x < this.dimension.width; x++) {
+ for (int y = 0; y < this.dimension.height; y++) {
+ newMinesweeperBoard.setCell(x, y, getCell(x, y).copy());
+ }
+ }
+ return newMinesweeperBoard;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperCell.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperCell.java
new file mode 100644
index 000000000..325e42b7b
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperCell.java
@@ -0,0 +1,73 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.model.elements.Element;
+import edu.rpi.legup.model.gameboard.GridCell;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+public class MinesweeperCell extends GridCell {
+
+ public MinesweeperCell(@NotNull MinesweeperTileData value, @NotNull Point location) {
+ super(value, location);
+ }
+
+ public @NotNull MinesweeperTileType getTileType() {
+ return super.data.type();
+ }
+
+ public @NotNull int getTileNumber() {
+ return super.data.data();
+ }
+
+ @Override
+ @Contract(pure = false)
+ /** Sets this cell's data to the value specified by {@link Element#getElementID()} */
+ public void setType(@NotNull Element element, @NotNull MouseEvent event) {
+ switch (element.getElementID()) {
+ case MinesweeperElementIdentifiers.BOMB -> {
+ this.data = MinesweeperTileData.bomb();
+ break;
+ }
+ case MinesweeperElementIdentifiers.FLAG -> {
+ final int currentData = super.data.data();
+ switch (event.getButton()) {
+ case MouseEvent.BUTTON1 -> {
+ if (currentData >= 8) {
+ this.data = MinesweeperTileData.empty();
+ return;
+ }
+ this.data = MinesweeperTileData.flag(currentData + 1);
+ return;
+ }
+ case MouseEvent.BUTTON2, MouseEvent.BUTTON3 -> {
+ if (currentData <= 0) {
+ this.data = MinesweeperTileData.empty();
+ return;
+ }
+ this.data = MinesweeperTileData.flag(currentData - 1);
+ return;
+ }
+ }
+ }
+ default -> {
+ this.data = MinesweeperTileData.empty();
+ }
+ }
+ }
+
+ public void setCellType(MinesweeperTileData type) {
+ this.data = type;
+ }
+
+ @Override
+ @Contract(pure = true)
+ public @NotNull MinesweeperCell copy() {
+ MinesweeperCell copy = new MinesweeperCell(data, (Point) location.clone());
+ copy.setIndex(index);
+ copy.setModifiable(isModifiable);
+ copy.setGiven(isGiven);
+ return copy;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperCellFactory.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperCellFactory.java
new file mode 100644
index 000000000..5fe6096a9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperCellFactory.java
@@ -0,0 +1,101 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.ElementFactory;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.awt.*;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class MinesweeperCellFactory extends ElementFactory {
+
+ /** The key of the data used in {@link NamedNodeMap} */
+ private static final String DATA_ATTRIBUTE = "data";
+
+ /** The key of the x position used in {@link NamedNodeMap} */
+ private static final String X_ATTRIBUTE = "x";
+
+ /** The key of the y position used in {@link NamedNodeMap} */
+ private static final String Y_ATTRIBUTE = "y";
+
+ private MinesweeperCellFactory() {}
+
+ public static final MinesweeperCellFactory INSTANCE = new MinesweeperCellFactory();
+
+ /**
+ * @param node node that represents the puzzleElement
+ * @param board Board to use to verify the newly created {@link MinesweeperCell} is valid
+ * @return a new {@link MinesweeperCell}
+ * @throws InvalidFileFormatException If the node name is not "cell"
+ * @throws NumberFormatException If the {@link #X_ATTRIBUTE} or {@link #Y_ATTRIBUTE} is not a
+ * number
+ * @throws NullPointerException If one of {@link #DATA_ATTRIBUTE}, {@link #X_ATTRIBUTE} or
+ * {@link #Y_ATTRIBUTE} does not exist.
+ */
+ @Override
+ @Contract(pure = false)
+ public @NotNull PuzzleElement importCell(
+ @NotNull Node node, @NotNull Board board) throws InvalidFileFormatException {
+ try {
+ if (!node.getNodeName().equalsIgnoreCase("cell")) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Factory: unknown puzzleElement puzzleElement");
+ }
+
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board;
+ final int width = minesweeperBoard.getWidth();
+ final int height = minesweeperBoard.getHeight();
+
+ final NamedNodeMap attributeList = node.getAttributes();
+ final int value =
+ Integer.parseInt(attributeList.getNamedItem(DATA_ATTRIBUTE).getNodeValue());
+ final int x = Integer.parseInt(attributeList.getNamedItem(X_ATTRIBUTE).getNodeValue());
+ final int y = Integer.parseInt(attributeList.getNamedItem(Y_ATTRIBUTE).getNodeValue());
+ if (x >= width || y >= height) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Factory: cell location out of bounds");
+ }
+ if (value < -2) {
+ throw new InvalidFileFormatException("Minesweeper Factory: cell unknown value");
+ }
+ final MinesweeperCell cell =
+ new MinesweeperCell(MinesweeperTileData.fromData(value), new Point(x, y));
+ cell.setIndex(y * height + x);
+ return cell;
+ } catch (NumberFormatException e) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Factory: unknown value where integer expected");
+ } catch (NullPointerException e) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Factory: could not find attribute(s)");
+ }
+ }
+
+ /**
+ * @param document Document used to create the element
+ * @param puzzleElement PuzzleElement cell
+ * @return a {@link Element} that contains the {@link #DATA_ATTRIBUTE}, {@link #X_ATTRIBUTE},
+ * and {@link #Y_ATTRIBUTE}
+ */
+ @Override
+ @Contract(pure = false)
+ public @NotNull Element exportCell(
+ @NotNull Document document,
+ @SuppressWarnings("rawtypes") @NotNull PuzzleElement puzzleElement) {
+ org.w3c.dom.Element cellElement = document.createElement("cell");
+
+ MinesweeperCell cell = (MinesweeperCell) puzzleElement;
+ Point loc = cell.getLocation();
+
+ cellElement.setAttribute(DATA_ATTRIBUTE, String.valueOf(cell.getData()));
+ cellElement.setAttribute(X_ATTRIBUTE, String.valueOf(loc.x));
+ cellElement.setAttribute(Y_ATTRIBUTE, String.valueOf(loc.y));
+
+ return cellElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperController.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperController.java
new file mode 100644
index 000000000..aaf061704
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperController.java
@@ -0,0 +1,57 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.controller.ElementController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import java.awt.event.MouseEvent;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+public class MinesweeperController extends ElementController {
+
+ /**
+ * If the button clicked was button 1, then {@link MinesweeperTileData#fromData(int)} is called
+ * with a value of {@code current.data() + 1}. If the button clicked was button 2 or 3, then
+ * {@link MinesweeperTileData#fromData(int)} is called with a value of {@code currentData() - 1}
+ * Otherwise {@link MinesweeperTileData#empty()} is returned.
+ *
+ * @param event The user's click data
+ * @param current The current data at the cell they clicked on
+ * @return A different cell data depending on what the current data is
+ */
+ @Contract(pure = true)
+ public static @NotNull MinesweeperTileData getNewCellDataOnClick(
+ @NotNull MouseEvent event, @NotNull MinesweeperTileData current) {
+ final int numberData = current.data();
+ return switch (event.getButton()) {
+ case MouseEvent.BUTTON1 -> MinesweeperTileData.fromData(numberData + 1);
+ case MouseEvent.BUTTON2, MouseEvent.BUTTON3 ->
+ MinesweeperTileData.fromData(numberData - 1);
+ default -> MinesweeperTileData.empty();
+ };
+ }
+
+ /**
+ * @see #getNewCellDataOnClick(MouseEvent, MinesweeperTileData)
+ * @param event The user's click data
+ * @param data The current data at the cell they clicked on
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ @Contract(pure = false)
+ public void changeCell(
+ @NotNull MouseEvent event, @SuppressWarnings("rawtypes") @NotNull PuzzleElement data) {
+ final MinesweeperCell cell = (MinesweeperCell) data;
+ if (event.isControlDown()) {
+ this.boardView
+ .getSelectionPopupMenu()
+ .show(
+ boardView,
+ this.boardView.getCanvas().getX() + event.getX(),
+ this.boardView.getCanvas().getY() + event.getY());
+ return;
+ }
+
+ final MinesweeperTileData newData = getNewCellDataOnClick(event, cell.getData());
+ data.setData(newData);
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperElementIdentifiers.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperElementIdentifiers.java
new file mode 100644
index 000000000..77e490f7e
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperElementIdentifiers.java
@@ -0,0 +1,16 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+public class MinesweeperElementIdentifiers {
+
+ /** ID for unset Minesweeper elements */
+ public static final String UNSET = "MINESWEEPER-UNSET";
+
+ /** ID for bomb Minesweeper elements */
+ public static final String BOMB = "MINESWEEPER-BOMB";
+
+ /** ID for empty Minesweeper elements */
+ public static final String EMPTY = "MINESWEEPER-EMPTY";
+
+ /** ID for flag Minesweeper elements */
+ public static final String FLAG = "MINESWEEPER-FLAG";
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperElementView.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperElementView.java
new file mode 100644
index 000000000..1bfc0d698
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperElementView.java
@@ -0,0 +1,83 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.ui.boardview.GridElementView;
+import java.awt.*;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+public class MinesweeperElementView extends GridElementView {
+
+ private static final Font FONT = new Font("TimesRoman", Font.BOLD, 16);
+ private static final Color FONT_COLOR = Color.BLACK;
+
+ public MinesweeperElementView(@NotNull MinesweeperCell cell) {
+ super(cell);
+ }
+
+ @Override
+ public @NotNull MinesweeperCell getPuzzleElement() {
+ return (MinesweeperCell) super.getPuzzleElement();
+ }
+
+ @Override
+ @SuppressWarnings("Duplicates")
+ @Contract(pure = true)
+ public void drawElement(@NotNull Graphics2D graphics2D) {
+ final MinesweeperCell cell = (MinesweeperCell) puzzleElement;
+ final MinesweeperTileType type = cell.getTileType();
+ if (type == MinesweeperTileType.FLAG) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.WHITE);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+
+ graphics2D.setColor(FONT_COLOR);
+ graphics2D.setFont(FONT);
+ final FontMetrics metrics = graphics2D.getFontMetrics(FONT);
+ final String value = String.valueOf(((MinesweeperCell) puzzleElement).getData().data());
+ final int xText = location.x + (size.width - metrics.stringWidth(value)) / 2;
+ final int yText =
+ location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent();
+ graphics2D.drawString(value, xText, yText);
+ return;
+ }
+ if (type == MinesweeperTileType.UNSET) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.DARK_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ return;
+ }
+ if (type == MinesweeperTileType.EMPTY) {
+ graphics2D.setColor(Color.LIGHT_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.drawImage(
+ MinesweeperView.EMPTY_IMAGE,
+ location.x,
+ location.y,
+ size.width,
+ size.height,
+ Color.GRAY,
+ null);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ }
+ if (type == MinesweeperTileType.BOMB) {
+ graphics2D.setColor(Color.LIGHT_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.drawImage(
+ MinesweeperView.BOMB_IMAGE,
+ location.x,
+ location.y,
+ size.width,
+ size.height,
+ Color.GRAY,
+ null);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperExporter.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperExporter.java
new file mode 100644
index 000000000..8ae9c5a9a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperExporter.java
@@ -0,0 +1,44 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.model.Puzzle;
+import edu.rpi.legup.model.PuzzleExporter;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class MinesweeperExporter extends PuzzleExporter {
+
+ public MinesweeperExporter(@NotNull Puzzle puzzle) {
+ super(puzzle);
+ }
+
+ @Override
+ @Contract(pure = true)
+ protected @NotNull Element createBoardElement(@NotNull Document newDocument) {
+ MinesweeperBoard board;
+ if (puzzle.getTree() != null) {
+ board = (MinesweeperBoard) puzzle.getTree().getRootNode().getBoard();
+ } else {
+ board = (MinesweeperBoard) puzzle.getBoardView().getBoard();
+ }
+
+ final org.w3c.dom.Element boardElement = newDocument.createElement("board");
+ boardElement.setAttribute("width", String.valueOf(board.getWidth()));
+ boardElement.setAttribute("height", String.valueOf(board.getHeight()));
+
+ final org.w3c.dom.Element cellsElement = newDocument.createElement("cells");
+ for (PuzzleElement> puzzleElement : board.getPuzzleElements()) {
+ final MinesweeperCell cell = (MinesweeperCell) puzzleElement;
+ if (!MinesweeperTileData.unset().equals(cell.getData())) {
+ final org.w3c.dom.Element cellElement =
+ puzzle.getFactory().exportCell(newDocument, puzzleElement);
+ cellsElement.appendChild(cellElement);
+ }
+ }
+
+ boardElement.appendChild(cellsElement);
+ return boardElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperImporter.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperImporter.java
new file mode 100644
index 000000000..419a69247
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperImporter.java
@@ -0,0 +1,128 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.model.PuzzleImporter;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.awt.*;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class MinesweeperImporter extends PuzzleImporter {
+
+ public MinesweeperImporter(@NotNull Minesweeper minesweeper) {
+ super(minesweeper);
+ }
+
+ @Override
+ @Contract(pure = true, value = "-> true")
+ public boolean acceptsRowsAndColumnsInput() {
+ return true;
+ }
+
+ @Override
+ @Contract(pure = true, value = "-> false")
+ public boolean acceptsTextInput() {
+ return false;
+ }
+
+ @Override
+ @Contract(pure = false)
+ public void initializeBoard(int rows, int columns) {
+ MinesweeperBoard minesweeperBoard = new MinesweeperBoard(columns, rows);
+
+ for (int y = 0; y < rows; y++) {
+ for (int x = 0; x < columns; x++) {
+ MinesweeperCell cell =
+ new MinesweeperCell(MinesweeperTileData.unset(), new Point(x, y));
+ cell.setIndex(y * columns + x);
+ cell.setModifiable(true);
+ minesweeperBoard.setCell(x, y, cell);
+ }
+ }
+ puzzle.setCurrentBoard(minesweeperBoard);
+ }
+
+ @Override
+ @Contract(pure = false)
+ public void initializeBoard(@NotNull Node node) throws InvalidFileFormatException {
+ try {
+ if (!node.getNodeName().equalsIgnoreCase("board")) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Importer: cannot find board puzzleElement");
+ }
+ final Element boardElement = (Element) node;
+ if (boardElement.getElementsByTagName("cells").getLength() == 0) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Importer: no puzzleElement found for board");
+ }
+ final Element dataElement =
+ (Element) boardElement.getElementsByTagName("cells").item(0);
+ final NodeList elementDataList = dataElement.getElementsByTagName("cell");
+
+ final MinesweeperBoard minesweeperBoard = getMinesweeperBoard(boardElement);
+
+ final int width = minesweeperBoard.getWidth();
+ final int height = minesweeperBoard.getHeight();
+
+ for (int i = 0; i < elementDataList.getLength(); i++) {
+ final MinesweeperCell cell =
+ (MinesweeperCell)
+ puzzle.getFactory()
+ .importCell(elementDataList.item(i), minesweeperBoard);
+ final Point loc = cell.getLocation();
+ if (MinesweeperTileData.unset().equals(cell.getData())) {
+ cell.setModifiable(false);
+ cell.setGiven(true);
+ }
+ minesweeperBoard.setCell(loc.x, loc.y, cell);
+ }
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ if (minesweeperBoard.getCell(x, y) == null) {
+ final MinesweeperCell cell =
+ new MinesweeperCell(MinesweeperTileData.unset(), new Point(x, y));
+ cell.setIndex(y * height + x);
+ cell.setModifiable(true);
+ minesweeperBoard.setCell(x, y, cell);
+ }
+ }
+ }
+ puzzle.setCurrentBoard(minesweeperBoard);
+ } catch (NumberFormatException e) {
+ throw new InvalidFileFormatException(
+ "Minesweeper Importer: unknown value where integer expected");
+ }
+ }
+
+ @Contract(pure = true)
+ private static @NotNull MinesweeperBoard getMinesweeperBoard(@NotNull Element boardElement)
+ throws InvalidFileFormatException {
+ MinesweeperBoard minesweeperBoard = null;
+ if (!boardElement.getAttribute("size").isEmpty()) {
+ final int size = Integer.parseInt(boardElement.getAttribute("size"));
+ minesweeperBoard = new MinesweeperBoard(size);
+ } else {
+ if (!boardElement.getAttribute("width").isEmpty()
+ && !boardElement.getAttribute("height").isEmpty()) {
+ final int width = Integer.parseInt(boardElement.getAttribute("width"));
+ final int height = Integer.parseInt(boardElement.getAttribute("height"));
+ minesweeperBoard = new MinesweeperBoard(width, height);
+ }
+ }
+
+ if (minesweeperBoard == null) {
+ throw new InvalidFileFormatException("Minesweeper Importer: invalid board dimensions");
+ }
+ return minesweeperBoard;
+ }
+
+ @Override
+ @Contract(value = "_ -> fail", pure = false)
+ public void initializeBoard(@NotNull String[] statements)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ throw new UnsupportedOperationException("Minesweeper does not support text input.");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperTileData.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperTileData.java
new file mode 100644
index 000000000..5296cf057
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperTileData.java
@@ -0,0 +1,107 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import java.util.Objects;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public record MinesweeperTileData(MinesweeperTileType type, int data) {
+
+ public static final int UNSET_DATA = -2;
+ public static final int BOMB_DATA = -1;
+ public static final int EMPTY_DATA = 0;
+
+ /**
+ * Always has a type of {@link MinesweeperTileType#UNSET}, and a data value of {@value
+ * UNSET_DATA}
+ */
+ private static final MinesweeperTileData UNSET =
+ new MinesweeperTileData(MinesweeperTileType.UNSET, UNSET_DATA);
+
+ /**
+ * Always has a type of {@link MinesweeperTileType#BOMB}, and a data value of {@value BOMB_DATA}
+ */
+ private static final MinesweeperTileData BOMB =
+ new MinesweeperTileData(MinesweeperTileType.BOMB, BOMB_DATA);
+
+ /**
+ * Always has a type of {@link MinesweeperTileType#EMPTY}, and a data value of {@value
+ * EMPTY_DATA}
+ */
+ private static final MinesweeperTileData EMPTY =
+ new MinesweeperTileData(MinesweeperTileType.EMPTY, EMPTY_DATA);
+
+ /**
+ * @param count how many bombs are near the flag
+ * @return a new {@link MinesweeperTileData} with a {@link MinesweeperTileData#type} of {@link
+ * MinesweeperTileType#FLAG} and a {@link MinesweeperTileData#data} of {@code count}
+ */
+ @Contract(pure = true)
+ public static @NotNull MinesweeperTileData flag(int count) {
+ return new MinesweeperTileData(MinesweeperTileType.FLAG, count);
+ }
+
+ /**
+ * @param data Determines what type of {@link MinesweeperTileData} to return.
+ * @return If {@code data} is one of {@link MinesweeperTileData#UNSET_DATA}, {@link
+ * MinesweeperTileData#BOMB_DATA}, or {@link MinesweeperTileData#EMPTY_DATA}, it will return
+ * that data. If {@code data} is less than any of the values, or greater than 8, it will
+ * return {@link MinesweeperTileData#UNSET_DATA}. Otherwise, it returns {@link
+ * MinesweeperTileData#flag(int)} and passes {@code data} as the parameter.
+ */
+ @Contract(pure = true)
+ public static @NotNull MinesweeperTileData fromData(int data) {
+ return switch (data) {
+ case UNSET_DATA -> unset();
+ case BOMB_DATA -> bomb();
+ case EMPTY_DATA -> empty();
+ default -> {
+ if (data <= -2 || data > 8) {
+ yield unset();
+ }
+ yield flag(data);
+ }
+ };
+ }
+
+ public static @NotNull MinesweeperTileData unset() {
+ return UNSET;
+ }
+
+ public static @NotNull MinesweeperTileData bomb() {
+ return BOMB;
+ }
+
+ public static @NotNull MinesweeperTileData empty() {
+ return EMPTY;
+ }
+
+ public boolean isUnset() {
+ return this.data == UNSET_DATA;
+ }
+
+ public boolean isBomb() {
+ return this.data == BOMB_DATA;
+ }
+
+ public boolean isEmpty() {
+ return this.data == EMPTY_DATA;
+ }
+
+ public boolean isFlag() {
+ return this.data > 0 && this.data <= 8;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MinesweeperTileData that = (MinesweeperTileData) o;
+ return data == that.data && type == that.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, data);
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperTileType.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperTileType.java
new file mode 100644
index 000000000..a682da5e5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperTileType.java
@@ -0,0 +1,14 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+public enum MinesweeperTileType {
+
+ /** A cell with nothing */
+ UNSET,
+
+ /** Represents a cell with no bombs in it */
+ EMPTY,
+ /** A flag has values 1-8 representing how many bombs are touching it */
+ FLAG,
+ /** A bomb tile that should be marked by nearby flags */
+ BOMB
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperUtilities.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperUtilities.java
new file mode 100644
index 000000000..d38460ac8
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperUtilities.java
@@ -0,0 +1,170 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.puzzle.minesweeper.rules.LessBombsThanFlagContradictionRule;
+import java.awt.*;
+import java.util.*;
+import java.util.Objects;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+public final class MinesweeperUtilities {
+
+ private static final int SURROUNDING_CELL_MIN_INDEX = 0;
+ private static final int SURROUNDING_CELL_MAX_INDEX = 9;
+
+ public static Stream getSurroundingCells(
+ MinesweeperBoard board, MinesweeperCell cell) {
+ final Point loc = cell.getLocation();
+ final int height = board.getHeight();
+ final int width = board.getWidth();
+ final int x = (int) loc.getX();
+ final int y = (int) loc.getY();
+ // IntStream of 0-9 to represent 2D matrix of surrounding elements,
+ // this maps from 0,0 to 2,2 so everything needs to be shifted
+ // left 1 and up 1 to become
+ // -1,1 to 1,1
+ // and 5 is skipped because we want to ignore 1,1
+ return IntStream.range(SURROUNDING_CELL_MIN_INDEX, SURROUNDING_CELL_MAX_INDEX)
+ // skip 0,0 element
+ .filter(i -> i != (SURROUNDING_CELL_MAX_INDEX - SURROUNDING_CELL_MIN_INDEX) / 2)
+ .mapToObj(
+ index -> {
+ final int newX = index / 3 - 1 + x;
+ final int newY = index % 3 - 1 + y;
+ // only keep valid locations
+ if (newX < 0 || newY < 0 || newX >= width || newY >= height) {
+ return null;
+ }
+ return board.getCell(newX, newY);
+ })
+ .filter(Objects::nonNull);
+ }
+
+ public static int countSurroundingType(
+ MinesweeperBoard board, MinesweeperCell cell, MinesweeperTileType type) {
+ final Stream stream =
+ getSurroundingCells(board, cell).map(MinesweeperCell::getData);
+ return (int)
+ (switch (type) {
+ case UNSET -> stream.filter(MinesweeperTileData::isUnset);
+ case BOMB -> stream.filter(MinesweeperTileData::isBomb);
+ case EMPTY -> stream.filter(MinesweeperTileData::isEmpty);
+ case FLAG -> stream.filter(MinesweeperTileData::isFlag);
+ })
+ .count();
+ }
+
+ public static int countSurroundingBombs(MinesweeperBoard board, MinesweeperCell cell) {
+ return countSurroundingType(board, cell, MinesweeperTileType.BOMB);
+ }
+
+ public static int countSurroundingUnset(MinesweeperBoard board, MinesweeperCell cell) {
+ return countSurroundingType(board, cell, MinesweeperTileType.UNSET);
+ }
+
+ public static int countSurroundingEmpty(MinesweeperBoard board, MinesweeperCell cell) {
+ return countSurroundingType(board, cell, MinesweeperTileType.EMPTY);
+ }
+
+ public static int countSurroundingFlags(MinesweeperBoard board, MinesweeperCell cell) {
+ return countSurroundingType(board, cell, MinesweeperTileType.FLAG);
+ }
+
+ /**
+ * @return how many bombs are left that need to be placed around {@code cell} which must be a
+ * flag
+ */
+ public int countNeededBombsFromFlag(MinesweeperBoard board, MinesweeperCell cell) {
+ if (!cell.getData().isFlag()) {
+ throw new IllegalArgumentException("Bombs are only needed surrounding flags");
+ }
+ return cell.getData().data() - countSurroundingBombs(board, cell);
+ }
+
+ public static boolean hasEmptyAdjacent(MinesweeperBoard board, MinesweeperCell cell) {
+ ArrayList adjCells = getAdjacentCells(board, cell);
+ for (MinesweeperCell adjCell : adjCells) {
+ if (adjCell.getTileType() == MinesweeperTileType.UNSET) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static ArrayList getAdjacentCells(
+ MinesweeperBoard board, MinesweeperCell cell) {
+ ArrayList adjCells = new ArrayList();
+ Point cellLoc = cell.getLocation();
+ for (int i = -1; i <= 1; i++) {
+ for (int j = -1; j <= 1; j++) {
+ if (cellLoc.getX() + i < 0
+ || cellLoc.y + j < 0
+ || cellLoc.x + i >= board.getWidth()
+ || cellLoc.y + j >= board.getHeight()) {
+ continue;
+ }
+ MinesweeperCell adjCell =
+ (MinesweeperCell) board.getCell(cellLoc.x + i, cellLoc.y + j);
+ if (adjCell == null || adjCell == cell) {
+ continue;
+ }
+ adjCells.add(adjCell);
+ }
+ }
+ return adjCells;
+ }
+
+ public static ArrayList getCombinations(int chosenNumItems, int totalNumItems) {
+ ArrayList combinations = new ArrayList();
+
+ // calculate all combinations
+ boolean[] array = new boolean[totalNumItems];
+ recurseCombinations(combinations, 0, chosenNumItems, 0, totalNumItems, array);
+
+ return combinations;
+ }
+
+ private static void recurseCombinations(
+ ArrayList result,
+ int curIndex,
+ int maxBlack,
+ int numBlack,
+ int len,
+ boolean[] workingArray) {
+ if (curIndex == len) {
+ // complete, but not valid solution
+ if (numBlack != maxBlack) {
+ return;
+ }
+ // complete and valid solution
+ result.add(workingArray.clone());
+ return;
+ }
+ // there is no chance of completing the required number of solutions, so quit
+ if (len - curIndex < maxBlack - numBlack) {
+ return;
+ }
+
+ if (numBlack < maxBlack) {
+ workingArray[curIndex] = true;
+ recurseCombinations(result, curIndex + 1, maxBlack, numBlack + 1, len, workingArray);
+ }
+ workingArray[curIndex] = false;
+ recurseCombinations(result, curIndex + 1, maxBlack, numBlack, len, workingArray);
+ }
+
+ public static boolean isForcedBomb(MinesweeperBoard board, MinesweeperCell cell) {
+
+ LessBombsThanFlagContradictionRule tooManyBombs = new LessBombsThanFlagContradictionRule();
+ MinesweeperBoard emptyCaseBoard = board.copy();
+ MinesweeperCell emptyCell = (MinesweeperCell) emptyCaseBoard.getPuzzleElement(cell);
+ emptyCell.setCellType(MinesweeperTileData.empty());
+ ArrayList adjCells = getAdjacentCells(emptyCaseBoard, emptyCell);
+ for (MinesweeperCell adjCell : adjCells) {
+ if (tooManyBombs.checkContradictionAt(emptyCaseBoard, adjCell) == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperView.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperView.java
new file mode 100644
index 000000000..e8ab8dfcb
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/MinesweeperView.java
@@ -0,0 +1,65 @@
+package edu.rpi.legup.puzzle.minesweeper;
+
+import edu.rpi.legup.controller.BoardController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.ui.boardview.GridBoardView;
+import java.awt.*;
+import java.io.IOException;
+import java.util.Objects;
+import javax.imageio.ImageIO;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
+public class MinesweeperView extends GridBoardView {
+
+ private static final Logger LOGGER = LogManager.getLogger(MinesweeperView.class.getName());
+ public static final Image BOMB_IMAGE;
+
+ public static final Image EMPTY_IMAGE;
+
+ static {
+ Image tempBombImage = null;
+ try {
+ tempBombImage =
+ ImageIO.read(
+ Objects.requireNonNull(
+ ClassLoader.getSystemClassLoader()
+ .getResource(
+ "edu/rpi/legup/images/minesweeper/tiles/Bomb.png")));
+ } catch (IOException e) {
+ LOGGER.error("Failed to open Minesweeper images");
+ }
+ BOMB_IMAGE = tempBombImage;
+ }
+
+ static {
+ Image tempEmptyImage = null;
+ try {
+ tempEmptyImage =
+ ImageIO.read(
+ Objects.requireNonNull(
+ ClassLoader.getSystemClassLoader()
+ .getResource(
+ "edu/rpi/legup/images/minesweeper/tiles/Empty.png")));
+ } catch (IOException e) {
+ LOGGER.error("Failed to open Minesweeper images");
+ }
+ EMPTY_IMAGE = tempEmptyImage;
+ }
+
+ public MinesweeperView(@NotNull MinesweeperBoard board) {
+ super(new BoardController(), new MinesweeperController(), board.getDimension());
+
+ for (PuzzleElement> puzzleElement : board.getPuzzleElements()) {
+ final MinesweeperCell cell = (MinesweeperCell) puzzleElement;
+ final Point loc = cell.getLocation();
+ final MinesweeperElementView elementView = new MinesweeperElementView(cell);
+ elementView.setIndex(cell.getIndex());
+ elementView.setSize(elementSize);
+ elementView.setLocation(
+ new Point(loc.x * elementSize.width, loc.y * elementSize.height));
+ elementViews.add(elementView);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java
new file mode 100644
index 000000000..78a5d320c
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/BombTile.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.minesweeper.elements;
+
+import edu.rpi.legup.model.elements.NonPlaceableElement;
+
+public class BombTile extends NonPlaceableElement {
+ public BombTile() {
+ super(
+ "MINE-UNPL-0001",
+ "Bomb",
+ "A bomb",
+ "edu/rpi/legup/images/minesweeper/tiles/Bomb.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/EmptyTile.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/EmptyTile.java
new file mode 100644
index 000000000..7149bfa6a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/EmptyTile.java
@@ -0,0 +1,14 @@
+package edu.rpi.legup.puzzle.minesweeper.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class EmptyTile extends PlaceableElement {
+
+ public EmptyTile() {
+ super(
+ "MINE-PLAC-0002",
+ "Empty",
+ "An empty tile",
+ "edu/rpi/legup/images/minesweeper/tiles/Empty.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/FlagTile.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/FlagTile.java
new file mode 100644
index 000000000..0bbca81f9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/FlagTile.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.minesweeper.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class FlagTile extends PlaceableElement {
+ public FlagTile() {
+ super(
+ "MINE-PLAC-0001",
+ "Flag",
+ "The flag",
+ "edu/rpi/legup/images/nurikabe/tiles/BlackTile.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java
new file mode 100644
index 000000000..447e2840c
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/UnsetTile.java
@@ -0,0 +1,14 @@
+package edu.rpi.legup.puzzle.minesweeper.elements;
+
+import edu.rpi.legup.model.elements.NonPlaceableElement;
+
+public class UnsetTile extends NonPlaceableElement {
+
+ public UnsetTile() {
+ super(
+ "MINE-UNPL-0002",
+ "Unset",
+ "An unset tile",
+ "edu/rpi/legup/images/minesweeper/tiles/Unset.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/minesweeper_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/minesweeper_elements_reference_sheet.txt
new file mode 100644
index 000000000..08ce23f59
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/elements/minesweeper_elements_reference_sheet.txt
@@ -0,0 +1,4 @@
+MINE-UNPL-0001 : BombTile
+MINE-PLAC-0001 : FlagTile
+MINE-PLAC-0002 : EmptyTile
+MINE-UNPL-0002 : UnsetTile
\ No newline at end of file
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/BombOrFilledCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/BombOrFilledCaseRule.java
new file mode 100644
index 000000000..a1ef97928
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/BombOrFilledCaseRule.java
@@ -0,0 +1,93 @@
+package edu.rpi.legup.puzzle.minesweeper.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperBoard;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperCell;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperTileData;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BombOrFilledCaseRule extends CaseRule {
+
+ public BombOrFilledCaseRule() {
+ super(
+ "MINE-CASE-0001",
+ "Bomb Or Filled",
+ "A cell can either be a bomb or filled.\n",
+ "edu/rpi/legup/images/minesweeper/cases/BombOrFilled.jpg");
+ }
+
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board.copy();
+ CaseBoard caseBoard = new CaseBoard(minesweeperBoard, this);
+ minesweeperBoard.setModifiable(false);
+ for (PuzzleElement data : minesweeperBoard.getPuzzleElements()) {
+ MinesweeperCell cell = (MinesweeperCell) data;
+ if (cell.getData().isUnset()) {
+ caseBoard.addPickableElement(data);
+ }
+ }
+ return caseBoard;
+ }
+
+ @Override
+ public List getCases(Board board, PuzzleElement puzzleElement) {
+ ArrayList cases = new ArrayList<>();
+
+ Board case1 = board.copy();
+ MinesweeperCell cell1 = (MinesweeperCell) case1.getPuzzleElement(puzzleElement);
+ cell1.setData(MinesweeperTileData.bomb());
+ case1.addModifiedData(cell1);
+ cases.add(case1);
+
+ Board case2 = board.copy();
+ MinesweeperCell cell2 = (MinesweeperCell) case2.getPuzzleElement(puzzleElement);
+ cell2.setData(MinesweeperTileData.empty());
+ case2.addModifiedData(cell2);
+ cases.add(case2);
+ return cases;
+ }
+
+ @Override
+ public String checkRuleRaw(TreeTransition transition) {
+ List childTransitions = transition.getParents().get(0).getChildren();
+ if (childTransitions.size() != 2) {
+ return super.getInvalidUseOfRuleMessage() + ": This case rule must have 2 children.";
+ }
+
+ TreeTransition case1 = childTransitions.get(0);
+ TreeTransition case2 = childTransitions.get(1);
+ if (case1.getBoard().getModifiedData().size() != 1
+ || case2.getBoard().getModifiedData().size() != 1) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must have 1 modified cell for each case.";
+ }
+
+ MinesweeperCell mod1 =
+ (MinesweeperCell) case1.getBoard().getModifiedData().iterator().next();
+ MinesweeperCell mod2 =
+ (MinesweeperCell) case2.getBoard().getModifiedData().iterator().next();
+ if (!mod1.getLocation().equals(mod2.getLocation())) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must modify the same cell for each case.";
+ }
+
+ if (!((mod1.getData().isBomb() && mod2.getData().isEmpty())
+ || (mod2.getData().isBomb() && mod1.getData().isEmpty()))) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must an empty cell and a bomb cell.";
+ }
+
+ return null;
+ }
+
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/FinishWithBombsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/FinishWithBombsDirectRule.java
new file mode 100644
index 000000000..e85008d23
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/FinishWithBombsDirectRule.java
@@ -0,0 +1,64 @@
+package edu.rpi.legup.puzzle.minesweeper.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.minesweeper.*;
+
+public class FinishWithBombsDirectRule extends DirectRule {
+ public FinishWithBombsDirectRule() {
+ super(
+ "MINE-BASC-0001",
+ "Finish with Bombs",
+ "The remaining unknowns around a flag must be bombs to satisfy the number",
+ "edu/rpi/legup/images/minesweeper/direct/Fill_Bombs.jpg");
+ }
+
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ MinesweeperBoard board = (MinesweeperBoard) transition.getBoard();
+ MinesweeperBoard parentBoard = (MinesweeperBoard) transition.getParents().get(0).getBoard();
+ MinesweeperCell cell = (MinesweeperCell) board.getPuzzleElement(puzzleElement);
+ MinesweeperCell parentCell = (MinesweeperCell) parentBoard.getPuzzleElement(puzzleElement);
+
+ if (!(parentCell.getTileType() == MinesweeperTileType.UNSET
+ && cell.getTileType() == MinesweeperTileType.BOMB)) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This cell must be black to be applicable with this rule.";
+ }
+
+ if (MinesweeperUtilities.isForcedBomb(parentBoard, cell)) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + ": This cell is not forced to be black";
+ }
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) node.getBoard().copy();
+ for (PuzzleElement element : minesweeperBoard.getPuzzleElements()) {
+ MinesweeperCell cell = (MinesweeperCell) element;
+ if (cell.getTileType() == MinesweeperTileType.UNSET
+ && MinesweeperUtilities.isForcedBomb(
+ (MinesweeperBoard) node.getBoard(), cell)) {
+ cell.setCellType(MinesweeperTileData.bomb());
+ minesweeperBoard.addModifiedData(cell);
+ }
+ }
+ if (minesweeperBoard.getModifiedData().isEmpty()) {
+ return null;
+ } else {
+ return minesweeperBoard;
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/LessBombsThanFlagContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/LessBombsThanFlagContradictionRule.java
new file mode 100644
index 000000000..c9919343f
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/LessBombsThanFlagContradictionRule.java
@@ -0,0 +1,50 @@
+package edu.rpi.legup.puzzle.minesweeper.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.minesweeper.*;
+import java.util.ArrayList;
+
+public class LessBombsThanFlagContradictionRule extends ContradictionRule {
+ private final String NO_CONTRADICTION_MESSAGE =
+ "Does not contain a contradiction at this index";
+ private final String INVALID_USE_MESSAGE = "Contradiction must be a region";
+
+ public LessBombsThanFlagContradictionRule() {
+ super(
+ "MINE-CONT-0000",
+ "Less Bombs Than Flag",
+ "There can not be less then the number of Bombs around a flag then the specified number\n",
+ "edu/rpi/legup/images/nurikabe/contradictions/NoNumber.png");
+ }
+
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board;
+ MinesweeperCell cell = (MinesweeperCell) minesweeperBoard.getPuzzleElement(puzzleElement);
+
+ int cellNum = cell.getTileNumber();
+ if (cellNum <= 0 || cellNum >= 9) {
+ return super.getNoContradictionMessage();
+ }
+ int numEmpty = 0;
+ int numAdj = 0;
+ ArrayList adjCells =
+ MinesweeperUtilities.getAdjacentCells(minesweeperBoard, cell);
+ for (MinesweeperCell adjCell : adjCells) {
+ numAdj++;
+ if (adjCell.getTileType() == MinesweeperTileType.EMPTY && adjCell != cell) {
+ numEmpty++;
+ }
+ }
+ System.out.println(numEmpty);
+ System.out.println(numAdj);
+ System.out.println(cellNum);
+ if (numEmpty > (numAdj - cellNum)) {
+ return null;
+ }
+
+ return super.getNoContradictionMessage();
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/MoreBombsThanFlagContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/MoreBombsThanFlagContradictionRule.java
new file mode 100644
index 000000000..ecfdbad66
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/MoreBombsThanFlagContradictionRule.java
@@ -0,0 +1,54 @@
+package edu.rpi.legup.puzzle.minesweeper.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperBoard;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperCell;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperTileType;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperUtilities;
+import java.util.ArrayList;
+
+public class MoreBombsThanFlagContradictionRule extends ContradictionRule {
+
+ public MoreBombsThanFlagContradictionRule() {
+ super(
+ "MINE-CONT-0001",
+ "More Bombs Than Flag",
+ "There can not be more Bombs around a flag than the specified number\n",
+ "edu/rpi/legup/images/minesweeper/contradictions/Bomb_Surplus.jpg");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board;
+ MinesweeperCell cell = (MinesweeperCell) minesweeperBoard.getPuzzleElement(puzzleElement);
+
+ int cellNum = cell.getTileNumber();
+ if (cellNum < 0 || cellNum >= 10) {
+ return super.getNoContradictionMessage();
+ }
+ int numBlack = 0;
+ ArrayList adjCells =
+ MinesweeperUtilities.getAdjacentCells(minesweeperBoard, cell);
+ for (MinesweeperCell adjCell : adjCells) {
+ if (adjCell.getTileType() == MinesweeperTileType.BOMB) {
+ numBlack++;
+ }
+ }
+ if (numBlack > cellNum) {
+ return null;
+ }
+
+ return super.getNoContradictionMessage();
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/SatisfyFlagCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/SatisfyFlagCaseRule.java
new file mode 100644
index 000000000..a59369b7a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/minesweeper/rules/SatisfyFlagCaseRule.java
@@ -0,0 +1,231 @@
+package edu.rpi.legup.puzzle.minesweeper.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.minesweeper.*;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+public class SatisfyFlagCaseRule extends CaseRule {
+ public SatisfyFlagCaseRule() {
+ super(
+ "MINE-CASE-0002",
+ "Satisfy Flag",
+ "Create a different path for each valid way to mark bombs and filled cells around a flag",
+ "edu/rpi/legup/images/minesweeper/cases/Satisfy_Flag.png");
+ }
+
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board.copy();
+ CaseBoard caseBoard = new CaseBoard(minesweeperBoard, this);
+ minesweeperBoard.setModifiable(false);
+ for (PuzzleElement data : minesweeperBoard.getPuzzleElements()) {
+ MinesweeperCell cell = (MinesweeperCell) data;
+ if (cell.getTileNumber() > 0
+ && cell.getTileNumber() <= 8
+ && MinesweeperUtilities.hasEmptyAdjacent(minesweeperBoard, cell)) {
+ caseBoard.addPickableElement(data);
+ }
+ }
+ return caseBoard;
+ }
+
+ @Override
+ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
+ ArrayList cases = new ArrayList();
+
+ // get value of cell
+ MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board.copy();
+ MinesweeperCell cell = (MinesweeperCell) minesweeperBoard.getPuzzleElement(puzzleElement);
+ int cellMaxBlack = cell.getTileNumber();
+ if (cellMaxBlack <= 0 || cellMaxBlack > 8) { // cell is not valid cell
+ return null;
+ }
+
+ // find number of black & unset squares
+ int cellNumBomb = 0;
+ int cellNumUnset = 0;
+ ArrayList unsetCells = new ArrayList();
+ ArrayList adjCells =
+ MinesweeperUtilities.getAdjacentCells(minesweeperBoard, cell);
+ for (MinesweeperCell adjCell : adjCells) {
+ if (adjCell.getTileType() == MinesweeperTileType.BOMB) {
+ cellNumBomb++;
+ }
+ if (adjCell.getTileType() == MinesweeperTileType.UNSET) {
+ cellNumUnset++;
+ unsetCells.add(adjCell);
+ }
+ }
+ // no cases if no empty or if too many black already
+ if (cellNumBomb >= cellMaxBlack || cellNumUnset == 0) {
+ return cases;
+ }
+
+ // generate all cases as boolean expressions
+ ArrayList combinations;
+ combinations =
+ MinesweeperUtilities.getCombinations(cellMaxBlack - cellNumBomb, cellNumUnset);
+
+ for (int i = 0; i < combinations.size(); i++) {
+ Board case_ = board.copy();
+ for (int j = 0; j < combinations.get(i).length; j++) {
+ cell = (MinesweeperCell) case_.getPuzzleElement(unsetCells.get(j));
+ if (combinations.get(i)[j]) {
+ cell.setCellType(MinesweeperTileData.bomb());
+ } else {
+ cell.setCellType(MinesweeperTileData.empty());
+ }
+ case_.addModifiedData(cell);
+ }
+ cases.add(case_);
+ }
+
+ return cases;
+ }
+
+ @Override
+ public String checkRuleRaw(TreeTransition transition) {
+ TreeNode parent = transition.getParents().get(0);
+ List childTransitions = parent.getChildren();
+
+ /*
+ * In order for the transition to be valid, it can only be applied to
+ * one cell, thus:
+ * * there must be modified cells
+ * * all modified cells must share at least one common adjacent
+ * cell
+ * * all modified cells must fit within a 3X3 square
+ * * the center of one of the possible squares must be a cell
+ * with a number
+ * * that cells possible combinations must match the transitions
+ * If all the above is verified, then the transition is valid
+ */
+
+ /* ensure there are modified cells */
+ Set modCells = transition.getBoard().getModifiedData();
+ if (modCells.size() <= 0) {
+ return super.getInvalidUseOfRuleMessage();
+ }
+
+ /* ensure modified cells occur within a 3X3 square */
+ int minVertLoc = Integer.MAX_VALUE, maxVertLoc = Integer.MIN_VALUE;
+ int minHorzLoc = Integer.MAX_VALUE, maxHorzLoc = Integer.MIN_VALUE;
+ for (PuzzleElement modCell : modCells) {
+ Point loc = ((MinesweeperCell) modCell).getLocation();
+ if (loc.x < minHorzLoc) {
+ minHorzLoc = loc.x;
+ }
+ if (loc.x > maxHorzLoc) {
+ maxHorzLoc = loc.x;
+ }
+ if (loc.y < minVertLoc) {
+ minVertLoc = loc.y;
+ }
+ if (loc.y > maxVertLoc) {
+ maxVertLoc = loc.y;
+ }
+ }
+ if (maxVertLoc - minVertLoc > 3 || maxHorzLoc - minHorzLoc > 3) {
+ return super.getInvalidUseOfRuleMessage();
+ }
+
+ /* get the center of all possible 3X3 squares,
+ * and collect all that have numbers */
+ MinesweeperBoard board = (MinesweeperBoard) transition.getParents().get(0).getBoard();
+ ArrayList possibleCenters = new ArrayList();
+ for (PuzzleElement modCell : modCells) {
+ ArrayList adjacentCells =
+ MinesweeperUtilities.getAdjacentCells(board, (MinesweeperCell) modCell);
+ for (MinesweeperCell cell : adjacentCells) {
+ possibleCenters.add(cell);
+ }
+ }
+
+ // removing all elements without a valid number
+ possibleCenters.removeIf(x -> x.getTileNumber() <= 0 || x.getTileNumber() >= 9);
+ if (possibleCenters.isEmpty()) {
+ return super.getInvalidUseOfRuleMessage();
+ }
+
+ /* Now go through the remaining centers, and check if their combinations
+ * match the transitions */
+ for (MinesweeperCell possibleCenter : possibleCenters) {
+ int numBlack = 0;
+ int numEmpty = 0;
+ int maxBlack = possibleCenter.getTileNumber();
+ for (MinesweeperCell adjCell :
+ MinesweeperUtilities.getAdjacentCells(board, possibleCenter)) {
+ if (adjCell.getTileType() == MinesweeperTileType.BOMB) {
+ numBlack++;
+ }
+ if (adjCell.getTileType() == MinesweeperTileType.UNSET) {
+ numEmpty++;
+ }
+ }
+ if (numEmpty <= 0 || numBlack > maxBlack) {
+ // this cell has no cases (no empty) or is already broken (too many black)
+ continue;
+ }
+
+ ArrayList combinations =
+ MinesweeperUtilities.getCombinations(maxBlack - numBlack, numEmpty);
+ if (combinations.size() != childTransitions.size()) {
+ // not this center because combinations do not match transitions
+ continue;
+ }
+ boolean quitEarly = false;
+ for (TreeTransition trans : childTransitions) {
+ /* convert the transition board into boolean format, so that it
+ * can be compared to the combinations */
+ MinesweeperBoard transBoard = (MinesweeperBoard) trans.getBoard();
+ ArrayList transModCells = new ArrayList();
+ for (PuzzleElement modCell : modCells) {
+ transModCells.add((MinesweeperCell) transBoard.getPuzzleElement(modCell));
+ }
+
+ boolean[] translatedModCells = new boolean[transModCells.size()];
+ for (int i = 0; i < transModCells.size(); i++) {
+ if (transModCells.get(i).getTileType() == MinesweeperTileType.BOMB) {
+ translatedModCells[i] = true;
+ } else {
+ translatedModCells[i] = false;
+ }
+ }
+
+ // try to find the above state in the combinations, remove if found
+ boolean removed = false;
+ for (boolean[] combination : combinations) {
+ if (Arrays.equals(combination, translatedModCells)) {
+ combinations.remove(combination);
+ removed = true;
+ break;
+ }
+ }
+ // if combination not found, no need to check further, just quit
+ if (!removed) {
+ quitEarly = true;
+ break;
+ }
+ }
+
+ /* we found a center that is valid */
+ if (combinations.isEmpty() && !quitEarly) {
+ return null;
+ }
+ }
+
+ return super.getInvalidUseOfRuleMessage();
+ }
+
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattle.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattle.java
new file mode 100644
index 000000000..39092bbc6
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattle.java
@@ -0,0 +1,35 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.Puzzle;
+import edu.rpi.legup.model.gameboard.Board;
+
+public class StarBattle extends Puzzle {
+ public StarBattle() {
+ super();
+ this.name = "StarBattle";
+
+ this.importer = new StarBattleImporter(this);
+ this.exporter = new StarBattleExporter(this);
+
+ this.factory = new StarBattleCellFactory();
+ }
+
+ @Override
+ public void initializeView() {
+ boardView = new StarBattleView((StarBattleBoard) currentBoard);
+ addBoardListener(boardView);
+ }
+
+ @Override
+ public Board generatePuzzle(int difficulty) {
+ return null;
+ }
+
+ @Override
+ public boolean isBoardComplete(Board board) {
+ return true;
+ }
+
+ @Override
+ public void onBoardChange(Board board) {}
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleBoard.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleBoard.java
new file mode 100644
index 000000000..5132f33e4
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleBoard.java
@@ -0,0 +1,113 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.gameboard.GridBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import java.util.*;
+
+public class StarBattleBoard extends GridBoard {
+
+ private int size;
+ private int puzzleNum;
+ protected List regions;
+
+ // private ArrayList groupSizes;
+
+ public StarBattleBoard(int size, int num) {
+ super(size, size);
+ this.size = size;
+ this.puzzleNum = num;
+ this.regions = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ regions.add(new StarBattleRegion());
+ }
+ }
+
+ @Override
+ public StarBattleCell getCell(int x, int y) {
+ return (StarBattleCell) super.getCell(x, y);
+ }
+
+ /*
+ public StarBattleCell getCell(int groupIndex, int x, int y) {
+ return getCell(x + (groupIndex % groupSize) * groupSize, y + (groupIndex / groupSize) * groupSize);
+ }*/
+
+ public int getSize() {
+ return size;
+ }
+
+ public Set getRow(int rowNum) {
+ Set row = new HashSet<>();
+ for (int i = 0; i < size; i++) {
+ row.add(getCell(i, rowNum));
+ }
+ return row;
+ }
+
+ public int getPuzzleNumber() {
+ return puzzleNum;
+ }
+
+ public Set getCol(int colNum) {
+ Set column = new HashSet<>();
+ for (int i = 0; i < size; i++) {
+ column.add(getCell(colNum, i));
+ }
+ return column;
+ }
+
+ public StarBattleRegion getRegion(int index) {
+ if (index >= size) {
+ return null;
+ }
+ return regions.get(index);
+ }
+
+ public StarBattleRegion getRegion(StarBattleCell cell) {
+ return getRegion(cell.getGroupIndex());
+ }
+
+ public void setRegion(int regionNum, StarBattleRegion region) {
+ regions.set(regionNum, region);
+ }
+
+ public int columnStars(int columnIndex) {
+ int stars = 0;
+ if (columnIndex < size) {
+ for (StarBattleCell c : this.getCol(columnIndex)) {
+ if (c.getType() == StarBattleCellType.STAR) {
+ ++stars;
+ }
+ }
+ }
+ return stars;
+ }
+
+ public int rowStars(int rowIndex) {
+ int stars = 0;
+ if (rowIndex < size) {
+ for (StarBattleCell c : this.getRow(rowIndex)) {
+ if (c.getType() == StarBattleCellType.STAR) {
+ ++stars;
+ }
+ }
+ }
+ return stars;
+ }
+
+ public StarBattleBoard copy() {
+ StarBattleBoard copy = new StarBattleBoard(size, puzzleNum);
+ for (int x = 0; x < this.dimension.width; x++) {
+ for (int y = 0; y < this.dimension.height; y++) {
+ copy.setCell(x, y, getCell(x, y).copy());
+ }
+ if (x < this.regions.size()) {
+ copy.regions.add(this.getRegion(x).copy());
+ }
+ }
+ for (PuzzleElement e : modifiedData) {
+ copy.getPuzzleElement(e).setModifiable(false);
+ }
+ return copy;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCell.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCell.java
new file mode 100644
index 000000000..ddae8f882
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCell.java
@@ -0,0 +1,87 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.elements.Element;
+import edu.rpi.legup.model.gameboard.GridCell;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+
+public class StarBattleCell extends GridCell {
+ private int groupIndex;
+ private int max;
+
+ /**
+ * StarBattleCell Constructor - creates a new StarBattle cell to hold the puzzleElement
+ *
+ * @param value value of the star battle cell denoting its state
+ * @param location location of the cell on the board
+ * @param groupIndex indicates what group # the cell is in.
+ * @param size size of the star battle cell
+ */
+ public StarBattleCell(int value, Point location, int groupIndex, int size) {
+ super(value, location);
+ this.groupIndex = groupIndex;
+ this.max = size;
+ }
+
+ public int getGroupIndex() {
+ return groupIndex;
+ }
+
+ @Override
+ public void setType(Element e, MouseEvent m) {
+ switch (e.getElementID()) {
+ case "STBL-PLAC-0001":
+ this.data = -3;
+ break;
+ case "STBL-PLAC-0002":
+ this.data = -2;
+ break;
+ case "STBL-PLAC-0003":
+ this.data = -1;
+ break;
+
+ case "STBL-UNPL-0001": // Not sure how button events work
+ switch (m.getButton()) {
+ case MouseEvent.BUTTON1:
+ if (this.data > 0 || this.data < -3) {
+ this.data = -3;
+ } else {
+ this.data = this.data + 1;
+ }
+ break;
+ case MouseEvent.BUTTON3:
+ if (this.data > -4) {
+ this.data = this.data - 1;
+ } else {
+ this.data = -1; // Unsure
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ public StarBattleCellType getType() {
+ switch (data) {
+ case -3:
+ return StarBattleCellType.UNKNOWN;
+ case -2:
+ return StarBattleCellType.STAR;
+ case -1:
+ return StarBattleCellType.BLACK;
+ default:
+ if (data >= 0) {
+ return StarBattleCellType.UNKNOWN;
+ }
+ }
+ return null;
+ }
+
+ public StarBattleCell copy() {
+ StarBattleCell copy = new StarBattleCell(data, (Point) location.clone(), groupIndex, max);
+ copy.setIndex(index);
+ copy.setModifiable(isModifiable);
+ copy.setGiven(isGiven);
+ return copy;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCellFactory.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCellFactory.java
new file mode 100644
index 000000000..eb2a830f6
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCellFactory.java
@@ -0,0 +1,66 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.ElementFactory;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.awt.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class StarBattleCellFactory extends ElementFactory {
+ @Override
+ public StarBattleCell importCell(Node node, Board board) throws InvalidFileFormatException {
+ try {
+ if (!node.getNodeName().equalsIgnoreCase("cell")) {
+ throw new InvalidFileFormatException(
+ "starbattle Factory: unknown puzzleElement puzzleElement");
+ }
+
+ StarBattleBoard starbattleBoard = (StarBattleBoard) board;
+ int size = starbattleBoard.getSize();
+
+ NamedNodeMap attributeList = node.getAttributes();
+ int value = Integer.valueOf(attributeList.getNamedItem("value").getNodeValue());
+ int x = Integer.valueOf(attributeList.getNamedItem("x").getNodeValue());
+ int y = Integer.valueOf(attributeList.getNamedItem("y").getNodeValue());
+ int groupIndex =
+ Integer.valueOf(attributeList.getNamedItem("groupIndex").getNodeValue());
+ if (x >= size || y >= size) {
+ throw new InvalidFileFormatException(
+ "starbattle Factory: cell location out of bounds");
+ }
+ if (groupIndex >= size || groupIndex < 0) {
+ throw new InvalidFileFormatException("starbattle Factory: not in a valid region");
+ }
+ if (value != 0) { // ALL INITIAL PUZZLES ARE BLANK, SUBJECT TO CHANGE
+ throw new InvalidFileFormatException("starbattle Factory: cell unknown value");
+ }
+
+ StarBattleCell cell = new StarBattleCell(value, new Point(x, y), groupIndex, size);
+ cell.setIndex(y * size + x);
+ return cell;
+ } catch (NumberFormatException e1) {
+ e1.printStackTrace();
+ throw new InvalidFileFormatException(
+ "starbattle Factory: unknown value where integer expected");
+ } catch (NullPointerException e2) {
+ e2.printStackTrace();
+ throw new InvalidFileFormatException("starbattle Factory: could not find attribute(s)");
+ }
+ }
+
+ public org.w3c.dom.Element exportCell(Document document, PuzzleElement puzzleElement) {
+ org.w3c.dom.Element cellElement = document.createElement("cell");
+
+ StarBattleCell cell = (StarBattleCell) puzzleElement;
+ Point loc = cell.getLocation();
+
+ cellElement.setAttribute("value", String.valueOf(cell.getData()));
+ cellElement.setAttribute("x", String.valueOf(loc.x));
+ cellElement.setAttribute("y", String.valueOf(loc.y));
+
+ return cellElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCellType.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCellType.java
new file mode 100644
index 000000000..e48e46fcb
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleCellType.java
@@ -0,0 +1,14 @@
+// StarBattleCellType.java
+package edu.rpi.legup.puzzle.starbattle;
+
+public enum StarBattleCellType {
+ STAR(-2),
+ BLACK(-1),
+ UNKNOWN(0);
+
+ public int value;
+
+ StarBattleCellType(int value) {
+ this.value = value;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleController.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleController.java
new file mode 100644
index 000000000..ba19361c9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleController.java
@@ -0,0 +1,36 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.controller.ElementController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import java.awt.event.MouseEvent;
+
+public class StarBattleController extends ElementController {
+ @Override
+ public void changeCell(MouseEvent e, PuzzleElement data) {
+ StarBattleCell cell = (StarBattleCell) data;
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ if (e.isControlDown()) {
+ this.boardView
+ .getSelectionPopupMenu()
+ .show(
+ boardView,
+ this.boardView.getCanvas().getX() + e.getX(),
+ this.boardView.getCanvas().getY() + e.getY());
+ } else {
+ if (cell.getData() >= 0) {
+ data.setData(-2);
+ } else {
+ data.setData(cell.getData() + 1);
+ }
+ }
+ } else {
+ if (e.getButton() == MouseEvent.BUTTON3) {
+ if (cell.getData() == -2) {
+ data.setData(0);
+ } else {
+ data.setData(cell.getData() - 1);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleElementView.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleElementView.java
new file mode 100644
index 000000000..66d59d364
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleElementView.java
@@ -0,0 +1,46 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.ui.boardview.GridElementView;
+import java.awt.*;
+
+public class StarBattleElementView extends GridElementView {
+
+ public StarBattleElementView(StarBattleCell cell) {
+ super(cell);
+ }
+
+ @Override
+ public StarBattleCell getPuzzleElement() {
+ return (StarBattleCell) super.getPuzzleElement();
+ }
+
+ @Override
+ public void drawElement(Graphics2D graphics2D) {
+ StarBattleCell cell = (StarBattleCell) puzzleElement;
+ StarBattleCellType type = cell.getType();
+ if (type == StarBattleCellType.STAR) {
+ graphics2D.setColor(Color.LIGHT_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.drawImage(
+ StarBattleView.STAR,
+ location.x,
+ location.y,
+ size.width,
+ size.height,
+ Color.WHITE,
+ null);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ } else if (type == StarBattleCellType.BLACK) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ } else if (type == StarBattleCellType.UNKNOWN) {
+ graphics2D.setStroke(new BasicStroke(1));
+ graphics2D.setColor(Color.LIGHT_GRAY);
+ graphics2D.fillRect(location.x, location.y, size.width, size.height);
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleExporter.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleExporter.java
new file mode 100644
index 000000000..f2d2c4d80
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleExporter.java
@@ -0,0 +1,33 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.PuzzleExporter;
+import org.w3c.dom.Document;
+
+public class StarBattleExporter extends PuzzleExporter {
+ public StarBattleExporter(StarBattle starbattle) {
+ super(starbattle);
+ }
+
+ @Override
+ protected org.w3c.dom.Element createBoardElement(Document newDocument) {
+ StarBattleBoard board = (StarBattleBoard) puzzle.getTree().getRootNode().getBoard();
+ org.w3c.dom.Element boardElement = newDocument.createElement("board");
+ boardElement.setAttribute("size", String.valueOf(board.getSize()));
+ boardElement.setAttribute("puzzle_num", String.valueOf(board.getPuzzleNumber()));
+ for (StarBattleRegion sb_region : board.regions) {
+ org.w3c.dom.Element regionsElement = newDocument.createElement("region");
+ org.w3c.dom.Element cellsElement = newDocument.createElement("cells");
+ for (StarBattleCell cell : sb_region.getCells()) {
+ if (cell.getData() == 0) {
+ org.w3c.dom.Element cellElement =
+ puzzle.getFactory().exportCell(newDocument, cell);
+ cellsElement.appendChild(cellElement);
+ }
+ regionsElement.appendChild(cellsElement);
+ }
+ boardElement.appendChild(regionsElement);
+ }
+
+ return boardElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleImporter.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleImporter.java
new file mode 100644
index 000000000..2a608c893
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleImporter.java
@@ -0,0 +1,98 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.PuzzleImporter;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.awt.Point;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class StarBattleImporter extends PuzzleImporter {
+
+ public StarBattleImporter(StarBattle starbattle) {
+ super(starbattle);
+ }
+
+ /** Puzzle setting to support row and column inputs */
+ @Override
+ public boolean acceptsRowsAndColumnsInput() {
+ return true;
+ }
+
+ /** Puzzle setting to disable support for text input */
+ @Override
+ public boolean acceptsTextInput() {
+ return false;
+ }
+
+ /**
+ * Constructs empty StarBattle gameboard as per the provided dimensions
+ *
+ * @param rows number of rows and columns for the gameboard
+ */
+ @Override
+ public void initializeBoard(int rows, int columns) {
+ int puzzle_num = 1;
+ StarBattleBoard StarBattleBoard = new StarBattleBoard(rows, puzzle_num);
+ puzzle.setCurrentBoard(StarBattleBoard);
+ }
+
+ /**
+ * Constructs StarBattle gameboard
+ *
+ * @param node xml document node
+ * @throws InvalidFileFormatException if file is invalid
+ */
+ @Override
+ public void initializeBoard(Node node) throws InvalidFileFormatException {
+ Element puzzleElement = (Element) node;
+ int puzzle_num = Integer.parseInt(puzzleElement.getAttribute("puzzle_num"));
+ NodeList regionNodes = puzzleElement.getElementsByTagName("region");
+ int size = Integer.parseInt(puzzleElement.getAttribute("size"));
+ if (regionNodes.getLength() != size) {
+ throw new InvalidFileFormatException(
+ "Not the current amount of regions in the puzzle.");
+ }
+
+ StarBattleBoard StarBattleBoard =
+ new StarBattleBoard(
+ size, puzzle_num); // Initialize the board with width and height from XML
+
+ for (int i = 0; i < regionNodes.getLength(); i++) {
+ Element regionElement = (Element) regionNodes.item(i);
+ NodeList cellNodes = regionElement.getElementsByTagName("cell");
+ StarBattleRegion region_i = new StarBattleRegion();
+
+ for (int j = 0; j < cellNodes.getLength(); j++) {
+ Element cellElement = (Element) cellNodes.item(j);
+ int x = Integer.parseInt(cellElement.getAttribute("x"));
+ int y = Integer.parseInt(cellElement.getAttribute("y"));
+ int value = Integer.parseInt(cellElement.getAttribute("value"));
+
+ Point cellPoint = new Point(x, y);
+
+ // Create the StarBattleCell with the cell type and value
+ StarBattleCell cell = new StarBattleCell(value, cellPoint, i, size);
+ cell.setIndex(y * size + x); // Calculate the index based on size
+ cell.setModifiable(true);
+
+ // Add the cell to the board
+ StarBattleBoard.setCell(x, y, cell);
+ region_i.addCell(cell);
+ }
+ StarBattleBoard.setRegion(i, region_i);
+ }
+
+ puzzle.setCurrentBoard(StarBattleBoard);
+ }
+
+ /**
+ * Initialize board via string of statements.
+ *
+ * @throws UnsupportedOperationException since StarBattle does not support text input
+ */
+ @Override
+ public void initializeBoard(String[] statements) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Star Battle does not accept text input");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleRegion.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleRegion.java
new file mode 100644
index 000000000..b35d80655
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleRegion.java
@@ -0,0 +1,27 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.gameboard.GridRegion;
+
+public class StarBattleRegion extends GridRegion {
+ public StarBattleRegion() {
+ super();
+ }
+
+ public StarBattleRegion copy() {
+ StarBattleRegion copy = new StarBattleRegion();
+ for (StarBattleCell c : regionCells) {
+ copy.addCell(c.copy());
+ }
+ return copy;
+ }
+
+ public int numStars() {
+ int stars = 0;
+ for (StarBattleCell c : regionCells) {
+ if (c.getType() == StarBattleCellType.STAR) {
+ ++stars;
+ }
+ }
+ return stars;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleView.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleView.java
new file mode 100644
index 000000000..550b5495d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/StarBattleView.java
@@ -0,0 +1,38 @@
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.controller.BoardController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.ui.boardview.GridBoardView;
+import java.awt.*;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+public class StarBattleView extends GridBoardView {
+ static Image STAR;
+
+ static {
+ try {
+ STAR =
+ ImageIO.read(
+ ClassLoader.getSystemClassLoader()
+ .getResource("edu/rpi/legup/images/starbattle/star.gif"));
+ } catch (IOException e) {
+ // pass
+ }
+ }
+
+ public StarBattleView(StarBattleBoard board) {
+ super(new BoardController(), new StarBattleController(), board.getDimension());
+
+ for (PuzzleElement puzzleElement : board.getPuzzleElements()) {
+ StarBattleCell cell = (StarBattleCell) puzzleElement;
+ Point loc = cell.getLocation();
+ StarBattleElementView elementView = new StarBattleElementView(cell);
+ elementView.setIndex(cell.getIndex());
+ elementView.setSize(elementSize);
+ elementView.setLocation(
+ new Point(loc.x * elementSize.width, loc.y * elementSize.height));
+ elementViews.add(elementView);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/allfiles.txt b/src/main/java/edu/rpi/legup/puzzle/starbattle/allfiles.txt
new file mode 100644
index 000000000..5a9ec0f0a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/allfiles.txt
@@ -0,0 +1,235 @@
+//StarBattle.java
+
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.Puzzle;
+import edu.rpi.legup.model.gameboard.Board;
+
+public class StarBattle extends Puzzle {
+ public StarBattle() {
+ super();
+ this.name = "StarBattle";
+
+ this.importer = new StarBattleImporter(this);
+ this.exporter = new StarBattleExporter(this);
+
+ this.factory = new StarBattleCellFactory();
+ }
+
+ @Override
+ public void initializeView() {
+ }
+
+ @Override
+ public Board generatePuzzle(int difficulty) {
+ return null;
+ }
+
+ @Override
+ public boolean isBoardComplete(Board board) {
+ return true;
+ }
+
+ @Override
+ public void onBoardChange(Board board) {
+ }
+}
+
+//StarBattleBoard.java
+
+package edu.rpi.legup.puzzle.lightup;
+
+import edu.rpi.legup.model.gameboard.GridBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+
+import java.awt.*;
+import java.util.HashSet;
+import java.util.Set;
+
+public class StarBattleBoard extends GridBoard {
+
+ private int size;
+ private vector group_sizes;
+
+ /**
+ * StarBattleBoard Constructor - create a new Star Battle board
+ *
+ * @param size size of one side of the star battle board
+ */
+
+ public StarBattleBoard(int size) {
+ super(size, size);
+ group_sizes = vector(size);
+ }
+
+ @Override
+ public StarBattleCell getCell(int x, int y) {
+ return (StarBattleCell) super.getCell(x, y);
+ }
+
+
+}
+
+//StarBattleCell.java
+
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.model.gameboard.GridCell;
+
+import java.awt.*;
+import java.util.HashSet;
+import java.util.Set;
+
+public class StarBattleCell extends GridCell {
+ private int groupIndex;
+ private int max;
+
+ /**
+ * StarBattleCell Constructor - creates a new StarBattle cell to hold the puzzleElement
+ *
+ * @param valueInt value of the star battle cell denoting its state
+ * @param location location of the cell on the board
+ * @param size size of the star battle cell
+ */
+ public StarBattleCell(int value, Point location, int groupIndex, int size) {
+ super(value, location);
+ this.groupIndex = groupIndex;
+ this.max = size;
+ }
+
+ @Override
+ public void setType(Element e, MouseEvent m) {
+ switch (e.getElementID()) {
+ case "SBUP-PLAC-0001":
+ this.data = -3;
+ break;
+ case "SBUP-PLAC-0002":
+ this.data = -2;
+ break;
+ case "SBUP-PLAC-0003":
+ this.data = -1;
+ break;
+ case "SBUP-UNPL-0001"://Not sure how button events work
+ switch (m.getButton()){
+ case MouseEvent.BUTTON1:
+ if (this.data < 0 || this.data > 3) {
+ this.data = 0;
+ }
+ else {
+ this.data = this.data + 1;
+ }
+ break;
+ case MouseEvent.BUTTON3:
+ if (this.data > 0) {
+ this.data = this.data - 1;
+ }
+ else {
+ this.data = 3;//Unsure
+ }
+ break;
+ }
+ break;
+ }
+ }
+
+ public LightUpCellType getType() {
+ switch (data) {
+ case -3:
+ return LightUpCellType.UNKNOWN;
+ case -2:
+ return LightUpCellType.STAR;
+ case -1:
+ return LightUpCellType.BLACK;
+ default:
+ if (data >= 0) {
+ return StarBattleCellType.WHITE;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the region index of the cell
+ *
+ * @return group index of the cell
+ */
+ public int getGroupIndex() {
+ return groupIndex;
+ }
+
+ /**
+ * Gets the size of the cell
+ *
+ * @return size of the cell
+ */
+
+ public int getMax() {
+ return max;
+ }
+
+}
+
+//StarBattleCellController.java
+
+package edu.rpi.legup.puzzle.starbattle;
+
+import edu.rpi.legup.controller.ElementController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+
+import java.awt.event.MouseEvent;
+
+public class StarBattleCellController extends ElementController {
+ @Override
+ public void changeCell(MouseEvent e, PuzzleElement data) {
+ StarBattleCell cell = (StarBattleCell) data;
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ if (e.isControlDown()) {
+ this.boardView.getSelectionPopupMenu().show(boardView, this.boardView.getCanvas().getX() + e.getX(), this.boardView.getCanvas().getY() + e.getY());
+ }
+ else {
+ if (cell.getData() == 0) {
+ data.setData(-3);
+ }
+ else {
+ data.setData(cell.getData() + 1);
+ }
+ }
+ }
+ else {
+ if (e.getButton() == MouseEvent.BUTTON3) {
+ if (cell.getData() == -3) {
+ data.setData(0);
+ }
+ else {
+ data.setData(cell.getData() - 1);
+ }
+ }
+ }
+ }
+}
+
+//StarBattleCellFactory.java
+
+
+
+//StarBattleCellType.java
+package edu.rpi.legup.puzzle.starbattle;
+
+public enum StarBattleType {
+ UNKNOWN(-3), STAR(-2), BLACK(-1), WHITE(0);
+
+ public int value;
+
+ StarBattleCell(int value) {
+ this.value = value;
+ }
+}
+
+//StarBattleExporter.java
+//StarBattleImporter.java
+//StarBattleView.java
+
+How to run Legup:
+
+./gradlew build
+Java -jar build/libs/Legup.jar
\ No newline at end of file
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java
new file mode 100644
index 000000000..99f42886e
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/BlackTile.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.starbattle.elements;
+
+import edu.rpi.legup.model.elements.NonPlaceableElement;
+
+public class BlackTile extends NonPlaceableElement {
+ public BlackTile() {
+ super(
+ "STBL-PLAC-0002",
+ "Black Tile",
+ "The black tile that shows where you cannot place a star",
+ "edu/rpi/legup/images/lightup/black.gif");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java
new file mode 100644
index 000000000..13ada3f4d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/StarTile.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.starbattle.elements;
+
+import edu.rpi.legup.model.elements.NonPlaceableElement;
+
+public class StarTile extends NonPlaceableElement {
+ public StarTile() {
+ super(
+ "STBL-PLAC-0001",
+ "Star Tile",
+ "The star tile, the token of the game.",
+ "edu/rpi/legup/images/starbattle/star.gif");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java
new file mode 100644
index 000000000..425fb5d5e
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/UnknownTile.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.starbattle.elements;
+
+import edu.rpi.legup.model.elements.NonPlaceableElement;
+
+public class UnknownTile extends NonPlaceableElement {
+ public UnknownTile() {
+ super(
+ "STBL-UNPL-0001",
+ "Unknown Tile",
+ "An empty tile",
+ "edu/rpi/legup/images/starbattle/star.gif");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/WhiteTile.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/WhiteTile.java
new file mode 100644
index 000000000..2227eb37a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/elements/WhiteTile.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.starbattle.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class WhiteTile extends PlaceableElement {
+ public WhiteTile() {
+ super(
+ "STBL-PLAC-0001",
+ "White Tile",
+ "The white tile",
+ "edu/rpi/legup/images/starbattle/white.gif");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/BlackoutDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/BlackoutDirectRule.java
new file mode 100644
index 000000000..2ab66cf93
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/BlackoutDirectRule.java
@@ -0,0 +1,64 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+
+public class BlackoutDirectRule extends DirectRule {
+
+ public BlackoutDirectRule() {
+ super(
+ "STBL-BASC-0001",
+ "Blackout",
+ "If a row, column, or region has enough stars, its unknown spaces are black.",
+ "edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+
+ StarBattleBoard board = (StarBattleBoard) transition.getBoard();
+ StarBattleBoard origBoard = (StarBattleBoard) transition.getParents().get(0).getBoard();
+ ContradictionRule contraRule = new TooManyStarsContradictionRule();
+
+ StarBattleCell cell = (StarBattleCell) board.getPuzzleElement(puzzleElement);
+
+ if (cell.getType() != StarBattleCellType.BLACK) {
+ return "Only black cells are allowed for this rule!";
+ }
+
+ StarBattleBoard modified = (StarBattleBoard) origBoard.copy();
+ modified.getPuzzleElement(puzzleElement).setData(StarBattleCellType.STAR.value);
+ if (contraRule.checkContradictionAt(modified, puzzleElement) != null) {
+ return "Black cells must be placed in a row, region, or column with enough stars!";
+ }
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ClashingOrbitContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ClashingOrbitContradictionRule.java
new file mode 100644
index 000000000..88f0072e5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ClashingOrbitContradictionRule.java
@@ -0,0 +1,59 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import java.awt.*;
+
+public class ClashingOrbitContradictionRule extends ContradictionRule {
+
+ public ClashingOrbitContradictionRule() {
+ super(
+ "STBL-CONT-0003",
+ "Clashing Orbit",
+ "No two stars can be adjacent to each other.",
+ "edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ StarBattleBoard starbattleBoard = (StarBattleBoard) board;
+ StarBattleCell cell = (StarBattleCell) starbattleBoard.getPuzzleElement(puzzleElement);
+
+ // Contradiction rule can only be applied to cells with a star in it
+ if (cell.getType() != StarBattleCellType.STAR) {
+ return super.getNoContradictionMessage();
+ }
+
+ // check neighboring cells for a star
+ Point location = cell.getLocation();
+
+ int rowStart = Math.max(location.x - 1, 0);
+ int rowEnd = Math.min(location.x + 1, starbattleBoard.getSize() - 1);
+ int colStart = Math.max(location.y - 1, 0);
+ int colEnd = Math.min(location.y + 1, starbattleBoard.getSize() - 1);
+
+ for (int row = rowStart; row <= rowEnd; row++) {
+ for (int col = colStart; col <= colEnd; col++) {
+ if (starbattleBoard.getCell(row, col).getType() == StarBattleCellType.STAR
+ && (row != location.x || col != location.y)) {
+ return null;
+ }
+ }
+ }
+
+ return super.getNoContradictionMessage();
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ColumnsWithinRegionsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ColumnsWithinRegionsDirectRule.java
new file mode 100644
index 000000000..433567460
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ColumnsWithinRegionsDirectRule.java
@@ -0,0 +1,95 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ColumnsWithinRegionsDirectRule extends DirectRule {
+ public ColumnsWithinRegionsDirectRule() {
+ super(
+ "STBL-BASC-0002",
+ "Columns Within Regions",
+ "If a number of columns is fully contained by a number of regions with an equal number of missing stars, spaces of other columns in those regions must be black.",
+ "edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ // assumption: the rule has been applied to its fullest extent and the rows and regions
+ // are now mutually encompassing
+ StarBattleBoard board = (StarBattleBoard) transition.getBoard();
+ StarBattleCell cell = (StarBattleCell) board.getPuzzleElement(puzzleElement);
+ if (cell.getType() != StarBattleCellType.BLACK) {
+ return "Only black cells are allowed for this rule!";
+ }
+ // the columns that are contained
+ Set columns = new HashSet();
+ // the regions that contain them
+ Set regions = new HashSet();
+ // columns and regions to process
+ Set columnsToCheck = new HashSet();
+ Set regionsToCheck = new HashSet();
+ int columnStars = 0;
+ int regionStars = 0;
+ regions.add(cell.getGroupIndex());
+ regionsToCheck.add(cell.getGroupIndex());
+
+ while (!columnsToCheck.isEmpty() || !regionsToCheck.isEmpty()) {
+ for (int r : regionsToCheck) {
+ regionStars += board.getRegion(r).numStars();
+ for (PuzzleElement c : board.getRegion(r).getCells()) {
+ int column = ((StarBattleCell) c).getLocation().x;
+ if (columns.add(column)) {
+ columnsToCheck.add(column);
+ }
+ }
+ regionsToCheck.remove(r);
+ }
+ for (int c : columnsToCheck) {
+ columnStars += board.columnStars(c);
+ for (int i = 0; i < board.getSize(); ++i) {
+ int region = board.getCell(c, i).getGroupIndex();
+ if (regions.add(region)) {
+ regionsToCheck.add(region);
+ }
+ }
+ columnsToCheck.remove(c);
+ }
+ }
+ // are the columns and regions missing an equal amount of stars
+ if (board.getPuzzleNumber() * columns.size() - columnStars
+ != board.getPuzzleNumber() * regions.size() - regionStars) {
+ return "The number of missing stars in the columns and regions must be equal and every extraneous cell must be black!";
+ }
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ColumnsWithinRowsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ColumnsWithinRowsDirectRule.java
new file mode 100644
index 000000000..5d108a0cd
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/ColumnsWithinRowsDirectRule.java
@@ -0,0 +1,99 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ColumnsWithinRowsDirectRule extends DirectRule {
+
+ public ColumnsWithinRowsDirectRule() {
+ super(
+ "STBL-BASC-0003",
+ "Columns Within Rows",
+ "If a number of columns is fully contained by a number of rows with an equal number of missing stars, spaces of other columns in those rows must be black.",
+ "edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+
+ // assumption: the rule has been applied to its fullest extent and the rows and columns
+ // are now mutually encompassing
+ StarBattleBoard board = (StarBattleBoard) transition.getBoard();
+ StarBattleCell cell = (StarBattleCell) board.getPuzzleElement(puzzleElement);
+ if (cell.getType() != StarBattleCellType.BLACK) {
+ return "Only black cells are allowed for this rule!";
+ }
+
+ // the columns that are contained
+ Set columns = new HashSet();
+ // the rows that contain them
+ Set rows = new HashSet();
+ // columns and rows to process
+ Set columnsToCheck = new HashSet();
+ Set rowsToCheck = new HashSet();
+ int columnStars = 0;
+ int rowStars = 0;
+ int firstRow = cell.getLocation().y;
+ rows.add(firstRow);
+ rowsToCheck.add(firstRow);
+
+ while (!columnsToCheck.isEmpty() || !rowsToCheck.isEmpty()) {
+ for (int r : rowsToCheck) {
+ rowStars += board.rowStars(r);
+ for (PuzzleElement c : board.getRow(r)) {
+ int column = ((StarBattleCell) c).getLocation().x;
+ if (columns.add(column)) {
+ columnsToCheck.add(column);
+ }
+ }
+ rowsToCheck.remove(r);
+ }
+ for (int c : columnsToCheck) {
+ columnStars += board.columnStars(c);
+ for (PuzzleElement r : board.getCol(c)) {
+ int row = ((StarBattleCell) r).getLocation().y;
+ if (rows.add(row)) {
+ rowsToCheck.add(row);
+ }
+ }
+ columnsToCheck.remove(c);
+ }
+ }
+ // are the columns and regions missing an equal amount of stars
+ if (board.getPuzzleNumber() * columns.size() - columnStars
+ != board.getPuzzleNumber() * rows.size() - rowStars) {
+ return "The number of missing stars in the columns and rows must be equal and every extraneous cell must be black!";
+ }
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/FinishWithStarsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/FinishWithStarsDirectRule.java
new file mode 100644
index 000000000..80ae9a4c8
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/FinishWithStarsDirectRule.java
@@ -0,0 +1,65 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+
+public class FinishWithStarsDirectRule extends DirectRule {
+
+ public FinishWithStarsDirectRule() {
+ super(
+ "STBL-BASC-0004",
+ "Finish With Stars",
+ "Unknown spaces must be stars if there are just enough in a row, column, or region to satisfy the puzzle number.",
+ "edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+
+ StarBattleBoard board = (StarBattleBoard) transition.getBoard();
+ StarBattleBoard origBoard = (StarBattleBoard) transition.getParents().get(0).getBoard();
+ ContradictionRule contraRule = new TooFewStarsContradictionRule();
+
+ StarBattleCell cell = (StarBattleCell) board.getPuzzleElement(puzzleElement);
+
+ if (cell.getType() != StarBattleCellType.STAR) {
+ return "Only star cells are allowed for this rule!";
+ }
+
+ StarBattleBoard modified = (StarBattleBoard) origBoard.copy();
+ modified.getPuzzleElement(puzzleElement).setData(StarBattleCellType.BLACK.value);
+ if (contraRule.checkContradictionAt(modified, puzzleElement) != null) {
+ return "Star cells must be placed in a row, region, or column without extra spaces!";
+ }
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RegionsWithinColumnsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RegionsWithinColumnsDirectRule.java
new file mode 100644
index 000000000..7022a06ac
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RegionsWithinColumnsDirectRule.java
@@ -0,0 +1,45 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+public class RegionsWithinColumnsDirectRule extends DirectRule {
+ public RegionsWithinColumnsDirectRule() {
+ super(
+ "STBL-BASC-0005",
+ "Regions Within Columns",
+ "If a number of regions is fully contained by a number of columns with an equal number of missing stars, spaces of other regions in those columns must be black.",
+ "edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ ColumnsWithinRegionsDirectRule correspondingRule = new ColumnsWithinRegionsDirectRule();
+ return correspondingRule.checkRuleRawAt(transition, puzzleElement);
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RegionsWithinRowsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RegionsWithinRowsDirectRule.java
new file mode 100644
index 000000000..7ab50d42b
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RegionsWithinRowsDirectRule.java
@@ -0,0 +1,46 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+public class RegionsWithinRowsDirectRule extends DirectRule {
+ public RegionsWithinRowsDirectRule() {
+ super(
+ "STBL-BASC-0006",
+ "Regions Within Rows",
+ "If a number of regions is fully contained by a number of rows with an equal number of missing stars, spaces of other regions in those rows must be black.",
+ "edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+
+ RowsWithinRegionsDirectRule correspondingRule = new RowsWithinRegionsDirectRule();
+ return correspondingRule.checkRuleRawAt(transition, puzzleElement);
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RowsWithinColumnsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RowsWithinColumnsDirectRule.java
new file mode 100644
index 000000000..2df20e464
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RowsWithinColumnsDirectRule.java
@@ -0,0 +1,47 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+public class RowsWithinColumnsDirectRule extends DirectRule {
+
+ public RowsWithinColumnsDirectRule() {
+ super(
+ "STBL-BASC-0007",
+ "Rows Withing Columns",
+ "If a number of rows is fully contained by a number of columns with an equal number of missing stars, spaces of other rows in those columns must be black.",
+ "edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+
+ ColumnsWithinRowsDirectRule correspondingRule = new ColumnsWithinRowsDirectRule();
+ return correspondingRule.checkRuleRawAt(transition, puzzleElement);
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RowsWithinRegionsDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RowsWithinRegionsDirectRule.java
new file mode 100644
index 000000000..78f8f00e7
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/RowsWithinRegionsDirectRule.java
@@ -0,0 +1,96 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import java.util.HashSet;
+import java.util.Set;
+
+public class RowsWithinRegionsDirectRule extends DirectRule {
+ public RowsWithinRegionsDirectRule() {
+ super(
+ "STBL-BASC-0008",
+ "Rows Within Regions",
+ "If a number of rows is fully contained by a number of regions with an equal number of missing stars, spaces of other rows in those regions must be black.",
+ "edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+
+ // assumption: the rule has been applied to its fullest extent and the rows and regions
+ // are now mutually encompassing
+ StarBattleBoard board = (StarBattleBoard) transition.getBoard();
+ StarBattleCell cell = (StarBattleCell) board.getPuzzleElement(puzzleElement);
+ if (cell.getType() != StarBattleCellType.BLACK) {
+ return "Only black cells are allowed for this rule!";
+ }
+ // the rows that are contained
+ Set rows = new HashSet();
+ // the regions that contain them
+ Set regions = new HashSet();
+ // rows and regions to process
+ Set rowsToCheck = new HashSet();
+ Set regionsToCheck = new HashSet();
+ int rowStars = 0;
+ int regionStars = 0;
+ regions.add(cell.getGroupIndex());
+ regionsToCheck.add(cell.getGroupIndex());
+
+ while (!rowsToCheck.isEmpty() || !regionsToCheck.isEmpty()) {
+ for (int r : regionsToCheck) {
+ regionStars += board.getRegion(r).numStars();
+ for (PuzzleElement ro : board.getRegion(r).getCells()) {
+ int row = ((StarBattleCell) ro).getLocation().y;
+ if (rows.add(row)) {
+ rowsToCheck.add(row);
+ }
+ }
+ regionsToCheck.remove(r);
+ }
+ for (int r : rowsToCheck) {
+ rowStars += board.rowStars(r);
+ for (int i = 0; i < board.getSize(); ++i) {
+ int region = board.getCell(i, r).getGroupIndex();
+ if (regions.add(region)) {
+ regionsToCheck.add(region);
+ }
+ }
+ rowsToCheck.remove(r);
+ }
+ }
+ // are the columns and regions missing an equal amount of stars
+ if (board.getPuzzleNumber() * rows.size() - rowStars
+ != board.getPuzzleNumber() * regions.size() - regionStars) {
+ return "The number of missing stars in the rows and regions must be equal and every extraneous cell must be black!";
+ }
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java
new file mode 100644
index 000000000..df900dcd5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/StarOrEmptyCaseRule.java
@@ -0,0 +1,106 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import java.util.ArrayList;
+import java.util.List;
+
+public class StarOrEmptyCaseRule extends CaseRule {
+
+ public StarOrEmptyCaseRule() {
+ super(
+ "STBL-CASE-0002",
+ "Star or Empty",
+ "Each unknown space is either a star or empty.",
+ "edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png");
+ }
+
+ /**
+ * Checks whether the {@link TreeTransition} logically follows from the parent node using this
+ * rule. This method is the one that should overridden in child classes.
+ *
+ * @param transition transition to check
+ * @return null if the child node logically follow from the parent node, otherwise error message
+ */
+ @Override
+ public String checkRuleRaw(TreeTransition transition) {
+ List childTransitions = transition.getParents().get(0).getChildren();
+ if (childTransitions.size() != 2) {
+ return super.getInvalidUseOfRuleMessage() + ": This case rule must have 2 children.";
+ }
+
+ TreeTransition case1 = childTransitions.get(0);
+ TreeTransition case2 = childTransitions.get(1);
+ if (case1.getBoard().getModifiedData().size() != 1
+ || case2.getBoard().getModifiedData().size() != 1) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must have 1 modified cell for each case.";
+ }
+
+ StarBattleCell mod1 = (StarBattleCell) case1.getBoard().getModifiedData().iterator().next();
+ StarBattleCell mod2 = (StarBattleCell) case2.getBoard().getModifiedData().iterator().next();
+ if (!mod1.getLocation().equals(mod2.getLocation())) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must modify the same cell for each case.";
+ }
+
+ if (!((mod1.getType() == StarBattleCellType.STAR
+ && mod2.getType() == StarBattleCellType.BLACK)
+ || (mod2.getType() == StarBattleCellType.STAR
+ && mod1.getType() == StarBattleCellType.BLACK))) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must create a star cell and a black cell.";
+ }
+
+ return null;
+ }
+
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ StarBattleBoard starBattleBoard = (StarBattleBoard) board.copy();
+ CaseBoard caseBoard = new CaseBoard(starBattleBoard, this);
+ starBattleBoard.setModifiable(false);
+ for (PuzzleElement element : starBattleBoard.getPuzzleElements()) {
+ if (((StarBattleCell) element).getType() == StarBattleCellType.UNKNOWN) {
+ caseBoard.addPickableElement(element);
+ }
+ }
+ return caseBoard;
+ }
+
+ /**
+ * Gets the possible cases at a specific location based on this case rule
+ *
+ * @param board the current board state
+ * @param puzzleElement equivalent puzzleElement
+ * @return a list of elements the specified could be
+ */
+ @Override
+ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
+ ArrayList cases = new ArrayList<>();
+ Board case1 = board.copy();
+ PuzzleElement data1 = case1.getPuzzleElement(puzzleElement);
+ data1.setData(StarBattleCellType.STAR.value);
+ case1.addModifiedData(data1);
+ cases.add(case1);
+
+ Board case2 = board.copy();
+ PuzzleElement data2 = case2.getPuzzleElement(puzzleElement);
+ data2.setData(StarBattleCellType.BLACK.value);
+ case2.addModifiedData(data2);
+ cases.add(case2);
+
+ return cases;
+ }
+
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return checkRuleRaw(transition);
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/SurroundStarDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/SurroundStarDirectRule.java
new file mode 100644
index 000000000..89857875d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/SurroundStarDirectRule.java
@@ -0,0 +1,64 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+
+public class SurroundStarDirectRule extends DirectRule {
+
+ public SurroundStarDirectRule() {
+ super(
+ "STBL-BASC-0009",
+ "Surround Star",
+ "Any space adjacent to a star must be black.",
+ "edu/rpi/legup/images/starbattle/rules/SurroundStar.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ StarBattleBoard board = (StarBattleBoard) transition.getBoard();
+ StarBattleBoard origBoard = (StarBattleBoard) transition.getParents().get(0).getBoard();
+ ContradictionRule contraRule = new ClashingOrbitContradictionRule();
+
+ StarBattleCell cell = (StarBattleCell) board.getPuzzleElement(puzzleElement);
+
+ if (cell.getType() != StarBattleCellType.BLACK) {
+ return "Only black cells are allowed for this rule!";
+ }
+
+ StarBattleBoard modified = (StarBattleBoard) origBoard.copy();
+ modified.getPuzzleElement(puzzleElement).setData(StarBattleCellType.STAR.value);
+ if (contraRule.checkContradictionAt(modified, puzzleElement) != null) {
+ return "Black cells must be placed adjacent to a star!";
+ }
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/TooFewStarsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/TooFewStarsContradictionRule.java
new file mode 100644
index 000000000..e88b7c6b9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/TooFewStarsContradictionRule.java
@@ -0,0 +1,63 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import edu.rpi.legup.puzzle.starbattle.StarBattleRegion;
+import java.awt.*;
+
+public class TooFewStarsContradictionRule extends ContradictionRule {
+
+ public TooFewStarsContradictionRule() {
+ super(
+ "STBL-CONT-0002",
+ "Too Few Stars",
+ "There are too few stars in this region/row/column and there are not enough places to put the remaining stars.",
+ "edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ StarBattleBoard sbBoard = (StarBattleBoard) board;
+ StarBattleCell cell = (StarBattleCell) puzzleElement;
+ Point location = cell.getLocation();
+ int column = location.x;
+ int row = location.y;
+ int rowCount = 0;
+ int columnCount = 0;
+ for (int i = 0; i < sbBoard.getSize(); ++i) {
+ if (sbBoard.getCell(row, i).getType() != StarBattleCellType.BLACK) {
+ ++rowCount;
+ }
+ if (sbBoard.getCell(i, column).getType() != StarBattleCellType.BLACK) {
+ ++columnCount;
+ }
+ }
+ if (rowCount < sbBoard.getPuzzleNumber() || columnCount < sbBoard.getPuzzleNumber()) {
+ return null;
+ }
+ StarBattleRegion region = sbBoard.getRegion(cell);
+ int regionCount = 0;
+ for (StarBattleCell c : region.getCells()) {
+ if (c.getType() != StarBattleCellType.BLACK) {
+ ++regionCount;
+ }
+ }
+ if (regionCount < sbBoard.getPuzzleNumber()) {
+ return null;
+ }
+ return super.getNoContradictionMessage();
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/TooManyStarsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/TooManyStarsContradictionRule.java
new file mode 100644
index 000000000..12603a6ba
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/TooManyStarsContradictionRule.java
@@ -0,0 +1,113 @@
+package edu.rpi.legup.puzzle.starbattle.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.starbattle.StarBattleBoard;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCell;
+import edu.rpi.legup.puzzle.starbattle.StarBattleCellType;
+import edu.rpi.legup.puzzle.starbattle.StarBattleRegion;
+import java.awt.*;
+import java.util.List;
+
+public class TooManyStarsContradictionRule extends ContradictionRule {
+ private final String INVALID_USE_MESSAGE =
+ "Contradiction must be applied to a cell containing a star.";
+
+ public TooManyStarsContradictionRule() {
+ super(
+ "STBL-CONT-0001",
+ "Too Many Stars",
+ "There are too many stars in this region/row/column.",
+ "edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ StarBattleBoard starbattleBoard = (StarBattleBoard) board;
+ StarBattleCell cell = (StarBattleCell) starbattleBoard.getPuzzleElement(puzzleElement);
+ Point location = cell.getLocation();
+ int starCount = 0;
+ int puzzleNum = starbattleBoard.getPuzzleNumber();
+ boolean valid = true;
+
+ if (cell.getType() != StarBattleCellType.STAR) {
+ return this.INVALID_USE_MESSAGE;
+ }
+
+ // check row
+ for (int i = location.x - 1; i >= 0; i--) {
+ StarBattleCell check = starbattleBoard.getCell(i, location.y);
+ if (check.getType() == StarBattleCellType.STAR) {
+ starCount++;
+ if (starCount >= puzzleNum) {
+ valid = false;
+ break;
+ }
+ }
+ }
+
+ for (int i = location.x + 1; i < starbattleBoard.getWidth(); i++) {
+ StarBattleCell check = starbattleBoard.getCell(i, location.y);
+ if (check.getType() == StarBattleCellType.STAR) {
+ starCount++;
+ if (starCount >= puzzleNum) {
+ valid = false;
+ break;
+ }
+ }
+ }
+
+ // check column
+ starCount = 0;
+ for (int j = location.y - 1; j >= 0; j--) {
+ StarBattleCell check = starbattleBoard.getCell(location.x, j);
+ if (check.getType() == StarBattleCellType.STAR) {
+ starCount++;
+ if (starCount >= puzzleNum) {
+ valid = false;
+ break;
+ }
+ }
+ }
+
+ for (int j = location.y + 1; j < starbattleBoard.getWidth(); j++) {
+ StarBattleCell check = starbattleBoard.getCell(location.x, j);
+ if (check.getType() == StarBattleCellType.STAR) {
+ starCount++;
+ if (starCount >= puzzleNum) {
+ valid = false;
+ break;
+ }
+ }
+ }
+
+ // check region
+ starCount = 0;
+ StarBattleRegion reg = starbattleBoard.getRegion(cell);
+ List cellList = reg.getCells(); // list of cells
+ for (int k = 0; k < cellList.size(); k++) {
+ if (cellList.get(k).getType() == StarBattleCellType.STAR) {
+ starCount++;
+ if (starCount > puzzleNum) {
+ valid = false;
+ break;
+ }
+ }
+ }
+
+ if (valid) {
+ return super.getNoContradictionMessage();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/starbattle_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/starbattle_reference_sheet.txt
new file mode 100644
index 000000000..f18965fd6
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/starbattle/rules/starbattle_reference_sheet.txt
@@ -0,0 +1,19 @@
+Case Rules:
+Add Star: STBL-CASE-0001
+Star or Empty: STBL-CASE-0002
+
+Basic Rules:
+Blackout: STBL-BASC-0001
+Columns Within Regions: STBL-BASC-0002
+Columns Within Rows: STBL-BASC-0003
+Finish With Stars: STBL-BASC-0004
+Regions Within Columns: STBL-BASC-0005
+Regions Within Rows: STBL-BASC-0006
+Rows Within Columns: STBL-BASC-0007
+Rows Within Regions: STBL-BASC-0008
+Surround Star: STBL-BASC-0009
+
+Contradiction Rules:
+Too Many Stars: STBL-CONT-0001
+Too Few Stars: STBL-CONT-0002
+Clashing Orbit: STBL-CONT-0003
\ No newline at end of file
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/Thermometer.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/Thermometer.java
new file mode 100644
index 000000000..8138104f5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/Thermometer.java
@@ -0,0 +1,56 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.model.Puzzle;
+import edu.rpi.legup.model.gameboard.Board;
+
+// basically just copy-pasted from dev guide on wiki
+public class Thermometer extends Puzzle {
+ public Thermometer() {
+ super();
+
+ this.name = "Thermometer";
+
+ this.importer = new ThermometerImporter(this);
+ this.exporter = new ThermometerExporter(this);
+ // we do not have a thermometerCellFactory class as
+ // thermometerVial has its own thermometerCell factory method
+ }
+
+ /** Initializes the game board. Called by the invoker of the class */
+ @Override
+ public void initializeView() {
+ boardView = new ThermometerView((ThermometerBoard) currentBoard);
+ boardView.setBoard(currentBoard);
+ addBoardListener(boardView);
+ }
+
+ /**
+ * Generates a random edu.rpi.legup.puzzle based on the difficulty
+ *
+ * @param difficulty level of difficulty (1-10)
+ * @return board of the random edu.rpi.legup.puzzle
+ */
+ @Override
+ public Board generatePuzzle(int difficulty) {
+ return null;
+ }
+
+ /**
+ * Determines if the current board is a valid state
+ *
+ * @param board board to check for validity
+ * @return true if board is valid, false otherwise
+ */
+ @Override
+ public boolean isBoardComplete(Board board) {
+ return true;
+ }
+
+ /**
+ * Callback for when the board puzzleElement changes
+ *
+ * @param board the board that has changed
+ */
+ @Override
+ public void onBoardChange(Board board) {}
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerBoard.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerBoard.java
new file mode 100644
index 000000000..95ff7ff83
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerBoard.java
@@ -0,0 +1,139 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.model.gameboard.GridBoard;
+import java.awt.*;
+import java.util.ArrayList;
+
+public class ThermometerBoard extends GridBoard {
+
+ // an array containing all of our vials on the board
+ private ArrayList thermometerVials;
+
+ // representations of the number requirements along rows and columns of the board
+ // we use rotation to store the number
+ private ArrayList colNumbers;
+ private ArrayList rowNumbers;
+
+ private ThermometerCell dummyCell;
+
+ // constructors for the boards and variables
+ public ThermometerBoard(int width, int height) {
+ super(width, height);
+
+ // initializing the row/col number arrays with zeros, so they can be
+ // easily updated using the setRow/ColNumber functions
+ colNumbers = new ArrayList<>();
+ for (int i = 0; i < width - 1; i++) {
+ ThermometerCell cell =
+ new ThermometerCell(
+ new Point(i, height - 1),
+ ThermometerType.UNKNOWN,
+ ThermometerFill.UNKNOWN,
+ 0);
+ cell.setIndex((height - 1) * height + i);
+ colNumbers.add(cell);
+ this.setCell(i, height - 1, cell);
+ }
+ rowNumbers = new ArrayList<>();
+ for (int i = 0; i < height - 1; i++) {
+ ThermometerCell cell =
+ new ThermometerCell(
+ new Point(width - 1, i),
+ ThermometerType.UNKNOWN,
+ ThermometerFill.UNKNOWN,
+ 0);
+ cell.setIndex(i * height + (width - 1));
+ rowNumbers.add(cell);
+ this.setCell(width - 1, i, cell);
+ }
+
+ // setting a dummy cell so board doesn't have null cells
+ dummyCell =
+ new ThermometerCell(
+ new Point(width - 1, height - 1),
+ ThermometerType.UNKNOWN,
+ ThermometerFill.UNKNOWN,
+ -1);
+ dummyCell.setIndex((height - 1) * height + width);
+ this.setCell(width - 1, height - 1, dummyCell);
+
+ // creating our empty vial of thermometers to add to
+ thermometerVials = new ArrayList<>();
+ }
+
+ // setters and accessors for our array of vials
+ public void addVial(ThermometerVial v) {
+ thermometerVials.add(v);
+ }
+
+ public ArrayList getVials() {
+ return thermometerVials;
+ }
+
+ // our setters for row/col numbers with simple input verification
+ public boolean setRowNumber(int row, int num) {
+ // first check is to verify we are updating an element in range
+ // second check is to verify the new number can be achieved by the puzzle
+ if (row < rowNumbers.size() && num <= colNumbers.size()) {
+ rowNumbers.get(row).setRotation(num);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean setColNumber(int col, int num) {
+ // first check is to verify we are updating an element in range
+ // second check is to verify the new number can be achieved by the puzzle
+ if (col < colNumbers.size() && num <= rowNumbers.size()) {
+ colNumbers.get(col).setRotation(num);
+ return true;
+ }
+ return false;
+ }
+
+ // basic accessors for row/col numbers
+ public int getRowNumber(int row) {
+ if (row < 0 || row >= rowNumbers.size()) return -1;
+ return rowNumbers.get(row).getRotation();
+ }
+
+ public int getColNumber(int col) {
+ if (col < 0 || col >= rowNumbers.size()) return -1;
+ return colNumbers.get(col).getRotation();
+ }
+
+ // Accessors for saving row/column
+ public ArrayList getRowNumbers() {
+ return rowNumbers;
+ }
+
+ public ArrayList getColNumbers() {
+ return colNumbers;
+ }
+
+ // we all suck at programming so instead of using provided array list
+ // we use our own array lists to keep track of the vials
+ // marginally useful because it means we are guaranteed to get a
+ // thermometer cell when calling get cell, but using some type casting
+ // this override function could very likely be refactored out
+ @Override
+ public ThermometerCell getCell(int x, int y) {
+ for (ThermometerVial vial : this.thermometerVials) {
+ for (ThermometerCell cell : vial.getCells()) {
+ if (cell.getLocation().x == x && cell.getLocation().y == y) return cell;
+ }
+ }
+
+ for (ThermometerCell cell : rowNumbers) {
+ if (cell.getLocation().x == x && cell.getLocation().y == y) return cell;
+ }
+
+ for (ThermometerCell cell : colNumbers) {
+ if (cell.getLocation().x == x && cell.getLocation().y == y) return cell;
+ }
+
+ if (x == this.getWidth() - 1 && y == this.getHeight() - 1) return dummyCell;
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerCell.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerCell.java
new file mode 100644
index 000000000..175a455b4
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerCell.java
@@ -0,0 +1,67 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.model.gameboard.GridCell;
+import java.awt.Point;
+
+public class ThermometerCell extends GridCell {
+
+ // information about the cell needed to display it
+ private ThermometerType type;
+ private ThermometerFill fill;
+ private int rotation;
+
+ public ThermometerCell(Point location, ThermometerType t, ThermometerFill f, int r) {
+ // since we do not use get/set data value int can be any value
+ super(1, location);
+ type = t;
+ fill = f;
+ rotation = r;
+ }
+
+ // Note: setdata does not work for our purposes
+ public void setType(ThermometerType t) {
+ type = t;
+ }
+
+ public ThermometerType getType() {
+ return type;
+ }
+
+ public void setFill(ThermometerFill f) {
+ fill = f;
+ }
+
+ public ThermometerFill getFill() {
+ return fill;
+ }
+
+ public void setRotation(int r) {
+ rotation = r;
+ }
+
+ public int getRotation() {
+ return rotation;
+ }
+
+ @Override
+ public ThermometerCell copy() {
+ ThermometerCell copy =
+ new ThermometerCell((Point) location.clone(), this.type, this.fill, this.rotation);
+ copy.setIndex(index);
+ copy.setModifiable(isModifiable);
+ copy.setGiven(isGiven);
+ return copy;
+ }
+
+ @Override
+ public String toString() {
+ return "("
+ + location.getX()
+ + ", "
+ + location.getY()
+ + ") TYPE = "
+ + getType()
+ + " FILL = "
+ + getFill();
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerController.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerController.java
new file mode 100644
index 000000000..cd2135bd7
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerController.java
@@ -0,0 +1,44 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.controller.ElementController;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import java.awt.event.MouseEvent;
+
+public class ThermometerController extends ElementController {
+
+ // method for updating thermometer cells since number cells have unknown for
+ // their fill type we don't need to worry about end user modifying them with this
+ @Override
+ public void changeCell(MouseEvent e, PuzzleElement data) {
+ ThermometerCell cell = (ThermometerCell) data;
+
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ if (e.isControlDown()) {
+ this.boardView
+ .getSelectionPopupMenu()
+ .show(
+ boardView,
+ this.boardView.getCanvas().getX() + e.getX(),
+ this.boardView.getCanvas().getY() + e.getY());
+ } else {
+ if (cell.getFill() == ThermometerFill.EMPTY) {
+ cell.setFill(ThermometerFill.FILLED);
+ } else if (cell.getFill() == ThermometerFill.FILLED) {
+ cell.setFill(ThermometerFill.BLOCKED);
+ } else {
+ cell.setFill(ThermometerFill.EMPTY);
+ }
+ }
+ } else if (e.getButton() == MouseEvent.BUTTON3) {
+ if (cell.getFill() == ThermometerFill.EMPTY) {
+ cell.setFill(ThermometerFill.BLOCKED);
+ } else if (cell.getFill() == ThermometerFill.BLOCKED) {
+ cell.setFill(ThermometerFill.FILLED);
+ } else {
+ cell.setFill(ThermometerFill.EMPTY);
+ }
+ } else if (e.getButton() == MouseEvent.BUTTON2) {
+ System.out.println("[DEBUG] " + cell);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerElementView.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerElementView.java
new file mode 100644
index 000000000..0657e95b0
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerElementView.java
@@ -0,0 +1,311 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.ui.boardview.GridElementView;
+import java.awt.*;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+
+public class ThermometerElementView extends GridElementView {
+
+ // mixture of stuff stolen from tree tent and dev guide
+ private static final Font FONT = new Font("TimesRoman", Font.BOLD, 16);
+ private static final Color FONT_COLOR = Color.BLACK;
+
+ public ThermometerElementView(ThermometerCell cell) {
+ super(cell);
+ }
+
+ @Override
+ public ThermometerCell getPuzzleElement() {
+ return (ThermometerCell) super.getPuzzleElement();
+ }
+
+ // method for drawing a thermometer cell
+ // basically copy/pasted from tree tent drawing tent images
+ @Override
+ public void drawElement(Graphics2D graphics2D) {
+
+ ThermometerCell cell = (ThermometerCell) puzzleElement;
+ ThermometerType type = cell.getType();
+ ThermometerFill fill = cell.getFill();
+ int rotation = cell.getRotation();
+
+ graphics2D.drawImage(
+ imageSrc(type, fill, rotation),
+ location.x,
+ location.y,
+ size.width,
+ size.height,
+ null,
+ null);
+
+ graphics2D.setColor(Color.BLACK);
+ graphics2D.drawRect(location.x, location.y, size.width, size.height);
+ }
+
+ // modified code from tree trent to display images
+ private Image imageSrc(ThermometerType t, ThermometerFill f, int r) {
+
+ // will have a 36 switch case at end to determine which image gets opened
+ int result = 0;
+
+ // 100 = NORTH, 200 = WEST, 300 = SOUTH, 400 = EAST
+ switch (r) {
+ case 0 -> result += 100;
+ case 90 -> result += 400;
+ case 180 -> result += 300;
+ case 270 -> result += 200;
+ default -> {
+ System.out.println("ThermometerElementView: Invalid Rotation");
+ return null;
+ }
+ }
+
+ // 10 = EMPTY, 20 = FILLED, 30 = BLOCKED
+ switch (f) {
+ case ThermometerFill.EMPTY -> result += 10;
+ case ThermometerFill.FILLED -> result += 20;
+ case ThermometerFill.BLOCKED -> result += 30;
+ default -> {
+ System.out.println("ThermometerElementView: Invalid Fill");
+ return null;
+ }
+ }
+
+ // 1 = HEAD, 2 = SHAFT, 3 = TIP
+ switch (t) {
+ case ThermometerType.HEAD -> result += 1;
+ case ThermometerType.SHAFT -> result += 2;
+ case ThermometerType.TIP -> result += 3;
+ default -> {
+ System.out.println("ThermometerElementView: Invalid Type");
+ return null;
+ }
+ }
+
+ try {
+ switch (result) {
+ case 111 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpN.png"));
+ }
+
+ case 112 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpN.png"));
+ }
+
+ case 113 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpN.png"));
+ }
+
+ case 121 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillN.png"));
+ }
+
+ case 122 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillN.png"));
+ }
+
+ case 123 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipFillN.png"));
+ }
+
+ case 131 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockN.png"));
+ }
+
+ case 132 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockN.png"));
+ }
+
+ case 133 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockN.png"));
+ }
+
+ case 211 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpE.png"));
+ }
+
+ case 212 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpE.png"));
+ }
+
+ case 213 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpE.png"));
+ }
+
+ case 221 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillE.png"));
+ }
+
+ case 222 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillE.png"));
+ }
+
+ case 223 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipFillE.png"));
+ }
+
+ case 231 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockE.png"));
+ }
+
+ case 232 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockE.png"));
+ }
+
+ case 233 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockE.png"));
+ }
+
+ case 311 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpS.png"));
+ }
+
+ case 312 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpS.png"));
+ }
+
+ case 313 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpS.png"));
+ }
+
+ case 321 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillS.png"));
+ }
+
+ case 322 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillS.png"));
+ }
+
+ case 323 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipFillS.png"));
+ }
+
+ case 331 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockS.png"));
+ }
+
+ case 332 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockS.png"));
+ }
+
+ case 333 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockS.png"));
+ }
+
+ case 411 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpW.png"));
+ }
+
+ case 412 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpW.png"));
+ }
+
+ case 413 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpW.png"));
+ }
+
+ case 421 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillW.png"));
+ }
+
+ case 422 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillW.png"));
+ }
+
+ case 423 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipFillW.png"));
+ }
+
+ case 431 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockW.png"));
+ }
+
+ case 432 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockW.png"));
+ }
+
+ case 433 -> {
+ return ImageIO.read(
+ ClassLoader.getSystemResourceAsStream(
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockW.png"));
+ }
+ }
+ } catch (IOException e) {
+ System.out.println("ThermometerElementView: Unexpected Issue");
+ return null;
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerExporter.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerExporter.java
new file mode 100644
index 000000000..d4e6dbd39
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerExporter.java
@@ -0,0 +1,72 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.model.PuzzleExporter;
+import java.util.ArrayList;
+import org.w3c.dom.Document;
+
+public class ThermometerExporter extends PuzzleExporter {
+
+ public ThermometerExporter(Thermometer thermometer) {
+ super(thermometer);
+ }
+
+ @Override
+ protected org.w3c.dom.Element createBoardElement(Document newDocument) {
+ ThermometerBoard board = (ThermometerBoard) puzzle.getTree().getRootNode().getBoard();
+
+ // Creating the XML section for the board
+ org.w3c.dom.Element boardElement = newDocument.createElement("board");
+ boardElement.setAttribute("width", String.valueOf(board.getWidth() - 1));
+ boardElement.setAttribute("height", String.valueOf(board.getHeight() - 1));
+
+ // Creating the XML section for the vials and appending to the board
+ org.w3c.dom.Element vialsElement = newDocument.createElement("vials");
+ ArrayList vials = board.getVials();
+ for (ThermometerVial vial : vials) {
+ org.w3c.dom.Element vialElement = newDocument.createElement("vial");
+ // The way the vials are created are with the head (bulb) position and the final
+ // position
+ // This implementation doesn't allow for curved thermometers, but for right now that's
+ // fine
+ vialElement.setAttribute(
+ "headx", String.valueOf((int) vial.getHead().getLocation().getX()));
+ vialElement.setAttribute(
+ "heady", String.valueOf((int) vial.getHead().getLocation().getY()));
+ vialElement.setAttribute(
+ "tailx", String.valueOf((int) vial.getTail().getLocation().getX()));
+ vialElement.setAttribute(
+ "taily", String.valueOf((int) vial.getTail().getLocation().getY()));
+ vialsElement.appendChild(vialElement);
+ }
+ boardElement.appendChild(vialsElement);
+
+ // Creating the XML section for the row numbers and appending to the board
+ org.w3c.dom.Element rowNumbersElement = newDocument.createElement("rowNumbers");
+ ArrayList rowNumbers = board.getRowNumbers();
+ // The row numbers are the numbers on the right most column, labeling how many filled
+ // sections
+ // are in the row
+ for (ThermometerCell cell : rowNumbers) {
+ int number = cell.getRotation();
+ org.w3c.dom.Element rowNumberElement = newDocument.createElement("row");
+ rowNumberElement.setAttribute("value", String.valueOf(number));
+ rowNumbersElement.appendChild(rowNumberElement);
+ }
+ boardElement.appendChild(rowNumbersElement);
+
+ // Creating the XML section for the col numbers and appending ot the board
+ org.w3c.dom.Element colNumbersElement = newDocument.createElement("colNumbers");
+ // The col numbers are the numbers on the bottom row, labeling how many filled sections
+ // are in the column
+ ArrayList colNumbers = board.getColNumbers();
+ for (ThermometerCell cell : colNumbers) {
+ int number = cell.getRotation();
+ org.w3c.dom.Element colNumberElement = newDocument.createElement("col");
+ colNumberElement.setAttribute("value", String.valueOf(number));
+ colNumbersElement.appendChild(colNumberElement);
+ }
+ boardElement.appendChild(colNumbersElement);
+
+ return boardElement;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerFill.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerFill.java
new file mode 100644
index 000000000..34a1ff12e
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerFill.java
@@ -0,0 +1,8 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+public enum ThermometerFill {
+ UNKNOWN,
+ EMPTY,
+ FILLED,
+ BLOCKED;
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerImporter.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerImporter.java
new file mode 100644
index 000000000..711418d63
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerImporter.java
@@ -0,0 +1,196 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import edu.rpi.legup.model.PuzzleImporter;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class ThermometerImporter extends PuzzleImporter {
+
+ // basic stuff stolen from dev guide/filled in by default
+ public ThermometerImporter(Thermometer thermometer) {
+ super(thermometer);
+ }
+
+ @Override
+ public boolean acceptsRowsAndColumnsInput() {
+ return false;
+ }
+
+ @Override
+ public boolean acceptsTextInput() {
+ return false;
+ }
+
+ @Override
+ public void initializeBoard(int rows, int columns) {}
+
+ // method for initializing board from an xml file which has
+ // a provided width/height
+ @Override
+ public void initializeBoard(Node node) throws InvalidFileFormatException {
+ // sticking everything in a try statement because god has forsaken everyone
+ try {
+ // checking basic formatting of file
+ if (!node.getNodeName().equalsIgnoreCase("board")) {
+ throw new InvalidFileFormatException(
+ "thermometer Importer: cannot find board puzzleElement");
+ }
+
+ // getting the list of vials to turn into real vials
+ Element boardElement = (Element) node;
+ if (boardElement.getElementsByTagName("vials").getLength() == 0) {
+ throw new InvalidFileFormatException(
+ "thermometer Importer: no puzzleElement found for board");
+ }
+ Element dataElement = (Element) boardElement.getElementsByTagName("vials").item(0);
+ NodeList elementDataList = dataElement.getElementsByTagName("vial");
+
+ // checking both a width and height were provided for the board
+ ThermometerBoard thermometerBoard = null;
+ if (!boardElement.getAttribute("width").isEmpty()
+ && !boardElement.getAttribute("height").isEmpty()) {
+
+ // grabbing the height/width of the board
+ int width = Integer.parseInt(boardElement.getAttribute("width"));
+ int height = Integer.parseInt(boardElement.getAttribute("height"));
+
+ // grabbing the lists of rowNumbers/colNumbers
+ Element rowElement =
+ (Element) boardElement.getElementsByTagName("rowNumbers").item(0);
+ NodeList rowNodeList = rowElement.getElementsByTagName("row");
+
+ Element colElement =
+ (Element) boardElement.getElementsByTagName("colNumbers").item(0);
+ NodeList colNodeList = colElement.getElementsByTagName("col");
+
+ // checking that the number of row and col numbers agrees with height/width of board
+ if (colNodeList.getLength() != width) {
+ throw new InvalidFileFormatException(
+ "Mismatch between width and number of colNums.\n colNodeList.length:"
+ + colNodeList.getLength()
+ + " width:"
+ + width);
+ }
+ if (rowNodeList.getLength() != height) {
+ throw new InvalidFileFormatException(
+ "thermometer Importer: no rowNumbers found for board");
+ }
+
+ // finally creating our thermometer board, we add one to the size since row/col
+ // numbers
+ // are considered cells on the grid
+ thermometerBoard = new ThermometerBoard(width + 1, height + 1);
+ // adding row and column numbers to our board
+ importRowColNums(rowNodeList, colNodeList, thermometerBoard);
+ } else {
+ throw new InvalidFileFormatException(
+ "thermometer Importer: invalid board height/width");
+ }
+
+ // grabbing height/width from board, need to subtract 1
+ // because grids height/width is 1 bigger than number of vials on board
+ int width = thermometerBoard.getWidth() - 1;
+ int height = thermometerBoard.getHeight() - 1;
+
+ // adding in the vials
+ for (int i = 0; i < elementDataList.getLength(); i++) {
+ importThermometerVial(elementDataList.item(i), thermometerBoard);
+ }
+
+ // verifying all vial cells were filled by vials
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ if (thermometerBoard.getCell(x, y) == null) {
+ throw new InvalidFileFormatException(
+ "Thermometer importer Undefined tile at (" + x + "," + y + ")");
+ }
+ }
+ }
+
+ puzzle.setCurrentBoard(thermometerBoard);
+ } catch (NumberFormatException e) {
+ throw new InvalidFileFormatException(
+ "thermometer Importer: unknown value where integer expected");
+ }
+ }
+
+ @Override
+ public void initializeBoard(String[] statements)
+ throws UnsupportedOperationException, IllegalArgumentException {}
+
+ private void importRowColNums(NodeList rowNodes, NodeList colNodes, ThermometerBoard board)
+ throws InvalidFileFormatException {
+
+ // going through our list or row nodes grabbed from the xml file and
+ // then calling the thermometer boards setRowNumber function to update the value
+ for (int i = 0; i < rowNodes.getLength(); i++) {
+ Node node = rowNodes.item(i);
+ int rowNum =
+ Integer.parseInt(node.getAttributes().getNamedItem("value").getNodeValue());
+ if (!board.setRowNumber(i, rowNum)) {
+ throw new InvalidFileFormatException("thermometer Importer: out of bounds rowNum");
+ }
+ }
+
+ // same process but for col numbers
+ for (int i = 0; i < colNodes.getLength(); i++) {
+ Node node = colNodes.item(i);
+ int colNum =
+ Integer.parseInt(node.getAttributes().getNamedItem("value").getNodeValue());
+ if (!board.setColNumber(i, colNum)) {
+ throw new InvalidFileFormatException("thermometer Importer: out of bounds colNum");
+ }
+ }
+ }
+
+ private void importThermometerVial(Node node, ThermometerBoard board)
+ throws InvalidFileFormatException {
+ // head is the top of the thermometer and tip is the end of the thermometer
+ // thermometers in the xml are specified only by their head and tip cells
+ int headX = Integer.parseInt(node.getAttributes().getNamedItem("headx").getNodeValue());
+ int headY = Integer.parseInt(node.getAttributes().getNamedItem("heady").getNodeValue());
+ int tipX = Integer.parseInt(node.getAttributes().getNamedItem("tailx").getNodeValue());
+ int tipY = Integer.parseInt(node.getAttributes().getNamedItem("taily").getNodeValue());
+
+ // making sure we can add the vial before doing so
+ if (verifyVial(headX, headY, tipX, tipY, board)) {
+ // adding the vial to the board
+ board.addVial(new ThermometerVial(headX, headY, tipX, tipY, board));
+ } else {
+ throw new InvalidFileFormatException("thermometer Vial Factory: overlapping vials");
+ }
+ }
+
+ private boolean verifyVial(int headX, int headY, int tipX, int tipY, ThermometerBoard board) {
+ // figuring out which axis the thermometer travels along
+ if (headX == tipX) {
+ // finding start and end of Vial
+ int top = min(headY, tipY);
+ int bottom = max(headY, tipY);
+
+ // verifying that every cell along path is currently unconstructed
+ for (int i = top; i <= bottom; i++) {
+ if (board.getCell(headX, i) != null) return false;
+ }
+ } else if (headY == tipY) {
+ // finding start and end of Vial
+ // I have words to say to james
+ int left = min(headX, tipX);
+ int right = max(headX, tipX);
+
+ // verifying that every cell along path is currently unconstructed
+ for (int i = left; i <= right; i++) {
+ if (board.getCell(i, headY) != null) return false;
+ }
+ } else {
+ // thermometer does not line up along a single axis
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerNumberView.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerNumberView.java
new file mode 100644
index 000000000..4a00b8a18
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerNumberView.java
@@ -0,0 +1,37 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.model.gameboard.GridCell;
+import edu.rpi.legup.ui.boardview.GridElementView;
+import java.awt.*;
+
+public class ThermometerNumberView extends GridElementView {
+ private static final Font FONT = new Font("TimesRoman", Font.BOLD, 16);
+ private static final Color FONT_COLOR = Color.BLACK;
+
+ public ThermometerNumberView(GridCell cell) {
+ super(cell);
+ }
+
+ @Override
+ public GridCell getPuzzleElement() {
+ return (GridCell) super.getPuzzleElement();
+ }
+
+ @Override
+ public void drawElement(Graphics2D graphics2D) {
+ ThermometerCell cell = (ThermometerCell) puzzleElement;
+
+ graphics2D.setColor(FONT_COLOR);
+ graphics2D.setFont(FONT);
+ FontMetrics metrics = graphics2D.getFontMetrics(FONT);
+ int val;
+
+ if (cell != null) val = cell.getRotation();
+ else val = -1;
+
+ int xText = location.x + (size.width - metrics.stringWidth(String.valueOf(val))) / 2;
+ int yText = location.y + ((size.height - metrics.getHeight()) / 2) + metrics.getAscent();
+
+ graphics2D.drawString(String.valueOf(val), xText, yText);
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerType.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerType.java
new file mode 100644
index 000000000..f482411a5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerType.java
@@ -0,0 +1,8 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+public enum ThermometerType {
+ UNKNOWN,
+ HEAD,
+ SHAFT,
+ TIP;
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerVial.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerVial.java
new file mode 100644
index 000000000..2cba64363
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerVial.java
@@ -0,0 +1,103 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import java.awt.*;
+import java.util.ArrayList;
+
+public class ThermometerVial {
+ private ArrayList cells;
+
+ public ThermometerVial(int headX, int headY, int tipX, int tipY, ThermometerBoard board) {
+ // basic constructor, instantiating our members field and then
+ // calling helper function to do all the heavy lifting
+ cells = new ArrayList();
+ fillData(headX, headY, tipX, tipY, board);
+ }
+
+ // function called by the constructor which adds in all of the cells to the array
+ // as well as updates their type on the board
+ private void fillData(int headX, int headY, int tipX, int tipY, ThermometerBoard board) {
+ // not totally happy with layout of code but most readable version I can think of atm
+ // top left coordinate is 0,0 cells are added from head to tip always
+ // because cells have already been verified by time constructor is called
+ // we can guarantee that only the x or only the y coordinates wont line up
+ if (headY < tipY) {
+ addCell(headX, headY, ThermometerType.HEAD, 0, board);
+ for (int i = headY + 1; i < tipY; i++) {
+ addCell(headX, i, ThermometerType.SHAFT, 0, board);
+ }
+ addCell(tipX, tipY, ThermometerType.TIP, 0, board);
+ } else if (tipY < headY) {
+ addCell(headX, headY, ThermometerType.HEAD, 180, board);
+ for (int i = headY - 1; i > tipY; i--) {
+ addCell(headX, i, ThermometerType.SHAFT, 180, board);
+ }
+ addCell(tipX, tipY, ThermometerType.TIP, 180, board);
+ } else if (headX < tipX) {
+ addCell(headX, headY, ThermometerType.HEAD, 90, board);
+ for (int i = headX + 1; i < tipX; i++) {
+ addCell(i, headY, ThermometerType.SHAFT, 90, board);
+ }
+ addCell(tipX, tipY, ThermometerType.TIP, 90, board);
+ } else {
+ addCell(headX, headY, ThermometerType.HEAD, 270, board);
+ for (int i = headX - 1; i > tipX; i--) {
+ addCell(i, headY, ThermometerType.SHAFT, 270, board);
+ }
+ addCell(tipX, tipY, ThermometerType.TIP, 270, board);
+ }
+ }
+
+ // helper function for adding a single cell
+ private void addCell(int x, int y, ThermometerType t, int rotation, ThermometerBoard board) {
+ ThermometerCell cell =
+ new ThermometerCell(new Point(x, y), t, ThermometerFill.EMPTY, rotation);
+ cell.setIndex(y * board.getHeight() + x);
+ this.cells.add(cell);
+ // still important for element view stuff
+ board.setCell(x, y, cell);
+ }
+
+ // TODO (probably) DOES NOT WORK AS INTENDED
+ // BECAUSE MOST RULES GET A PUZZLE ELEMENT PASSED IN AND WEIRD
+ // TYPE CASTING STUFF, PAY ATTENTION TO THIS WHEN WE START
+ // DEBUGGING RULES
+ // a basic accessor to check if a cell is contained in vial
+ public boolean containsCell(ThermometerCell cell) {
+ for (ThermometerCell c : cells) {
+ if (c.getLocation() == cell.getLocation()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Returns cell containing head of thermometer
+ public ThermometerCell getHead() {
+ return cells.getFirst();
+ }
+
+ // Returns cell containing tail of thermometer
+ public ThermometerCell getTail() {
+ return cells.getLast();
+ }
+
+ // Retruns all cells in vial, from head to tip
+ public ArrayList getCells() {
+ return cells;
+ }
+
+ // checking for discontinuous flow inside of vial
+ public boolean continuousFlow() {
+ // bool which is true until it runs into an empty/blocked cell in the vial
+ // if an empty cell in the vial is found while flow is set to false
+ // we know there is a break in the flow
+ boolean flow = true;
+
+ for (ThermometerCell c : cells) {
+ if (c.getFill() != ThermometerFill.FILLED && flow) flow = false;
+
+ if (c.getFill() == ThermometerFill.FILLED && !flow) return false;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerView.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerView.java
new file mode 100644
index 000000000..444037cfe
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/ThermometerView.java
@@ -0,0 +1,48 @@
+package edu.rpi.legup.puzzle.thermometer;
+
+import edu.rpi.legup.controller.BoardController;
+import edu.rpi.legup.ui.boardview.GridBoardView;
+import java.awt.*;
+
+public class ThermometerView extends GridBoardView {
+
+ public ThermometerView(ThermometerBoard board) {
+ super(new BoardController(), new ThermometerController(), board.getDimension());
+
+ // loop for displaying the vial cells
+ // stolen largely from dev guide
+ for (ThermometerVial vial : board.getVials()) {
+ for (ThermometerCell cell : vial.getCells()) {
+ Point loc = cell.getLocation();
+ ThermometerElementView elementView = new ThermometerElementView(cell);
+ elementView.setIndex(cell.getIndex());
+ elementView.setSize(elementSize);
+ elementView.setLocation(
+ new Point(loc.x * elementSize.width, loc.y * elementSize.height));
+ elementViews.add(elementView);
+ }
+ }
+
+ // loop for displaying row numbers, same as above
+ for (ThermometerCell rowNum : board.getRowNumbers()) {
+ Point loc = rowNum.getLocation();
+ ThermometerNumberView numberView = new ThermometerNumberView(rowNum);
+ numberView.setIndex(rowNum.getIndex());
+ numberView.setSize(elementSize);
+ numberView.setLocation(
+ new Point(loc.x * elementSize.width, loc.y * elementSize.height));
+ elementViews.add(numberView);
+ }
+
+ // loop for displaying col numbers, also same as above
+ for (ThermometerCell colNum : board.getColNumbers()) {
+ Point loc = colNum.getLocation();
+ ThermometerNumberView numberView = new ThermometerNumberView(colNum);
+ numberView.setIndex(colNum.getIndex());
+ numberView.setSize(elementSize);
+ numberView.setLocation(
+ new Point(loc.x * elementSize.width, loc.y * elementSize.height));
+ elementViews.add(numberView);
+ }
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckE.java
new file mode 100644
index 000000000..8f0507ab5
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileBlckE extends PlaceableElement {
+ public HeadTileBlckE() {
+ super(
+ "Therm-PLAC-0001",
+ "Head Tile Block East",
+ "The tile corresponding to the blocked head of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckN.java
new file mode 100644
index 000000000..f195c299b
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileBlckN extends PlaceableElement {
+ public HeadTileBlckN() {
+ super(
+ "Therm-PLAC-0002",
+ "Head Tile Block North",
+ "The tile corresponding to the blocked head of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckS.java
new file mode 100644
index 000000000..d9e4c4a6a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileBlckS extends PlaceableElement {
+ public HeadTileBlckS() {
+ super(
+ "Therm-PLAC-0003",
+ "Head Tile Block South",
+ "The tile corresponding to the blocked head of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckW.java
new file mode 100644
index 000000000..2bcbfdf2d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileBlckW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileBlckW extends PlaceableElement {
+ public HeadTileBlckW() {
+ super(
+ "Therm-PLAC-0004",
+ "Head Tile Block West",
+ "The tile corresponding to the blocked head of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadBlockW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpE.java
new file mode 100644
index 000000000..0b678ed73
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileEmpE extends PlaceableElement {
+ public HeadTileEmpE() {
+ super(
+ "Therm-PLAC-0005",
+ "Head Tile Empty East",
+ "The tile corresponding to the empty head of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpN.java
new file mode 100644
index 000000000..b865b0cae
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileEmpN extends PlaceableElement {
+ public HeadTileEmpN() {
+ super(
+ "Therm-PLAC-0006",
+ "Head Tile Empty North",
+ "The tile corresponding to the empty head of a North thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpS.java
new file mode 100644
index 000000000..40989d814
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileEmpS extends PlaceableElement {
+ public HeadTileEmpS() {
+ super(
+ "Therm-PLAC-0007",
+ "Head Tile Empty South",
+ "The tile corresponding to the empty head of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpW.java
new file mode 100644
index 000000000..ba344ff8a
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileEmpW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileEmpW extends PlaceableElement {
+ public HeadTileEmpW() {
+ super(
+ "Therm-PLAC-0008",
+ "Head Tile Empty West",
+ "The tile corresponding to the empty head of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadEmpW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillE.java
new file mode 100644
index 000000000..e8bfb8f82
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileFillE extends PlaceableElement {
+ public HeadTileFillE() {
+ super(
+ "Therm-PLAC-0009",
+ "Head Tile Filled East",
+ "The tile corresponding to the filled head of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillN.java
new file mode 100644
index 000000000..4a835601c
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileFillN extends PlaceableElement {
+ public HeadTileFillN() {
+ super(
+ "Therm-PLAC-0010",
+ "Head Tile Filled North",
+ "The tile corresponding to the filled head of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillS.java
new file mode 100644
index 000000000..df559ec6f
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileFillS extends PlaceableElement {
+ public HeadTileFillS() {
+ super(
+ "Therm-PLAC-0011",
+ "Head Tile Filled South",
+ "The tile corresponding to the filled head of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillW.java
new file mode 100644
index 000000000..80194ceaf
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/HeadTileFillW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class HeadTileFillW extends PlaceableElement {
+ public HeadTileFillW() {
+ super(
+ "Therm-PLAC-0012",
+ "Head Tile Filled West",
+ "The tile corresponding to the filled head of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/HeadFillW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckE.java
new file mode 100644
index 000000000..7080b9a47
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileBlckE extends PlaceableElement {
+ public ShaftTileBlckE() {
+ super(
+ "Therm-PLAC-0013",
+ "Shaft Tile Blocked East",
+ "The tile corresponding to a Blocked middle segment of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckN.java
new file mode 100644
index 000000000..760baf624
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileBlckN extends PlaceableElement {
+ public ShaftTileBlckN() {
+ super(
+ "Therm-PLAC-0014",
+ "Shaft Tile Blocked North",
+ "The tile corresponding to a Blocked middle segment of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckS.java
new file mode 100644
index 000000000..ec14a669d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileBlckS extends PlaceableElement {
+ public ShaftTileBlckS() {
+ super(
+ "Therm-PLAC-0015",
+ "Shaft Tile Blocked South",
+ "The tile corresponding to a Blocked middle segment of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckW.java
new file mode 100644
index 000000000..183fd798c
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileBlckW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileBlckW extends PlaceableElement {
+ public ShaftTileBlckW() {
+ super(
+ "Therm-PLAC-0016",
+ "Shaft Tile Blocked West",
+ "The tile corresponding to a Blocked middle segment of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftBlockW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpE.java
new file mode 100644
index 000000000..771c1afc3
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileEmpE extends PlaceableElement {
+ public ShaftTileEmpE() {
+ super(
+ "Therm-PLAC-0017",
+ "Shaft Tile Empty East",
+ "The tile corresponding to an empty middle segment of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpN.java
new file mode 100644
index 000000000..c2ec4e0ab
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileEmpN extends PlaceableElement {
+ public ShaftTileEmpN() {
+ super(
+ "Therm-PLAC-0018",
+ "Shaft Tile Empty North",
+ "The tile corresponding to an empty middle segment of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpS.java
new file mode 100644
index 000000000..fc2828ddb
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileEmpS extends PlaceableElement {
+ public ShaftTileEmpS() {
+ super(
+ "Therm-PLAC-0019",
+ "Shaft Tile Empty South",
+ "The tile corresponding to an empty middle segment of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpW.java
new file mode 100644
index 000000000..ce0e7bce3
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileEmpW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileEmpW extends PlaceableElement {
+ public ShaftTileEmpW() {
+ super(
+ "Therm-PLAC-0020",
+ "Shaft Tile Empty West",
+ "The tile corresponding to an empty middle segment of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftEmpW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillE.java
new file mode 100644
index 000000000..a8aeb44ed
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileFillE extends PlaceableElement {
+ public ShaftTileFillE() {
+ super(
+ "Therm-PLAC-0021",
+ "Shaft Tile Filled East",
+ "The tile corresponding to a filled middle segment of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillN.java
new file mode 100644
index 000000000..0366c3d0b
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileFillN extends PlaceableElement {
+ public ShaftTileFillN() {
+ super(
+ "Therm-PLAC-0022",
+ "Shaft Tile Filled North",
+ "The tile corresponding to a filled middle segment of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillS.java
new file mode 100644
index 000000000..b55d2a1f9
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileFillS extends PlaceableElement {
+ public ShaftTileFillS() {
+ super(
+ "Therm-PLAC-0023",
+ "Shaft Tile Filled South",
+ "The tile corresponding to a filled middle segment of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillW.java
new file mode 100644
index 000000000..3b2cd0454
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/ShaftTileFillW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class ShaftTileFillW extends PlaceableElement {
+ public ShaftTileFillW() {
+ super(
+ "Therm-PLAC-0024",
+ "Shaft Tile Filled West",
+ "The tile corresponding to a filled middle segment of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/ShaftFillS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckE.java
new file mode 100644
index 000000000..ea94846c2
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileBlckE extends PlaceableElement {
+ public TipTileBlckE() {
+ super(
+ "Therm-PLAC-0025",
+ "Tip Tile Block East",
+ "The tile corresponding to the Blocked tip of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckN.java
new file mode 100644
index 000000000..25ae8afda
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileBlckN extends PlaceableElement {
+ public TipTileBlckN() {
+ super(
+ "Therm-PLAC-0026",
+ "Tip Tile Block North",
+ "The tile corresponding to the Blocked tip of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckS.java
new file mode 100644
index 000000000..e19082162
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileBlckS extends PlaceableElement {
+ public TipTileBlckS() {
+ super(
+ "Therm-PLAC-0027",
+ "Tip Tile Block South",
+ "The tile corresponding to the Blocked tip of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckW.java
new file mode 100644
index 000000000..a0c49bc77
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileBlckW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileBlckW extends PlaceableElement {
+ public TipTileBlckW() {
+ super(
+ "Therm-PLAC-0028",
+ "Tip Tile Block West",
+ "The tile corresponding to the Blocked tip of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipBlockW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpE.java
new file mode 100644
index 000000000..6595da855
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileEmpE extends PlaceableElement {
+ public TipTileEmpE() {
+ super(
+ "Therm-PLAC-0029",
+ "Tip Tile Empty East",
+ "The tile corresponding to the empty tip of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpN.java
new file mode 100644
index 000000000..cacfe5d5d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileEmpN extends PlaceableElement {
+ public TipTileEmpN() {
+ super(
+ "Therm-PLAC-0030",
+ "Tip Tile Empty North",
+ "The tile corresponding to the empty tip of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpS.java
new file mode 100644
index 000000000..2815b9fa1
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileEmpS extends PlaceableElement {
+ public TipTileEmpS() {
+ super(
+ "Therm-PLAC-0031",
+ "Tip Tile Empty South",
+ "The tile corresponding to the empty tip of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpW.java
new file mode 100644
index 000000000..3bd77495f
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileEmpW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileEmpW extends PlaceableElement {
+ public TipTileEmpW() {
+ super(
+ "Therm-PLAC-0032",
+ "Tip Tile Empty West",
+ "The tile corresponding to the empty tip of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipEmpW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillE.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillE.java
new file mode 100644
index 000000000..8c9953dd2
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillE.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileFillE extends PlaceableElement {
+ public TipTileFillE() {
+ super(
+ "Therm-PLAC-0033",
+ "Tip Tile Fill East",
+ "The tile corresponding to the filled tip of an east thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipFillE.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillN.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillN.java
new file mode 100644
index 000000000..f5ce01c7b
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillN.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileFillN extends PlaceableElement {
+ public TipTileFillN() {
+ super(
+ "Therm-PLAC-0034",
+ "Tip Tile Fill North",
+ "The tile corresponding to the filled tip of a north thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipFillN.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillS.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillS.java
new file mode 100644
index 000000000..05d68fe81
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillS.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileFillS extends PlaceableElement {
+ public TipTileFillS() {
+ super(
+ "Therm-PLAC-0035",
+ "Tip Tile Fill South",
+ "The tile corresponding to the filled tip of a south thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipFillS.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillW.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillW.java
new file mode 100644
index 000000000..6aa486ba1
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/elements/TipTileFillW.java
@@ -0,0 +1,13 @@
+package edu.rpi.legup.puzzle.thermometer.elements;
+
+import edu.rpi.legup.model.elements.PlaceableElement;
+
+public class TipTileFillW extends PlaceableElement {
+ public TipTileFillW() {
+ super(
+ "Therm-PLAC-0036",
+ "Tip Tile Fill West",
+ "The tile corresponding to the filled tip of a west thermometer",
+ "edu/rpi/legup/images/thermometer/Elements/TipFillW.png");
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/DiscontinuousMercuryContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/DiscontinuousMercuryContradictionRule.java
new file mode 100644
index 000000000..b013b3493
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/DiscontinuousMercuryContradictionRule.java
@@ -0,0 +1,62 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.thermometer.ThermometerBoard;
+import edu.rpi.legup.puzzle.thermometer.ThermometerCell;
+import edu.rpi.legup.puzzle.thermometer.ThermometerVial;
+import java.util.ArrayList;
+
+// TODO: Rule is untested
+public class DiscontinuousMercuryContradictionRule extends ContradictionRule {
+
+ private final String NO_CONTRADICTION_MESSAGE =
+ "Does not contain a contradiction at this index";
+ private final String INVALID_USE_MESSAGE = "Contradiction must be a vial";
+
+ public DiscontinuousMercuryContradictionRule() {
+ super(
+ "THERM-CONT-0001",
+ "Discontinuous Mercury",
+ "A vial has a filled cell after an empty or blocked cell",
+ "edu/rpi/legup/images/thermometer/MercuryInBody.png");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ // User can click on any cell in a vial with a discontinuous flow
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ // useful variables
+ ThermometerBoard thermometerBoard = (ThermometerBoard) board;
+
+ ThermometerCell cell = (ThermometerCell) thermometerBoard.getPuzzleElement(puzzleElement);
+
+ ArrayList thermometerVials = thermometerBoard.getVials();
+
+ // finding out which vial contains the specified cell
+ for (int i = 0; i < thermometerVials.size(); i++) {
+ ThermometerVial thermometerVial = thermometerVials.get(i);
+ // if a vial contains the clicked on cell
+ // checking if the vial has a break in the flow
+ if (thermometerVial.containsCell(cell)) {
+ if (thermometerVial.continuousFlow()) {
+ return super.getNoContradictionMessage() + ": " + this.NO_CONTRADICTION_MESSAGE;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ // if none of the vials contain the clicked on cell yell at user
+ return super.getInvalidUseOfRuleMessage() + ": " + this.INVALID_USE_MESSAGE;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/FinishWithBlockedDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/FinishWithBlockedDirectRule.java
new file mode 100644
index 000000000..d09b98300
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/FinishWithBlockedDirectRule.java
@@ -0,0 +1,44 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+// TODO: Rule is unimplemented
+public class FinishWithBlockedDirectRule extends DirectRule {
+ public FinishWithBlockedDirectRule() {
+ super(
+ "THERM-BASC-0004",
+ "Finish With Blocked",
+ "Remaining tiles must be blocked once requirement is satisfied",
+ "edu/rpi/legup/images/thermometer/FinishWithBlocked.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/FinishWithMercuryDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/FinishWithMercuryDirectRule.java
new file mode 100644
index 000000000..09fb8874d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/FinishWithMercuryDirectRule.java
@@ -0,0 +1,44 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+// TODO: Rule is unimplemented
+public class FinishWithMercuryDirectRule extends DirectRule {
+ public FinishWithMercuryDirectRule() {
+ super(
+ "THERM-BASC-0003",
+ "Finish with Mercury",
+ "Remaining tiles must be filled to satisfy requirement",
+ "edu/rpi/legup/images/thermometer/FinishWithMercury.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/MercuryOrBlockedCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/MercuryOrBlockedCaseRule.java
new file mode 100644
index 000000000..a0644aec0
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/MercuryOrBlockedCaseRule.java
@@ -0,0 +1,114 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.thermometer.*;
+import java.util.ArrayList;
+import java.util.List;
+
+// TODO: Rule is untested
+public class MercuryOrBlockedCaseRule extends CaseRule {
+ public MercuryOrBlockedCaseRule() {
+ super(
+ "THERM-CASE-0001",
+ "Mercury or Blocked",
+ "Each unassigned tile must be filled with mercury or blocked.",
+ "edu/rpi/legup/images/thermometer/MercOrBlocked.png");
+ }
+
+ /**
+ * Checks whether the transition logically follows from the parent node using this rule
+ *
+ * @param transition transition to check
+ * @return null if the child node logically follow from the parent node, otherwise error message
+ */
+ @Override
+ public String checkRuleRaw(TreeTransition transition) {
+ List childTransitions = transition.getParents().get(0).getChildren();
+ if (childTransitions.size() != 2) {
+ return super.getInvalidUseOfRuleMessage() + ": This case rule must have 2 children.";
+ }
+
+ TreeTransition case1 = childTransitions.get(0);
+ TreeTransition case2 = childTransitions.get(1);
+ if (case1.getBoard().getModifiedData().size() != 1
+ || case2.getBoard().getModifiedData().size() != 1) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must have 1 modified cell for each case.";
+ }
+
+ ThermometerCell mod1 =
+ (ThermometerCell) case1.getBoard().getModifiedData().iterator().next();
+ ThermometerCell mod2 =
+ (ThermometerCell) case2.getBoard().getModifiedData().iterator().next();
+ if (!mod1.getLocation().equals(mod2.getLocation())) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must modify the same cell for each case.";
+ }
+
+ if (!((mod1.getFill() == ThermometerFill.BLOCKED
+ && mod2.getFill() == ThermometerFill.FILLED)
+ || (mod2.getFill() == ThermometerFill.BLOCKED
+ && mod1.getFill() == ThermometerFill.FILLED))) {
+ return super.getInvalidUseOfRuleMessage()
+ + ": This case rule must have a filled or blocked cell.";
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ ThermometerBoard thermometerBoard = (ThermometerBoard) board.copy();
+ CaseBoard caseBoard = new CaseBoard(thermometerBoard, this);
+ thermometerBoard.setModifiable(false);
+ for (PuzzleElement element : thermometerBoard.getPuzzleElements()) {
+ if (((ThermometerCell) element).getFill() == ThermometerFill.UNKNOWN) {
+ caseBoard.addPickableElement(element);
+ }
+ }
+ return caseBoard;
+ }
+
+ /**
+ * Gets the possible cases at a specific location based on this case rule
+ *
+ * @param board the current board state
+ * @param puzzleElement equivalent puzzleElement
+ * @return a list of elements the specified could be
+ */
+ @Override
+ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
+ ArrayList cases = new ArrayList<>();
+ Board case1 = board.copy();
+ ThermometerCell data1 = (ThermometerCell) case1.getPuzzleElement(puzzleElement);
+ data1.setFill(ThermometerFill.FILLED);
+ case1.addModifiedData(data1);
+ cases.add(case1);
+
+ Board case2 = board.copy();
+ ThermometerCell data2 = (ThermometerCell) case2.getPuzzleElement(puzzleElement);
+ data2.setFill(ThermometerFill.BLOCKED);
+ case2.addModifiedData(data2);
+ cases.add(case2);
+
+ return cases;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/MinimumFillDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/MinimumFillDirectRule.java
new file mode 100644
index 000000000..ab389d6ff
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/MinimumFillDirectRule.java
@@ -0,0 +1,44 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+// TODO: Rule is unimplemented
+public class MinimumFillDirectRule extends DirectRule {
+ public MinimumFillDirectRule() {
+ super(
+ "THERM-BASC-0005",
+ "Minimum Fill",
+ "Some thermometers must be filled a minimum amount to satisfy requirement",
+ "edu/rpi/legup/images/thermometer/MinimumFill.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/PriorFilledDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/PriorFilledDirectRule.java
new file mode 100644
index 000000000..61622ddb1
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/PriorFilledDirectRule.java
@@ -0,0 +1,101 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.thermometer.ThermometerBoard;
+import edu.rpi.legup.puzzle.thermometer.ThermometerCell;
+import edu.rpi.legup.puzzle.thermometer.ThermometerFill;
+import edu.rpi.legup.puzzle.thermometer.ThermometerVial;
+import java.util.ArrayList;
+
+// TODO: Rule is untested
+public class PriorFilledDirectRule extends DirectRule {
+
+ public PriorFilledDirectRule() {
+ super(
+ "THERM-BASC-0002",
+ "Prior is Filled",
+ "All tiles proceeding a filled tile in a vial must be filled",
+ "edu/rpi/legup/images/thermometer/PriorIsFilled.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ ThermometerBoard initialBoard =
+ (ThermometerBoard) transition.getParents().get(0).getBoard();
+ ThermometerBoard finalBoard = (ThermometerBoard) transition.getBoard();
+
+ ThermometerCell cell = (ThermometerCell) finalBoard.getPuzzleElement(puzzleElement);
+ if (cell.getFill() != ThermometerFill.FILLED) {
+ return super.getInvalidUseOfRuleMessage() + ": Cell is not filled at this index";
+ }
+
+ ArrayList allVials = finalBoard.getVials();
+ ThermometerVial host = null;
+ for (ThermometerVial vials : allVials) {
+ if (vials.containsCell((cell))) {
+ host = vials;
+ }
+ }
+ if (host == null) return super.getInvalidUseOfRuleMessage() + ": Something went wrong - 1";
+ int x = (int) cell.getLocation().getX();
+ int y = (int) cell.getLocation().getX();
+
+ // Identifies next cell from tail location, checks if it is filled
+ if (host.getTail() == cell) {
+ return super.getInvalidUseOfRuleMessage() + ": rule can not apply to tail";
+ } else if (host.getTail().getLocation().getX() == x) {
+ if (host.getTail().getLocation().getY() > y) {
+ if (initialBoard.getCell(x, y + 1).getFill() == ThermometerFill.FILLED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else if (host.getTail().getLocation().getY() < y) {
+ if (initialBoard.getCell(x, y - 1).getFill() == ThermometerFill.FILLED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else return super.getInvalidUseOfRuleMessage() + ": Something went wrong - 2";
+ } else if (host.getTail().getLocation().getY() == y) {
+ if (host.getTail().getLocation().getX() > x) {
+ if (initialBoard.getCell(x + 1, y).getFill() == ThermometerFill.FILLED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else if (host.getTail().getLocation().getX() < x) {
+ if (initialBoard.getCell(x - 1, y).getFill() == ThermometerFill.FILLED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else return super.getInvalidUseOfRuleMessage() + ": Something went wrong - 2.1";
+ }
+ return super.getInvalidUseOfRuleMessage() + "Something went wrong - 3";
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/RestEmptyDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/RestEmptyDirectRule.java
new file mode 100644
index 000000000..486c5c1da
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/RestEmptyDirectRule.java
@@ -0,0 +1,101 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.thermometer.ThermometerBoard;
+import edu.rpi.legup.puzzle.thermometer.ThermometerCell;
+import edu.rpi.legup.puzzle.thermometer.ThermometerFill;
+import edu.rpi.legup.puzzle.thermometer.ThermometerVial;
+import java.util.ArrayList;
+
+// TODO: Rule is untested
+public class RestEmptyDirectRule extends DirectRule {
+
+ public RestEmptyDirectRule() {
+ super(
+ "THERM-BASC-0001",
+ "Rest is Empty",
+ "All tiles following a blocked tile in a vial must be blocked",
+ "edu/rpi/legup/images/thermometer/RestIsEmpty.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ ThermometerBoard initialBoard =
+ (ThermometerBoard) transition.getParents().get(0).getBoard();
+ ThermometerBoard finalBoard = (ThermometerBoard) transition.getBoard();
+
+ ThermometerCell cell = (ThermometerCell) finalBoard.getPuzzleElement(puzzleElement);
+ if (cell.getFill() != ThermometerFill.BLOCKED) {
+ return super.getInvalidUseOfRuleMessage() + ": Cell is not blocked at this index";
+ }
+
+ ArrayList allVials = finalBoard.getVials();
+ ThermometerVial host = null;
+ for (ThermometerVial vials : allVials) {
+ if (vials.containsCell((cell))) {
+ host = vials;
+ }
+ }
+ if (host == null) return super.getInvalidUseOfRuleMessage() + ": Something went wrong - 1";
+ int x = (int) cell.getLocation().getX();
+ int y = (int) cell.getLocation().getX();
+
+ // Identifies previous cell from head location, checks if it is blocked
+ if (host.getHead() == cell) {
+ return super.getInvalidUseOfRuleMessage() + ": rule can not apply to head";
+ } else if (host.getHead().getLocation().getX() == x) {
+ if (host.getHead().getLocation().getY() > y) {
+ if (initialBoard.getCell(x, y + 1).getFill() == ThermometerFill.BLOCKED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else if (host.getHead().getLocation().getY() < y) {
+ if (initialBoard.getCell(x, y - 1).getFill() == ThermometerFill.BLOCKED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else return super.getInvalidUseOfRuleMessage() + ": Something went wrong - 2";
+ } else if (host.getHead().getLocation().getY() == y) {
+ if (host.getHead().getLocation().getX() > x) {
+ if (initialBoard.getCell(x + 1, y).getFill() == ThermometerFill.BLOCKED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else if (host.getHead().getLocation().getX() < x) {
+ if (initialBoard.getCell(x - 1, y).getFill() == ThermometerFill.BLOCKED) {
+ return null;
+ } else {
+ return super.getInvalidUseOfRuleMessage() + "rule does not apply to this cell";
+ }
+ } else return super.getInvalidUseOfRuleMessage() + ": Something went wrong - 2.1";
+ }
+ return super.getInvalidUseOfRuleMessage() + "Something went wrong - 3";
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/SatisfyMercuryCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/SatisfyMercuryCaseRule.java
new file mode 100644
index 000000000..05c861281
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/SatisfyMercuryCaseRule.java
@@ -0,0 +1,62 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.CaseBoard;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.CaseRule;
+import edu.rpi.legup.model.tree.TreeTransition;
+import edu.rpi.legup.puzzle.thermometer.*;
+import java.util.ArrayList;
+
+// TODO:This rule is unimplemented
+public class SatisfyMercuryCaseRule extends CaseRule {
+ public SatisfyMercuryCaseRule() {
+ super(
+ "THERM-CASE-0002",
+ "Satisfy Mercury",
+ "There are multiple ways column/row requirements can be fufilled",
+ "edu/rpi/legup/images/thermometer/SatisfyMercury.png");
+ }
+
+ /**
+ * Checks whether the transition logically follows from the parent node using this rule
+ *
+ * @param transition transition to check
+ * @return null if the child node logically follow from the parent node, otherwise error message
+ */
+ @Override
+ public String checkRuleRaw(TreeTransition transition) {
+ return null;
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ /**
+ * Gets the possible cases at a specific location based on this case rule
+ *
+ * @param board the current board state
+ * @param puzzleElement equivalent puzzleElement
+ * @return a list of elements the specified could be
+ */
+ @Override
+ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ @Override
+ public CaseBoard getCaseBoard(Board board) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/ThermometerTooLargeDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/ThermometerTooLargeDirectRule.java
new file mode 100644
index 000000000..d679781b7
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/ThermometerTooLargeDirectRule.java
@@ -0,0 +1,44 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.DirectRule;
+import edu.rpi.legup.model.tree.TreeNode;
+import edu.rpi.legup.model.tree.TreeTransition;
+
+// TODO: Rule is unimplemented
+public class ThermometerTooLargeDirectRule extends DirectRule {
+ public ThermometerTooLargeDirectRule() {
+ super(
+ "THERM-BASC-0006",
+ "Thermometer Too Large",
+ "If thermometer is larger than required mercury, some of it must be blocked",
+ "edu/rpi/legup/images/thermometer/ThermometerTooLarge.png");
+ }
+
+ /**
+ * Checks whether the child node logically follows from the parent node at the specific
+ * puzzleElement index using this rule
+ *
+ * @param transition transition to check
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the child node logically follow from the parent node at the specified
+ * puzzleElement, otherwise error message
+ */
+ @Override
+ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
+ return null;
+ }
+
+ /**
+ * Creates a transition {@link Board} that has this rule applied to it using the {@link
+ * TreeNode}.
+ *
+ * @param node tree node used to create default transition board
+ * @return default board or null if this rule cannot be applied to this tree node
+ */
+ @Override
+ public Board getDefaultBoard(TreeNode node) {
+ return null;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/TooFewMercuryContradiction.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/TooFewMercuryContradiction.java
new file mode 100644
index 000000000..8750eb64d
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/TooFewMercuryContradiction.java
@@ -0,0 +1,57 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.thermometer.ThermometerBoard;
+import edu.rpi.legup.puzzle.thermometer.ThermometerCell;
+import edu.rpi.legup.puzzle.thermometer.ThermometerFill;
+
+// TODO: Rule is untested
+public class TooFewMercuryContradiction extends ContradictionRule {
+
+ private final String Invalid_Use_Message = "Mercury can still reach limit";
+
+ public TooFewMercuryContradiction() {
+ super(
+ "THERM-CONT-0002",
+ "Too Few Mercury",
+ "Not enough mercury in column/row to fufill requirement",
+ "edu/rpi/legup/images/thermometer/NotEnoughMercury.png");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ @Override
+ // Checks if row or column of input element has too many blocked tiles
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ ThermometerBoard grid = (ThermometerBoard) board;
+ ThermometerCell cell = (ThermometerCell) grid.getPuzzleElement(puzzleElement);
+ int blocked = 0;
+ for (int i = 0; i < grid.getHeight(); i++) {
+ if (grid.getCell((int) cell.getLocation().getX(), i).getFill()
+ == ThermometerFill.BLOCKED) {
+ blocked++;
+ }
+ }
+ if (grid.getRowNumber((int) cell.getLocation().getX()) > blocked) return null;
+
+ blocked = 0;
+ for (int i = 0; i < grid.getWidth(); i++) {
+ if (grid.getCell(i, (int) cell.getLocation().getY()).getFill()
+ == ThermometerFill.BLOCKED) {
+ blocked++;
+ }
+ }
+ if (grid.getColNumber((int) cell.getLocation().getY()) > blocked) return null;
+
+ return Invalid_Use_Message;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/TooManyMercuryContradiction.java b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/TooManyMercuryContradiction.java
new file mode 100644
index 000000000..06b8a017c
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/TooManyMercuryContradiction.java
@@ -0,0 +1,56 @@
+package edu.rpi.legup.puzzle.thermometer.rules;
+
+import edu.rpi.legup.model.gameboard.Board;
+import edu.rpi.legup.model.gameboard.PuzzleElement;
+import edu.rpi.legup.model.rules.ContradictionRule;
+import edu.rpi.legup.puzzle.thermometer.ThermometerBoard;
+import edu.rpi.legup.puzzle.thermometer.ThermometerCell;
+import edu.rpi.legup.puzzle.thermometer.ThermometerFill;
+
+// TODO: Rule is untested
+public class TooManyMercuryContradiction extends ContradictionRule {
+
+ private final String Invalid_Use_Message = "Mercury does not exceed limit";
+
+ public TooManyMercuryContradiction() {
+ super(
+ "THERM-CONT-0003",
+ "Too Many Mercury",
+ "More mercury in column/row than target",
+ "edu/rpi/legup/images/thermometer/TooManyMercury.png");
+ }
+
+ /**
+ * Checks whether the transition has a contradiction at the specific puzzleElement index using
+ * this rule
+ *
+ * @param board board to check contradiction
+ * @param puzzleElement equivalent puzzleElement
+ * @return null if the transition contains a contradiction at the specified puzzleElement,
+ * otherwise error message
+ */
+ @Override
+ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) {
+ ThermometerBoard grid = (ThermometerBoard) board;
+ ThermometerCell cell = (ThermometerCell) grid.getPuzzleElement(puzzleElement);
+ int filled = 0;
+ for (int i = 0; i < grid.getHeight(); i++) {
+ if (grid.getCell((int) cell.getLocation().getX(), i).getFill()
+ == ThermometerFill.FILLED) {
+ filled++;
+ }
+ }
+ if (grid.getRowNumber((int) cell.getLocation().getX()) > filled) return null;
+
+ filled = 0;
+ for (int i = 0; i < grid.getWidth(); i++) {
+ if (grid.getCell(i, (int) cell.getLocation().getY()).getFill()
+ == ThermometerFill.FILLED) {
+ filled++;
+ }
+ }
+ if (grid.getColNumber((int) cell.getLocation().getY()) > filled) return null;
+
+ return Invalid_Use_Message;
+ }
+}
diff --git a/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/thermometer_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/thermometer_reference_sheet.txt
new file mode 100644
index 000000000..546fb89a6
--- /dev/null
+++ b/src/main/java/edu/rpi/legup/puzzle/thermometer/rules/thermometer_reference_sheet.txt
@@ -0,0 +1,16 @@
+THERM-BASC-0001 : RestEmptyDirectRule
+THERM-BASC-0002 : PriorFilledDirectRule
+THERM-BASC-0003 : FinishWithMercuryDirectRule
+THERM-BASC-0004 : FinishWithBlockedDirectRule
+THERM-BASC-0005 : MinimumFillDirectRule
+THERM-BASC-0006 : ThermometerTooLargeDirectRule
+
+THERM-CONT-0001 : DiscontinuousMercuryContradictionRule
+THERM-CONT-0002 : TooFewMercuryContradiction
+THERM-CONT-0003 : TooManyMercuryContradiction
+
+THERM-CASE-0001 : MercuryOrBlockedCaseRule
+THERM-CASE-0002 : SatisfyMercuryCaseRule
+
+Images can be found/edited here:
+https://docs.google.com/presentation/d/1YHNog2fGvLJEx4kbJZiwwAlP-m2-E1O7hGh0HJ7S0gE/edit?usp=sharing
\ No newline at end of file
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
index 09706f92a..c8962aa03 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
@@ -53,21 +53,29 @@ public TreeTentCell getCell(int x, int y) {
@Override
public PuzzleElement getPuzzleElement(PuzzleElement element) {
- switch (element.getIndex()) {
- case -2:
- return element;
- case -1:
- TreeTentLine line = (TreeTentLine) element;
- TreeTentLine thisLine = null;
- for (TreeTentLine l : lines) {
- if (line.compare(l)) {
- thisLine = l;
- break;
- }
- }
- return thisLine;
- default:
- return super.getPuzzleElement(element);
+ return switch (element.getIndex()) {
+ case -2 -> element;
+ case -1 -> element;
+ default -> super.getPuzzleElement(element);
+ };
+ }
+
+ @Override
+ public void setPuzzleElement(int index, PuzzleElement puzzleElement) {
+ if (index == -1) {
+ lines.add((TreeTentLine) puzzleElement);
+ } else if (index < puzzleElements.size()) {
+ puzzleElements.set(index, puzzleElement);
+ }
+ }
+
+ @Override
+ public void notifyChange(PuzzleElement puzzleElement) {
+ int index = puzzleElement.getIndex();
+ if (index == -1) {
+ lines.add((TreeTentLine) puzzleElement);
+ } else if (index < puzzleElements.size()) {
+ puzzleElements.set(index, puzzleElement);
}
}
@@ -168,20 +176,20 @@ public List getDiagonals(TreeTentCell cell, TreeTentType type) {
*
* @param index the row or column number
* @param type type of TreeTent element
- * @param isRow boolean value beased on whether a row of column is being checked
+ * @param isRow boolean value based on whether a row of column is being checked
* @return List of TreeTentCells that match the given TreeTentType
*/
public List getRowCol(int index, TreeTentType type, boolean isRow) {
List list = new ArrayList<>();
if (isRow) {
- for (int i = 0; i < dimension.height; i++) {
+ for (int i = 0; i < dimension.width; i++) {
TreeTentCell cell = getCell(i, index);
if (cell.getType() == type) {
list.add(cell);
}
}
} else {
- for (int i = 0; i < dimension.width; i++) {
+ for (int i = 0; i < dimension.height; i++) {
TreeTentCell cell = getCell(index, i);
if (cell.getType() == type) {
list.add(cell);
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentCellFactory.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentCellFactory.java
index 1697b8bd5..a3553940d 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentCellFactory.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentCellFactory.java
@@ -39,7 +39,7 @@ public PuzzleElement importCell(Node node, Board board) throws InvalidFileFormat
}
TreeTentCell cell = new TreeTentCell(TreeTentType.valueOf(value), new Point(x, y));
- cell.setIndex(y * height + x);
+ cell.setIndex(y * width + x);
return cell;
} else {
if (node.getNodeName().equalsIgnoreCase("line")) {
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentClue.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentClue.java
index bcba7dc94..7b93f1301 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentClue.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentClue.java
@@ -50,6 +50,6 @@ public void setType(TreeTentType type) {
}
public TreeTentClue copy() {
- return null;
+ return new TreeTentClue(data, clueIndex, type);
}
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentController.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentController.java
index 79e074657..667c2ba7d 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentController.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentController.java
@@ -32,8 +32,10 @@ public TreeTentController() {
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON2) {
BoardView boardView = getInstance().getLegupUI().getBoardView();
- dragStart = boardView.getElement(e.getPoint());
- lastCellPressed = boardView.getElement(e.getPoint());
+ if (boardView != null) {
+ dragStart = boardView.getElement(e.getPoint());
+ lastCellPressed = boardView.getElement(e.getPoint());
+ }
}
}
@@ -105,6 +107,8 @@ public void mouseReleased(MouseEvent e) {
}
dragStart = null;
lastCellPressed = null;
+ } else {
+ super.mouseReleased(e);
}
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentExporter.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentExporter.java
index 475aaab1e..82c1b373d 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentExporter.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentExporter.java
@@ -52,7 +52,7 @@ protected org.w3c.dom.Element createBoardElement(Document newDocument) {
org.w3c.dom.Element axisSouth = newDocument.createElement("axis");
axisSouth.setAttribute("side", "south");
- for (TreeTentClue clue : board.getRowClues()) {
+ for (TreeTentClue clue : board.getColClues()) {
org.w3c.dom.Element clueElement = newDocument.createElement("clue");
clueElement.setAttribute("value", String.valueOf(clue.getData()));
clueElement.setAttribute("index", String.valueOf(clue.getClueIndex()));
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java
index 0117f41ce..56dcca59f 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java
@@ -3,6 +3,8 @@
import edu.rpi.legup.model.PuzzleImporter;
import edu.rpi.legup.save.InvalidFileFormatException;
import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -113,7 +115,7 @@ public void initializeBoard(Node node) throws InvalidFileFormatException {
for (int x = 0; x < width; x++) {
if (treeTentBoard.getCell(x, y) == null) {
TreeTentCell cell = new TreeTentCell(TreeTentType.UNKNOWN, new Point(x, y));
- cell.setIndex(y * height + x);
+ cell.setIndex(y * width + x);
cell.setModifiable(true);
treeTentBoard.setCell(x, y, cell);
}
@@ -216,4 +218,12 @@ public void initializeBoard(Node node) throws InvalidFileFormatException {
public void initializeBoard(String[] statements) throws UnsupportedOperationException {
throw new UnsupportedOperationException("Tree Tent cannot accept text input");
}
+
+ @Override
+ public List getImporterElements() {
+ List elements = new ArrayList<>();
+ elements.add("cell");
+ elements.add("line");
+ return elements;
+ }
}
diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java
index 0116c0dcd..aaa1a8fbc 100644
--- a/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java
+++ b/src/main/java/edu/rpi/legup/puzzle/treetent/rules/FillinRowCaseRule.java
@@ -61,7 +61,7 @@ public CaseBoard getCaseBoard(Board board) {
*/
@Override
public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
- ArrayList cases = new ArrayList();
+ ArrayList cases;
List group;
int tentsLeft;
TreeTentClue clue = ((TreeTentClue) puzzleElement);
@@ -70,7 +70,7 @@ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
if (clue.getType() == TreeTentType.CLUE_SOUTH) {
group = tBoard.getRowCol(clueIndex, TreeTentType.UNKNOWN, false);
tentsLeft =
- tBoard.getRowClues().get(clueIndex).getData()
+ tBoard.getColClues().get(clueIndex).getData()
- tBoard.getRowCol(clueIndex, TreeTentType.TENT, false).size();
cases = genCombinations(tBoard, group, tentsLeft, clueIndex, false);
} else {
@@ -83,60 +83,113 @@ public ArrayList getCases(Board board, PuzzleElement puzzleElement) {
// generate every combination (nCr)
// call goodBoard for each generated combination
- // alternitive would be to implement collision avoidance while generating instead of after
+ // alternative would be to implement collision avoidance while generating instead of after
if (cases.size() > 0) {
return cases;
}
return null;
}
+ /**
+ * @param iBoard the board to place tents onto
+ * @param tiles the locations where tents can be placed
+ * @param target the target number of tents to place
+ * @param index the index of tiles which is trying to be placed
+ * @param isRow Used to check validity of board
+ * @return the list of boards created
+ */
private ArrayList genCombinations(
TreeTentBoard iBoard,
List tiles,
int target,
Integer index,
boolean isRow) {
- return genCombRecursive(
- iBoard, tiles, tiles, target, 0, new ArrayList(), index, isRow);
+ ArrayList generatedBoards = new ArrayList<>();
+ genCombRecursive(
+ iBoard,
+ tiles,
+ target,
+ 0,
+ new ArrayList(),
+ 0,
+ index,
+ generatedBoards,
+ isRow);
+ return generatedBoards;
}
- private ArrayList genCombRecursive(
+ /**
+ * Recursive function to generate all ways of placing the target number of tents from the list
+ * of tiles to fill.
+ *
+ * @param iBoard The board
+ * @param tiles Unknown Tiles to fill
+ * @param target number of tents to place
+ * @param current number of tents already placed
+ * @param currentTile index of the next tile to add
+ * @param selected the cells which have tents
+ * @param index The index of the clue
+ * @param isRow Used for checking if the board is good
+ * The generated boards are placed into generatedBoards (passed by reference)
+ */
+ private void genCombRecursive(
TreeTentBoard iBoard,
- List original,
List tiles,
int target,
int current,
List selected,
+ int currentTile,
Integer index,
+ ArrayList generatedBoards,
boolean isRow) {
- ArrayList b = new ArrayList<>();
+ // Base Case: Enough tents have been placed
if (target == current) {
- TreeTentBoard temp = iBoard.copy();
- for (TreeTentCell c : original) {
- if (selected.contains(c)) {
- PuzzleElement change = temp.getPuzzleElement(c);
- change.setData(TreeTentType.TENT);
- temp.addModifiedData(change);
- } else {
- PuzzleElement change = temp.getPuzzleElement(c);
- change.setData(TreeTentType.GRASS);
- temp.addModifiedData(change);
+ TreeTentBoard boardCopy = iBoard.copy();
+ // Selected Tiles should already be filled
+ // Fill in other tiles with Grass
+ for (TreeTentCell tile : tiles) {
+ if (!selected.contains(tile)) {
+ PuzzleElement element = boardCopy.getPuzzleElement(tile);
+ element.setData(TreeTentType.GRASS);
+ boardCopy.addModifiedData(element);
}
}
- if (goodBoard(temp, index, isRow)) {
- b.add(temp);
- }
- return b;
+ // board validity is checked after placing every tent
+ // because the base case doesn't place any tents, the board
+ // should still be valid
+ generatedBoards.add(boardCopy);
+ return;
}
- for (int i = 0; i < tiles.size(); ++i) {
- List sub = tiles.subList(i + 1, tiles.size());
- List next = new ArrayList(selected);
- next.add(tiles.get(i));
- b.addAll(
- genCombRecursive(
- iBoard, original, sub, target, current + 1, next, index, isRow));
+
+ // Recursive Case:
+ // Looking at the group of possible tiles, save one of the tiles into selected,
+ // Place it on the board,
+ // Check if the board is good and recurse
+ //
+ // Backtracking:
+ // Remove the placed tent from the board and selected
+ for (int i = currentTile; i < tiles.size(); ++i) {
+ TreeTentCell tile = tiles.get(i);
+ selected.add(tile);
+ PuzzleElement element = iBoard.getPuzzleElement(tile);
+ element.setData(TreeTentType.TENT);
+ iBoard.addModifiedData(element);
+ if (goodBoard(iBoard, index, isRow)) {
+ genCombRecursive(
+ iBoard,
+ tiles,
+ target,
+ current + 1,
+ selected,
+ i + 1,
+ index,
+ generatedBoards,
+ isRow);
+ }
+ element.setData(TreeTentType.UNKNOWN);
+ iBoard.addModifiedData(element);
+ selected.remove(tile);
}
- return b;
}
// Effectively runs TouchingTents check on all the added tents to make sure that the proposed
@@ -153,7 +206,7 @@ private boolean goodBoard(TreeTentBoard board, Integer index, boolean isRow) {
for (TreeTentCell t : tents) {
List adj = board.getAdjacent(t, TreeTentType.TENT);
List diag = board.getDiagonals(t, TreeTentType.TENT);
- if (adj.size() > 0 || diag.size() > 0) {
+ if (!adj.isEmpty() || !diag.isEmpty()) {
return false;
}
}
diff --git a/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java b/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java
index 956f83ba4..645a2c0d7 100644
--- a/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java
+++ b/src/main/java/edu/rpi/legup/ui/ProofEditorPanel.java
@@ -581,30 +581,30 @@ private void saveProofAs() {
return;
}
- fileDialog.setMode(FileDialog.SAVE);
- fileDialog.setTitle("Save As");
- String curFileName = GameBoardFacade.getInstance().getCurFileName();
- if (curFileName == null) {
- fileDialog.setDirectory(
- LegupPreferences.getInstance().getUserPref(LegupPreferences.WORK_DIRECTORY));
- } else {
- File curFile = new File(curFileName);
- fileDialog.setDirectory(curFile.getParent());
+ LegupPreferences preferences = LegupPreferences.getInstance();
+ File preferredDirectory =
+ new File(preferences.getUserPref(LegupPreferences.WORK_DIRECTORY));
+ if (preferences.getSavedPath() != "") {
+ preferredDirectory = new File(preferences.getSavedPath());
}
- fileDialog.setVisible(true);
+ folderBrowser = new JFileChooser(preferredDirectory);
- String fileName = null;
- if (fileDialog.getDirectory() != null && fileDialog.getFile() != null) {
- fileName = fileDialog.getDirectory() + File.separator + fileDialog.getFile();
- }
+ folderBrowser.showSaveDialog(this);
+ folderBrowser.setVisible(true);
+ folderBrowser.setCurrentDirectory(new File(LegupPreferences.WORK_DIRECTORY));
+ folderBrowser.setDialogTitle("Select Directory");
+ folderBrowser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ folderBrowser.setAcceptAllFileFilterUsed(false);
- if (fileName != null) {
+ String path = folderBrowser.getSelectedFile().getAbsolutePath();
+
+ if (path != null) {
try {
PuzzleExporter exporter = puzzle.getExporter();
if (exporter == null) {
throw new ExportFileException("Puzzle exporter null");
}
- exporter.exportPuzzle(fileName);
+ exporter.exportPuzzle(path);
} catch (ExportFileException e) {
e.printStackTrace();
}
diff --git a/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java b/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java
index b3cd30ffb..f50c8d6fc 100644
--- a/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java
+++ b/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java
@@ -35,6 +35,7 @@ public class PuzzleEditorPanel extends LegupPanel implements IHistoryListener {
private JMenuItem helpLegup, aboutLegup;
private JMenuBar menuBar;
private JToolBar toolBar;
+ private JFileChooser folderBrowser;
private JFrame frame;
private JButton[] buttons;
JSplitPane splitPanel;
@@ -380,25 +381,37 @@ public void loadPuzzleFromHome(String game, String[] statements) {
public Object[] promptPuzzle() {
GameBoardFacade facade = GameBoardFacade.getInstance();
if (facade.getBoard() != null) {
- if (noQuit("Opening a new puzzle to edit?")) {
+ if (noQuit("Opening a new puzzle?")) {
return new Object[0];
}
}
- if (fileDialog == null) {
- fileDialog = new FileDialog(this.frame);
- }
+
LegupPreferences preferences = LegupPreferences.getInstance();
String preferredDirectory = preferences.getUserPref(LegupPreferences.WORK_DIRECTORY);
+ if (preferences.getSavedPath() != "") {
+ preferredDirectory = preferences.getSavedPath();
+ }
- fileDialog.setMode(FileDialog.LOAD);
- fileDialog.setTitle("Select Puzzle");
- fileDialog.setDirectory(preferredDirectory);
- fileDialog.setVisible(true);
+ File preferredDirectoryFile = new File(preferredDirectory);
+ JFileChooser fileBrowser = new JFileChooser(preferredDirectoryFile);
String fileName = null;
File puzzleFile = null;
- if (fileDialog.getDirectory() != null && fileDialog.getFile() != null) {
- fileName = fileDialog.getDirectory() + File.separator + fileDialog.getFile();
- puzzleFile = new File(fileName);
+
+ fileBrowser.showOpenDialog(this);
+ fileBrowser.setVisible(true);
+ fileBrowser.setCurrentDirectory(new File(preferredDirectory));
+ fileBrowser.setDialogTitle("Select Proof File");
+ fileBrowser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ fileBrowser.setAcceptAllFileFilterUsed(false);
+
+ File puzzlePath = fileBrowser.getSelectedFile();
+ System.out.println(puzzlePath.getAbsolutePath());
+
+ if (puzzlePath != null) {
+ fileName = puzzlePath.getAbsolutePath();
+ String lastDirectoryPath = fileName.substring(0, fileName.lastIndexOf(File.separator));
+ preferences.setSavedPath(lastDirectoryPath);
+ puzzleFile = puzzlePath;
} else {
// The attempt to prompt a puzzle ended gracefully (cancel)
return null;
@@ -538,39 +551,35 @@ private String savePuzzle() {
}
}
- if (fileDialog == null) {
- fileDialog = new FileDialog(this.frame);
+ LegupPreferences preferences = LegupPreferences.getInstance();
+ File preferredDirectory =
+ new File(preferences.getUserPref(LegupPreferences.WORK_DIRECTORY));
+ if (preferences.getSavedPath() != "") {
+ preferredDirectory = new File(preferences.getSavedPath());
}
+ folderBrowser = new JFileChooser(preferredDirectory);
- fileDialog.setMode(FileDialog.SAVE);
- fileDialog.setTitle("Save Proof");
- String curFileName = GameBoardFacade.getInstance().getCurFileName();
- if (curFileName == null) {
- fileDialog.setDirectory(
- LegupPreferences.getInstance().getUserPref(LegupPreferences.WORK_DIRECTORY));
- } else {
- File curFile = new File(curFileName);
- fileDialog.setDirectory(curFile.getParent());
- }
- fileDialog.setVisible(true);
+ folderBrowser.showSaveDialog(this);
+ folderBrowser.setVisible(true);
+ folderBrowser.setCurrentDirectory(new File(LegupPreferences.WORK_DIRECTORY));
+ folderBrowser.setDialogTitle("Select Directory");
+ folderBrowser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ folderBrowser.setAcceptAllFileFilterUsed(false);
- String fileName = null;
- if (fileDialog.getDirectory() != null && fileDialog.getFile() != null) {
- fileName = fileDialog.getDirectory() + File.separator + fileDialog.getFile();
- }
+ String path = folderBrowser.getSelectedFile().getAbsolutePath();
- if (fileName != null) {
+ if (path != null) {
try {
PuzzleExporter exporter = puzzle.getExporter();
if (exporter == null) {
throw new ExportFileException("Puzzle exporter null");
}
- exporter.exportPuzzle(fileName);
+ exporter.exportPuzzle(path);
} catch (ExportFileException e) {
e.printStackTrace();
}
}
- return fileName;
+ return path;
}
public DynamicView getDynamicBoardView() {
diff --git a/src/main/java/edu/rpi/legup/utility/LegupUtils.java b/src/main/java/edu/rpi/legup/utility/LegupUtils.java
index 94f119a5e..ce21a8d9a 100644
--- a/src/main/java/edu/rpi/legup/utility/LegupUtils.java
+++ b/src/main/java/edu/rpi/legup/utility/LegupUtils.java
@@ -94,9 +94,13 @@ private static List findClassesZip(String path, String packageName)
&& entry.getName().endsWith(".class")
&& entry.getName().startsWith(packageName)) {
String className = entry.getName().replace('/', '.');
- classes.add(
- Class.forName(
- className.substring(0, className.length() - ".class".length())));
+ String substr = className.substring(0, className.length() - ".class".length());
+ try {
+ Class> c = Class.forName(substr);
+ classes.add(c);
+ } catch (LinkageError | ClassNotFoundException e) {
+ System.out.println("Failed on " + substr);
+ }
}
}
return classes;
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png
new file mode 100644
index 000000000..a74654d43
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/CompleteRowColumnDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png
new file mode 100644
index 000000000..214aa5348
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/DuplicateRowOrColumnContradictionRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png
new file mode 100644
index 000000000..73072f2ce
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/OneOrZeroCaseRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png
new file mode 100644
index 000000000..b68f67e44
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/OneTileGapDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png
new file mode 100644
index 000000000..67a4e47f3
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/SurroundPairDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png
new file mode 100644
index 000000000..862408b63
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/ThreeAdjacentContradictionRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png b/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png
new file mode 100644
index 000000000..029bd12ac
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/binary/rules/UnbalancedRowColumnContradictionRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/cases/BombOrFilled.jpg b/src/main/resources/edu/rpi/legup/images/minesweeper/cases/BombOrFilled.jpg
new file mode 100644
index 000000000..ce7c7418a
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/cases/BombOrFilled.jpg differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/cases/SatisfyFlag.jpg b/src/main/resources/edu/rpi/legup/images/minesweeper/cases/SatisfyFlag.jpg
new file mode 100644
index 000000000..ae575bd8c
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/cases/SatisfyFlag.jpg differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/cases/Satisfy_Flag.png b/src/main/resources/edu/rpi/legup/images/minesweeper/cases/Satisfy_Flag.png
new file mode 100644
index 000000000..7272bbdb6
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/cases/Satisfy_Flag.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/contradictions/Bomb_Surplus.jpg b/src/main/resources/edu/rpi/legup/images/minesweeper/contradictions/Bomb_Surplus.jpg
new file mode 100644
index 000000000..5e8c36662
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/contradictions/Bomb_Surplus.jpg differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/direct/Fill_Bombs.jpg b/src/main/resources/edu/rpi/legup/images/minesweeper/direct/Fill_Bombs.jpg
new file mode 100644
index 000000000..8214972ac
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/direct/Fill_Bombs.jpg differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/tiles/Bomb.png b/src/main/resources/edu/rpi/legup/images/minesweeper/tiles/Bomb.png
new file mode 100644
index 000000000..dd6bad509
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/tiles/Bomb.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/minesweeper/tiles/Empty.png b/src/main/resources/edu/rpi/legup/images/minesweeper/tiles/Empty.png
new file mode 100644
index 000000000..c553ce15a
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/minesweeper/tiles/Empty.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/UnknownTile.png b/src/main/resources/edu/rpi/legup/images/starbattle/UnknownTile.png
new file mode 100644
index 000000000..850fbf127
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/UnknownTile.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/black.gif b/src/main/resources/edu/rpi/legup/images/starbattle/black.gif
new file mode 100644
index 000000000..13381a717
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/black.gif differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png
new file mode 100644
index 000000000..0383711bb
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/cases/StarOrEmptyCaseRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png
new file mode 100644
index 000000000..d25340b40
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/ClashingOrbitContradictionRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png
new file mode 100644
index 000000000..cc019752f
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooFewStarsContradictionRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png
new file mode 100644
index 000000000..b468ae6f6
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/contradictions/TooManyStarsContradictionRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/empty.gif b/src/main/resources/edu/rpi/legup/images/starbattle/empty.gif
new file mode 100644
index 000000000..38b91d0a2
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/empty.gif differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png
new file mode 100644
index 000000000..f72fd7d7f
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/BlackOutDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png
new file mode 100644
index 000000000..1a88e9d07
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRegionsDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png
new file mode 100644
index 000000000..24fb0d48a
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/ColumnsWithinRowsDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png
new file mode 100644
index 000000000..11cfbf899
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/FinishWithStarDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png
new file mode 100644
index 000000000..fde683dcd
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinColumnsDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png
new file mode 100644
index 000000000..30170df40
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RegionsWithinRowsDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png
new file mode 100644
index 000000000..bac64a87c
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinColumnsDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png
new file mode 100644
index 000000000..8907e0475
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/RowsWithinRegionsDirectRule.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/rules/SurroundStar.png b/src/main/resources/edu/rpi/legup/images/starbattle/rules/SurroundStar.png
new file mode 100644
index 000000000..13287f779
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/rules/SurroundStar.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/star.gif b/src/main/resources/edu/rpi/legup/images/starbattle/star.gif
new file mode 100644
index 000000000..e289b287a
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/star.gif differ
diff --git a/src/main/resources/edu/rpi/legup/images/starbattle/white.gif b/src/main/resources/edu/rpi/legup/images/starbattle/white.gif
new file mode 100644
index 000000000..fc2c683eb
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/starbattle/white.gif differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Element Template.png b/src/main/resources/edu/rpi/legup/images/thermometer/Element Template.png
new file mode 100644
index 000000000..52e3e36b7
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Element Template.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockE.png
new file mode 100644
index 000000000..238a301af
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockN.png
new file mode 100644
index 000000000..8f343a3ba
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockS.png
new file mode 100644
index 000000000..f95536076
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockW.png
new file mode 100644
index 000000000..b1bbd31be
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadBlockW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpE.png
new file mode 100644
index 000000000..852043171
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpN.png
new file mode 100644
index 000000000..78179a633
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpS.png
new file mode 100644
index 000000000..463b4308c
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpW.png
new file mode 100644
index 000000000..19240e5fc
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadEmpW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillE.png
new file mode 100644
index 000000000..d577c25f5
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillN.png
new file mode 100644
index 000000000..9905da3aa
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillS.png
new file mode 100644
index 000000000..2e6c6d75d
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillW.png
new file mode 100644
index 000000000..94ad0b2b4
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/HeadFillW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockE.png
new file mode 100644
index 000000000..d351674c5
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockN.png
new file mode 100644
index 000000000..505aa6ec9
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockS.png
new file mode 100644
index 000000000..91356f95e
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockW.png
new file mode 100644
index 000000000..6013fca19
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftBlockW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpE.png
new file mode 100644
index 000000000..b038ea65f
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpN.png
new file mode 100644
index 000000000..c59c1a930
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpS.png
new file mode 100644
index 000000000..f70a25cf1
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpW.png
new file mode 100644
index 000000000..48cde1b37
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftEmpW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillE.png
new file mode 100644
index 000000000..06a507b74
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillN.png
new file mode 100644
index 000000000..26c177a91
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillS.png
new file mode 100644
index 000000000..046b96e26
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillW.png
new file mode 100644
index 000000000..3688bbe0a
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/ShaftFillW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockE.png
new file mode 100644
index 000000000..d78a9723a
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockN.png
new file mode 100644
index 000000000..eca516d89
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockS.png
new file mode 100644
index 000000000..a207de1c4
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockW.png
new file mode 100644
index 000000000..f48ff2b28
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipBlockW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpE.png
new file mode 100644
index 000000000..660506aa7
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpN.png
new file mode 100644
index 000000000..ae3ca519d
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpS.png
new file mode 100644
index 000000000..62f60dfb4
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpW.png
new file mode 100644
index 000000000..d67801d76
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipEmpW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillE.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillE.png
new file mode 100644
index 000000000..1f24029e5
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillE.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillN.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillN.png
new file mode 100644
index 000000000..e7abdb0bb
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillN.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillS.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillS.png
new file mode 100644
index 000000000..bfb506b99
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillS.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillW.png b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillW.png
new file mode 100644
index 000000000..e931a7555
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/Elements/TipFillW.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/FinishWithBlocked.png b/src/main/resources/edu/rpi/legup/images/thermometer/FinishWithBlocked.png
new file mode 100644
index 000000000..0a36aa3ac
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/FinishWithBlocked.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/FinishWithMercury.png b/src/main/resources/edu/rpi/legup/images/thermometer/FinishWithMercury.png
new file mode 100644
index 000000000..6693e2d7b
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/FinishWithMercury.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/MercOrBlocked.png b/src/main/resources/edu/rpi/legup/images/thermometer/MercOrBlocked.png
new file mode 100644
index 000000000..66c994de0
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/MercOrBlocked.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/MercuryInBody.png b/src/main/resources/edu/rpi/legup/images/thermometer/MercuryInBody.png
new file mode 100644
index 000000000..06cb87e42
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/MercuryInBody.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/MinimumFill.png b/src/main/resources/edu/rpi/legup/images/thermometer/MinimumFill.png
new file mode 100644
index 000000000..28719cf53
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/MinimumFill.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/NotEnoughMercury.png b/src/main/resources/edu/rpi/legup/images/thermometer/NotEnoughMercury.png
new file mode 100644
index 000000000..d2277e8ef
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/NotEnoughMercury.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/PriorIsFilled.png b/src/main/resources/edu/rpi/legup/images/thermometer/PriorIsFilled.png
new file mode 100644
index 000000000..9bda3a60b
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/PriorIsFilled.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/RestIsEmpty.png b/src/main/resources/edu/rpi/legup/images/thermometer/RestIsEmpty.png
new file mode 100644
index 000000000..91374e062
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/RestIsEmpty.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/SatisfyMercury.png b/src/main/resources/edu/rpi/legup/images/thermometer/SatisfyMercury.png
new file mode 100644
index 000000000..f450a205e
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/SatisfyMercury.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/ThermometerTooLarge.png b/src/main/resources/edu/rpi/legup/images/thermometer/ThermometerTooLarge.png
new file mode 100644
index 000000000..eb61b4f1b
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/ThermometerTooLarge.png differ
diff --git a/src/main/resources/edu/rpi/legup/images/thermometer/TooManyMercury.png b/src/main/resources/edu/rpi/legup/images/thermometer/TooManyMercury.png
new file mode 100644
index 000000000..99a215b12
Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/thermometer/TooManyMercury.png differ
diff --git a/src/main/resources/edu/rpi/legup/legup/config b/src/main/resources/edu/rpi/legup/legup/config
index ccd4f5be3..1ee9ed79c 100644
--- a/src/main/resources/edu/rpi/legup/legup/config
+++ b/src/main/resources/edu/rpi/legup/legup/config
@@ -4,6 +4,10 @@
qualifiedClassName="edu.rpi.legup.puzzle.battleship.Battleship"
fileType=".xml"
fileCreationDisabled="true"/>
+
+
-
+
+
+
diff --git a/src/test/java/legup/TestRunner.java b/src/test/java/legup/TestRunner.java
index 8661f0e69..5486a0353 100644
--- a/src/test/java/legup/TestRunner.java
+++ b/src/test/java/legup/TestRunner.java
@@ -3,10 +3,11 @@
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
-import puzzles.battleship.rules.*;
+import puzzles.battleship.rules.AdjacentShipsContradictionRuleTest;
+import puzzles.battleship.rules.FinishWithShipsDirectRuleTests;
import puzzles.lightup.rules.*;
+import puzzles.minesweeper.MinesweeperUtilitiesTest;
import puzzles.nurikabe.rules.*;
-import puzzles.skyscrapers.rules.*;
import puzzles.treetent.rules.*;
/** This class runs all of the tests for the project without needing to run build scripts. */
@@ -93,6 +94,10 @@ public static void main(String[] args) {
printTestResults(result36);
Result result37 = JUnitCore.runClasses(TentOrGrassCaseRuleTest.class);
printTestResults(result37);
+
+ // Minesweeper
+ Result result38 = JUnitCore.runClasses(MinesweeperUtilitiesTest.class);
+ printTestResults(result38);
}
private static void printTestResults(Result result) {
diff --git a/src/test/java/puzzles/minesweeper/MinesweeperUtilitiesTest.java b/src/test/java/puzzles/minesweeper/MinesweeperUtilitiesTest.java
new file mode 100644
index 000000000..0e0112040
--- /dev/null
+++ b/src/test/java/puzzles/minesweeper/MinesweeperUtilitiesTest.java
@@ -0,0 +1,69 @@
+package puzzles.minesweeper;
+
+import edu.rpi.legup.puzzle.minesweeper.Minesweeper;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperBoard;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperCell;
+import edu.rpi.legup.puzzle.minesweeper.MinesweeperUtilities;
+import edu.rpi.legup.save.InvalidFileFormatException;
+import java.util.stream.Stream;
+import legup.MockGameBoardFacade;
+import legup.TestUtilities;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class MinesweeperUtilitiesTest {
+
+ private static Minesweeper minesweeper;
+
+ @BeforeClass
+ public static void setUp() {
+ MockGameBoardFacade.getInstance();
+ minesweeper = new Minesweeper();
+ }
+
+ @Test
+ public void getSurroundingCellsSizeThreeByThreeAtOneXOneTest()
+ throws InvalidFileFormatException {
+
+ TestUtilities.importTestBoard("puzzles/minesweeper/utilities/3x3test", minesweeper);
+
+ final MinesweeperBoard board = (MinesweeperBoard) minesweeper.getCurrentBoard();
+ MinesweeperCell cell = board.getCell(1, 1);
+
+ final Stream