From f2fe06f720927c400ff3b90176342a784d44840b Mon Sep 17 00:00:00 2001 From: fassband Date: Fri, 19 Nov 2021 08:32:32 +0100 Subject: [PATCH] Add files via upload --- doc/Coordinates-Cheat-Sheet.svg | 5333 +++++++++++++++++ lib/slf4j-api-2.0.0-alpha1.jar | Bin 0 -> 56383 bytes lib/text-io-3.4.0.jar | Bin 0 -> 98005 bytes src/ch/zhaw/catan/Config.java | 254 + src/ch/zhaw/catan/Dummy.java | 59 + src/ch/zhaw/catan/SiedlerBoard.java | 37 + src/ch/zhaw/catan/SiedlerBoardTextView.java | 12 + src/ch/zhaw/catan/SiedlerGame.java | 255 + src/ch/zhaw/hexboard/Edge.java | 117 + .../hexboard/FieldAnnotationPosition.java | 114 + src/ch/zhaw/hexboard/HexBoard.java | 541 ++ src/ch/zhaw/hexboard/HexBoardTextView.java | 366 ++ src/ch/zhaw/hexboard/Label.java | 50 + test/ch/zhaw/catan/SiedlerGameTest.java | 21 + test/ch/zhaw/catan/SiedlerGameTestBasic.java | 258 + test/ch/zhaw/catan/Tuple.java | 42 + .../zhaw/catan/games/ThreePlayerStandard.java | 480 ++ test/ch/zhaw/hexboard/EdgeTest.java | 106 + test/ch/zhaw/hexboard/HexBoardTest.java | 121 + 19 files changed, 8166 insertions(+) create mode 100644 doc/Coordinates-Cheat-Sheet.svg create mode 100644 lib/slf4j-api-2.0.0-alpha1.jar create mode 100644 lib/text-io-3.4.0.jar create mode 100644 src/ch/zhaw/catan/Config.java create mode 100644 src/ch/zhaw/catan/Dummy.java create mode 100644 src/ch/zhaw/catan/SiedlerBoard.java create mode 100644 src/ch/zhaw/catan/SiedlerBoardTextView.java create mode 100644 src/ch/zhaw/catan/SiedlerGame.java create mode 100644 src/ch/zhaw/hexboard/Edge.java create mode 100644 src/ch/zhaw/hexboard/FieldAnnotationPosition.java create mode 100644 src/ch/zhaw/hexboard/HexBoard.java create mode 100644 src/ch/zhaw/hexboard/HexBoardTextView.java create mode 100644 src/ch/zhaw/hexboard/Label.java create mode 100644 test/ch/zhaw/catan/SiedlerGameTest.java create mode 100644 test/ch/zhaw/catan/SiedlerGameTestBasic.java create mode 100644 test/ch/zhaw/catan/Tuple.java create mode 100644 test/ch/zhaw/catan/games/ThreePlayerStandard.java create mode 100644 test/ch/zhaw/hexboard/EdgeTest.java create mode 100644 test/ch/zhaw/hexboard/HexBoardTest.java 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 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + 1 + 2 + 3 + + + + 4 + 5 + 6 + + + + 7 + 8 + 9 + + + + 10 + 11 + 12 + + + + 13 + 14 + 0 + + + + + + + + + + + + + + + + + + 0 + 1 + 3 + 4 + 6 + 7 + 9 + 10 + 12 + 13 + 15 + 16 + 18 + 19 + 21 + 22 + + + + + + + + 2 + 5 + 8 + 11 + 14 + 17 + 20 + + 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 0000000000000000000000000000000000000000..c38dbb589627d6023506b39e4481ebd7df81a009 GIT binary patch literal 56383 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 literal 0 HcmV?d00001 diff --git a/lib/text-io-3.4.0.jar b/lib/text-io-3.4.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..2d0806a5e99279560b49514cbca4b3b858819ad5 GIT binary patch literal 98005 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 BH2?qr literal 0 HcmV?d00001 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 INITIAL_RESOURCE_CARDS_BANK = Map.of(Resource.LUMBER, 19, + Resource.BRICK, 19, Resource.WOOL, 19, Resource.GRAIN, 19, Resource.ORE, 19); + + // SPECIFICATION OF AVAILABLE RESOURCE TYPES + /** + * This {@link Enum} specifies the available resource types in the game. + * + * @author tebe + */ + public enum Resource { + GRAIN("GR"), WOOL("WL"), LUMBER("LU"), ORE("OR"), BRICK("BR"); + + private String name; + + private Resource(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + // SPECIFICATION OF AVAILABLE LAND TYPES + /** + * This {@link Enum} specifies the available lands in the game. Some land types + * produce resources (e.g., {@link Land#FOREST}, others do not (e.g., + * {@link Land#WATER}. + * + * @author tebe + */ + public enum Land { + FOREST(Resource.LUMBER), PASTURE(Resource.WOOL), FIELDS(Resource.GRAIN), + MOUNTAIN(Resource.ORE), HILLS(Resource.BRICK), WATER("~~"), DESERT("--"); + + private Resource resource = null; + private String name; + + private Land(Resource resource) { + this(resource.toString()); + this.resource = resource; + } + + private Land(String name) { + this.name = name; + } + + @Override + public String toString() { + return this.name; + } + + /** + * Returns the {@link Resource} that this land provides or null, + * if it does not provide any. + * + * @return the {@link Resource} or null + */ + public Resource getResource() { + return resource; + } + } + + // STRUCTURES (with costs) + private static final int NUMBER_OF_ROADS_PER_PLAYER = 15; + private static final int NUMBER_OF_SETTLEMENTS_PER_PLAYER = 5; + private static final int NUMBER_OF_CITIES_PER_PLAYER = 4; + public static final int MAX_CARDS_IN_HAND_NO_DROP = 7; + + /** + * This enum models the different structures that can be built. + *

