diff --git a/bin/main/edu/rpi/legup/legup/config b/bin/main/edu/rpi/legup/legup/config deleted file mode 100644 index ccd4f5be3..000000000 --- a/bin/main/edu/rpi/legup/legup/config +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - diff --git a/puzzles files/binary/6x6 easy/089764562 b/puzzles files/binary/6x6 easy/089764562 new file mode 100644 index 000000000..7b22ffc10 --- /dev/null +++ b/puzzles files/binary/6x6 easy/089764562 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 easy/128903434 b/puzzles files/binary/6x6 easy/128903434 new file mode 100644 index 000000000..ea8ef93b0 --- /dev/null +++ b/puzzles files/binary/6x6 easy/128903434 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 easy/876868768 b/puzzles files/binary/6x6 easy/876868768 new file mode 100644 index 000000000..0f0ff745e --- /dev/null +++ b/puzzles files/binary/6x6 easy/876868768 @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/binary/6x6 easy/927364891 b/puzzles files/binary/6x6 easy/927364891 new file mode 100644 index 000000000..da76d067b --- /dev/null +++ b/puzzles files/binary/6x6 easy/927364891 @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/minesweeper/5x5 Minesweeper Easy/123456 b/puzzles files/minesweeper/5x5 Minesweeper Easy/123456 new file mode 100644 index 000000000..2aa0b46ab --- /dev/null +++ b/puzzles files/minesweeper/5x5 Minesweeper Easy/123456 @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/5x5 Star Battle 1 star Normal/5x5 Star Battle 1 star Normal 1.xml b/puzzles files/starbattle/5x5 Star Battle 1 star Normal/5x5 Star Battle 1 star Normal 1.xml new file mode 100644 index 000000000..b86e16ff2 --- /dev/null +++ b/puzzles files/starbattle/5x5 Star Battle 1 star Normal/5x5 Star Battle 1 star Normal 1.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal1.xml b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal1.xml new file mode 100644 index 000000000..110dd9abe --- /dev/null +++ b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal1.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal2.xml b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal2.xml new file mode 100644 index 000000000..49efeba59 --- /dev/null +++ b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 Star Battle 1star Normal2.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 StarBattle 1star Normal3.xml b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 StarBattle 1star Normal3.xml new file mode 100644 index 000000000..855943612 --- /dev/null +++ b/puzzles files/starbattle/6x6 Star Battle 1 star Normal/6x6 StarBattle 1star Normal3.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Hard/7x7 Star Battle 1star Hard1.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Hard/7x7 Star Battle 1star Hard1.xml new file mode 100644 index 000000000..c1d7770f6 --- /dev/null +++ b/puzzles files/starbattle/7x7 Star Battle 1 star Hard/7x7 Star Battle 1star Hard1.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal.xml new file mode 100644 index 000000000..cab0a0a5e --- /dev/null +++ b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal1.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal1.xml new file mode 100644 index 000000000..70b81e376 --- /dev/null +++ b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal1.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal2.xml b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal2.xml new file mode 100644 index 000000000..c541ece06 --- /dev/null +++ b/puzzles files/starbattle/7x7 Star Battle 1 star Normal/7x7 Star Battle 1star Normal2.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal1.xml b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal1.xml new file mode 100644 index 000000000..02dd5d6c0 --- /dev/null +++ b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal1.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal2.xml b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal2.xml new file mode 100644 index 000000000..0df84ef62 --- /dev/null +++ b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal2.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal3.xml b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal3.xml new file mode 100644 index 000000000..725c91d7f --- /dev/null +++ b/puzzles files/starbattle/8x8 Star Battle 1 star Normal/8x8 Star Battle 1star Normal3.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/puzzles files/thermometer/therm_test.xml b/puzzles files/thermometer/therm_test.xml new file mode 100644 index 000000000..66e841dc5 --- /dev/null +++ b/puzzles files/thermometer/therm_test.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/treetent/8x8 TreeTent Easy/1646651 b/puzzles files/treetent/8x8 TreeTent Easy/1646651 index 36dbc7f1e..db70ca164 100644 --- a/puzzles files/treetent/8x8 TreeTent Easy/1646651 +++ b/puzzles files/treetent/8x8 TreeTent Easy/1646651 @@ -1,5 +1,6 @@ - + + @@ -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 cells = MinesweeperUtilities.getSurroundingCells(board, cell); + + final long count = cells.count(); + Assert.assertEquals(count, 8); + } + + @Test + public void getSurroundingCellsSizeThreeByThreeAtZeroXZeroTest() + throws InvalidFileFormatException { + + TestUtilities.importTestBoard("puzzles/minesweeper/utilities/3x3test", minesweeper); + + final MinesweeperBoard board = (MinesweeperBoard) minesweeper.getCurrentBoard(); + MinesweeperCell cell = board.getCell(0, 0); + + final Stream cells = MinesweeperUtilities.getSurroundingCells(board, cell); + + final long count = cells.count(); + Assert.assertEquals(count, 3); + } + + @Test + public void getSurroundingCellsSizeThreeByThreeAtZeroXOneTest() + throws InvalidFileFormatException { + + TestUtilities.importTestBoard("puzzles/minesweeper/utilities/3x3test", minesweeper); + + final MinesweeperBoard board = (MinesweeperBoard) minesweeper.getCurrentBoard(); + MinesweeperCell cell = board.getCell(0, 1); + + final Stream cells = MinesweeperUtilities.getSurroundingCells(board, cell); + + final long count = cells.count(); + Assert.assertEquals(count, 5); + } +} diff --git a/src/test/java/puzzles/starbattle/rules/BlackoutDirectRuleTest.java b/src/test/java/puzzles/starbattle/rules/BlackoutDirectRuleTest.java new file mode 100644 index 000000000..59d5b37af --- /dev/null +++ b/src/test/java/puzzles/starbattle/rules/BlackoutDirectRuleTest.java @@ -0,0 +1,76 @@ +// This test is for a puzzle that is not fully implemented yet and is causing issues. +// Commenting this out for now, but once Star Battle is fully implemented this should +// be uncommented and finished. + +// package puzzles.starbattle.rules; +// +// import edu.rpi.legup.puzzle.nurikabe.Nurikabe; +// import edu.rpi.legup.puzzle.nurikabe.NurikabeBoard; +// import edu.rpi.legup.puzzle.nurikabe.NurikabeCell; +// import edu.rpi.legup.puzzle.nurikabe.NurikabeType; +// import legup.MockGameBoardFacade; +// import legup.TestUtilities; +// import edu.rpi.legup.model.tree.TreeNode; +// import edu.rpi.legup.model.tree.TreeTransition; +// import org.junit.Assert; +// import org.junit.BeforeClass; +// import org.junit.Test; +// +// import edu.rpi.legup.puzzle.starbattle.StarBattle; +// 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.rules.BlackoutDirectRule; +// import edu.rpi.legup.save.InvalidFileFormatException; +// +// import java.awt.*; +// +// public class BlackoutDirectRuleTest { +// +// private static final BlackoutDirectRule RULE = new BlackoutDirectRule(); +// private static StarBattle starbattle; +// +// @BeforeClass +// public static void setUp() { +// MockGameBoardFacade.getInstance(); +// starbattle = new StarBattle(); +// } +// +// @Test +// public void BlackoutDirectRuleTest_ColumnBlackout() throws InvalidFileFormatException { +// +// TestUtilities.importTestBoard("puzzles/starbattle/rules/BlackoutDirectRule/ColumnBlackout", +// starbattle); +// TreeNode rootNode = starbattle.getTree().getRootNode(); +// TreeTransition transition = rootNode.getChildren().get(0); +// transition.setRule(RULE); +// +// StarBattleBoard board = (StarBattleBoard) transition.getBoard(); +// +// StarBattleCell cell1 = board.getCell(1, 1); +// cell1.setData(StarBattleCellType.BLACK.value); +// StarBattleCell cell2 = board.getCell(1, 2); +// cell2.setData(StarBattleCellType.BLACK.value); +// StarBattleCell cell3 = board.getCell(1, 3); +// cell3.setData(StarBattleCellType.BLACK.value); +// +// board.addModifiedData(cell1); +// board.addModifiedData(cell2); +// board.addModifiedData(cell3); +// +// Assert.assertNull(RULE.checkRule(transition)); +// +// for (int i = 0; i < board.getHeight(); i++) { +// for (int k = 0; k < board.getWidth(); k++) { +// Point point = new Point(k, i); +// if (point.equals(cell1.getLocation()) || point.equals(cell2.getLocation()) || +// point.equals(cell3.getLocation())) { +// Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(k, i))); +// } +// else { +// Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(k, i))); +// } +// } +// } +// } +// } diff --git a/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java index 38284c410..c704d59b7 100644 --- a/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java @@ -26,9 +26,14 @@ public static void setUp() { treetent = new TreeTent(); } - // creates a 3x3 puzzle with no trees - // make the (1,1) tile GRASS - // checks if tiles logically follow the EmptyFieldDirectRule + /** + * 3x3 TreeTent puzzle Tests EmptyFieldDirectRule + * + *

