Skip to content

Commit

Permalink
Finish room case rule attempt (#717)
Browse files Browse the repository at this point in the history
* Update SurroundRegionDirectRule.java

* Update SurroundRegionDirectRule.java

* Update SurroundRegionDirectRule.java

* Create FinishRoomCaseRule.java

* more work done to finish room case rule. Also since there currently is no case rules that alter 2 separate puzzzle tiles I added the basis for those in the case rule class

* Finish Room Case rule work

Remove unecessary changes from caseRule.java. Show all work before deleting uneccesary bits from finishRoom case rule attempt.

* FinishRoomCaseRule initial completed attempt

First clean attempt at rule.

* get draft pull

need help

* Changed the ID

* Update FinishRoomCaseRule.java

* Finish Room Case Rule Basically Done

Rooms seem to be finished correctly including all edge cases. Arrow color is still red but may be fixed when updating my branch.

* Cleaned up code

Removed print statements and unnecessary lines

* Finish Room green arrows and test case start

Green arrows now show when rule is ran correctly. Red arrow when incorrect and larger than 5 paths. Started the test case for it.

* Tests and rule rework

Work on test class and start of work within Rule and CaseRule to correctly display error message when too many cases are generated

* Generalized max cases

Max cases is generalized for each case rule. Can be altered from the original 10 to whichever you choose. I input 5 for finish room and 2 for black or white case rule in Nurikabe but can be done for case rules in other puzzles. AutoCaseRuleCommand was changed as well to display message when too many cases of the case rule is generated.

* FinishRoom Image

Created and added new image to display the finish room case rule

* Added argument for minimum number of case rules

* Test Case initial

Initial passing cases, may need additional work but currently passes.

* Update CaseRule.java

* False justification bug fix

Fixes green arrow when using case rule after altering 1 random tile. Also sets minimum cases number to 2.

---------

Co-authored-by: Ivan Ho <[email protected]>
Co-authored-by: Charles Tian <[email protected]>
Co-authored-by: charlestian23 <[email protected]>
  • Loading branch information
4 people authored Apr 2, 2024
1 parent 9270d0e commit 2ad0fd7
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 6 deletions.
11 changes: 7 additions & 4 deletions src/main/java/edu/rpi/legup/history/AutoCaseRuleCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public class AutoCaseRuleCommand extends PuzzleCommand {

private List<TreeTransition> caseTrans;

private static final int MAX_CASES = 10;

/**
* AutoCaseRuleCommand Constructor creates a command for validating a case rule
Expand Down Expand Up @@ -118,9 +117,13 @@ 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";
int numberOfCaseRules = caseRule.getCases(caseBoard.getBaseBoard(), elementView.getPuzzleElement()).size();
System.out.println("Number of cases:" + numberOfCaseRules);
if (numberOfCaseRules > caseRule.MAX_CASES) {
return "The selection can produce a max of " + caseRule.MAX_CASES + " cases";
}
if (numberOfCaseRules < caseRule.MIN_CASES) {
return "The selection must produce a minimum of " + caseRule.MIN_CASES + " cases";
}

return null;
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/edu/rpi/legup/model/rules/CaseRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
public abstract class CaseRule extends Rule {

private final String INVALID_USE_MESSAGE;
public int MAX_CASES;
public int MIN_CASES;

/**
* CaseRule Constructor creates a new case rule.
Expand All @@ -27,6 +29,8 @@ public CaseRule(String ruleID, String ruleName, String description, String image
super(ruleID, ruleName, description, imageName);
this.ruleType = CASE;
this.INVALID_USE_MESSAGE = "Invalid use of the case rule " + this.ruleName;
this.MAX_CASES = 10;
this.MIN_CASES = 1; // By default, this will not actually have any effect
}

/**
Expand Down Expand Up @@ -68,7 +72,6 @@ public String checkRule(TreeTransition transition) {
return "All children nodes must be justified with the same case rule.";
}
}

String check = checkRuleRaw(transition);

// Mark transition and new data as valid or not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public BlackOrWhiteCaseRule() {
"Black or White",
"Each blank cell is either black or white.",
"edu/rpi/legup/images/nurikabe/cases/BlackOrWhite.png");
this.MAX_CASES = 2;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package edu.rpi.legup.puzzle.nurikabe.rules;

import edu.rpi.legup.model.gameboard.Board;
import edu.rpi.legup.model.gameboard.CaseBoard;
import edu.rpi.legup.model.gameboard.PuzzleElement;
import edu.rpi.legup.model.rules.CaseRule;
import edu.rpi.legup.model.tree.TreeTransition;
import edu.rpi.legup.puzzle.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.awt.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class FinishRoomCaseRule extends CaseRule {
public FinishRoomCaseRule() {
super("NURI-CASE-0002",
"Finish Room",
"Room can be finished in up to five ways",
"edu/rpi/legup/images/nurikabe/cases/FinishRoom.png");
this.MAX_CASES = 5;
this.MIN_CASES = 2;
}

/**
* Checks whether the {@link TreeTransition} logically follows from the parent node using this rule. This method is
* the one that should overridden in child classes.
*
* @param transition transition to check
* @return null if the child node logically follow from the parent node, otherwise error message
*/
@Override
public String checkRuleRaw(TreeTransition transition) {
NurikabeBoard destBoardState = (NurikabeBoard) transition.getBoard();
List<TreeTransition> childTransitions = transition.getParents().get(0).getChildren();
if (childTransitions.size() > 5) {
return super.getInvalidUseOfRuleMessage() + ": This case rule must have 5 or less children.";
}
if(childTransitions.size() < 2) {
return super.getInvalidUseOfRuleMessage() + ": This case rule must have 2 or more children.";
}
Set<Point> locations = new HashSet<>();
for (TreeTransition t1 : childTransitions) {
locations.add(((NurikabeCell) t1.getBoard().getModifiedData().iterator().next()).getLocation()); //loop see if matches
if (t1.getBoard().getModifiedData().size() != 1) {
return super.getInvalidUseOfRuleMessage() + ": This case rule must have 1 modified cell for each case.";
}
for (Point loc : locations) {
for (Point loc2 : locations) {
if (!(loc.equals(loc2)) && (loc.x == loc2.x) && (loc.y == loc2.y)) {
return super.getInvalidUseOfRuleMessage() + ": This case rule must alter a different cell for each case.";
}
}
}
}

return null;
}

@Override
public CaseBoard getCaseBoard(Board board) {
NurikabeBoard nurikabeBoard = (NurikabeBoard) board.copy();
CaseBoard caseBoard = new CaseBoard(nurikabeBoard, this);
DisjointSets<NurikabeCell> regions = NurikabeUtilities.getNurikabeRegions(nurikabeBoard);
nurikabeBoard.setModifiable(false);


for (PuzzleElement element : nurikabeBoard.getPuzzleElements()) { //loops all puzzle elements
if (((NurikabeCell) element).getType() == NurikabeType.NUMBER) { //if the tile is a white number block
Set<NurikabeCell> disRow = regions.getSet(((NurikabeCell) element)); //store the row of the white region
boolean only = true; //placeholder boolean of if the element being tested is the only number block in the room or not
for(NurikabeCell d : disRow) { //loops through tiles in the room
if((d.getType() == NurikabeType.NUMBER) && !(d.getData().equals(((NurikabeCell) element).getData()))) { //if found another number tile and it's data is different than the element we're working with
only = false; //set only to false
}
}
if (disRow.size() + 1 == ((NurikabeCell) element).getData() && only) { //if size of region is 1 less than the number block and the number block is only number block in the region
caseBoard.addPickableElement(element); //add that room as a pickable element
}
}
}
return caseBoard;
}

/**
* Gets the possible cases at a specific location based on this case rule
*
* @param board the current board state
* @param puzzleElement equivalent puzzleElement
* @return a list of elements the specified could be
*/
@Override
public ArrayList<Board> getCases(Board board, PuzzleElement puzzleElement) {//throws IllegalStateException {
ArrayList<Board> cases = new ArrayList<>(); //makes array list of cases
NurikabeBoard nuriBoard = (NurikabeBoard) board.copy(); //nurikabe board to edit
NurikabeCell numbaCell = nuriBoard.getCell(((NurikabeCell) puzzleElement).getLocation().x,
((NurikabeCell) puzzleElement).getLocation().y); //number cell whose room we want to fill
int filledRoomSize = numbaCell.getData(); //size of room we want afterward
Set<Point> locations = new HashSet<>(); //locations where white space is added to finish room
Point left = new Point(-1,0);
Point right = new Point(1, 0);
Point bot = new Point(0, -1);
Point top = new Point(0, 1);
Set<Point> directions = new HashSet<>();
directions.add(left);
directions.add(right);
directions.add(top);
directions.add(bot);
Set<Point> checkedPoints = new HashSet<>(); //add all into checked points and continue at start of loop if inside
DisjointSets<NurikabeCell> regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); //gathers regions
Set<NurikabeCell> disRow = regions.getSet(numbaCell); //set of white spaces
for (NurikabeCell d : disRow) { //loops through white spaces
if(cases.size() >= 6) { //no need to check this many cases
//throw new IllegalStateException("Too many cases");
continue; //crash/runtime protection
}
for(Point direction : directions) {
if(cases.size() >= 6) { //no need to check this many cases
//throw new IllegalStateException("Too many cases");
continue; //crash/runtime protection
}
if(!((nuriBoard.getWidth() > (d.getLocation().x + direction.x) && (nuriBoard.getHeight() > d.getLocation().y + direction.y) &&
(d.getLocation().x + direction.x >= 0) && (d.getLocation().y + direction.y >= 0)))) {
continue; //if next location check would be outside of grid then continue
}
NurikabeCell curr = nuriBoard.getCell(d.getLocation().x + direction.x, d.getLocation().y + direction.y);
if(checkedPoints.contains(curr.getLocation())) {
continue; //if we already checked whether or not making this tile white would complete the room then continue
}
checkedPoints.add(curr.getLocation()); //adds location to checkedPoints so we don't check it again and accidentally add
if (curr.getType() == NurikabeType.UNKNOWN) { //found adjacent space to region that is currently unknown
curr.setData(NurikabeType.WHITE.toValue()); //changes adjacent cell color to white
nuriBoard.addModifiedData(curr); // adds modified before check
regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); //update regions
Set<NurikabeCell> disCreatedRow = regions.getSet(curr); //gets set of created row with new white cell added
if (disCreatedRow.size() == filledRoomSize) { //If adding white fills the room to exact size of number block and doesn't connect with another room
Point here = curr.getLocation(); //gets current location of new white tile that fills room
boolean alreadyIn = false; //sets whether or not the tile has already been added to false
for (Point p : locations) { //loops through locations of previously added tiles
if (p == here) { //if point is already in
alreadyIn = true; //change already in to true
break;
}
}
if (!alreadyIn) { //if point wasn't already in
Board casey = nuriBoard.copy(); //copy the current board with white tile changed
PuzzleElement datacasey = curr; //gets changed white tile as a puzzle element
datacasey.setData(NurikabeType.WHITE.toValue()); //ensure set to white, probably redundant
casey.addModifiedData(datacasey); //ensure confirmed white change
regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); //update regions
cases.add(casey); //add this case to list of cases
locations.add(here); //add location of new white tile to list of locations so that we don't accidentally add it again later
}
}
curr.setData(NurikabeType.UNKNOWN.toValue()); //set cell type back to unknown
nuriBoard.addModifiedData(curr); // confirms change back to unknown
regions = NurikabeUtilities.getNurikabeRegions(nuriBoard); //updates regions
}
}
}
return cases;
}

