Trình tạo bảng để triển khai Food Chain Magnate

Aug 17 2020

Tôi đang triển khai logic của trò chơi hội đồng bằng Java để thực hành kỹ năng viết mã của mình. Tôi chọn Food Chain Magnate vì đây là một trò chơi khá phức tạp, đòi hỏi các cấu trúc dữ liệu khác nhau. Một trong những nhiệm vụ đầu tiên của tôi là tạo cấu trúc dữ liệu cho bảng trò chơi. Trong trò chơi, bảng được xây dựng bằng cách sử dụng một số (tất cả, tùy thuộc vào số người chơi) trong số 20 ô có sẵn. Mỗi ô là một lưới ô vuông 5 x 5. Danh tính của mỗi ô không quan trọng trong trò chơi, chỉ khi một số ô vuông nằm trong ô khác với ô khác.

Tôi đã tạo một Boardlớp về cơ bản là một lớp bao bọc trên một mảng Đối tượng 2D với các phương thức bổ sung để tính toán và một lớp BoardGeneratortạo Boards và khởi tạo nó với nội dung của các ô khác nhau.

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

}

Các Pointlớp là một đơn giản, bất biến lớp điểm 2D với một constructor Point(int x, int y), phương pháp x(), y()để thu hồi và một add(int dx, int dy)phương pháp mà trả về điểm (x + dx, y + dy). Tôi không viết nó ở đây để tập trung vào các lớp khác.

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

Các tiles.txttập tin có chứa các mô tả về 20 gạch. Nội dung của mỗi ô có thể được xem ở đây:https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx(không bao gồm các ô mở rộng). Đó là một tệp văn bản thuần túy được tạo với mỗi dòng gồm 5 ký tự. Mỗi 5 dòng mô tả một ô (5 x 5). Mỗi ô được gán một ký tự như được hiển thị trong liên kết tham chiếu, là ô 5 dòng đầu tiên là ô A, ô 5 tiếp theo là ô B, v.v. Mỗi ký tự (hoặc nhóm ký tự) đại diện cho một đối tượng. Ví dụ, ô E được mô tả là

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

(ký tự /\có thể đại diện cho một trong hai kiểu rẽ có thể xảy ra, tùy thuộc vào ngữ cảnh)

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

Directionlà một enum với các giá trị UP, RIGHT, DOWN, LEFT(theo thứ tự đó). Houselà một lớp dữ liệu đơn giản với một thuộc ordertính, nhưng nó sẽ thay đổi các thuộc tính khác trong trò chơi. GoodsSourcelà một lớp đơn giản, không thay đổi, chỉ có thể có 3 trường hợp có thể.

BoardGeneratorTest.java (để biết ví dụ về cách nó được sử dụng)

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

}

Trả lời

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

Có thể gọi cái này TILE_EDGE_SIZEhoặc cái gì đó tương tự, vì một ô không chứa 5 ô vuông.

public Object get(int x, int y) {

Loại bỏ cái này, chỉ sử dụng get(Point p), nó không phải là công việc nhiều hơn.

return OFF_LIMIT;

Giá trị ma thuật duy nhất này ngăn bạn sử dụng đúng lớp cho Mảng. Giá trị ma thuật là điều bạn có thể muốn tránh. Nếu bạn thực sự muốn tránh một ngoại lệ, hãy sử dụng Optional<Square>. Nhưng cá nhân tôi sẽ ném một ngoại lệ và sử dụng Optionalthay vì quay lại null.

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

Tôi biết bạn có thể làm điều đó, bây giờ hãy làm cho các phương pháp đối xứng về cách chúng xử lý ngoài giới hạn.

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

Hoàn hảo, không có lý do tại sao ứng dụng sẽ không bao giờ trở lại OFF_LIMITsau đó.


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

Trộn các bộ sưu tập và mảng không phải là một ý kiến ​​hay; chỉ sử dụng các bộ sưu tập.

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

Nói chính xác, bạn sẽ sử dụng ArrayListở đây, không phải danh sách liên kết.

Collections.shuffle(randomTiles);

Bởi vì tìm kiếm các chỉ mục ngẫu nhiên và sau đó di chuyển chúng trong một danh sách được liên kết không phải là một ý tưởng hay.

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

Quá nhiều điều đang xảy ra trong phương pháp này, hãy chia nhỏ nó ra. Tại sao điều này lại xảy ra và nó đang làm gì? Tại sao lại có phép thuật 4trong đó, 4 trong số những gì?

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

Một lần nữa, từ đây chúng ta có thể thấy mọi thứ đang được thực hiện như thế nào, nhưng không phải là gì hoặc tại sao. JavaDoc hữu ích (nếu không đầy đủ) thực sự giúp ích phần nào, nhưng một nhận xét sẽ được đánh giá cao.

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

Quá nhiều cách đang được thực hiện trong phương pháp này, mức độ phức tạp đáng kinh ngạc.

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

Ở đây một công tắc sẽ làm nên điều kỳ diệu, nhưng đừng quên các break;tuyên bố.

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

Còn Object tileContent;khai báo thì sao, đặt nó vào switch, và cuối cùng là gán nó cho currentTileContent[i][Board.TILE_SIZE - lineCount]. Quá nhiều sao chép / dán nếu bạn hỏi tôi.

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

À, một con đường mà đi false, true, false, true. Đó không phải là một truecon đường, hãy sử dụng enumthay vì các tham số boolean. Nó trong Java hiệu quả, mà bạn nên đọc.

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

đẹp hơn rất nhiều, bạn có đồng ý không?

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

Một giá trị được trả về: a Road. Phương pháp có thể? Vì vậy, dễ dàng phân biệt.

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

À, giờ thì tôi hiểu rồi. A Road, GoodsSourcea Househoặc không có gì được mong đợi. Tuy nhiên, TileContentít nhất hãy tạo một giao diện đánh dấu chẳng hạn như có RoadGoodsSourcetriển khai nó để bạn không cần Object, vì nó quá xấu.

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

Không thể đọc được đầy đủ có lẽ là một ngoại lệ tốt hơn. RuntimeExceptionnên được ưu tiên hơn Errorhệ thống thường không thể khôi phục được .

} 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, vậy là bạn đang tạo ra những ngôi nhà lớn hơn. Tôi nghĩ rằng tôi có thể xấu tính và tạo ra một ngôi nhà được tạo thành từ các phần riêng biệt. Tôi hy vọng rằng ngôi nhà của bạn là hình vuông :) Nhưng thực sự, một lần nữa, cung cấp các phương pháp.

Các giá trị Enum có thể được so sánh trực tiếp, không cần phải so sánh orderđể bình đẳng.

throw new IllegalArgumentException("Should not happen");

Tôi đồng ý ở đó, một ngoại lệ như vậy là không thể chấp nhận được.

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

Hoặc possibleDirections.contains(inDirection)(xem ở trên?)