Add files via upload

This commit is contained in:
fassband 2021-11-19 08:32:32 +01:00 committed by GitHub Enterprise
parent e9a9181cd0
commit f2fe06f720
19 changed files with 8166 additions and 0 deletions

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

BIN
lib/text-io-3.4.0.jar Normal file

Binary file not shown.

View File

@ -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.
* <p>
* 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 <a href=
* "https://www.catan.de/files/downloads/4002051693602_catan_-_das_spiel_0.pdf">here</a>
* </p>
* @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<Resource, Integer> 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.
* <p>
* The enum provides information about the cost of a structure and how many of
* these structures are available per player.
* </p>
*/
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<Resource> costs;
private int stockPerPlayer;
private Structure(List<Resource> costs, int stockPerPlayer) {
this.costs = costs;
this.stockPerPlayer = stockPerPlayer;
}
/**
* Returns the build costs of this structure.
* <p>
* Each list entry represents a resource card. The value of an entry (e.g., {@link Resource#LUMBER})
* identifies the resource type of the card.
* </p>
* @return the build costs
*/
public List<Resource> 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<Resource, Long> 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<Point, Integer> getStandardDiceNumberPlacement() {
Map<Point, Integer> 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 <a href=
* "https://www.catan.de/files/downloads/4002051693602_catan_-_das_spiel_0.pdf">standard
* setup</a> of the game Catan..
*
* @return the field to {@link Land} mapping for the standard setup
*/
public static final Map<Point, Land> getStandardLandPlacement() {
Map<Point, Land> 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);
}
}

View File

@ -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<Point, Label> lowerFieldLabel = new HashMap<>();
lowerFieldLabel.put(new Point(2, 2), new Label('0', '9'));
SiedlerBoardTextView view = new SiedlerBoardTextView(board);
for (Map.Entry<Point, Label> 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 extends Enum<T>> T getEnumValue(TextIO textIO, Class<T> commands) {
return textIO.newEnumInputReader(commands).read("What would you like to do?");
}
public static void main(String[] args) {
new Dummy().run();
}
}

View File

@ -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<Land, String, String, String> {
//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<Point> 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<Land> getLandsForCorner(Point corner) {
//TODO: Implement.
return Collections.emptyList();
}
}

View File

@ -0,0 +1,12 @@
package ch.zhaw.catan;
import ch.zhaw.catan.Config.Land;
import ch.zhaw.hexboard.HexBoardTextView;
public class SiedlerBoardTextView extends HexBoardTextView<Land, String, String, String> {
public SiedlerBoardTextView(SiedlerBoard board) {
super(board);
}
}

View File

@ -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.
*
* <p>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.
*
* <strong>Important note:</strong> The list must contain the
* factions of active players only.</p>
*
* @return the list with player's factions
*/
public List<Faction> 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.
*
* <p>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.</p>
*
* @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 <b>an empty list (not null)</b>!.
*
* <p>
* 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).
* </p>
*
* @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<Faction, List<Resource>> throwDice(int dicethrow) {
// TODO: Implement
return null;
}
/**
* Builds a settlement at the specified position on the board.
*
* <p>The settlement can be built if:
* <ul>
* <li> the player possesses the required resource cards</li>
* <li> a settlement to place on the board</li>
* <li> the specified position meets the build rules for settlements</li>
* </ul>
*
* @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.
*
* <p>The city can be built if:
* <ul>
* <li> the player possesses the required resource cards</li>
* <li> a city to place on the board</li>
* <li> the specified position meets the build rules for cities</li>
* </ul>
*
* @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.
*
* <p>The road can be built if:
* <ul>
* <li> the player possesses the required resource cards</li>
* <li> a road to place on the board</li>
* <li> the specified position meets the build rules for roads</li>
* </ul>
*
* @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;
}
}

View File

@ -0,0 +1,117 @@
package ch.zhaw.hexboard;
import java.awt.Point;
/**
* This class models an edge on @see ch.zhaw.hexboard.HexBoard.
* <p>
* Edges are non-directional and can be created by providing the two points that
* span an edge on the hex-grid defined by @see ch.zhaw.hexboard.HexBoard
* </p>
* @author tebe
*
*/
final class Edge {
private Point start;
private Point end;
/**
* Creates an edge between the two points.
*
* @param p1 first point
* @param p2 second point
* @throws IllegalArgumentException if the points are not non-null or not a
* valid point for an edge on the grid defined
* by @see ch.zhaw.hexboard.HexBoard
*/
public Edge(Point p1, Point p2) {
if (Edge.isEdge(p1, p2)) {
if (p1.x > p2.x || (p1.x == p2.x && p1.y > p2.y)) {
this.start = new Point(p2);
this.end = new Point(p1);
} else {
this.start = new Point(p1);
this.end = new Point(p2);
}
} else {
throw new IllegalArgumentException(
"Coordinates " + p1 + " and " + p2 + " are not coordinates of an edge.");
}
}
static boolean isEdge(Point p1, Point p2) {
boolean isEdge = false;
if (p1 != null && p2 != null && HexBoard.isCornerCoordinate(p1)
&& HexBoard.isCornerCoordinate(p2)) {
int xdistance = Math.abs(p1.x - p2.x);
int ydistance = Math.abs(p1.y - p2.y);
boolean isVerticalEdge = xdistance == 0 && ydistance == 2;
boolean isDiagonalEdge = xdistance == 1 && ydistance == 1;
isEdge = isVerticalEdge || isDiagonalEdge;
}
return isEdge;
}
public boolean isEdgePoint(Point p1) {
return start.equals(p1) || end.equals(p1);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((end == null) ? 0 : end.hashCode());
result = prime * result + ((start == null) ? 0 : start.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Edge other = (Edge) obj;
if (end == null) {
if (other.end != null) {
return false;
}
} else if (!end.equals(other.end)) {
return false;
}
if (start == null) {
if (other.start != null) {
return false;
}
} else if (!start.equals(other.start)) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Edge [start=" + start + ", end=" + end + "]";
}
}

View File

@ -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 + "]";
}
}