+ * 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 costs; + private int stockPerPlayer; + + private Structure(List costs, int stockPerPlayer) { + this.costs = costs; + this.stockPerPlayer = stockPerPlayer; + } + + /** + * Returns the build costs of this structure. + *

+ * 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 List getCosts() { + return costs; + } + + /** + * Returns the build costs of this structure. + * + * @return the build costs in terms of the number of resource cards per resource type + */ + public Map getCostsAsMap() { + return costs.stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + } + + /** + * Returns the number of pieces that are available of a certain structure (per + * player). For example, there are {@link Config#NUMBER_OF_ROADS_PER_PLAYER} + * pieces of the structure {@link Structure#ROAD} per player. + * + * + * @return the stock per player + */ + public int getStockPerPlayer() { + return stockPerPlayer; + } + } + + // STANDARD FIXED DICE NUMBER TO FIELD SETUP + /** + * Returns a mapping of the dice values per field. + * + * @return the dice values per field + */ + public static final Map getStandardDiceNumberPlacement() { + Map assignment = new HashMap<>(); + assignment.put(new Point(4, 8), 2); + assignment.put(new Point(7, 5), 3); + assignment.put(new Point(8, 14), 3); + assignment.put(new Point(6, 8), 4); + assignment.put(new Point(7, 17), 4); + + assignment.put(new Point(3, 11), 5); + assignment.put(new Point(8, 8), 5); + assignment.put(new Point(5, 5), 6); + assignment.put(new Point(9, 11), 6); + + assignment.put(new Point(7, 11), 7); + assignment.put(new Point(9, 5), 8); + assignment.put(new Point(5, 17), 8); + assignment.put(new Point(5, 11), 9); + assignment.put(new Point(11, 11), 9); + assignment.put(new Point(4, 14), 10); + assignment.put(new Point(10, 8), 10); + assignment.put(new Point(6, 14), 11); + assignment.put(new Point(9, 17), 11); + assignment.put(new Point(10, 14), 12); + return Collections.unmodifiableMap(assignment); + } + + // STANDARD FIXED LAND SETUP + /** + * Returns the field (coordinate) to {@link Land} mapping for the standard + * setup of the game Catan.. + * + * @return the field to {@link Land} mapping for the standard setup + */ + public static final Map getStandardLandPlacement() { + Map assignment = new HashMap<>(); + Point[] water = { new Point(4, 2), new Point(6, 2), new Point(8, 2), new Point(10, 2), + new Point(3, 5), new Point(11, 5), new Point(2, 8), new Point(12, 8), new Point(1, 11), + new Point(13, 11), new Point(2, 14), new Point(12, 14), new Point(3, 17), new Point(11, 17), + new Point(4, 20), new Point(6, 20), new Point(8, 20), new Point(10, 20) }; + + for (Point p : water) { + assignment.put(p, Land.WATER); + } + + assignment.put(new Point(5, 5), Land.FOREST); + assignment.put(new Point(7, 5), Land.PASTURE); + assignment.put(new Point(9, 5), Land.PASTURE); + + assignment.put(new Point(4, 8), Land.FIELDS); + assignment.put(new Point(6, 8), Land.MOUNTAIN); + assignment.put(new Point(8, 8), Land.FIELDS); + assignment.put(new Point(10, 8), Land.FOREST); + + assignment.put(new Point(3, 11), Land.FOREST); + assignment.put(new Point(5, 11), Land.HILLS); + assignment.put(new Point(7, 11), Land.DESERT); + assignment.put(new Point(9, 11), Land.MOUNTAIN); + assignment.put(new Point(11, 11), Land.FIELDS); + + assignment.put(new Point(4, 14), Land.FIELDS); + assignment.put(new Point(6, 14), Land.MOUNTAIN); + assignment.put(new Point(8, 14), Land.FOREST); + assignment.put(new Point(10, 14), Land.PASTURE); + + assignment.put(new Point(5, 17), Land.PASTURE); + assignment.put(new Point(7, 17), Land.HILLS); + assignment.put(new Point(9, 17), Land.HILLS); + + return Collections.unmodifiableMap(assignment); + } + +} diff --git a/src/ch/zhaw/catan/Dummy.java b/src/ch/zhaw/catan/Dummy.java new file mode 100644 index 0000000..6f7f3f8 --- /dev/null +++ b/src/ch/zhaw/catan/Dummy.java @@ -0,0 +1,59 @@ +package ch.zhaw.catan; + +import ch.zhaw.catan.Config.Land; +import ch.zhaw.hexboard.Label; +import java.awt.Point; +import java.util.HashMap; +import java.util.Map; +import org.beryx.textio.TextIO; +import org.beryx.textio.TextIoFactory; +import org.beryx.textio.TextTerminal; + +public class Dummy { + + public enum Actions { + SHOW, QUIT + } + + private void run() { + TextIO textIO = TextIoFactory.getTextIO(); + TextTerminal textTerminal = textIO.getTextTerminal(); + + SiedlerBoard board = new SiedlerBoard(); + board.addField(new Point(2, 2), Land.FOREST); + board.setCorner(new Point(3, 3), "RR"); + board.setEdge(new Point(2, 0), new Point(3, 1), "r"); + board.addFieldAnnotation(new Point(2, 2), new Point(3, 1), "AA"); + + Map lowerFieldLabel = new HashMap<>(); + lowerFieldLabel.put(new Point(2, 2), new Label('0', '9')); + SiedlerBoardTextView view = new SiedlerBoardTextView(board); + + for (Map.Entry e : lowerFieldLabel.entrySet()) { + view.setLowerFieldLabel(e.getKey(), e.getValue()); + } + + boolean running = true; + while (running) { + switch (getEnumValue(textIO, Actions.class)) { + case SHOW: + textTerminal.println(view.toString()); + break; + case QUIT: + running = false; + break; + default: + throw new IllegalStateException("Internal error found - Command not implemented."); + } + } + textIO.dispose(); + } + + public static > T getEnumValue(TextIO textIO, Class commands) { + return textIO.newEnumInputReader(commands).read("What would you like to do?"); + } + + public static void main(String[] args) { + new Dummy().run(); + } +} diff --git a/src/ch/zhaw/catan/SiedlerBoard.java b/src/ch/zhaw/catan/SiedlerBoard.java new file mode 100644 index 0000000..9143967 --- /dev/null +++ b/src/ch/zhaw/catan/SiedlerBoard.java @@ -0,0 +1,37 @@ +package ch.zhaw.catan; + +import ch.zhaw.catan.Config.Land; +import ch.zhaw.hexboard.HexBoard; + +import java.awt.*; +import java.util.Collections; +import java.util.List; + +public class SiedlerBoard extends HexBoard { + + + //TODO: Add fields, constructors and methods as you see fit. Do NOT change the signature + // of the methods below. + + /** + * Returns the fields associated with the specified dice value. + * + * @param dice the dice value + * @return the fields associated with the dice value + */ + public List getFieldsForDiceValue(int dice) { + //TODO: Implement. + return Collections.emptyList(); + } + + /** + * Returns the {@link Land}s adjacent to the specified corner. + * + * @param corner the corner + * @return the list with the adjacent {@link Land}s + */ + public List getLandsForCorner(Point corner) { + //TODO: Implement. + return Collections.emptyList(); + } +} diff --git a/src/ch/zhaw/catan/SiedlerBoardTextView.java b/src/ch/zhaw/catan/SiedlerBoardTextView.java new file mode 100644 index 0000000..d8e3206 --- /dev/null +++ b/src/ch/zhaw/catan/SiedlerBoardTextView.java @@ -0,0 +1,12 @@ +package ch.zhaw.catan; + +import ch.zhaw.catan.Config.Land; +import ch.zhaw.hexboard.HexBoardTextView; + +public class SiedlerBoardTextView extends HexBoardTextView { + + public SiedlerBoardTextView(SiedlerBoard board) { + super(board); + } + +} diff --git a/src/ch/zhaw/catan/SiedlerGame.java b/src/ch/zhaw/catan/SiedlerGame.java new file mode 100644 index 0000000..a482921 --- /dev/null +++ b/src/ch/zhaw/catan/SiedlerGame.java @@ -0,0 +1,255 @@ +package ch.zhaw.catan; + +import ch.zhaw.catan.Config.Faction; +import ch.zhaw.catan.Config.Resource; +import java.awt.Point; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +/** + * This class performs all actions related to modifying the game state. + * + * TODO: (your documentation) + * + * @author TODO + * + */ +public class SiedlerGame { + static final int FOUR_TO_ONE_TRADE_OFFER = 4; + static final int FOUR_TO_ONE_TRADE_WANT = 1; + + /** + * Constructs a SiedlerGame game state object. + * + * @param winPoints the number of points required to win the game + * @param numberOfPlayers the number of players + * + * @throws IllegalArgumentException if winPoints is lower than + * three or players is not between two and four + */ + public SiedlerGame(int winPoints, int numberOfPlayers) { + // TODO: Implement + } + + /** + * Switches to the next player in the defined sequence of players. + */ + public void switchToNextPlayer() { + // TODO: Implement + } + + /** + * Switches to the previous player in the defined sequence of players. + */ + public void switchToPreviousPlayer() { + // TODO: Implement + } + + /** + * Returns the {@link Faction}s of the active players. + * + *

