Generator papan untuk implementasi Food Chain Magnate

Aug 17 2020

Saya menerapkan logika permainan papan di Java untuk melatih keterampilan pengkodean saya. Saya memilih Food Chain Magnate karena merupakan game yang cukup kompleks yang membutuhkan struktur data berbeda. Salah satu tugas pertama saya adalah membuat struktur data untuk papan permainan. Dalam permainan, papan dibuat menggunakan beberapa (dari semua, tergantung dari # pemain) dari 20 ubin yang tersedia. Setiap ubin adalah kotak berukuran 5 x 5 kotak. Identitas setiap ubin tidak penting selama permainan, hanya jika beberapa kotak berada di ubin yang berbeda dari yang lain.

Saya membuat Boardkelas yang pada dasarnya adalah pembungkus di atas array Objek 2D dengan metode tambahan untuk perhitungan, dan BoardGeneratoryang membuat Boarddan menginisialisasinya dengan konten dari ubin yang berbeda.

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

}

The Pointkelas sederhana, berubah 2D kelas titik dengan konstruktor Point(int x, int y), metode x(), y()untuk pengambilan dan add(int dx, int dy)metode yang kembali titik (x + dx, y + dy). Saya tidak menulisnya di sini untuk fokus di kelas lain.

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

The tiles.txtfile berisi deskripsi 20 ubin. Isi tiap tile bisa dilihat disini:https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx(tidak termasuk ubin ekspansi). Ini adalah file teks biasa yang dibentuk dengan baris masing-masing 5 karakter. Setiap 5 baris menggambarkan ubin (5 x 5). Setiap ubin diberi karakter seperti yang ditunjukkan pada tautan referensi, menjadi ubin 5 baris pertama A, 5 ubin berikutnya B dan seterusnya. Setiap karakter (atau kelompok karakter) mewakili sebuah objek. Misalnya, ubin E dijelaskan sebagai

/-/OO
|BOOO
/O8H/
OOHH|
OO/-/

(karakter /dan \dapat mewakili salah satu dari dua kemungkinan jenis belokan, tergantung konteks)

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();
    }
}

Directionadalah enum dengan nilai UP, RIGHT, DOWN, LEFT(dalam urutan itu). Houseadalah kelas data sederhana dengan orderproperti, tetapi akan mengubah properti lain selama permainan. GoodsSourceadalah kelas sederhana dan tidak dapat diubah yang hanya dapat memiliki 3 kemungkinan instance.

BoardGeneratorTest.java (untuk contoh bagaimana itu digunakan)

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

}

Jawaban

2 MaartenBodewes Aug 20 2020 at 23:57
public static final int TILE_SIZE = 5;

Mungkin menyebutnya ini TILE_EDGE_SIZEatau yang serupa, karena ubin tidak berisi 5 kotak.

public Object get(int x, int y) {

Hapus ini, gunakan saja get(Point p), bukan pekerjaan yang lebih banyak.

return OFF_LIMIT;

Nilai ajaib tunggal ini mencegah Anda menggunakan kelas yang benar untuk Array. Nilai-nilai ajaib adalah sesuatu yang mungkin ingin Anda hindari. Jika Anda benar-benar ingin menghindari pengecualian, gunakan Optional<Square>. Tapi secara pribadi saya akan melempar pengecualian dan menggunakan Optionalalih-alih kembali null.

throw new IndexOutOfBoundsException("Point " + new Point(x, y) + " is out of the board");

Saya tahu Anda bisa melakukannya, sekarang buat metode simetris tentang bagaimana mereka menangani di luar batas.

public int getWidth() {
...
public int getHeight() {

Sempurna, tidak ada alasan mengapa aplikasi tersebut kembali lagi OFF_LIMIT.


Map<Character, Object[][]> tileList = getTileList();

Mencampur koleksi dan array bukanlah ide yang bagus; gunakan saja koleksi.

List<Character> randomTiles = new LinkedList<>(tileList.keySet());

Tepatnya, Anda akan menggunakan di ArrayListsini, bukan daftar tertaut.

Collections.shuffle(randomTiles);

Karena mencari indeks acak dan kemudian memindahkannya dalam daftar tertaut bukanlah ide yang bagus.

fillWithTile(board, tileList.get(randomTiles.get(i * heightTiles + j)), i * Board.TILE_SIZE, j * Board.TILE_SIZE, rnd.nextInt(4));

Terlalu banyak hal yang terjadi dalam metode ini, pisahkan. Mengapa ini terjadi dan apa yang dilakukannya? Kenapa ada keajaiban 4di sana, 4 dari apa?

if (2 * i + 1 < rows[rows.length - 1 - j].length()) {

Sekali lagi, dari sini kita bisa melihat bagaimana sesuatu dilakukan, tapi tidak tahu apa atau mengapa. JavaDoc yang membantu (jika tidak lengkap) memang membantu, tetapi komentar akan sangat dihargai.

private static Map<Character, Object[][]> getTileList() {

Terlalu banyak yang dilakukan dalam metode ini, jumlah kerumitannya sangat mencengangkan.

if (lineChar == 'O') { // ... endless else if's

Di sini sakelar akan melakukan keajaiban, tetapi jangan lupa break;pernyataannya.

currentTileContent[i][Board.TILE_SIZE - lineCount] = null;

Bagaimana dengan Object tileContent;deklarasi, lalu atur di switch, dan pada akhirnya tetapkan ke currentTileContent[i][Board.TILE_SIZE - lineCount]. Terlalu banyak salin / tempel jika Anda bertanya kepada saya.

 currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, false, true);

Ah, jalan yang lurus false, true, false, true. Itu bukan truejalan, gunakan enumsebagai pengganti parameter boolean. Ada di Java Efektif, yang harus Anda baca.

 EnumSet<Direction> possibleDirections = EnumSet.of(Direction.RIGHT, Direction.LEFT);

jauh lebih baik, bukankah Anda setuju?

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

Satu nilai dikembalikan: a Road. Metode mungkin? Sangat mudah dibedakan.

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

Ah, sekarang saya mengerti. A Road, diharapkan GoodsSourcea Houseatau tidak sama sekali. Namun, buat antarmuka marker seperti TileContentsetidaknya, dan miliki Roadserta GoodsSourceterapkan sehingga Anda tidak perlu Object, karena itu terlalu jelek.

throw new Error("tiles.txt not available", e);

Tidak sepenuhnya terbaca mungkin merupakan pengecualian yang lebih baik. RuntimeExceptionharus lebih disukai daripada sistem Erroryang umumnya tidak dapat dipulihkan secara luas .

} else if ((prevHouse = board.get(boardPoint.add(0, -1))) instanceof House
        && ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
    // check house below
    board.set(boardPoint, prevHouse);

Oke, jadi Anda sedang membuat rumah yang lebih besar. Saya pikir saya bisa kejam dan membuat rumah yang terdiri dari bagian-bagian yang terpisah. Saya berharap rumah Anda berbentuk persegi :) Tapi sungguh, sekali lagi, berikan metode.

Nilai enum dapat langsung dibandingkan, tidak perlu membandingkan orderuntuk persamaan.

throw new IllegalArgumentException("Should not happen");

Saya setuju di sana, pengecualian seperti itu tidak dapat diterima.

    return this.canMove[inDirection.ordinal()];

Atau possibleDirections.contains(inDirection)(lihat di atas?)