Add files via upload
This commit is contained in:
committed by
GitHub Enterprise
parent
e9a9181cd0
commit
f2fe06f720
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package ch.zhaw.hexboard;
|
||||
|
||||
import java.awt.Point;
|
||||
|
||||
/**
|
||||
* This class models an edge on @see ch.zhaw.hexboard.HexBoard.
|
||||
* <p>
|
||||
* Edges are non-directional and can be created by providing the two points that
|
||||
* span an edge on the hex-grid defined by @see ch.zhaw.hexboard.HexBoard
|
||||
* </p>
|
||||
* @author tebe
|
||||
*
|
||||
*/
|
||||
final class Edge {
|
||||
private Point start;
|
||||
private Point end;
|
||||
|
||||
/**
|
||||
* Creates an edge between the two points.
|
||||
*
|
||||
* @param p1 first point
|
||||
* @param p2 second point
|
||||
* @throws IllegalArgumentException if the points are not non-null or not a
|
||||
* valid point for an edge on the grid defined
|
||||
* by @see ch.zhaw.hexboard.HexBoard
|
||||
*/
|
||||
public Edge(Point p1, Point p2) {
|
||||
if (Edge.isEdge(p1, p2)) {
|
||||
if (p1.x > p2.x || (p1.x == p2.x && p1.y > p2.y)) {
|
||||
this.start = new Point(p2);
|
||||
this.end = new Point(p1);
|
||||
} else {
|
||||
this.start = new Point(p1);
|
||||
this.end = new Point(p2);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Coordinates " + p1 + " and " + p2 + " are not coordinates of an edge.");
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isEdge(Point p1, Point p2) {
|
||||
boolean isEdge = false;
|
||||
if (p1 != null && p2 != null && HexBoard.isCornerCoordinate(p1)
|
||||
&& HexBoard.isCornerCoordinate(p2)) {
|
||||
int xdistance = Math.abs(p1.x - p2.x);
|
||||
int ydistance = Math.abs(p1.y - p2.y);
|
||||
boolean isVerticalEdge = xdistance == 0 && ydistance == 2;
|
||||
boolean isDiagonalEdge = xdistance == 1 && ydistance == 1;
|
||||
isEdge = isVerticalEdge || isDiagonalEdge;
|
||||
}
|
||||
return isEdge;
|
||||
}
|
||||
|
||||
public boolean isEdgePoint(Point p1) {
|
||||
return start.equals(p1) || end.equals(p1);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((end == null) ? 0 : end.hashCode());
|
||||
result = prime * result + ((start == null) ? 0 : start.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
|
||||
}
|
||||
Edge other = (Edge) obj;
|
||||
if (end == null) {
|
||||
if (other.end != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!end.equals(other.end)) {
|
||||
return false;
|
||||
}
|
||||
if (start == null) {
|
||||
if (other.start != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!start.equals(other.start)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Edge [start=" + start + ", end=" + end + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package ch.zhaw.hexboard;
|
||||
|
||||
import java.awt.Point;
|
||||
|
||||
/**
|
||||
* This class models an annotation for the hex-fields of the hex-grid defined
|
||||
* by @see ch.zhaw.hexboard.HexBoard
|
||||
*
|
||||
* @author tebe
|
||||
*
|
||||
*/
|
||||
final class FieldAnnotationPosition {
|
||||
private Point field;
|
||||
private Point corner;
|
||||
|
||||
/**
|
||||
* Creates a field annotation for the specified field.
|
||||
*
|
||||
* @param field the field to be annotated
|
||||
* @param corner the location of the annotation
|
||||
* @throws IllegalArgumentException if arguments are null or not valid
|
||||
* field/corner coordinates (@see
|
||||
* ch.zhaw.hexboard.HexBoard).
|
||||
*/
|
||||
public FieldAnnotationPosition(Point field, Point corner) {
|
||||
if (HexBoard.isCorner(field, corner)) {
|
||||
this.field = field;
|
||||
this.corner = corner;
|
||||
} else {
|
||||
throw new IllegalArgumentException("" + field + " is not a field coordinate or " + corner
|
||||
+ " is not a corner of the field.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the provided coordinate matches the position of the annotation
|
||||
* within the field.
|
||||
*
|
||||
* @param p the corner coordinate
|
||||
* @return true, if they match
|
||||
*/
|
||||
public boolean isCorner(Point p) {
|
||||
return corner.equals(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the provided coordinate matches the field coordinate of this
|
||||
* annotation.
|
||||
*
|
||||
* @param p a field coordinate
|
||||
* @return true, if they match
|
||||
*/
|
||||
public boolean isField(Point p) {
|
||||
return field.equals(p);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((field == null) ? 0 : field.hashCode());
|
||||
result = prime * result + ((corner == null) ? 0 : corner.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
FieldAnnotationPosition other = (FieldAnnotationPosition) obj;
|
||||
if (field == null) {
|
||||
if (other.field != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!field.equals(other.field)) {
|
||||
return false;
|
||||
}
|
||||
if (corner == null) {
|
||||
if (other.corner != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!corner.equals(other.corner)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FieldAnnotationPosition [field=" + field + ", corner=" + corner + "]";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user