The 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 List getPlayerFactions() { + // TODO: Implement + return Collections.emptyList(); + } + + + /** + * Returns the game board. + * + * @return the game board + */ + public SiedlerBoard getBoard() { + // TODO: Implement + return null; + } + + /** + * Returns the {@link Faction} of the current player. + * + * @return the faction of the current player + */ + public Faction getCurrentPlayerFaction() { + // TODO: Implement + return null; + } + + /** + * Returns how many resource cards of the specified type + * the current player owns. + * + * @param resource the resource type + * @return the number of resource cards of this type + */ + public int getCurrentPlayerResourceStock(Resource resource) { + // TODO: Implement + return 0; + } + + /** + * Places a settlement in the founder's phase (phase II) of the game. + * + *

The 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 Map> throwDice(int dicethrow) { + // TODO: Implement + return null; + } + + /** + * Builds a settlement at the specified position on the board. + * + *

The settlement can be built if: + *

    + *
  • the player possesses the required resource cards
  • + *
  • a settlement to place on the board
  • + *
  • the specified position meets the build rules for settlements
  • + *
+ * + * @param position the position of the settlement + * @return true, if the placement was successful + */ + public boolean buildSettlement(Point position) { + // TODO: Implement + return false; + } + + /** + * Builds a city at the specified position on the board. + * + *

The city can be built if: + *

    + *
  • the player possesses the required resource cards
  • + *
  • a city to place on the board
  • + *
  • the specified position meets the build rules for cities
  • + *
+ * + * @param position the position of the city + * @return true, if the placement was successful + */ + public boolean buildCity(Point position) { + // TODO: OPTIONAL task - Implement + return false; + } + + /** + * Builds a road at the specified position on the board. + * + *

The road can be built if: + *

    + *
  • the player possesses the required resource cards
  • + *
  • a road to place on the board
  • + *
  • the specified position meets the build rules for roads
  • + *
+ * + * @param roadStart the position of the start of the road + * @param roadEnd the position of the end of the road + * @return true, if the placement was successful + */ + public boolean buildRoad(Point roadStart, Point roadEnd) { + // TODO: Implement + return false; + } + + + /** + * Trades in {@link #FOUR_TO_ONE_TRADE_OFFER} resource cards of the + * offered type for {@link #FOUR_TO_ONE_TRADE_WANT} resource cards of the wanted type. + * + * The trade only works when bank and player possess the resource cards + * for the trade before the trade is executed. + * + * @param offer offered type + * @param want wanted type + * @return true, if the trade was successful + */ + public boolean tradeWithBankFourToOne(Resource offer, Resource want) { + // TODO: Implement + return false; + } + + /** + * Returns the winner of the game, if any. + * + * @return the winner of the game or null, if there is no winner (yet) + */ + public Faction getWinner() { + // TODO: Implement + return null; + } + + + /** + * Places the thief on the specified field and steals a random resource card (if + * the player has such cards) from a random player with a settlement at that + * field (if there is a settlement) and adds it to the resource cards of the + * current player. + * + * @param field the field on which to place the thief + * @return false, if the specified field is not a field or the thief cannot be + * placed there (e.g., on water) + */ + public boolean placeThiefAndStealCard(Point field) { + //TODO: Implement (or longest road functionality) + return false; + } + +} diff --git a/src/ch/zhaw/hexboard/Edge.java b/src/ch/zhaw/hexboard/Edge.java new file mode 100644 index 0000000..b83c810 --- /dev/null +++ b/src/ch/zhaw/hexboard/Edge.java @@ -0,0 +1,117 @@ +package ch.zhaw.hexboard; + +import java.awt.Point; + +/** + * This class models an edge on @see ch.zhaw.hexboard.HexBoard. + *

+ * 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 Data type for the field data objects + * @param Data type for the corner data objects + * @param Data type for the edge data objects + * @param Data type for the annotation data objects + * + * @author tebe + * + */ +public class HexBoard { + private int maxCoordinateX = 0; + private int maxCoordinateY = 0; + private final Map field; + private final Map corner; + private final Map edge; + private final Map 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 getFieldAnnotationsForCorner(Point corner) { + List list = new LinkedList<>(); + for (Entry 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 getFieldAnnotationsForField(Point center) { + List list = new LinkedList<>(); + for (Entry 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 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 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 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 getCorners() { + List result = new LinkedList<>(); + for (C c : this.corner.values()) { + if (c != null) { + result.add(c); + } + } + return Collections.unmodifiableList(result); + } + + protected Set getCornerCoordinates() { + return Collections.unmodifiableSet(this.corner.keySet()); + } + + private static List 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 getFields() { + List result = new LinkedList<>(); + for (Entry 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. + *

+ * 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 getFields(Point corner) { + List 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. + *

+ * 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 getNeighboursOfCorner(Point center) { + List 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. + *

+ * 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 getAdjacentEdges(Point corner) { + List result = new LinkedList<>(); + for (Entry 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 getCornersOfField(Point center) { + List 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); + } + +} diff --git a/src/ch/zhaw/hexboard/HexBoardTextView.java b/src/ch/zhaw/hexboard/HexBoardTextView.java new file mode 100644 index 0000000..97c51af --- /dev/null +++ b/src/ch/zhaw/hexboard/HexBoardTextView.java @@ -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}. + *

+ * 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:

+ *
    + *
  • 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).
  • + *
  • If there is a data object associated with an edge, corner or field, the + * {@link ch.zhaw.hexboard.Label} is determined by calling: + *
      + *
    • EL = {@link #getEdgeLabel(Object)}
    • + *
    • CL = {@link #getCornerLabel(Object)}
    • + *
    • UL = {@link #getFieldLabelUpper(Object)}
    • + *
    + *
  • + *
+ *

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 See {@link ch.zhaw.hexboard.HexBoard} + * @param See {@link ch.zhaw.hexboard.HexBoard} + * @param See {@link ch.zhaw.hexboard.HexBoard} + * @param
See {@link ch.zhaw.hexboard.HexBoard} + * + * + * @author tebe + */ +public class HexBoardTextView { + + 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 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 fixedLowerFieldLabels; + + /** + * Creates a view for the specified board. + * + * @param board the board + */ + public HexBoardTextView(HexBoard 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; + } + + /** + *

+ * 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 e : Config.getStandardLandPlacement().entrySet()) { + assertEquals(e.getValue(), model.getBoard().getField(e.getKey()), + "Land placement does not match default placement."); + } + } + + /** + * Tests whether the {@link ThreePlayerStandard#getAfterSetupPhase(int)}} game board is not empty (returns + * an object) at positions where settlements and roads have been placed. + */ + @Test + public void requirementSettlementAndRoadPositionsOccupiedThreePlayerStandard() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhase(DEFAULT_WINPOINTS); + assertEquals(DEFAULT_NUMBER_OF_PLAYERS, model.getPlayerFactions().size()); + for (Config.Faction f : model.getPlayerFactions()) { + assertTrue(model.getBoard().getCorner(ThreePlayerStandard.INITIAL_SETTLEMENT_POSITIONS.get(f).first) != null); + assertTrue(model.getBoard().getCorner(ThreePlayerStandard.INITIAL_SETTLEMENT_POSITIONS.get(f).second) != null); + assertTrue(model.getBoard().getEdge(ThreePlayerStandard.INITIAL_SETTLEMENT_POSITIONS.get(f).first, ThreePlayerStandard.INITIAL_ROAD_ENDPOINTS.get(f).first) != null); + assertTrue(model.getBoard().getEdge(ThreePlayerStandard.INITIAL_SETTLEMENT_POSITIONS.get(f).second, ThreePlayerStandard.INITIAL_ROAD_ENDPOINTS.get(f).second) != null); + } + } + + /** + * Checks that the resource card payout for different dice values matches + * the expected payout for the game state {@link ThreePlayerStandard#getAfterSetupPhase(int)}}. + * + * Note, that for the test to work, the {@link Map} returned by {@link SiedlerGame#throwDice(int)} + * must contain a {@link List} with resource cards (empty {@link List}, if the player gets none) + * for each of the players. + * + * @param diceValue the dice value + */ + @ParameterizedTest + @ValueSource(ints = {2, 3, 4, 5, 6, 8, 9, 10, 11, 12}) + public void requirementDiceThrowResourcePayoutThreePlayerStandardTest(int diceValue) { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhase(DEFAULT_WINPOINTS); + Map> expectd = ThreePlayerStandard.INITIAL_DICE_THROW_PAYOUT.get(diceValue); + Map> actual = model.throwDice(diceValue); + assertEquals(ThreePlayerStandard.INITIAL_DICE_THROW_PAYOUT.get(diceValue), model.throwDice(diceValue)); + } + + /** + * Tests whether the resource card stock of the players matches the expected stock + * for the game state {@link ThreePlayerStandard#getAfterSetupPhase(int)}}. + */ + @Test + public void requirementPlayerResourceCardStockAfterSetupPhase() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhase(DEFAULT_WINPOINTS); + assertPlayerResourceCardStockEquals(model, ThreePlayerStandard.INITIAL_PLAYER_CARD_STOCK); + } + + /** + * Tests whether the resource card stock of the players matches the expected stock + * for the game state {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}}. + */ + @Test + public void requirementPlayerResourceCardStockAfterSetupPhaseAlmostEmptyBank() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhaseAlmostEmptyBank(DEFAULT_WINPOINTS); + assertPlayerResourceCardStockEquals(model, ThreePlayerStandard.BANK_ALMOST_EMPTY_RESOURCE_CARD_STOCK); + } + + /** + * Tests whether the resource card stock of the players matches the expected stock + * for the game state {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}}. + */ + @Test + public void requirementPlayerResourceCardStockPlayerOneReadyToBuildFifthSettlement() { + SiedlerGame model = ThreePlayerStandard.getPlayerOneReadyToBuildFifthSettlement(DEFAULT_WINPOINTS); + assertPlayerResourceCardStockEquals(model, ThreePlayerStandard.PLAYER_ONE_READY_TO_BUILD_FIFTH_SETTLEMENT_RESOURCE_CARD_STOCK); + } + + /** + * Throws each dice value except 7 once and tests whether the resource + * card stock of the players matches the expected stock. + */ + @Test + public void requirementDiceThrowPlayerResourceCardStockUpdateTest() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhase(DEFAULT_WINPOINTS); + for(int i : List.of(2, 3, 4, 5, 6, 8, 9, 10, 11, 12)) { + model.throwDice(i); + } + Map> expected = Map.of( + Config.Faction.values()[0], Map.of(Config.Resource.GRAIN, 1, Config.Resource.WOOL, 2, + Config.Resource.BRICK, 2, Config.Resource.ORE, 1, Config.Resource.LUMBER, 1), + Config.Faction.values()[1], + Map.of(Config.Resource.GRAIN, 1, Config.Resource.WOOL, 5, Config.Resource.BRICK, 0, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 0), + Config.Faction.values()[2], + Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 0, Config.Resource.BRICK, 2, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 1)); + + assertPlayerResourceCardStockEquals(model, expected); + } + + private void assertPlayerResourceCardStockEquals(SiedlerGame model, Map> expected) { + for (int i = 0; i < expected.keySet().size(); i++) { + Config.Faction f = model.getCurrentPlayerFaction(); + for (Config.Resource r : Config.Resource.values()) { + assertEquals(expected.get(f).get(r), model.getCurrentPlayerResourceStock(r), + "Resource card stock of player " + i + " [faction " + f + "] for resource type " + r + " does not match."); + } + model.switchToNextPlayer(); + } + } + + /** + * Tests whether player one can build two roads starting in game state + * {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}. + */ + @Test + public void requirementBuildRoad() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhaseAlmostEmptyBank(DEFAULT_WINPOINTS); + assertTrue(model.buildRoad(new Point(6, 6), new Point(6, 4))); + assertTrue(model.buildRoad(new Point(6, 4), new Point(7, 3))); + } + + /** + * Tests whether player one can build a road and a settlement starting in game state + * {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}. + */ + @Test + public void requirementBuildSettlement() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhaseAlmostEmptyBank(DEFAULT_WINPOINTS); + assertTrue(model.buildRoad(new Point(9, 15), new Point(9, 13))); + assertTrue(model.buildSettlement(new Point(9, 13))); + } + + /** + * Tests whether payout with multiple settlements of the same player at one field works + * {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}. + */ + @Test + public void requirementTwoSettlementsSamePlayerSameFieldResourceCardPayout() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhase(DEFAULT_WINPOINTS); + for(int diceValue : List.of(2, 6, 6, 11)){ + model.throwDice(diceValue); + } + assertTrue(model.buildRoad(new Point(6, 6), new Point(7, 7))); + assertTrue(model.buildSettlement(new Point(7, 7))); + assertEquals(List.of(Config.Resource.ORE, Config.Resource.ORE), model.throwDice(4).get(model.getCurrentPlayerFaction())); + } + + /** + * Tests whether player one can build a city starting in game state + * {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}. + */ + @Test + public void requirementBuildCity() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhaseAlmostEmptyBank(DEFAULT_WINPOINTS); + assertTrue(model.buildCity(new Point(10, 16))); + } + + /** + * Tests whether player two can trade in resources with the bank and has the + * correct number of resource cards afterwards. The test starts from game state + * {@link ThreePlayerStandard#getAfterSetupPhaseAlmostEmptyBank(int)}. + */ + @Test + public void requirementCanTradeFourToOneWithBank() { + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhaseAlmostEmptyBank(DEFAULT_WINPOINTS); + model.switchToNextPlayer(); + + Map expectedResourceCards = ThreePlayerStandard.BANK_ALMOST_EMPTY_RESOURCE_CARD_STOCK.get(model.getCurrentPlayerFaction()); + assertEquals(expectedResourceCards.get(Config.Resource.WOOL), model.getCurrentPlayerResourceStock(Config.Resource.WOOL)); + assertEquals(expectedResourceCards.get(Config.Resource.LUMBER), model.getCurrentPlayerResourceStock(Config.Resource.LUMBER)); + + model.tradeWithBankFourToOne(Config.Resource.WOOL, Config.Resource.LUMBER); + + int cardsOffered = 4; + int cardsReceived = 1; + assertEquals(expectedResourceCards.get(Config.Resource.WOOL)-cardsOffered, model.getCurrentPlayerResourceStock(Config.Resource.WOOL)); + assertEquals(expectedResourceCards.get(Config.Resource.LUMBER)+cardsReceived, model.getCurrentPlayerResourceStock(Config.Resource.LUMBER)); + } + + /*** + * This test is not actually a test and should be removed. However, + * we leave it in for you to have a quick and easy way to look at the + * game board produced by {@link ThreePlayerStandard#getAfterSetupPhase(int)}, + * augmented by annotations, which you won't need since we do not ask for + * more advanced trading functionality using harbours. + */ + @Disabled + @Test + public void print(){ + SiedlerGame model = ThreePlayerStandard.getAfterSetupPhase(DEFAULT_WINPOINTS); + model.getBoard().addFieldAnnotation(new Point(6, 8),new Point(6, 6), "N "); + model.getBoard().addFieldAnnotation(new Point(6, 8),new Point(5, 7), "NE"); + model.getBoard().addFieldAnnotation(new Point(6, 8),new Point(5, 9), "SE"); + model.getBoard().addFieldAnnotation(new Point(6, 8),new Point(6, 10), "S "); + model.getBoard().addFieldAnnotation(new Point(6, 8),new Point(7, 7), "NW"); + model.getBoard().addFieldAnnotation(new Point(6, 8),new Point(7, 9), "SW"); + System.out.println(new SiedlerBoardTextView(model.getBoard()).toString()); + } +} diff --git a/test/ch/zhaw/catan/Tuple.java b/test/ch/zhaw/catan/Tuple.java new file mode 100644 index 0000000..9957113 --- /dev/null +++ b/test/ch/zhaw/catan/Tuple.java @@ -0,0 +1,42 @@ +package ch.zhaw.catan; + +public class Tuple { + public final X first; + public final Y second; + + public Tuple(X x, Y y) { + this.first = x; + this.second = y; + } + + @Override + public String toString() { + return "(" + first + "," + second + ")"; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Tuple)) { + return false; + } + + @SuppressWarnings("unchecked") + Tuple otherCasted = (Tuple) other; + + // null is not a valid value for first and second tuple element + return otherCasted.first.equals(this.first) && otherCasted.second.equals(this.second); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((first == null) ? 0 : first.hashCode()); + result = prime * result + ((second == null) ? 0 : second.hashCode()); + return result; + } +} diff --git a/test/ch/zhaw/catan/games/ThreePlayerStandard.java b/test/ch/zhaw/catan/games/ThreePlayerStandard.java new file mode 100644 index 0000000..a0d525e --- /dev/null +++ b/test/ch/zhaw/catan/games/ThreePlayerStandard.java @@ -0,0 +1,480 @@ +package ch.zhaw.catan.games; + +import ch.zhaw.catan.Config; +import ch.zhaw.catan.Config.Resource; +import ch.zhaw.catan.Tuple; +import ch.zhaw.catan.SiedlerGame; + +import java.awt.*; +import java.util.List; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * This class can be used to prepare some predefined siedler game situations and, for some + * of the situations, it provides information about the expected game state, + * for example the number of resource cards in each player's stock or the expected resource + * card payout when the dices are thrown (for each dice value). + *
+ * The basic game situations upon which all other situations that can be retrieved are based is + * the following: + *
+ *                                 (  )            (  )            (  )            (  )
+ *                              //      \\      //      \\      //      \\      //      \\
+ *                         (  )            (  )            (  )            (  )            (  )
+ *                          ||      ~~      ||      ~~      ||      ~~      ||      ~~      ||
+ *                          ||              ||              ||              ||              ||
+ *                         (  )            (  )            (  )            (  )            (  )
+ *                      //      \\      //      \\      //      \\      //      \\      //      \\
+ *                 (  )            (  )            (  )            (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: + *
    + *
  • Player 1: WOOL BRICK
  • + *
  • Player 2: WOOL WOOL
  • + *
  • Player 3: BRICK
  • + *
+ *

The main ideas for this setup were the following:

+ *
    + *
  • Player one has access to all resource types from the start so that any resource card can be acquired by + * throwing the corresponding dice value.
  • + *
  • The settlements are positioned in a way that for each dice value, there is only one resource card paid + * to one player, except for the dice values 4 and 12.
  • + *
  • There is a settlement next to water and the owner has access to resource types required to build roads
  • + *
  • The initial resource card stock of each player does not allow to build anything without getting + * additional resources first
  • + *
+ * + * @author tebe + */ +public class ThreePlayerStandard { + public final static int NUMBER_OF_PLAYERS = 3; + + public static final Map> INITIAL_SETTLEMENT_POSITIONS = + Map.of( Config.Faction.values()[0], new Tuple<>(new Point(5, 7), new Point(10, 16)), + Config.Faction.values()[1], new Tuple<>(new Point(11, 13), new Point(8, 4)), + Config.Faction.values()[2], new Tuple<>(new Point(2, 12), new Point(7, 19))); + + public static final Map> INITIAL_ROAD_ENDPOINTS = Map.of(Config.Faction.values()[0], + new Tuple<>(new Point(6, 6), new Point(9, 15)), Config.Faction.values()[1], + new Tuple<>(new Point(12, 12), new Point(8, 6)), Config.Faction.values()[2], + new Tuple<>(new Point(2, 10), new Point(8, 18))); + + public static final Map> INITIAL_PLAYER_CARD_STOCK = Map.of( + Config.Faction.values()[0], Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 1, + Config.Resource.BRICK, 1, Config.Resource.ORE, 0, Config.Resource.LUMBER, 0), + Config.Faction.values()[1], + Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 2, Config.Resource.BRICK, 0, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 0), + Config.Faction.values()[2], + Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 0, Config.Resource.BRICK, 1, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 0)); + + public static final Map> BANK_ALMOST_EMPTY_RESOURCE_CARD_STOCK = Map.of( + Config.Faction.values()[0], Map.of(Config.Resource.GRAIN, 8, Config.Resource.WOOL, 9, + Config.Resource.BRICK, 9, Config.Resource.ORE, 7, Config.Resource.LUMBER, 9), + Config.Faction.values()[1], + Map.of(Config.Resource.GRAIN, 8, Config.Resource.WOOL, 10, Config.Resource.BRICK, 0, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 0), + Config.Faction.values()[2], + Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 0, Config.Resource.BRICK, 8, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 9)); + + public static final Map> PLAYER_ONE_READY_TO_BUILD_FIFTH_SETTLEMENT_RESOURCE_CARD_STOCK = Map.of( + Config.Faction.values()[0], Map.of(Config.Resource.GRAIN, 2, Config.Resource.WOOL, 2, + Config.Resource.BRICK, 3, Config.Resource.ORE, 0, Config.Resource.LUMBER, 3), + Config.Faction.values()[1], + Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 5, Config.Resource.BRICK, 0, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 0), + Config.Faction.values()[2], + Map.of(Config.Resource.GRAIN, 0, Config.Resource.WOOL, 0, Config.Resource.BRICK, 1, + Config.Resource.ORE, 0, Config.Resource.LUMBER, 0)); + + public static final Map>> INITIAL_DICE_THROW_PAYOUT = Map.of( + 2, Map.of( + Config.Faction.values()[0], List.of(Resource.GRAIN), + Config.Faction.values()[1], List.of(), + Config.Faction.values()[2], List.of()), + 3, Map.of( + Config.Faction.values()[0], List.of(), + Config.Faction.values()[1], List.of(Resource.WOOL), + Config.Faction.values()[2], List.of()), + 4, Map.of( + Config.Faction.values()[0], List.of(Resource.ORE), + Config.Faction.values()[1], List.of(), + Config.Faction.values()[2], List.of(Resource.BRICK)), + 5, Map.of( + Config.Faction.values()[0], List.of(), + Config.Faction.values()[1], List.of(), + Config.Faction.values()[2], List.of(Resource.LUMBER)), + 6, Map.of( + Config.Faction.values()[0], List.of(Resource.LUMBER), + Config.Faction.values()[1], List.of(), + Config.Faction.values()[2], List.of()), + 8, Map.of( + Config.Faction.values()[0], List.of(), + Config.Faction.values()[1], List.of(Resource.WOOL), + Config.Faction.values()[2], List.of()), + 9, Map.of( + Config.Faction.values()[0], List.of(), + Config.Faction.values()[1], List.of(Resource.GRAIN), + Config.Faction.values()[2], List.of()), + 10, Map.of( + Config.Faction.values()[0], List.of(), + Config.Faction.values()[1], List.of(), + Config.Faction.values()[2], List.of()), + 11, Map.of( + Config.Faction.values()[0], List.of(Resource.BRICK), + Config.Faction.values()[1], List.of(), + Config.Faction.values()[2], List.of()), + 12, Map.of( + Config.Faction.values()[0], List.of(Resource.WOOL), + Config.Faction.values()[1], List.of(Resource.WOOL), + Config.Faction.values()[2], List.of())); + + public static final Map RESOURCE_CARDS_IN_BANK_AFTER_STARTUP_PHASE = Map.of(Resource.LUMBER, 19, + Resource.BRICK, 17, Resource.WOOL, 16, Resource.GRAIN, 19, Resource.ORE, 19); + + public static final Point PLAYER_ONE_READY_TO_BUILD_FIFTH_SETTLEMENT_FIFTH_SETTLEMENT_POSITION = new Point(9, 13); + public static final List playerOneReadyToBuildFifthSettlementAllSettlementPositions = + List.of(INITIAL_SETTLEMENT_POSITIONS.get(Config.Faction.values()[0]).first, + INITIAL_SETTLEMENT_POSITIONS.get(Config.Faction.values()[0]).second, + new Point(7, 7),new Point(6, 4), PLAYER_ONE_READY_TO_BUILD_FIFTH_SETTLEMENT_FIFTH_SETTLEMENT_POSITION); + + /** + * Returns a siedler game after the setup phase in the setup + * and with the initial resource card setup as described + * in {@link ThreePlayerStandard}. + * + * @param winpoints the number of points required to win the game + * @return the siedler game + */ + public static SiedlerGame getAfterSetupPhase(int winpoints) { + SiedlerGame model = new SiedlerGame(winpoints, NUMBER_OF_PLAYERS); + for (int i = 0; i < model.getPlayerFactions().size(); i++) { + Config.Faction f = model.getCurrentPlayerFaction(); + assertTrue(model.placeInitialSettlement(INITIAL_SETTLEMENT_POSITIONS.get(f).first, false)); + assertTrue(model.placeInitialRoad(INITIAL_SETTLEMENT_POSITIONS.get(f).first, INITIAL_ROAD_ENDPOINTS.get(f).first)); + model.switchToNextPlayer(); + } + for (int i = 0; i < model.getPlayerFactions().size(); i++) { + model.switchToPreviousPlayer(); + Config.Faction f = model.getCurrentPlayerFaction(); + assertTrue(model.placeInitialSettlement(INITIAL_SETTLEMENT_POSITIONS.get(f).second, true)); + assertTrue(model.placeInitialRoad(INITIAL_SETTLEMENT_POSITIONS.get(f).second, INITIAL_ROAD_ENDPOINTS.get(f).second)); + } + return model; + } + + /** + * Returns a siedler game after the setup phase in the setup + * described in {@link ThreePlayerStandard} and with the bank almost empty. + * + * The following resource cards should be in the stock of the bank: + *
    + *
  • LUMBER: 1
  • + *
  • BRICK: 2
  • + *
  • GRAIN: 3
  • + *
  • ORE: 13
  • + *
  • WOOL: 0
  • + *
+ * + * The stocks of the players should contain: + *
+ * Player 1: + *
    + *
  • LUMBER: 9
  • + *
  • BRICK: 9
  • + *
  • GRAIN: 8
  • + *
  • ORE: 7
  • + *
  • WOOL: 9
  • + *
+ * Player 2: + *
    + *
  • LUMBER: 0
  • + *
  • BRICK: 0
  • + *
  • GRAIN: 8
  • + *
  • ORE: 0
  • + *
  • WOOL: 10
  • + *
+ * Player 3: + *
    + *
  • LUMBER: 9
  • + *
  • BRICK: 8
  • + *
  • GRAIN: 0
  • + *
  • ORE: 0
  • + *
  • WOOL: 0
  • + *
+ * + * @param winpoints the number of points required to win the game + * @return the siedler game + */ + public static SiedlerGame getAfterSetupPhaseAlmostEmptyBank(int winpoints) { + SiedlerGame model = getAfterSetupPhase(winpoints); + throwDiceMultipleTimes(model, 6, 9); + throwDiceMultipleTimes(model, 11, 8); + throwDiceMultipleTimes(model, 2, 8); + throwDiceMultipleTimes(model, 4, 7); + throwDiceMultipleTimes(model, 12, 8); + throwDiceMultipleTimes(model, 5, 9); + throwDiceMultipleTimes(model, 9, 8); + return model; + } + + + /** + * Returns a {@link SiedlerGame} with several roads added but none longer than + * 4 elements. Hence, no player meets the longest road criteria yet. Furthermore, + * players one and three have enough resource cards to build additional roads and settlements. + * + *

+ *

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: + *

    + *
  • LUMBER: 6
  • + *
  • BRICK: 6
  • + *
  • GRAIN: 1
  • + *
  • ORE: 11
  • + *
  • WOOL: 1
  • + *
+ * Player 2: + *
    + *
  • LUMBER: 0
  • + *
  • BRICK: 0
  • + *
  • GRAIN: 0
  • + *
  • ORE: 0
  • + *
  • WOOL: 2
  • + *
+ * Player 3: + *
    + *
  • LUMBER: 6
  • + *
  • BRICK: 6
  • + *
  • GRAIN: 1
  • + *
  • ORE: 0
  • + *
  • WOOL: 1
  • + *
+ * + * @param winpoints the number of points required to win the game + * @return the siedler game + */ + public static SiedlerGame getAfterSetupPhaseSomeRoads(int winpoints) { + SiedlerGame model = getAfterSetupPhase(winpoints); + throwDiceMultipleTimes(model, 6, 7); + throwDiceMultipleTimes(model, 11, 6); + throwDiceMultipleTimes(model, 4, 5); + throwDiceMultipleTimes(model, 5, 6); + throwDiceMultipleTimes(model, 2, 1); + + model.switchToNextPlayer(); + model.switchToNextPlayer(); + model.buildRoad(new Point(2,12), new Point(3,13)); + buildRoad(model, List.of(new Point(2,10), new Point(3,9), new Point(3,7))); + model.buildRoad(new Point(8,18), new Point(8,16)); + buildRoad(model, List.of(new Point(7,19), new Point(6,18), new Point(6,16))); + model.switchToNextPlayer(); + model.buildRoad(new Point(10,16), new Point(11,15)); + model.buildRoad(new Point(10,16), new Point(10,18)); + buildRoad(model, List.of(new Point(9,15), new Point(9,13), new Point(10,12))); + buildRoad(model, List.of(new Point(5,7), new Point(5,9), new Point(4,10), new Point(3, 9))); + + throwDiceMultipleTimes(model, 6, 6); + throwDiceMultipleTimes(model, 11, 6); + throwDiceMultipleTimes(model, 4, 6); + throwDiceMultipleTimes(model, 5, 6); + + model.switchToNextPlayer(); + model.switchToNextPlayer(); + throwDiceMultipleTimes(model, 5, 4); + model.tradeWithBankFourToOne(Resource.LUMBER, Resource.GRAIN); + throwDiceMultipleTimes(model, 5, 4); + model.tradeWithBankFourToOne(Resource.LUMBER, Resource.WOOL); + model.switchToNextPlayer(); + return model; + } + + + + private static SiedlerGame throwDiceMultipleTimes(SiedlerGame model, int diceValue, int numberOfTimes) { + for(int i=0; i

+ *

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:

+ *
+ * Player 1: + *
    + *
  • LUMBER: 3
  • + *
  • BRICK: 3
  • + *
  • GRAIN: 2
  • + *
  • ORE: 0
  • + *
  • WOOL: 2
  • + *
+ * Player 2: + *
    + *
  • LUMBER: 0
  • + *
  • BRICK: 0
  • + *
  • GRAIN: 0
  • + *
  • ORE: 0
  • + *
  • WOOL: 5
  • + *
+ * Player 3: + *
    + *
  • LUMBER: 0
  • + *
  • BRICK: 1
  • + *
  • GRAIN: 0
  • + *
  • ORE: 0
  • + *
  • WOOL: 0
  • + *
+ * + * @param winpoints the number of points required to win the game + * @return the siedler game + */ + public static SiedlerGame getPlayerOneReadyToBuildFifthSettlement(int winpoints) { + SiedlerGame model = getAfterSetupPhase(winpoints); + //generate resources to build four roads and four settlements. + throwDiceMultipleTimes(model, 6, 8); + throwDiceMultipleTimes(model, 11, 7); + throwDiceMultipleTimes(model, 2, 4); + throwDiceMultipleTimes(model, 12, 3); + model.buildRoad(new Point(6,6), new Point(7,7)); + model.buildRoad(new Point(6,6), new Point(6,4)); + model.buildRoad(new Point(9,15), new Point(9,13)); + model.buildSettlement(playerOneReadyToBuildFifthSettlementAllSettlementPositions.get(2)); + model.buildSettlement(playerOneReadyToBuildFifthSettlementAllSettlementPositions.get(3)); + return model; + } + private static void buildSettlement(SiedlerGame model, Point position, List roads) { + buildRoad(model, roads); + assertTrue(model.buildSettlement(position)); + } + + private static void buildRoad(SiedlerGame model, List roads) { + for (int i = 0; i < roads.size()-1; i++) { + assertTrue(model.buildRoad(roads.get(i), roads.get(i + 1))); + } + } +} diff --git a/test/ch/zhaw/hexboard/EdgeTest.java b/test/ch/zhaw/hexboard/EdgeTest.java new file mode 100644 index 0000000..e07ff73 --- /dev/null +++ b/test/ch/zhaw/hexboard/EdgeTest.java @@ -0,0 +1,106 @@ +package ch.zhaw.hexboard; + +import java.awt.Point; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/*** + *

+ * 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 board; + private Point[] corner; + + /** + * Setup for a test - Instantiates a board and adds one field at (7,5). + * + *
+   *         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))); + } +}