View File

@ -0,0 +1,541 @@
package ch.zhaw.hexboard;
import java.awt.Point;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/***
* <p>
* This class represents a simple generic hexagonal game board.
* </p>
* <p>The game board uses a fixed coordinate system which is structured as follows:</p>
*
* <pre>
* 0 1 2 3 4 5 6 7 8
* | | | | | | | | | ...
*
* 0---- C C C C C
* \ / \ / \ / \ / \
* 1---- C C C C C
*
* 2---- F | F | F | F | F | ...
*
* 3---- C C C C C
* / \ / \ / \ / \ /
* 4---- C C C C C
*
* 5---- | F | F | F | F | F ...
*
* 6---- C C C C C
* \ / \ / \ / \ / \
* 7---- C C C C C
*
* ...
* </pre>
*
* <p>
* Fields <strong>F</strong> and corners <strong>C</strong> can be retrieved
* using their coordinates ({@link java.awt.Point}) on the board. Edges can be
* retrieved using the coordinates of the two corners they connect.
* </p>
*
* <p>
* When created, the board is empty (no fields added). To add fields, the
* #{@link #addField(Point, Object)} function can be used. Edges and corners are
* automatically created when adding a field. They cannot be created/removed
* individually. When adding a field, edges and corners that were already
* created, e.g., because adding an adjacent field already created them, are
* left untouched.
* </p>
*
* <p>
* Fields, edges and corners can store an object of the type of the
* corresponding type parameter each.
* </p>
*
* <p>
* Furthermore, the hexagonal game board can store six additional objects, so
* called annotations, for each field. These objects are identified by the
* coordinates of the field and the corner. Hence, they can be thought of being
* located between the center and the respective corner. Or in other words,
* their positions correspond to the positions N, NW, SW, NE, NW, SE and NE in
* the below visualization of a field.
* </p>
*
* <pre>
* SW (C) SE
* / N \
* (C) NW NE (C)
* | F |
* | |
* (C) SW SE (C)
* \ S /
* NW (C) NE
* </pre>
*
* @param <F> Data type for the field data objects
* @param <C> Data type for the corner data objects
* @param <E> Data type for the edge data objects
* @param <A> Data type for the annotation data objects
*
* @author tebe
*
*/
public class HexBoard<F, C, E, A> {
private int maxCoordinateX = 0;
private int maxCoordinateY = 0;
private final Map<Point, F> field;
private final Map<Point, C> corner;
private final Map<Edge, E> edge;
private final Map<FieldAnnotationPosition, A> annotation;
/**
* Constructs an empty hexagonal board.
*/
public HexBoard() {
field = new HashMap<>();
corner = new HashMap<>();
edge = new HashMap<>();
annotation = new HashMap<>();
}
/**
* Adds a field to the board and creates the surrounding (empty) corners and
* edges if they do not yet exist Note: Corners and edges of a field might
* already have been created while creating adjacent fields.
*
* @param center Coordinate of the center of a field on the unit grid
* @param element Data element to be stored for this field
*
* @throws IllegalArgumentException if center is not the center of a field, the
* field already exists or data is null
*/
public void addField(Point center, F element) {
if (isFieldCoordinate(center) && !field.containsKey(center)) {
field.put(center, element);
maxCoordinateX = Math.max(center.x + 1, maxCoordinateX);
maxCoordinateY = Math.max(center.y + 2, maxCoordinateY);
// add (empty) edge, if they do not yet exist
for (Edge e : constructEdgesOfField(center)) {
if (!edge.containsKey(e)) {
edge.put(e, null);
}
}
// add (empty) corners, if they do not yet exist
for (Point p : getCornerCoordinatesOfField(center)) {
if (!corner.containsKey(p)) {
corner.put(p, null);
}
}
} else {
throw new IllegalArgumentException(
"Coordinates are not the center of a field, the field already exists or data is null - ("
+ center.x + ", " + center.y + ")");
}
}
/**
* Add an annotation for the specified field and corner.
*
* @param center the center of the field
* @param corner the corner of the field
* @param data the annotation
* @throws IllegalArgumentException if the field does not exists or when the
* annotation already exists
*/
public void addFieldAnnotation(Point center, Point corner, A data) {
FieldAnnotationPosition annotationPosition = new FieldAnnotationPosition(center, corner);
if (!annotation.containsKey(annotationPosition)) {
annotation.put(annotationPosition, data);
} else {
throw new IllegalArgumentException("Annotation: " + annotation + " already exists for field "
+ center + " and position " + corner);
}
}
/**
* Get an annotation for the specified field and corner.
*
* @param center the center of the field
* @param corner the corner of the field
* @return the annotation
* @throws IllegalArgumentException if coordinates are not a field and
* corresponding corner coordinate
*/
public A getFieldAnnotation(Point center, Point corner) {
return annotation.get(new FieldAnnotationPosition(center, corner));
}
/**
* Get field annotation whose position information includes the specified corner.
*
* @param corner the corner
* @return a list with the annotations that are not null
* @throws IllegalArgumentException if corner is not a corner
*/
public List<A> getFieldAnnotationsForCorner(Point corner) {
List<A> list = new LinkedList<>();
for (Entry<FieldAnnotationPosition, A> entry : annotation.entrySet()) {
if (entry.getKey().isCorner(corner) && entry.getValue() != null) {
list.add(entry.getValue());
}
}
return list;
}
/**
* Get all field annotation of the specified field.
*
* @param center the field
* @return a list with the annotations that are not null
* @throws IllegalArgumentException if center is not a field
*/
public List<A> getFieldAnnotationsForField(Point center) {
List<A> list = new LinkedList<>();
for (Entry<FieldAnnotationPosition, A> entry : annotation.entrySet()) {
if (entry.getKey().isField(center) && entry.getValue() != null) {
list.add(entry.getValue());
}
}
return list;
}
/**
* Determines whether the field at the specified position exists.
*
* @param center the field
* @return false, if the field does not exist or the position is not a field
*/
public boolean hasField(Point center) {
if (!HexBoard.isFieldCoordinate(center)) {
return false;
}
return field.containsKey(center);
}
static boolean isFieldCoordinate(Point position) {
boolean isYFieldCoordinateEven = (position.y - 2) % 6 == 0;
boolean isYFieldCoordinateOdd = (position.y - 5) % 6 == 0;
boolean isXFieldCoordinateEven = position.x % 2 == 0;
boolean isXFieldCoordinateOdd = (position.x - 1) % 2 == 0;
return (position.y >= 2 && position.x >= 1)
&& (isYFieldCoordinateEven && isXFieldCoordinateEven)
|| (isYFieldCoordinateOdd && isXFieldCoordinateOdd);
}
static boolean isCornerCoordinate(Point p) {
// On the horizontal center lines, no edge points exist
boolean isOnFieldCenterLineHorizontal = (p.y - 2) % 3 == 0;
// On the vertical center lines, edge points exist
boolean isOnFieldCenterLineVerticalOdd = (p.x - 1) % 3 == 0 && p.x % 2 == 0;
boolean isOnFieldCenterLineVerticalEven = (p.x - 1) % 3 == 0 && (p.x - 1) % 2 == 0;
boolean isNotAnEdgePointOnFieldCentralVerticalLine = isOnFieldCenterLineVerticalOdd
&& !(p.y % 6 == 0 || (p.y + 2) % 6 == 0)
|| isOnFieldCenterLineVerticalEven && !((p.y + 5) % 6 == 0 || (p.y + 3) % 6 == 0);
return !(isOnFieldCenterLineHorizontal || isNotAnEdgePointOnFieldCentralVerticalLine);
}
private List<Edge> constructEdgesOfField(Point position) {
Edge[] e = new Edge[6];
e[0] = new Edge(new Point(position.x, position.y - 2),
new Point(position.x + 1, position.y - 1));
e[1] = new Edge(new Point(position.x + 1, position.y - 1),
new Point(position.x + 1, position.y + 1));
e[2] = new Edge(new Point(position.x + 1, position.y + 1),
new Point(position.x, position.y + 2));
e[3] = new Edge(new Point(position.x, position.y + 2),
new Point(position.x - 1, position.y + 1));
e[4] = new Edge(new Point(position.x - 1, position.y + 1),
new Point(position.x - 1, position.y - 1));
e[5] = new Edge(new Point(position.x - 1, position.y - 1),
new Point(position.x, position.y - 2));
return Arrays.asList(e);
}
private static List<Point> getCornerCoordinatesOfField(Point position) {
Point[] corner = new Point[6];
corner[0] = new Point(position.x, position.y - 2);
corner[1] = new Point(position.x + 1, position.y - 1);
corner[2] = new Point(position.x + 1, position.y + 1);
corner[3] = new Point(position.x, position.y + 2);
corner[4] = new Point(position.x - 1, position.y - 1);
corner[5] = new Point(position.x - 1, position.y + 1);
return Collections.unmodifiableList(Arrays.asList(corner));
}
protected static List<Point> getAdjacentCorners(Point position) {
Point[] corner = new Point[3];
if (position.y % 3 == 0) {
corner[0] = new Point(position.x, position.y - 2);
corner[1] = new Point(position.x + 1, position.y + 1);
corner[2] = new Point(position.x - 1, position.y + 1);
} else {
corner[0] = new Point(position.x, position.y + 2);
corner[1] = new Point(position.x + 1, position.y - 1);
corner[2] = new Point(position.x - 1, position.y - 1);
}
return Collections.unmodifiableList(Arrays.asList(corner));
}
/**
* Returns all non-null corner data elements.
*
* @return the non-null corner data elements
*/
public List<C> getCorners() {
List<C> result = new LinkedList<>();
for (C c : this.corner.values()) {
if (c != null) {
result.add(c);
}
}
return Collections.unmodifiableList(result);
}
protected Set<Point> getCornerCoordinates() {
return Collections.unmodifiableSet(this.corner.keySet());
}
private static List<Point> getAdjacentFields(Point corner) {
Point[] field = new Point[3];
if (corner.y % 3 == 0) {
field[0] = new Point(corner.x, corner.y + 2);
field[1] = new Point(corner.x + 1, corner.y - 1);
field[2] = new Point(corner.x - 1, corner.y - 1);
} else {
field[0] = new Point(corner.x, corner.y - 2);
field[1] = new Point(corner.x + 1, corner.y + 1);
field[2] = new Point(corner.x - 1, corner.y + 1);
}
return Collections.unmodifiableList(Arrays.asList(field));
}
/**
* Returns the data for the field denoted by the point.
*
* @param center the location of the field
* @return the stored data (or null)
* @throws IllegalArgumentException if the requested field does not exist
*/
public F getField(Point center) {
if (field.containsKey(center)) {
return field.get(center);
} else {
throw new IllegalArgumentException("No field exists at these coordinates: " + center);
}
}
/**
* Returns the fields with non-null data elements.
*
* @return the list with the (non-null) field data
*/
public List<Point> getFields() {
List<Point> result = new LinkedList<>();
for (Entry<Point, F> e : field.entrySet()) {
if (e.getValue() != null) {
result.add(e.getKey());
}
}
return Collections.unmodifiableList(result);
}
/**
* Returns the field data of the fields that touch this corner.
* <p>
* If the specified corner is not a corner or none of the fields that touch this
* corner have a non-null data element, an empty list is returned.
* </p>
* @param corner the location of the corner
* @return the list with the (non-null) field data
*/
public List<F> getFields(Point corner) {
List<F> result = new LinkedList<>();
if (isCornerCoordinate(corner)) {
for (Point f : getAdjacentFields(corner)) {
if (field.get(f) != null) {
result.add(field.get(f));
}
}
}
return Collections.unmodifiableList(result);
}
/**
* Returns the data for the edge denoted by the two points.
*
* @param p1 first point
* @param p2 second point
* @return the stored data (or null)
*/
public E getEdge(Point p1, Point p2) {
Edge e = new Edge(p1, p2);
if (edge.containsKey(e)) {
return edge.get(e);
} else {
return null;
}
}
/**
* Stores the data for the edge denoted by the two points.
*
* @param p1 first point
* @param p2 second point
* @param data the data to be stored
* @throws IllegalArgumentException if the two points do not identify an
* EXISTING edge of the field
*/
public void setEdge(Point p1, Point p2, E data) {
Edge e = new Edge(p1, p2);
if (edge.containsKey(e)) {
edge.put(e, data);
} else {
throw new IllegalArgumentException("Edge does not exist => no data can be stored: " + e);
}
}
/**
* Returns the data for the corner denoted by the point.
*
* @param location the location of the corner
* @return the data stored for this node (or null)
* @throws IllegalArgumentException if the requested corner does not exist
*/
public C getCorner(Point location) {
if (corner.containsKey(location)) {
return corner.get(location);
} else {
throw new IllegalArgumentException("No corner exists at the coordinates: " + location);
}
}
/**
* Stores the data for the edge denoted by the two points.
*
* @param location the location of the corner
* @param data the data to be stored
* @return the old data entry (or null)
* @throws IllegalArgumentException if there is no corner at this location
*/
public C setCorner(Point location, C data) {
C old = corner.get(location);
if (corner.containsKey(location)) {
corner.put(location, data);
return old;
} else {
throw new IllegalArgumentException(
"Corner does not exist => no data can be stored: " + location);
}
}
/**
* Returns the (non-null) corner data elements of the corners that are direct
* neighbors of the specified corner.
* <p>
* Each corner has three direct neighbors, except corners that are located at
* the border of the game board.
* </p>
* @param center the location of the corner for which to return the direct
* neighbors
* @return list with non-null corner data elements
*/
public List<C> getNeighboursOfCorner(Point center) {
List<C> result = new LinkedList<>();
for (Point c : HexBoard.getAdjacentCorners(center)) {
C temp = corner.get(c);
if (temp != null) {
result.add(temp);
}
}
return result;
}
/**
* Returns the (non-null) edge data elements of the edges that directly connect
* to that corner.
* <p>
* Each corner has three edges connecting to it, except edges that are located
* at the border of the game board.
* </p>
* @param corner corner for which to get the edges
* @return list with non-null edge data elements of edges connecting to the
* specified edge
*/
public List<E> getAdjacentEdges(Point corner) {
List<E> result = new LinkedList<>();
for (Entry<Edge, E> e : this.edge.entrySet()) {
if (e.getKey().isEdgePoint(corner)
&& e.getValue() != null) {
result.add(e.getValue());
}
}
return result;
}
/**
* Returns the (non-null) data elements of the corners of the specified field.
*
* @param center the location of the field
* @return list with non-null corner data elements
*/
public List<C> getCornersOfField(Point center) {
List<C> result = new LinkedList<>();
for (Point c : getCornerCoordinatesOfField(center)) {
C temp = getCorner(c);
if (temp != null) {
result.add(temp);
}
}
return result;
}
int getMaxCoordinateX() {
return maxCoordinateX;
}
int getMaxCoordinateY() {
return maxCoordinateY;
}
/**
* Checks whether there is a corner at that specified location.
* @param location the location to check
* @return true, if there is a corner at this location
*/
public boolean hasCorner(Point location) {
if (!HexBoard.isCornerCoordinate(location)) {
return false;
}
return corner.containsKey(location);
}
/**
* Checks whether there is an edge between the two points.
* @param p1 first point
* @param p2 second point
* @return true, if there is an edge between the two points
*/
public boolean hasEdge(Point p1, Point p2) {
if (Edge.isEdge(p1, p2)) {
return edge.containsKey(new Edge(p1, p2));
} else {
return false;
}
}
static boolean isCorner(Point field, Point corner) {
return HexBoard.isFieldCoordinate(field)
&& HexBoard.getCornerCoordinatesOfField(field).contains(corner);
}
}

