Skip to content

Commit

Permalink
Minesweeper (#815)
Browse files Browse the repository at this point in the history
* Initial Minesweeper implementation work

* Add Unset tile type

* Init elements

Created initial classes for bomb, flag, utilities, and contradiction rule.

* Make clicking tiles cycle between numbers, bomb, and empty tiles.

* Fix checkstyle errors

* Added

* Revert "Added"

This reverts commit 89fbe94.

* Added

* Create 123456

* Revert "Create 123456"

This reverts commit ace7122.

* Created Bomb or Filled Case Rule

* Update BombOrFilledCaseRule.java

removed fillapix imports

* Various additions

Added the unset puzzle element, added the minesweeper board copy function, fixed the bomb or filled case rule

* Revert "Create 123456"

This reverts commit ace7122.

* Update Minesweeper classes to use newer Java features

* Add documentation to Minesweeper classes

* Added helper functions to utilities class

Added helper functions used for getting cells adjacent to flag, as well as combinations of possible bomb tiles.

* Added function to retrieve a tile's number

* Create SatisfyFlagCaseRule.java

* Fixed "bomb or filled" case rule's picture

* Fixed "satisfy flag" case rule's picture

* Add some methods to MinesweeperUtilities and created some tests to verify the getSurroundingCells function works as expected.

* temp

* Added BombOrFilledCaseRule

* Revert "temp"

This reverts commit b4c8ed9.

* Update minesweeperUtilities.java

* Add bomb image

* Added reference sheet for Minesweeper tiles for puzzle editor

* Added Minesweeper Utility Functions

-Fixed Bomb or Filled case rule picture
-Added getTileNumber and setCellType functions
-added 5x5 test puzzle

* Fixed "satisfy flag" case rule

* Automated Java code formatting changes

* Revert "Automated Java code formatting changes"

This reverts commit f9d52a3.

* Added "More Bombs Than Flag" Contradiction Rule

* Delete Unset (replaced by UnsetTile)

* Added dot to empty tile image

* Fixed "Satisfy Flag" Case Rule Picture

* Fixed "satisfy flag" bug

fixed bug where "satisfy flag" allowed you to select an empty tile instead of a flag tile

* Added "Finish With Bombs" Direct Rule

* Update FinishWithBombsDirectRule.java

* Automated Java code formatting changes

* Automated Java code formatting changes

---------

Co-authored-by: Fisher <[email protected]>
Co-authored-by: FisherLuba <[email protected]>
Co-authored-by: philippark <[email protected]>
Co-authored-by: Chase Grajeda <[email protected]>
Co-authored-by: Bram van Heuveln <[email protected]>
Co-authored-by: Charles Tian <[email protected]>
  • Loading branch information
7 people authored May 5, 2024
1 parent 58eba41 commit d68859b
Show file tree
Hide file tree
Showing 51 changed files with 2,061 additions and 539 deletions.
11 changes: 11 additions & 0 deletions puzzles files/minesweeper/5x5 Minesweeper Easy/123456
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Legup version="2.0.0">
<puzzle name="Minesweeper">
<board height="5" width="5">
<cells>

</cells>
</board>
</puzzle>
<solved isSolved="false" lastSaved="--"/>
</Legup>
27 changes: 13 additions & 14 deletions src/main/java/edu/rpi/legup/model/PuzzleImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
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 @@ -132,18 +130,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;
}
/**
* 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
64 changes: 64 additions & 0 deletions src/main/java/edu/rpi/legup/puzzle/minesweeper/Minesweeper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package edu.rpi.legup.puzzle.minesweeper;

import edu.rpi.legup.model.Puzzle;
import edu.rpi.legup.model.gameboard.Board;
import edu.rpi.legup.model.gameboard.PuzzleElement;
import edu.rpi.legup.model.rules.ContradictionRule;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class Minesweeper extends Puzzle {

public static final String NAME = "Minesweeper";

public Minesweeper() {
this.name = NAME;
this.importer = new MinesweeperImporter(this);
this.exporter = new MinesweeperExporter(this);
this.factory = MinesweeperCellFactory.INSTANCE;
}

@Override
@Contract(pure = false)
public void initializeView() {
this.boardView = new MinesweeperView((MinesweeperBoard) this.currentBoard);
this.boardView.setBoard(this.currentBoard);
addBoardListener(boardView);
}

@Override
@Contract("_ -> null")
public @Nullable Board generatePuzzle(int difficulty) {
return null;
}

@Override
@Contract(pure = true)
public boolean isBoardComplete(@NotNull Board board) {
MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board;

for (ContradictionRule rule : contradictionRules) {
if (rule.checkContradiction(minesweeperBoard) == null) {
return false;
}
}
for (PuzzleElement<?> data : minesweeperBoard.getPuzzleElements()) {
final MinesweeperCell cell = (MinesweeperCell) data;
if (cell.getData().equals(MinesweeperTileData.empty())) {
return false;
}
}
return true;
}

@Override
@Contract(pure = true)
public void onBoardChange(@NotNull Board board) {}

@Override
@Contract(pure = true)
public boolean isValidDimensions(int rows, int columns) {
return rows >= 1 && columns >= 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package edu.rpi.legup.puzzle.minesweeper;

import edu.rpi.legup.model.gameboard.GridBoard;

public class MinesweeperBoard extends GridBoard {

public MinesweeperBoard(int width, int height) {
super(width, height);
}

public MinesweeperBoard(int size) {
super(size);
}

@Override
public MinesweeperCell getCell(int x, int y) {
return (MinesweeperCell) super.getCell(x, y);
}

/**
* Performs a deep copy of the Board
*
* @return a new copy of the board that is independent of this one
*/
@Override
public MinesweeperBoard copy() {
MinesweeperBoard newMinesweeperBoard =
new MinesweeperBoard(this.dimension.width, this.dimension.height);
for (int x = 0; x < this.dimension.width; x++) {
for (int y = 0; y < this.dimension.height; y++) {
newMinesweeperBoard.setCell(x, y, getCell(x, y).copy());
}
}
return newMinesweeperBoard;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package edu.rpi.legup.puzzle.minesweeper;

import edu.rpi.legup.model.elements.Element;
import edu.rpi.legup.model.gameboard.GridCell;
import java.awt.*;
import java.awt.event.MouseEvent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

public class MinesweeperCell extends GridCell<MinesweeperTileData> {

public MinesweeperCell(@NotNull MinesweeperTileData value, @NotNull Point location) {
super(value, location);
}

public @NotNull MinesweeperTileType getTileType() {
return super.data.type();
}

public @NotNull int getTileNumber() {
return super.data.data();
}

@Override
@Contract(pure = false)
/** Sets this cell's data to the value specified by {@link Element#getElementID()} */
public void setType(@NotNull Element element, @NotNull MouseEvent event) {
switch (element.getElementID()) {
case MinesweeperElementIdentifiers.BOMB -> {
this.data = MinesweeperTileData.bomb();
break;
}
case MinesweeperElementIdentifiers.FLAG -> {
final int currentData = super.data.data();
switch (event.getButton()) {
case MouseEvent.BUTTON1 -> {
if (currentData >= 8) {
this.data = MinesweeperTileData.empty();
return;
}
this.data = MinesweeperTileData.flag(currentData + 1);
return;
}
case MouseEvent.BUTTON2, MouseEvent.BUTTON3 -> {
if (currentData <= 0) {
this.data = MinesweeperTileData.empty();
return;
}
this.data = MinesweeperTileData.flag(currentData - 1);
return;
}
}
}
default -> {
this.data = MinesweeperTileData.empty();
}
}
}

public void setCellType(MinesweeperTileData type) {
this.data = type;
}

@Override
@Contract(pure = true)
public @NotNull MinesweeperCell copy() {
MinesweeperCell copy = new MinesweeperCell(data, (Point) location.clone());
copy.setIndex(index);
copy.setModifiable(isModifiable);
copy.setGiven(isGiven);
return copy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package edu.rpi.legup.puzzle.minesweeper;

import edu.rpi.legup.model.gameboard.Board;
import edu.rpi.legup.model.gameboard.ElementFactory;
import edu.rpi.legup.model.gameboard.PuzzleElement;
import edu.rpi.legup.save.InvalidFileFormatException;
import java.awt.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

public class MinesweeperCellFactory extends ElementFactory {

/** The key of the data used in {@link NamedNodeMap} */
private static final String DATA_ATTRIBUTE = "data";

/** The key of the x position used in {@link NamedNodeMap} */
private static final String X_ATTRIBUTE = "x";

/** The key of the y position used in {@link NamedNodeMap} */
private static final String Y_ATTRIBUTE = "y";

private MinesweeperCellFactory() {}

public static final MinesweeperCellFactory INSTANCE = new MinesweeperCellFactory();

/**
* @param node node that represents the puzzleElement
* @param board Board to use to verify the newly created {@link MinesweeperCell} is valid
* @return a new {@link MinesweeperCell}
* @throws InvalidFileFormatException If the node name is not "cell"
* @throws NumberFormatException If the {@link #X_ATTRIBUTE} or {@link #Y_ATTRIBUTE} is not a
* number
* @throws NullPointerException If one of {@link #DATA_ATTRIBUTE}, {@link #X_ATTRIBUTE} or
* {@link #Y_ATTRIBUTE} does not exist.
*/
@Override
@Contract(pure = false)
public @NotNull PuzzleElement<MinesweeperTileData> importCell(
@NotNull Node node, @NotNull Board board) throws InvalidFileFormatException {
try {
if (!node.getNodeName().equalsIgnoreCase("cell")) {
throw new InvalidFileFormatException(
"Minesweeper Factory: unknown puzzleElement puzzleElement");
}

MinesweeperBoard minesweeperBoard = (MinesweeperBoard) board;
final int width = minesweeperBoard.getWidth();
final int height = minesweeperBoard.getHeight();

final NamedNodeMap attributeList = node.getAttributes();
final int value =
Integer.parseInt(attributeList.getNamedItem(DATA_ATTRIBUTE).getNodeValue());
final int x = Integer.parseInt(attributeList.getNamedItem(X_ATTRIBUTE).getNodeValue());
final int y = Integer.parseInt(attributeList.getNamedItem(Y_ATTRIBUTE).getNodeValue());
if (x >= width || y >= height) {
throw new InvalidFileFormatException(
"Minesweeper Factory: cell location out of bounds");
}
if (value < -2) {
throw new InvalidFileFormatException("Minesweeper Factory: cell unknown value");
}
final MinesweeperCell cell =
new MinesweeperCell(MinesweeperTileData.fromData(value), new Point(x, y));
cell.setIndex(y * height + x);
return cell;
} catch (NumberFormatException e) {
throw new InvalidFileFormatException(
"Minesweeper Factory: unknown value where integer expected");
} catch (NullPointerException e) {
throw new InvalidFileFormatException(
"Minesweeper Factory: could not find attribute(s)");
}
}

/**
* @param document Document used to create the element
* @param puzzleElement PuzzleElement cell
* @return a {@link Element} that contains the {@link #DATA_ATTRIBUTE}, {@link #X_ATTRIBUTE},
* and {@link #Y_ATTRIBUTE}
*/
@Override
@Contract(pure = false)
public @NotNull Element exportCell(
@NotNull Document document,
@SuppressWarnings("rawtypes") @NotNull PuzzleElement puzzleElement) {
org.w3c.dom.Element cellElement = document.createElement("cell");

MinesweeperCell cell = (MinesweeperCell) puzzleElement;
Point loc = cell.getLocation();

cellElement.setAttribute(DATA_ATTRIBUTE, String.valueOf(cell.getData()));
cellElement.setAttribute(X_ATTRIBUTE, String.valueOf(loc.x));
cellElement.setAttribute(Y_ATTRIBUTE, String.valueOf(loc.y));

return cellElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package edu.rpi.legup.puzzle.minesweeper;

import edu.rpi.legup.controller.ElementController;
import edu.rpi.legup.model.gameboard.PuzzleElement;
import java.awt.event.MouseEvent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

public class MinesweeperController extends ElementController {

/**
* If the button clicked was button 1, then {@link MinesweeperTileData#fromData(int)} is called
* with a value of {@code current.data() + 1}. If the button clicked was button 2 or 3, then
* {@link MinesweeperTileData#fromData(int)} is called with a value of {@code currentData() - 1}
* Otherwise {@link MinesweeperTileData#empty()} is returned.
*
* @param event The user's click data
* @param current The current data at the cell they clicked on
* @return A different cell data depending on what the current data is
*/
@Contract(pure = true)
public static @NotNull MinesweeperTileData getNewCellDataOnClick(
@NotNull MouseEvent event, @NotNull MinesweeperTileData current) {
final int numberData = current.data();
return switch (event.getButton()) {
case MouseEvent.BUTTON1 -> MinesweeperTileData.fromData(numberData + 1);
case MouseEvent.BUTTON2, MouseEvent.BUTTON3 ->
MinesweeperTileData.fromData(numberData - 1);
default -> MinesweeperTileData.empty();
};
}

/**
* @see #getNewCellDataOnClick(MouseEvent, MinesweeperTileData)
* @param event The user's click data
* @param data The current data at the cell they clicked on
*/
@Override
@SuppressWarnings("unchecked")
@Contract(pure = false)
public void changeCell(
@NotNull MouseEvent event, @SuppressWarnings("rawtypes") @NotNull PuzzleElement data) {
final MinesweeperCell cell = (MinesweeperCell) data;
if (event.isControlDown()) {
this.boardView
.getSelectionPopupMenu()
.show(
boardView,
this.boardView.getCanvas().getX() + event.getX(),
this.boardView.getCanvas().getY() + event.getY());
return;
}

final MinesweeperTileData newData = getNewCellDataOnClick(event, cell.getData());
data.setData(newData);
}
}
Loading

0 comments on commit d68859b

Please sign in to comment.