/**
* Checks whether the child node logically follows from the parent node
* at the specific puzzleElement index using this rule
*
* @param transition transition to check
* @param puzzleElement equivalent puzzleElement
* @return null if the child node logically follow from the parent node at the specified puzzleElement,
* otherwise error message
*/
@Override
public String checkRuleRawAt(TreeTransition transition, PuzzleElement puzzleElement) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ NURI-CONT-0005 : NoNumberContradictionRule
NURI-CONT-0006 : TooFewSpacesContradictionRule
NURI-CONT-0007 : TooManySpacesContradictionRule

NURI-CASE-0001 : BlackOrWhiteCaseRule
NURI-CASE-0001 : BlackOrWhiteCaseRule
NURI-CASE-0002 : FinishRoomCaseRule
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 114 additions & 0 deletions src/test/java/puzzles/nurikabe/rules/FinishRoomCaseRuleTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package puzzles.nurikabe.rules;

import edu.rpi.legup.history.AutoCaseRuleCommand;
import edu.rpi.legup.model.gameboard.Board;
import edu.rpi.legup.puzzle.nurikabe.*;
import edu.rpi.legup.puzzle.nurikabe.rules.FinishRoomCaseRule;
import edu.rpi.legup.utility.DisjointSets;
import legup.MockGameBoardFacade;
import legup.TestUtilities;
import edu.rpi.legup.model.tree.TreeNode;
import edu.rpi.legup.model.tree.TreeTransition;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import edu.rpi.legup.save.InvalidFileFormatException;