View File

@ -0,0 +1,366 @@
package ch.zhaw.hexboard;
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
/**
* This class can be used to get a textual representation of a hex-grid modeled
* by {@link ch.zhaw.hexboard.HexBoard}.
* <p>
* It creates a textual representation of the {@link ch.zhaw.hexboard.HexBoard}
* that includes all defined fields, edges, corners and annotations.
* </p>
* The generation of the textual representation is basically working on a line
* by line basis. Thereby, the two text lines needed for the diagonal edges are
* treated like "one line" in that they are created in one step together.
* <p>
* The textual representation does not contain the hex-grid as such but only the
* fields that actually exist on the hex-board. Note that if a field exists,
* also its corners and edges exist and are therefore shown in the textual
* representation.
* </p>
* <p>
* This class defines how edges, corners and fields look like (their "label").
* This is done as follows:</p>
* <ul>
* <li>If there is no data object associated with an edge, corner or field,
* their default representation is used. Note that the default representation of
* an edge depends on its direction (see below).</li>
* <li>If there is a data object associated with an edge, corner or field, the
* {@link ch.zhaw.hexboard.Label} is determined by calling:
* <ul>
* <li>EL = {@link #getEdgeLabel(Object)}</li>
* <li>CL = {@link #getCornerLabel(Object)}</li>
* <li>UL = {@link #getFieldLabelUpper(Object)}</li>
* </ul>
* </li>
* </ul>
* <p>In addition to edges, corners and field labels, the hex-board's field
* annotations are included too. If an annotation exists for one of the corners
* (N, NW, SW, S, SE, NE), which means that an associated data object exists, it
* is turned into a {@link ch.zhaw.hexboard.Label} with
* {@link #getAnnotationLabel(Object)}.</p>
* <br>
* <p>Two examples of how that looks like are shown below. The first example shows
* a case with all edges, corners and the field with no data associated with
* them. The second one has all edges corner and the upper field label defined
* by calling the corresponding method for creating the Label for the associated
* data object.</p>
*
* <pre>
* DEFAULT LABELS FROM DATA
*
* ( ) (CL)
* // \\ EL EL
* // \\ EL N EL
* ( ) ( ) (CL) NW NE (CL)
* || || EL UL EL
* || || EL EL
* ( ) ( ) (CL) SW SE (CL)
* \\ // EL S EL
* \\ // EL EL
* ( ) (CL)
* </pre>
* <br>
* <p>To override the default behavior, which creates a Label using the two first
* characters of the string returned by the toString() method of the
* edge/corner/field data object, you might override the respective methods.
* </p>
* <br>
* <p>
* Finally, a field can be labeled with a lower label (LL) by providing a map of
* field coordinates and associated labels. An example of a representation with
* all field annotations, corner labels and field labels defined but default
* edges is the following:</p>
*
* <pre>
* (CL)
* // N \\
* // \\
* (CL) NW NE (CL)
* || UL ||
* || LL ||
* (CL) SW SE (CL)
* \\ S //
* \\ //
* (CL)
* </pre>
*
* @param <F> See {@link ch.zhaw.hexboard.HexBoard}
* @param <C> See {@link ch.zhaw.hexboard.HexBoard}
* @param <E> See {@link ch.zhaw.hexboard.HexBoard}
* @param <A> See {@link ch.zhaw.hexboard.HexBoard}
*
*
* @author tebe
*/
public class HexBoardTextView<F, C, E, A> {
private static final String ONE_SPACE = " ";
private static final String TWO_SPACES = " ";
private static final String FOUR_SPACES = " ";
private static final String FIVE_SPACES = " ";
private static final String SIX_SPACES = " ";
private static final String SEVEN_SPACES = " ";
private static final String NINE_SPACES = " ";
private final HexBoard<F, C, E, A> board;
private final Label emptyLabel = new Label(' ', ' ');
private final Label defaultDiagonalEdgeDownLabel = new Label('\\', '\\');
private final Label defaultDiagonalEdgeUpLabel = new Label('/', '/');
private final Label defaultVerticalEdgeLabel = new Label('|', '|');
private Map<Point, Label> fixedLowerFieldLabels;
/**
* Creates a view for the specified board.
*
* @param board the board
*/
public HexBoardTextView(HexBoard<F, C, E, A> board) {
this.fixedLowerFieldLabels = new HashMap<>();
this.board = board;
}
/**
* Sets the lower field label for the specified field.
*
* @param field the field
* @param label the label
* @throws IllegalArgumentException if arguments are null or if the field does
* not exist
*/
public void setLowerFieldLabel(Point field, Label label) {
if (field == null || label == null || !board.hasField(field)) {
throw new IllegalArgumentException("Argument(s) must not be null and field must exist.");
}
fixedLowerFieldLabels.put(field, label);
}
/**
* Returns a label to be used as label for the edge. This method is called to
* determine the label for this edge.
*
* @param e edge data object
* @return the label
*/
protected Label getEdgeLabel(E e) {
return deriveLabelFromToStringRepresentation(e);
}
/**
* Returns a label to be used as label for the corner. This method is called to
* determine the label for this corner.
*
* @param c corner data object
* @return the label
*/
protected Label getCornerLabel(C c) {
return deriveLabelFromToStringRepresentation(c);
}
/**
* Returns a label to be used as upper label for the field. This method is
* called to determine the upper label for this field.
*
* @param f field data object
* @return the label
*/
protected Label getFieldLabelUpper(F f) {
return deriveLabelFromToStringRepresentation(f);
}
/**
* Returns a label to be used as lower label for the field at this position.
* This method is called to determine the lower label for this field.
*
* @param p location of the field
* @return the label
*/
private Label getFieldLabelLower(Point p) {
Label l = this.fixedLowerFieldLabels.get(p);
l = l == null ? emptyLabel : l;
return l;
}
private Label deriveLabelFromToStringRepresentation(Object o) {
Label label = emptyLabel;
if (o.toString().length() > 0) {
String s = o.toString();
if (s.length() > 1) {
return new Label(s.charAt(0), s.charAt(1));
} else {
return new Label(s.charAt(0), ' ');
}
}
return label;
}
/**
* <p>
* This method returns a single-line string with all corners and field
* annotations for a given y-coordinate. It produces the string by iterating
* over corner positions and appending per corner:
* </p>
* <p>
* "(CL) NE NW " for y%3==1 "(CL) SE SW " for y%3==0
* </p>
* <p>
* Corners/labels that do not exist are replaced by spaces.
* </p>
*/
private String printCornerLine(int y) {
StringBuilder cornerLine = new StringBuilder("");
int offset = 0;
if (y % 2 != 0) {
cornerLine.append(NINE_SPACES);
offset = 1;
}
for (int x = offset; x <= board.getMaxCoordinateX(); x = x + 2) {
Point p = new Point(x, y);
Label cornerLabel;
// handle corner labels for corners other than north and south corners
Point center;
Label first = null;
Label second = null;
switch (y % 3) {
case 0:
center = new Point(x + 1, y - 1);
first = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x - 1, center.y + 1)));
second = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x + 1, center.y + 1)));
break;
case 1:
center = new Point(x + 1, y + 1);
first = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x - 1, center.y - 1)));
second = this.getAnnotationLabel(
board.getFieldAnnotation(center, new Point(center.x + 1, center.y - 1)));
break;
default:
throw new IllegalArgumentException("Not a corner line");
}
if (board.hasCorner(p)) {
cornerLabel = board.getCorner(p) != null ? getCornerLabel(board.getCorner(p)) : emptyLabel;
cornerLine.append("(").append(cornerLabel.getFirst()).append(cornerLabel.getSecond()).append(")");
} else {
cornerLine.append(FOUR_SPACES);
}
cornerLine.append(ONE_SPACE).append(first.getFirst()).append(first.getSecond());
cornerLine.append(FIVE_SPACES).append(second.getFirst()).append(second.getSecond()).append(TWO_SPACES);
}
return cornerLine.toString();
}
private Label getAnnotationLabel(A annotation) {
if (annotation == null) {
return emptyLabel;
} else {
return deriveLabelFromToStringRepresentation(annotation);
}
}
private String printMiddlePartOfField(int y) {
boolean isOffsetRow = (y - 2) % 6 == 0;
StringBuilder lower = new StringBuilder(isOffsetRow ? NINE_SPACES : "");
StringBuilder upper = new StringBuilder(isOffsetRow ? NINE_SPACES : "");
int xstart = isOffsetRow ? 2 : 1;
for (int x = xstart; x <= board.getMaxCoordinateX() + 1; x = x + 2) {
Point edgeStart = new Point(x - 1, y - 1);
Point edgeEnd = new Point(x - 1, y + 1);
Label l = this.emptyLabel;
if (board.hasEdge(edgeStart, edgeEnd)) {
E edge = board.getEdge(edgeStart, edgeEnd);
if (edge != null) {
l = this.getEdgeLabel(edge);
} else {
l = this.defaultVerticalEdgeLabel;
}
}
Point center = new Point(x, y);
boolean hasFieldWithData = board.hasField(center) && board.getField(center) != null;
Label lowerFieldLabel = hasFieldWithData ? getFieldLabelLower(center) : emptyLabel;
Label upperFieldLabel = hasFieldWithData ? getFieldLabelUpper(board.getField(center))
: emptyLabel;
lower.append(ONE_SPACE).append(l.getFirst()).append(l.getSecond()).append(SEVEN_SPACES);
lower.append(lowerFieldLabel.getFirst()).append(lowerFieldLabel.getSecond()).append(SIX_SPACES);
upper.append(ONE_SPACE).append(l.getFirst()).append(l.getSecond()).append(SEVEN_SPACES);
upper.append(upperFieldLabel.getFirst()).append(upperFieldLabel.getSecond()).append(SIX_SPACES);
}
return upper + System.lineSeparator() + lower;
}
private String printDiagonalEdges(int y) {
StringBuilder builder = new StringBuilder();
Point edgeStart;
Point edgeEnd;
Label annotation = null;
Label l;
boolean isDown = y % 6 == 0;
builder.append(" ");
for (int x = 0; x <= board.getMaxCoordinateX(); x = x + 1) {
if (isDown) {
edgeStart = new Point(x, y);
edgeEnd = new Point(x + 1, y + 1);
annotation = getAnnotationLabel(board.getFieldAnnotation(new Point(x + 1, y - 1), new Point(x + 1, y + 1)));
} else {
edgeStart = new Point(x, y + 1);
edgeEnd = new Point(x + 1, y);
annotation = getAnnotationLabel(board.getFieldAnnotation(new Point(x + 1, y + 2), new Point(x + 1, y)));
}
l = determineEdgeLabel(isDown, edgeStart, edgeEnd);
if (!isDown) {
builder.append(TWO_SPACES + l.getFirst() + l.getSecond() + TWO_SPACES + annotation.getFirst() + annotation.getSecond());
} else {
builder.append(TWO_SPACES + l.getFirst() + l.getSecond() + TWO_SPACES + annotation.getFirst() + annotation.getSecond());
}
isDown = !isDown;
}
return builder.toString();
}
private Label determineEdgeLabel(boolean isDown, Point edgeStart, Point edgeEnd) {
Label l;
if (board.hasEdge(edgeStart, edgeEnd)) {
// does it have data associated with it?
if (board.getEdge(edgeStart, edgeEnd) != null) {
l = this.getEdgeLabel(board.getEdge(edgeStart, edgeEnd));
} else {
// default visualization
l = isDown ? this.defaultDiagonalEdgeDownLabel : this.defaultDiagonalEdgeUpLabel;
}
} else {
l = this.emptyLabel;
}
return l;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int y = 0; y <= board.getMaxCoordinateY(); y = y + 3) {
sb.append(printCornerLine(y));
sb.append(System.lineSeparator());
sb.append(printDiagonalEdges(y));
sb.append(System.lineSeparator());
sb.append(printCornerLine(y + 1));
sb.append(System.lineSeparator());
sb.append(printMiddlePartOfField(y + 2));
sb.append(System.lineSeparator());
}
return sb.toString();
}
}

