食物連鎖マグネイト実装用のボードジェネレーター

Aug 17 2020

コーディングスキルを練習するために、Javaでボードゲームのロジックを実装しています。さまざまなデータ構造を必要とするかなり複雑なゲームであるため、Food ChainMagnateを選択しました。私の最初のタスクの1つは、ゲームボードのデータ構造を生成することです。ゲームでは、ボードは利用可能な20個のタイルの一部(プレーヤーの数に応じて)を使用して作成されます。各タイルは5x5の正方形のグリッドです。ある正方形が別の正方形とは異なるタイルにある場合にのみ、ゲーム中に各タイルのIDは重要ではありません。

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クラスは、取得用のコンストラクターPoint(int x, int y)、methods x()y()およびadd(int dx, int dy)ポイント(x + dx、y + dy)を返すメソッドを備えた単純で不変の2Dポイントクラスです。他のクラスに焦点を当てるためにここに書いているのではありません。

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/-/

(文字で/あり\、コンテキストに応じて、2つの可能なタイプのターンのいずれかを表すことができます)

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(この順序で)。Houseorderプロパティを持つ単純なデータクラスですが、ゲーム中に他のプロパティを変更します。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)))));
    }

}

回答

2 MaartenBodewes Aug 20 2020 at 23:57
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。ブールパラメータの代わりに使用してください。それはあなたが読むべきであるEffectiveJavaにあります。

 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 + ")");
}

1つの値が返されます:a Road。多分方法?とても簡単に区別できます。

currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.SODA;

ああ、今私はそれを取得します。A RoadGoodsSourceAHouseまたは何を期待されています。それでも、TileContent少なくとものようなマーカーインターフェイスを作成し、それを持っRoadGoodsSource実装するので、それは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)(上記を参照?)