import java.util.ArrayList;

import java.awt.*;
import java.util.Set;

public class FinishRoomCaseRuleTest {

private static final FinishRoomCaseRule RULE = new FinishRoomCaseRule();
private static Nurikabe nurikabe;

@BeforeClass
public static void setUp() {
MockGameBoardFacade.getInstance();
nurikabe = new Nurikabe();
}

/**
* Tests the Finish Room case rule by ensuring that it results in 5 or less children, that contain a modified cell that is white
*/
@Test
public void FinishRoomCaseRule_FinishRoomCaseRuleBaseTest() throws InvalidFileFormatException {
TestUtilities.importTestBoard("puzzles/nurikabe/rules/FinishRoomCaseRule/FinishRoomCaseRuleBase", nurikabe);
TreeNode rootNode = nurikabe.getTree().getRootNode();
TreeTransition transition = rootNode.getChildren().get(0);
transition.setRule(RULE);

NurikabeBoard board = (NurikabeBoard) transition.getBoard();
NurikabeCell cell = board.getCell(6,4);
ArrayList<Board> cases = RULE.getCases(board,cell);
Assert.assertEquals(2,cases.size());

NurikabeBoard caseBoard = (NurikabeBoard) cases.get(0);
NurikabeBoard caseBoard2 = (NurikabeBoard) cases.get(1);

NurikabeType board1Type = caseBoard.getCell(5,5).getType();
NurikabeType board2Type = caseBoard2.getCell(6,6).getType();

Assert.assertTrue((board1Type.equals(NurikabeType.WHITE) && board2Type.equals(NurikabeType.WHITE)));

Assert.assertEquals(caseBoard.getHeight(),caseBoard2.getHeight(), board.getHeight());
Assert.assertEquals(caseBoard.getWidth(),caseBoard2.getWidth(), board.getWidth());

DisjointSets<NurikabeCell> regions = NurikabeUtilities.getNurikabeRegions(caseBoard); //gathers regions
Set<NurikabeCell> disRow = regions.getSet(caseBoard.getCell(5, 5));
Assert.assertEquals(disRow.size(), 3);

DisjointSets<NurikabeCell> regions2 = NurikabeUtilities.getNurikabeRegions(caseBoard2); //gathers regions
Set<NurikabeCell> disRow2 = regions2.getSet(caseBoard2.getCell(6, 6));
Assert.assertEquals(disRow2.size(), 3);

for(int i=0; i<caseBoard.getHeight(); i++){
for(int k=0; k<caseBoard.getWidth(); k++){
Point point = new Point(k,i);
if(point.equals(caseBoard.getCell(k,i).getLocation())){
continue;
}
Assert.assertTrue(caseBoard.getCell(k,i).equals(caseBoard2.getCell(k,i))
|| caseBoard2.getCell(k,i).equals(caseBoard2.getCell(k,i)));
}
}




NurikabeCell cell2 = board.getCell(4,2);
ArrayList<Board> cases2 = RULE.getCases(board,cell2);

Assert.assertEquals(6,cases2.size()); //correctly stops generating possible cases after
// more than 5 (the max) is found. Would have generated 8 cases
// FinishRoomCaseRule finny = new FinishRoomCaseRule();
// finny.checkRuleRaw();
//"Invalid use of the case rule FinishRoom: This case rule must have 5 or less children."

//getErrorString in auto case rule
//should display "The selection can produce a max of 5 cases."
//AutoCaseRuleCommand autoCaseRuleCommand = new AutoCaseRuleCommand(elementView, selection, caseBoard.getCaseRule(), caseBoard, e);

// NurikabeBoard caseyBoard = (NurikabeBoard) cases2.get(0);
// NurikabeBoard caseyBoard2 = (NurikabeBoard) cases2.get(1);
// NurikabeBoard caseyBoard3 = (NurikabeBoard) cases2.get(2);
// NurikabeBoard caseyBoard4 = (NurikabeBoard) cases2.get(3);
// NurikabeBoard caseyBoard5 = (NurikabeBoard) cases2.get(4);
// NurikabeBoard caseyBoard6 = (NurikabeBoard) cases2.get(5);
// NurikabeBoard caseyBoard7 = (NurikabeBoard) cases2.get(6);
// NurikabeBoard caseyBoard8 = (NurikabeBoard) cases2.get(7);
//
// NurikabeType boardy1Type = caseyBoard.getCell(5,5).getType();
// NurikabeType boardy2Type = caseyBoard2.getCell(6,6).getType();
// NurikabeType boardy3Type = caseyBoard.getCell(5,5).getType();
// NurikabeType boardy4Type = caseyBoard2.getCell(6,6).getType();
// NurikabeType boardy5Type = caseyBoard.getCell(5,5).getType();
// NurikabeType boardy6Type = caseyBoard2.getCell(6,6).getType();
// NurikabeType boardy7Type = caseyBoard.getCell(5,5).getType();
// NurikabeType boardy8Type = caseyBoard2.getCell(6,6).getType();


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Legup>
<puzzle name="Nurikabe">
<board width="7" height="7">
<cells>
<cell value="1" x="3" y="0"/>
<cell value="6" x="4" y="2"/>
<cell value="2" x="6" y="2"/>
<cell value="3" x="6" y="4"/>
<cell value="3" x="3" y="6"/>
<cell value="-1" x="5" y="2"/>
<cell value="-1" x="5" y="4"/>
<cell value="-1" x="6" y="3"/>
<cell value="0" x="6" y="5"/>
<cell value="0" x="1" y="2"/>
<cell value="0" x="1" y="3"/>
<cell value="0" x="2" y="2"/>
<cell value="0" x="3" y="2"/>
</cells>
</board>
</puzzle>
</Legup>

0 comments on commit 2ad0fd7

Please sign in to comment.