Empty XXX XGX XXX + * + *

Makes the (1, 1) tile GRASS Checks if the rule correctly detects no trees around the grass + * tile + */ @Test public void EmptyFieldTest() throws InvalidFileFormatException { TestUtilities.importTestBoard( @@ -64,10 +69,14 @@ public void EmptyFieldTest() throws InvalidFileFormatException { } } - // creates a 3x3 puzzle with 4 trees - // trees are at (0,0), (2,0), (0,2), and (2,2) - // make the (1,1) tile GRASS. - // checks if tiles logically follow the EmptyFieldDirectRule + /** + * 3x3 TreeTent puzzle Tests EmptyFieldDirectRule + * + *

Trees are at (0, 0), (2, 0), (0, 2), and (2, 2) RXR XGX RXR + * + *

Makes the (1, 1) tile GRASS Checks if the rule correctly ignores the trees on the + * diagonals + */ @Test public void DiagonalTreeTest() throws InvalidFileFormatException { TestUtilities.importTestBoard( @@ -103,10 +112,13 @@ public void DiagonalTreeTest() throws InvalidFileFormatException { } } - // creates a 3x3 puzzle with 4 trees - // trees are at (0,1), (1,0), (1,2), and (2,1) - // make the (1,1) tile GRASS. - // checks if tiles don't logically follow the EmptyFieldDirectRule + /** + * 3x3 TreeTent puzzle Tests EmptyFieldDirectRule + * + *

Trees are at (0, 1), (1, 0), (1, 2), and (2, 1) XRX RGR XRX + * + *

Makes the (1, 1) tile GRASS Checks if the rule is not valid when there are adjacent trees + */ @Test public void EmptyFieldTestFail() throws InvalidFileFormatException { TestUtilities.importTestBoard( @@ -137,10 +149,14 @@ public void EmptyFieldTestFail() throws InvalidFileFormatException { } } - // creates a 3x3 puzzle with 1 tree - // tree is at (1,0) - // make the (1,1) tile GRASS. - // checks if tiles don't logically follow the EmptyFieldDirectRule + /** + * 3x3 TreeTent puzzle Tests EmptyFieldDirectRule + * + *

Tree at (1, 0) XRX XGX XXX + * + *

Makes the (1, 1) tile GRASS Checks if the rule is not valid when there is one adjacent + * tree + */ @Test public void EmptyFieldTestFailTop() throws InvalidFileFormatException { TestUtilities.importTestBoard( @@ -170,106 +186,4 @@ public void EmptyFieldTestFailTop() throws InvalidFileFormatException { } } } - - // creates a 3x3 puzzle with 1 tree - // tree is at (1,2) - // make the (1,1) tile GRASS. - // checks if tiles don't logically follow the EmptyFieldDirectRule - @Test - public void EmptyFieldTestFailTopBottom() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom", treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - // get board state - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - // change the board's cells considering breaking the EmptyField rule - TreeTentCell cell1 = board.getCell(1, 1); - cell1.setData(TreeTentType.GRASS); - board.addModifiedData(cell1); - - // confirm there is not a logical following of the EmptyField rule - Assert.assertNotNull(RULE.checkRule(transition)); - - // the cells should not follow the rule - TreeTentCell c; - for (int i = 0; i < board.getWidth(); i++) { - for (int j = 0; j < board.getHeight(); j++) { - c = board.getCell(j, i); - // does not use the rule to logically follow - Assert.assertNotNull(RULE.checkRuleAt(transition, c)); - } - } - } - - // creates a 3x3 puzzle with 1 tree - // tree is at (0,1) - // make the (1,1) tile GRASS. - // checks if tiles don't logically follow the EmptyFieldDirectRule - @Test - public void EmptyFieldTestFailLeft() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft", treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - // get board state - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - // change the board's cells considering breaking the EmptyField rule - TreeTentCell cell1 = board.getCell(1, 1); - cell1.setData(TreeTentType.GRASS); - board.addModifiedData(cell1); - - // confirm there is not a logical following of the EmptyField rule - Assert.assertNotNull(RULE.checkRule(transition)); - - // the cells should not follow the rule - TreeTentCell c; - for (int i = 0; i < board.getWidth(); i++) { - for (int j = 0; j < board.getHeight(); j++) { - c = board.getCell(j, i); - // does not use the rule to logically follow - Assert.assertNotNull(RULE.checkRuleAt(transition, c)); - } - } - } - - // creates a 3x3 puzzle with 1 tree - // tree is at (2,1) - // make the (1,1) tile GRASS. - // checks if tiles don't logically follow the EmptyFieldDirectRule - @Test - public void EmptyFieldTestFailRight() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight", treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - // get board state - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - // change the board's cells considering breaking the EmptyField rule - TreeTentCell cell1 = board.getCell(1, 1); - cell1.setData(TreeTentType.GRASS); - board.addModifiedData(cell1); - - // confirm there is not a logical following of the EmptyField rule - Assert.assertNotNull(RULE.checkRule(transition)); - - // the cells should not follow the rule - TreeTentCell c; - for (int i = 0; i < board.getWidth(); i++) { - for (int j = 0; j < board.getHeight(); j++) { - c = board.getCell(j, i); - // does not use the rule to logically follow - Assert.assertNotNull(RULE.checkRuleAt(transition, c)); - } - } - } } diff --git a/src/test/java/puzzles/treetent/rules/FillinRowCaseRuleTest.java b/src/test/java/puzzles/treetent/rules/FillinRowCaseRuleTest.java new file mode 100644 index 000000000..3b8389407 --- /dev/null +++ b/src/test/java/puzzles/treetent/rules/FillinRowCaseRuleTest.java @@ -0,0 +1,455 @@ +package puzzles.treetent.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.treetent.*; +import edu.rpi.legup.puzzle.treetent.rules.FillinRowCaseRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import java.util.ArrayList; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class FillinRowCaseRuleTest { + private static final FillinRowCaseRule RULE = new FillinRowCaseRule(); + private static TreeTent treetent; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + treetent = new TreeTent(); + } + + /** + * empty 3x3 TreeTent puzzle Tests FillinRowCaseRule on row with 3 UNKNOWN tiles and a clue of 0 + * tents in the row. + * + *

checks that 1 case is created and that it is equivalent to FinishWithGrass rule May need + * to change checks due to issue #777 + * + * @throws InvalidFileFormatException + */ + @Test + public void TentOrTreeTestZeroTentClue() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ZeroTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + /* Test the Row */ + TreeTentClue testing_row = board.getClue(3, 1); + ArrayList cases = RULE.getCases(board, testing_row); + + // assert that one case was found + Assert.assertEquals(1, cases.size()); + + // assert the case filled the row with grass + TreeTentBoard testCase = (TreeTentBoard) cases.getFirst(); + Assert.assertEquals(3, testCase.getRowCol(1, TreeTentType.GRASS, true).size()); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (h == 1) { + continue; + } + + original_cell = board.getCell(w, h); + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + + /* Test the Column */ + TreeTentClue testing_col = board.getClue(1, 3); + cases = RULE.getCases(board, testing_col); + + // assert one case was created + Assert.assertEquals(1, cases.size()); + + // assert the case filled the column with grass + testCase = (TreeTentBoard) cases.getFirst(); + Assert.assertEquals(3, testCase.getRowCol(1, TreeTentType.GRASS, false).size()); + + // checks other cells have not been modified + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (w == 1) { + continue; + } + + original_cell = board.getCell(w, h); + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests FillinRowCaseRule on row with 3 UNKNOWN tiles and a clue of 1 + * tent in the row. The column rules make the board impossible, but they are not checked here. + * + *

checks 3 cases are created checks; first case is TENT tile at x=0, second case is TENT + * tile at x=1, and a third case is TENT tile at x=2. The cases can be in any order. Then, it + * checks that other cells have not been modified + * + * @throws InvalidFileFormatException + */ + @Test + public void FillInRowEmptyOneTentClue() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3OneTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + /* Test the Row */ + TreeTentClue testing_row = board.getClue(3, 1); + ArrayList cases = RULE.getCases(board, testing_row); + + // assert correct number of cases created + Assert.assertEquals(3, cases.size()); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + // Each case must have 1 tent in the row + Assert.assertEquals(1, testCase.getRowCol(1, TreeTentType.TENT, true).size()); + + // and they must have 2 grass tiles in the row + Assert.assertEquals(2, testCase.getRowCol(1, TreeTentType.GRASS, true).size()); + } + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (h == 1) { + continue; + } + + original_cell = board.getCell(w, h); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + + /* Test the Column */ + TreeTentClue testing_col = board.getClue(1, 3); + cases = RULE.getCases(board, testing_col); + + // assert correct number of cases created + Assert.assertEquals(3, cases.size()); + // Only one arrangement is possible when taking into account the + // touching tents contradiction rule. + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + // Each case must have 1 tent in the column + Assert.assertEquals(1, testCase.getRowCol(1, TreeTentType.TENT, false).size()); + + // and they must have 2 grass tiles in the column + Assert.assertEquals(2, testCase.getRowCol(1, TreeTentType.GRASS, false).size()); + } + + // checks other cells have not been modified + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (w == 1) { + continue; + } + + original_cell = board.getCell(w, h); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests FillinRowCaseRule on row with 3 UNKNOWN tiles and a clue of 2 + * tent in the row. The column rules make the board impossible, but they are not checked here. + * + *

checks 1 case is created. Checks that the case is when there are TENT tiles at x=0 and + * x=2. Then, it checks that other cells have not been modified + * + * @throws InvalidFileFormatException + */ + @Test + public void FillInRowEmptyTwoTentClue() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3TwoTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + /* Test the Row */ + TreeTentClue testing_row = board.getClue(3, 1); + ArrayList cases = RULE.getCases(board, testing_row); + + // assert correct number of cases created + Assert.assertEquals(1, cases.size()); + // Only one arrangement is possible when taking into account the + // touching tents contradiction rule. + + TreeTentBoard testCase = (TreeTentBoard) cases.getFirst(); + + // The two side tiles are tents, + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(0, 1).getType()); + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(2, 1).getType()); + + // and the center tile is grass. + Assert.assertEquals(TreeTentType.GRASS, testCase.getCell(1, 1).getType()); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (h == 1) { + continue; + } + + original_cell = board.getCell(w, h); + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + + /* Test the Column */ + TreeTentClue testing_col = board.getClue(1, 3); + cases = RULE.getCases(board, testing_col); + + // assert correct number of cases created + Assert.assertEquals(1, cases.size()); + // Only one arrangement is possible when taking into account the + // touching tents contradiction rule. + + testCase = (TreeTentBoard) cases.getFirst(); + + // The two side tiles are tents, + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(1, 0).getType()); + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(1, 2).getType()); + + // and the center tile is grass. + Assert.assertEquals(TreeTentType.GRASS, testCase.getCell(1, 1).getType()); + + // checks other cells have not been modified + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (w == 1) { + continue; + } + + original_cell = board.getCell(w, h); + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests FillinRowCaseRule on row with 3 UNKNOWN tiles and a clue of 3 + * tent in the row. + * + *

checks that 0 cases are created + * + * @throws InvalidFileFormatException + */ + @Test + public void FillInRowEmptyThreeTentClue() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ThreeTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + /* Test the Row */ + TreeTentClue testing_row = board.getClue(3, 1); + ArrayList cases = RULE.getCases(board, testing_row); + + // assert there were no cases found, as filling in all tiles causes the tents to touch + Assert.assertNull(cases); + + /* Test the Column */ + TreeTentClue testing_col = board.getClue(1, 3); + cases = RULE.getCases(board, testing_row); + + Assert.assertNull(cases); + } + + /** + * empty 5x5 TreeTent puzzle Tests FillinRowCaseRule on row with 5 UNKNOWN tiles and a clue of 2 + * tents in the row. + * + *

checks that 6 cases are created and each case has the right number of tents + * + * @throws InvalidFileFormatException + */ + @Test + public void FillInRowEmpty5x5TwoTentClue() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/FillinRowCaseRule/EmptyRow5x5TwoTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + // Test the Row + TreeTentClue testing_row = board.getClue(5, 2); + ArrayList cases = RULE.getCases(board, testing_row); + + // assert correct number of cases created + Assert.assertEquals(6, cases.size()); + // Only one arrangement is possible when taking into account the + // touching tents contradiction rule. + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + // Each case must have 2 tens in the row + Assert.assertEquals(2, testCase.getRowCol(2, TreeTentType.TENT, true).size()); + + // and they must have 3 grass tiles in the row + Assert.assertEquals(3, testCase.getRowCol(2, TreeTentType.GRASS, true).size()); + } + + TreeTentCell original_cell; + TreeTentCell case_cell; + + // checks other cells have not been modified + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (h == 2) { + continue; + } + + original_cell = board.getCell(w, h); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + + // Test the Column + TreeTentClue testing_col = board.getClue(2, 5); + cases = RULE.getCases(board, testing_col); + + // assert correct number of cases created + Assert.assertEquals(6, cases.size()); + // Only one arrangement is possible when taking into account the + // touching tents contradiction rule. + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + // Each case must have 2 tents in the column + Assert.assertEquals(2, testCase.getRowCol(2, TreeTentType.TENT, false).size()); + + // and they must have 4 grass tiles in the column + Assert.assertEquals(3, testCase.getRowCol(2, TreeTentType.GRASS, false).size()); + } + + // checks other cells have not been modified + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + if (w == 2) { + continue; + } + + original_cell = board.getCell(w, h); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + } + + /** + * 7x3 TreeTent puzzle Tests FillinRowCaseRule on col with 3 UNKNOWN tiles separated by grass + * tiles and a clue of 3 tents in the col. + * + *

checks that 1 case is created and that all three UNKNOWN tiles have become tents + * + * @throws InvalidFileFormatException + */ + @Test + public void FillInRowPartialFillThreeTent() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/FillinRowCaseRule/PartialFillOneTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentClue testing_col = board.getClue(1, 7); + ArrayList cases = RULE.getCases(board, testing_col); + + // assert that one case was found + Assert.assertEquals(1, cases.size()); + + // assert the case has three tents in the column + TreeTentBoard testCase = (TreeTentBoard) cases.getFirst(); + Assert.assertEquals(3, testCase.getRowCol(1, TreeTentType.TENT, false).size()); + + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(1, 1).getType()); + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(1, 3).getType()); + Assert.assertEquals(TreeTentType.TENT, testCase.getCell(1, 5).getType()); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + if (w == 1) { + continue; + } + for (int h = 0; h < board.getHeight(); h++) { + + original_cell = board.getCell(w, h); + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } +} diff --git a/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java index 0783ab8b8..c89c96dd1 100644 --- a/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java @@ -29,10 +29,12 @@ public static void setUp() { } /** - * 3x3 TreeTent puzzle with a tent at (0,0) Tests FinishWithGrassDirectRule on GRASS tiles - * horizontal of the tent at (1,0) and (2,0) + * 3x3 TreeTent puzzle Tests FinishWithGrassDirectRule * - * @throws InvalidFileFormatException + *

Tent at (1, 1) XXX x GTG 1 XXX x xxx + * + *

Makes (0, 1) and (2, 1) GRASS Checks if the rule detects the middle row to be filled in + * correctly */ @Test public void FinishWithGrassHorizontalTest() throws InvalidFileFormatException { @@ -75,10 +77,12 @@ public void FinishWithGrassHorizontalTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a tent at (0,0) Tests FinishWithGrassDirectRule on GRASS tiles - * vertical of the tent at (0,1) and (0,2) + * 3x3 TreeTent puzzle Tests FinishWithGrassDirectRule + * + *

Tent at (0, 0) TXX x GXX x GXX x 1xx * - * @throws InvalidFileFormatException + *

Makes (0, 1) and (0, 2) GRASS Checks if the rule detects the leftmost column to be filled + * in correctly */ @Test public void FinishWithGrassVerticalTest() throws InvalidFileFormatException { @@ -121,10 +125,12 @@ public void FinishWithGrassVerticalTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a tent at (0,0) Tests FinishWithGrassDirectRule on GRASS tiles at - * (1,0), (2,0), (0,1), and (0,2) + * 3x3 TreeTent puzzle Tests FinishWithGrassDirectRule * - * @throws InvalidFileFormatException + *

Tent at (0, 0) TGG 1 GXX x GXX x 1xx + * + *

Makes (0, 1), (0, 2), (1, 0), and (2, 0) GRASS Checks if the rule detects the top row and + * leftmost column to be filled in correctly */ @Test public void FinishWithGrassTest() throws InvalidFileFormatException { @@ -175,10 +181,12 @@ public void FinishWithGrassTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with no tents Tests FinishWithGrassDirectRule on GRASS tiles GRASS tiles - * fill entire board + * 3x3 TreeTent puzzle Tests FinishWithGrassDirectRule + * + *

Empty GGG 0 GGG 0 GGG 0 000 * - * @throws InvalidFileFormatException + *

Fill Board with GRASS Checks if the rule allows all cells to be filled when the clue for + * all rows and columns is zero. */ @Test public void NoTentTest() throws InvalidFileFormatException { @@ -216,10 +224,12 @@ public void NoTentTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a tent at (1,1) Tests FinishWithGrassDirectRule on GRASS tiles - * surrounding the tent at (1,0), (0,1), (2,1), and (1,2) + * 3x3 TreeTent puzzle Tests FinishWithGrassDirectRule * - * @throws InvalidFileFormatException + *

Tent at (1, 1) XGX x GTG 1 XGX x x1x + * + *

Makes (1, 0), (0, 1), (2, 1), and (1, 2) GRASS Checks if the rule correctly allows the + * central row and column to be filled with grass. */ @Test public void MiddleTentTest() throws InvalidFileFormatException { @@ -271,10 +281,12 @@ public void MiddleTentTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with missing tents Tests FinishWithGrassDirectRule on GRASS tiles filling - * the puzzle all GRASS tiles should fail the FinishWithGrassDirectRule + * 3x3 TreeTent puzzle Tests FinishWithGrassDirectRule + * + *

Empty GGG 1 GGG 1 GGG 1 111 * - * @throws InvalidFileFormatException + *

Fill Board with GRASS Checks if the rule is not valid when a row or column does not have + * the required number of tents but is filled with grass */ @Test public void FailTentTest() throws InvalidFileFormatException { @@ -312,10 +324,13 @@ public void FailTentTest() throws InvalidFileFormatException { } /** - * 7x7 TreeTent puzzle with multiple tents spaced out Tests FinishWithGrassDirectRule on GRASS - * tiles between the tents at (0,3), (2,3), (4,3), and (6,3) + * 7x7 TreeTent puzzle Tests FinishWithGrassDirectRule + * + *

Tents at (1, 3), (3, 3), and (5, 3) XXXXXXX x XXXXXXX x XXXXXXX x TGTGTGT 4 XXXXXXX x + * XXXXXXX x XXXXXXX x xxxxxxx * - * @throws InvalidFileFormatException + *

Makes (0, 3), (2, 3), (4, 3), and (6, 3) GRASS Checks if applying the rule on row 3 is + * valid */ @Test public void SpacedOutTentTest() throws InvalidFileFormatException { diff --git a/src/test/java/puzzles/treetent/rules/FinishWithTentsDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/FinishWithTentsDirectRuleTest.java index 652af615f..b72b0f556 100644 --- a/src/test/java/puzzles/treetent/rules/FinishWithTentsDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/FinishWithTentsDirectRuleTest.java @@ -28,10 +28,11 @@ public static void setUp() { } /** - * 3x3 TreeTent puzzle with a GRASS tile at (0,0) Tests FinishWithTentsDirectRule on TENT tiles - * horizontal of the GRASS tile at (1,0) and (2,0) + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule * - * @throws InvalidFileFormatException + *

Grass at (0, 0) GTT 2 XXX x XXX x xxx + * + *

Makes (1, 0) and (2, 0) TENT Checks that the rule correctly fills in the first row */ @Test public void FinishWithHorizontalTentsTest() throws InvalidFileFormatException { @@ -68,10 +69,11 @@ public void FinishWithHorizontalTentsTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a GRASS tile at (0,0) Tests FinishWithTentsDirectRule on TENT tiles - * vertical of the GRASS tile at (0,1) and (0,2) + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule + * + *

Grass at (0, 0) GXX x TXX x TXX x 2xx * - * @throws InvalidFileFormatException + *

Makes (0, 1) and (0, 2) TENT Checks that the rule correctly fills in the first column */ @Test public void FinishWithVerticalTentsTest() throws InvalidFileFormatException { @@ -108,10 +110,12 @@ public void FinishWithVerticalTentsTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a GRASS tile at (1,1) Tests FinishWithTentsDirectRule on TENT tiles - * around the GRASS tile at (1,0), (1,2), (0,1), and (2,1) + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule * - * @throws InvalidFileFormatException + *

Grass at (0, 0) GTT 2 TXX x TXX x 2xx + * + *

Makes (1, 0), (2, 0), (0, 1) and (0, 2) TENT Checks that the rule correctly fills both the + * first row and first column */ @Test public void FinishWithTentsTest() throws InvalidFileFormatException { @@ -156,10 +160,12 @@ public void FinishWithTentsTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a TENT tile at (1,1) Tests FinishWithTentsDirectRule on TENT tiles - * around the TENT tile at (1,0), (1,2), (0,1), and (2,1) + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule + * + *

Tent at (1, 1) XTX x TTT 3 XTX x x3x * - * @throws InvalidFileFormatException + *

Makes (1, 0), (0, 1), (2, 1), and (1, 2) TENT Checks that the rule correctly fills in the + * middle row and column when a tent starts at (1, 1) */ @Test public void AdditionalTentsTest() throws InvalidFileFormatException { @@ -204,10 +210,12 @@ public void AdditionalTentsTest() throws InvalidFileFormatException { } /** - * Empty 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule on TENT tiles of entire puzzle all - * TENT tiles should fail FinishWithTentsDirectRule as no TENT tiles should be there + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule * - * @throws InvalidFileFormatException + *

Empty TTT 0 TTT 0 TTT 0 000 + * + *

Fills the board with tents Checks that the rule does not allow for more tents in any of + * the rows or columns */ @Test public void FinishWithTentsFailTest() throws InvalidFileFormatException { @@ -240,11 +248,12 @@ public void FinishWithTentsFailTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a TENT tile at (1,1) Tests FinishWithTentsDirectRule on TENT tiles - * around the TENT tile at (1,0), (1,2), (0,1), and (2,1) all TENT tiles should fail - * FinishWithTentsDirectRule as there were already sufficient number of TENT tiles + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule + * + *

Tent at (1, 1) XTX x TTT 1 XTX x x1x * - * @throws InvalidFileFormatException + *

Makes (1, 0), (0, 1), (2, 1) and (1, 2) Tent Checks that the rule does not allow for more + * tents in the central row or central column */ @Test public void TooManyTentsTest() throws InvalidFileFormatException { @@ -280,12 +289,12 @@ public void TooManyTentsTest() throws InvalidFileFormatException { } /** - * 3x3 TreeTent puzzle with a TENT tile at (1,1) Tests FinishWithTentsDirectRule on TENT tiles - * around the TENT tile at (1,0), (1,2), (0,1), and (2,1) all TENT tiles should fail - * FinishWithTentsDirectRule as there are multiple configurations of the placement of the TENT - * tiles + * 3x3 TreeTent puzzle Tests FinishWithTentsDirectRule + * + *

Tent at (1, 1) XTX x TTT 2 XTX x x2x * - * @throws InvalidFileFormatException + *

Makes (1, 0), (0, 1), (2, 1) and (1, 2) Tent Checks that the rule is not satisfied because + * there are multiple configurations of tents for the central row and central column */ @Test public void AmbiguousTentsTest() throws InvalidFileFormatException { diff --git a/src/test/java/puzzles/treetent/rules/LastCampingSpotDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/LastCampingSpotDirectRuleTest.java index 92d6e4a59..fdd55029f 100644 --- a/src/test/java/puzzles/treetent/rules/LastCampingSpotDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/LastCampingSpotDirectRuleTest.java @@ -27,9 +27,11 @@ public static void setUp() { } /** - * @throws InvalidFileFormatException - *

Checks if a test works for an empty square above a tree which is surrounded on all - * other sides. + * 3x3 TreeTent puzzle Tests LastCampingSpotDirectRule + * + *

TREE at (1, 1) and (0, 1); GRASS at (1, 2) and (2, 1) XTX RRG XGX + * + *

Makes (1, 0) TENT Checks that a tent must be placed above the central tree */ @Test public void EmptyFieldTest_Up() throws InvalidFileFormatException { @@ -61,9 +63,11 @@ public void EmptyFieldTest_Up() throws InvalidFileFormatException { } /** - * @throws InvalidFileFormatException - *

Checks if a test works for an empty square below a tree which is surrounded on all - * other sides. + * 3x3 TreeTent puzzle Tests LastCampingSpotDirectRule + * + *

TREE at (1, 1) and (0, 1); GRASS at (1, 0) and (1, 2) XGX RRG XTX + * + *

Makes (1, 2) TENT Checks that a tent must be placed below the central tree */ @Test public void EmptyFieldTest_Down() throws InvalidFileFormatException { @@ -95,9 +99,11 @@ public void EmptyFieldTest_Down() throws InvalidFileFormatException { } /** - * @throws InvalidFileFormatException - *

Checks if a test works for an empty square to the left of a tree which is surrounded - * on all other sides. + * 3x3 TreeTent puzzle Tests LastCampingSpotDirectRule + * + *

TREE at (1, 1) and (2, 1); GRASS at (1, 0) and (1, 2) XGX TRR XGX + * + *

Makes (0, 1) TENT Checks that a tent must be placed on the left of the central tree */ @Test public void EmptyFieldTest_Left() throws InvalidFileFormatException { @@ -129,9 +135,11 @@ public void EmptyFieldTest_Left() throws InvalidFileFormatException { } /** - * @throws InvalidFileFormatException - *

Checks if a test works for an empty square to the right of a tree which is surrounded - * on all other sides. + * 3x3 TreeTent puzzle Tests LastCampingSpotDirectRule + * + *

TREE at (1, 1) and (1, 2); GRASS at (0, 1) and (1, 0) XGX GRT XRX + * + *

Makes (2, 1) TENT Checks that a tent must be placed to the right of the central tree */ @Test public void EmptyFieldTest_Right() throws InvalidFileFormatException { diff --git a/src/test/java/puzzles/treetent/rules/LinkTentCaseRuleTest.java b/src/test/java/puzzles/treetent/rules/LinkTentCaseRuleTest.java new file mode 100644 index 000000000..ed482eba0 --- /dev/null +++ b/src/test/java/puzzles/treetent/rules/LinkTentCaseRuleTest.java @@ -0,0 +1,184 @@ +package puzzles.treetent.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.treetent.*; +import edu.rpi.legup.puzzle.treetent.rules.LinkTentCaseRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import java.util.ArrayList; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class LinkTentCaseRuleTest { + private static final LinkTentCaseRule RULE = new LinkTentCaseRule(); + private static TreeTent treetent; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + treetent = new TreeTent(); + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tent with one tree surrounding + * it. + * + *

checks that 1 cases is with the line connecting the central tent and the tree + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentOneTreeTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/LinkTentCaseRule/OneTreeOneTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that one cases was found + Assert.assertEquals(1, cases.size()); + TreeTentBoard testCase = (TreeTentBoard) cases.getFirst(); + + TreeTentLine expectedLine = new TreeTentLine(board.getCell(1, 1), board.getCell(1, 0)); + + ArrayList lines = testCase.getLines(); + + // One line connecting the tree to the tent + Assert.assertEquals(1, lines.size()); + TreeTentLine line = lines.getFirst(); + + // Expected line + Assert.assertTrue(line.compare(expectedLine)); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + original_cell = board.getCell(w, h); + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tent with four trees + * surrounding it. + * + *

checks that 4 cases are created, each of which create a line connecting the tent to one of + * the four trees without repeat. + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentFourTreesTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/LinkTentCaseRule/FourTreesOneTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that four cases were found + Assert.assertEquals(4, cases.size()); + + ArrayList expectedLines = new ArrayList<>(); + expectedLines.addFirst(new TreeTentLine(board.getCell(1, 1), board.getCell(1, 0))); + expectedLines.addFirst(new TreeTentLine(board.getCell(1, 1), board.getCell(0, 1))); + expectedLines.addFirst(new TreeTentLine(board.getCell(1, 1), board.getCell(2, 1))); + expectedLines.addFirst(new TreeTentLine(board.getCell(1, 1), board.getCell(1, 2))); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + ArrayList lines = testCase.getLines(); + + // Each case should connect one line from the tent to + // one of the four trees next to it + Assert.assertEquals(1, lines.size()); + TreeTentLine line = lines.getFirst(); + + // Check to make sure that cases do not repeat + // and cover all four possibilities + boolean exists = false; + for (TreeTentLine expectedLine : expectedLines) { + if (line.compare(expectedLine)) { + expectedLines.remove(expectedLine); + exists = true; + break; + } + } + + Assert.assertTrue(exists); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + original_cell = board.getCell(w, h); + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tent with zero trees around it. + * + *

Ensures no cases are created + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentNoTreesTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/LinkTentCaseRule/NoTrees", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that no cases were found + Assert.assertEquals(0, cases.size()); + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tent with trees on a diagonal. + * + *

Ensures no cases are created + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentDiagTreesTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/LinkTentCaseRule/DiagTrees", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that no cases were found + Assert.assertEquals(0, cases.size()); + } +} diff --git a/src/test/java/puzzles/treetent/rules/LinkTreeCaseRuleTest.java b/src/test/java/puzzles/treetent/rules/LinkTreeCaseRuleTest.java new file mode 100644 index 000000000..be237e8d4 --- /dev/null +++ b/src/test/java/puzzles/treetent/rules/LinkTreeCaseRuleTest.java @@ -0,0 +1,182 @@ +package puzzles.treetent.rules; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.treetent.TreeTent; +import edu.rpi.legup.puzzle.treetent.TreeTentBoard; +import edu.rpi.legup.puzzle.treetent.TreeTentCell; +import edu.rpi.legup.puzzle.treetent.TreeTentLine; +import edu.rpi.legup.puzzle.treetent.rules.LinkTreeCaseRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import java.util.ArrayList; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class LinkTreeCaseRuleTest { + private static final LinkTreeCaseRule RULE = new LinkTreeCaseRule(); + private static TreeTent treetent; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + treetent = new TreeTent(); + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tree with one tent above + * + *

Ensures one case is created that connects the tree to the tent. + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentOneTentTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/LinkTreeCaseRule/OneTent", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that no cases were found + Assert.assertEquals(1, cases.size()); + TreeTentBoard testCase = (TreeTentBoard) cases.getFirst(); + + TreeTentLine expectedLine = new TreeTentLine(board.getCell(1, 1), board.getCell(1, 0)); + + ArrayList lines = testCase.getLines(); + + // One line connecting the tree to the tent + Assert.assertEquals(1, lines.size()); + TreeTentLine line = lines.getFirst(); + + // Expected line + Assert.assertTrue(line.compare(expectedLine)); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + original_cell = board.getCell(w, h); + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tree with two tents, one on the + * left and one on the right. + * + *

Ensures two cases are created, one connecting the tree and the left tent, and one + * connecting the tree and the right tent. Because tents must be surrounded by grass, there can + * be at most two tents around a given tree. + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentTwoTentsTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/LinkTreeCaseRule/TwoTents", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that no cases were found + Assert.assertEquals(2, cases.size()); + + ArrayList expectedLines = new ArrayList<>(); + expectedLines.addFirst(new TreeTentLine(board.getCell(1, 1), board.getCell(0, 1))); + expectedLines.addFirst(new TreeTentLine(board.getCell(1, 1), board.getCell(2, 1))); + + for (Board testCaseBoard : cases) { + TreeTentBoard testCase = (TreeTentBoard) testCaseBoard; + ArrayList lines = testCase.getLines(); + + // Each case should connect one line from the tent to + // either the left or right tree + Assert.assertEquals(1, lines.size()); + TreeTentLine line = lines.getFirst(); + + // Check to make sure that cases do not repeat + // and cover both possibilities + boolean exists = false; + for (TreeTentLine expectedLine : expectedLines) { + if (line.compare(expectedLine)) { + expectedLines.remove(expectedLine); + exists = true; + break; + } + } + + Assert.assertTrue(exists); + + // checks other cells have not been modified + TreeTentCell original_cell; + TreeTentCell case_cell; + + for (int w = 0; w < board.getWidth(); w++) { + for (int h = 0; h < board.getHeight(); h++) { + original_cell = board.getCell(w, h); + case_cell = testCase.getCell(w, h); + Assert.assertEquals(original_cell.getType(), case_cell.getType()); + } + } + } + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tree with zero tents around it. + * + *

Ensures no cases are created + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentNoTreesTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/LinkTreeCaseRule/NoTents", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that no cases were found + Assert.assertEquals(0, cases.size()); + } + + /** + * empty 3x3 TreeTent puzzle Tests LinkTentCaseRule on a central tree with tents on a diagonal. + * + *

Ensures no cases are created + * + * @throws InvalidFileFormatException + */ + @Test + public void LinkTentDiagTentsTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/LinkTreeCaseRule/NoTents", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + TreeTentCell test_location = board.getCell(1, 1); + ArrayList cases = RULE.getCases(board, test_location); + + // assert that no cases were found + Assert.assertEquals(0, cases.size()); + } +} diff --git a/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java index 7ff57a052..dc61cb863 100644 --- a/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java @@ -28,8 +28,12 @@ public static void setUp() { } /** - * @throws InvalidFileFormatException Test to check if all adjacent and diagonals not filled - * with a tree are filled with grass + * 3x3 TreeTent puzzle Tests SurroundTentWithGrassDirectRule + * + *

TREE at (0, 0), (2, 0), (0, 1), (2, 1), (1, 2), and (2, 2); TENT at (1, 1) RGR RTR GRR + * + *

Makes (1, 0) and (0, 2) GRASS Checks that the rule detects unknown adjacent and diagonal + * tiles correctly */ @Test public void SurroundTentWithGrassBasicRuleTest() throws InvalidFileFormatException { @@ -65,10 +69,12 @@ public void SurroundTentWithGrassBasicRuleTest() throws InvalidFileFormatExcepti } /** - * @throws InvalidFileFormatException - *

Test with a 3x3 board with an absolutely empty area aside from a tent in the middle - * While such a situation is an illegal treetent setup, this direct rule doesn't consider - * that aspect, so its ok in this context + * 3x3 TreeTent puzzle Tests SurroundTentWithGrassDirectRule + * + *

TENT at (1, 1) GGG GTG GGG + * + *

Makes all cells adjacent and diagonal to the tent GRASS Checks that the rule detects all + * adjacent and diagonal tiles correctly */ @Test public void SurroundTentWithGrassBasicRuleTest_BadBoard() throws InvalidFileFormatException { @@ -130,9 +136,11 @@ public void SurroundTentWithGrassBasicRuleTest_BadBoard() throws InvalidFileForm } /** - * @throws InvalidFileFormatException - *

Test to see if the rule passes even if no grass was able to be placed due to the - * presence of trees. + * 3x3 TreeTent puzzle Tests SurroundTentWithGrassDirectRule + * + *

TENT at (1, 1); TREE on all adjacent and diagonal tiles RRR RTR RRR + * + *

Null Checks that the rule correctly detects no missing tiles */ @Test public void SurroundTentWithGrassBasicRuleTest_FullBoard() throws InvalidFileFormatException { diff --git a/src/test/java/puzzles/treetent/rules/TentForTreeDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/TentForTreeDirectRuleTest.java index 68dbeaf48..6bd2db071 100644 --- a/src/test/java/puzzles/treetent/rules/TentForTreeDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/TentForTreeDirectRuleTest.java @@ -1,14 +1,158 @@ package puzzles.treetent.rules; -// This feature is no longer supported +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.treetent.*; +import edu.rpi.legup.puzzle.treetent.rules.TentForTreeDirectRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import java.util.ArrayList; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + public class TentForTreeDirectRuleTest { - // private static final TentForTreeBasicRule RULE = new TentForTreeBasicRule(); - // private static TreeTent treetent; - // - // @BeforeClass - // public static void setUp() { - // MockGameBoardFacade.getInstance(); - // treetent = new TreeTent(); - // } + private static final TentForTreeDirectRule RULE = new TentForTreeDirectRule(); + private static TreeTent treetent; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + treetent = new TreeTent(); + } + + /** + * 3x3 TreeTent puzzle Tests TentForTreeDirectRule + * + *

TREE at (1, 0); TENT at (1, 1) XRX XTX XXX + * + *

Makes a line between (1, 0) and (1, 1) Checks that the rule correctly detects the central + * tent as the only possible connection + */ + @Test + public void TentForTreeTestOneTreeOneTentTest() throws InvalidFileFormatException { + + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TentForTreeDirectRule/OneTreeOneTent", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 0); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNull(RULE.checkRule(transition)); + + ArrayList lines = board.getLines(); + for (TreeTentLine l : lines) { + if (l.compare((line))) { + Assert.assertNull(RULE.checkRuleAt(transition, l)); + } else { + Assert.assertNotNull(RULE.checkRuleAt(transition, l)); + } + } + } + + /** + * 3x3 TreeTent puzzle Tests TentForTreeDirectRule + * + *

TREE at (1, 0) and (1, 2); TENT at (1, 1) XRX XTX XRX + * + *

Makes a line between (1, 0) and (1, 1) Checks that the rule works when connecting a line + * between the tree at (1, 0) and tent at (1, 1) + */ + @Test + public void TentForTreeArbitraryTreeTest() throws InvalidFileFormatException { + + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TentForTreeDirectRule/ArbitraryTree", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 0); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNull(RULE.checkRule(transition)); + } + + /** + * 3x3 TreeTent puzzle Tests TentForTreeDirectRule + * + *

TREE at (1, 0) and (1, 2); TENT at (1, 1); LINE between (1, 0) and (1, 1) XRX XTX XRX + * + *

Makes a line between (1, 1) and (1, 2) Checks that the rule fails for the tree at (1, 2) + * because there are no valid tents to connect to + */ + @Test + public void TentForTreeConnectedTent() throws InvalidFileFormatException { + + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TentForTreeDirectRule/ArbitraryTree", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 2); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNull(RULE.checkRule(transition)); + + ArrayList lines = board.getLines(); + for (TreeTentLine l : lines) { + Assert.assertNotNull(RULE.checkRuleAt(transition, l)); + } + } + + /** + * 3x3 TreeTent puzzle Tests TentForTreeDirectRule + * + *

TREE at (1, 1); TENT at (1, 0) and (1, 2) XTX XRX XTX + * + *

Makes a line between (1, 1) and (1, 2) Checks that the rule fails for the tree at (1, 1) + * because there are two valid tents to connect to + */ + @Test + public void TentForTreeOneTreeTwoAdjacentTent() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TentForTreeDirectRule/OneTreeTwoAdjacentTent", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 2); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNotNull(RULE.checkRule(transition)); + } } diff --git a/src/test/java/puzzles/treetent/rules/TooManyTentsContradictionRuleTest.java b/src/test/java/puzzles/treetent/rules/TooManyTentsContradictionRuleTest.java index 2e542b3d2..c1dc6380a 100644 --- a/src/test/java/puzzles/treetent/rules/TooManyTentsContradictionRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/TooManyTentsContradictionRuleTest.java @@ -29,7 +29,7 @@ public static void setUp() { TESTING BASIS: All test in this Rule use a 3x3 table. There is a Tree at (1,1) - There are tents at (0,1) and (2,2) + There are tents at (1,0) and (2,2) All Tent Counts are listed left to right or top to bottom */ diff --git a/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java b/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java index 9f5455a92..e44543ab6 100644 --- a/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java @@ -135,90 +135,6 @@ public void TouchingTentsContradictionRule_2By2Square() throws InvalidFileFormat Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); } - /** - * @throws InvalidFileFormatException Tests a tent of orientation TT T - */ - @Test - public void TouchingTentsContradictionRule_UpLeft() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpLeft", - treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - Assert.assertNull(RULE.checkContradiction(board)); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); - } - - /** - * @throws InvalidFileFormatException Tests a tent of orientation TT T - */ - @Test - public void TouchingTentsContradictionRule_UpRight() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpRight", - treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - Assert.assertNull(RULE.checkContradiction(board)); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); - } - - /** - * @throws InvalidFileFormatException Tests a tent of orientation T TT - */ - @Test - public void TouchingTentsContradictionRule_DownLeft() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft", - treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - Assert.assertNull(RULE.checkContradiction(board)); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); - } - - /** - * @throws InvalidFileFormatException Tests a tent of orientation T TT - */ - @Test - public void TouchingTentsContradictionRule_DownRight() throws InvalidFileFormatException { - TestUtilities.importTestBoard( - "puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownRight", - treetent); - TreeNode rootNode = treetent.getTree().getRootNode(); - TreeTransition transition = rootNode.getChildren().get(0); - transition.setRule(RULE); - - TreeTentBoard board = (TreeTentBoard) transition.getBoard(); - - Assert.assertNull(RULE.checkContradiction(board)); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); - } - /** * @throws InvalidFileFormatException Tests if tree adjacent triggers a null */ @@ -254,6 +170,6 @@ public void TouchingTentsContradictionRule_TreeDiagonal() throws InvalidFileForm Assert.assertNotNull(RULE.checkContradiction(board)); Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); } } diff --git a/src/test/java/puzzles/treetent/rules/TreeForTentDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/TreeForTentDirectRuleTest.java index f4ea6703b..3cccdc417 100644 --- a/src/test/java/puzzles/treetent/rules/TreeForTentDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/TreeForTentDirectRuleTest.java @@ -1,14 +1,152 @@ package puzzles.treetent.rules; -// This feature is no longer supported +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.treetent.TreeTent; +import edu.rpi.legup.puzzle.treetent.TreeTentBoard; +import edu.rpi.legup.puzzle.treetent.TreeTentCell; +import edu.rpi.legup.puzzle.treetent.TreeTentLine; +import edu.rpi.legup.puzzle.treetent.rules.TreeForTentDirectRule; +import edu.rpi.legup.save.InvalidFileFormatException; +import java.util.ArrayList; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + public class TreeForTentDirectRuleTest { - // private static final TreeForTentBasicRule RULE = new TreeForTentBasicRule(); - // private static TreeTent treetent; + private static final TreeForTentDirectRule RULE = new TreeForTentDirectRule(); + private static TreeTent treetent; + + @BeforeClass + public static void setUp() { + MockGameBoardFacade.getInstance(); + treetent = new TreeTent(); + } + + /** + * 3x3 TreeTent puzzle Tests TreeForTentDirectRule + * + *

TENT at (1, 0); TREE at (1, 1) XTX XRX XXX + * + *

Makes a line between (1, 0) and (1, 1) Checks that the rule correctly detects the central + * tree as the only possible connection + */ + @Test + public void TreeForTentTestOneTreeOneTentTest() throws InvalidFileFormatException { + + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TreeForTentDirectRule/OneTentOneTree", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 0); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNull(RULE.checkRule(transition)); + } + + /** + * 3x3 TreeTent puzzle Tests TreeForTentDirectRule + * + *

TENT at (1, 0) and (1, 2); TREE at (1, 1) XTX XRX XTX + * + *

Makes a line between (1, 0) and (1, 1) Checks that the rule works when connecting a line + * between the tent at (1, 0) and the tree at (1, 1) + */ + @Test + public void TentForTreeWithArbitraryTreeTest() throws InvalidFileFormatException { + + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TreeForTentDirectRule/ArbitraryTent", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 0); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNull(RULE.checkRule(transition)); + } + + /** + * 3x3 TreeTent puzzle Tests TreeForTentDirectRule + * + *

TENT at (1, 0) and (1, 2); TREE at (1, 1); LINE between (1, 0) and (1, 1) XTX XRX XTX + * + *

Makes a line between (1, 1) and (1, 2) Checks that the rule fails for the tent at (1, 2) + * because there are no valid trees to connect to + */ + @Test + public void TentForTreeConnectedTent() throws InvalidFileFormatException { + + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TreeForTentDirectRule/ConnectedTree", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 2); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); + + Assert.assertNull(RULE.checkRule(transition)); + + ArrayList lines = board.getLines(); + for (TreeTentLine l : lines) { + Assert.assertNotNull(RULE.checkRuleAt(transition, l)); + } + } + + /** + * 3x3 TreeTent puzzle Tests TreeForTentDirectRule + * + *

TENT at (1, 1); TREE at (1, 0) and (1, 2) XRX XTX XRX + * + *

Makes a line between (1, 1) and (1, 2) Checks that the rule fails for the tree at (1, 1) + * because there are two valid trees to connect to + */ + @Test + public void TentForTreeOneTreeTwoAdjacentTent() throws InvalidFileFormatException { + TestUtilities.importTestBoard( + "puzzles/treetent/rules/TreeForTentDirectRule/OneTentTwoAdjacentTrees", treetent); + + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(1, 2); + TreeTentCell cell2 = board.getCell(1, 1); + TreeTentLine line = new TreeTentLine(cell1, cell2); + + board.addModifiedData(line); + board.getLines().add(line); - // @BeforeClass - // public static void setUp() { - // MockGameBoardFacade.getInstance(); - // treetent = new TreeTent(); - // } + Assert.assertNotNull(RULE.checkRule(transition)); + } } diff --git a/src/test/resources/puzzles/minesweeper/utilities/3x3test b/src/test/resources/puzzles/minesweeper/utilities/3x3test new file mode 100644 index 000000000..2bf4f5c3b --- /dev/null +++ b/src/test/resources/puzzles/minesweeper/utilities/3x3test @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/ColumnBlackout b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/ColumnBlackout new file mode 100644 index 000000000..ddcc4dc9a --- /dev/null +++ b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/ColumnBlackout @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RegionBlackout b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RegionBlackout new file mode 100644 index 000000000..f2a5b42d9 --- /dev/null +++ b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RegionBlackout @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RowBlackout b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RowBlackout new file mode 100644 index 000000000..f2a5b42d9 --- /dev/null +++ b/src/test/resources/puzzles/starbattle.rules/BlackoutDirectRule/RowBlackout @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3OneTent similarity index 78% rename from src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft rename to src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3OneTent index d19e01daf..1e1259a44 100644 --- a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft +++ b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3OneTent @@ -2,16 +2,15 @@ - - + - + diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ThreeTent similarity index 78% rename from src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight rename to src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ThreeTent index bf3954964..7c352347d 100644 --- a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight +++ b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ThreeTent @@ -2,16 +2,15 @@ - - + - + diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3TwoTent similarity index 57% rename from src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft rename to src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3TwoTent index af4ca0831..1f4a907eb 100644 --- a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft +++ b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3TwoTent @@ -1,18 +1,17 @@ - + - - - - + + - + + diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ZeroTent similarity index 92% rename from src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom rename to src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ZeroTent index 80deadaea..a13c7cc55 100644 --- a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom +++ b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow3x3ZeroTent @@ -2,7 +2,6 @@ - diff --git a/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow5x5TwoTent b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow5x5TwoTent new file mode 100644 index 000000000..ddfcf44db --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/EmptyRow5x5TwoTent @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/PartialFillOneTent b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/PartialFillOneTent new file mode 100644 index 000000000..47d06d5b9 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/FillinRowCaseRule/PartialFillOneTent @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/DiagTrees b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/DiagTrees new file mode 100644 index 000000000..4a10b61f2 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/DiagTrees @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/FourTreesOneTent b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/FourTreesOneTent new file mode 100644 index 000000000..96a06de3b --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/FourTreesOneTent @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/NoTrees b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/NoTrees new file mode 100644 index 000000000..c781cfde9 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/NoTrees @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/OneTreeOneTent b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/OneTreeOneTent new file mode 100644 index 000000000..103c1ce3e --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTentCaseRule/OneTreeOneTent @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/DiagTents b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/DiagTents new file mode 100644 index 000000000..e50bef8c1 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/DiagTents @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/NoTents b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/NoTents new file mode 100644 index 000000000..3bab63f75 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/NoTents @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/OneTent b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/OneTent new file mode 100644 index 000000000..69eb680de --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/OneTent @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/TwoTents b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/TwoTents new file mode 100644 index 000000000..7fb4b6e46 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/LinkTreeCaseRule/TwoTents @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/ArbitraryTree b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/ArbitraryTree new file mode 100644 index 000000000..258ebec21 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/ArbitraryTree @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/ConnectedTent b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/ConnectedTent new file mode 100644 index 000000000..031bca068 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/ConnectedTent @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/OneTreeOneTent b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/OneTreeOneTent new file mode 100644 index 000000000..dcc428c8b --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/OneTreeOneTent @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/OneTreeTwoAdjacentTent b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/OneTreeTwoAdjacentTent new file mode 100644 index 000000000..508cda0a8 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TentForTreeDirectRule/OneTreeTwoAdjacentTent @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownRight b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownRight deleted file mode 100644 index 05b7a7667..000000000 --- a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownRight +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpLeft b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpLeft deleted file mode 100644 index b8b81c7b4..000000000 --- a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpLeft +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpRight b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpRight deleted file mode 100644 index af7fb5df2..000000000 --- a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpRight +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeDiagonal b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeDiagonal index b2f54aef3..b25eb6c4c 100644 --- a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeDiagonal +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeDiagonal @@ -3,7 +3,7 @@ - + diff --git a/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/ArbitraryTent b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/ArbitraryTent new file mode 100644 index 000000000..ce083cfc0 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/ArbitraryTent @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/ConnectedTree b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/ConnectedTree new file mode 100644 index 000000000..6ebecc06f --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/ConnectedTree @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/OneTentOneTree b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/OneTentOneTree new file mode 100644 index 000000000..fabfe06e2 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/OneTentOneTree @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/OneTentTwoAdjacentTrees b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/OneTentTwoAdjacentTrees new file mode 100644 index 000000000..8218fb2f1 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TreeForTentDirectRule/OneTentTwoAdjacentTrees @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file