Генератор плат для реализации Food Chain Magnate
Я реализую логику настольной игры на Java, чтобы практиковать свои навыки программирования. Я выбрал Food Chain Magnate, потому что это довольно сложная игра, требующая разных структур данных. Одна из моих первых задач - создать структуру данных для игрового поля. В игре доска построена с использованием некоторых (всего, в зависимости от количества игроков) из 20 доступных плиток. Каждая плитка представляет собой сетку 5 х 5 квадратов. Идентичность каждой плитки не важна во время игры, только если какой-то квадрат находится в другой плитке, чем другой.
Я создал Board
класс, который в основном представляет собой оболочку над массивом 2D-объектов с дополнительными методами для вычислений, а также класс, BoardGenerator
который создает Board
и инициализирует его содержимым различных плиток.
Board.java
package com.lartkma.fcm.model.board;
public class Board {
public static final int TILE_SIZE = 5;
public static final Object OFF_LIMIT = new Object();
private Object[][] boardSquares;
public Board(int widthTiles, int heightTiles) {
this.boardSquares = new Object[widthTiles * TILE_SIZE][heightTiles * TILE_SIZE];
}
public Object get(int x, int y) {
if (x >= 0 && x < this.boardSquares.length && y >= 0 && y < this.boardSquares[0].length) {
return this.boardSquares[x][y];
} else {
return OFF_LIMIT;
}
}
public Object get(Point p) {
return get(p.x(), p.y());
}
public void set(int x, int y, Object obj) {
if (x >= 0 && x < this.boardSquares.length && y >= 0 && y < this.boardSquares[0].length) {
this.boardSquares[x][y] = obj;
} else {
throw new IndexOutOfBoundsException("Point " + new Point(x, y) + " is out of the board");
}
}
public void set(Point p, Object obj) {
set(p.x(), p.y(), obj);
}
public int getWidth() {
return this.boardSquares.length;
}
public int getHeight() {
return this.boardSquares[0].length;
}
/**
* Returns the tile where the square belongs, relative to this board. The value
* is not related to the original tile used to build the board, only allows to
* differentiate one tile from another.
* @param p
* @return
*/
public int getTileNumber(Point p) {
return (p.y() / TILE_SIZE) * (this.boardSquares.length / TILE_SIZE) + (p.x() / TILE_SIZE);
}
}
Point
Класс является простым, неизменным 2D класса точки с конструктором Point(int x, int y)
, методами x()
, y()
для извлечения и add(int dx, int dy)
методом , который возвращает точку (х +, у + Dy). Я пишу это здесь не для того, чтобы сосредоточиться на других классах.
BoardGenerator.java
package com.lartkma.fcm.model.board;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Stream;
public class BoardGenerator {
public static Board fromRandom(int widthTiles, int heightTiles) {
Random rnd = new Random();
Map<Character, Object[][]> tileList = getTileList();
List<Character> randomTiles = new LinkedList<>(tileList.keySet());
Collections.shuffle(randomTiles);
Board board = new Board(widthTiles, heightTiles);
for (int i = 0; i < widthTiles; i++) {
for (int j = 0; j < heightTiles; j++) {
fillWithTile(board, tileList.get(randomTiles.get(i * heightTiles + j)), i * Board.TILE_SIZE,
j * Board.TILE_SIZE, rnd.nextInt(4));
}
}
return board;
}
/**
* Generates a board using the tiles and rotations indicated in the expression.
*
* The expression is composed of (# tiles tall) subexpressions separated by
* newlines or spaces, each subexpression made of (# tiles wide x 2) characters.
*
* Each 2 characters of a subexpression describe a tile and the rotation of such
* tile. The tile is indicated with one of the upper-case characters used in
* <a href="https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx">this page</a>.
* The rotation is described as a digit from 1 to 4, where 1 is the orientation shown in
* the page mentioned above (with the identified in the bottom left), 2 rotates the
* reference orientation rotated 90 degrees clockwise, and so on.
*
* @param expression
* @return
*/
public static Board fromExpression(String expression) {
String[] rows = expression.split("\n|\r\n| ");
int heightTiles = rows.length;
int widthTiles = Stream.of(rows).mapToInt(s -> s.length() / 2).max().orElse(0);
Board board = new Board(widthTiles, heightTiles);
Map<Character, Object[][]> tileList = getTileList();
for (int i = 0; i < widthTiles; i++) {
for (int j = 0; j < heightTiles; j++) {
if (2 * i + 1 < rows[rows.length - 1 - j].length()) {
char tileId = rows[rows.length - 1 - j].charAt(2 * i);
char tileRotationFactor = rows[rows.length - 1 - j].charAt(2 * i + 1);
if (tileList.containsKey(tileId) && tileRotationFactor >= '1' && tileRotationFactor <= '4') {
// Number of rotations goes from 0 to 3
fillWithTile(board, tileList.get(tileId), i * Board.TILE_SIZE, j * Board.TILE_SIZE,
tileRotationFactor - '1');
} else {
throw new IllegalArgumentException(
"Board tile expression \"" + tileId + tileRotationFactor + "\" cannot be read");
}
}
}
}
return board;
}
private static Map<Character, Object[][]> getTileList() {
Map<Character, Object[][]> outputMap = new HashMap<>();
try (BufferedReader stream = new BufferedReader(
new InputStreamReader(BoardGenerator.class.getResourceAsStream("tiles.txt")))) {
int lineCount = 1;
Object[][] currentTileContent = new Object[Board.TILE_SIZE][Board.TILE_SIZE];
char currentTileIdentifier = 'A';
String currentLine;
while ((currentLine = stream.readLine()) != null) {
for (int i = 0; i < Board.TILE_SIZE; i++) {
char lineChar = currentLine.charAt(i);
if (lineChar == 'O') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = null;
} else if (lineChar == '-') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, false, true);
} else if (lineChar == '|') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, true, false);
} else if (lineChar == '/') {
// check the previous and next squares in the same line to check if this is
// a up-to-right turn or a right-to-up turn
char previous = (i == 0 ? 'O' : currentLine.charAt(i - 1));
char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
if ((isHorizontalRoad(previous) || i == 0) && !isHorizontalRoad(next)) {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, false,
true);
} else if (!isHorizontalRoad(previous)
&& (isHorizontalRoad(next) || i == Board.TILE_SIZE - 1)) {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, true,
false);
} else {
throw new IllegalStateException("Unknown combination on ( " + currentLine + ")");
}
} else if (lineChar == '\\') {
// check the previous and next squares in the same line to check if this is
// a up-to-left turn or a left-to-up turn
char previous = (i == 0 ? 'O' : currentLine.charAt(i - 1));
char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
if ((isHorizontalRoad(previous) || i == 0) && !isHorizontalRoad(next)) {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, false, true,
true);
} else if (!isHorizontalRoad(previous)
&& (isHorizontalRoad(next) || i == Board.TILE_SIZE - 1)) {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, false,
false);
} else {
throw new IllegalStateException("Unknown combination on ( " + currentLine + ")");
}
} else if (lineChar == '^') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, false, true);
} else if (lineChar == '>') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, true, false);
} else if (lineChar == 'V') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, true, true);
} else if (lineChar == '<') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, true, true);
} else if (lineChar == '+') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, true, true);
} else if (lineChar == 'S') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.SODA;
} else if (lineChar == 'B') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.BEER;
} else if (lineChar == 'L') {
currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.LEMONADE;
} else if (lineChar >= '0' && lineChar <= '9') {
Object previous = (i == 0 ? null : currentTileContent[i - 1][Board.TILE_SIZE - lineCount]);
if (previous instanceof House) {
// part of a two-digit house, same entity as left
currentTileContent[i][Board.TILE_SIZE
- lineCount] = currentTileContent[i - 1][Board.TILE_SIZE - lineCount];
} else {
int houseOrder = (lineChar - '0'); // classic
char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
if (next >= '0' && next <= '9') { // two digit id
houseOrder = houseOrder * 10 + (next - '0');
}
currentTileContent[i][Board.TILE_SIZE - lineCount] = new House(houseOrder);
}
} else if (lineChar == 'H') {
Object previous = (i == 0 ? null : currentTileContent[i - 1][Board.TILE_SIZE - lineCount]);
if (previous instanceof House) {
// same entity as left
currentTileContent[i][Board.TILE_SIZE - lineCount] = previous;
} else {
previous = (lineCount == 1 ? null
: currentTileContent[i][Board.TILE_SIZE - lineCount + 1]);
if (previous instanceof House) {
// same entity as up
currentTileContent[i][Board.TILE_SIZE - lineCount] = previous;
} else {
throw new IllegalStateException(
"Unknown combination on ( " + currentLine + "): no house defined near H");
}
}
} else {
throw new IllegalStateException("Unknown symbol: " + lineChar);
}
}
lineCount += 1;
if (lineCount > Board.TILE_SIZE) {
outputMap.put(currentTileIdentifier, currentTileContent);
lineCount = 1;
currentTileContent = new Object[Board.TILE_SIZE][Board.TILE_SIZE];
currentTileIdentifier += 1;
}
}
return outputMap;
} catch (IOException e) {
throw new Error("tiles.txt not available", e);
}
}
private static boolean isHorizontalRoad(char c) {
return c == '-' || c == '/' || c == '\\' || c == '^' || c == 'V' || c == '+';
}
private static void fillWithTile(Board board, Object[][] tileArray, int xStart, int yStart, int numRotations) {
for (int i = 0; i < Board.TILE_SIZE; i++) {
for (int j = 0; j < Board.TILE_SIZE; j++) {
Point boardPoint = new Point(xStart + i, yStart + j);
Point tileCoords = toTileCoords(i, j, numRotations);
Object inTile = tileArray[tileCoords.x()][tileCoords.y()];
if (inTile instanceof House) {
Object prevHouse;
if ((prevHouse = board.get(boardPoint.add(-1, 0))) instanceof House
&& ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
// check house at the left
board.set(boardPoint, prevHouse);
} else if ((prevHouse = board.get(boardPoint.add(0, -1))) instanceof House
&& ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
// check house below
board.set(boardPoint, prevHouse);
} else {
board.set(boardPoint, new House(((House) inTile).getOrder()));
}
} else if (inTile instanceof Road) {
board.set(boardPoint, ((Road) inTile).rotate(numRotations));
} else if (inTile instanceof GoodsSource || inTile == null) {
board.set(boardPoint, inTile);
} else {
throw new IllegalStateException("Unknown object: " + inTile.getClass());
}
}
}
}
private static Point toTileCoords(int x, int y, int rotations) {
switch (rotations) {
case 0:
return new Point(x, y);
case 1:
return new Point(Board.TILE_SIZE - 1 - y, x);
case 2:
return new Point(Board.TILE_SIZE - 1 - x, Board.TILE_SIZE - 1 - y);
case 3:
return new Point(y, Board.TILE_SIZE - 1 - x);
default:
throw new IllegalArgumentException("Should not happen");
}
}
}
tiles.txt
Файл содержит описание 20 плиток. Здесь можно увидеть содержимое каждой плитки:https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx(не включает плитки расширения). Это простой текстовый файл, состоящий из строк по 5 символов каждая. Каждые 5 строк описывают плитку (5 x 5). Каждой плитке назначается символ, как показано в ссылочной ссылке, это первые 5 строк плитки A, следующие 5 строк B и так далее. Каждый символ (или группа символов) представляет собой объект. Например, плитка E описывается как
/-/OO
|BOOO
/O8H/
OOHH|
OO/-/
(символы /
и \
могут представлять один из двух возможных типов поворотов, в зависимости от контекста)
Road.java
package com.lartkma.fcm.model.board;
import java.util.Arrays;
import java.util.StringJoiner;
public class Road {
private boolean[] canMove;
public Road(boolean canGoUp, boolean canGoRight, boolean canGoDown, boolean canGoLeft) {
this.canMove = new boolean[] { canGoUp, canGoRight, canGoDown, canGoLeft };
}
public boolean canMove(Direction inDirection) {
return this.canMove[inDirection.ordinal()];
}
public Road rotate(int amountRotations) {
Road rotated = new Road(this.canMove[0], this.canMove[1], this.canMove[2], this.canMove[3]);
if (amountRotations < 0) {
// Java operator % returns a remainder, that is different from a mathematical
// modulus
// https://stackoverflow.com/questions/5385024/mod-in-java-produces-negative-numbers
// https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.17.3
amountRotations = amountRotations % rotated.canMove.length + rotated.canMove.length;
} else {
amountRotations = amountRotations % rotated.canMove.length;
}
boolean swapTemp;
for (int k = 0; k < amountRotations; k++) {
for (int i = 1; i < rotated.canMove.length; i++) { // it makes no sense for the first element
swapTemp = rotated.canMove[0];
rotated.canMove[0] = rotated.canMove[i];
rotated.canMove[i] = swapTemp;
}
}
return rotated;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Road) {
return Arrays.equals(this.canMove, ((Road) obj).canMove);
} else {
return false;
}
}
@Override
public String toString() {
StringJoiner name = new StringJoiner("-", "Road[", "]");
for (Direction d : Direction.values()) {
if (canMove(d)) {
name.add(d.name());
}
}
return name.toString();
}
}
Direction
- это перечисление со значениями UP, RIGHT, DOWN, LEFT
(в указанном порядке). House
- это простой класс данных со order
свойством, но он изменяет другие свойства во время игры. GoodsSource
- простой неизменяемый класс, который может иметь только 3 возможных экземпляра.
BoardGeneratorTest.java (для примера того, как он используется)
package com.lartkma.fcm.model.board;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import com.lartkma.fcm.model.board.Board;
import com.lartkma.fcm.model.board.GoodsSource;
import com.lartkma.fcm.model.board.House;
import com.lartkma.fcm.model.board.BoardGenerator;
import com.lartkma.fcm.model.board.Road;
public class BoardGeneratorTest {
@Test
@DisplayName("The board generator can receive specific tiles as parameters and should generate the correct amount of squares")
public void testBoardGeneratorSizeFromExpression() {
Board board = BoardGenerator.fromExpression("G1E2\nI3M4");
assertAll("The board should be of 2 x 2 tiles (10 x 10 squares)",
() -> assertEquals(10, board.getWidth(), "Board width"),
() -> assertEquals(10, board.getHeight(), "Board height"));
}
@Test
@DisplayName("The board generator can generate a random board and should generate the correct amount of squares")
public void testBoardGeneratorSizeFromRandom() {
Board board = BoardGenerator.fromRandom(3, 2);
assertAll("The board should be of 3 x 2 tiles (15 x 10 squares)",
() -> assertEquals(15, board.getWidth(), "Board width"),
() -> assertEquals(10, board.getHeight(), "Board height"));
}
@Test
@DisplayName("The board generator can create a 1-tile board with the correct contents")
public void testBoardGeneratorContent() {
Board board = BoardGenerator.fromExpression("E1");
assertAll("The board should have the following contents",
() -> assertThat("In 0, 0", board.get(0, 0), is(nullValue())),
() -> assertThat("In 1, 0", board.get(1, 0), is(nullValue())),
() -> assertThat("In 2, 0", board.get(2, 0), is(equalTo(new Road(false, true, true, false)))),
() -> assertThat("In 3, 0", board.get(3, 0), is(equalTo(new Road(false, true, false, true)))),
() -> assertThat("In 4, 0", board.get(4, 0), is(equalTo(new Road(true, false, false, true)))),
() -> assertThat("In 0, 1", board.get(0, 1), is(nullValue())),
() -> assertThat("In 1, 1", board.get(1, 1), is(nullValue())),
() -> assertThat("In 2, 1", board.get(2, 1), is(equalTo(new House(8)))),
() -> assertThat("In 3, 1", board.get(3, 1), is(sameInstance(board.get(2, 1)))),
() -> assertThat("In 4, 1", board.get(4, 1), is(equalTo(new Road(true, false, true, false)))),
() -> assertThat("In 0, 2", board.get(0, 2), is(equalTo(new Road(true, false, false, true)))),
() -> assertThat("In 1, 2", board.get(1, 2), is(nullValue())),
() -> assertThat("In 2, 2", board.get(2, 2), is(sameInstance(board.get(2, 1)))),
() -> assertThat("In 3, 2", board.get(3, 2), is(sameInstance(board.get(2, 1)))),
() -> assertThat("In 4, 2", board.get(4, 2), is(equalTo(new Road(false, true, true, false)))),
() -> assertThat("In 0, 3", board.get(0, 3), is(equalTo(new Road(true, false, true, false)))),
() -> assertThat("In 1, 3", board.get(1, 3), is(equalTo(GoodsSource.BEER))),
() -> assertThat("In 2, 3", board.get(2, 3), is(nullValue())),
() -> assertThat("In 3, 3", board.get(3, 3), is(nullValue())),
() -> assertThat("In 4, 3", board.get(4, 3), is(nullValue())),
() -> assertThat("In 0, 4", board.get(0, 4), is(equalTo(new Road(false, true, true, false)))),
() -> assertThat("In 1, 4", board.get(1, 4), is(equalTo(new Road(false, true, false, true)))),
() -> assertThat("In 2, 4", board.get(2, 4), is(equalTo(new Road(true, false, false, true)))),
() -> assertThat("In 3, 4", board.get(3, 4), is(nullValue())),
() -> assertThat("In 4, 4", board.get(4, 4), is(nullValue())));
}
@Test
@DisplayName("The board generator can create a rotated 1-tile board with the correct contents")
public void testBoardGeneratorContentRotated() {
Board board = BoardGenerator.fromExpression("E2");
assertAll("The board should have the following contents",
() -> assertThat("In 0, 0", board.get(0, 0), is(equalTo(new Road(true, true, false, false)))),
() -> assertThat("In 1, 0", board.get(1, 0), is(equalTo(new Road(false, true, false, true)))),
() -> assertThat("In 2, 0", board.get(2, 0), is(equalTo(new Road(false, false, true, true)))),
() -> assertThat("In 3, 0", board.get(3, 0), is(nullValue())),
() -> assertThat("In 4, 0", board.get(4, 0), is(nullValue())),
() -> assertThat("In 0, 1", board.get(0, 1), is(equalTo(new Road(true, false, true, false)))),
() -> assertThat("In 1, 1", board.get(1, 1), is(equalTo(new House(8)))),
() -> assertThat("In 2, 1", board.get(2, 1), is(sameInstance(board.get(1, 1)))),
() -> assertThat("In 3, 1", board.get(3, 1), is(nullValue())),
() -> assertThat("In 4, 1", board.get(4, 1), is(nullValue())),
() -> assertThat("In 0, 2", board.get(0, 2), is(equalTo(new Road(false, false, true, true)))),
() -> assertThat("In 1, 2", board.get(1, 2), is(sameInstance(board.get(1, 1)))),
() -> assertThat("In 2, 2", board.get(2, 2), is(sameInstance(board.get(1, 1)))),
() -> assertThat("In 3, 2", board.get(3, 2), is(nullValue())),
() -> assertThat("In 4, 2", board.get(4, 2), is(equalTo(new Road(true, true, false, false)))),
() -> assertThat("In 0, 3", board.get(0, 3), is(nullValue())),
() -> assertThat("In 1, 3", board.get(1, 3), is(nullValue())),
() -> assertThat("In 2, 3", board.get(2, 3), is(nullValue())),
() -> assertThat("In 3, 3", board.get(3, 3), is(equalTo(GoodsSource.BEER))),
() -> assertThat("In 4, 3", board.get(4, 3), is(equalTo(new Road(true, false, true, false)))),
() -> assertThat("In 0, 4", board.get(0, 4), is(nullValue())),
() -> assertThat("In 1, 4", board.get(1, 4), is(nullValue())),
() -> assertThat("In 2, 4", board.get(2, 4), is(equalTo(new Road(true, true, false, false)))),
() -> assertThat("In 3, 4", board.get(3, 4), is(equalTo(new Road(false, true, false, true)))),
() -> assertThat("In 4, 4", board.get(4, 4), is(equalTo(new Road(false, false, true, true)))));
}
}
Ответы
public static final int TILE_SIZE = 5;
Может быть, назовите это TILE_EDGE_SIZE
или что-то подобное, ведь плитка не содержит 5 квадратов.
public Object get(int x, int y) {
Удалите это, просто используйте get(Point p)
, это не так уж и много.
return OFF_LIMIT;
Это единственное магическое значение мешает вам использовать правильный класс для массива. Возможно, вам стоит избегать магических ценностей. Если вы действительно хотите избежать исключения, используйте Optional<Square>
. Но лично я бы выбросил исключение и использовал бы Optional
вместо возврата null
.
throw new IndexOutOfBoundsException("Point " + new Point(x, y) + " is out of the board");
Я знал, что вы можете это сделать, теперь сделайте методы симметричными в отношении того, как они выходят за рамки.
public int getWidth() {
...
public int getHeight() {
Отлично, нет причин, по которым приложение когда-либо вернется OFF_LIMIT
.
Map<Character, Object[][]> tileList = getTileList();
Смешивать коллекции и массивы - не лучшая идея; просто используйте коллекции.
List<Character> randomTiles = new LinkedList<>(tileList.keySet());
Чтобы быть точным, вы бы использовали ArrayList
здесь, а не связанный список.
Collections.shuffle(randomTiles);
Потому что искать случайные индексы и затем перемещать их в связанном списке - не лучшая идея.
fillWithTile(board, tileList.get(randomTiles.get(i * heightTiles + j)), i * Board.TILE_SIZE, j * Board.TILE_SIZE, rnd.nextInt(4));
Слишком много всего происходит в этом методе, разбейте его на части. Почему это происходит и что происходит? Почему там волшебство 4
, 4 из чего?
if (2 * i + 1 < rows[rows.length - 1 - j].length()) {
Опять же, отсюда мы можем видеть, как дела идут, но не что и почему. Полезный (если неполный) JavaDoc в некоторой степени помогает, но комментарий будет оценен.
private static Map<Character, Object[][]> getTileList() {
Слишком много делается в этом методе, его сложность поражает.
if (lineChar == 'O') { // ... endless else if's
Здесь переключатель творил бы чудеса, но не забывайте break;
утверждения.
currentTileContent[i][Board.TILE_SIZE - lineCount] = null;
Что насчет Object tileContent;
объявления, то установите его в switch
, и в конце назначьте его currentTileContent[i][Board.TILE_SIZE - lineCount]
. Слишком много копирования / вставки, если вы спросите меня.
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, false, true);
Ах, дорога идет false, true, false, true
. Это не true
дорога, используйте enum
вместо логических параметров. Это в Эффективной Java, которую вы должны прочитать.
EnumSet<Direction> possibleDirections = EnumSet.of(Direction.RIGHT, Direction.LEFT);
это намного приятнее, не так ли?
char previous = (i == 0 ? 'O' : currentLine.charAt(i - 1));
char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
if ((isHorizontalRoad(previous) || i == 0) && !isHorizontalRoad(next)) {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, false,
true);
} else if (!isHorizontalRoad(previous)
&& (isHorizontalRoad(next) || i == Board.TILE_SIZE - 1)) {
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, true,
false);
} else {
throw new IllegalStateException("Unknown combination on ( " + currentLine + ")");
}
Возвращается одно значение: a Road
. Может быть, метод? Так легко отличить.
currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.SODA;
Ах, теперь я понял. A Road
, GoodsSource
a House
или ничего не ожидается. Тем не менее, создайте интерфейс маркера, как TileContent
минимум, Road
и GoodsSource
внедрите его, чтобы он вам не понадобился Object
, потому что это слишком некрасиво.
throw new Error("tiles.txt not available", e);
Не до конца читаемый, возможно, лучшее исключение. RuntimeException
следует отдавать предпочтение тому, Error
который обычно не подлежит восстановлению в масштабах всей системы .
} else if ((prevHouse = board.get(boardPoint.add(0, -1))) instanceof House
&& ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
// check house below
board.set(boardPoint, prevHouse);
Итак, вы создаете дома побольше. Я думаю, что могу быть злым и создать дом, состоящий из отдельных частей. Надеюсь, что у вас дома квадратные :) Но правда, опять же, методы предоставьте.
Значения перечислений можно сравнивать напрямую, нет необходимости сравнивать order
на равенство.
throw new IllegalArgumentException("Should not happen");
Я согласен с этим, такое исключение недопустимо.
return this.canMove[inDirection.ordinal()];
Или possibleDirections.contains(inDirection)
(см. Выше?)