package lpa.model;
import java.awt.Dimension;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import lpa.command.CompoundCommand;
import lpa.command.EdgeCommand;
import lpa.model.Edge.EdgeState;
/**
* A rectangular grid of cells with integers. Some members (instance variable,
* inner class) are protected instead of private to allow access from inherited
* classes.
*
* @author Dimitry Leonov
*/
public final class Grid {
private final List<GridElement> items = new ArrayList<>();
private boolean isInEditMode = false;
private int width = 0, height = 0;
/**
* Constructs the grid using the scanner as an input source.
*
* @param scanner {@link Scanner} linked with the file with puzzle data.
*/
public Grid(Scanner scanner) {
int i = 0, j = 0;
while (scanner.hasNext()) {
char[] chars = scanner.nextLine().toCharArray();
for (char ch : chars) {
boolean is_cell = i % 2 == 1 && j % 2 == 1;
if (is_cell) {
if ((ch < '0' || ch > '3') && !Character.isWhitespace(ch)) {
throw new IllegalArgumentException("Grid contains invalid symbols.");
}
items.add(new Cell(i, j, Character.isWhitespace(ch) ? 4 : ch - '0'));
} else {
switch (ch) {
case '+':
items.add(new Vertex(i, j));
break;
case '.':
case ' ':
items.add(new Edge(i, j, EdgeState.MAYBE));
break;
case '-':
case '|':
items.add(new Edge(i, j, EdgeState.YES));
break;
}
}
++j;
}
++i;
width = j;
j = 0;
}
height = i;
GridElement.setParentGrid(this);
}
/**
* Constructs empty grid using given width and height.
*
* @param _width Width of the grid.
* @param _height Height of the grid.
*/
public Grid(int _width, int _height) {
this.width = _width * 2 + 1;
this.height = _height * 2 + 1;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if (i % 2 == 1 && j % 2 == 1) {
items.add(new Cell(i, j, 4));
} else if (i % 2 == 0 && j % 2 == 0) {
items.add(new Vertex(i, j));
} else {
items.add(new Edge(i, j, EdgeState.MAYBE));
}
}
}
GridElement.setParentGrid(this);
}
/**
* Returns list of elements in the grid.
*
* @return List of elements in the grid.
*/
public List<GridElement> getGridElements() {
return items;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
int i = 0;
for (GridElement e : items) {
str.append(e.toString());
if (i >= width() - 1) {
i = 0;
str.append("\n");
} else {
++i;
}
}
return str.toString();
}
/**
* Performs hit test using provided point.
*
* @param p {@link Point} to test.
* @param parentSize {@link Dimension} of GUI component.
* @return Element which was hitted, or null if no element was hitted.
*/
public GridElement getElementByPoint(Point p, Dimension parentSize) {
for (GridElement e : items) {
if (e.isHit(p, parentSize)) {
return e;
}
}
return null;
}
/**
* Returns total number of elements in grid.
*
* @return Total number of elements in grid.
*/
protected int itemCount() {
return items.size();
}
/**
* Returns width of the grid, in elements.
*
* @return Width of the grid.
*/
protected int width() {
return width;
}
/**
* Returns height of the grid, in elements.
*
* @return Height of the grid.
*/
protected int height() {
return height;
}
/**
* Calculates value of the edge's minimal dimension.
*
* @param size {@link Dimension} of the caller GUI component.
* @return Value of the edge's minimal dimension.
*/
protected int getElementSize(Dimension size) {
int unit_width = width / 2 * 6 + 1;
int unit_height = height / 2 * 6 + 1;
int unit_size = Math.min(size.width / unit_width, size.height / unit_height);
return unit_size;
}
/**
* Checks the validity of the grid.
*
* @return String with the error or message about completing the puzzle.
*/
public String checkGrid() {
String error = "";
for (GridElement e : items) {
if (e instanceof Cell) {
if (!((Cell) e).isEdgesValid() && "".equals(error)) {
error = "Invalid edge count at cell " + e.coordsToString();
}
}
if (e instanceof Vertex) {
if (!((Vertex) e).isEdgesValid() && "".equals(error)) {
error = "Invalid edge count at vertex " + e.coordsToString();
}
}
}
if ("".equals(error)) {
List<Edge> edges = new ArrayList<>();
for (GridElement e : items) {
if (e instanceof Edge && ((Edge) e).getState() == EdgeState.YES) {
edges.add((Edge) e);
}
}
if (edges.size() > 0) {
boolean[] flags = new boolean[edges.size()];
Arrays.fill(flags, true);
Edge current_edge = edges.get(0);
flags[0] = false;
while (true) {
List<Edge> list = current_edge.getAdjacentEdges();
if (list.isEmpty()) {
break;
}
int i1 = edges.indexOf(list.get(0));
int i2 = edges.indexOf(list.get(1));
if (flags[i1]) {
current_edge = list.get(0);
flags[i1] = false;
} else if (flags[i2]) {
current_edge = list.get(1);
flags[i2] = false;
} else {
break;
}
}
for (boolean i : flags) {
if (i) {
error = "Multiple loops found.";
break;
}
}
}
}
if ("".equals(error)) {
error = "Congratulations. Puzzle solved.";
}
return error;
}
/**
* Turns edit mode on and off.
*
* @param is_on True for enabling edit mode, false otherwise.
*/
public void setEditMode(boolean is_on) {
isInEditMode = is_on;
}
/**
* Returns true if edit mode is enabled, false otherwise.
*
* @return True if edit mode is enabled, false otherwise.
*/
public boolean editMode() {
return isInEditMode;
}
private static int getEdgeCount(List<Edge> edges, EdgeState state) {
int count = 0;
for (Edge i : edges) {
if (i.getState() == state) {
++count;
}
}
return count;
}
private static boolean applyStrategy(List<Edge> edges, EdgeState condition_state,
EdgeState target_state, CompoundCommand command) {
//int count = getEdgeCount(edges, target_state);
int action_count = 0;
for (Edge i : edges) {
if (i.getState() != condition_state && i.getState() != target_state) {
command.add(new EdgeCommand(i, target_state));
i.setState(target_state);
++action_count;
}
}
return action_count != 0;
}
private boolean iterateStrategies(CompoundCommand command) {
for (GridElement e : items) {
if (e instanceof Vertex) {
Vertex v = (Vertex) e;
List<Edge> edges = v.surroundingEdges(false);
/* strategy 2
* If a vertex has two yes-edges,
* then its other edges can be marked as no-edges
* */
int yes_edges = getEdgeCount(edges, EdgeState.YES);
if (yes_edges == 2) {
if (applyStrategy(edges, EdgeState.YES, EdgeState.NO, command)) {
return true;
}
}
/* strategy 4
* If all but one edge at a vertex are no-edges,
* then the remaining edge can be marked as no-edge as well.
* */
int no_edges = getEdgeCount(edges, EdgeState.NO);
if (no_edges == edges.size() - 1) {
if (applyStrategy(edges, EdgeState.NO, EdgeState.NO, command)) {
return true;
}
}
}
if (e instanceof Cell) {
Cell c = (Cell) e;
List<Edge> edges = c.surroundingEdges(false);
/* strategy 1
* If a cell containing digit d is surrounded by d yes-edges,
* then all its other edges can be marked as no-edges.
* */
int yes_edges = getEdgeCount(edges, EdgeState.YES);
if (yes_edges == c.getValue() && !c.isEmpty()) {
if (applyStrategy(edges, EdgeState.YES, EdgeState.NO, command)) {
return true;
}
}
/* strategy 3
* If a cell containing digit d is surrounded by 4 - d no-edges,
* then the remaining edges can be marked as yes-edges.
* */
int no_edges = getEdgeCount(edges, EdgeState.NO);
if (no_edges == 4 - c.getValue() && !c.isEmpty()) {
if (applyStrategy(edges, EdgeState.NO, EdgeState.YES, command)) {
return true;
}
}
}
}
return false;
}
/**
* Applies all the strategies until no strategy is suitable.
*
* @return {@link CompoundCommand} with all the actions performed while
* applying strategies.
*/
public CompoundCommand applyStrategies() {
CompoundCommand command = new CompoundCommand();
while (iterateStrategies(command)) {
}
/* strategies 5 and 6 (corner cells)
* If corner cell contains digit 1,
* then its two outer edges can be marked as no-edges.
* If corner cell contains digit 3,
* then its two outer edges can be marked as yes-edges.
*/
//upper-left cell
Cell c11 = (Cell) items.get(width() + 1);
if (c11.getValue() == 1) {
((Edge) items.get(1)).setState(EdgeState.NO);
((Edge) items.get(width())).setState(EdgeState.NO);
} else if (c11.getValue() == 3) {
((Edge) items.get(1)).setState(EdgeState.YES);
((Edge) items.get(width())).setState(EdgeState.YES);
}
//upper-right cell
Cell c12 = (Cell) items.get(width() * 2 - 2);
if (c12.getValue() == 1) {
((Edge) items.get(width() - 2)).setState(EdgeState.NO);
((Edge) items.get(width() * 2 - 1)).setState(EdgeState.NO);
} else if (c12.getValue() == 3) {
((Edge) items.get(width() - 2)).setState(EdgeState.YES);
((Edge) items.get(width() * 2 - 1)).setState(EdgeState.YES);
}
//bottom-left cell
Cell c21 = (Cell) items.get(itemCount() - width() * 2 + 1);
if (c21.getValue() == 1) {
((Edge) items.get(itemCount() - width() * 2)).setState(EdgeState.NO);
((Edge) items.get(itemCount() - width() + 1)).setState(EdgeState.NO);
} else if (c21.getValue() == 3) {
((Edge) items.get(itemCount() - width() * 2)).setState(EdgeState.YES);
((Edge) items.get(itemCount() - width() + 1)).setState(EdgeState.YES);
}
//bottom-right cell
Cell c22 = (Cell) items.get(itemCount() - width() - 2);
if (c22.getValue() == 1) {
((Edge) items.get(itemCount() - width() - 1)).setState(EdgeState.NO);
((Edge) items.get(itemCount() - 2)).setState(EdgeState.NO);
} else if (c22.getValue() == 3) {
((Edge) items.get(itemCount() - width() - 1)).setState(EdgeState.YES);
((Edge) items.get(itemCount() - 2)).setState(EdgeState.YES);
}
return command;
}
}