View File

@ -0,0 +1,50 @@
package ch.zhaw.hexboard;
/**
* This class defines a label composed of two characters.
*
* @author tebe
*
*/
public final class Label {
public static final char DEFAULT_CHARACTER = ' ';
private final char first;
private final char second;
/**
* Creates a label from two characters.
*
* @param firstChar first character
* @param secondChar second character
*/
public Label(char firstChar, char secondChar) {
first = firstChar;
second = secondChar;
}
/**
* Creates a label using the default character {@link #DEFAULT_CHARACTER}.
*/
public Label() {
first = ' ';
second = ' ';
}
public char getFirst() {
return first;
}
public char getSecond() {
return second;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "" + first + second;
}
}

View File

@ -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);
}
}

View File

@ -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
* <p></p>
* <p>DO NOT MODIFY THIS CLASS</p>
*
* @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<Point, Config.Land> 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<Config.Faction, List<Config.Resource>> expectd = ThreePlayerStandard.INITIAL_DICE_THROW_PAYOUT.get(diceValue);
Map<Config.Faction, List<Config.Resource>> 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<Config.Faction, Map<Config.Resource, Integer>> 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<Config.Faction, Map<Config.Resource, Integer>> 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<Config.Resource, Integer> 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());
}
}

View File

@ -0,0 +1,42 @@
package ch.zhaw.catan;
public class Tuple<X, Y> {
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<X, Y> otherCasted = (Tuple<X, Y>) 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;
}
}

