diff --git a/bin/main/edu/rpi/legup/legup/config b/bin/main/edu/rpi/legup/legup/config index 24fdcf365..19e63a2a3 100644 --- a/bin/main/edu/rpi/legup/legup/config +++ b/bin/main/edu/rpi/legup/legup/config @@ -7,7 +7,7 @@ + fileCreationDisabled="false"/> - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index fafa54cac..1ba7ea006 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,6 @@ dependencies { implementation 'org.jetbrains:annotations:20.1.0' implementation 'org.jetbrains:annotations:20.1.0' implementation 'com.formdev:flatlaf:3.0' - implementation project(':legup-update') implementation 'com.google.firebase:firebase-admin:6.3.0' implementation 'org.apache.httpcomponents:httpclient:4.5.1' implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' diff --git a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/BlockInVerticalPath b/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/BlockInVerticalPath deleted file mode 100644 index 5f27b3ec8..000000000 --- a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/BlockInVerticalPath +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/CannotFillMiddle b/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/CannotFillMiddle deleted file mode 100644 index e69de29bb..000000000 diff --git a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/LightInHorizontalPath b/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/LightInHorizontalPath deleted file mode 100644 index 633ccc80b..000000000 --- a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/LightInHorizontalPath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/LightInVerticalPath b/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/LightInVerticalPath deleted file mode 100644 index 70419c40c..000000000 --- a/build/resources/test/puzzles/lightup/rules/BulbsInPathContradictionRule/LightInVerticalPath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/CannotLightACellContradictionRule/CannotFillCorners b/build/resources/test/puzzles/lightup/rules/CannotLightACellContradictionRule/CannotFillCorners deleted file mode 100644 index 38b52f04d..000000000 --- a/build/resources/test/puzzles/lightup/rules/CannotLightACellContradictionRule/CannotFillCorners +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/CannotLightACellContradictionRule/CannotFillMiddle b/build/resources/test/puzzles/lightup/rules/CannotLightACellContradictionRule/CannotFillMiddle deleted file mode 100644 index 44086f145..000000000 --- a/build/resources/test/puzzles/lightup/rules/CannotLightACellContradictionRule/CannotFillMiddle +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/BlockEnclosed b/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/BlockEnclosed deleted file mode 100644 index a57a2473e..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/BlockEnclosed +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/CompleteBoardBlockEnclosed b/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/CompleteBoardBlockEnclosed deleted file mode 100644 index f48d240f0..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/CompleteBoardBlockEnclosed +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/CornerBlockEnclosed b/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/CornerBlockEnclosed deleted file mode 100644 index 1a9cd60d9..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/CornerBlockEnclosed +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/ManyBlocksEnclosed b/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/ManyBlocksEnclosed deleted file mode 100644 index 32200d831..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooFewBulbsContradictionRule/ManyBlocksEnclosed +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/BlockEnclosed b/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/BlockEnclosed deleted file mode 100644 index c5760aede..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/BlockEnclosed +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/CompleteBoardBlockEnclosed b/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/CompleteBoardBlockEnclosed deleted file mode 100644 index 88fb0a8f1..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/CompleteBoardBlockEnclosed +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/CornerBlockEnclosed b/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/CornerBlockEnclosed deleted file mode 100644 index a9a8dc5a0..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/CornerBlockEnclosed +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/ManyBlocksEnclosed b/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/ManyBlocksEnclosed deleted file mode 100644 index e743862eb..000000000 --- a/build/resources/test/puzzles/lightup/rules/TooManyBulbsContradictionRule/ManyBlocksEnclosed +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/puzzles files/fillapix/10x10 Fillapix Advanced/FillapixAdvanced10x10_1 b/puzzles files/fillapix/10x10 Fillapix Advanced/FillapixAdvanced10x10_1 new file mode 100644 index 000000000..3706caf3b --- /dev/null +++ b/puzzles files/fillapix/10x10 Fillapix Advanced/FillapixAdvanced10x10_1 @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/fillapix/10x10 Fillapix Very Easy/03010000074 b/puzzles files/fillapix/10x10 Fillapix Basic/FillapixBasic10x10_1 similarity index 100% rename from puzzles files/fillapix/10x10 Fillapix Very Easy/03010000074 rename to puzzles files/fillapix/10x10 Fillapix Basic/FillapixBasic10x10_1 diff --git a/puzzles files/fillapix/15x15 Fillapix Advanced/FillapixAdvanced15x15_1 b/puzzles files/fillapix/15x15 Fillapix Advanced/FillapixAdvanced15x15_1 new file mode 100644 index 000000000..954d6d409 --- /dev/null +++ b/puzzles files/fillapix/15x15 Fillapix Advanced/FillapixAdvanced15x15_1 @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/fillapix/15x15 Fillapix Basic/FillapixBasic15x15_1 b/puzzles files/fillapix/15x15 Fillapix Basic/FillapixBasic15x15_1 new file mode 100644 index 000000000..d17e230f0 --- /dev/null +++ b/puzzles files/fillapix/15x15 Fillapix Basic/FillapixBasic15x15_1 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/fillapix/20x20 Fillaix Advanced/FillapixAdvanced20x20_1 b/puzzles files/fillapix/20x20 Fillaix Advanced/FillapixAdvanced20x20_1 new file mode 100644 index 000000000..3582ba947 --- /dev/null +++ b/puzzles files/fillapix/20x20 Fillaix Advanced/FillapixAdvanced20x20_1 @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/fillapix/20x20 Fillapix Basic/FillapixBasic20x20_1 b/puzzles files/fillapix/20x20 Fillapix Basic/FillapixBasic20x20_1 new file mode 100644 index 000000000..59d8a2114 --- /dev/null +++ b/puzzles files/fillapix/20x20 Fillapix Basic/FillapixBasic20x20_1 @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/puzzles files/fillapix/20x20 Fillapix Basic/FillapixBasic20x20_2 b/puzzles files/fillapix/20x20 Fillapix Basic/FillapixBasic20x20_2 new file mode 100644 index 000000000..af3f936f8 --- /dev/null +++ b/puzzles files/fillapix/20x20 Fillapix Basic/FillapixBasic20x20_2 @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index 936e82b6f..f9f593d2b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,2 @@ rootProject.name = 'Legup' -include ':legup' -include ':legup-update' diff --git a/src/main/java/edu/rpi/legup/controller/TreeController.java b/src/main/java/edu/rpi/legup/controller/TreeController.java index b954edd79..6eae4ac3b 100644 --- a/src/main/java/edu/rpi/legup/controller/TreeController.java +++ b/src/main/java/edu/rpi/legup/controller/TreeController.java @@ -2,6 +2,7 @@ import edu.rpi.legup.model.Puzzle; import edu.rpi.legup.model.tree.Tree; +import edu.rpi.legup.model.tree.TreeElementType; import edu.rpi.legup.ui.boardview.BoardView; import edu.rpi.legup.ui.proofeditorui.treeview.*; @@ -42,7 +43,7 @@ public void mousePressed(MouseEvent e) { /** * Mouse Released event sets the cursor back to the default cursor and reset info for panning - * + * Set board modifiability * @param e MouseEvent object */ @Override diff --git a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java index b42f73c9a..331a3dddf 100644 --- a/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java @@ -24,6 +24,8 @@ public class AutoCaseRuleCommand extends PuzzleCommand { private List caseTrans; + private static final int MAX_CASES = 10; + /** * AutoCaseRuleCommand Constructor creates a command for validating a case rule * @@ -112,6 +114,10 @@ public String getErrorString() { return "The selection must produce at least one case"; } + if (caseRule.getCases(caseBoard.getBaseBoard(), elementView.getPuzzleElement()).size() > MAX_CASES) { + return "The selection can produce a max of " + MAX_CASES + " cases"; + } + return null; } diff --git a/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java b/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java index a0f58a571..23f8dce21 100644 --- a/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java +++ b/src/main/java/edu/rpi/legup/history/ValidateContradictionRuleCommand.java @@ -66,9 +66,11 @@ public void executeCommand() { if (transition == null) { transition = tree.addNewTransition(treeNode); transition.setRule(newRule); + transition.getBoard().setModifiable(false); tree.addTreeElement(transition); } else { + transition.getBoard().setModifiable(false); tree.addTreeElement(treeNode, transition); } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/Fillapix.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/Fillapix.java index ca10ab266..ef78f66aa 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/Fillapix.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/Fillapix.java @@ -26,6 +26,8 @@ public Fillapix() { @Override public void initializeView() { boardView = new FillapixView((FillapixBoard) currentBoard); + boardView.setBoard(currentBoard); + addBoardListener(boardView); } @Override @@ -42,8 +44,7 @@ public Board generatePuzzle(int difficulty) { * @return true if the given dimensions are valid for Fillapix, false otherwise */ public boolean isValidDimensions(int rows, int columns) { - // This is a placeholder, this method needs to be implemented - throw new UnsupportedOperationException(); + return super.isValidDimensions(rows, columns); } @Override diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixBoard.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixBoard.java index 24615db5c..67987a6fd 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixBoard.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixBoard.java @@ -53,6 +53,9 @@ public int getNumCells(FillapixCell cell, FillapixCellType type) { int numCells = 0; for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { + if (loc.x + i > dimension.width || loc.y + j > dimension.height) { + continue; + } FillapixCell c = getCell(loc.x + i, loc.y + j); if (c != null && c.getType() == type) { numCells++; diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java index 00f66708d..5e6d4b9ed 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCell.java @@ -1,10 +1,13 @@ package edu.rpi.legup.puzzle.fillapix; +import edu.rpi.legup.model.elements.Element; import edu.rpi.legup.model.gameboard.GridCell; import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.Objects; -public class FillapixCell extends GridCell { +public class FillapixCell extends GridCell implements Comparable { public static final int DEFAULT_VALUE = 10; @@ -33,10 +36,44 @@ public FillapixCellType getType() { } } - public void setType(FillapixCellType type) { + public void setCellType(FillapixCellType type) { data = type.value * 100 + (data % 100); } + @Override + public void setType(Element e, MouseEvent m) { + switch(e.getElementID()) { + case "FPIX-PLAC-0001": + this.setCellType(FillapixCellType.BLACK); + break; + case "FPIX-PLAC-0002": + this.setCellType(FillapixCellType.WHITE); + break; + case "FPIX-UNPL-0001": + int n = this.getNumber(); + switch (m.getButton()) { + case MouseEvent.BUTTON1: + n++; + break; + case MouseEvent.BUTTON3: + n--; + break; + } + if (n > 9) { + n = 0; + } + if (n < 0) { + n = 9; + } + this.setNumber(n); + break; + default: + this.setCellType(FillapixCellType.UNKNOWN); + this.data = -1; + break; + } + } + /** * Performs a deep copy on the FillapixCell * @@ -49,4 +86,19 @@ public FillapixCell copy() { cell.setModifiable(isModifiable); return cell; } + + public boolean equals(FillapixCell otherCell) { +// return this.location.equals(otherCell.location) && this.index == otherCell.index && this.data == otherCell.data; + //return this.index == otherCell.index && this.data == otherCell.data; + //return this.index == otherCell.index; + return this.location.x == otherCell.location.x && this.location.y == otherCell.location.y; + } + + public int compareTo(FillapixCell otherCell) { + return this.index - otherCell.index; + } + + public int hashCode() { + return Objects.hash(this.index); + } } \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCellController.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCellController.java index 62d14630d..df3bba403 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCellController.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixCellController.java @@ -15,15 +15,32 @@ public void changeCell(MouseEvent e, PuzzleElement puzzleElement) { } else { if (cell.getType() == FillapixCellType.UNKNOWN) { - cell.setType(FillapixCellType.BLACK); + cell.setCellType(FillapixCellType.BLACK); } else { if (cell.getType() == FillapixCellType.BLACK) { - cell.setType(FillapixCellType.WHITE); + cell.setCellType(FillapixCellType.WHITE); } else { if (cell.getType() == FillapixCellType.WHITE) { - cell.setType(FillapixCellType.UNKNOWN); + cell.setCellType(FillapixCellType.UNKNOWN); + } + } + } + } + } + else { + if (e.getButton() == MouseEvent.BUTTON3) { + if (cell.getType() == FillapixCellType.UNKNOWN) { + cell.setCellType(FillapixCellType.WHITE); + } + else { + if (cell.getType() == FillapixCellType.BLACK) { + cell.setCellType(FillapixCellType.UNKNOWN); + } + else { + if (cell.getType() == FillapixCellType.WHITE) { + cell.setCellType(FillapixCellType.BLACK); } } } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixElementView.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixElementView.java index 00d0a6807..93fa0d451 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixElementView.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixElementView.java @@ -47,7 +47,7 @@ public void drawElement(Graphics2D graphics2D) { break; } graphics2D.fillRect(location.x, location.y, size.width, size.height); - if (cell.getNumber() != -1) { + if (cell.getNumber() >= 0 && cell.getNumber() < 10) { graphics2D.setColor(type == FillapixCellType.WHITE ? BLACK_COLOR : WHITE_COLOR); graphics2D.setFont(FONT); FontMetrics metrics = graphics2D.getFontMetrics(FONT); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixImporter.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixImporter.java index 6c30b2272..5213e7139 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixImporter.java @@ -32,7 +32,18 @@ public boolean acceptsTextInput() { */ @Override public void initializeBoard(int rows, int columns) { + FillapixBoard fillapixBoard = new FillapixBoard(columns, rows); + for (int y = 0; y < rows; y++) { + for (int x = 0; x < columns; x++) { + FillapixCell cell = new FillapixCell(FillapixCellType.UNKNOWN.value, new Point(x, y)); + cell.setIndex(y * columns + x); + cell.setNumber(FillapixCell.DEFAULT_VALUE); + cell.setModifiable(true); + fillapixBoard.setCell(x, y, cell); + } + } + puzzle.setCurrentBoard(fillapixBoard); } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixUtilities.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixUtilities.java index 2098676b5..a7feac91d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixUtilities.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixUtilities.java @@ -1,23 +1,206 @@ package edu.rpi.legup.puzzle.fillapix; +import edu.rpi.legup.model.rules.ContradictionRule; import edu.rpi.legup.puzzle.fillapix.rules.TooFewBlackCellsContradictionRule; import edu.rpi.legup.puzzle.fillapix.rules.TooManyBlackCellsContradictionRule; +import java.awt.*; +import java.util.ArrayList; + public class FillapixUtilities { public static boolean isForcedBlack(FillapixBoard board, FillapixCell cell) { TooFewBlackCellsContradictionRule tooManyBlackCells = new TooFewBlackCellsContradictionRule(); FillapixBoard whiteCaseBoard = board.copy(); FillapixCell whiteCell = (FillapixCell) whiteCaseBoard.getPuzzleElement(cell); - whiteCell.setType(FillapixCellType.WHITE); - return tooManyBlackCells.checkContradictionAt(whiteCaseBoard, cell) != null; + whiteCell.setCellType(FillapixCellType.WHITE); + ArrayList adjCells = getAdjacentCells(whiteCaseBoard, whiteCell); + for (FillapixCell adjCell : adjCells) { + if (tooManyBlackCells.checkContradictionAt(whiteCaseBoard, adjCell) == null) { + return true; + } + } + return false; } public static boolean isForcedWhite(FillapixBoard board, FillapixCell cell) { TooManyBlackCellsContradictionRule tooManyBlackCells = new TooManyBlackCellsContradictionRule(); FillapixBoard blackCaseBoard = board.copy(); FillapixCell blackCell = (FillapixCell) blackCaseBoard.getPuzzleElement(cell); - blackCell.setType(FillapixCellType.BLACK); - return tooManyBlackCells.checkContradictionAt(blackCaseBoard, cell) != null; + blackCell.setCellType(FillapixCellType.BLACK); + ArrayList adjCells = getAdjacentCells(blackCaseBoard, blackCell); + for (FillapixCell adjCell : adjCells) { + if (tooManyBlackCells.checkContradictionAt(blackCaseBoard, adjCell) == null) { + return true; + } + } + return false; + } + + public static boolean isComplete(FillapixBoard board, FillapixCell cell) { + int cellNum = cell.getNumber(); + int cellTouchBlack = 0; + ArrayList adjCells = getAdjacentCells(board, cell); + for (FillapixCell adjCell : adjCells) { + if (adjCell.getType() == FillapixCellType.BLACK) { + cellTouchBlack++; + } + } + return cellNum == cellTouchBlack; + } + + public static boolean hasEmptyAdjacent(FillapixBoard board, FillapixCell cell) { + ArrayList adjCells = getAdjacentCells(board, cell); + for (FillapixCell adjCell : adjCells) { + if (adjCell.getType() == FillapixCellType.UNKNOWN) { + return true; + } + } + return false; + } + + /** + * Gets all cells adjacent to a specific cell. The cell itself will be included. + */ + public static ArrayList getAdjacentCells(FillapixBoard board, FillapixCell 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; + } + FillapixCell adjCell = board.getCell(cellLoc.x + i, cellLoc.y + j); + if (adjCell == null) { + continue; + } + adjCells.add(adjCell); + } + } + return adjCells; + } + + /** + * Gets all cells that are contained in the square defined as having 'distance' + * cells between the center and the outer wall. For example, distance = 1:

+ * |X|X|X|X|X|

+ * |X| | | |X|

+ * |X| |O| |X|

+ * |X| | | |X|

+ * |X|X|X|X|X|

+ * O is 'cell', and all 'X' will be returned in the ArrayList + */ + public static ArrayList getCellsAtDistance(FillapixBoard board, FillapixCell cell, int distance) { + ArrayList adjCells = new ArrayList(); + Point cellLoc = cell.getLocation(); + int i = 0, j = 0; + // top line + for (i = cellLoc.x - (distance), j = cellLoc.y - (distance+1); i <= cellLoc.x + (distance+1); i++) { + if (cellLoc.getX() + i < 0 || cellLoc.y + j < 0 || cellLoc.x + i >= board.getWidth() || cellLoc.y + j >= board.getHeight()) { + continue; + } + FillapixCell adjCell = board.getCell(cellLoc.x + i, cellLoc.y + j); + if (adjCell == null) { + continue; + } + adjCells.add(adjCell); + } + // right line + for (i = cellLoc.x + (distance+1), j = cellLoc.y - (distance); j <= cellLoc.y + (distance+1); j++) { + if (cellLoc.getX() + i < 0 || cellLoc.y + j < 0 || cellLoc.x + i >= board.getWidth() || cellLoc.y + j >= board.getHeight()) { + continue; + } + FillapixCell adjCell = board.getCell(cellLoc.x + i, cellLoc.y + j); + if (adjCell == null) { + continue; + } + adjCells.add(adjCell); + } + // bottom line + for (i = cellLoc.x + (distance), j = cellLoc.y + (distance+1); i <= cellLoc.x - (distance+1); i--) { + if (cellLoc.getX() + i < 0 || cellLoc.y + j < 0 || cellLoc.x + i >= board.getWidth() || cellLoc.y + j >= board.getHeight()) { + continue; + } + FillapixCell adjCell = board.getCell(cellLoc.x + i, cellLoc.y + j); + if (adjCell == null) { + continue; + } + adjCells.add(adjCell); + } + // left line + for (i = cellLoc.x - (distance+1), j = cellLoc.y + (distance); j <= cellLoc.y - (distance+1); j--) { + if (cellLoc.getX() + i < 0 || cellLoc.y + j < 0 || cellLoc.x + i >= board.getWidth() || cellLoc.y + j >= board.getHeight()) { + continue; + } + FillapixCell adjCell = board.getCell(cellLoc.x + i, cellLoc.y + j); + if (adjCell == null) { + continue; + } + adjCells.add(adjCell); + } + + return adjCells; + } + + /** + * Finds all possible combinations of chosenNumObj items can be + * chosen from totalNumObj total items. + * For example, if 1 item is chosen from 2 possible items, the combinations + * are: + *

[ [true,false], [false,true] ]
+ * + * @param totalNumItems the total number of items that can possibly be chosen + * @param chosenNumItems the number of items to be chosen + * + * @return an ArrayList of Boolean arrays. Each index in the ArrayList represents + * a distinct combination. Each Boolean array will be totalNumItems + * long and each index will be true if the corresponding item is + * included in that combination, and false otherwise. + */ + 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 checkBoardForContradiction(FillapixBoard board) { + ContradictionRule tooManyBlack = new TooManyBlackCellsContradictionRule(); + ContradictionRule tooManyWhite = new TooFewBlackCellsContradictionRule(); + for (int i= 0; i < board.getWidth(); i++) { + for (int j=0; j < board.getHeight(); j++) { + if (tooManyBlack.checkContradictionAt(board, board.getCell(i, j)) == null || + tooManyWhite.checkContradictionAt(board, board.getCell(i, j)) == null) { + return true; + } + } + } + return false; } } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java new file mode 100644 index 000000000..7e43fc6b4 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/BlackTile.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.fillapix.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class BlackTile extends PlaceableElement { + public BlackTile() { + super("FPIX-PLAC-0001", "Black Tile", "The black tile", "edu/rpi/legup/images/fillapix/tiles/BlackTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java new file mode 100644 index 000000000..beee70e21 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/NumberTile.java @@ -0,0 +1,27 @@ +package edu.rpi.legup.puzzle.fillapix.elements; + +import edu.rpi.legup.model.elements.NonPlaceableElement; + +public class NumberTile extends NonPlaceableElement { + private int object_num; + + public NumberTile() { + super("FPIX-UNPL-0001", "Number Tile", "A numbered tile", "edu/rpi/legup/images/fillapix/tiles/NumberTile.png"); + object_num = 0; + } + + /** + * @return this object's tile number... + */ + public int getTileNumber() { + return object_num; + } + + /** + * @param num Amount to set tile object to. + */ + public void setTileNumber(int num) { + object_num = num; + } + +} diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java new file mode 100644 index 000000000..ef754782f --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/UnknownTile.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.fillapix.elements; + +import edu.rpi.legup.model.elements.NonPlaceableElement; + +public class UnknownTile extends NonPlaceableElement { + public UnknownTile() { + super("FPIX-UNPL-0002", "Unknown Tile", "A blank tile", "edu/rpi/legup/images/fillapix/tiles/UnknownTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java new file mode 100644 index 000000000..dd27d2834 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/WhiteTile.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.fillapix.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class WhiteTile extends PlaceableElement { + public WhiteTile() { + super("FPIX-PLAC-0002", "White Tile", "The white tile", "edu/rpi/legup/images/fillapix/tiles/WhiteTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt new file mode 100644 index 000000000..0409fa800 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/elements/fillapix_elements_reference_sheet.txt @@ -0,0 +1,5 @@ +FPIX-PLAC-0001 : BlackTile +FPIX-PLAC-0002 : WhiteTile + +FPIX-UNPL-0001 : NumberTile +FPIX-UNPL-0002 : UnknownTile \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java index fd6b7c022..1e5151c48 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/BlackOrWhiteCaseRule.java @@ -40,13 +40,13 @@ public ArrayList getCases(Board board, PuzzleElement puzzleElement) { Board case1 = board.copy(); FillapixCell cell1 = (FillapixCell) case1.getPuzzleElement(puzzleElement); - cell1.setType(FillapixCellType.BLACK); + cell1.setCellType(FillapixCellType.BLACK); case1.addModifiedData(cell1); cases.add(case1); Board case2 = board.copy(); FillapixCell cell2 = (FillapixCell) case2.getPuzzleElement(puzzleElement); - cell2.setType(FillapixCellType.WHITE); + cell2.setCellType(FillapixCellType.WHITE); case2.addModifiedData(cell2); cases.add(case2); diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithBlackDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithBlackDirectRule.java index afd226693..06a8045ed 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithBlackDirectRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithBlackDirectRule.java @@ -8,6 +8,7 @@ import edu.rpi.legup.puzzle.fillapix.FillapixBoard; import edu.rpi.legup.puzzle.fillapix.FillapixCell; import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; public class FinishWithBlackDirectRule extends DirectRule { public FinishWithBlackDirectRule() { @@ -28,7 +29,7 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem return super.getInvalidUseOfRuleMessage() + ": This cell must be black to be applicable with this rule."; } - if (isForcedBlack(parentBoard, cell)) { + if (FillapixUtilities.isForcedBlack(parentBoard, cell)) { return null; } else { @@ -36,14 +37,6 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem } } - private boolean isForcedBlack(FillapixBoard board, FillapixCell cell) { - TooFewBlackCellsContradictionRule tooManyBlackCells = new TooFewBlackCellsContradictionRule(); - FillapixBoard whiteCaseBoard = board.copy(); - FillapixCell whiteCell = (FillapixCell) whiteCaseBoard.getPuzzleElement(cell); - whiteCell.setType(FillapixCellType.WHITE); - return tooManyBlackCells.checkContradictionAt(whiteCaseBoard, cell) == null; - } - /** * Creates a transition {@link Board} that has this rule applied to it using the {@link TreeNode}. * @@ -55,8 +48,8 @@ public Board getDefaultBoard(TreeNode node) { FillapixBoard fillapixBoard = (FillapixBoard) node.getBoard().copy(); for (PuzzleElement element : fillapixBoard.getPuzzleElements()) { FillapixCell cell = (FillapixCell) element; - if (cell.getType() == FillapixCellType.UNKNOWN && isForcedBlack((FillapixBoard) node.getBoard(), cell)) { - cell.setType(FillapixCellType.BLACK); + if (cell.getType() == FillapixCellType.UNKNOWN && FillapixUtilities.isForcedBlack((FillapixBoard) node.getBoard(), cell)) { + cell.setCellType(FillapixCellType.BLACK); fillapixBoard.addModifiedData(cell); } } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithWhiteDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithWhiteDirectRule.java index ec482d5f7..7e213a59c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithWhiteDirectRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/FinishWithWhiteDirectRule.java @@ -8,10 +8,11 @@ import edu.rpi.legup.puzzle.fillapix.FillapixBoard; import edu.rpi.legup.puzzle.fillapix.FillapixCell; import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; public class FinishWithWhiteDirectRule extends DirectRule { public FinishWithWhiteDirectRule() { - super("FFIX-BASC-0002", + super("FPIX-BASC-0002", "Finish with White", "The remaining unknowns around and on a cell must be white to satisfy the number", "edu/rpi/legup/images/fillapix/rules/FinishWithWhite.png"); @@ -28,7 +29,7 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem return super.getInvalidUseOfRuleMessage() + ": This cell must be white to be applicable with this rule"; } - if (isForcedWhite(parentBoard, cell)) { + if (FillapixUtilities.isForcedWhite(parentBoard, cell)) { return null; } else { @@ -36,14 +37,6 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem } } - private boolean isForcedWhite(FillapixBoard board, FillapixCell cell) { - TooManyBlackCellsContradictionRule tooManyBlackCells = new TooManyBlackCellsContradictionRule(); - FillapixBoard blackCaseBoard = board.copy(); - FillapixCell blackCell = (FillapixCell) blackCaseBoard.getPuzzleElement(cell); - blackCell.setType(FillapixCellType.BLACK); - return tooManyBlackCells.checkContradictionAt(blackCaseBoard, cell) == null; - } - /** * Creates a transition {@link Board} that has this rule applied to it using the {@link TreeNode}. * @@ -55,8 +48,8 @@ public Board getDefaultBoard(TreeNode node) { FillapixBoard fillapixBoard = (FillapixBoard) node.getBoard().copy(); for (PuzzleElement element : fillapixBoard.getPuzzleElements()) { FillapixCell cell = (FillapixCell) element; - if (cell.getType() == FillapixCellType.UNKNOWN && isForcedWhite((FillapixBoard) node.getBoard(), cell)) { - cell.setType(FillapixCellType.WHITE); + if (cell.getType() == FillapixCellType.UNKNOWN && FillapixUtilities.isForcedWhite((FillapixBoard) node.getBoard(), cell)) { + cell.setCellType(FillapixCellType.WHITE); fillapixBoard.addModifiedData(cell); } } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/MirrorDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/MirrorDirectRule.java new file mode 100644 index 000000000..656cedb3f --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/MirrorDirectRule.java @@ -0,0 +1,103 @@ +package edu.rpi.legup.puzzle.fillapix.rules; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.CaseRule; +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.fillapix.FillapixBoard; +import edu.rpi.legup.puzzle.fillapix.FillapixCell; +import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; + +public class MirrorDirectRule extends DirectRule { + public MirrorDirectRule() { + super("FPIX-BASC-0003", + "Mirror", + "Two adjacent clues with the same value must have the same number of black squares in their unshared regions", + "edu/rpi/legup/images/fillapix/rules/Mirror.png"); + } + + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + FillapixBoard board = (FillapixBoard) transition.getBoard(); + FillapixBoard parentBoard = (FillapixBoard) transition.getParents().get(0).getBoard().copy(); + FillapixCell cell = (FillapixCell) board.getPuzzleElement(puzzleElement); + FillapixCell parentCell = (FillapixCell) parentBoard.getPuzzleElement(puzzleElement); + + // cell has to have been empty before + if (parentCell.getType() != FillapixCellType.UNKNOWN) { + return super.getInvalidUseOfRuleMessage(); + } + + // parentBoard cannot have any contradictions + if (FillapixUtilities.checkBoardForContradiction(parentBoard)) { + return super.getInvalidUseOfRuleMessage(); + } + + // find all cells adjacent to cell that are numbered + ArrayList adjCells = FillapixUtilities.getAdjacentCells(parentBoard, parentCell); + ArrayList adjNums = new ArrayList(); + for (int i=0; i < adjCells.size(); i++) { + if ((adjCells.get(i)).getNumber() >= 0 && adjCells.get(i).getNumber() < 10) { + adjNums.add(adjCells.get(i)); + } + } + // the numbered cells must be next to another numbered cell of the same value + Iterator itr = adjNums.iterator(); + while (itr.hasNext()) { + FillapixCell adjNum = itr.next(); + adjCells = FillapixUtilities.getAdjacentCells(parentBoard, adjNum); + boolean found = false; + for (FillapixCell adjCell : adjCells) { + if (adjCell.getNumber() == adjNum.getNumber() && adjCell.getIndex() != adjNum.getIndex()) { + found = true; + } + } + if (!found) { + itr.remove(); + } + } + + // change the color of the parentCell, and check if there exists a valid board + if (cell.getType() == FillapixCellType.BLACK) { + parentCell.setCellType(FillapixCellType.WHITE); + } + else { + parentCell.setCellType(FillapixCellType.BLACK); + } + parentBoard.addModifiedData(parentCell); + CaseRule completeClue = new SatisfyClueCaseRule(); + List caseBoards; + for (FillapixCell adjNum : adjNums) { + caseBoards = completeClue.getCases(parentBoard, adjNum); + boolean found = true; + for (Board b : caseBoards) { + if (!FillapixUtilities.checkBoardForContradiction((FillapixBoard) b)) { + found = false; + } + } + if (found) { + return null; + } + } + + return super.getInvalidUseOfRuleMessage(); + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/NonTouchingSharedDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/NonTouchingSharedDirectRule.java new file mode 100644 index 000000000..fe94dbcb4 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/NonTouchingSharedDirectRule.java @@ -0,0 +1,99 @@ +package edu.rpi.legup.puzzle.fillapix.rules; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.CaseRule; +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.fillapix.FillapixBoard; +import edu.rpi.legup.puzzle.fillapix.FillapixCell; +import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; + +public class NonTouchingSharedDirectRule extends DirectRule { + public NonTouchingSharedDirectRule() { + super("FPIX-BASC-0005", + "NonTouching Shared", + "Clues with shared cells have the same difference in black cells in their unshared regions as the difference in their numbers", + "edu/rpi/legup/images/fillapix/rules/TouchingSides.png"); + } + + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + FillapixBoard board = (FillapixBoard) transition.getBoard(); + FillapixBoard parentBoard = (FillapixBoard) transition.getParents().get(0).getBoard().copy(); + FillapixCell cell = (FillapixCell) board.getPuzzleElement(puzzleElement); + FillapixCell parentCell = (FillapixCell) parentBoard.getPuzzleElement(puzzleElement); + + // cell has to have been empty before + if (parentCell.getType() != FillapixCellType.UNKNOWN) { + return super.getInvalidUseOfRuleMessage(); + } + + // parentBoard cannot have any contradictions + if (FillapixUtilities.checkBoardForContradiction(parentBoard)) { + return super.getInvalidUseOfRuleMessage(); + } + + // get all adjCells that have a number + ArrayList adjCells = FillapixUtilities.getAdjacentCells(parentBoard, parentCell); + adjCells.removeIf(x -> x.getNumber() < 0 || x.getNumber() >= 10); + /* remove any number cell that does not have another number cell not + * touching, but sharing cells */ + Iterator itr = adjCells.iterator(); + while (itr.hasNext()) { + ArrayList sharingCells = FillapixUtilities.getCellsAtDistance(parentBoard, parentCell, 1); + boolean found = false; + for (FillapixCell sharingCell : sharingCells) { + if (sharingCell.getNumber() >= 0 && sharingCell.getNumber() < 10) { + found = true; + } + } + if (!found) { + itr.remove(); + } + } + + // change the cell to the opposite color + if (cell.getType() == FillapixCellType.BLACK) { + parentCell.setCellType(FillapixCellType.WHITE); + } + else { + parentCell.setCellType(FillapixCellType.BLACK); + } + // check for some contradiction in all cases + parentBoard.addModifiedData(parentCell); + CaseRule completeClue = new SatisfyClueCaseRule(); + List caseBoards; + for (FillapixCell adjCell : adjCells) { + caseBoards = completeClue.getCases(parentBoard, adjCell); + boolean found = true; + for (Board b : caseBoards) { + if (!FillapixUtilities.checkBoardForContradiction((FillapixBoard) b)) { + found = false; + } + } + if (found) { + return null; + } + } + + return super.getInvalidUseOfRuleMessage(); + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java new file mode 100644 index 000000000..4520add31 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/SatisfyClueCaseRule.java @@ -0,0 +1,234 @@ +package edu.rpi.legup.puzzle.fillapix.rules; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +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.fillapix.FillapixBoard; +import edu.rpi.legup.puzzle.fillapix.FillapixCell; +import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; + +public class SatisfyClueCaseRule extends CaseRule { + public SatisfyClueCaseRule() { + super("FPIX-CASE-0002", + "Satisfy Clue", + "Each clue must touch that number of squares.", + "edu/rpi/legup/images/fillapix/cases/SatisfyClue.png"); + } + + @Override + public CaseBoard getCaseBoard(Board board) { + FillapixBoard fillapixBoard = (FillapixBoard) board.copy(); + CaseBoard caseBoard = new CaseBoard(fillapixBoard, this); + fillapixBoard.setModifiable(false); + for (PuzzleElement data : fillapixBoard.getPuzzleElements()) { + FillapixCell cell = (FillapixCell) data; + if (cell.getNumber() >= 0 && cell.getNumber() <= 9 && FillapixUtilities.hasEmptyAdjacent(fillapixBoard, cell)) { + caseBoard.addPickableElement(data); + } + } + return caseBoard; + } + + @Override + public ArrayList getCases(Board board, PuzzleElement puzzleElement) { + ArrayList cases = new ArrayList(); + + // get value of cell + FillapixBoard fillapixBoard = (FillapixBoard) board.copy(); + FillapixCell cell = (FillapixCell) fillapixBoard.getPuzzleElement(puzzleElement); + int cellMaxBlack = cell.getNumber(); + if (cellMaxBlack < 0 || cellMaxBlack > 9) { // cell is not valid cell + return null; + } + + // find number of black & empty squares + int cellNumBlack = 0; + int cellNumEmpty = 0; + ArrayList emptyCells = new ArrayList(); + ArrayList adjCells = FillapixUtilities.getAdjacentCells(fillapixBoard, cell); + for (FillapixCell adjCell : adjCells) { + if (adjCell.getType() == FillapixCellType.BLACK) { + cellNumBlack++; + } + if (adjCell.getType() == FillapixCellType.UNKNOWN) { + cellNumEmpty++; + emptyCells.add(adjCell); + } + } + // no cases if no empty or if too many black already + if (cellNumBlack > cellMaxBlack || cellNumEmpty == 0) { + return cases; + } + + // generate all cases as boolean expressions + ArrayList combinations; + combinations = FillapixUtilities.getCombinations(cellMaxBlack - cellNumBlack, cellNumEmpty); + + for (int i=0; i < combinations.size(); i++) { + Board case_ = board.copy(); + for (int j=0; j < combinations.get(i).length; j++) { + cell = (FillapixCell) case_.getPuzzleElement(emptyCells.get(j)); + if (combinations.get(i)[j]) { + cell.setCellType(FillapixCellType.BLACK); + } + else { + cell.setCellType(FillapixCellType.WHITE); + } + 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 squaress 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 = ((FillapixCell) 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 */ + FillapixBoard board = (FillapixBoard) transition.getParents().get(0).getBoard(); + Set possibleCenters = new TreeSet(); + possibleCenters.addAll(FillapixUtilities.getAdjacentCells(board, (FillapixCell) modCells.iterator().next())); + for (PuzzleElement modCell : modCells) { + possibleCenters.retainAll((FillapixUtilities.getAdjacentCells(board, (FillapixCell) modCell))); + } + // removing all elements without a valid number + possibleCenters.removeIf(x -> x.getNumber() < 0 || x.getNumber() >= 10); + if (possibleCenters.isEmpty()) { + return super.getInvalidUseOfRuleMessage(); + } + + + /* Now go through the remaining centers, and check if their combinations + * match the transitions */ + for (FillapixCell possibleCenter : possibleCenters) { + int numBlack = 0; + int numEmpty = 0; + int maxBlack = possibleCenter.getNumber(); + for (FillapixCell adjCell : FillapixUtilities.getAdjacentCells(board, possibleCenter)) { + if (adjCell.getType() == FillapixCellType.BLACK) { + numBlack++; + } + if (adjCell.getType() == FillapixCellType.UNKNOWN) { + numEmpty++; + } + } + if (numEmpty <= 0 || numBlack > maxBlack) { + // this cell has no cases (no empty) or is already broken (too many black) + continue; + } + + ArrayList combinations = FillapixUtilities.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 */ + FillapixBoard transBoard = (FillapixBoard) trans.getBoard(); + ArrayList transModCells = new ArrayList(); + for (PuzzleElement modCell : modCells) { + transModCells.add((FillapixCell) transBoard.getPuzzleElement(modCell)); + } + + boolean[] translatedModCells = new boolean[transModCells.size()]; + for (int i=0; i < transModCells.size(); i++) { + if (transModCells.get(i).getType() == FillapixCellType.BLACK) { + 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooFewBlackCellsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooFewBlackCellsContradictionRule.java index e8dd32860..c37050978 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooFewBlackCellsContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooFewBlackCellsContradictionRule.java @@ -6,8 +6,9 @@ import edu.rpi.legup.puzzle.fillapix.FillapixBoard; import edu.rpi.legup.puzzle.fillapix.FillapixCell; import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; -import java.awt.*; +import java.util.ArrayList; public class TooFewBlackCellsContradictionRule extends ContradictionRule { @@ -31,22 +32,24 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { FillapixBoard fillapixBoard = (FillapixBoard) board; FillapixCell cell = (FillapixCell) fillapixBoard.getPuzzleElement(puzzleElement); - Point loc = cell.getLocation(); - for (int i = -1; i < 2; i++) { - for (int j = -1; j < 2; j++) { - FillapixCell adjCell = fillapixBoard.getCell(loc.x + i, loc.y + j); - if (adjCell != null) { - int cellNum = adjCell.getNumber(); - if (cellNum >= 0) { - int numBlackCells = fillapixBoard.getNumCells(adjCell, FillapixCellType.BLACK); - int numUnknownCells = fillapixBoard.getNumCells(adjCell, FillapixCellType.UNKNOWN); - if (numBlackCells + numUnknownCells < cellNum) { - return null; - } - } - } + int cellNum = cell.getNumber(); + if (cellNum < 0 || cellNum >= 10) { + return super.getNoContradictionMessage(); + } + int numBlack = 0, numEmpty = 0; + ArrayList adjCells = FillapixUtilities.getAdjacentCells(fillapixBoard, cell); + for (FillapixCell adjCell : adjCells) { + if (adjCell.getType() == FillapixCellType.BLACK) { + numBlack++; + } + if (adjCell.getType() == FillapixCellType.UNKNOWN) { + numEmpty++; } } + if (numBlack + numEmpty < cellNum) { + return null; + } + return super.getNoContradictionMessage(); } } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooManyBlackCellsContradictionRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooManyBlackCellsContradictionRule.java index 5a1192cc9..68395ce7f 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooManyBlackCellsContradictionRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TooManyBlackCellsContradictionRule.java @@ -6,8 +6,9 @@ import edu.rpi.legup.puzzle.fillapix.FillapixBoard; import edu.rpi.legup.puzzle.fillapix.FillapixCell; import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; -import java.awt.*; +import java.util.ArrayList; public class TooManyBlackCellsContradictionRule extends ContradictionRule { @@ -31,21 +32,21 @@ public String checkContradictionAt(Board board, PuzzleElement puzzleElement) { FillapixBoard fillapixBoard = (FillapixBoard) board; FillapixCell cell = (FillapixCell) fillapixBoard.getPuzzleElement(puzzleElement); - Point loc = cell.getLocation(); - for (int i = -1; i < 2; i++) { - for (int j = -1; j < 2; j++) { - FillapixCell adjCell = fillapixBoard.getCell(loc.x + i, loc.y + j); - if (adjCell != null) { - int cellNum = adjCell.getNumber(); - if (cellNum >= 0) { - int numBlackCells = fillapixBoard.getNumCells(adjCell, FillapixCellType.BLACK); - if (numBlackCells > cellNum) { - return null; - } - } - } + int cellNum = cell.getNumber(); + if (cellNum < 0 || cellNum >= 10) { + return super.getNoContradictionMessage(); + } + int numBlack = 0; + ArrayList adjCells = FillapixUtilities.getAdjacentCells(fillapixBoard, cell); + for (FillapixCell adjCell : adjCells) { + if (adjCell.getType() == FillapixCellType.BLACK) { + numBlack++; } } + if (numBlack > cellNum) { + return null; + } + return super.getNoContradictionMessage(); } } diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TouchingCornersDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TouchingCornersDirectRule.java new file mode 100644 index 000000000..6f4be7842 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TouchingCornersDirectRule.java @@ -0,0 +1,108 @@ +package edu.rpi.legup.puzzle.fillapix.rules; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.CaseRule; +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.fillapix.FillapixBoard; +import edu.rpi.legup.puzzle.fillapix.FillapixCell; +import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; + +public class TouchingCornersDirectRule extends DirectRule { + public TouchingCornersDirectRule() { + super("FPIX-BASC-0005", + "Touching Corners", + "Clues with touching corners have the same difference in black cells in their unshared regions as the difference in their numbers", + "edu/rpi/legup/images/fillapix/rules/TouchingCorners.png"); + } + + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + FillapixBoard board = (FillapixBoard) transition.getBoard(); + FillapixBoard parentBoard = (FillapixBoard) transition.getParents().get(0).getBoard().copy(); + FillapixCell cell = (FillapixCell) board.getPuzzleElement(puzzleElement); + FillapixCell parentCell = (FillapixCell) parentBoard.getPuzzleElement(puzzleElement); + + // cell has to have been empty before + if (parentCell.getType() != FillapixCellType.UNKNOWN) { + return super.getInvalidUseOfRuleMessage(); + } + + // parentBoard cannot have any contradictions + if (FillapixUtilities.checkBoardForContradiction(parentBoard)) { + return super.getInvalidUseOfRuleMessage(); + } + + // get all adjCells that have a number + ArrayList adjCells = FillapixUtilities.getAdjacentCells(parentBoard, parentCell); + adjCells.removeIf(x -> x.getNumber() < 0 || x.getNumber() >= 10); + /* remove any number cell that does not have another number cell diagonally + * adjacent to it on the opposite side of the modified cell */ + Iterator itr = adjCells.iterator(); + while (itr.hasNext()) { + FillapixCell adjCell = itr.next(); + + boolean found = false; + ArrayList adjAdjCells = FillapixUtilities.getAdjacentCells(parentBoard, adjCell); + for (FillapixCell adjAdjCell : adjAdjCells) { + if (adjAdjCell.getLocation().x != adjCell.getLocation().x && + adjAdjCell.getLocation().y != adjCell.getLocation().y && + adjAdjCell.getNumber() >= 0 && adjAdjCell.getNumber() < 10 && + adjAdjCell.getIndex() != parentCell.getIndex()) { + // adjAdjCell is diagonally adjacent to adjCell && it has a + // number && it is not parentCell + found = true; + } + } + + // does not qualify for this rule + if (!found) { + itr.remove(); + } + } + + // change the cell to the opposite color + if (cell.getType() == FillapixCellType.BLACK) { + parentCell.setCellType(FillapixCellType.WHITE); + } + else { + parentCell.setCellType(FillapixCellType.BLACK); + } + // check for some contradiction in all cases + parentBoard.addModifiedData(parentCell); + CaseRule completeClue = new SatisfyClueCaseRule(); + List caseBoards; + for (FillapixCell adjCell : adjCells) { + caseBoards = completeClue.getCases(parentBoard, adjCell); + boolean found = true; + for (Board b : caseBoards) { + if (!FillapixUtilities.checkBoardForContradiction((FillapixBoard) b)) { + found = false; + } + } + if (found) { + return null; + } + } + + return super.getInvalidUseOfRuleMessage(); + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TouchingSidesDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TouchingSidesDirectRule.java new file mode 100644 index 000000000..bd6dd0169 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/TouchingSidesDirectRule.java @@ -0,0 +1,115 @@ +package edu.rpi.legup.puzzle.fillapix.rules; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import edu.rpi.legup.model.gameboard.Board; +import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.model.rules.CaseRule; +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.fillapix.FillapixBoard; +import edu.rpi.legup.puzzle.fillapix.FillapixCell; +import edu.rpi.legup.puzzle.fillapix.FillapixCellType; +import edu.rpi.legup.puzzle.fillapix.FillapixUtilities; + +public class TouchingSidesDirectRule extends DirectRule { + public TouchingSidesDirectRule() { + super("FPIX-BASC-0004", + "Touching Sides", + "Clues with touching sides have the same difference in black cells in their unshared regions as the difference in their numbers", + "edu/rpi/legup/images/fillapix/rules/TouchingSides.png"); + } + + @Override + public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { + FillapixBoard board = (FillapixBoard) transition.getBoard(); + FillapixBoard parentBoard = (FillapixBoard) transition.getParents().get(0).getBoard().copy(); + FillapixCell cell = (FillapixCell) board.getPuzzleElement(puzzleElement); + FillapixCell parentCell = (FillapixCell) parentBoard.getPuzzleElement(puzzleElement); + + // cell has to have been empty before + if (parentCell.getType() != FillapixCellType.UNKNOWN) { + return super.getInvalidUseOfRuleMessage(); + } + + // parentBoard cannot have any contradictions + if (FillapixUtilities.checkBoardForContradiction(parentBoard)) { + return super.getInvalidUseOfRuleMessage(); + } + + // get all adjCells that have a number + ArrayList adjCells = FillapixUtilities.getAdjacentCells(parentBoard, parentCell); + adjCells.removeIf(x -> x.getNumber() < 0 || x.getNumber() >= 10); + /* remove any number cell that does not have another number cell adjacent + * to it on the opposite side of the modified cell */ + Iterator itr = adjCells.iterator(); + while (itr.hasNext()) { + // calculate x and y offset of adjCell from cell + FillapixCell adjCell = itr.next(); + int xOffset = adjCell.getLocation().x - cell.getLocation().x; + int yOffset = adjCell.getLocation().y - cell.getLocation().y; + + boolean found = false; + // check vertically for numbered cell in opposite direction of cell + if (adjCell.getLocation().x + xOffset >= 0 && adjCell.getLocation().x < parentBoard.getWidth()) { + int adjNum = parentBoard.getCell(adjCell.getLocation().x + xOffset, adjCell.getLocation().y).getNumber(); + if (adjNum >= 0 && adjNum < 10) { + found = true; + } + } + // check horizontally for numbered cell in opposite direction of cell + if (adjCell.getLocation().y + yOffset >= 0 && adjCell.getLocation().y < parentBoard.getHeight()) { + int adjNum = parentBoard.getCell(adjCell.getLocation().x, adjCell.getLocation().y + yOffset).getNumber(); + if (adjNum >= 0 && adjNum < 10) { + found = true; + } + } + + // if no horizontally or vertically adjacent cell on opposite side of 'cell' has number, + // then adjCell is not valid, so should be removed + if (!found) { + itr.remove(); + } + } + + // change the cell to the opposite color + if (cell.getType() == FillapixCellType.BLACK) { + parentCell.setCellType(FillapixCellType.WHITE); + } + else { + parentCell.setCellType(FillapixCellType.BLACK); + } + // check for some contradiction in all cases + parentBoard.addModifiedData(parentCell); + CaseRule completeClue = new SatisfyClueCaseRule(); + List caseBoards; + for (FillapixCell adjCell : adjCells) { + caseBoards = completeClue.getCases(parentBoard, adjCell); + boolean found = true; + for (Board b : caseBoards) { + if (!FillapixUtilities.checkBoardForContradiction((FillapixBoard) b)) { + found = false; + } + } + if (found) { + return null; + } + } + + return super.getInvalidUseOfRuleMessage(); + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/fillapix_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/fillapix_reference_sheet.txt index b6172e7fb..2e3c96a86 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/fillapix_reference_sheet.txt +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/rules/fillapix_reference_sheet.txt @@ -1,7 +1,11 @@ FPIX-BASC-0001 : FinishWithBlackDirectRule FPIX-BASC-0002 : FinishWithWhiteDirectRule +FPIX-BASC-0003 : MirrorDirectRule +FPIX-BASC-0004 : TouchingSidesDirectRule +FPIX-BASC-0005 : TouchingCornersDirectRule FPIX-CONT-0001 : TooFewBlackCellsContradictionRule FPIX-CONT-0002 : TooManyBlackCellsContradictionRule -FPIX-CASE-0001 : BlackOrWhiteCaseRule \ No newline at end of file +FPIX-CASE-0001 : BlackOrWhiteCaseRule +FPIX-CASE-0002 : CompleteClueCaseRule \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/SurroundRegionDirectRule.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/SurroundRegionDirectRule.java index b77f8a79f..d992fd22c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/SurroundRegionDirectRule.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/rules/SurroundRegionDirectRule.java @@ -3,12 +3,20 @@ 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.rules.ContradictionRule; import edu.rpi.legup.model.tree.TreeNode; import edu.rpi.legup.model.tree.TreeTransition; import edu.rpi.legup.puzzle.nurikabe.NurikabeBoard; import edu.rpi.legup.puzzle.nurikabe.NurikabeCell; import edu.rpi.legup.puzzle.nurikabe.NurikabeType; +import edu.rpi.legup.puzzle.nurikabe.NurikabeUtilities; +import edu.rpi.legup.utility.DisjointSets; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.HashSet; +import java.util.Set; +import java.awt.*; public class SurroundRegionDirectRule extends DirectRule { @@ -29,7 +37,6 @@ public SurroundRegionDirectRule() { */ @Override public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) { - ContradictionRule contraRule = new TooManySpacesContradictionRule(); NurikabeBoard destBoardState = (NurikabeBoard) transition.getBoard(); NurikabeBoard origBoardState = (NurikabeBoard) transition.getParents().get(0).getBoard(); @@ -44,12 +51,35 @@ public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElem NurikabeCell modCell = (NurikabeCell) modified.getPuzzleElement(puzzleElement); modCell.setData(NurikabeType.WHITE.toValue()); - if (contraRule.checkContradiction(modified) == null) { - return null; - } - else { - return "Does not follow from this rule at this index"; + if(cell.getType() == NurikabeType.BLACK) { + DisjointSets regions = NurikabeUtilities.getNurikabeRegions(destBoardState); + Set adj = new HashSet<>(); //set to hold adjacent cells + Point loc = cell.getLocation(); //position of placed cell + List directions = Arrays.asList(new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1)); + for(Point direction : directions) { + NurikabeCell curr = destBoardState.getCell(loc.x + direction.x, loc.y + direction.y); + if(curr != null) { + if(curr.getType() == NurikabeType.WHITE || curr.getType() == NurikabeType.NUMBER) { + adj.add(curr); //adds cells to adj only if they are white or number blocks + } + } + } + List numberedCells = new ArrayList<>(); //number value of number cells + for (NurikabeCell c : adj) { //loops through adjacent cells + Set disRow = regions.getSet(c); //set of white spaces + for (NurikabeCell d : disRow) { //loops through white spaces + if (d.getType() == NurikabeType.NUMBER) { //if the white space is a number + numberedCells.add(d); //add that number to numberedCells + } + } + } + for (NurikabeCell number : numberedCells) { //loops through numberedCells + if (regions.getSet(number).size() == number.getData()) { //if that cells white area is the exact + return null; //size of the number of one of the number cells within that set + } + } } + return "Does not follow from this rule at this index"; } /** diff --git a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java index fa08972ed..ecf59146d 100644 --- a/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java +++ b/src/main/java/edu/rpi/legup/ui/proofeditorui/treeview/TreeView.java @@ -1,784 +1,756 @@ -package edu.rpi.legup.ui.proofeditorui.treeview; - -import edu.rpi.legup.app.GameBoardFacade; -import edu.rpi.legup.controller.TreeController; -import edu.rpi.legup.model.observer.ITreeListener; -import edu.rpi.legup.model.tree.Tree; -import edu.rpi.legup.model.tree.TreeElement; -import edu.rpi.legup.model.tree.TreeNode; -import edu.rpi.legup.model.tree.TreeTransition; -import edu.rpi.legup.ui.ScrollView; -import edu.rpi.legup.utility.DisjointSets; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.*; -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import static edu.rpi.legup.model.tree.TreeElementType.NODE; -import static edu.rpi.legup.model.tree.TreeElementType.TRANSITION; -import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.DIAMETER; -import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.RADIUS; - -public class TreeView extends ScrollView implements ITreeListener { - private final static Logger LOGGER = LogManager.getLogger(TreeView.class.getName()); - - private static final int TRANS_GAP = 5; - - private static final int NODE_GAP_WIDTH = 70; - private static final int NODE_GAP_HEIGHT = 15; - - private static final int BORDER_GAP_HEIGHT = 20; - private static final int BORDER_GAP_WIDTH = 20; - - private static final int BORDER_SPACING = 100; - - private TreeNodeView nodeHover; - - private ArrayList currentStateBoxes; - private Rectangle bounds = new Rectangle(0, 0, 0, 0); - - private Tree tree; - private TreeNodeView rootNodeView; - private Map viewMap; - private Dimension dimension; - - private TreeViewSelection selection; - - public TreeView(TreeController treeController) { - super(treeController); - currentStateBoxes = new ArrayList<>(); - setSize(dimension = new Dimension(100, 200)); - setPreferredSize(new Dimension(640, 160)); - - viewMap = new HashMap<>(); - - selection = new TreeViewSelection(); - } - - public TreeViewSelection getSelection() { - return selection; - } - - /** - * Gets the tree node puzzleElement that the mouse is hovering over - * - * @return tree node puzzleElement that the mouse is hovering over - */ - public TreeNodeView getNodeHover() { - return nodeHover; - } - - /** - * Sets the tree node puzzleElement that the mouse is hovering over - * - * @param nodeHover tree node puzzleElement the mouse is hovering over - */ - public void setNodeHover(TreeNodeView nodeHover) { - this.nodeHover = nodeHover; - } - - /** - * Gets the TreeElementView by the specified point or null if no view exists at the specified point - * - * @param point location to query for a view - * @return TreeElementView at the point specified, otherwise null - */ - public TreeElementView getTreeElementView(Point point) { - return getTreeElementView(point, rootNodeView); - } - - /** - * Recursively gets the TreeElementView by the specified point or null if no view exists at the specified point or - * the view specified is null - * - * @param point location to query for a view - * @param elementView view to determine if the point is contained within it - * @return TreeElementView at the point specified, otherwise null - */ - private TreeElementView getTreeElementView(Point point, TreeElementView elementView) { - if (elementView == null) { - return null; - } - else { - if (elementView.contains(point) && elementView.isVisible()) { - if (elementView.getType() == NODE && ((TreeNodeView) elementView).isContradictoryState()) { - return null; - } - return elementView; - } - else { - if (elementView.getType() == NODE) { - TreeNodeView nodeView = (TreeNodeView) elementView; - for (TreeTransitionView transitionView : nodeView.getChildrenViews()) { - TreeElementView view = getTreeElementView(point, transitionView); - if (view != null) { - return view; - } - } - } - else { - TreeTransitionView transitionView = (TreeTransitionView) elementView; - return getTreeElementView(point, transitionView.getChildView()); - } - } - } - return null; - } - - public void updateTreeView(Tree tree) { - this.tree = tree; - if (selection.getSelectedViews().size() == 0) { - selection.newSelection(new TreeNodeView(tree.getRootNode())); - } - repaint(); - } - - /** - * Sets the tree associated with this TreeView - * - * @param tree tree - */ - public void setTree(Tree tree) { - this.tree = tree; - } - - public void updateTreeSize() { - if (GameBoardFacade.getInstance().getTree() == null) { - return; - } - setSize(bounds.getSize()); - } - - public void reset() { - if (bounds.x != 0 || bounds.y != 0) { - updateTreeSize(); - } - } - - public void zoomFit() { - double fitWidth = (viewport.getWidth() - 8.0) / (getSize().width - 200); - double fitHeight = (viewport.getHeight() - 8.0) / (getSize().height - 120); - zoomTo(Math.min(fitWidth, fitHeight)); - viewport.setViewPosition(new Point(0, viewport.getHeight() / 2)); - } - - /** - * Creates a customized viewport for the scroll pane - * - * @return viewport for the scroll pane - */ - @Override - protected JViewport createViewport() { - return new JViewport() { - @Override - protected LayoutManager createLayoutManager() { - return new ViewportLayout() { - @Override - public void layoutContainer(Container parent) { - Point point = viewport.getViewPosition(); - // determine the maximum x and y view positions - int mx = getCanvas().getWidth() - viewport.getWidth(); - int my = getCanvas().getHeight() - viewport.getHeight(); - // obey edge boundaries - if (point.x < 0) { - point.x = 0; - } - if (point.x > mx) { - point.x = mx; - } - if (point.y < 0) { - point.y = 0; - } - if (point.y > my) { - point.y = my; - } - // center margins - if (mx < 0) { - point.x = 0; - } - if (my < 0) { - point.y = my / 2; - } - viewport.setViewPosition(point); - } - }; - } - }; - } - - public void draw(Graphics2D graphics2D) { - currentStateBoxes.clear(); - Tree tree = GameBoardFacade.getInstance().getTree(); - if (tree != null) { - //setSize(bounds.getDimension()); - graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - - drawTree(graphics2D); - - dimension.width += BORDER_SPACING; - setSize(dimension); -// graphics2D.drawRect(0,0, dimension.width, dimension.height); - - if (selection.getHover() != null) { - drawMouseOver(graphics2D); - } - } - } - - public void zoomReset() { - zoomTo(1.0); - viewport.setViewPosition(new Point(0, 0)); - } - - private void redrawTree(Graphics2D graphics2D, TreeNodeView nodeView) { - if (nodeView != null) { - nodeView.draw(graphics2D); - for (TreeTransitionView transitionView : nodeView.getChildrenViews()) { - transitionView.draw(graphics2D); - redrawTree(graphics2D, transitionView.getChildView()); - } - } - } - - public void removeTreeElement(TreeElementView view) { - if (view.getType() == NODE) { - TreeNodeView nodeView = (TreeNodeView) view; - nodeView.getParentView().setChildView(null); - } - else { - TreeTransitionView transitionView = (TreeTransitionView) view; - transitionView.getParentViews().forEach((TreeNodeView n) -> n.removeChildrenView(transitionView)); - } - } - - /** - * When the edu.rpi.legup.user hovers over the transition, draws the corresponding rules image - * - * @param g the graphics to use to draw - */ - public void drawMouseOver(Graphics2D g) { - if (selection.getHover().getType() == TRANSITION && ((TreeTransitionView) selection.getHover()).getTreeElement().isJustified()) { - TreeTransition transition = (TreeTransition) selection.getHover().treeElement; - int imgWidth = 100; - int imgHeight = 100; - - BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB); - image.createGraphics().drawImage(transition.getRule().getImageIcon().getImage(), 0, 0, null); - Point mousePoint = selection.getMousePoint(); - g.drawImage(image, mousePoint.x, mousePoint.y - 50, imgWidth, imgHeight, null); - } - } - - public void resetView() { - this.tree = null; - this.rootNodeView = null; - this.selection.clearSelection(); - this.selection.clearHover(); - } - - /** - * Called when a tree puzzleElement is added to the tree - * - * @param treeElement TreeElement that was added to the tree - */ - @Override - public void onTreeElementAdded(TreeElement treeElement) { - if (treeElement.getType() == NODE) { - addTreeNode((TreeNode) treeElement); - } - else { - addTreeTransition((TreeTransition) treeElement); - } - repaint(); - } - - /** - * Called when a tree puzzleElement is removed from the tree - * - * @param element TreeElement that was removed to the tree - */ - @Override - public void onTreeElementRemoved(TreeElement element) { - if (element.getType() == NODE) { - TreeNode node = (TreeNode) element; - TreeNodeView nodeView = (TreeNodeView) viewMap.get(node); - - nodeView.getParentView().setChildView(null); - removeTreeNode(node); - } - else { - TreeTransition trans = (TreeTransition) element; - TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); - - transView.getParentViews().forEach(n -> n.removeChildrenView(transView)); - removeTreeTransition(trans); - } - repaint(); - } - - /** - * Called when the tree selection was changed - * - * @param selection tree selection that was changed - */ - @Override - public void onTreeSelectionChanged(TreeViewSelection selection) { - this.selection.getSelectedViews().forEach(v -> v.setSelected(false)); - selection.getSelectedViews().forEach(v -> v.setSelected(true)); - - this.selection = selection; - -// List selectedViews = selection.getSelectedViews(); -// Point avg = new Point(); -// for(TreeElementView elementView : selectedViews) { -// if(elementView.getType() == NODE) { -// TreeNodeView nodeView = (TreeNodeView)elementView; -// avg.x += nodeView.getX(); -// avg.y += nodeView.getY(); -// } else { -// TreeTransitionView transitionView = (TreeTransitionView) elementView; -// avg.x += transitionView.getEndX(); -// avg.y += transitionView.getEndY(); -// } -// } -// avg.x /= selectedViews.size(); -// avg.y /= selectedViews.size(); -// -// Point pos = viewport.getViewPosition(); -// if(pos.x < avg.x) { -// pos.x = avg.x - pos.x; -// } -// if(pos.y < avg.y) { -// pos.y = avg.y - pos.y; -// } -// -// viewport.setViewPosition(pos); - - repaint(); - } - - /** - * Called when the model has finished updating the tree. - */ - @Override - public void onUpdateTree() { - repaint(); - } - - /** - * Gets the TreeElementView by the corresponding TreeElement associated with it - * - * @param element TreeElement of the view - * @return TreeElementView of the TreeElement associated with it - */ - public TreeElementView getElementView(TreeElement element) { - return viewMap.get(element); - } - - private void removeTreeNode(TreeNode node) { - viewMap.remove(node); - node.getChildren().forEach(t -> removeTreeTransition(t)); - } - - private void removeTreeTransition(TreeTransition trans) { - viewMap.remove(trans); - if (trans.getChildNode() != null) { - removeTreeNode(trans.getChildNode()); - } - } - - private void addTreeNode(TreeNode node) { - TreeTransition parent = node.getParent(); - - TreeNodeView nodeView = new TreeNodeView(node); - TreeTransitionView parentView = (TreeTransitionView) viewMap.get(parent); - - nodeView.setParentView(parentView); - parentView.setChildView(nodeView); - - viewMap.put(node, nodeView); - - if (!node.getChildren().isEmpty()) { - node.getChildren().forEach(t -> addTreeTransition(t)); - } - } - - private void addTreeTransition(TreeTransition trans) { - List parents = trans.getParents(); - - TreeTransitionView transView = new TreeTransitionView(trans); - for (TreeNode parent : parents) { - TreeNodeView parentNodeView = (TreeNodeView) viewMap.get(parent); - transView.addParentView(parentNodeView); - parentNodeView.addChildrenView(transView); - } - - viewMap.put(trans, transView); - - if (trans.getChildNode() != null) { - addTreeNode(trans.getChildNode()); - } - } - - ///New Draw Methods - - public void drawTree(Graphics2D graphics2D) { - if (tree == null) { - LOGGER.error("Unable to draw tree."); - } - else { - if (rootNodeView == null) { - rootNodeView = new TreeNodeView(tree.getRootNode()); - - LOGGER.debug("Creating new views for tree view."); - createViews(rootNodeView); - - selection.newSelection(rootNodeView); - } - - dimension = new Dimension(0, 0); - calcSpan(rootNodeView); - rootNodeView.setSpan(rootNodeView.getSpan() + DIAMETER + BORDER_SPACING); - - calculateViewLocations(rootNodeView, 0); - dimension.height = (int) rootNodeView.getSpan(); - - redrawTree(graphics2D, rootNodeView); - LOGGER.debug("DrawTree: dimensions - " + dimension.width + "x" + dimension.height); - } - } - - public void createViews(TreeNodeView nodeView) { - if (nodeView != null) { - viewMap.put(nodeView.getTreeElement(), nodeView); - - TreeNode node = nodeView.getTreeElement(); - for (TreeTransition trans : node.getChildren()) { - TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); - if (transView != null) { - nodeView.addChildrenView(transView); - transView.addParentView(nodeView); - break; - } - transView = new TreeTransitionView(trans); - - viewMap.put(transView.getTreeElement(), transView); - - transView.addParentView(nodeView); - nodeView.addChildrenView(transView); - - TreeNode childNode = trans.getChildNode(); - if (childNode != null) { - TreeNodeView childNodeView = new TreeNodeView(childNode); - viewMap.put(childNodeView.getTreeElement(), childNodeView); - - childNodeView.setParentView(transView); - transView.setChildView(childNodeView); - - createViews(childNodeView); - } - } - } - } - - public void calculateViewLocations(TreeNodeView nodeView, int depth) { - nodeView.setDepth(depth); - int xLoc = (NODE_GAP_WIDTH + DIAMETER) * depth + DIAMETER; - nodeView.setX(xLoc); - dimension.width = Math.max(dimension.width, xLoc); - - TreeTransitionView parentTransView = nodeView.getParentView(); - int yLoc = parentTransView == null ? (int) nodeView.getSpan() / 2 : parentTransView.getEndY(); - nodeView.setY(yLoc); - - ArrayList children = nodeView.getChildrenViews(); - switch (children.size()) { - case 0: - break; - case 1: { - TreeTransitionView childView = children.get(0); - - List parentsViews = childView.getParentViews(); - if (parentsViews.size() == 1) { - childView.setEndY(yLoc); - - childView.setDepth(depth); - - Point lineStartPoint = childView.getLineStartPoint(0); - lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2; - lineStartPoint.y = yLoc; - childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2); - - dimension.width = Math.max(dimension.width, childView.getEndX()); - - TreeNodeView childNodeView = childView.getChildView(); - if (childNodeView != null) { - calculateViewLocations(childNodeView, depth + 1); - } - } - else { - if (parentsViews.size() > 1 && parentsViews.get(parentsViews.size() - 1) == nodeView) { - int yAvg = 0; - for (int i = 0; i < parentsViews.size(); i++) { - TreeNodeView parentNodeView = parentsViews.get(i); - depth = Math.max(depth, parentNodeView.getDepth()); - yAvg += parentNodeView.getY(); - - Point lineStartPoint = childView.getLineStartPoint(i); - lineStartPoint.x = parentNodeView.getX() + RADIUS + TRANS_GAP / 2; - lineStartPoint.y = parentNodeView.getY(); - } - yAvg /= parentsViews.size(); - childView.setEndY(yAvg); - - childView.setDepth(depth); - - childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2); - - dimension.width = Math.max(dimension.width, childView.getEndX()); - - TreeNodeView childNodeView = childView.getChildView(); - if (childNodeView != null) { - calculateViewLocations(childNodeView, depth + 1); - } - } - } - break; - } - default: { - int span = 0; - for (TreeTransitionView childView : children) { - span += childView.getSpan(); - } - - span = (int) ((nodeView.getSpan() - span) / 2); - for (int i = 0; i < children.size(); i++) { - TreeTransitionView childView = children.get(i); - - childView.setDepth(depth); - - Point lineStartPoint = childView.getLineStartPoint(0); - lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2; - lineStartPoint.y = yLoc; - childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2); - childView.setEndY(yLoc - (int) (nodeView.getSpan() / 2) + span + (int) (childView.getSpan() / 2)); - - span += childView.getSpan(); - TreeNodeView childNodeView = childView.getChildView(); - if (childNodeView != null) { - calculateViewLocations(childNodeView, depth + 1); - } - } - break; - } - } - } - - public void calcSpan(TreeElementView view) { - if (view.getType() == NODE) { - TreeNodeView nodeView = (TreeNodeView) view; - TreeNode node = nodeView.getTreeElement(); - if (nodeView.getChildrenViews().size() == 0) { - nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - if (nodeView.getChildrenViews().size() == 1) { - TreeTransitionView childView = nodeView.getChildrenViews().get(0); - calcSpan(childView); - if (childView.getParentViews().size() > 1) { - nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - nodeView.setSpan(childView.getSpan()); - } - } - else { - DisjointSets branches = node.findMergingBranches(); - List children = node.getChildren(); - - if (node == children.get(0).getParents().get(0)) { - reorderBranches(node, branches); - ArrayList newChildrenViews = new ArrayList<>(); - for (TreeTransition trans : node.getChildren()) { - newChildrenViews.add((TreeTransitionView) viewMap.get(trans)); - } - nodeView.setChildrenViews(newChildrenViews); - } - - List> mergingSets = branches.getAllSets(); - - double span = 0.0; - for (Set mergeSet : mergingSets) { - if (mergeSet.size() > 1) { - TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet); - TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint); - double subSpan = 0.0; - for (TreeTransition branch : mergeSet) { - TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch); - subCalcSpan(branchView, mergePointView); - subSpan += branchView.getSpan(); - } - calcSpan(mergePointView); - span += Math.max(mergePointView.getSpan(), subSpan); - } - else { - TreeTransition trans = mergeSet.iterator().next(); - TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); - calcSpan(transView); - span += transView.getSpan(); - } - } - nodeView.setSpan(span); - } - } - } - else { - TreeTransitionView transView = (TreeTransitionView) view; - TreeNodeView nodeView = transView.getChildView(); - if (nodeView == null) { - transView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - calcSpan(nodeView); - transView.setSpan(nodeView.getSpan()); - } - } - } - - /** - * Calculates the sub span of a given sub tree rooted at the specified view and stops at the tree puzzleElement view - * specified as stop. Stop tree puzzleElement is NOT included in the span calculation - * - * @param view - * @param stop - */ - private void subCalcSpan(TreeElementView view, TreeElementView stop) { - //safe-guard for infinite loop - if (view == stop) { - return; - } - - if (view.getType() == NODE) { - TreeNodeView nodeView = (TreeNodeView) view; - TreeNode node = nodeView.getTreeElement(); - if (nodeView.getChildrenViews().size() == 0) { - nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - if (nodeView.getChildrenViews().size() == 1) { - TreeTransitionView childView = nodeView.getChildrenViews().get(0); - if (childView == stop) { - nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - subCalcSpan(childView, stop); - if (childView.getParentViews().size() > 1) { - nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - nodeView.setSpan(childView.getSpan()); - } - } - } - else { - DisjointSets branches = node.findMergingBranches(); - List children = node.getChildren(); - - if (node == children.get(0).getParents().get(0)) { - reorderBranches(node, branches); - } - - List> mergingSets = branches.getAllSets(); - - double span = 0.0; - for (Set mergeSet : mergingSets) { - if (mergeSet.size() > 1) { - TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet); - TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint); - double subSpan = 0.0; - for (TreeTransition branch : mergeSet) { - TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch); - subCalcSpan(branchView, mergePointView); - subSpan += branchView.getSpan(); - } - subCalcSpan(mergePointView, stop); - span += Math.max(mergePointView.getSpan(), subSpan); - } - else { - TreeTransition trans = mergeSet.iterator().next(); - TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); - subCalcSpan(transView, stop); - span += transView.getSpan(); - } - } - - nodeView.setSpan(span); - } - } - } - else { - TreeTransitionView transView = (TreeTransitionView) view; - TreeNodeView nodeView = transView.getChildView(); - if (nodeView == null || nodeView == stop) { - transView.setSpan(DIAMETER + NODE_GAP_HEIGHT); - } - else { - calcSpan(nodeView); - transView.setSpan(nodeView.getSpan()); - } - } - } - - /** - * Reorders branches such that merging branches are sequentially grouped together and transitions are kept in - * relative order in the list of child transitions of the specified node - * - * @param node root node of the branches - * @param branches DisjointSets of the child branches of the specified node which determine which branches merge - */ - private void reorderBranches(TreeNode node, DisjointSets branches) { - List children = node.getChildren(); - List> mergingSets = branches.getAllSets(); - - List> newOrder = new ArrayList<>(); - for (Set set : mergingSets) { - List mergeBranch = new ArrayList<>(); - newOrder.add(mergeBranch); - children.forEach(t -> { - if (set.contains(t)) { - mergeBranch.add(t); - } - }); - mergeBranch.sort((TreeTransition t1, TreeTransition t2) -> - children.indexOf(t1) <= children.indexOf(t2) ? -1 : 1); - } - - newOrder.sort((List b1, List b2) -> { - int low1 = -1; - int low2 = -1; - for (TreeTransition t1 : b1) { - int curIndex = children.indexOf(t1); - if (low1 == -1 || curIndex < low1) { - low1 = curIndex; - } - } - for (TreeTransition t1 : b2) { - int curIndex = children.indexOf(t1); - if (low1 == -1 || curIndex < low1) { - low1 = curIndex; - } - } - return low1 < low2 ? -1 : 1; - }); - - List newChildren = new ArrayList<>(); - newOrder.forEach(l -> newChildren.addAll(l)); - node.setChildren(newChildren); - } +package edu.rpi.legup.ui.proofeditorui.treeview; + +import edu.rpi.legup.app.GameBoardFacade; +import edu.rpi.legup.controller.TreeController; +import edu.rpi.legup.model.observer.ITreeListener; +import edu.rpi.legup.model.tree.Tree; +import edu.rpi.legup.model.tree.TreeElement; +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.ui.ScrollView; +import edu.rpi.legup.utility.DisjointSets; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.*; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import static edu.rpi.legup.model.tree.TreeElementType.NODE; +import static edu.rpi.legup.model.tree.TreeElementType.TRANSITION; +import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.DIAMETER; +import static edu.rpi.legup.ui.proofeditorui.treeview.TreeNodeView.RADIUS; + +public class TreeView extends ScrollView implements ITreeListener { + private final static Logger LOGGER = LogManager.getLogger(TreeView.class.getName()); + + private static final int TRANS_GAP = 5; + + private static final int NODE_GAP_WIDTH = 70; + private static final int NODE_GAP_HEIGHT = 15; + + private static final int BORDER_GAP_HEIGHT = 20; + private static final int BORDER_GAP_WIDTH = 20; + + private static final int BORDER_SPACING = 100; + + private TreeNodeView nodeHover; + + private ArrayList currentStateBoxes; + private Rectangle bounds = new Rectangle(0, 0, 0, 0); + + private Tree tree; + private TreeNodeView rootNodeView; + private Map viewMap; + private Dimension dimension; + + private TreeViewSelection selection; + + public TreeView(TreeController treeController) { + super(treeController); + currentStateBoxes = new ArrayList<>(); + setSize(dimension = new Dimension(100, 200)); + setPreferredSize(new Dimension(640, 160)); + + viewMap = new HashMap<>(); + + selection = new TreeViewSelection(); + } + + public TreeViewSelection getSelection() { + return selection; + } + + /** + * Gets the tree node puzzleElement that the mouse is hovering over + * + * @return tree node puzzleElement that the mouse is hovering over + */ + public TreeNodeView getNodeHover() { + return nodeHover; + } + + /** + * Sets the tree node puzzleElement that the mouse is hovering over + * + * @param nodeHover tree node puzzleElement the mouse is hovering over + */ + public void setNodeHover(TreeNodeView nodeHover) { + this.nodeHover = nodeHover; + } + + /** + * Gets the TreeElementView by the specified point or null if no view exists at the specified point + * + * @param point location to query for a view + * @return TreeElementView at the point specified, otherwise null + */ + public TreeElementView getTreeElementView(Point point) { + return getTreeElementView(point, rootNodeView); + } + + /** + * Recursively gets the TreeElementView by the specified point or null if no view exists at the specified point or + * the view specified is null + * + * @param point location to query for a view + * @param elementView view to determine if the point is contained within it + * @return TreeElementView at the point specified, otherwise null + */ + private TreeElementView getTreeElementView(Point point, TreeElementView elementView) { + if (elementView == null) { + return null; + } + else { + if (elementView.contains(point) && elementView.isVisible()) { + if (elementView.getType() == NODE && ((TreeNodeView) elementView).isContradictoryState()) { + return null; + } + return elementView; + } + else { + if (elementView.getType() == NODE) { + TreeNodeView nodeView = (TreeNodeView) elementView; + for (TreeTransitionView transitionView : nodeView.getChildrenViews()) { + TreeElementView view = getTreeElementView(point, transitionView); + if (view != null) { + return view; + } + } + } + else { + TreeTransitionView transitionView = (TreeTransitionView) elementView; + return getTreeElementView(point, transitionView.getChildView()); + } + } + } + return null; + } + + public void updateTreeView(Tree tree) { + this.tree = tree; + if (selection.getSelectedViews().size() == 0) { + selection.newSelection(new TreeNodeView(tree.getRootNode())); + } + repaint(); + } + + /** + * Sets the tree associated with this TreeView + * + * @param tree tree + */ + public void setTree(Tree tree) { + this.tree = tree; + } + + public void updateTreeSize() { + if (GameBoardFacade.getInstance().getTree() == null) { + return; + } + setSize(bounds.getSize()); + } + + public void reset() { + if (bounds.x != 0 || bounds.y != 0) { + updateTreeSize(); + } + } + + public void zoomFit() { + double fitWidth = (viewport.getWidth() - 8.0) / (getSize().width - 200); + double fitHeight = (viewport.getHeight() - 8.0) / (getSize().height - 120); + zoomTo(Math.min(fitWidth, fitHeight)); + viewport.setViewPosition(new Point(0, viewport.getHeight() / 2)); + } + + /** + * Creates a customized viewport for the scroll pane + * + * @return viewport for the scroll pane + */ + @Override + protected JViewport createViewport() { + return new JViewport() { + @Override + protected LayoutManager createLayoutManager() { + return new ViewportLayout() { + @Override + public void layoutContainer(Container parent) { + Point point = viewport.getViewPosition(); + // determine the maximum x and y view positions + int mx = getCanvas().getWidth() - viewport.getWidth(); + int my = getCanvas().getHeight() - viewport.getHeight(); + // obey edge boundaries + if (point.x < 0) { + point.x = 0; + } + if (point.x > mx) { + point.x = mx; + } + if (point.y < 0) { + point.y = 0; + } + if (point.y > my) { + point.y = my; + } + // center margins + if (mx < 0) { + point.x = 0; + } + if (my < 0) { + point.y = my / 2; + } + viewport.setViewPosition(point); + } + }; + } + }; + } + + public void draw(Graphics2D graphics2D) { + currentStateBoxes.clear(); + Tree tree = GameBoardFacade.getInstance().getTree(); + if (tree != null) { + //setSize(bounds.getDimension()); + graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + drawTree(graphics2D); + + dimension.width += BORDER_SPACING; + setSize(dimension); +// graphics2D.drawRect(0,0, dimension.width, dimension.height); + + if (selection.getHover() != null) { + drawMouseOver(graphics2D); + } + } + } + + public void zoomReset() { + zoomTo(1.0); + viewport.setViewPosition(new Point(0, 0)); + } + + private void redrawTree(Graphics2D graphics2D, TreeNodeView nodeView) { + if (nodeView != null) { + nodeView.draw(graphics2D); + for (TreeTransitionView transitionView : nodeView.getChildrenViews()) { + transitionView.draw(graphics2D); + redrawTree(graphics2D, transitionView.getChildView()); + } + } + } + + public void removeTreeElement(TreeElementView view) { + if (view.getType() == NODE) { + TreeNodeView nodeView = (TreeNodeView) view; + nodeView.getParentView().setChildView(null); + } + else { + TreeTransitionView transitionView = (TreeTransitionView) view; + transitionView.getParentViews().forEach((TreeNodeView n) -> n.removeChildrenView(transitionView)); + } + } + + /** + * When the edu.rpi.legup.user hovers over the transition, draws the corresponding rules image + * + * @param g the graphics to use to draw + */ + public void drawMouseOver(Graphics2D g) { + if (selection.getHover().getType() == TRANSITION && ((TreeTransitionView) selection.getHover()).getTreeElement().isJustified()) { + TreeTransition transition = (TreeTransition) selection.getHover().treeElement; + int imgWidth = 100; + int imgHeight = 100; + + BufferedImage image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB); + image.createGraphics().drawImage(transition.getRule().getImageIcon().getImage(), 0, 0, null); + Point mousePoint = selection.getMousePoint(); + g.drawImage(image, mousePoint.x, mousePoint.y - 50, imgWidth, imgHeight, null); + } + } + + public void resetView() { + this.tree = null; + this.rootNodeView = null; + this.selection.clearSelection(); + this.selection.clearHover(); + } + + /** + * Called when a tree puzzleElement is added to the tree + * + * @param treeElement TreeElement that was added to the tree + */ + @Override + public void onTreeElementAdded(TreeElement treeElement) { + if (treeElement.getType() == NODE) { + addTreeNode((TreeNode) treeElement); + } + else { + addTreeTransition((TreeTransition) treeElement); + } + repaint(); + } + + /** + * Called when a tree puzzleElement is removed from the tree + * + * @param element TreeElement that was removed to the tree + */ + @Override + public void onTreeElementRemoved(TreeElement element) { + if (element.getType() == NODE) { + TreeNode node = (TreeNode) element; + TreeNodeView nodeView = (TreeNodeView) viewMap.get(node); + + nodeView.getParentView().setChildView(null); + removeTreeNode(node); + } + else { + TreeTransition trans = (TreeTransition) element; + TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); + + transView.getParentViews().forEach(n -> n.removeChildrenView(transView)); + removeTreeTransition(trans); + } + repaint(); + } + + /** + * Called when the tree selection was changed + * + * @param selection tree selection that was changed + */ + @Override + public void onTreeSelectionChanged(TreeViewSelection selection) { + this.selection.getSelectedViews().forEach(v -> v.setSelected(false)); + selection.getSelectedViews().forEach(v -> v.setSelected(true)); + this.selection = selection; + repaint(); + } + + /** + * Called when the model has finished updating the tree. + */ + @Override + public void onUpdateTree() { + repaint(); + } + + /** + * Gets the TreeElementView by the corresponding TreeElement associated with it + * + * @param element TreeElement of the view + * @return TreeElementView of the TreeElement associated with it + */ + public TreeElementView getElementView(TreeElement element) { + return viewMap.get(element); + } + + private void removeTreeNode(TreeNode node) { + viewMap.remove(node); + node.getChildren().forEach(t -> removeTreeTransition(t)); + } + + private void removeTreeTransition(TreeTransition trans) { + viewMap.remove(trans); + if (trans.getChildNode() != null) { + removeTreeNode(trans.getChildNode()); + } + } + + private void addTreeNode(TreeNode node) { + TreeTransition parent = node.getParent(); + + TreeNodeView nodeView = new TreeNodeView(node); + TreeTransitionView parentView = (TreeTransitionView) viewMap.get(parent); + + nodeView.setParentView(parentView); + parentView.setChildView(nodeView); + + viewMap.put(node, nodeView); + + if (!node.getChildren().isEmpty()) { + node.getChildren().forEach(t -> addTreeTransition(t)); + } + } + + private void addTreeTransition(TreeTransition trans) { + List parents = trans.getParents(); + + TreeTransitionView transView = new TreeTransitionView(trans); + for (TreeNode parent : parents) { + TreeNodeView parentNodeView = (TreeNodeView) viewMap.get(parent); + transView.addParentView(parentNodeView); + parentNodeView.addChildrenView(transView); + } + + viewMap.put(trans, transView); + + if (trans.getChildNode() != null) { + addTreeNode(trans.getChildNode()); + } + } + + ///New Draw Methods + + public void drawTree(Graphics2D graphics2D) { + if (tree == null) { + LOGGER.error("Unable to draw tree."); + } + else { + if (rootNodeView == null) { + rootNodeView = new TreeNodeView(tree.getRootNode()); + + LOGGER.debug("Creating new views for tree view."); + createViews(rootNodeView); + + selection.newSelection(rootNodeView); + } + + dimension = new Dimension(0, 0); + calcSpan(rootNodeView); + rootNodeView.setSpan(rootNodeView.getSpan() + DIAMETER + BORDER_SPACING); + + calculateViewLocations(rootNodeView, 0); + dimension.height = (int) rootNodeView.getSpan(); + + redrawTree(graphics2D, rootNodeView); + LOGGER.debug("DrawTree: dimensions - " + dimension.width + "x" + dimension.height); + } + } + + public void createViews(TreeNodeView nodeView) { + if (nodeView != null) { + viewMap.put(nodeView.getTreeElement(), nodeView); + + TreeNode node = nodeView.getTreeElement(); + for (TreeTransition trans : node.getChildren()) { + TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); + if (transView != null) { + nodeView.addChildrenView(transView); + transView.addParentView(nodeView); + break; + } + transView = new TreeTransitionView(trans); + + viewMap.put(transView.getTreeElement(), transView); + + transView.addParentView(nodeView); + nodeView.addChildrenView(transView); + + TreeNode childNode = trans.getChildNode(); + if (childNode != null) { + TreeNodeView childNodeView = new TreeNodeView(childNode); + viewMap.put(childNodeView.getTreeElement(), childNodeView); + + childNodeView.setParentView(transView); + transView.setChildView(childNodeView); + + createViews(childNodeView); + } + } + } + } + + public void calculateViewLocations(TreeNodeView nodeView, int depth) { + nodeView.setDepth(depth); + int xLoc = (NODE_GAP_WIDTH + DIAMETER) * depth + DIAMETER; + nodeView.setX(xLoc); + dimension.width = Math.max(dimension.width, xLoc); + + TreeTransitionView parentTransView = nodeView.getParentView(); + int yLoc = parentTransView == null ? (int) nodeView.getSpan() / 2 : parentTransView.getEndY(); + nodeView.setY(yLoc); + + ArrayList children = nodeView.getChildrenViews(); + switch (children.size()) { + case 0: + break; + case 1: { + TreeTransitionView childView = children.get(0); + + List parentsViews = childView.getParentViews(); + if (parentsViews.size() == 1) { + childView.setEndY(yLoc); + + childView.setDepth(depth); + + Point lineStartPoint = childView.getLineStartPoint(0); + lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2; + lineStartPoint.y = yLoc; + childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2); + + dimension.width = Math.max(dimension.width, childView.getEndX()); + + TreeNodeView childNodeView = childView.getChildView(); + if (childNodeView != null) { + calculateViewLocations(childNodeView, depth + 1); + } + } + else { + if (parentsViews.size() > 1 && parentsViews.get(parentsViews.size() - 1) == nodeView) { + int yAvg = 0; + for (int i = 0; i < parentsViews.size(); i++) { + TreeNodeView parentNodeView = parentsViews.get(i); + depth = Math.max(depth, parentNodeView.getDepth()); + yAvg += parentNodeView.getY(); + + Point lineStartPoint = childView.getLineStartPoint(i); + lineStartPoint.x = parentNodeView.getX() + RADIUS + TRANS_GAP / 2; + lineStartPoint.y = parentNodeView.getY(); + } + yAvg /= parentsViews.size(); + childView.setEndY(yAvg); + + childView.setDepth(depth); + + childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2); + + dimension.width = Math.max(dimension.width, childView.getEndX()); + + TreeNodeView childNodeView = childView.getChildView(); + if (childNodeView != null) { + calculateViewLocations(childNodeView, depth + 1); + } + } + } + break; + } + default: { + int span = 0; + for (TreeTransitionView childView : children) { + span += childView.getSpan(); + } + + span = (int) ((nodeView.getSpan() - span) / 2); + for (int i = 0; i < children.size(); i++) { + TreeTransitionView childView = children.get(i); + + childView.setDepth(depth); + + Point lineStartPoint = childView.getLineStartPoint(0); + lineStartPoint.x = xLoc + RADIUS + TRANS_GAP / 2; + lineStartPoint.y = yLoc; + childView.setEndX((NODE_GAP_WIDTH + DIAMETER) * (depth + 1) + RADIUS - TRANS_GAP / 2); + childView.setEndY(yLoc - (int) (nodeView.getSpan() / 2) + span + (int) (childView.getSpan() / 2)); + + span += childView.getSpan(); + TreeNodeView childNodeView = childView.getChildView(); + if (childNodeView != null) { + calculateViewLocations(childNodeView, depth + 1); + } + } + break; + } + } + } + + public void calcSpan(TreeElementView view) { + if (view.getType() == NODE) { + TreeNodeView nodeView = (TreeNodeView) view; + TreeNode node = nodeView.getTreeElement(); + if (nodeView.getChildrenViews().size() == 0) { + nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + if (nodeView.getChildrenViews().size() == 1) { + TreeTransitionView childView = nodeView.getChildrenViews().get(0); + calcSpan(childView); + if (childView.getParentViews().size() > 1) { + nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + nodeView.setSpan(childView.getSpan()); + } + } + else { + DisjointSets branches = node.findMergingBranches(); + List children = node.getChildren(); + + if (node == children.get(0).getParents().get(0)) { + reorderBranches(node, branches); + ArrayList newChildrenViews = new ArrayList<>(); + for (TreeTransition trans : node.getChildren()) { + newChildrenViews.add((TreeTransitionView) viewMap.get(trans)); + } + nodeView.setChildrenViews(newChildrenViews); + } + + List> mergingSets = branches.getAllSets(); + + double span = 0.0; + for (Set mergeSet : mergingSets) { + if (mergeSet.size() > 1) { + TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet); + TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint); + double subSpan = 0.0; + for (TreeTransition branch : mergeSet) { + TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch); + subCalcSpan(branchView, mergePointView); + subSpan += branchView.getSpan(); + } + calcSpan(mergePointView); + span += Math.max(mergePointView.getSpan(), subSpan); + } + else { + TreeTransition trans = mergeSet.iterator().next(); + TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); + calcSpan(transView); + span += transView.getSpan(); + } + } + nodeView.setSpan(span); + } + } + } + else { + TreeTransitionView transView = (TreeTransitionView) view; + TreeNodeView nodeView = transView.getChildView(); + if (nodeView == null) { + transView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + calcSpan(nodeView); + transView.setSpan(nodeView.getSpan()); + } + } + } + + /** + * Calculates the sub span of a given sub tree rooted at the specified view and stops at the tree puzzleElement view + * specified as stop. Stop tree puzzleElement is NOT included in the span calculation + * + * @param view + * @param stop + */ + private void subCalcSpan(TreeElementView view, TreeElementView stop) { + //safe-guard for infinite loop + if (view == stop) { + return; + } + + if (view.getType() == NODE) { + TreeNodeView nodeView = (TreeNodeView) view; + TreeNode node = nodeView.getTreeElement(); + if (nodeView.getChildrenViews().size() == 0) { + nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + if (nodeView.getChildrenViews().size() == 1) { + TreeTransitionView childView = nodeView.getChildrenViews().get(0); + if (childView == stop) { + nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + subCalcSpan(childView, stop); + if (childView.getParentViews().size() > 1) { + nodeView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + nodeView.setSpan(childView.getSpan()); + } + } + } + else { + DisjointSets branches = node.findMergingBranches(); + List children = node.getChildren(); + + if (node == children.get(0).getParents().get(0)) { + reorderBranches(node, branches); + } + + List> mergingSets = branches.getAllSets(); + + double span = 0.0; + for (Set mergeSet : mergingSets) { + if (mergeSet.size() > 1) { + TreeTransition mergePoint = TreeNode.findMergingPoint(mergeSet); + TreeTransitionView mergePointView = (TreeTransitionView) viewMap.get(mergePoint); + double subSpan = 0.0; + for (TreeTransition branch : mergeSet) { + TreeTransitionView branchView = (TreeTransitionView) viewMap.get(branch); + subCalcSpan(branchView, mergePointView); + subSpan += branchView.getSpan(); + } + subCalcSpan(mergePointView, stop); + span += Math.max(mergePointView.getSpan(), subSpan); + } + else { + TreeTransition trans = mergeSet.iterator().next(); + TreeTransitionView transView = (TreeTransitionView) viewMap.get(trans); + subCalcSpan(transView, stop); + span += transView.getSpan(); + } + } + + nodeView.setSpan(span); + } + } + } + else { + TreeTransitionView transView = (TreeTransitionView) view; + TreeNodeView nodeView = transView.getChildView(); + if (nodeView == null || nodeView == stop) { + transView.setSpan(DIAMETER + NODE_GAP_HEIGHT); + } + else { + calcSpan(nodeView); + transView.setSpan(nodeView.getSpan()); + } + } + } + + /** + * Reorders branches such that merging branches are sequentially grouped together and transitions are kept in + * relative order in the list of child transitions of the specified node + * + * @param node root node of the branches + * @param branches DisjointSets of the child branches of the specified node which determine which branches merge + */ + private void reorderBranches(TreeNode node, DisjointSets branches) { + List children = node.getChildren(); + List> mergingSets = branches.getAllSets(); + + List> newOrder = new ArrayList<>(); + for (Set set : mergingSets) { + List mergeBranch = new ArrayList<>(); + newOrder.add(mergeBranch); + children.forEach(t -> { + if (set.contains(t)) { + mergeBranch.add(t); + } + }); + mergeBranch.sort((TreeTransition t1, TreeTransition t2) -> + children.indexOf(t1) <= children.indexOf(t2) ? -1 : 1); + } + + newOrder.sort((List b1, List b2) -> { + int low1 = -1; + int low2 = -1; + for (TreeTransition t1 : b1) { + int curIndex = children.indexOf(t1); + if (low1 == -1 || curIndex < low1) { + low1 = curIndex; + } + } + for (TreeTransition t1 : b2) { + int curIndex = children.indexOf(t1); + if (low1 == -1 || curIndex < low1) { + low1 = curIndex; + } + } + return low1 < low2 ? -1 : 1; + }); + + List newChildren = new ArrayList<>(); + newOrder.forEach(l -> newChildren.addAll(l)); + node.setChildren(newChildren); + } } \ No newline at end of file diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/cases/BlackOrWhite.png b/src/main/resources/edu/rpi/legup/images/fillapix/cases/BlackOrWhite.png index ff234c17f..17df92f1a 100644 Binary files a/src/main/resources/edu/rpi/legup/images/fillapix/cases/BlackOrWhite.png and b/src/main/resources/edu/rpi/legup/images/fillapix/cases/BlackOrWhite.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/cases/SatisfyClue.png b/src/main/resources/edu/rpi/legup/images/fillapix/cases/SatisfyClue.png new file mode 100644 index 000000000..8d84efed9 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/cases/SatisfyClue.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooFewBlackCells.png b/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooFewBlackCells.png index b017078a2..b93d81e67 100644 Binary files a/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooFewBlackCells.png and b/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooFewBlackCells.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooManyBlackCells.png b/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooManyBlackCells.png index a0b27424d..393a8c544 100644 Binary files a/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooManyBlackCells.png and b/src/main/resources/edu/rpi/legup/images/fillapix/contradictions/TooManyBlackCells.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithBlack.png b/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithBlack.png index 41e540ad1..d098bce41 100644 Binary files a/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithBlack.png and b/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithBlack.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithWhite.png b/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithWhite.png index 330f705f1..758ca7591 100644 Binary files a/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithWhite.png and b/src/main/resources/edu/rpi/legup/images/fillapix/rules/FinishWithWhite.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/rules/Mirror.png b/src/main/resources/edu/rpi/legup/images/fillapix/rules/Mirror.png new file mode 100644 index 000000000..991408d9c Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/rules/Mirror.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/rules/NontouchingShared.png b/src/main/resources/edu/rpi/legup/images/fillapix/rules/NontouchingShared.png new file mode 100644 index 000000000..1be04f3ce Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/rules/NontouchingShared.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/rules/TouchingCorners.png b/src/main/resources/edu/rpi/legup/images/fillapix/rules/TouchingCorners.png new file mode 100644 index 000000000..f3eba46b6 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/rules/TouchingCorners.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/rules/TouchingSides.png b/src/main/resources/edu/rpi/legup/images/fillapix/rules/TouchingSides.png new file mode 100644 index 000000000..6d3b12dfd Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/rules/TouchingSides.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/tiles/BlackTile.png b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/BlackTile.png new file mode 100644 index 000000000..93e169df8 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/BlackTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/tiles/NumberTile.png b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/NumberTile.png new file mode 100644 index 000000000..5a8540d02 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/NumberTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/tiles/UnknownTile.png b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/UnknownTile.png new file mode 100644 index 000000000..850fbf127 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/UnknownTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/fillapix/tiles/WhiteTile.png b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/WhiteTile.png new file mode 100644 index 000000000..fc2c683eb Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/fillapix/tiles/WhiteTile.png differ diff --git a/src/main/resources/edu/rpi/legup/legup/config b/src/main/resources/edu/rpi/legup/legup/config index 24fdcf365..19e63a2a3 100644 --- a/src/main/resources/edu/rpi/legup/legup/config +++ b/src/main/resources/edu/rpi/legup/legup/config @@ -7,7 +7,7 @@ + fileCreationDisabled="false"/> - \ No newline at end of file + diff --git a/src/test/java/puzzles/shorttruthtable/rules/AndEliminationDirectRuleTest.java b/src/test/java/puzzles/shorttruthtable/rules/AndEliminationDirectRuleTest.java new file mode 100644 index 000000000..0d94eb672 --- /dev/null +++ b/src/test/java/puzzles/shorttruthtable/rules/AndEliminationDirectRuleTest.java @@ -0,0 +1,204 @@ +package puzzles.shorttruthtable.rules; + +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTable; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableBoard; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCell; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCellType; +import edu.rpi.legup.puzzle.shorttruthtable.rules.basic.elimination.DirectRuleAndElimination; +import edu.rpi.legup.save.InvalidFileFormatException; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class AndEliminationDirectRuleTest { + private static final DirectRuleAndElimination RULE = new DirectRuleAndElimination(); + private static ShortTruthTable stt; + + @BeforeClass + public static void setup() { + MockGameBoardFacade.getInstance(); + stt = new ShortTruthTable(); + } + + /** + * Given one statement: B^C where ^ is true + * + * Checks all possible combinations of true, false, and unknown for B and C + * except for where both B and C are true and asserts that each one of them + * is not a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void trueAndTest1() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AndEliminationDirectRule/TrueAnd", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableCellType[] cellTypes = {ShortTruthTableCellType.TRUE, ShortTruthTableCellType.FALSE, ShortTruthTableCellType.UNKNOWN}; + + for (ShortTruthTableCellType cellType1 : cellTypes) { + for (ShortTruthTableCellType cellType2 : cellTypes) { + if (cellType1 == cellType2 && cellType1 == ShortTruthTableCellType.TRUE) { + continue; + } + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + ShortTruthTableCell bonnie = board.getCell(0, 0); + ShortTruthTableCell clyde = board.getCell(2, 0); + + if (cellType1 != ShortTruthTableCellType.UNKNOWN) { + bonnie.setData(cellType1); + board.addModifiedData(bonnie); + } + + if (cellType2 != ShortTruthTableCellType.UNKNOWN) { + clyde.setData(cellType2); + board.addModifiedData(clyde); + } + + Assert.assertNotNull(RULE.checkRule(transition)); + } + } + } + + /** + * Given one statement: B^C where ^ is true + * + * Checks all possible combinations of true and unknown for B and C + * except for where both B and C are unknown and asserts that each one + * of them is a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void trueAndTest2() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AndEliminationDirectRule/TrueAnd", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableCellType[] cellTypes = {ShortTruthTableCellType.TRUE, ShortTruthTableCellType.UNKNOWN}; + + for (ShortTruthTableCellType cellType1 : cellTypes) { + for (ShortTruthTableCellType cellType2 : cellTypes) { + if (cellType1 == cellType2 && cellType1 == ShortTruthTableCellType.UNKNOWN) { + continue; + } + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + ShortTruthTableCell bonnie = board.getCell(0, 0); + ShortTruthTableCell clyde = board.getCell(2, 0); + + if (cellType1 != ShortTruthTableCellType.UNKNOWN) { + bonnie.setData(cellType1); + board.addModifiedData(bonnie); + } + + if (cellType2 != ShortTruthTableCellType.UNKNOWN) { + clyde.setData(cellType2); + board.addModifiedData(clyde); + } + + Assert.assertNull(RULE.checkRule(transition)); + } + } + } + + /** + * Given one statement: B^C where ^ is false + * + * Checks all possible combinations of true, false, and unknown for B and C + * and asserts that each one of them is not a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void falseAndWithUnknownsTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAnd", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableCellType[] cellTypes = {ShortTruthTableCellType.TRUE, ShortTruthTableCellType.FALSE, ShortTruthTableCellType.UNKNOWN}; + + for (ShortTruthTableCellType cellType1 : cellTypes) { + for (ShortTruthTableCellType cellType2 : cellTypes) { + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + ShortTruthTableCell bonnie = board.getCell(0, 0); + ShortTruthTableCell clyde = board.getCell(2, 0); + + if (cellType1 != ShortTruthTableCellType.UNKNOWN) { + bonnie.setData(cellType1); + board.addModifiedData(bonnie); + } + + if (cellType2 != ShortTruthTableCellType.UNKNOWN) { + clyde.setData(cellType2); + board.addModifiedData(clyde); + } + + Assert.assertNotNull(RULE.checkRule(transition)); + } + } + } + + /** + * Given one statement: B^C where both B and ^ are false + * + * Asserts that this is not a valid application of the rule if C is set to + * either true or false. + * + * @throws InvalidFileFormatException + */ + @Test + public void falseAndWithKnownFalseTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownFalse", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell clyde = board.getCell(2, 0); + clyde.setData(ShortTruthTableCellType.TRUE); + board.addModifiedData(clyde); + Assert.assertNotNull(RULE.checkRule(transition)); + + clyde.setData(ShortTruthTableCellType.FALSE); + board.addModifiedData(clyde); + Assert.assertNotNull(RULE.checkRule(transition)); + } + + /** + * Given one statement: B^C where B is true and ^ is false + * + * Asserts that this is a valid application of the rule if and only if C is + * set to false. + * + * @throws InvalidFileFormatException + */ + @Test + public void falseAndWithKnownTrueTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownTrue", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell clyde = board.getCell(2, 0); + clyde.setData(ShortTruthTableCellType.TRUE); + board.addModifiedData(clyde); + Assert.assertNotNull(RULE.checkRule(transition)); + + clyde.setData(ShortTruthTableCellType.FALSE); + board.addModifiedData(clyde); + Assert.assertNull(RULE.checkRule(transition)); + } +} \ No newline at end of file diff --git a/src/test/java/puzzles/shorttruthtable/rules/AtomicDirectRuleTest.java b/src/test/java/puzzles/shorttruthtable/rules/AtomicDirectRuleTest.java new file mode 100644 index 000000000..51aa213c6 --- /dev/null +++ b/src/test/java/puzzles/shorttruthtable/rules/AtomicDirectRuleTest.java @@ -0,0 +1,188 @@ +package puzzles.shorttruthtable.rules; + +import edu.rpi.legup.model.tree.TreeNode; +import edu.rpi.legup.model.tree.TreeTransition; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTable; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableBoard; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCell; +import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableCellType; +import edu.rpi.legup.puzzle.shorttruthtable.rules.basic.DirectRuleAtomic; +import edu.rpi.legup.save.InvalidFileFormatException; +import legup.MockGameBoardFacade; +import legup.TestUtilities; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class AtomicDirectRuleTest { + private static final DirectRuleAtomic RULE = new DirectRuleAtomic(); + private static ShortTruthTable stt; + + @BeforeClass + public static void setup() { + MockGameBoardFacade.getInstance(); + stt = new ShortTruthTable(); + } + + /** + * Given two statements: + * A + * A + * where the first A is set to false. + * + * This test sets the second A to false and then asserts that this + * is a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void MatchingFalseTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AtomicDirectRule/FalseA", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell cell = board.getCell(0, 2); + cell.setData(ShortTruthTableCellType.FALSE); + board.addModifiedData(cell); + + Assert.assertNull(RULE.checkRule(transition)); + } + + /** + * Given two statements: + * A + * A + * where the first A is set to false. + * + * This test sets the second A to true and then asserts that this + * is not a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void MismatchingFalseTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AtomicDirectRule/FalseA", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell cell = board.getCell(0, 2); + cell.setData(ShortTruthTableCellType.TRUE); + board.addModifiedData(cell); + + Assert.assertNotNull(RULE.checkRule(transition)); + } + + /** + * Given two statements: + * B + * B + * where the first B is set to true. + * + * This test sets the second B to true and then asserts that this + * is a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void MatchingTrueTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AtomicDirectRule/TrueB", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell cell = board.getCell(0, 2); + cell.setData(ShortTruthTableCellType.TRUE); + board.addModifiedData(cell); + + Assert.assertNull(RULE.checkRule(transition)); + } + + /** + * Given two statements: + * B + * B + * where the first B is set to true. + * + * This test sets the second B to false and then asserts that this + * is not a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void MismatchingTrueTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AtomicDirectRule/TrueB", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell cell = board.getCell(0, 2); + cell.setData(ShortTruthTableCellType.FALSE); + board.addModifiedData(cell); + + Assert.assertNotNull(RULE.checkRule(transition)); + } + + /** + * Given two statements: + * C + * C + * where neither statement is set to anything. + * + * This test sets the second C to false and then asserts that this + * is not a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void NothingPreviouslyMarkedTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AtomicDirectRule/Empty", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell cell = board.getCell(0, 2); + cell.setData(ShortTruthTableCellType.FALSE); + board.addModifiedData(cell); + + Assert.assertNotNull(RULE.checkRule(transition)); + } + + /** + * Given two statements: + * C + * C + * where neither statement is set to anything. + * + * This test sets the second C to true and then asserts that this + * is not a valid application of the rule. + * + * @throws InvalidFileFormatException + */ + @Test + public void NothingPreviouslyMarkedTest2() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/shorttruthtable/rules/AtomicDirectRule/Empty", stt); + TreeNode rootNode = stt.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + ShortTruthTableBoard board = (ShortTruthTableBoard) transition.getBoard(); + + ShortTruthTableCell cell = board.getCell(0, 2); + cell.setData(ShortTruthTableCellType.TRUE); + board.addModifiedData(cell); + + Assert.assertNotNull(RULE.checkRule(transition)); + } +} \ No newline at end of file diff --git a/src/test/java/puzzles/shorttruthtable/rules/DirectRuleAtomicTest.java b/src/test/java/puzzles/shorttruthtable/rules/DirectRuleAtomicTest.java deleted file mode 100644 index 81991fa46..000000000 --- a/src/test/java/puzzles/shorttruthtable/rules/DirectRuleAtomicTest.java +++ /dev/null @@ -1,6 +0,0 @@ -package puzzles.shorttruthtable.rules; - -class DirectRuleAtomicTest { - - -} \ No newline at end of file diff --git a/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java index 50e2bb970..b7ec8eb02 100644 --- a/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/EmptyFieldDirectRuleTest.java @@ -27,6 +27,9 @@ 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 @Test public void EmptyFieldTest() throws InvalidFileFormatException { TestUtilities.importTestBoard("puzzles/treetent/rules/EmptyFieldDirectRule/EmptyField", treetent); @@ -34,28 +37,38 @@ public void EmptyFieldTest() throws InvalidFileFormatException { TreeTransition transition = rootNode.getChildren().get(0); transition.setRule(RULE); + // get board state TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + // change the board's cells considering the EmptyField rule TreeTentCell cell1 = board.getCell(1, 1); cell1.setData(TreeTentType.GRASS); - board.addModifiedData(cell1); + // confirm there is a logical following of the EmptyField rule Assert.assertNull(RULE.checkRule(transition)); + // only the cell above should change following the rule + TreeTentCell c; 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())) { - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); } else { - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); } } } } + // 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 @Test public void DiagonalTreeTest() throws InvalidFileFormatException { TestUtilities.importTestBoard("puzzles/treetent/rules/EmptyFieldDirectRule/DiagonalTree", treetent); @@ -63,26 +76,197 @@ public void DiagonalTreeTest() throws InvalidFileFormatException { TreeTransition transition = rootNode.getChildren().get(0); transition.setRule(RULE); + // get board state TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + //change the board's cells considering the EmptyField rule TreeTentCell cell1 = board.getCell(1, 1); cell1.setData(TreeTentType.GRASS); - board.addModifiedData(cell1); + // confirm there is a logical following of the EmptyField rule Assert.assertNull(RULE.checkRule(transition)); + // only the cell above should change following the rule + TreeTentCell c; 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())) { - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); } else { - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); } } } } + + // 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 + @Test + public void EmptyFieldTestFail() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFail", 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 (1,0) + // make the (1,1) tile GRASS. + // checks if tiles don't logically follow the EmptyFieldDirectRule + @Test + public void EmptyFieldTestFailTop() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailTop", 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 (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/FinishWithGrassDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java index 8dbec657a..2517df563 100644 --- a/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/FinishWithGrassDirectRuleTest.java @@ -15,6 +15,8 @@ import org.junit.Test; import java.awt.*; +import java.util.List; +import java.util.ArrayList; public class FinishWithGrassDirectRuleTest { @@ -27,15 +29,24 @@ public static void setUp() { treetent = new TreeTent(); } + /** + * 3x3 TreeTent puzzle with a tent at (0,0) + * Tests FinishWithGrassDirectRule on GRASS tiles horizontal of the tent + * at (1,0) and (2,0) + * + * @throws InvalidFileFormatException + */ @Test - public void EmptyFieldTest() throws InvalidFileFormatException { - TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/FinishWithGrass", treetent); + public void FinishWithGrassHorizontalTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/CornerTent", 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 the FinishWithGrass rule TreeTentCell cell1 = board.getCell(1, 0); cell1.setData(TreeTentType.GRASS); TreeTentCell cell2 = board.getCell(2, 0); @@ -44,21 +55,316 @@ public void EmptyFieldTest() throws InvalidFileFormatException { board.addModifiedData(cell1); board.addModifiedData(cell2); + // confirm there is a logical following of the EmptyField rule Assert.assertNull(RULE.checkRule(transition)); + // only the cell above should change following the rule + TreeTentCell c; 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())) { - Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation()) || c.getLocation().equals(cell2.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); } else { - Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); } } } } -} + /** + * 3x3 TreeTent puzzle with a tent at (0,0) + * Tests FinishWithGrassDirectRule on GRASS tiles vertical of the tent + * at (0,1) and (0,2) + * + * @throws InvalidFileFormatException + */ + @Test + public void FinishWithGrassVerticalTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/CornerTent", 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 the FinishWithGrass rule + TreeTentCell cell1 = board.getCell(0, 1); + cell1.setData(TreeTentType.GRASS); + TreeTentCell cell2 = board.getCell(0, 2); + cell2.setData(TreeTentType.GRASS); + + board.addModifiedData(cell1); + board.addModifiedData(cell2); + + // confirm there is a logical following of the EmptyField rule + Assert.assertNull(RULE.checkRule(transition)); + + // only the cell above should change following the rule + TreeTentCell c; + for (int i = 0; i < board.getHeight(); i++) { + for (int k = 0; k < board.getWidth(); k++) { + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation()) || c.getLocation().equals(cell2.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); + } + else { + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); + } + } + } + } + + /** + * 3x3 TreeTent puzzle with a tent at (0,0) + * Tests FinishWithGrassDirectRule on GRASS tiles + * at (1,0), (2,0), (0,1), and (0,2) + * + * @throws InvalidFileFormatException + */ + @Test + public void FinishWithGrassTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/CornerTent", 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 the FinishWithGrass rule + TreeTentCell cell1 = board.getCell(1, 0); + cell1.setData(TreeTentType.GRASS); + TreeTentCell cell2 = board.getCell(2, 0); + cell2.setData(TreeTentType.GRASS); + TreeTentCell cell3 = board.getCell(0, 1); + cell3.setData(TreeTentType.GRASS); + TreeTentCell cell4 = board.getCell(0, 2); + cell4.setData(TreeTentType.GRASS); + + board.addModifiedData(cell1); + board.addModifiedData(cell2); + board.addModifiedData(cell3); + board.addModifiedData(cell4); + + // confirm there is a logical following of the EmptyField rule + Assert.assertNull(RULE.checkRule(transition)); + + // only the cell above should change following the rule + TreeTentCell c; + for (int i = 0; i < board.getHeight(); i++) { + for (int k = 0; k < board.getWidth(); k++) { + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation()) || + c.getLocation().equals(cell2.getLocation()) || + c.getLocation().equals(cell3.getLocation()) || + c.getLocation().equals(cell4.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); + } + else { + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); + } + } + } + } + + /** + * 3x3 TreeTent puzzle with no tents + * Tests FinishWithGrassDirectRule on GRASS tiles + * GRASS tiles fill entire board + * + * @throws InvalidFileFormatException + */ + @Test + public void NoTentTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/NoTent", 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 the FinishWithGrass rule + List cells = new ArrayList(); + for (int i = 0; i < board.getWidth(); i++) { + for (int k = 0; k < board.getHeight(); k++) { + TreeTentCell c = board.getCell(i, k); + c.setData(TreeTentType.GRASS); + cells.add(c); + } + } + + for (TreeTentCell c : cells) { + board.addModifiedData(c); + } + + // confirm there is a logical following of the EmptyField rule + Assert.assertNull(RULE.checkRule(transition)); + + // all cells should change following the rule + for (TreeTentCell c : cells) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); + } + } + /** + * 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) + * + * @throws InvalidFileFormatException + */ + @Test + public void MiddleTentTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/MiddleTent", 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 the FinishWithGrass rule + TreeTentCell cell1 = board.getCell(1, 0); + TreeTentCell cell2 = board.getCell(0, 1); + TreeTentCell cell3 = board.getCell(2, 1); + TreeTentCell cell4 = board.getCell(1, 2); + + cell1.setData(TreeTentType.GRASS); + cell2.setData(TreeTentType.GRASS); + cell3.setData(TreeTentType.GRASS); + cell4.setData(TreeTentType.GRASS); + + board.addModifiedData(cell1); + board.addModifiedData(cell2); + board.addModifiedData(cell3); + board.addModifiedData(cell4); + + // confirm there is a logical following of the EmptyField rule + Assert.assertNull(RULE.checkRule(transition)); + + // only the cell above should change following the rule + TreeTentCell c; + for (int i = 0; i < board.getHeight(); i++) { + for (int k = 0; k < board.getWidth(); k++) { + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation()) || + c.getLocation().equals(cell2.getLocation()) || + c.getLocation().equals(cell3.getLocation()) || + c.getLocation().equals(cell4.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); + } + else { + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); + } + } + } + } + + /** + * 3x3 TreeTent puzzle with missing tents + * Tests FinishWithGrassDirectRule on GRASS tiles filling the puzzle + * all GRASS tiles should fail the FinishWithGrassDirectRule + * + * @throws InvalidFileFormatException + */ + @Test + public void FailTentTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/FailTent", 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 not following the FinishWithGrass rule + List cells = new ArrayList(); + for (int i = 0; i < board.getWidth(); i++) { + for (int k = 0; k < board.getHeight(); k++) { + TreeTentCell c = board.getCell(i, k); + c.setData(TreeTentType.GRASS); + cells.add(c); + } + } + + for (TreeTentCell c : cells) { + board.addModifiedData(c); + } + + // confirm there is a logical following of the EmptyField rule + Assert.assertNotNull(RULE.checkRule(transition)); + + // all cells should fail the rule test + for (TreeTentCell c : cells) { + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); + } + } + /** + * 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) + * + * @throws InvalidFileFormatException + */ + @Test + public void SpacedOutTentTest() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/FinishWithGrassDirectRule/SpacedOutTent", 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 the FinishWithGrass rule + TreeTentCell cell1 = board.getCell(0, 3); + TreeTentCell cell2 = board.getCell(2, 3); + TreeTentCell cell3 = board.getCell(4, 3); + TreeTentCell cell4 = board.getCell(6, 3); + + cell1.setData(TreeTentType.GRASS); + cell2.setData(TreeTentType.GRASS); + cell3.setData(TreeTentType.GRASS); + cell4.setData(TreeTentType.GRASS); + + board.addModifiedData(cell1); + board.addModifiedData(cell2); + board.addModifiedData(cell3); + board.addModifiedData(cell4); + + // confirm there is a logical following of the EmptyField rule + Assert.assertNull(RULE.checkRule(transition)); + + // only the cell above should change following the rule + TreeTentCell c; + for (int i = 0; i < board.getHeight(); i++) { + for (int k = 0; k < board.getWidth(); k++) { + c = board.getCell(k, i); + if (c.getLocation().equals(cell1.getLocation()) || + c.getLocation().equals(cell2.getLocation()) || + c.getLocation().equals(cell3.getLocation()) || + c.getLocation().equals(cell4.getLocation())) { + // logically follows + Assert.assertNull(RULE.checkRuleAt(transition, c)); + } + else { + // does not use the rule to logically follow + Assert.assertNotNull(RULE.checkRuleAt(transition, c)); + } + } + } + } +} \ No newline at end of file diff --git a/src/test/java/puzzles/treetent/rules/NoTentForTreeContradictionRuleTest.java b/src/test/java/puzzles/treetent/rules/NoTentForTreeContradictionRuleTest.java index b2d47e222..52aea28c0 100644 --- a/src/test/java/puzzles/treetent/rules/NoTentForTreeContradictionRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/NoTentForTreeContradictionRuleTest.java @@ -28,8 +28,12 @@ public static void setUp() { treetent = new TreeTent(); } + /** + * @throws InvalidFileFormatException + * Tests if a tree is next to only grass in a 2x2 grid triggers the contradiction + */ @Test - public void NoTentForTreeContradictionRule_() throws InvalidFileFormatException { + public void NoTentForTreeContradictionRule_Basic() throws InvalidFileFormatException { TestUtilities.importTestBoard("puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTree", treetent); TreeNode rootNode = treetent.getTree().getRootNode(); TreeTransition transition = rootNode.getChildren().get(0); @@ -43,5 +47,65 @@ public void NoTentForTreeContradictionRule_() throws InvalidFileFormatException Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); } + + /** + * @throws InvalidFileFormatException + * Tests similarly to above, but now with a tent diagonally next to the tree, which should still contradict + */ + @Test + public void NoTentForTreeContradictionRule_Diagonal() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeDiagonal", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + } + + /** + * @throws InvalidFileFormatException + * Tests that adjacent trees do not allow a pass + */ + @Test + public void NoTentForTreeContradictionRule_TwoTrees() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTrees", 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.assertNotNull(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 similarly to above, but now with a tent diagonally next to two trees, which should still contradict on one. + */ + @Test + public void NoTentForTreeContradictionRule_TwoTreesDiagonal() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTreesDiagonal", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + 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/NoTreeForTentContradictionRuleTest.java b/src/test/java/puzzles/treetent/rules/NoTreeForTentContradictionRuleTest.java index c3003cef1..2ad2ac90e 100644 --- a/src/test/java/puzzles/treetent/rules/NoTreeForTentContradictionRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/NoTreeForTentContradictionRuleTest.java @@ -10,13 +10,9 @@ 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.TreeTentType; import edu.rpi.legup.puzzle.treetent.rules.NoTreeForTentContradictionRule; import edu.rpi.legup.save.InvalidFileFormatException; -import java.awt.*; - public class NoTreeForTentContradictionRuleTest { private static final NoTreeForTentContradictionRule RULE = new NoTreeForTentContradictionRule(); @@ -28,9 +24,13 @@ public static void setUp() { treetent = new TreeTent(); } + /** + * @throws InvalidFileFormatException + * Tests if, in a 2x2 Grid, a Tent in the NW corner has no adjacent trees + */ @Test - public void NoTreeForTentContradictionRule_() throws InvalidFileFormatException { - TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent", treetent); + public void NoTreeForTentContradictionRule_NW() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNW", treetent); TreeNode rootNode = treetent.getTree().getRootNode(); TreeTransition transition = rootNode.getChildren().get(0); transition.setRule(RULE); @@ -43,5 +43,165 @@ public void NoTreeForTentContradictionRule_() throws InvalidFileFormatException Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); } + + /** + * @throws InvalidFileFormatException + * Tests if, in a 2x2 Grid, a Tent in the NE corner has no adjacent trees + */ + @Test + public void NoTreeForTentContradictionRule_NE() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNE", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(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 if, in a 2x2 Grid, a Tent in the NW corner has no adjacent trees + */ + @Test + public void NoTreeForTentContradictionRule_SW() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSW", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + } + + /** + * @throws InvalidFileFormatException + * Tests if, in a 2x2 Grid, a Tent in the SE corner has no adjacent trees + */ + @Test + public void NoTreeForTentContradictionRule_SE() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSE", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + } + + /** + * @throws InvalidFileFormatException + * Tests if, in a 3x3 Grid with no trees, a Tent in the center cell has no adjacent trees + */ + @Test + public void NoTreeForTentContradictionRule_3x3() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent3x3", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 2))); + } + + /** + * @throws InvalidFileFormatException + * Tests if, in a 3x3 Grid with diagonal trees, a Tent in the center cell has no adjacent trees + */ + @Test + public void NoTreeForTentContradictionRule_3x3WithDiagonalTrees() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentDiagonals", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 2))); + } + + /** + * @throws InvalidFileFormatException + * Tests if, in a 3x3 Grid with an adjacent tree, test does not assert null. + */ + @Test + public void NoTreeForTentContradictionRule_YesTree() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentYesTree", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + 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(2, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 2))); + } + + /** + * @throws InvalidFileFormatException + * Tests if, in a 3x3 Grid with touching tents, a Tent in the center cell has no adjacent trees + */ + @Test + public void NoTreeForTentContradictionRule_JustTent() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentJustTent", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 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(2, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 2))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(2, 2))); + } } diff --git a/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java b/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java index 14afb4dc0..999405747 100644 --- a/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/SurroundTentWithGrassDirectRuleTest.java @@ -27,6 +27,10 @@ public static void setUp() { treetent = new TreeTent(); } + /** + * @throws InvalidFileFormatException + * Test to check if all adjacent and diagonals not filled with a tree are filled with grass + */ @Test public void SurroundTentWithGrassBasicRuleTest() throws InvalidFileFormatException { TestUtilities.importTestBoard("puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrass", treetent); @@ -58,6 +62,90 @@ 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 + */ + @Test + public void SurroundTentWithGrassBasicRuleTest_BadBoard() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassBad", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + TreeTentCell cell1 = board.getCell(0, 0); + cell1.setData(TreeTentType.GRASS); + TreeTentCell cell2 = board.getCell(1, 0); + cell2.setData(TreeTentType.GRASS); + TreeTentCell cell3 = board.getCell(2, 0); + cell3.setData(TreeTentType.GRASS); + TreeTentCell cell4 = board.getCell(0, 1); + cell4.setData(TreeTentType.GRASS); + //Skip (1,1) due to being the Tent + TreeTentCell cell5 = board.getCell(2, 1); + cell5.setData(TreeTentType.GRASS); + TreeTentCell cell6 = board.getCell(0, 2); + cell6.setData(TreeTentType.GRASS); + TreeTentCell cell7 = board.getCell(1, 2); + cell7.setData(TreeTentType.GRASS); + TreeTentCell cell8 = board.getCell(2, 2); + cell8.setData(TreeTentType.GRASS); + + board.addModifiedData(cell1); + board.addModifiedData(cell2); + board.addModifiedData(cell3); + //board.addModifiedData(cell4); + board.addModifiedData(cell5); + board.addModifiedData(cell6); + board.addModifiedData(cell7); + board.addModifiedData(cell8); + + 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()) || //point.equals(cell4.getLocation()) || + point.equals(cell5.getLocation()) || point.equals(cell6.getLocation()) || + point.equals(cell7.getLocation()) || point.equals(cell8.getLocation()) + ) { + Assert.assertNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + } + else { + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + } + } + } + } + + /** + * @throws InvalidFileFormatException + * + * Test to see if the rule passes even if no grass was able to be placed due to the presence of trees. + */ + @Test + public void SurroundTentWithGrassBasicRuleTest_FullBoard() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassTrees", treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + Assert.assertNull(RULE.checkRule(transition)); + + for (int i = 0; i < board.getHeight(); i++){ + for (int k = 0; k < board.getWidth(); k++){ + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(k, i))); + } + } + } } diff --git a/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java b/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java index 79fc70118..f48afe5d7 100644 --- a/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java +++ b/src/test/java/puzzles/treetent/rules/TouchingTentsContradictionRuleTest.java @@ -10,13 +10,9 @@ 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.TreeTentType; import edu.rpi.legup.puzzle.treetent.rules.TouchingTentsContradictionRule; import edu.rpi.legup.save.InvalidFileFormatException; -import java.awt.*; - public class TouchingTentsContradictionRuleTest { private static final TouchingTentsContradictionRule RULE = new TouchingTentsContradictionRule(); @@ -28,8 +24,14 @@ public static void setUp() { treetent = new TreeTent(); } + //DIAGONAL TESTS + /** + * @throws InvalidFileFormatException + * Tests a tent diagonal of orientation T + * T + **/ @Test - public void TouchingTentsContradictionRule_Diagonal() throws InvalidFileFormatException { + public void TouchingTentsContradictionRule_DiagonalUpLeftToDownRight() throws InvalidFileFormatException { TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsDiagonal", treetent); TreeNode rootNode = treetent.getTree().getRootNode(); TreeTransition transition = rootNode.getChildren().get(0); @@ -44,8 +46,35 @@ public void TouchingTentsContradictionRule_Diagonal() throws InvalidFileFormatEx Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); } + /** + * @throws InvalidFileFormatException + * Tests a tent diagonal of orientation T + * T + **/ @Test - public void TouchingTentsContradictionRule_Adjacent() throws InvalidFileFormatException { + public void TouchingTentsContradictionRule_DiagonalDownLeftToUpRight() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsDiagonalAlt",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(1,0))); + Assert.assertNull(RULE.checkRuleAt(transition,board.getCell(0,1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + } + + //ADJACENT TESTS + /** + * @throws InvalidFileFormatException + * Tests a tent adjacent of orientation T + * T + **/ + @Test + public void TouchingTentsContradictionRule_AdjacentVertical() throws InvalidFileFormatException { TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsAdjacent", treetent); TreeNode rootNode = treetent.getTree().getRootNode(); TreeTransition transition = rootNode.getChildren().get(0); @@ -59,6 +88,162 @@ public void TouchingTentsContradictionRule_Adjacent() throws InvalidFileFormatEx Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); } + + /** + * @throws InvalidFileFormatException + * Tests a tent adjacent of orientation TT + **/ + @Test + public void TouchingTentsContradictionRule_AdjacentHorizontal() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsAdjacentAlt", 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.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 1))); + } + //MIXED TESTS + /** + * @throws InvalidFileFormatException + * Tests a tent of orientation TT + * TT + **/ + @Test + public void TouchingTentsContradictionRule_2By2Square() throws InvalidFileFormatException { + TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsFull2By2",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.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 + */ + @Test + public void TouchingTentsContradictionRule_TreeAdjacent() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeAdjacent",treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + Assert.assertNotNull(RULE.checkContradiction(board)); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 1))); + } + /** + * @throws InvalidFileFormatException + * Tests if tree diagonal triggers a null + */ + @Test + public void TouchingTentsContradictionRule_TreeDiagonal() throws InvalidFileFormatException{ + TestUtilities.importTestBoard("puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeDiagonal",treetent); + TreeNode rootNode = treetent.getTree().getRootNode(); + TreeTransition transition = rootNode.getChildren().get(0); + transition.setRule(RULE); + + TreeTentBoard board = (TreeTentBoard) transition.getBoard(); + + Assert.assertNotNull(RULE.checkContradiction(board)); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(0, 0))); + Assert.assertNotNull(RULE.checkRuleAt(transition, board.getCell(1, 0))); + } + } diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAnd b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAnd new file mode 100644 index 000000000..f6f60abc3 --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAnd @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownFalse b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownFalse new file mode 100644 index 000000000..d8edf4a76 --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownFalse @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownTrue b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownTrue new file mode 100644 index 000000000..364d8faf6 --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/FalseAndWithKnownTrue @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/TrueAnd b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/TrueAnd new file mode 100644 index 000000000..307f6d14a --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AndEliminationDirectRule/TrueAnd @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/Empty b/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/Empty new file mode 100644 index 000000000..6a6effadf --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/Empty @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/FalseA b/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/FalseA new file mode 100644 index 000000000..8c5ddc7b9 --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/FalseA @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/TrueB b/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/TrueB new file mode 100644 index 000000000..46a1beb2d --- /dev/null +++ b/src/test/resources/puzzles/shorttruthtable/rules/AtomicDirectRule/TrueB @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFail b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFail new file mode 100644 index 000000000..f826667e3 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFail @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom new file mode 100644 index 000000000..80deadaea --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailBottom @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft new file mode 100644 index 000000000..d19e01daf --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailLeft @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight new file mode 100644 index 000000000..bf3954964 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailRight @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailTop b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailTop new file mode 100644 index 000000000..8eaa974ea --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/EmptyFieldDirectRule/EmptyFieldFailTop @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/FinishWithGrass b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/CornerTent similarity index 92% rename from src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/FinishWithGrass rename to src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/CornerTent index 3e293d22f..daba3648e 100644 --- a/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/FinishWithGrass +++ b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/CornerTent @@ -10,7 +10,7 @@ - + diff --git a/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/FailTent b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/FailTent new file mode 100644 index 000000000..9fa14ebe4 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/FailTent @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/MiddleTent b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/MiddleTent new file mode 100644 index 000000000..8f71a57f9 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/MiddleTent @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/NoTent b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/NoTent new file mode 100644 index 000000000..a13c7cc55 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/NoTent @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/SpacedOutTent b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/SpacedOutTent new file mode 100644 index 000000000..f7b523b0a --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/FinishWithGrassDirectRule/SpacedOutTent @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeDiagonal b/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeDiagonal new file mode 100644 index 000000000..af0b49e31 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeDiagonal @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTrees b/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTrees new file mode 100644 index 000000000..5111e0585 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTrees @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTreesDiagonal b/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTreesDiagonal new file mode 100644 index 000000000..10391b85c --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTentForTreeContradictionRule/NoTentForTreeTwoTreesDiagonal @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent3x3 b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent3x3 new file mode 100644 index 000000000..9c5e81936 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent3x3 @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentDiagonals b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentDiagonals new file mode 100644 index 000000000..ad086bbc1 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentDiagonals @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentJustTent b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentJustTent new file mode 100644 index 000000000..93aaf451f --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentJustTent @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNE b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNE new file mode 100644 index 000000000..80ffb9525 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNE @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNW similarity index 100% rename from src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTent rename to src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentNW diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSE b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSE new file mode 100644 index 000000000..c2fc5206d --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSE @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSW b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSW new file mode 100644 index 000000000..8e8249b27 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentSW @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentYesTree b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentYesTree new file mode 100644 index 000000000..f46e4b5a5 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/NoTreeForTentContradictionRule/NoTreeForTentYesTree @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassBad b/src/test/resources/puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassBad new file mode 100644 index 000000000..6065d42cd --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassBad @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassTrees b/src/test/resources/puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassTrees new file mode 100644 index 000000000..e8fef64dc --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/SurroundTentWithGrassDirectRule/SurroundTentWithGrassTrees @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsAdjacentAlt b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsAdjacentAlt new file mode 100644 index 000000000..902fb3ee7 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsAdjacentAlt @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsDiagonalAlt b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsDiagonalAlt new file mode 100644 index 000000000..9b35fb998 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsDiagonalAlt @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsFull2By2 b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsFull2By2 new file mode 100644 index 000000000..59805ca35 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsFull2By2 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft new file mode 100644 index 000000000..af4ca0831 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownLeft @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 000000000..05b7a7667 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedDownRight @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 000000000..b8b81c7b4 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpLeft @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 000000000..af7fb5df2 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsMixedUpRight @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeAdjacent b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeAdjacent new file mode 100644 index 000000000..4c63558c2 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeAdjacent @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 000000000..b2f54aef3 --- /dev/null +++ b/src/test/resources/puzzles/treetent/rules/TouchingTentsContradictionRule/TouchingTentsTreeDiagonal @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file