diff --git a/doc/Coordinates-Cheat-Sheet.svg b/doc/Coordinates-Cheat-Sheet.svg new file mode 100644 index 0000000..379786b --- /dev/null +++ b/doc/Coordinates-Cheat-Sheet.svg @@ -0,0 +1,5333 @@ + + + + diff --git a/lib/slf4j-api-2.0.0-alpha1.jar b/lib/slf4j-api-2.0.0-alpha1.jar new file mode 100644 index 0000000..c38dbb5 Binary files /dev/null and b/lib/slf4j-api-2.0.0-alpha1.jar differ diff --git a/lib/text-io-3.4.0.jar b/lib/text-io-3.4.0.jar new file mode 100644 index 0000000..2d0806a Binary files /dev/null and b/lib/text-io-3.4.0.jar differ diff --git a/src/ch/zhaw/catan/Config.java b/src/ch/zhaw/catan/Config.java new file mode 100644 index 0000000..2be09db --- /dev/null +++ b/src/ch/zhaw/catan/Config.java @@ -0,0 +1,254 @@ +package ch.zhaw.catan; + +import java.awt.Point; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * This class specifies the most important and basic parameters of the game + * Catan. + *
+ * The class provides definitions such as for the type and number of resource + * cards or the number of available road elements per player. Furthermore, it + * provides a dice number to field and a field to land type mapping for the + * standard setup detailed here + *
+ * @author tebe + * + */ +public class Config { + // Minimum number of players + // Note: The max. number is equal to the number of factions (see Faction enum) + public static final int MIN_NUMBER_OF_PLAYERS = 2; + + // Initial thief position (on the desert field) + public static final Point INITIAL_THIEF_POSITION = new Point(7, 11); + + // Available factions + public enum Faction { + RED("rr"), BLUE("bb"), GREEN("gg"), YELLOW("yy"); + + private String name; + + private Faction(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + // RESOURCE CARD DECK + public static final Map+ * The enum provides information about the cost of a structure and how many of + * these structures are available per player. + *
+ */ + public enum Structure { + SETTLEMENT(List.of(Resource.LUMBER, Resource.BRICK, Resource.WOOL, Resource.GRAIN), + NUMBER_OF_SETTLEMENTS_PER_PLAYER), + CITY(List.of(Resource.ORE, Resource.ORE, Resource.ORE, Resource.GRAIN, Resource.GRAIN), + NUMBER_OF_CITIES_PER_PLAYER), + ROAD(List.of(Resource.LUMBER, Resource.BRICK), NUMBER_OF_ROADS_PER_PLAYER); + + private List+ * Each list entry represents a resource card. The value of an entry (e.g., {@link Resource#LUMBER}) + * identifies the resource type of the card. + *
+ * @return the build costs + */ + public ListThe order of the player's factions in the list must + * correspond to the oder in which they play. + * Hence, the player that sets the first settlement must be + * at position 0 in the list etc. + * + * Important note: The list must contain the + * factions of active players only.
+ * + * @return the list with player's factions + */ + public ListThe placement does not cost any resource cards. If payout is + * set to true, for each adjacent resource-producing field, a resource card of the + * type of the resource produced by the field is taken from the bank (if available) and added to + * the players' stock of resource cards.
+ * + * @param position the position of the settlement + * @param payout if true, the player gets one resource card per adjacent resource-producing field + * @return true, if the placement was successful + */ + public boolean placeInitialSettlement(Point position, boolean payout) { + // TODO: Implement + return false; + } + + /** + * Places a road in the founder's phase (phase II) of the game. + * The placement does not cost any resource cards. + * + * @param roadStart position of the start of the road + * @param roadEnd position of the end of the road + * @return true, if the placement was successful + */ + public boolean placeInitialRoad(Point roadStart, Point roadEnd) { + // TODO: Implement + return false; + } + + /** + * This method takes care of actions depending on the dice throw result. + * + * A key action is the payout of the resource cards to the players + * according to the payout rules of the game. This includes the + * "negative payout" in case a 7 is thrown and a player has more than + * {@link Config#MAX_CARDS_IN_HAND_NO_DROP} resource cards. + * + * If a player does not get resource cards, the list for this players' + * {@link Faction} is an empty list (not null)!. + * + *+ * The payout rules of the game take into account factors such as, the number + * of resource cards currently available in the bank, settlement types + * (settlement or city), and the number of players that should get resource + * cards of a certain type (relevant if there are not enough left in the bank). + *
+ * + * @param dicethrow the resource cards that have been distributed to the players + * @return the resource cards added to the stock of the different players + */ + public MapThe settlement can be built if: + *
The city can be built if: + *
The road can be built if: + *
+ * 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 + *
+ * @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 + "]"; + } +} \ No newline at end of file diff --git a/src/ch/zhaw/hexboard/FieldAnnotationPosition.java b/src/ch/zhaw/hexboard/FieldAnnotationPosition.java new file mode 100644 index 0000000..4f369f4 --- /dev/null +++ b/src/ch/zhaw/hexboard/FieldAnnotationPosition.java @@ -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 + "]"; + } +} diff --git a/src/ch/zhaw/hexboard/HexBoard.java b/src/ch/zhaw/hexboard/HexBoard.java new file mode 100644 index 0000000..79767f0 --- /dev/null +++ b/src/ch/zhaw/hexboard/HexBoard.java @@ -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; + +/*** + *+ * This class represents a simple generic hexagonal game board. + *
+ *The game board uses a fixed coordinate system which is structured as follows:
+ * + *+ * 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 + * + * ... + *+ * + *
+ * Fields F and corners C 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. + *
+ * + *+ * 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. + *
+ * + *+ * Fields, edges and corners can store an object of the type of the + * corresponding type parameter each. + *
+ * + *+ * 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. + *
+ * + *+ * SW (C) SE + * / N \ + * (C) NW NE (C) + * | F | + * | | + * (C) SW SE (C) + * \ S / + * NW (C) NE + *+ * + * @param
+ * 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. + *
+ * @param corner the location of the corner + * @return the list with the (non-null) field data + */ + public List+ * Each corner has three direct neighbors, except corners that are located at + * the border of the game board. + *
+ * @param center the location of the corner for which to return the direct + * neighbors + * @return list with non-null corner data elements + */ + public List+ * Each corner has three edges connecting to it, except edges that are located + * at the border of the game board. + *
+ * @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+ * It creates a textual representation of the {@link ch.zhaw.hexboard.HexBoard} + * that includes all defined fields, edges, corners and annotations. + *
+ * 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. + *+ * 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. + *
+ *+ * This class defines how edges, corners and fields look like (their "label"). + * This is done as follows:
+ *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)}.
+ *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.
+ * + *+ * 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) + *+ *
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. + *
+ *+ * 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:
+ * + *+ * (CL) + * // N \\ + * // \\ + * (CL) NW NE (CL) + * || UL || + * || LL || + * (CL) SW SE (CL) + * \\ S // + * \\ // + * (CL) + *+ * + * @param
+ * 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: + *
+ *+ * "(CL) NE NW " for y%3==1 "(CL) SE SW " for y%3==0 + *
+ *+ * Corners/labels that do not exist are replaced by spaces. + *
+ */ + 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(); + } + +} diff --git a/src/ch/zhaw/hexboard/Label.java b/src/ch/zhaw/hexboard/Label.java new file mode 100644 index 0000000..6e766ef --- /dev/null +++ b/src/ch/zhaw/hexboard/Label.java @@ -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; + } +} diff --git a/test/ch/zhaw/catan/SiedlerGameTest.java b/test/ch/zhaw/catan/SiedlerGameTest.java new file mode 100644 index 0000000..2b3d4d6 --- /dev/null +++ b/test/ch/zhaw/catan/SiedlerGameTest.java @@ -0,0 +1,21 @@ +package ch.zhaw.catan; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + + +/*** + * TODO Write your own tests in this class. + * + * Note: Have a look at {@link ch.zhaw.catan.games.ThreePlayerStandard}. It can be used + * to get several different game states. + * + */ +public class SiedlerGameTest { + + @Test + public void dummyTestMethod() { + assertTrue(false); + } + +} \ No newline at end of file diff --git a/test/ch/zhaw/catan/SiedlerGameTestBasic.java b/test/ch/zhaw/catan/SiedlerGameTestBasic.java new file mode 100644 index 0000000..4c6e6fe --- /dev/null +++ b/test/ch/zhaw/catan/SiedlerGameTestBasic.java @@ -0,0 +1,258 @@ +package ch.zhaw.catan; + +import ch.zhaw.catan.games.ThreePlayerStandard; + + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.awt.*; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * This class contains some basic tests for the {@link SiedlerGame} class + * + *DO NOT MODIFY THIS CLASS
+ * + * @author tebe + */ +public class SiedlerGameTestBasic { + private final static int DEFAULT_WINPOINTS = 5; + private final static int DEFAULT_NUMBER_OF_PLAYERS = 3; + + /** + * Tests whether the functionality for switching to the next/previous player + * works as expected for different numbers of players. + * + * @param numberOfPlayers the number of players + */ + @ParameterizedTest + @ValueSource(ints = {2, 3, 4}) + public void requirementPlayerSwitching(int numberOfPlayers) { + SiedlerGame model = new SiedlerGame(DEFAULT_WINPOINTS, numberOfPlayers); + assertTrue(numberOfPlayers == model.getPlayerFactions().size(), + "Wrong number of players returned by getPlayers()"); + //Switching forward + for (int i = 0; i < numberOfPlayers; i++) { + assertEquals(Config.Faction.values()[i], model.getCurrentPlayerFaction(), + "Player order does not match order of Faction.values()"); + model.switchToNextPlayer(); + } + assertEquals(Config.Faction.values()[0], model.getCurrentPlayerFaction(), + "Player wrap-around from last player to first player did not work."); + //Switching backward + for (int i = numberOfPlayers - 1; i >= 0; i--) { + model.switchToPreviousPlayer(); + assertEquals(Config.Faction.values()[i], model.getCurrentPlayerFaction(), + "Switching players in reverse order does not work as expected."); + } + } + + /** + * Tests whether the game board meets the required layout/land placement. + */ + @Test + public void requirementLandPlacementTest() { + SiedlerGame model = new SiedlerGame(DEFAULT_WINPOINTS, DEFAULT_NUMBER_OF_PLAYERS); + assertTrue(Config.getStandardLandPlacement().size() == model.getBoard().getFields().size(), + "Check if explicit init must be done (violates spec): " + + "modify initializeSiedlerGame accordingly."); + for (Map.Entry+ * ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) ( ) ( ) + * || ~~ || ~~ || ~~ || ~~ || + * || || || || || + * ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) (bb) ( ) ( ) + * || ~~ || LU || WL bb WL || ~~ || + * || || 06 || 03 bb 08 || || + * ( ) ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ rr \\ // \\ // \\ // \\ + * ( ) ( ) (rr) ( ) ( ) ( ) ( ) + * || ~~ || GR || OR || GR || LU || ~~ || + * || || 02 || 04 || 05 || 10 || || + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * || ~~ gg LU || BR || -- || OR || GR || ~~ || + * || gg 05 || 09 || 07 || 06 || 09 || || + * ( ) (gg) ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // \\ // \\ bb \\ // + * ( ) ( ) ( ) ( ) ( ) (bb) ( ) + * || ~~ || GR || OR || LU || WL || ~~ || + * || || 10 || 11 || 03 || 12 || || + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // rr // \\ // + * ( ) ( ) ( ) ( ) (rr) ( ) + * || ~~ || WL || BR || BR || ~~ || + * || || 08 || 04 || 11 || || + * ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ gg \\ // \\ // + * ( ) ( ) (gg) ( ) ( ) + * || ~~ || ~~ || ~~ || ~~ || + * || || || || || + * ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // + * ( ) ( ) ( ) ( ) + *+ * Resource cards after the setup phase: + *
The main ideas for this setup were the following:
+ *The game board should look as follows: + *
+ * ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) ( ) ( ) + * || ~~ || ~~ || ~~ || ~~ || + * || || || || || + * ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) (bb) ( ) ( ) + * || ~~ || LU || WL bb WL || ~~ || + * || || 06 || 03 bb 08 || || + * ( ) ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ rr \\ // \\ // \\ // \\ + * ( ) ( ) (rr) ( ) ( ) ( ) ( ) + * || ~~ gg GR rr OR || GR || LU || ~~ || + * || gg 02 rr 04 || 05 || 10 || || + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * // \\ gg rr rr \\ // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * || ~~ gg LU || BR || -- || OR || GR || ~~ || + * || gg 05 || 09 || 07 || 06 || 09 || || + * ( ) (gg) ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // gg // \\ // \\ // \\ rr \\ bb \\ // + * ( ) ( ) ( ) ( ) ( ) (bb) ( ) + * || ~~ || GR || OR || LU rr WL || ~~ || + * || || 10 || 11 || 03 rr 12 || || + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // rr rr \\ // + * ( ) ( ) ( ) ( ) (rr) ( ) + * || ~~ || WL gg BR gg BR rr ~~ || + * || || 08 gg 04 gg 11 rr || + * ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // gg gg \\ // \\ // + * ( ) ( ) (gg) ( ) ( ) + * || ~~ || ~~ || ~~ || ~~ || + * || || || || || + * ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // + * ( ) ( ) ( ) ( ) + *+ *
+ * And the player resource card stocks:
+ *
+ * Player 1:
+ *
The game board should look as follows: + *
+ * ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) ( ) ( ) + * || ~~ || ~~ || ~~ || ~~ || + * || || || || || + * ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ // \\ + * ( ) ( ) (rr) (bb) ( ) ( ) + * || ~~ || LU rr WL bb WL || ~~ || + * || || 06 rr 03 bb 08 || || + * ( ) ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ rr rr // \\ // \\ // \\ + * ( ) ( ) (rr) (rr) ( ) ( ) ( ) + * || ~~ || GR || OR || GR || LU || ~~ || + * || || 02 || 04 || 05 || 10 || || + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * // \\ // \\ // \\ // \\ // \\ // \\ // \\ + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * || ~~ gg LU || BR || -- || OR || GR || ~~ || + * || gg 05 || 09 || 07 || 06 || 09 || || + * ( ) (gg) ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // \\ // \\ bb \\ // + * ( ) ( ) ( ) ( ) ( ) (bb) ( ) + * || ~~ || GR || OR || LU rr WL || ~~ || + * || || 10 || 11 || 03 rr 12 || || + * ( ) ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // rr // \\ // + * ( ) ( ) ( ) ( ) (rr) ( ) + * || ~~ || WL || BR || BR || ~~ || + * || || 08 || 04 || 11 || || + * ( ) ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ gg \\ // \\ // + * ( ) ( ) (gg) ( ) ( ) + * || ~~ || ~~ || ~~ || ~~ || + * || || || || || + * ( ) ( ) ( ) ( ) ( ) + * \\ // \\ // \\ // \\ // + * ( ) ( ) ( ) ( ) + * + *+ *
And the player resource card stocks:
+ *+ * This class performs tests for the class {@link Edge}. + *
+ * + * @author tebe + * + **/ +public class EdgeTest { + + private Point[] hexagon22 = { new Point(2, 0), new Point(3, 1), new Point(3, 3), new Point(2, 4), + new Point(1, 3), new Point(1, 1) }; + private Point[] hexagon75 = { new Point(7, 3), new Point(8, 4), new Point(8, 6), new Point(7, 7), + new Point(6, 6), new Point(6, 4) }; + + @Test + public void createValidEdge() { + new Edge(new Point(0, 0), new Point(1, 1)); + } + + @Test + public void edgeEqualityStartEndPointReversed() { + for (int i = 0; i < hexagon22.length - 1; i++) { + assertEquals(new Edge(hexagon22[i], hexagon22[i + 1]), + new Edge(hexagon22[i + 1], hexagon22[i])); + } + for (int i = 0; i < hexagon75.length - 1; i++) { + assertEquals(new Edge(hexagon75[i], hexagon75[i + 1]), + new Edge(hexagon75[i + 1], hexagon75[i])); + } + } + + @Test + public void notEquals() { + assertNotEquals(new Edge(hexagon22[0], hexagon22[1]), + new Edge(hexagon22[1], hexagon22[2])); + } + + @Test + public void createWithBothArgumentsNull() { + assertThrows(IllegalArgumentException.class, () -> new Edge(null, null)); + } + + @Test + public void createWithFirstArgumentNull() { + assertThrows(IllegalArgumentException.class, () -> new Edge(null, new Point(1, 0))); + } + + @Test + public void createWithSecondArgumentNull() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(1, 0), null)); + } + + @Test + public void createWithStartAndEndpointIdentical() { + assertThrows(IllegalArgumentException.class, () -> new Edge(hexagon22[0], hexagon22[0])); + } + + @Test + public void notAnEdgeHorizontalOddTop() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(5, 7), new Point(7, 7))); + } + + @Test + public void notAnEdgeHorizontalOddMiddle() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(3, 2), new Point(5, 2))); + } + + @Test + public void notAnEdgeHorizontalOddBottom() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(5, 3), new Point(7, 3))); + } + + @Test + public void notAnEdgeHorizontalEvenTop() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(4, 4), new Point(6, 4))); + } + + @Test + public void notAnEdgeHorizontalEvenMiddle() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(2, 5), new Point(4, 5))); + } + + @Test + public void notAnEdgeHorizontalEvenBottom() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(4, 6), new Point(6, 6))); + } + + @Test + public void notAnEdgeVerticalEven() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(7, 7), new Point(7, 3))); + } + + @Test + public void notAnEdgeVerticalOdd() { + assertThrows(IllegalArgumentException.class, () -> new Edge(new Point(6, 4), new Point(6, 0))); + } + +} diff --git a/test/ch/zhaw/hexboard/HexBoardTest.java b/test/ch/zhaw/hexboard/HexBoardTest.java new file mode 100644 index 0000000..2e3e078 --- /dev/null +++ b/test/ch/zhaw/hexboard/HexBoardTest.java @@ -0,0 +1,121 @@ +package ch.zhaw.hexboard; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.awt.Point; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/*** + *+ * Tests for the class {@link HexBoard}. + *
+ * @author tebe + */ +public class HexBoardTest { + private HexBoard+ * 0 1 2 3 4 5 6 7 8 + * | | | | | | | | | ... + * + * 0---- + * + * 1---- + * + * 2---- + * + * 3---- C + * / \ + * 4---- C C + * + * 5---- | F | ... + * + * 6---- C C + * \ / + * 7---- C + *+ */ + @BeforeEach + public void setUp() { + board = new HexBoard<>(); + board.addField(new Point(7, 5), "00"); + Point[] singleField = { new Point(7, 3), new Point(8, 4), new Point(8, 6), new Point(7, 7), + new Point(6, 6), new Point(6, 4) }; + this.corner = singleField; + } + + // Edge retrieval + @Test + public void edgeTest() { + for (int i = 0; i < corner.length - 1; i++) { + assertNull(board.getEdge(corner[i], corner[i + 1])); + board.setEdge(corner[i], corner[i + 1], Integer.toString(i)); + assertEquals(board.getEdge(corner[i], corner[i + 1]), Integer.toString(i)); + } + } + + @Test + public void noEdgeCoordinatesTest() { + assertThrows(IllegalArgumentException.class, + () -> board.getEdge(new Point(2, 2), new Point(0, 2))); + } + + @Test + public void edgeDoesNotExistTest() { + assertThrows(IllegalArgumentException.class, + () -> board.getEdge(new Point(0, 2), new Point(3, 1))); + } + + // Corner retrieval + @Test + public void cornerTest() { + for (Point p : corner) { + assertNull(board.getCorner(p)); + board.setCorner(p, p.toString()); + assertEquals(board.getCorner(p), p.toString()); + } + } + + @Test + public void noCornerCoordinateTest() { + assertThrows(IllegalArgumentException.class, () -> board.getCorner(new Point(2, 2))); + } + + @Test + public void cornerDoesNotExistTest() { + assertThrows(IllegalArgumentException.class, () -> board.getCorner(new Point(2, 2))); + } + + // Field addition/retrieval + @Test + public void fieldAreadyExistsErrorTest() { + board.addField(new Point(2, 2), "22"); + assertThrows(IllegalArgumentException.class, () -> board.addField(new Point(2, 2), "22")); + } + + @Test + public void fieldRetrievalTest() { + Point field = new Point(2, 2); + board.addField(field, new String("22")); + assertTrue(board.hasField(field)); + assertEquals(board.getField(field), "22"); + } + + @Test + public void fieldRetrievalWrongCoordinatesOutsideTest() { + assertThrows(IllegalArgumentException.class, () -> board.getField(new Point(10, 10))); + } + + @Test + public void fieldRetrievalWrongCoordinatesInsideTest() { + assertThrows(IllegalArgumentException.class, () -> board.getField(new Point(2, 2))); + } +}