de/joshuagleitze/tilinggame

Board.java

Download file
package 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} &lt;
     *            {@code positions.length - 1}: {@code positions[i]} denotes a
     *            neighbor of {@code positions[i + 1]}
     *            <li>for each {@code j} with 0 ≤ {@code j} &lt;
     *            {@code positions.length - 1}:
     *            {@code positions[j] != positions[j + 1]}
     *            <li>for each {@code k} with 0 ≤ {@code k} &lt;
     *            {@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;
    }
}