View File

@ -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).
* <br>
* The basic game situations upon which all other situations that can be retrieved are based is
* the following:
* <pre>
* ( ) ( ) ( ) ( )
* // \\ // \\ // \\ // \\
* ( ) ( ) ( ) ( ) ( )
* || ~~ || ~~ || ~~ || ~~ ||
* || || || || ||
* ( ) ( ) ( ) ( ) ( )
* // \\ // \\ // \\ // \\ // \\
* ( ) ( ) ( ) (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) ( ) ( )
* || ~~ || ~~ || ~~ || ~~ ||
* || || || || ||
* ( ) ( ) ( ) ( ) ( )
* \\ // \\ // \\ // \\ //
* ( ) ( ) ( ) ( )
* </pre>
* Resource cards after the setup phase:
* <ul>
* <li>Player 1: WOOL BRICK</li>
* <li>Player 2: WOOL WOOL</li>
* <li>Player 3: BRICK</li>
* </ul>
* <p>The main ideas for this setup were the following:</p>
* <ul>
* <li>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.</li>
* <li>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.</li>
* <li>There is a settlement next to water and the owner has access to resource types required to build roads</li>
* <li>The initial resource card stock of each player does not allow to build anything without getting
* additional resources first</li>
* </ul>
*
* @author tebe
*/
public class ThreePlayerStandard {
public final static int NUMBER_OF_PLAYERS = 3;
public static final Map<Config.Faction, Tuple<Point, Point>> 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<Config.Faction, Tuple<Point, Point>> 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<Config.Faction, Map<Config.Resource, Integer>> 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<Config.Faction, Map<Config.Resource, Integer>> 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<Config.Faction, Map<Config.Resource, Integer>> 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<Integer, Map<Config.Faction, List<Resource>>> 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, Integer> 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<Point> 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:
* <ul>
* <li>LUMBER: 1</li>
* <li>BRICK: 2</li>
* <li>GRAIN: 3</li>
* <li>ORE: 13</li>
* <li>WOOL: 0</li>
* </ul>
*
* The stocks of the players should contain:
* <br>
* Player 1:
* <ul>
* <li>LUMBER: 9</li>
* <li>BRICK: 9</li>
* <li>GRAIN: 8</li>
* <li>ORE: 7</li>
* <li>WOOL: 9</li>
* </ul>
* Player 2:
* <ul>
* <li>LUMBER: 0</li>
* <li>BRICK: 0</li>
* <li>GRAIN: 8</li>
* <li>ORE: 0</li>
* <li>WOOL: 10</li>
* </ul>
* Player 3:
* <ul>
* <li>LUMBER: 9</li>
* <li>BRICK: 8</li>
* <li>GRAIN: 0</li>
* <li>ORE: 0</li>
* <li>WOOL: 0</li>
* </ul>
*
* @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.
*
* <p></p>
* <p>The game board should look as follows:
* <pre>
* ( ) ( ) ( ) ( )
* // \\ // \\ // \\ // \\
* ( ) ( ) ( ) ( ) ( )
* || ~~ || ~~ || ~~ || ~~ ||
* || || || || ||
* ( ) ( ) ( ) ( ) ( )
* // \\ // \\ // \\ // \\ // \\
* ( ) ( ) ( ) (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) ( ) ( )
* || ~~ || ~~ || ~~ || ~~ ||
* || || || || ||
* ( ) ( ) ( ) ( ) ( )
* \\ // \\ // \\ // \\ //
* ( ) ( ) ( ) ( )
* </pre>
* <p>
* And the player resource card stocks:
* <br>
* Player 1:
* <ul>
* <li>LUMBER: 6</li>
* <li>BRICK: 6</li>
* <li>GRAIN: 1</li>
* <li>ORE: 11</li>
* <li>WOOL: 1</li>
* </ul>
* Player 2:
* <ul>
* <li>LUMBER: 0</li>
* <li>BRICK: 0</li>
* <li>GRAIN: 0</li>
* <li>ORE: 0</li>
* <li>WOOL: 2</li>
* </ul>
* Player 3:
* <ul>
* <li>LUMBER: 6</li>
* <li>BRICK: 6</li>
* <li>GRAIN: 1</li>
* <li>ORE: 0</li>
* <li>WOOL: 1</li>
* </ul>
*
* @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<numberOfTimes; i++) {
model.throwDice(diceValue);
}
return model;
}
/**
* Returns a siedler game after building four additional roads and two
* settlements after the setup phase with the resource cards and roads
* for player one ready to build a fifth settlement at {@link #PLAYER_ONE_READY_TO_BUILD_FIFTH_SETTLEMENT_FIFTH_SETTLEMENT_POSITION}
*<p></p>
*<p>The game board should look as follows:
* <pre>
* ( ) ( ) ( ) ( )
* // \\ // \\ // \\ // \\
* ( ) ( ) ( ) ( ) ( )
* || ~~ || ~~ || ~~ || ~~ ||
* || || || || ||
* ( ) ( ) ( ) ( ) ( )
* // \\ // \\ // \\ // \\ // \\
* ( ) ( ) (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) ( ) ( )
* || ~~ || ~~ || ~~ || ~~ ||
* || || || || ||
* ( ) ( ) ( ) ( ) ( )
* \\ // \\ // \\ // \\ //
* ( ) ( ) ( ) ( )
*
* </pre>
* <br>
* <p>And the player resource card stocks:</p>
* <br>
* Player 1:
* <ul>
* <li>LUMBER: 3</li>
* <li>BRICK: 3</li>
* <li>GRAIN: 2</li>
* <li>ORE: 0</li>
* <li>WOOL: 2</li>
* </ul>
* Player 2:
* <ul>
* <li>LUMBER: 0</li>
* <li>BRICK: 0</li>
* <li>GRAIN: 0</li>
* <li>ORE: 0</li>
* <li>WOOL: 5</li>
* </ul>
* Player 3:
* <ul>
* <li>LUMBER: 0</li>
* <li>BRICK: 1</li>
* <li>GRAIN: 0</li>
* <li>ORE: 0</li>
* <li>WOOL: 0</li>
* </ul>
*
* @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<Point> roads) {
buildRoad(model, roads);
assertTrue(model.buildSettlement(position));
}
private static void buildRoad(SiedlerGame model, List<Point> roads) {
for (int i = 0; i < roads.size()-1; i++) {
assertTrue(model.buildRoad(roads.get(i), roads.get(i + 1)));
}
}
}

View File

@ -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.*;
/***
* <p>
* This class performs tests for the class {@link Edge}.
* </p>
*
* @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)));
}
}

View File

@ -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;
/***
* <p>
* Tests for the class {@link HexBoard}.
* </p>
* @author tebe
*/
public class HexBoardTest {
private HexBoard<String, String, String, String> board;
private Point[] corner;
/**
* Setup for a test - Instantiates a board and adds one field at (7,5).
*
* <pre>
* 0 1 2 3 4 5 6 7 8
* | | | | | | | | | ...
*
* 0----
*
* 1----
*
* 2----
*
* 3---- C
* / \
* 4---- C C
*
* 5---- | F | ...
*
* 6---- C C
* \ /
* 7---- C
* </pre>
*/
@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)));
}
}