Generatore di schede per un'implementazione di Food Chain Magnate
Sto implementando la logica di un gioco da tavolo in Java per esercitare le mie capacità di programmazione. Ho scelto Food Chain Magnate perché è un gioco abbastanza complesso che richiede strutture dati differenti. Uno dei miei primi compiti è generare la struttura dei dati per il tabellone di gioco. Nel gioco, il tabellone viene costruito utilizzando alcune (tra tutte, a seconda del numero di giocatori) delle 20 tessere disponibili. Ogni tessera è una griglia di 5 x 5 quadrati. L'identità di ogni tessera non è importante durante il gioco, solo se una casella è in una tessera diversa da un'altra.
Ho creato una Board
classe che è fondamentalmente un wrapper su un array di oggetti 2D con metodi aggiuntivi per il calcolo, e una BoardGenerator
che crea se Board
la inizializza con il contenuto delle diverse tessere.
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);
}
}
La Point
classe è una semplice, immutabile classe punto 2D con un costruttore Point(int x, int y)
, metodi x()
, y()
per il recupero e un add(int dx, int dy)
metodo che restituisce il punto (x + dx, y + dy). Non lo sto scrivendo qui per concentrarmi sulle altre classi.
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");
}
}
}
Il tiles.txt
file contiene la descrizione delle 20 tessere. Il contenuto di ogni tessera può essere visto qui:https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx(non include le tessere di espansione). È un file di testo semplice formato da righe di 5 caratteri ciascuna. Ogni 5 righe descrive una tessera (5 x 5). Ad ogni tessera è assegnato un carattere come mostrato nel link di riferimento, essendo le prime 5 linee tessera A, le successive 5 tessere B e così via. Ogni carattere (o gruppo di caratteri) rappresenta un oggetto. Ad esempio, la tessera E è descritta come
/-/OO
|BOOO
/O8H/
OOHH|
OO/-/
(caratteri /
e \
possono rappresentare uno dei due possibili tipi di turni, a seconda del contesto)
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
è un'enumerazione con valori UP, RIGHT, DOWN, LEFT
(in quest'ordine). House
è una semplice classe di dati con una order
proprietà, ma cambierà altre proprietà durante il gioco. GoodsSource
è una classe semplice e immutabile che può avere solo 3 possibili istanze.
BoardGeneratorTest.java (per un esempio di come viene utilizzato)
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)))));
}
}
Risposte
public static final int TILE_SIZE = 5;
Forse chiamalo TILE_EDGE_SIZE
o qualcosa di simile, poiché una tessera non contiene 5 quadrati.
public Object get(int x, int y) {
Rimuovilo, usalo get(Point p)
, non è più un lavoro.
return OFF_LIMIT;
Questo singolo valore magico ti impedisce di usare la classe corretta per l'Array. I valori magici sono qualcosa che potresti voler evitare. Se vuoi davvero evitare un'eccezione, usa Optional<Square>
. Ma personalmente vorrei lanciare un'eccezione e usarla Optional
invece di tornare null
.
throw new IndexOutOfBoundsException("Point " + new Point(x, y) + " is out of the board");
Sapevo che potevi farlo, ora rendi i metodi simmetrici su come gestiscono i fuori limite.
public int getWidth() {
...
public int getHeight() {
Perfetto, nessun motivo per cui l'applicazione sarebbe mai tornata OFF_LIMIT
.
Map<Character, Object[][]> tileList = getTileList();
Mescolare raccolte e array non è una buona idea; usa solo le raccolte.
List<Character> randomTiles = new LinkedList<>(tileList.keySet());
Per essere precisi, useresti ArrayList
qui, non un elenco collegato.
Collections.shuffle(randomTiles);
Perché cercare indici casuali e poi spostarli in un elenco collegato non è una buona idea.
fillWithTile(board, tileList.get(randomTiles.get(i * heightTiles + j)), i * Board.TILE_SIZE, j * Board.TILE_SIZE, rnd.nextInt(4));
Troppe cose stanno succedendo in questo metodo, dividerlo. Perché sta succedendo e cosa sta facendo? Perché c'è una magia 4
lì dentro, 4 di cosa?
if (2 * i + 1 < rows[rows.length - 1 - j].length()) {
Di nuovo, da qui possiamo vedere come vengono fatte le cose, ma non cosa o perché. L'utile (se incompleto) JavaDoc aiuta in qualche modo, ma un commento sarebbe apprezzato.
private static Map<Character, Object[][]> getTileList() {
Troppo viene fatto con questo metodo, la quantità di complessità è sbalorditiva.
if (lineChar == 'O') { // ... endless else if's
Qui un interruttore farebbe miracoli, ma non dimenticare le break;
dichiarazioni.
currentTileContent[i][Board.TILE_SIZE - lineCount] = null;
Che dire della Object tileContent;
dichiarazione, quindi impostala nel switch
, e alla fine assegnala a currentTileContent[i][Board.TILE_SIZE - lineCount]
. Troppo copia / incolla se me lo chiedi.
currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, false, true);
Ah, una strada che va false, true, false, true
. Non è una true
strada, usa enum
invece dei parametri booleani. È in Effective Java, che dovresti leggere.
EnumSet<Direction> possibleDirections = EnumSet.of(Direction.RIGHT, Direction.LEFT);
è davvero molto più carino, non sei d'accordo?
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 + ")");
}
Viene restituito un valore: a Road
. Metodo forse? Così facile da distinguere.
currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.SODA;
Ah, ora ho capito. A Road
, GoodsSource
a House
o niente è previsto. Tuttavia, crea un'interfaccia marker come TileContent
almeno, e disponila Road
e GoodsSource
implementala in modo che non sia necessario Object
, perché è troppo brutto.
throw new Error("tiles.txt not available", e);
Non completamente leggibile è forse un'eccezione migliore. RuntimeException
dovrebbe essere preferito rispetto a quello Error
che comunemente non è recuperabile a livello di sistema .
} else if ((prevHouse = board.get(boardPoint.add(0, -1))) instanceof House
&& ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
// check house below
board.set(boardPoint, prevHouse);
OK, quindi stai creando case più grandi. Penso di poter essere meschino e creare una casa composta da parti separate. Spero che le tue case siano quadrate :) Ma in realtà, ancora una volta, fornisci metodi.
I valori di enum possono essere confrontati direttamente, non è necessario confrontare order
per l'uguaglianza.
throw new IllegalArgumentException("Should not happen");
Sono d'accordo, un'eccezione del genere non è accettabile.
return this.canMove[inDirection.ordinal()];
Oppure possibleDirections.contains(inDirection)
(vedi sopra?)