Add files via upload

This commit is contained in:
fassband
2021-11-19 08:32:32 +01:00
committed by GitHub Enterprise
parent e9a9181cd0
commit f2fe06f720
19 changed files with 8166 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
package ch.zhaw.hexboard;
import java.awt.Point;
/**
* This class models an edge on @see ch.zhaw.hexboard.HexBoard.
* <p>
* Edges are non-directional and can be created by providing the two points that
* span an edge on the hex-grid defined by @see ch.zhaw.hexboard.HexBoard
* </p>
* @author tebe
*
*/
final class Edge {
private Point start;
private Point end;
/**
* Creates an edge between the two points.
*
* @param p1 first point
* @param p2 second point
* @throws IllegalArgumentException if the points are not non-null or not a
* valid point for an edge on the grid defined
* by @see ch.zhaw.hexboard.HexBoard
*/
public Edge(Point p1, Point p2) {
if (Edge.isEdge(p1, p2)) {
if (p1.x > p2.x || (p1.x == p2.x && p1.y > p2.y)) {
this.start = new Point(p2);
this.end = new Point(p1);
} else {
this.start = new Point(p1);
this.end = new Point(p2);
}
} else {
throw new IllegalArgumentException(
"Coordinates " + p1 + " and " + p2 + " are not coordinates of an edge.");
}
}
static boolean isEdge(Point p1, Point p2) {
boolean isEdge = false;
if (p1 != null && p2 != null && HexBoard.isCornerCoordinate(p1)
&& HexBoard.isCornerCoordinate(p2)) {
int xdistance = Math.abs(p1.x - p2.x);
int ydistance = Math.abs(p1.y - p2.y);
boolean isVerticalEdge = xdistance == 0 && ydistance == 2;
boolean isDiagonalEdge = xdistance == 1 && ydistance == 1;
isEdge = isVerticalEdge || isDiagonalEdge;
}
return isEdge;
}
public boolean isEdgePoint(Point p1) {
return start.equals(p1) || end.equals(p1);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((end == null) ? 0 : end.hashCode());
result = prime * result + ((start == null) ? 0 : start.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Edge other = (Edge) obj;
if (end == null) {
if (other.end != null) {
return false;
}
} else if (!end.equals(other.end)) {
return false;
}
if (start == null) {
if (other.start != null) {
return false;
}
} else if (!start.equals(other.start)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Edge [start=" + start + ", end=" + end + "]";
}
}
@@ -0,0 +1,114 @@
package ch.zhaw.hexboard;
import java.awt.Point;
/**
* This class models an annotation for the hex-fields of the hex-grid defined
* by @see ch.zhaw.hexboard.HexBoard
*
* @author tebe
*
*/
final class FieldAnnotationPosition {
private Point field;
private Point corner;
/**
* Creates a field annotation for the specified field.
*
* @param field the field to be annotated
* @param corner the location of the annotation
* @throws IllegalArgumentException if arguments are null or not valid
* field/corner coordinates (@see
* ch.zhaw.hexboard.HexBoard).
*/
public FieldAnnotationPosition(Point field, Point corner) {
if (HexBoard.isCorner(field, corner)) {
this.field = field;
this.corner = corner;
} else {
throw new IllegalArgumentException("" + field + " is not a field coordinate or " + corner
+ " is not a corner of the field.");
}
}
/**
* Checks whether the provided coordinate matches the position of the annotation
* within the field.
*
* @param p the corner coordinate
* @return true, if they match
*/
public boolean isCorner(Point p) {
return corner.equals(p);
}
/**
* Checks whether the provided coordinate matches the field coordinate of this
* annotation.
*
* @param p a field coordinate
* @return true, if they match
*/
public boolean isField(Point p) {
return field.equals(p);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((field == null) ? 0 : field.hashCode());
result = prime * result + ((corner == null) ? 0 : corner.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
FieldAnnotationPosition other = (FieldAnnotationPosition) obj;
if (field == null) {
if (other.field != null) {
return false;
}
} else if (!field.equals(other.field)) {
return false;
}
if (corner == null) {
if (other.corner != null) {
return false;
}
} else if (!corner.equals(other.corner)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "FieldAnnotationPosition [field=" + field + ", corner=" + corner + "]";
}
}
+541
View File
@@ -0,0 +1,541 @@
package ch.zhaw.hexboard;
import java.awt.Point;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/***
* <p>
* This class represents a simple generic hexagonal game board.
* </p>
* <p>The game board uses a fixed coordinate system which is structured as follows:</p>
*
* <pre>
* 0 1 2 3 4 5 6 7 8
* | | | | | | | | | ...
*
* 0---- C C C C C
* \ / \ / \ / \ / \
* 1---- C C C C C
*
* 2---- F | F | F | F | F | ...
*
* 3---- C C C C C
* / \ / \ / \ / \ /
* 4---- C C C C C
*
* 5---- | F | F | F | F | F ...
*
* 6---- C C C C C
* \ / \ / \ / \ / \
* 7---- C C C C C
*
* ...
* </pre>
*
* <p>
* Fields <strong>F</strong> and corners <strong>C</strong> can be retrieved
* using their coordinates ({@link java.awt.Point}) on the board. Edges can be
* retrieved using the coordinates of the two corners they connect.
* </p>
*
* <p>
* When created, the board is empty (no fields added). To add fields, the
* #{@link #addField(Point, Object)} function can be used. Edges and corners are
* automatically created when adding a field. They cannot be created/removed
* individually. When adding a field, edges and corners that were already
* created, e.g., because adding an adjacent field already created them, are
* left untouched.
* </p>
*
* <p>
* Fields, edges and corners can store an object of the type of the
* corresponding type parameter each.
* </p>
*
* <p>
* Furthermore, the hexagonal game board can store six additional objects, so
* called annotations, for each field. These objects are identified by the
* coordinates of the field and the corner. Hence, they can be thought of being
* located between the center and the respective corner. Or in other words,
* their positions correspond to the positions N, NW, SW, NE, NW, SE and NE in
* the below visualization of a field.
* </p>
*
* <pre>
* SW (C) SE
* / N \
* (C) NW NE (C)
* | F |
* | |
* (C) SW SE (C)
* \ S /
* NW (C) NE
* </pre>
*
* @param <F> Data type for the field data objects
* @param <C> Data type for the corner data objects
* @param <E> Data type for the edge data objects
* @param <A> Data type for the annotation data objects
*
* @author tebe
*
*/
public class HexBoard<F, C, E, A> {
private int maxCoordinateX = 0;
private int maxCoordinateY = 0;
private final Map<Point, F> field;
private final Map<Point, C> corner;
private final Map<Edge, E> edge;
private final Map<FieldAnnotationPosition, A> annotation;
/**
* Constructs an empty hexagonal board.
*/
public HexBoard() {
field = new HashMap<>();
corner = new HashMap<>();
edge = new HashMap<>();
annotation = new HashMap<>();
}
/**
* Adds a field to the board and creates the surrounding (empty) corners and
* edges if they do not yet exist Note: Corners and edges of a field might
* already have been created while creating adjacent fields.
*
* @param center Coordinate of the center of a field on the unit grid
* @param element Data element to be stored for this field
*
* @throws IllegalArgumentException if center is not the center of a field, the
* field already exists or data is null
*/
public void addField(Point center, F element) {
if (isFieldCoordinate(center) && !field.containsKey(center)) {
field.put(center, element);
maxCoordinateX = Math.max(center.x + 1, maxCoordinateX);
maxCoordinateY = Math.max(center.y + 2, maxCoordinateY);
// add (empty) edge, if they do not yet exist
for (Edge e : constructEdgesOfField(center)) {
if (!edge.containsKey(e)) {
edge.put(e, null);
}
}
// add (empty) corners, if they do not yet exist
for (Point p : getCornerCoordinatesOfField(center)) {
if (!corner.containsKey(p)) {
corner.put(p, null);
}
}
} else {
throw new IllegalArgumentException(
"Coordinates are not the center of a field, the field already exists or data is null - ("
+ center.x + ", " + center.y + ")");
}
}
/**
* Add an annotation for the specified field and corner.
*
* @param center the center of the field
* @param corner the corner of the field
* @param data the annotation
* @throws IllegalArgumentException if the field does not exists or when the
* annotation already exists
*/
public void addFieldAnnotation(Point center, Point corner, A data) {
FieldAnnotationPosition annotationPosition = new FieldAnnotationPosition(center, corner);
if (!annotation.containsKey(annotationPosition)) {
annotation.put(annotationPosition, data);
} else {
throw new IllegalArgumentException("Annotation: " + annotation + " already exists for field "
+ center + " and position " + corner);
}
}
/**
* Get an annotation for the specified field and corner.
*
* @param center the center of the field
* @param corner the corner of the field
* @return the annotation
* @throws IllegalArgumentException if coordinates are not a field and
* corresponding corner coordinate
*/
public A getFieldAnnotation(Point center, Point corner) {
return annotation.get(new FieldAnnotationPosition(center, corner));
}
/**
* Get field annotation whose position information includes the specified corner.
*
* @param corner the corner
* @return a list with the annotations that are not null
* @throws IllegalArgumentException if corner is not a corner
*/
public List<A> getFieldAnnotationsForCorner(Point corner) {
List<A> list = new LinkedList<>();
for (Entry<FieldAnnotationPosition, A> entry : annotation.entrySet()) {
if (entry.getKey().isCorner(corner) && entry.getValue() != null) {
list.add(entry.getValue());
}
}
return list;
}
/**
* Get all field annotation of the specified field.
*
* @param center the field
* @return a list with the annotations that are not null
* @throws IllegalArgumentException if center is not a field
*/
public List<A> getFieldAnnotationsForField(Point center) {
List<A> list = new LinkedList<>();
for (Entry<FieldAnnotationPosition, A> entry : annotation.entrySet()) {
if (entry.getKey().isField(center) && entry.getValue() != null) {
list.add(entry.getValue());
}
}
return list;
}
/**
* Determines whether the field at the specified position exists.
*
* @param center the field
* @return false, if the field does not exist or the position is not a field
*/
public boolean hasField(Point center) {
if (!HexBoard.isFieldCoordinate(center)) {
return false;
}
return field.containsKey(center);
}
static boolean isFieldCoordinate(Point position) {
boolean isYFieldCoordinateEven = (position.y - 2) % 6 == 0;
boolean isYFieldCoordinateOdd = (position.y - 5) % 6 == 0;
boolean isXFieldCoordinateEven = position.x % 2 == 0;
boolean isXFieldCoordinateOdd = (position.x - 1) % 2 == 0;
return (position.y >= 2 && position.x >= 1)
&& (isYFieldCoordinateEven && isXFieldCoordinateEven)
|| (isYFieldCoordinateOdd && isXFieldCoordinateOdd);
}
static boolean isCornerCoordinate(Point p) {
// On the horizontal center lines, no edge points exist
boolean isOnFieldCenterLineHorizontal = (p.y - 2) % 3 == 0;
// On the vertical center lines, edge points exist
boolean isOnFieldCenterLineVerticalOdd = (p.x - 1) % 3 == 0 && p.x % 2 == 0;
boolean isOnFieldCenterLineVerticalEven = (p.x - 1) % 3 == 0 && (p.x - 1) % 2 == 0;
boolean isNotAnEdgePointOnFieldCentralVerticalLine = isOnFieldCenterLineVerticalOdd
&& !(p.y % 6 == 0 || (p.y + 2) % 6 == 0)
|| isOnFieldCenterLineVerticalEven && !((p.y + 5) % 6 == 0 || (p.y + 3) % 6 == 0);
return !(isOnFieldCenterLineHorizontal || isNotAnEdgePointOnFieldCentralVerticalLine);
}
private List<Edge> constructEdgesOfField(Point position) {
Edge[] e = new Edge[6];
e[0] = new Edge(new Point(position.x, position.y - 2),
new Point(position.x + 1, position.y - 1));
e[1] = new Edge(new Point(position.x + 1, position.y - 1),
new Point(position.x + 1, position.y + 1));
e[2] = new Edge(new Point(position.x + 1, position.y + 1),
new Point(position.x, position.y + 2));
e[3] = new Edge(new Point(position.x, position.y + 2),
new Point(position.x - 1, position.y + 1));
e[4] = new Edge(new Point(position.x - 1, position.y + 1),
new Point(position.x - 1, position.y - 1));
e[5] = new Edge(new Point(position.x - 1, position.y - 1),
new Point(position.x, position.y - 2));
return Arrays.asList(e);
}
private static List<Point> getCornerCoordinatesOfField(Point position) {
Point[] corner = new Point[6];
corner[0] = new Point(position.x, position.y - 2);
corner[1] = new Point(position.x + 1, position.y - 1);
corner[2] = new Point(position.x + 1, position.y + 1);
corner[3] = new Point(position.x, position.y + 2);
corner[4] = new Point(position.x - 1, position.y - 1);
corner[5] = new Point(position.x - 1, position.y + 1);
return Collections.unmodifiableList(Arrays.asList(corner));
}
protected static List<Point> getAdjacentCorners(Point position) {
Point[] corner = new Point[3];
if (position.y % 3 == 0) {
corner[0] = new Point(position.x, position.y - 2);
corner[1] = new Point(position.x + 1, position.y + 1);
corner[2] = new Point(position.x - 1, position.y + 1);
} else {
corner[0] = new Point(position.x, position.y + 2);
corner[1] = new Point(position.x + 1, position.y - 1);
corner[2] = new Point(position.x - 1, position.y - 1);
}
return Collections.unmodifiableList(Arrays.asList(corner));
}
/**
* Returns all non-null corner data elements.
*
* @return the non-null corner data elements
*/
public List<C> getCorners() {
List<C> result = new LinkedList<>();
for (C c : this.corner.values()) {
if (c != null) {
result.add(c);
}
}
return Collections.unmodifiableList(result);
}
protected Set<Point> getCornerCoordinates() {
return Collections.unmodifiableSet(this.corner.keySet());
}
private static List<Point> getAdjacentFields(Point corner) {
Point[] field = new Point[3];
if (corner.y % 3 == 0) {
field[0] = new Point(corner.x, corner.y + 2);
field[1] = new Point(corner.x + 1, corner.y - 1);
field[2] = new Point(corner.x - 1, corner.y - 1);
} else {
field[0] = new Point(corner.x, corner.y - 2);
field[1] = new Point(corner.x + 1, corner.y + 1);
field[2] = new Point(corner.x - 1, corner.y + 1);
}
return Collections.unmodifiableList(Arrays.asList(field));
}
/**
* Returns the data for the field denoted by the point.
*
* @param center the location of the field
* @return the stored data (or null)
* @throws IllegalArgumentException if the requested field does not exist
*/
public F getField(Point center) {
if (field.containsKey(center)) {
return field.get(center);
} else {
throw new IllegalArgumentException("No field exists at these coordinates: " + center);
}
}
/**
* Returns the fields with non-null data elements.
*
* @return the list with the (non-null) field data
*/
public List<Point> getFields() {
List<Point> result = new LinkedList<>();
for (Entry<Point, F> e : field.entrySet()) {
if (e.getValue() != null) {
result.add(e.getKey());
}
}
return Collections.unmodifiableList(result);
}
/**
* Returns the field data of the fields that touch this corner.
* <p>
* If the specified corner is not a corner or none of the fields that touch this
* corner have a non-null data element, an empty list is returned.
* </p>
* @param corner the location of the corner
* @return the list with the (non-null) field data
*/
public List<F> getFields(Point corner) {
List<F> result = new LinkedList<>();
if (isCornerCoordinate(corner)) {
for (Point f : getAdjacentFields(corner)) {
if (field.get(f) != null) {
result.add(field.get(f));
}
}
}
return Collections.unmodifiableList(result);
}
/**
* Returns the data for the edge denoted by the two points.
*
* @param p1 first point
* @param p2 second point
* @return the stored data (or null)
*/
public E getEdge(Point p1, Point p2) {
Edge e = new Edge(p1, p2);
if (edge.containsKey(e)) {
return edge.get(e);
} else {
return null;
}
}
/**
* Stores the data for the edge denoted by the two points.
*
* @param p1 first point
* @param p2 second point
* @param data the data to be stored
* @throws IllegalArgumentException if the two points do not identify an
* EXISTING edge of the field
*/
public void setEdge(Point p1, Point p2, E data) {
Edge e = new Edge(p1, p2);
if (edge.containsKey(e)) {
edge.put(e, data);
} else {
throw new IllegalArgumentException("Edge does not exist => no data can be stored: " + e);
}
}
/**
* Returns the data for the corner denoted by the point.
*
* @param location the location of the corner
* @return the data stored for this node (or null)
* @throws IllegalArgumentException if the requested corner does not exist
*/
public C getCorner(Point location) {
if (corner.containsKey(location)) {
return corner.get(location);
} else {
throw new IllegalArgumentException("No corner exists at the coordinates: " + location);
}
}
/**
* Stores the data for the edge denoted by the two points.
*
* @param location the location of the corner
* @param data the data to be stored
* @return the old data entry (or null)
* @throws IllegalArgumentException if there is no corner at this location
*/
public C setCorner(Point location, C data) {
C old = corner.get(location);
if (corner.containsKey(location)) {
corner.put(location, data);
return old;
} else {
throw new IllegalArgumentException(
"Corner does not exist => no data can be stored: " + location);
}
}
/**
* Returns the (non-null) corner data elements of the corners that are direct
* neighbors of the specified corner.
* <p>
* Each corner has three direct neighbors, except corners that are located at
* the border of the game board.
* </p>
* @param center the location of the corner for which to return the direct
* neighbors
* @return list with non-null corner data elements
*/
public List<C> getNeighboursOfCorner(Point center) {
List<C> result = new LinkedList<>();
for (Point c : HexBoard.getAdjacentCorners(center)) {
C temp = corner.get(c);
if (temp != null) {
result.add(temp);
}
}
return result;
}
/**
* Returns the (non-null) edge data elements of the edges that directly connect
* to that corner.
* <p>
* Each corner has three edges connecting to it, except edges that are located
* at the border of the game board.
* </p>
* @param corner corner for which to get the edges
* @return list with non-null edge data elements of edges connecting to the
* specified edge
*/
public List<E> getAdjacentEdges(Point corner) {
List<E> result = new LinkedList<>();
for (Entry<Edge, E> e : this.edge.entrySet()) {
if (e.getKey().isEdgePoint(corner)
&& e.getValue() != null) {
result.add(e.getValue());
}
}
return result;
}
/**
* Returns the (non-null) data elements of the corners of the specified field.
*
* @param center the location of the field
* @return list with non-null corner data elements
*/
public List<C> getCornersOfField(Point center) {
List<C> result = new LinkedList<>();
for (Point c : getCornerCoordinatesOfField(center)) {
C temp = getCorner(c);
if (temp != null) {
result.add(temp);
}
}
return result;
}
int getMaxCoordinateX() {
return maxCoordinateX;
}
int getMaxCoordinateY() {
return maxCoordinateY;
}
/**
* Checks whether there is a corner at that specified location.
* @param location the location to check
* @return true, if there is a corner at this location
*/
public boolean hasCorner(Point location) {
if (!HexBoard.isCornerCoordinate(location)) {
return false;
}
return corner.containsKey(location);
}
/**
* Checks whether there is an edge between the two points.
* @param p1 first point
* @param p2 second point
* @return true, if there is an edge between the two points
*/
public boolean hasEdge(Point p1, Point p2) {
if (Edge.isEdge(p1, p2)) {
return edge.containsKey(new Edge(p1, p2));
} else {
return false;
}
}
static boolean isCorner(Point field, Point corner) {
return HexBoard.isFieldCoordinate(field)
&& HexBoard.getCornerCoordinatesOfField(field).contains(corner);
}
}
+366
View File
@@ -0,0 +1,366 @@
package ch.zhaw.hexboard;
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
/**
* This class can be used to get a textual representation of a hex-grid modeled
* by {@link ch.zhaw.hexboard.HexBoard}.
* <p>
* It creates a textual representation of the {@link ch.zhaw.hexboard.HexBoard}
* that includes all defined fields, edges, corners and annotations.
* </p>
* The generation of the textual representation is basically working on a line
* by line basis. Thereby, the two text lines needed for the diagonal edges are
* treated like "one line" in that they are created in one step together.
* <p>
* The textual representation does not contain the hex-grid as such but only the
* fields that actually exist on the hex-board. Note that if a field exists,
* also its corners and edges exist and are therefore shown in the textual
* representation.
* </p>
* <p>
* This class defines how edges, corners and fields look like (their "label").
* This is done as follows:</p>
* <ul>
* <li>If there is no data object associated with an edge, corner or field,
* their default representation is used. Note that the default representation of
* an edge depends on its direction (see below).</li>
* <li>If there is a data object associated with an edge, corner or field, the
* {@link ch.zhaw.hexboard.Label} is determined by calling:
* <ul>
* <li>EL = {@link #getEdgeLabel(Object)}</li>
* <li>CL = {@link #getCornerLabel(Object)}</li>
* <li>UL = {@link #getFieldLabelUpper(Object)}</li>
* </ul>
* </li>
* </ul>
* <p>In addition to edges, corners and field labels, the hex-board's field
* annotations are included too. If an annotation exists for one of the corners
* (N, NW, SW, S, SE, NE), which means that an associated data object exists, it
* is turned into a {@link ch.zhaw.hexboard.Label} with
* {@link #getAnnotationLabel(Object)}.</p>
* <br>
* <p>Two examples of how that looks like are shown below. The first example shows
* a case with all edges, corners and the field with no data associated with
* them. The second one has all edges corner and the upper field label defined
* by calling the corresponding method for creating the Label for the associated
* data object.</p>
*
* <pre>
* DEFAULT LABELS FROM DATA
*
* ( ) (CL)
* // \\ EL EL
* // \\ EL N EL
* ( ) ( ) (CL) NW NE (CL)
* || || EL UL EL
* || || EL EL
* ( ) ( ) (CL) SW SE (CL)
* \\ // EL S EL
* \\ // EL EL
* ( ) (CL)
* </pre>
* <br>
* <p>To override the default behavior, which creates a Label using the two first
* characters of the string returned by the toString() method of the
* edge/corner/field data object, you might override the respective methods.
* </p>
* <br>
* <p>
* Finally, a field can be labeled with a lower label (LL) by providing a map of
* field coordinates and associated labels. An example of a representation with
* all field annotations, corner labels and field labels defined but default
* edges is the following:</p>
*
* <pre>
* (CL)
* // N \\
* // \\
* (CL) NW NE (CL)
* || UL ||
* || LL ||
* (CL) SW SE (CL)
* \\ S //
* \\ //
* (CL)
* </pre>
*
* @param <F> See {@link ch.zhaw.hexboard.HexBoard}
* @param <C> See {@link ch.zhaw.hexboard.HexBoard}
* @param <E> See {@link ch.zhaw.hexboard.HexBoard}
* @param <A> See {@link ch.zhaw.hexboard.HexBoard}
*
*
* @author tebe
*/
public class HexBoardTextView<F, C, E, A> {
private static final String ONE_SPACE = " ";
private static final String TWO_SPACES = " ";
private static final String FOUR_SPACES = " ";
private static final String FIVE_SPACES = " ";
private static final String SIX_SPACES = " ";
private static final String SEVEN_SPACES = " ";
private static final String NINE_SPACES = " ";
private final HexBoard<F, C, E, A> board;
private final Label emptyLabel = new Label(' ', ' ');
private final Label defaultDiagonalEdgeDownLabel = new Label('\\', '\\');
private final Label defaultDiagonalEdgeUpLabel = new Label('/', '/');
private final Label defaultVerticalEdgeLabel = new Label('|', '|');
private Map<Point, Label> fixedLowerFieldLabels;
/**
* Creates a view for the specified board.
*
* @param board the board
*/
public HexBoardTextView(HexBoard<F, C, E, A> board) {
this.fixedLowerFieldLabels = new HashMap<>();
this.board = board;
}
/**
* Sets the lower field label for the specified field.
*
* @param field the field
* @param label the label
* @throws IllegalArgumentException if arguments are null or if the field does
* not exist
*/
public void setLowerFieldLabel(Point field, Label label) {
if (field == null || label == null || !board.hasField(field)) {
throw new IllegalArgumentException("Argument(s) must not be null and field must exist.");
}
fixedLowerFieldLabels.put(field, label);
}
/**
* Returns a label to be used as label for the edge. This method is called to
* determine the label for this edge.
*
* @param e edge data object
* @return the label
*/
protected Label getEdgeLabel(E e) {
return deriveLabelFromToStringRepresentation(e);
}
/**
* Returns a label to be used as label for the corner. This method is called to
* determine the label for this corner.
*
* @param c corner data object
* @return the label
*/
protected Label getCornerLabel(C c) {
return deriveLabelFromToStringRepresentation(c);
}
/**
* Returns a label to be used as upper label for the field. This method is
* called to determine the upper label for this field.
*
* @param f field data object
* @return the label
*/
protected Label getFieldLabelUpper(F f) {
return deriveLabelFromToStringRepresentation(f);
}
/**
* Returns a label to be used as lower label for the field at this position.
* This method is called to determine the lower label for this field.
*
* @param p location of the field
* @return the label
*/
private Label getFieldLabelLower(Point p) {
Label l = this.fixedLowerFieldLabels.get(p);
l = l == null ? emptyLabel : l;
return l;
}
private Label deriveLabelFromToStringRepresentation(Object o) {
Label label = emptyLabel;
if (o.toString().length() > 0) {
String s = o.toString();
if (s.length() > 1) {
return new Label(s.charAt(0), s.charAt(1));
} else {
return new Label(s.charAt(0), ' ');
}
}
return label;
}
/**
* <p>
* This method returns a single-line string with all corners and field
* annotations for a given y-coordinate. It produces the string by iterating
* over corner positions and appending per corner:
* </p>
* <p>
* "(CL) NE NW " for y%3==1 "(CL) SE SW " for y%3==0
* </p>
* <p>
* Corners/labels that do not exist are replaced by spaces.
* </p>
*/
private String printCornerLine(int y) {
StringBuilder cornerLine = new StringBuilder("");
int offset = 0;
if (y % 2 != 0) {
cornerLine.append(NINE_SPACES);
offset = 1;
}
for (int x = offset; x <= board.getMaxCoordinateX(); x = x + 2) {
Point p = new Point(x, y);
Label cornerLabel;
// handle corner labels for corners other than north and south corners
Point center;
Label first = null;
Label second = null;
switch (y % 3) {
case 0:
center = new Point(x + 1, y - 1);
first = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x - 1, center.y + 1)));
second = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x + 1, center.y + 1)));
break;
case 1:
center = new Point(x + 1, y + 1);
first = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x - 1, center.y - 1)));
second = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x + 1, center.y - 1)));
break;
default:
throw new IllegalArgumentException("Not a corner line");
}
if (board.hasCorner(p)) {
cornerLabel = board.getCorner(p) != null ? getCornerLabel(board.getCorner(p)) : emptyLabel;
cornerLine.append("(").append(cornerLabel.getFirst()).append(cornerLabel.getSecond()).append(")");
} else {
cornerLine.append(FOUR_SPACES);
}
cornerLine.append(ONE_SPACE).append(first.getFirst()).append(first.getSecond());
cornerLine.append(FIVE_SPACES).append(second.getFirst()).append(second.getSecond()).append(TWO_SPACES);
}
return cornerLine.toString();
}
private Label getAnnotationLabel(A annotation) {
if (annotation == null) {
return emptyLabel;
} else {
return deriveLabelFromToStringRepresentation(annotation);
}
}
private String printMiddlePartOfField(int y) {
boolean isOffsetRow = (y - 2) % 6 == 0;
StringBuilder lower = new StringBuilder(isOffsetRow ? NINE_SPACES : "");
StringBuilder upper = new StringBuilder(isOffsetRow ? NINE_SPACES : "");
int xstart = isOffsetRow ? 2 : 1;
for (int x = xstart; x <= board.getMaxCoordinateX() + 1; x = x + 2) {
Point edgeStart = new Point(x - 1, y - 1);
Point edgeEnd = new Point(x - 1, y + 1);
Label l = this.emptyLabel;
if (board.hasEdge(edgeStart, edgeEnd)) {
E edge = board.getEdge(edgeStart, edgeEnd);
if (edge != null) {
l = this.getEdgeLabel(edge);
} else {
l = this.defaultVerticalEdgeLabel;
}
}
Point center = new Point(x, y);
boolean hasFieldWithData = board.hasField(center) && board.getField(center) != null;
Label lowerFieldLabel = hasFieldWithData ? getFieldLabelLower(center) : emptyLabel;
Label upperFieldLabel = hasFieldWithData ? getFieldLabelUpper(board.getField(center))
: emptyLabel;
lower.append(ONE_SPACE).append(l.getFirst()).append(l.getSecond()).append(SEVEN_SPACES);
lower.append(lowerFieldLabel.getFirst()).append(lowerFieldLabel.getSecond()).append(SIX_SPACES);
upper.append(ONE_SPACE).append(l.getFirst()).append(l.getSecond()).append(SEVEN_SPACES);
upper.append(upperFieldLabel.getFirst()).append(upperFieldLabel.getSecond()).append(SIX_SPACES);
}
return upper + System.lineSeparator() + lower;
}
private String printDiagonalEdges(int y) {
StringBuilder builder = new StringBuilder();
Point edgeStart;
Point edgeEnd;
Label annotation = null;
Label l;
boolean isDown = y % 6 == 0;
builder.append(" ");
for (int x = 0; x <= board.getMaxCoordinateX(); x = x + 1) {
if (isDown) {
edgeStart = new Point(x, y);
edgeEnd = new Point(x + 1, y + 1);
annotation = getAnnotationLabel(board.getFieldAnnotation(new Point(x + 1, y - 1), new Point(x + 1, y + 1)));
} else {
edgeStart = new Point(x, y + 1);
edgeEnd = new Point(x + 1, y);
annotation = getAnnotationLabel(board.getFieldAnnotation(new Point(x + 1, y + 2), new Point(x + 1, y)));
}
l = determineEdgeLabel(isDown, edgeStart, edgeEnd);
if (!isDown) {
builder.append(TWO_SPACES + l.getFirst() + l.getSecond() + TWO_SPACES + annotation.getFirst() + annotation.getSecond());
} else {
builder.append(TWO_SPACES + l.getFirst() + l.getSecond() + TWO_SPACES + annotation.getFirst() + annotation.getSecond());
}
isDown = !isDown;
}
return builder.toString();
}
private Label determineEdgeLabel(boolean isDown, Point edgeStart, Point edgeEnd) {
Label l;
if (board.hasEdge(edgeStart, edgeEnd)) {
// does it have data associated with it?
if (board.getEdge(edgeStart, edgeEnd) != null) {
l = this.getEdgeLabel(board.getEdge(edgeStart, edgeEnd));
} else {
// default visualization
l = isDown ? this.defaultDiagonalEdgeDownLabel : this.defaultDiagonalEdgeUpLabel;
}
} else {
l = this.emptyLabel;
}
return l;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int y = 0; y <= board.getMaxCoordinateY(); y = y + 3) {
sb.append(printCornerLine(y));
sb.append(System.lineSeparator());
sb.append(printDiagonalEdges(y));
sb.append(System.lineSeparator());
sb.append(printCornerLine(y + 1));
sb.append(System.lineSeparator());
sb.append(printMiddlePartOfField(y + 2));
sb.append(System.lineSeparator());
}
return sb.toString();
}
}
+50
View File
@@ -0,0 +1,50 @@
package ch.zhaw.hexboard;
/**
* This class defines a label composed of two characters.
*
* @author tebe
*
*/
public final class Label {
public static final char DEFAULT_CHARACTER = ' ';
private final char first;
private final char second;
/**
* Creates a label from two characters.
*
* @param firstChar first character
* @param secondChar second character
*/
public Label(char firstChar, char secondChar) {
first = firstChar;
second = secondChar;
}
/**
* Creates a label using the default character {@link #DEFAULT_CHARACTER}.
*/
public Label() {
first = ' ';
second = ' ';
}
public char getFirst() {
return first;
}
public char getSecond() {
return second;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "" + first + second;
}
}