diff --git a/bin/main/edu/rpi/legup/legup/config b/bin/main/edu/rpi/legup/legup/config index bb7da871a..24fdcf365 100644 --- a/bin/main/edu/rpi/legup/legup/config +++ b/bin/main/edu/rpi/legup/legup/config @@ -27,7 +27,7 @@ + fileCreationDisabled="false"/> diff --git a/puzzles files/shorttruthtable/empty_test.xml b/puzzles files/shorttruthtable/empty_test.xml new file mode 100644 index 000000000..2d8e4b6c8 --- /dev/null +++ b/puzzles files/shorttruthtable/empty_test.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/main/java/edu/rpi/legup/app/GameBoardFacade.java b/src/main/java/edu/rpi/legup/app/GameBoardFacade.java index 2686086a8..55273ab4f 100644 --- a/src/main/java/edu/rpi/legup/app/GameBoardFacade.java +++ b/src/main/java/edu/rpi/legup/app/GameBoardFacade.java @@ -1,23 +1,22 @@ package edu.rpi.legup.app; +import edu.rpi.legup.history.History; import edu.rpi.legup.history.IHistoryListener; import edu.rpi.legup.history.IHistorySubject; +import edu.rpi.legup.model.Puzzle; import edu.rpi.legup.model.PuzzleImporter; import edu.rpi.legup.model.gameboard.Board; -import edu.rpi.legup.model.Puzzle; import edu.rpi.legup.model.tree.Tree; +import edu.rpi.legup.save.InvalidFileFormatException; +import edu.rpi.legup.ui.LegupUI; import edu.rpi.legup.ui.ProofEditorPanel; import edu.rpi.legup.ui.PuzzleEditorPanel; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; -import edu.rpi.legup.save.InvalidFileFormatException; -import edu.rpi.legup.ui.LegupUI; -import edu.rpi.legup.history.History; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -144,6 +143,30 @@ public boolean validateDimensions(String game, int rows, int columns) throws Run } } + /** + * Validates the given text input for the given puzzle + * + * @param game the name of the puzzle + * @param statements an array of statements + * @return true if it is possible to create a board for the given game with the given statements, + * false otherwise + * @throws RuntimeException if any of the input is invalid + */ + public boolean validateTextInput(String game, String[] statements) throws RuntimeException { + String qualifiedClassName = config.getPuzzleClassForName(game); + try { + Class c = Class.forName(qualifiedClassName); + Constructor constructor = c.getConstructor(); + Puzzle puzzle = (Puzzle) constructor.newInstance(); + return puzzle.isValidTextInput(statements); + } + catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | + InstantiationException e) { + LOGGER.error(e); + throw new RuntimeException("Error validating puzzle text input"); + } + } + /** * Loads an empty puzzle * @@ -159,7 +182,6 @@ public void loadPuzzle(String game, int rows, int columns) throws RuntimeExcepti Class c = Class.forName(qualifiedClassName); Constructor cons = c.getConstructor(); Puzzle puzzle = (Puzzle) cons.newInstance(); - setWindowTitle(puzzle.getName(), "New " + puzzle.getName() + " Puzzle"); PuzzleImporter importer = puzzle.getImporter(); if (importer == null) { @@ -167,6 +189,13 @@ public void loadPuzzle(String game, int rows, int columns) throws RuntimeExcepti throw new RuntimeException("Puzzle importer null"); } + // Theoretically, this exception should never be thrown, since LEGUP should not be + // allowing the user to give row/column input for a puzzle that doesn't support it + if (!importer.acceptsRowsAndColumnsInput()) { + throw new IllegalArgumentException(puzzle.getName() + " does not accept rows and columns input"); + } + + setWindowTitle(puzzle.getName(), "New " + puzzle.getName() + " Puzzle"); importer.initializePuzzle(rows, columns); puzzle.initializeView(); @@ -183,6 +212,45 @@ public void loadPuzzle(String game, int rows, int columns) throws RuntimeExcepti } } + public void loadPuzzle(String game, String[] statements) { + String qualifiedClassName = config.getPuzzleClassForName(game); + LOGGER.debug("Loading " + qualifiedClassName); + + try { + Class c = Class.forName(qualifiedClassName); + Constructor cons = c.getConstructor(); + Puzzle puzzle = (Puzzle) cons.newInstance(); + + PuzzleImporter importer = puzzle.getImporter(); + if (importer == null) { + LOGGER.error("Puzzle importer is null"); + throw new RuntimeException("Puzzle importer null"); + } + + // Theoretically, this exception should never be thrown, since LEGUP should not be + // allowing the user to give text input for a puzzle that doesn't support it + if (!importer.acceptsTextInput()) { + throw new IllegalArgumentException(puzzle.getName() + " does not accept text input"); + } + + setWindowTitle(puzzle.getName(), "New " + puzzle.getName() + " Puzzle"); + importer.initializePuzzle(statements); + + puzzle.initializeView(); +// puzzle.getBoardView().onTreeElementChanged(puzzle.getTree().getRootNode()); + setPuzzleEditor(puzzle); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(exception.getMessage()); + } + catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | + IllegalAccessException | InstantiationException e) { + LOGGER.error(e); + throw new RuntimeException("Puzzle creation error"); + } + + } + /** * Loads a puzzle file * diff --git a/src/main/java/edu/rpi/legup/model/Puzzle.java b/src/main/java/edu/rpi/legup/model/Puzzle.java index d25afa2cb..18614131b 100644 --- a/src/main/java/edu/rpi/legup/model/Puzzle.java +++ b/src/main/java/edu/rpi/legup/model/Puzzle.java @@ -204,12 +204,26 @@ public boolean isValidDimensions(int rows, int columns) { return rows > 0 && columns > 0; } + /** + * Checks if the given array of statements is valid text input for the given puzzle + * + * @param statements + * @return + */ + public boolean isValidTextInput(String[] statements) { + return statements.length > 0; + } + /** * Determines if the edu.rpi.legup.puzzle was solves correctly * * @return true if the board was solved correctly, false otherwise */ public boolean isPuzzleComplete() { + if (tree == null) { + return false; + } + boolean isComplete = tree.isValid(); if (isComplete) { for (TreeElement leaf : tree.getLeafTreeElements()) { diff --git a/src/main/java/edu/rpi/legup/model/PuzzleExporter.java b/src/main/java/edu/rpi/legup/model/PuzzleExporter.java index a2f662772..613d2ed1c 100644 --- a/src/main/java/edu/rpi/legup/model/PuzzleExporter.java +++ b/src/main/java/edu/rpi/legup/model/PuzzleExporter.java @@ -29,6 +29,7 @@ public abstract class PuzzleExporter { /** * PuzzleExporter Constructor exports the puzzle object to a file + * * @param puzzle puzzle that is to be exported */ public PuzzleExporter(Puzzle puzzle) { diff --git a/src/main/java/edu/rpi/legup/model/PuzzleImporter.java b/src/main/java/edu/rpi/legup/model/PuzzleImporter.java index c2b5b37fc..327a92773 100644 --- a/src/main/java/edu/rpi/legup/model/PuzzleImporter.java +++ b/src/main/java/edu/rpi/legup/model/PuzzleImporter.java @@ -12,10 +12,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public abstract class PuzzleImporter { private static final Logger LOGGER = LogManager.getLogger(PuzzleImporter.class.getName()); @@ -24,12 +21,17 @@ public abstract class PuzzleImporter { /** * PuzzleImporter Constructor creates the puzzle object + * * @param puzzle puzzle that is imported */ public PuzzleImporter(Puzzle puzzle) { this.puzzle = puzzle; } + public abstract boolean acceptsRowsAndColumnsInput(); + + public abstract boolean acceptsTextInput(); + /** * Initializes an empty puzzle * @@ -46,6 +48,13 @@ public void initializePuzzle(int rows, int columns) throws RuntimeException { } } + public void initializePuzzle(String[] statements) throws InputMismatchException, IllegalArgumentException { + // Note: Error checking for the statements will be left up to the puzzles that support + // text input. For example, some puzzles may be okay with "blank" statements (Strings with + // length = 0) while others may not. + initializeBoard(statements); + } + /** * Initializes the puzzle attributes * @@ -116,6 +125,8 @@ public void initializePuzzle(Node node) throws InvalidFileFormatException { */ public abstract void initializeBoard(Node node) throws InvalidFileFormatException; + public abstract void initializeBoard(String[] statements) throws UnsupportedOperationException, IllegalArgumentException; + /** * Creates the proof for building * diff --git a/src/main/java/edu/rpi/legup/model/tree/Tree.java b/src/main/java/edu/rpi/legup/model/tree/Tree.java index 31ef92359..79c0bcece 100644 --- a/src/main/java/edu/rpi/legup/model/tree/Tree.java +++ b/src/main/java/edu/rpi/legup/model/tree/Tree.java @@ -101,6 +101,7 @@ public Set getLeafTreeElements() { /** * Gets a Set of TreeNodes that are leaf nodes from the sub tree rooted at the specified node + * * @param node node that is input * @return Set of TreeNodes that are leaf nodes from the sub tree */ diff --git a/src/main/java/edu/rpi/legup/puzzle/battleship/BattleshipImporter.java b/src/main/java/edu/rpi/legup/puzzle/battleship/BattleshipImporter.java index aa7209f71..749ceaaa9 100644 --- a/src/main/java/edu/rpi/legup/puzzle/battleship/BattleshipImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/battleship/BattleshipImporter.java @@ -13,6 +13,16 @@ public BattleshipImporter(Battleship battleShip) { super(battleShip); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -177,4 +187,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { "unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Battleship cannot accept text input"); + } } 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 45ad786e8..6c30b2272 100644 --- a/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/fillapix/FillapixImporter.java @@ -13,6 +13,16 @@ public FillapixImporter(Fillapix fillapix) { super(fillapix); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -88,4 +98,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("Fillapix Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Fillapix cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/heyawake/HeyawakeImporter.java b/src/main/java/edu/rpi/legup/puzzle/heyawake/HeyawakeImporter.java index d09a15389..7527c717f 100644 --- a/src/main/java/edu/rpi/legup/puzzle/heyawake/HeyawakeImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/heyawake/HeyawakeImporter.java @@ -14,6 +14,16 @@ public HeyawakeImporter(Heyawake heyawake) { super(heyawake); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -91,4 +101,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("Heyawake Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Hey Awake cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpImporter.java b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpImporter.java index fd9fd49e9..7ef24ca69 100644 --- a/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/lightup/LightUpImporter.java @@ -13,6 +13,16 @@ public LightUpImporter(LightUp lightUp) { super(lightUp); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -102,4 +112,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("lightup Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Light Up cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/masyu/MasyuImporter.java b/src/main/java/edu/rpi/legup/puzzle/masyu/MasyuImporter.java index 50bf0c0c7..3e0d328c4 100644 --- a/src/main/java/edu/rpi/legup/puzzle/masyu/MasyuImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/masyu/MasyuImporter.java @@ -13,6 +13,16 @@ public MasyuImporter(Masyu masyu) { super(masyu); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -90,4 +100,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("Masyu Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Masyu cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeImporter.java b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeImporter.java index 7665a4865..2cbcc9ad0 100644 --- a/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/nurikabe/NurikabeImporter.java @@ -13,6 +13,16 @@ public NurikabeImporter(Nurikabe nurikabe) { super(nurikabe); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -100,4 +110,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("nurikabe Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Nurikabe cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTable.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTable.java index 3ce185b6c..e8f9ffc0d 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTable.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTable.java @@ -25,6 +25,7 @@ public ShortTruthTable() { public void initializeView() { ShortTruthTableBoard sttBoard = (ShortTruthTableBoard) currentBoard; boardView = new ShortTruthTableView(sttBoard); + boardView.setBoard(currentBoard); addBoardListener(boardView); } @@ -48,8 +49,32 @@ public Board generatePuzzle(int difficulty) { * @return true if the given dimensions are valid for Short Truth Table, false otherwise */ public boolean isValidDimensions(int rows, int columns) { - // This is a placeholder, this method needs to be implemented - throw new UnsupportedOperationException(); + // Number of rows must be odd to allow for proper spacing between the statements + if (rows % 2 != 1) { + return false; + } + + return true; + } + + /** + * Determines if the given statements are valid for Short Truth Table + * + * @param statements + * @return true if the statements are valid for Short Truth Table, false otherwise + */ + public boolean isValidTextInput(String[] statements) { + if (statements.length == 0) { + return false; + } + + ShortTruthTableImporter importer = (ShortTruthTableImporter) this.getImporter(); + for (String s : statements) { + if (!importer.validGrammar(s)) { + return false; + } + } + return true; } /** diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java index 768b4ed2a..59b5f4272 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCell.java @@ -1,15 +1,17 @@ package edu.rpi.legup.puzzle.shorttruthtable; +import edu.rpi.legup.model.elements.Element; import edu.rpi.legup.model.gameboard.GridCell; import edu.rpi.legup.puzzle.shorttruthtable.ShortTruthTableStatement; import java.awt.Point; +import java.awt.event.MouseEvent; public class ShortTruthTableCell extends GridCell { //The symbol on the cell - private final char symbol; + private char symbol; //This is a reference to the statement that contains this cell private ShortTruthTableStatement statement; @@ -127,5 +129,104 @@ public ShortTruthTableCell copy() { return copy; } + /** + * Sets the type of this ShortTruthTableCell + * + * @param e element to set the type of this Short Truth Table cell to + */ + @Override + public void setType(Element e, MouseEvent m) { + // Do not allow odd rows to be modified since they are spacer rows + if (this.getLocation().getY() % 2 == 1) { + return; + } + // Red Element + if (e.getElementID().equals("STTT-PLAC-0002")) { + this.data = ShortTruthTableCellType.FALSE; + } + // Green Element + else { + if (e.getElementID().equals("STTT-PLAC-0001")) { + this.data = ShortTruthTableCellType.TRUE; + } + // Unknown Element + else { + if (e.getElementID().equals("STTT-PLAC-0003")) { + this.data = ShortTruthTableCellType.UNKNOWN; + } + // Argument Element + else { + if (e.getElementID().equals("STTT-UNPL-0001")) { + // Prevents non-argument symbols from being changed + if (!(this.symbol >= 'A' && this.symbol <= 'Z')) { + return; + } + + if (m.getButton() == MouseEvent.BUTTON1) { + this.symbol += 1; + if (this.symbol > 'Z') { + this.symbol = 'A'; + } + } + else { + if (m.getButton() == MouseEvent.BUTTON3) { + this.symbol -= 1; + if (this.symbol < 'A') { + this.symbol = 'Z'; + } + } + } + } + // And/Or Element + else { + if (e.getElementID().equals("STTT-UNPL-0002")) { + if (m.getButton() == MouseEvent.BUTTON1) { + if (this.symbol == '^') { + this.symbol = '|'; + } + else { + if (this.symbol == '|') { + this.symbol = '>'; + } + else { + if (this.symbol == '>') { + this.symbol = '-'; + } + else { + if (this.symbol == '-') { + this.symbol = '^'; + } + } + } + } + } + else { + if (m.getButton() == MouseEvent.BUTTON3) { + if (this.symbol == '^') { + this.symbol = '-'; + } + else { + if (this.symbol == '|') { + this.symbol = '^'; + } + else { + if (this.symbol == '>') { + this.symbol = '|'; + } + else { + if (this.symbol == '-') { + this.symbol = '>'; + } + } + } + } + } + } + } + } + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCellType.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCellType.java index c01fe74d8..c997faf5f 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCellType.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableCellType.java @@ -26,6 +26,7 @@ public static ShortTruthTableCellType valueOf(int cellType) { /** * Gets the char value of a cell, Used for debugging + * * @param type cell type input * @return true if value is 1, false if value is 0, ? if value is -1, or blank otherwise */ diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableController.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableController.java index ccf4e9274..bddde44a5 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableController.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableController.java @@ -12,7 +12,7 @@ public void changeCell(MouseEvent e, PuzzleElement data) { System.out.println("STTController: Cell change"); - //cast the data to a short truth table cell + // cast the data to a short truth table cell ShortTruthTableCell cell = (ShortTruthTableCell) data; if (e.getButton() == MouseEvent.BUTTON1) { diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableExporter.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableExporter.java index bcb744789..9d6553c7c 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableExporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableExporter.java @@ -2,6 +2,7 @@ import edu.rpi.legup.model.PuzzleExporter; import edu.rpi.legup.model.gameboard.PuzzleElement; +import edu.rpi.legup.puzzle.nurikabe.NurikabeBoard; import org.w3c.dom.Document; public class ShortTruthTableExporter extends PuzzleExporter { @@ -12,7 +13,13 @@ public ShortTruthTableExporter(ShortTruthTable stt) { @Override protected org.w3c.dom.Element createBoardElement(Document newDocument) { - ShortTruthTableBoard board = (ShortTruthTableBoard) puzzle.getTree().getRootNode().getBoard(); + ShortTruthTableBoard board; + if (puzzle.getTree() != null) { + board = (ShortTruthTableBoard) puzzle.getTree().getRootNode().getBoard(); + } + else { + board = (ShortTruthTableBoard) puzzle.getBoardView().getBoard(); + } org.w3c.dom.Element boardElement = newDocument.createElement("board"); diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableImporter.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableImporter.java index cccdbca19..84d04fb45 100644 --- a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/ShortTruthTableImporter.java @@ -3,16 +3,15 @@ import edu.rpi.legup.model.PuzzleImporter; import edu.rpi.legup.save.InvalidFileFormatException; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import org.w3c.dom.NamedNodeMap; +import javax.swing.*; import java.awt.*; - -import java.util.List; import java.util.ArrayList; - -import javax.swing.*; +import java.util.LinkedList; +import java.util.List; class ShortTruthTableImporter extends PuzzleImporter { @@ -55,8 +54,8 @@ private List getCells(String statement, int y) { * @return the length, in chars, of the longest statement */ private int parseAllStatementsAndCells(final NodeList statementData, - List> allCells, - List statements) throws InvalidFileFormatException { + List> allCells, + List statements) throws InvalidFileFormatException { int maxStatementLength = 0; @@ -65,7 +64,10 @@ private int parseAllStatementsAndCells(final NodeList statementData, //Get the atributes from the statement i in the file NamedNodeMap attributeList = statementData.item(i).getAttributes(); + String statementRep = attributeList.getNamedItem("representation").getNodeValue(); + System.out.println("STATEMENT REP: " + statementRep); + System.out.println("ROW INDEX: " + attributeList.getNamedItem("row_index").getNodeValue()); //parser time (on statementRep) //if (!validGrammar(statementRep)) throw some error if (!validGrammar(statementRep)) { @@ -85,10 +87,32 @@ private int parseAllStatementsAndCells(final NodeList statementData, } return maxStatementLength; + } + + private int parseAllStatementsAndCells(String[] statementData, + List> allCells, + List statements) throws IllegalArgumentException { + int maxStatementLength = 0; + + for (int i = 0; i < statementData.length; i++) { + if (!validGrammar(statementData[i])) { + JOptionPane.showMessageDialog(null, "ERROR: Invalid file syntax"); + throw new IllegalArgumentException("shorttruthtable importer: invalid sentence syntax"); + } + + //get the cells for the statement + List rowOfCells = getCells(statementData[i], i * 2); + allCells.add(rowOfCells); + statements.add(new ShortTruthTableStatement(statementData[i], rowOfCells)); + + //keep track of the length of the longest statement + maxStatementLength = Math.max(maxStatementLength, statementData[i].length()); + } + return maxStatementLength; } - private boolean validGrammar(String sentence) { + protected boolean validGrammar(String sentence) { int open = 0; int close = 0; char[] valid_characters = new char[]{'^', 'v', '!', '>', '-', '&', '|', '~', '$', '%'}; @@ -217,6 +241,16 @@ private void setGivenCells(ShortTruthTableBoard sttBoard, } + @Override + public boolean acceptsRowsAndColumnsInput() { + return false; + } + + @Override + public boolean acceptsTextInput() { + return true; + } + /** * Creates an empty board for building * @@ -278,9 +312,37 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { catch (NumberFormatException e) { throw new InvalidFileFormatException("short truth table Importer: unknown value where integer expected"); } + } + /** + * Creates the board for building using statements + * + * @param statementInput + * @throws UnsupportedOperationException + * @throws IllegalArgumentException + */ + public void initializeBoard(String[] statementInput) throws UnsupportedOperationException, IllegalArgumentException { + List statementsList = new LinkedList<>(); + for (String s : statementInput) { + if (s.strip().length() > 0) { + statementsList.add(s); + } + } + String[] statementData = statementsList.toArray(new String[statementsList.size()]); - } + if (statementData.length == 0) { + throw new IllegalArgumentException("short truth table Importer: no statements found for board"); + } + // Store all cells and statements + List> allCells = new ArrayList<>(); + List statements = new ArrayList<>(); + // Parse the data + int maxStatementLength = parseAllStatementsAndCells(statementData, allCells, statements); + + // Generate and set the board - don't set given cell values since none are given + ShortTruthTableBoard sttBoard = generateBoard(allCells, statements, maxStatementLength); + puzzle.setCurrentBoard(sttBoard); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java new file mode 100644 index 000000000..9f238a9bf --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/ArgumentElement.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.shorttruthtable.elements; + +import edu.rpi.legup.model.elements.NonPlaceableElement; + +public class ArgumentElement extends NonPlaceableElement { + public ArgumentElement() { + super("STTT-UNPL-0001", "Argument Element", "Argument of logic statement element", "edu/rpi/legup/images/shorttruthtable/tiles/LetterTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java new file mode 100644 index 000000000..605f6a207 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/GreenElement.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.shorttruthtable.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class GreenElement extends PlaceableElement { + public GreenElement() { + super("STTT-PLAC-0001", "Green Element", "A green tile to set certain tiles to true", "edu/rpi/legup/images/shorttruthtable/tiles/GreenTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java new file mode 100644 index 000000000..b2adfddef --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/LogicSymbolElement.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.shorttruthtable.elements; + +import edu.rpi.legup.model.elements.NonPlaceableElement; + +public class LogicSymbolElement extends NonPlaceableElement { + public LogicSymbolElement() { + super("STTT-UNPL-0002", "Logic Symbol Element", "Logic symbol element", "edu/rpi/legup/images/shorttruthtable/tiles/ConditionalBiconditionalTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java new file mode 100644 index 000000000..ecc7d5a02 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/RedElement.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.shorttruthtable.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class RedElement extends PlaceableElement { + public RedElement() { + super("STTT-PLAC-0002", "Red Element", "A red tile to set certain tiles to false", "edu/rpi/legup/images/shorttruthtable/tiles/RedTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java new file mode 100644 index 000000000..9a9ab8b84 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/UnknownElement.java @@ -0,0 +1,9 @@ +package edu.rpi.legup.puzzle.shorttruthtable.elements; + +import edu.rpi.legup.model.elements.PlaceableElement; + +public class UnknownElement extends PlaceableElement { + public UnknownElement() { + super("STTT-PLAC-0003", "Unknown Element", "A blank tile", "edu/rpi/legup/images/shorttruthtable/tiles/UnknownTile.png"); + } +} diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet new file mode 100644 index 000000000..471631553 --- /dev/null +++ b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/elements/shorttruthtable_elements_reference_sheet @@ -0,0 +1,6 @@ +STTT-UNPL-0001 : ArgumentElement +STTT-UNPL-0002 : ConditionalBiconditionalElement + +STTT-PLAC-0001 : GreenElement +STTT-PLAC-0002 : RedElement +STTT-PLAC-0003 : UnknownElement \ No newline at end of file diff --git a/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/shorttruthtable_reference_sheet.txt b/src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/shorttruthtable_rules_reference_sheet.txt similarity index 100% rename from src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/shorttruthtable_reference_sheet.txt rename to src/main/java/edu/rpi/legup/puzzle/shorttruthtable/rules/shorttruthtable_rules_reference_sheet.txt diff --git a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersImporter.java b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersImporter.java index 22af18a4c..6dded9764 100644 --- a/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/skyscrapers/SkyscrapersImporter.java @@ -13,6 +13,16 @@ public SkyscrapersImporter(Skyscrapers skyscrapers) { super(skyscrapers); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -150,4 +160,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("Skyscraper Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Skyscrapers cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java index f77a68d7a..dccec52d4 100644 --- a/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/sudoku/SudokuImporter.java @@ -13,6 +13,16 @@ public SudokuImporter(Sudoku sudoku) { super(sudoku); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -112,4 +122,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("Sudoku Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Sudoku cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java index e48122a7a..2b4861c9f 100644 --- a/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java +++ b/src/main/java/edu/rpi/legup/puzzle/treetent/TreeTentImporter.java @@ -13,6 +13,16 @@ public TreeTentImporter(TreeTent treeTent) { super(treeTent); } + @Override + public boolean acceptsRowsAndColumnsInput() { + return true; + } + + @Override + public boolean acceptsTextInput() { + return false; + } + /** * Creates an empty board for building * @@ -174,4 +184,9 @@ public void initializeBoard(Node node) throws InvalidFileFormatException { throw new InvalidFileFormatException("TreeTent Importer: unknown value where integer expected"); } } + + @Override + public void initializeBoard(String[] statements) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Tree Tent cannot accept text input"); + } } diff --git a/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java b/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java index 8e0858b72..fa049ab38 100644 --- a/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java +++ b/src/main/java/edu/rpi/legup/ui/CreatePuzzleDialog.java @@ -9,35 +9,71 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Arrays; +import java.util.Objects; public class CreatePuzzleDialog extends JDialog { private HomePanel homePanel; private String[] games; private JComboBox gameBox; + private ActionListener gameBoxListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JComboBox comboBox = (JComboBox) e.getSource(); + String puzzleName = (String) comboBox.getSelectedItem(); + if (puzzleName.equals("ShortTruthTable")) { + textInputScrollPane.setVisible(true); + rowsLabel.setVisible(false); + rows.setVisible(false); + columnsLabel.setVisible(false); + columns.setVisible(false); + } + else { + textInputScrollPane.setVisible(false); + rowsLabel.setVisible(true); + rows.setVisible(true); + columnsLabel.setVisible(true); + columns.setVisible(true); + } + } + }; - private JLabel puzzleLabel = new JLabel("Puzzle:"); + private JLabel puzzleLabel; + private JLabel rowsLabel; private JTextField rows; + private JLabel columnsLabel; private JTextField columns; + private JTextArea textArea; + private JScrollPane textInputScrollPane; + private JButton ok = new JButton("Ok"); private ActionListener okButtonListener = new ActionListener() { /** * Attempts to open the puzzle editor interface for the given game with the given dimensions - * @param e the event to be processed + * @param ae the event to be processed */ @Override public void actionPerformed(ActionEvent ae) { String game = Config.convertDisplayNameToClassName((String) gameBox.getSelectedItem()); // Check if all 3 TextFields are filled - if (game.equals("") || rows.getText().equals("") || columns.getText().equals("")) { + if (game.equals("ShortTruthTable") && textArea.getText().equals("")) { + System.out.println("Unfilled fields"); + return; + } + if (!game.equals("ShortTruthTable") && (game.equals("") || rows.getText().equals("") || columns.getText().equals(""))) { System.out.println("Unfilled fields"); return; } try { - homePanel.openEditorWithNewPuzzle(game, Integer.valueOf(rows.getText()), Integer.valueOf(columns.getText())); + if (game.equals("ShortTruthTable")) { + homePanel.openEditorWithNewPuzzle("ShortTruthTable", textArea.getText().split("\n")); + } + else { + homePanel.openEditorWithNewPuzzle(game, Integer.valueOf(rows.getText()), Integer.valueOf(columns.getText())); + } setVisible(false); } catch (IllegalArgumentException e) { @@ -75,6 +111,7 @@ public CreatePuzzleDialog(JFrame parent, HomePanel homePanel) { Container c = getContentPane(); c.setLayout(null); + puzzleLabel = new JLabel("Puzzle:"); puzzleLabel.setBounds(10, 30, 70, 25); gameBox.setBounds(80, 30, 190, 25); @@ -87,8 +124,8 @@ public CreatePuzzleDialog(JFrame parent, HomePanel homePanel) { rows = new JTextField(); columns = new JTextField(); - JLabel rowsLabel = new JLabel("Rows:"); - JLabel columnsLabel = new JLabel("Columns:"); + rowsLabel = new JLabel("Rows:"); + columnsLabel = new JLabel("Columns:"); rowsLabel.setBounds(30, 70, 60, 25); columnsLabel.setBounds(30, 95, 60, 25); @@ -102,9 +139,31 @@ public CreatePuzzleDialog(JFrame parent, HomePanel homePanel) { c.add(rows); c.add(columns); + textArea = new JTextArea(); + textInputScrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + textInputScrollPane.setBounds(10, 70, this.getWidth() - 30, 50); + c.add(textInputScrollPane); + c.add(ok); c.add(cancel); + if (Objects.equals(this.gameBox.getSelectedItem(), "ShortTruthTable")) { + textInputScrollPane.setVisible(true); + rowsLabel.setVisible(false); + rows.setVisible(false); + columnsLabel.setVisible(false); + columns.setVisible(false); + } + else { + textInputScrollPane.setVisible(false); + rowsLabel.setVisible(true); + rows.setVisible(true); + columnsLabel.setVisible(true); + columns.setVisible(true); + } + + ActionListener cursorSelectedGame = CursorController.createListener(this, gameBoxListener); + gameBox.addActionListener(cursorSelectedGame); ActionListener cursorPressedOk = CursorController.createListener(this, okButtonListener); ok.addActionListener(cursorPressedOk); ActionListener cursorPressedCancel = CursorController.createListener(this, cancelButtonListener); @@ -123,7 +182,13 @@ public void actionPerformed(ActionEvent e) { String game = Config.convertDisplayNameToClassName((String) gameBox.getSelectedItem()); try { - this.homePanel.openEditorWithNewPuzzle(game, Integer.valueOf(this.rows.getText()), Integer.valueOf(this.columns.getText())); + if (game.equals("ShortTruthTable")) { + this.homePanel.openEditorWithNewPuzzle("ShortTruthTable", this.textArea.getText().split("\n")); + } + else { + this.homePanel.openEditorWithNewPuzzle(game, Integer.valueOf(this.rows.getText()), Integer.valueOf(this.columns.getText())); + + } this.setVisible(false); } catch (IllegalArgumentException exception) { diff --git a/src/main/java/edu/rpi/legup/ui/HomePanel.java b/src/main/java/edu/rpi/legup/ui/HomePanel.java index f72694cc0..48f0d03d7 100644 --- a/src/main/java/edu/rpi/legup/ui/HomePanel.java +++ b/src/main/java/edu/rpi/legup/ui/HomePanel.java @@ -1,11 +1,9 @@ package edu.rpi.legup.ui; -import edu.rpi.legup.Legup; import edu.rpi.legup.app.GameBoardFacade; import edu.rpi.legup.app.LegupPreferences; import edu.rpi.legup.controller.CursorController; import edu.rpi.legup.save.InvalidFileFormatException; -import edu.rpi.legup.app.LegupPreferences; import edu.rpi.legup.model.Puzzle; import edu.rpi.legup.model.PuzzleExporter; import edu.rpi.legup.save.ExportFileException; @@ -29,7 +27,6 @@ import java.io.FileWriter; import java.net.URI; import java.net.URL; -import java.util.Objects; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -640,4 +637,28 @@ public void openEditorWithNewPuzzle(String game, int rows, int columns) throws I this.legupUI.displayPanel(2); this.legupUI.getPuzzleEditor().loadPuzzleFromHome(game, rows, columns); } + + /** + * Opens the puzzle editor for the specified game with the given statements + * + * @param game a String containing the name of the game + * @param statements an array of statements + */ + public void openEditorWithNewPuzzle(String game, String[] statements) { + // Validate the text input + GameBoardFacade facade = GameBoardFacade.getInstance(); + boolean isValidTextInput = facade.validateTextInput(game, statements); + if (!isValidTextInput) { + JOptionPane.showMessageDialog(null, + "The input you entered is invalid. Please double check \n" + + "your statements and try again.", + "ERROR: Invalid Text Input", + JOptionPane.ERROR_MESSAGE); + throw new IllegalArgumentException("ERROR: Invalid dimensions given"); + } + + // Set game type on the puzzle editor + this.legupUI.displayPanel(2); + this.legupUI.getPuzzleEditor().loadPuzzleFromHome(game, statements); + } } diff --git a/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java b/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java index cce4feec5..90f0c3cdf 100644 --- a/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java +++ b/src/main/java/edu/rpi/legup/ui/PuzzleEditorPanel.java @@ -327,6 +327,20 @@ public void loadPuzzleFromHome(String game, int rows, int columns) throws Illega } } + public void loadPuzzleFromHome(String game, String[] statements) { + GameBoardFacade facade = GameBoardFacade.getInstance(); + try { + facade.loadPuzzle(game, statements); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(exception.getMessage()); + } + catch (RuntimeException e) { + e.printStackTrace(); + LOGGER.error(e.getMessage()); + } + } + // File opener public Object[] promptPuzzle() { GameBoardFacade facade = GameBoardFacade.getInstance(); diff --git a/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/AndOrTile.png b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/AndOrTile.png new file mode 100644 index 000000000..9ea932502 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/AndOrTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/ConditionalBiconditionalTile.png b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/ConditionalBiconditionalTile.png new file mode 100644 index 000000000..b1af3468e Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/ConditionalBiconditionalTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/GreenTile.png b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/GreenTile.png new file mode 100644 index 000000000..a438e3037 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/GreenTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/LetterTile.png b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/LetterTile.png new file mode 100644 index 000000000..5f94da225 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/LetterTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/RedTile.png b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/RedTile.png new file mode 100644 index 000000000..7955c8f92 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/RedTile.png differ diff --git a/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/UnknownTile.png b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/UnknownTile.png new file mode 100644 index 000000000..850fbf127 Binary files /dev/null and b/src/main/resources/edu/rpi/legup/images/shorttruthtable/tiles/UnknownTile.png differ diff --git a/src/main/resources/edu/rpi/legup/legup/config b/src/main/resources/edu/rpi/legup/legup/config index bb7da871a..24fdcf365 100644 --- a/src/main/resources/edu/rpi/legup/legup/config +++ b/src/main/resources/edu/rpi/legup/legup/config @@ -27,7 +27,7 @@ + fileCreationDisabled="false"/>