Skip to content

Commit

Permalink
Tree Tent Test Suite (#775)
Browse files Browse the repository at this point in the history
* TentForTree test cases

One valid case of connecting a tree to a tent
One invalid case of connecting a tree to a tent when there is another valid tent

* Incomplete test for already connected tree

* Test for Fill in Row Case Rule

Looks like the case rule was implemented incorrectly :/

* Fill in Row Test Cases

Actually case generator works properly

* Fill in Row Test Cases

When the clue is that there are 0 tents, the case returned should be equivalent to FinishWithGrass.

* Comments

* Fix TreeTent Puzzle Editor

* LinkTentCaseRule Four Trees Test Case

Expected to create four cases, each one connecting a line between the tree in the center and one of the four surrounding trees.

* LinkTentCaseRule Various Test Cases

With one tree, one case is created.
With no trees, no cases are created.
With diagonal trees, no cases are created.

* TreeTent Puzzle Editor Fix

Removed double-calling setCell due to custom and super mouseReleased

* LinkTreeCaseRule Test Cases

Similar test cases to LinkTentCaseRule
- Make sure diagonal tents have no bearing on cases
- Make sure no tents around the tree fails
- One tent should connect to the correct tree
- Two tents should create two cases
- No other cases to check, as there cannot be more than two tents around a tree; otherwise, the tents would touch

* Reset puzzle

* Improved FillInRowCaseRule

Rewrote recursive generate board function to be slightly more optimized

* Fix FillInRowCaseRule

* Implement Copy Clue function

Fixes a bug where you couldn't apply the FillInRow Case Rule on a child node

* Allow Importing Puzzle-Specific Puzzle Elements

When checking the proof tree, the Puzzle Importer only checks for changed cells. However, for TreeTent, the user can create lines, which cannot be saved as a "cell" object. Now, an importer class can override the getImporterElements function to specify what types of changes the board may experience.

* Fix Puzzles Uneven Sides

Simple mistake in calculation of index

* Check Rule on Columns

Adjust all simple cases to also check the central column rule

* Change test puzzle names

* FillInRowCaseRule Test

Checks that the rule works on an empty 5x5 board

* TreeForTent tests and TentForTree tests

* Minor formatting changes

* Fix boards with uneven side lengths

* Comments

* Standardizing Comments

* Standardizing Comments

All Direct Rules

* Fix Build Issues

The Ubuntu and Checkstyle Autocompilers have trouble opening this file for some reason

* Comment out problematic file

* Uncommenting problematic test

* Update file name

* Commented out Star Battle test

---------

Co-authored-by: Charles Tian <[email protected]>
Co-authored-by: charlestian23 <[email protected]>
  • Loading branch information
3 people authored May 5, 2024
1 parent e7ea944 commit 8b2397a
Show file tree
Hide file tree
Showing 48 changed files with 2,155 additions and 588 deletions.
5 changes: 3 additions & 2 deletions puzzles files/treetent/8x8 TreeTent Easy/1646651
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Legup version="2.0.0">
<Legup version="3.0.0">
<saved/>
<puzzle name="TreeTent">
<board height="8" width="8">
<cells>
Expand Down Expand Up @@ -38,5 +39,5 @@
</axis>
</board>
</puzzle>
<solved isSolved="false" lastSaved="--"/>
<solved isSolved="false" lastSaved="2024-03-12 17:29:25"/>
</Legup>
18 changes: 17 additions & 1 deletion src/main/java/edu/rpi/legup/model/PuzzleImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import edu.rpi.legup.model.rules.Rule;
import edu.rpi.legup.model.tree.*;
import edu.rpi.legup.save.InvalidFileFormatException;

import java.lang.reflect.Array;
import java.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -130,6 +132,19 @@ public void initializePuzzle(Node node) throws InvalidFileFormatException {
public abstract void initializeBoard(String[] statements)
throws UnsupportedOperationException, IllegalArgumentException;

/**
* Used to check that elements in the proof tree are saved properly.
* <p> Make sure the list elements are lowercase
*
* @return A list of elements that will change when solving the puzzle
* * e.g. {"cell"}, {"cell", "line"}
*/
public List<String> getImporterElements() {
List<String> elements = new ArrayList<>();
elements.add("cell");
return elements;
}

/**
* Creates the proof for building
*
Expand Down Expand Up @@ -379,7 +394,8 @@ protected void makeTransitionChanges(TreeTransition transition, Node transElemen
NodeList cellList = transElement.getChildNodes();
for (int i = 0; i < cellList.getLength(); i++) {
Node node = cellList.item(i);
if (node.getNodeName().equalsIgnoreCase("cell")) {
List<String> elements = getImporterElements();
if (elements.contains(node.getNodeName().toLowerCase())) {
Board board = transition.getBoard();
PuzzleElement cell = puzzle.getFactory().importCell(node, board);

Expand Down
46 changes: 28 additions & 18 deletions src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import edu.rpi.legup.model.gameboard.Board;
import edu.rpi.legup.model.gameboard.GridBoard;
import edu.rpi.legup.model.gameboard.PuzzleElement;
import edu.rpi.legup.model.tree.Tree;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -53,21 +55,29 @@ public TreeTentCell getCell(int x, int y) {

@Override
public PuzzleElement getPuzzleElement(PuzzleElement element) {
switch (element.getIndex()) {
case -2:
return element;
case -1:
TreeTentLine line = (TreeTentLine) element;
TreeTentLine thisLine = null;
for (TreeTentLine l : lines) {
if (line.compare(l)) {
thisLine = l;
break;
}
}
return thisLine;
default:
return super.getPuzzleElement(element);
return switch (element.getIndex()) {
case -2 -> element;
case -1 -> element;
default -> super.getPuzzleElement(element);
};
}

@Override
public void setPuzzleElement(int index, PuzzleElement puzzleElement) {
if (index == -1) {
lines.add((TreeTentLine) puzzleElement);
} else if (index < puzzleElements.size()) {
puzzleElements.set(index, puzzleElement);
}
}

@Override
public void notifyChange(PuzzleElement puzzleElement) {
int index = puzzleElement.getIndex();
if (index == -1) {
lines.add((TreeTentLine) puzzleElement);
} else if (index < puzzleElements.size()) {
puzzleElements.set(index, puzzleElement);
}
}

Expand Down Expand Up @@ -168,20 +178,20 @@ public List<TreeTentCell> getDiagonals(TreeTentCell cell, TreeTentType type) {
*
* @param index the row or column number
* @param type type of TreeTent element
* @param isRow boolean value beased on whether a row of column is being checked
* @param isRow boolean value based on whether a row of column is being checked
* @return List of TreeTentCells that match the given TreeTentType
*/
public List<TreeTentCell> getRowCol(int index, TreeTentType type, boolean isRow) {
List<TreeTentCell> list = new ArrayList<>();
if (isRow) {
for (int i = 0; i < dimension.height; i++) {
for (int i = 0; i < dimension.width; i++) {
TreeTentCell cell = getCell(i, index);
if (cell.getType() == type) {
list.add(cell);
}
}
} else {
for (int i = 0; i < dimension.width; i++) {
for (int i = 0; i < dimension.height; i++) {
TreeTentCell cell = getCell(index, i);
if (cell.getType() == type) {
list.add(cell);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public PuzzleElement importCell(Node node, Board board) throws InvalidFileFormat
}

TreeTentCell cell = new TreeTentCell(TreeTentType.valueOf(value), new Point(x, y));
cell.setIndex(y * height + x);
cell.setIndex(y * width + x);
return cell;
} else {
if (node.getNodeName().equalsIgnoreCase("line")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ public void setType(TreeTentType type) {
}

public TreeTentClue copy() {
return null;
return new TreeTentClue(data, clueIndex, type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ public TreeTentController() {
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON2) {
BoardView boardView = getInstance().getLegupUI().getBoardView();
dragStart = boardView.getElement(e.getPoint());
lastCellPressed = boardView.getElement(e.getPoint());
if (boardView != null) {
dragStart = boardView.getElement(e.getPoint());
lastCellPressed = boardView.getElement(e.getPoint());
}
}
}

Expand Down Expand Up @@ -105,6 +107,8 @@ public void mouseReleased(MouseEvent e) {
}
dragStart = null;
lastCellPressed = null;
} else {
super.mouseReleased(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected org.w3c.dom.Element createBoardElement(Document newDocument) {

org.w3c.dom.Element axisSouth = newDocument.createElement("axis");
axisSouth.setAttribute("side", "south");
for (TreeTentClue clue : board.getRowClues()) {
for (TreeTentClue clue : board.getColClues()) {
org.w3c.dom.Element clueElement = newDocument.createElement("clue");
clueElement.setAttribute("value", String.valueOf(clue.getData()));
clueElement.setAttribute("index", String.valueOf(clue.getClueIndex()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import edu.rpi.legup.model.PuzzleImporter;
import edu.rpi.legup.save.InvalidFileFormatException;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
Expand Down Expand Up @@ -113,7 +116,7 @@ public void initializeBoard(Node node) throws InvalidFileFormatException {
for (int x = 0; x < width; x++) {
if (treeTentBoard.getCell(x, y) == null) {
TreeTentCell cell = new TreeTentCell(TreeTentType.UNKNOWN, new Point(x, y));
cell.setIndex(y * height + x);
cell.setIndex(y * width + x);
cell.setModifiable(true);
treeTentBoard.setCell(x, y, cell);
}
Expand Down Expand Up @@ -216,4 +219,12 @@ public void initializeBoard(Node node) throws InvalidFileFormatException {
public void initializeBoard(String[] statements) throws UnsupportedOperationException {
throw new UnsupportedOperationException("Tree Tent cannot accept text input");
}

@Override
public List<String> getImporterElements() {
List<String> elements = new ArrayList<>();
elements.add("cell");
elements.add("line");
return elements;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package edu.rpi.legup.puzzle.treetent.rules;

import edu.rpi.legup.model.Puzzle;
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.Tree;
import edu.rpi.legup.model.tree.TreeTransition;
import edu.rpi.legup.puzzle.treetent.TreeTentBoard;
import edu.rpi.legup.puzzle.treetent.TreeTentCell;
import edu.rpi.legup.puzzle.treetent.TreeTentClue;
import edu.rpi.legup.puzzle.treetent.TreeTentType;
import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class FillinRowCaseRule extends CaseRule {

Expand Down Expand Up @@ -61,7 +65,7 @@ public CaseBoard getCaseBoard(Board board) {
*/
@Override
public ArrayList<Board> getCases(Board board, PuzzleElement puzzleElement) {
ArrayList<Board> cases = new ArrayList<Board>();
ArrayList<Board> cases;
List<TreeTentCell> group;
int tentsLeft;
TreeTentClue clue = ((TreeTentClue) puzzleElement);
Expand All @@ -70,7 +74,7 @@ public ArrayList<Board> getCases(Board board, PuzzleElement puzzleElement) {
if (clue.getType() == TreeTentType.CLUE_SOUTH) {
group = tBoard.getRowCol(clueIndex, TreeTentType.UNKNOWN, false);
tentsLeft =
tBoard.getRowClues().get(clueIndex).getData()
tBoard.getColClues().get(clueIndex).getData()
- tBoard.getRowCol(clueIndex, TreeTentType.TENT, false).size();
cases = genCombinations(tBoard, group, tentsLeft, clueIndex, false);
} else {
Expand All @@ -83,60 +87,100 @@ public ArrayList<Board> getCases(Board board, PuzzleElement puzzleElement) {

// generate every combination (nCr)
// call goodBoard for each generated combination
// alternitive would be to implement collision avoidance while generating instead of after
// alternative would be to implement collision avoidance while generating instead of after
if (cases.size() > 0) {
return cases;
}
return null;
}

/**
*
* @param iBoard the board to place tents onto
* @param tiles the locations where tents can be placed
* @param target the target number of tents to place
* @param index the index of tiles which is trying to be placed
* @param isRow Used to check validity of board
* @return the list of boards created
*/
private ArrayList<Board> genCombinations(
TreeTentBoard iBoard,
List<TreeTentCell> tiles,
int target,
Integer index,
boolean isRow) {
return genCombRecursive(
iBoard, tiles, tiles, target, 0, new ArrayList<TreeTentCell>(), index, isRow);
ArrayList<Board> generatedBoards = new ArrayList<>();
genCombRecursive(
iBoard, tiles, target, 0, new ArrayList<TreeTentCell>(), 0, index, generatedBoards, isRow);
return generatedBoards;
}

private ArrayList<Board> genCombRecursive(
/**
*
* Recursive function to generate all ways of placing the target number of tents
* from the list of tiles to fill.
*
* @param iBoard The board
* @param tiles Unknown Tiles to fill
* @param target number of tents to place
* @param current number of tents already placed
* @param currentTile index of the next tile to add
* @param selected the cells which have tents
* @param index The index of the clue
* @param isRow Used for checking if the board is good
*
* The generated boards are placed into generatedBoards (passed by reference)
*/

private void genCombRecursive(
TreeTentBoard iBoard,
List<TreeTentCell> original,
List<TreeTentCell> tiles,
int target,
int current,
List<TreeTentCell> selected,
int currentTile,
Integer index,
ArrayList<Board> generatedBoards,
boolean isRow) {
ArrayList<Board> b = new ArrayList<>();
// Base Case: Enough tents have been placed
if (target == current) {
TreeTentBoard temp = iBoard.copy();
for (TreeTentCell c : original) {
if (selected.contains(c)) {
PuzzleElement change = temp.getPuzzleElement(c);
change.setData(TreeTentType.TENT);
temp.addModifiedData(change);
} else {
PuzzleElement change = temp.getPuzzleElement(c);
change.setData(TreeTentType.GRASS);
temp.addModifiedData(change);
TreeTentBoard boardCopy = iBoard.copy();
// Selected Tiles should already be filled
// Fill in other tiles with Grass
for (TreeTentCell tile : tiles) {
if (!selected.contains(tile)) {
PuzzleElement element = boardCopy.getPuzzleElement(tile);
element.setData(TreeTentType.GRASS);
boardCopy.addModifiedData(element);
}
}
if (goodBoard(temp, index, isRow)) {
b.add(temp);
}
return b;
// board validity is checked after placing every tent
// because the base case doesn't place any tents, the board
// should still be valid
generatedBoards.add(boardCopy);
return;
}
for (int i = 0; i < tiles.size(); ++i) {
List<TreeTentCell> sub = tiles.subList(i + 1, tiles.size());
List<TreeTentCell> next = new ArrayList<TreeTentCell>(selected);
next.add(tiles.get(i));
b.addAll(
genCombRecursive(
iBoard, original, sub, target, current + 1, next, index, isRow));

// Recursive Case:
// Looking at the group of possible tiles, save one of the tiles into selected,
// Place it on the board,
// Check if the board is good and recurse
//
// Backtracking:
// Remove the placed tent from the board and selected
for (int i = currentTile; i < tiles.size(); ++i){
TreeTentCell tile = tiles.get(i);
selected.add(tile);
PuzzleElement element = iBoard.getPuzzleElement(tile);
element.setData(TreeTentType.TENT);
iBoard.addModifiedData(element);
if (goodBoard(iBoard, index, isRow)) {
genCombRecursive(iBoard, tiles, target, current + 1, selected, i + 1, index, generatedBoards, isRow);
}
element.setData(TreeTentType.UNKNOWN);
iBoard.addModifiedData(element);
selected.remove(tile);
}
return b;
}

// Effectively runs TouchingTents check on all the added tents to make sure that the proposed
Expand All @@ -153,7 +197,7 @@ private boolean goodBoard(TreeTentBoard board, Integer index, boolean isRow) {
for (TreeTentCell t : tents) {
List<TreeTentCell> adj = board.getAdjacent(t, TreeTentType.TENT);
List<TreeTentCell> diag = board.getDiagonals(t, TreeTentType.TENT);
if (adj.size() > 0 || diag.size() > 0) {
if (!adj.isEmpty() || !diag.isEmpty()) {
return false;
}
}
Expand Down
Loading

0 comments on commit 8b2397a

Please sign in to comment.