Board.java
Download filepackage de.joshuagleitze.tilinggame;
/**
* A game board in a simple tiling game. In this game, 12 {@linkplain Tile
* tiles} can be placed on a board, in columns of 3. Such placements can be
* {@linkplain #isValid valid} or not, and several queries can be performed on
* the board.
*
* <p>
* The tiles on the board are identified by a position index. This index starts
* at 0 at the top left corner of the board. It then counts through the colums.
* The tile below the tile at index 0 thus has index 1.
*
* @author Joshua Gleitze
* @version 1.0
*/
public class Board {
/**
* The board’s size.
*/
private static final int BOARD_SIZE = 12;
/**
* Number of tile in a column.
*/
private static final int COLUMN_LENGTH = 3;
/**
* The tiles on this board. No tile is {@code null}.
*/
private final Tile[] tiles = new Tile[BOARD_SIZE];
/**
* Creates a board with only empty tiles.
*/
public Board() {
for (int position = 0; position < this.tiles.length; position++) {
this.tiles[position] = new Tile();
}
}
/**
* Queries the tile at the provided position.
*
* @param position
* The position of the requested tile. Must satisfy 0 ≤
* {@code position} ≤ 11.
* @return A copy of the tile at the provided {@code position}.
*/
public Tile getTile(final int position) {
return this.tiles[position].copy();
}
/**
* Sets the tile at the provided position to a copy of the provided tile.
*
* @param position
* The position to replace the tile at. Must satisfy 0 ≤
* {@code position} ≤ 11.
* @param newTile
* The tile to place a copy of. Must not be {@code null}.
*/
public void setTile(final int position, final Tile newTile) {
this.tiles[position] = newTile.copy();
}
/**
* Sets the tile at the provided position to an empty tile.
*
* @param position
* The position at which the tile is to be replaced with an empty
* tile. Must satisfy 0 ≤ {@code position} ≤ 11.
*/
public void removeTile(final int position) {
this.tiles[position] = new Tile();
}
/**
* Queries whether all tiles on this board are {@linkplain Tile#isEmpty()
* empty}.
*
* @return {@code true} iff every tile on this board is
* {@linkplain Tile#isEmpty() empty}.
* @see Tile#isEmpty()
*/
public boolean isEmpty() {
for (final Tile tile : this.tiles) {
if (!tile.isEmpty()) {
return false;
}
}
return true;
}
/**
* {@linkplain Tile#rotateClockwise() Rotates} the tile at the provided
* position on the board clockwise.
*
* @param position
* The position of the tile to rotate. Must satisfy 0 ≤
* {@code position} ≤ 11.
* @see Tile#rotateClockwise()
*/
public void rotateTileClockwise(final int position) {
this.tiles[position].rotateClockwise();
}
/**
* {@linkplain Tile#rotateClockwise() Rotates} the tile at the provided
* position on the board counterclockwise.
*
* @param position
* The position of the tile to rotate. Must satisfy 0 ≤
* {@code position} ≤ 11.
* @see Tile#rotateClockwise()
*/
public void rotateTileCounterClockwise(final int position) {
this.tiles[position].rotateCounterClockwise();
}
/**
* Queries the number of different {@linkplain LineType#isColor() colors} on
* this board.
*
* @return The number of different {@linkplain LineType#isColor() colors} of
* which a line can be found on any tile on this board.
*/
public int getNumberOfColors() {
int numberOfColors = 0;
for (final LineType color : LineType.colors()) {
for (final Tile tile : this.tiles) {
if (tile.hasType(color)) {
numberOfColors++;
break;
}
}
}
return numberOfColors;
}
/**
* Queries whether all neighbored tiles on this board are
* {@linkplain Tile#fitsTo(Tile, int) fit to} each other.
*
* @return {@code true} if every pair of neighbored tiles on this board
* {@linkplain Tile#fitsTo(Tile, int) fit to} each other.
*/
public boolean isValid() {
// for every tile, look at half of its neighbors.
for (int position = 0; position < BOARD_SIZE; position++) {
for (int edge = 0; edge < Tile.EDGE_COUNT / 2; edge++) {
final Tile neighbor = this.getNeighborTile(position, edge);
if (neighbor != null && !this.tiles[position].fitsTo(neighbor, edge)) {
return false;
}
}
}
return true;
}
/**
* Queries whether the provided positions form a path of lines with the same
* {@linkplain LineType#isColor() color}. If there is such a path, its
* {@linkplain LineType#isColor() color} is returned.
*
* @param positions
* A list of positions on this board. Must satisfy:
* <ul>
* <li>for each {@code i} with 0 ≤ {@code i} <
* {@code positions.length - 1}: {@code positions[i]} denotes a
* neighbor of {@code positions[i + 1]}
* <li>for each {@code j} with 0 ≤ {@code j} <
* {@code positions.length - 1}:
* {@code positions[j] != positions[j + 1]}
* <li>for each {@code k} with 0 ≤ {@code k} <
* {@code positions.length - 2}:
* {@code positions[j] != positions[j + 2]}
* </ul>
* @return If there is a {@linkplain LineType#isColor() color} {@code c},
* such that every pair of tiles denoted by consecutive positions in
* {@code positions} is connected at edges at which lines of color
* {@code c} meet, {@code c} is returned. Otherwise
* {@link LineType#NONE} is returned.
*/
public LineType getConnectedPathColor(final int[] positions) {
Tile lastTile;
Tile thisTile = this.tiles[positions[0]];
final LineType color = thisTile
.getLineTypeAtIndex(Board.getConnectingEdgeIndex(positions[0], positions[1]));
for (int i = 1; i < positions.length; i++) {
lastTile = thisTile;
thisTile = this.tiles[positions[i]];
final int lastConnectingEdge = Board.getConnectingEdgeIndex(positions[i - 1], positions[i]);
final int thisConnectingEdge = Tile.getOppositeEdgeIndex(lastConnectingEdge);
if (lastTile.getLineTypeAtIndex(lastConnectingEdge) != color
|| thisTile.getLineTypeAtIndex(thisConnectingEdge) != color) {
return LineType.NONE;
}
}
return color;
}
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
for (int row = 0; row < this.tiles.length / COLUMN_LENGTH; row++) {
for (int column = 0; column < COLUMN_LENGTH; column++) {
result.append(this.tiles[row * 3 + column]).append(';');
}
result.append(System.lineSeparator());
}
return result.toString();
}
/**
* Queries the tile that is placed next to the tile at the provided
* position, connected with it at the denoted edge. Only supports edge
* neighbors that are above or right of a tile.
*
* @param position
* A tile to search the neighbor of. Must not be {@code null}.
* @param edgeIndex
* The index of the edge the neighbor is placed at. Must satisfy
* 0 ≤ {@code position} ≤ 2.
* @return The neighbor of the tile at {@code position}, placed at the edge
* denoted by {@code edgeIndex}. {@code null} if there is no such
* neighbor.
*/
private Tile getNeighborTile(final int position, final int edgeIndex) {
final int otherPosition;
if (edgeIndex == 0) { // top neighbor
otherPosition = position - 1;
} else { // righthand neighbor
otherPosition = position + COLUMN_LENGTH + edgeIndex - 2;
}
return otherPosition >= this.tiles.length || otherPosition < 0 ? null : this.tiles[otherPosition];
}
/**
* Queries the edge at which two neighboring tiles are connected.
*
* @param thisTilePosition
* The position of a tile. Must satisfy 0 ≤ {@code position} ≤
* 11.
* @param otherTilePosition
* The position of another tile. Must satisfy 0 ≤
* {@code position} ≤ 11 and must denote a tile that’s a neighbor
* of {@code thisTilePosition}.
* @return The index of the edge of the tile denoted by
* {@code thisTilePosition}, at which the tile denoted by
* {@code otherTilePosition} is placed.
*/
private static int getConnectingEdgeIndex(final int thisTilePosition, final int otherTilePosition) {
final int diff = otherTilePosition - thisTilePosition;
if (diff < 0) { // top or lefthand neighbor, switch roles.
return Tile.getOppositeEdgeIndex(Board.getConnectingEdgeIndex(otherTilePosition, thisTilePosition));
} else if (diff == 1) { // bottom neighbor
return Tile.EDGE_COUNT / 2;
}
// righthand neighbor
return diff - COLUMN_LENGTH + 2;
}
}