เครื่องกำเนิดบอร์ดสำหรับการใช้งาน Food Chain Magnate

Aug 17 2020

ฉันใช้ตรรกะของเกมกระดานใน Java เพื่อฝึกฝนทักษะการเขียนโค้ด ฉันเลือกFood Chain Magnateเพราะเป็นเกมที่ค่อนข้างซับซ้อนซึ่งต้องใช้โครงสร้างข้อมูลที่แตกต่างกัน งานแรกของฉันอย่างหนึ่งคือการสร้างโครงสร้างข้อมูลสำหรับบอร์ดเกม ในเกมกระดานถูกสร้างขึ้นโดยใช้บางส่วน (ทั้งหมดขึ้นอยู่กับจำนวนผู้เล่น) จาก 20 แผ่นที่มี กระเบื้องแต่ละแผ่นมีขนาด 5 x 5 สี่เหลี่ยม เอกลักษณ์ของแต่ละไทล์ไม่สำคัญในระหว่างเกมเฉพาะในกรณีที่บางตารางอยู่ในไทล์อื่น

ฉันสร้างBoardคลาสที่โดยพื้นฐานแล้วเป็น wrapper บนอาร์เรย์วัตถุ 2 มิติด้วยวิธีการเพิ่มเติมสำหรับการคำนวณและBoardGeneratorที่สร้างBoards และเริ่มต้นด้วยเนื้อหาของไทล์ต่างๆ

บอร์ด java

package com.lartkma.fcm.model.board;

public class Board {
    
    public static final int TILE_SIZE = 5;
    public static final Object OFF_LIMIT = new Object();
    
    private Object[][] boardSquares;
    
    public Board(int widthTiles, int heightTiles) {
        this.boardSquares = new Object[widthTiles * TILE_SIZE][heightTiles * TILE_SIZE];
    }
    
    public Object get(int x, int y) {
        if (x >= 0 && x < this.boardSquares.length && y >= 0 && y < this.boardSquares[0].length) {
            return this.boardSquares[x][y];
        } else {
            return OFF_LIMIT;
        }
    }
    
    public Object get(Point p) {
        return get(p.x(), p.y());
    }
    
    public void set(int x, int y, Object obj) {
        if (x >= 0 && x < this.boardSquares.length && y >= 0 && y < this.boardSquares[0].length) {
            this.boardSquares[x][y] = obj;
        } else {
            throw new IndexOutOfBoundsException("Point " + new Point(x, y) + " is out of the board");
        }
    }
    
    public void set(Point p, Object obj) {
        set(p.x(), p.y(), obj);
    }
    
    public int getWidth() {
        return this.boardSquares.length;
    }
    
    public int getHeight() {
        return this.boardSquares[0].length;
    }
    
    /**
     * Returns the tile where the square belongs, relative to this board. The value
     * is not related to the original tile used to build the board, only allows to
     * differentiate one tile from another.
     * @param p
     * @return
     */
    public int getTileNumber(Point p) {
        return (p.y() / TILE_SIZE) * (this.boardSquares.length / TILE_SIZE) + (p.x() / TILE_SIZE);
    }

}

Pointระดับเป็นง่ายไม่เปลี่ยนรูประดับจุด 2D กับผู้สร้างPoint(int x, int y)วิธีการx(), y()สำหรับการเรียกใช้และadd(int dx, int dy)วิธีการที่ผลตอบแทนจุด (x + DX, y + DY) ฉันไม่ได้เขียนที่นี่เพื่อมุ่งเน้นในชั้นเรียนอื่น ๆ

BoardGenerator.java

package com.lartkma.fcm.model.board;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Stream;

public class BoardGenerator {

    public static Board fromRandom(int widthTiles, int heightTiles) {
        Random rnd = new Random();
        Map<Character, Object[][]> tileList = getTileList();
        List<Character> randomTiles = new LinkedList<>(tileList.keySet());
        Collections.shuffle(randomTiles);

        Board board = new Board(widthTiles, heightTiles);
        for (int i = 0; i < widthTiles; i++) {
            for (int j = 0; j < heightTiles; j++) {
                fillWithTile(board, tileList.get(randomTiles.get(i * heightTiles + j)), i * Board.TILE_SIZE,
                        j * Board.TILE_SIZE, rnd.nextInt(4));
            }
        }
        return board;
    }

    /**
     * Generates a board using the tiles and rotations indicated in the expression.
     * 
     * The expression is composed of (# tiles tall) subexpressions separated by
     * newlines or spaces, each subexpression made of (# tiles wide x 2) characters.
     * 
     * Each 2 characters of a subexpression describe a tile and the rotation of such
     * tile. The tile is indicated with one of the upper-case characters used in
     * <a href="https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx">this page</a>.
     * The rotation is described as a digit from 1 to 4, where 1 is the orientation shown in
     * the page mentioned above (with the identified in the bottom left), 2 rotates the
     * reference orientation rotated 90 degrees clockwise, and so on.
     * 
     * @param expression
     * @return
     */
    public static Board fromExpression(String expression) {
        String[] rows = expression.split("\n|\r\n| ");
        int heightTiles = rows.length;
        int widthTiles = Stream.of(rows).mapToInt(s -> s.length() / 2).max().orElse(0);
        Board board = new Board(widthTiles, heightTiles);
        Map<Character, Object[][]> tileList = getTileList();
        for (int i = 0; i < widthTiles; i++) {
            for (int j = 0; j < heightTiles; j++) {
                if (2 * i + 1 < rows[rows.length - 1 - j].length()) {
                    char tileId = rows[rows.length - 1 - j].charAt(2 * i);
                    char tileRotationFactor = rows[rows.length - 1 - j].charAt(2 * i + 1);
                    if (tileList.containsKey(tileId) && tileRotationFactor >= '1' && tileRotationFactor <= '4') {
                        // Number of rotations goes from 0 to 3
                        fillWithTile(board, tileList.get(tileId), i * Board.TILE_SIZE, j * Board.TILE_SIZE,
                                tileRotationFactor - '1');
                    } else {
                        throw new IllegalArgumentException(
                                "Board tile expression \"" + tileId + tileRotationFactor + "\" cannot be read");
                    }
                }
            }
        }
        return board;
    }

    private static Map<Character, Object[][]> getTileList() {
        Map<Character, Object[][]> outputMap = new HashMap<>();
        try (BufferedReader stream = new BufferedReader(
                new InputStreamReader(BoardGenerator.class.getResourceAsStream("tiles.txt")))) {
            int lineCount = 1;
            Object[][] currentTileContent = new Object[Board.TILE_SIZE][Board.TILE_SIZE];
            char currentTileIdentifier = 'A';
            String currentLine;
            while ((currentLine = stream.readLine()) != null) {
                for (int i = 0; i < Board.TILE_SIZE; i++) {
                    char lineChar = currentLine.charAt(i);
                    if (lineChar == 'O') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = null;
                    } else if (lineChar == '-') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, false, true);
                    } else if (lineChar == '|') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, true, false);
                    } else if (lineChar == '/') {
                        // check the previous and next squares in the same line to check if this is
                        // a up-to-right turn or a right-to-up turn
                        char previous = (i == 0 ? 'O' : currentLine.charAt(i - 1));
                        char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
                        if ((isHorizontalRoad(previous) || i == 0) && !isHorizontalRoad(next)) {
                            currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, false,
                                    true);
                        } else if (!isHorizontalRoad(previous)
                                && (isHorizontalRoad(next) || i == Board.TILE_SIZE - 1)) {
                            currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, true,
                                    false);
                        } else {
                            throw new IllegalStateException("Unknown combination on ( " + currentLine + ")");
                        }
                    } else if (lineChar == '\\') {
                        // check the previous and next squares in the same line to check if this is
                        // a up-to-left turn or a left-to-up turn
                        char previous = (i == 0 ? 'O' : currentLine.charAt(i - 1));
                        char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
                        if ((isHorizontalRoad(previous) || i == 0) && !isHorizontalRoad(next)) {
                            currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, false, true,
                                    true);
                        } else if (!isHorizontalRoad(previous)
                                && (isHorizontalRoad(next) || i == Board.TILE_SIZE - 1)) {
                            currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, false,
                                    false);
                        } else {
                            throw new IllegalStateException("Unknown combination on ( " + currentLine + ")");
                        }
                    } else if (lineChar == '^') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, false, true);
                    } else if (lineChar == '>') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, true, false);
                    } else if (lineChar == 'V') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, true, true);
                    } else if (lineChar == '<') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, true, true);
                    } else if (lineChar == '+') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, true, true, true);
                    } else if (lineChar == 'S') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.SODA;
                    } else if (lineChar == 'B') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.BEER;
                    } else if (lineChar == 'L') {
                        currentTileContent[i][Board.TILE_SIZE - lineCount] = GoodsSource.LEMONADE;
                    } else if (lineChar >= '0' && lineChar <= '9') {
                        Object previous = (i == 0 ? null : currentTileContent[i - 1][Board.TILE_SIZE - lineCount]);
                        if (previous instanceof House) {
                            // part of a two-digit house, same entity as left
                            currentTileContent[i][Board.TILE_SIZE
                                    - lineCount] = currentTileContent[i - 1][Board.TILE_SIZE - lineCount];
                        } else {
                            int houseOrder = (lineChar - '0'); // classic
                            char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
                            if (next >= '0' && next <= '9') { // two digit id
                                houseOrder = houseOrder * 10 + (next - '0');
                            }
                            currentTileContent[i][Board.TILE_SIZE - lineCount] = new House(houseOrder);
                        }
                    } else if (lineChar == 'H') {
                        Object previous = (i == 0 ? null : currentTileContent[i - 1][Board.TILE_SIZE - lineCount]);
                        if (previous instanceof House) {
                            // same entity as left
                            currentTileContent[i][Board.TILE_SIZE - lineCount] = previous;
                        } else {
                            previous = (lineCount == 1 ? null
                                    : currentTileContent[i][Board.TILE_SIZE - lineCount + 1]);
                            if (previous instanceof House) {
                                // same entity as up
                                currentTileContent[i][Board.TILE_SIZE - lineCount] = previous;
                            } else {
                                throw new IllegalStateException(
                                        "Unknown combination on ( " + currentLine + "): no house defined near H");
                            }
                        }
                    } else {
                        throw new IllegalStateException("Unknown symbol: " + lineChar);
                    }
                }

                lineCount += 1;
                if (lineCount > Board.TILE_SIZE) {
                    outputMap.put(currentTileIdentifier, currentTileContent);
                    lineCount = 1;
                    currentTileContent = new Object[Board.TILE_SIZE][Board.TILE_SIZE];
                    currentTileIdentifier += 1;
                }
            }

            return outputMap;
        } catch (IOException e) {
            throw new Error("tiles.txt not available", e);
        }
    }

    private static boolean isHorizontalRoad(char c) {
        return c == '-' || c == '/' || c == '\\' || c == '^' || c == 'V' || c == '+';
    }

    private static void fillWithTile(Board board, Object[][] tileArray, int xStart, int yStart, int numRotations) {
        for (int i = 0; i < Board.TILE_SIZE; i++) {
            for (int j = 0; j < Board.TILE_SIZE; j++) {
                Point boardPoint = new Point(xStart + i, yStart + j);
                Point tileCoords = toTileCoords(i, j, numRotations);
                Object inTile = tileArray[tileCoords.x()][tileCoords.y()];
                if (inTile instanceof House) {
                    Object prevHouse;
                    if ((prevHouse = board.get(boardPoint.add(-1, 0))) instanceof House
                            && ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
                        // check house at the left
                        board.set(boardPoint, prevHouse);
                    } else if ((prevHouse = board.get(boardPoint.add(0, -1))) instanceof House
                            && ((House) prevHouse).getOrder() == ((House) inTile).getOrder()) {
                        // check house below
                        board.set(boardPoint, prevHouse);
                    } else {
                        board.set(boardPoint, new House(((House) inTile).getOrder()));
                    }
                } else if (inTile instanceof Road) {
                    board.set(boardPoint, ((Road) inTile).rotate(numRotations));
                } else if (inTile instanceof GoodsSource || inTile == null) {
                    board.set(boardPoint, inTile);
                } else {
                    throw new IllegalStateException("Unknown object: " + inTile.getClass());
                }
            }
        }
    }

    private static Point toTileCoords(int x, int y, int rotations) {
        switch (rotations) {
            case 0:
                return new Point(x, y);
            case 1:
                return new Point(Board.TILE_SIZE - 1 - y, x);
            case 2:
                return new Point(Board.TILE_SIZE - 1 - x, Board.TILE_SIZE - 1 - y);
            case 3:
                return new Point(y, Board.TILE_SIZE - 1 - x);
            default:
                throw new IllegalArgumentException("Should not happen");
        }
    }
}

tiles.txtไฟล์มีคำอธิบายของ 20 กระเบื้อง สามารถดูเนื้อหาของแต่ละกระเบื้องได้ที่นี่:https://boardgamehelpers.com/FoodChainMagnate/MapTileKey.aspx(ไม่รวมไทล์ส่วนขยาย) เป็นไฟล์ข้อความธรรมดาที่มีบรรทัดละ 5 อักขระ แต่ละบรรทัดอธิบายถึงไทล์ (5 x 5) แต่ละไทล์จะถูกกำหนดอักขระตามที่แสดงในลิงก์อ้างอิงโดยเป็นไทล์ 5 บรรทัดแรก A, 5 ไทล์ B ถัดไปและอื่น ๆ อักขระแต่ละตัว (หรือกลุ่มอักขระ) แสดงถึงวัตถุ ตัวอย่างเช่นกระเบื้อง E อธิบายว่า

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

(อักขระ/และ\สามารถแสดงถึงหนึ่งในสองประเภทที่เป็นไปได้ของการเลี้ยวขึ้นอยู่กับบริบท)

Road.java

package com.lartkma.fcm.model.board;

import java.util.Arrays;
import java.util.StringJoiner;

public class Road {
    private boolean[] canMove;

    public Road(boolean canGoUp, boolean canGoRight, boolean canGoDown, boolean canGoLeft) {
        this.canMove = new boolean[] { canGoUp, canGoRight, canGoDown, canGoLeft };
    }

    public boolean canMove(Direction inDirection) {
        return this.canMove[inDirection.ordinal()];
    }

    public Road rotate(int amountRotations) {
        Road rotated = new Road(this.canMove[0], this.canMove[1], this.canMove[2], this.canMove[3]);
        if (amountRotations < 0) {
            // Java operator % returns a remainder, that is different from a mathematical
            // modulus
            // https://stackoverflow.com/questions/5385024/mod-in-java-produces-negative-numbers
            // https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.17.3
            amountRotations = amountRotations % rotated.canMove.length + rotated.canMove.length;
        } else {
            amountRotations = amountRotations % rotated.canMove.length;
        }
        boolean swapTemp;
        for (int k = 0; k < amountRotations; k++) {
            for (int i = 1; i < rotated.canMove.length; i++) { // it makes no sense for the first element
                swapTemp = rotated.canMove[0];
                rotated.canMove[0] = rotated.canMove[i];
                rotated.canMove[i] = swapTemp;
            }
        }
        return rotated;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Road) {
            return Arrays.equals(this.canMove, ((Road) obj).canMove);
        } else {
            return false;
        }
    }

    @Override
    public String toString() {
        StringJoiner name = new StringJoiner("-", "Road[", "]");
        for (Direction d : Direction.values()) {
            if (canMove(d)) {
                name.add(d.name());
            }
        }
        return name.toString();
    }
}

Directionคือ enum ที่มีค่าUP, RIGHT, DOWN, LEFT(ตามลำดับ) Houseเป็นคลาสข้อมูลธรรมดาที่มีorderคุณสมบัติ แต่จะกลายพันธุ์คุณสมบัติอื่น ๆ ในระหว่างเกม GoodsSourceเป็นคลาสที่เรียบง่ายไม่เปลี่ยนรูปซึ่งสามารถมีได้ 3 อินสแตนซ์เท่านั้น

BoardGeneratorTest.java (สำหรับตัวอย่างวิธีการใช้งาน)

package com.lartkma.fcm.model.board;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.lartkma.fcm.model.board.Board;
import com.lartkma.fcm.model.board.GoodsSource;
import com.lartkma.fcm.model.board.House;
import com.lartkma.fcm.model.board.BoardGenerator;
import com.lartkma.fcm.model.board.Road;

public class BoardGeneratorTest {

    @Test
    @DisplayName("The board generator can receive specific tiles as parameters and should generate the correct amount of squares")
    public void testBoardGeneratorSizeFromExpression() {
        Board board = BoardGenerator.fromExpression("G1E2\nI3M4");
        assertAll("The board should be of 2 x 2 tiles (10 x 10 squares)",
                () -> assertEquals(10, board.getWidth(), "Board width"),
                () -> assertEquals(10, board.getHeight(), "Board height"));
    }

    @Test
    @DisplayName("The board generator can generate a random board and should generate the correct amount of squares")
    public void testBoardGeneratorSizeFromRandom() {
        Board board = BoardGenerator.fromRandom(3, 2);
        assertAll("The board should be of 3 x 2 tiles (15 x 10 squares)",
                () -> assertEquals(15, board.getWidth(), "Board width"),
                () -> assertEquals(10, board.getHeight(), "Board height"));
    }

    @Test
    @DisplayName("The board generator can create a 1-tile board with the correct contents")
    public void testBoardGeneratorContent() {
        Board board = BoardGenerator.fromExpression("E1");
        assertAll("The board should have the following contents",
                () -> assertThat("In 0, 0", board.get(0, 0), is(nullValue())),
                () -> assertThat("In 1, 0", board.get(1, 0), is(nullValue())),
                () -> assertThat("In 2, 0", board.get(2, 0), is(equalTo(new Road(false, true, true, false)))),
                () -> assertThat("In 3, 0", board.get(3, 0), is(equalTo(new Road(false, true, false, true)))),
                () -> assertThat("In 4, 0", board.get(4, 0), is(equalTo(new Road(true, false, false, true)))),
                () -> assertThat("In 0, 1", board.get(0, 1), is(nullValue())),
                () -> assertThat("In 1, 1", board.get(1, 1), is(nullValue())),
                () -> assertThat("In 2, 1", board.get(2, 1), is(equalTo(new House(8)))),
                () -> assertThat("In 3, 1", board.get(3, 1), is(sameInstance(board.get(2, 1)))),
                () -> assertThat("In 4, 1", board.get(4, 1), is(equalTo(new Road(true, false, true, false)))),
                () -> assertThat("In 0, 2", board.get(0, 2), is(equalTo(new Road(true, false, false, true)))),
                () -> assertThat("In 1, 2", board.get(1, 2), is(nullValue())),
                () -> assertThat("In 2, 2", board.get(2, 2), is(sameInstance(board.get(2, 1)))),
                () -> assertThat("In 3, 2", board.get(3, 2), is(sameInstance(board.get(2, 1)))),
                () -> assertThat("In 4, 2", board.get(4, 2), is(equalTo(new Road(false, true, true, false)))),
                () -> assertThat("In 0, 3", board.get(0, 3), is(equalTo(new Road(true, false, true, false)))),
                () -> assertThat("In 1, 3", board.get(1, 3), is(equalTo(GoodsSource.BEER))),
                () -> assertThat("In 2, 3", board.get(2, 3), is(nullValue())),
                () -> assertThat("In 3, 3", board.get(3, 3), is(nullValue())),
                () -> assertThat("In 4, 3", board.get(4, 3), is(nullValue())),
                () -> assertThat("In 0, 4", board.get(0, 4), is(equalTo(new Road(false, true, true, false)))),
                () -> assertThat("In 1, 4", board.get(1, 4), is(equalTo(new Road(false, true, false, true)))),
                () -> assertThat("In 2, 4", board.get(2, 4), is(equalTo(new Road(true, false, false, true)))),
                () -> assertThat("In 3, 4", board.get(3, 4), is(nullValue())),
                () -> assertThat("In 4, 4", board.get(4, 4), is(nullValue())));
    }

    @Test
    @DisplayName("The board generator can create a rotated 1-tile board with the correct contents")
    public void testBoardGeneratorContentRotated() {
        Board board = BoardGenerator.fromExpression("E2");
        assertAll("The board should have the following contents",
                () -> assertThat("In 0, 0", board.get(0, 0), is(equalTo(new Road(true, true, false, false)))),
                () -> assertThat("In 1, 0", board.get(1, 0), is(equalTo(new Road(false, true, false, true)))),
                () -> assertThat("In 2, 0", board.get(2, 0), is(equalTo(new Road(false, false, true, true)))),
                () -> assertThat("In 3, 0", board.get(3, 0), is(nullValue())),
                () -> assertThat("In 4, 0", board.get(4, 0), is(nullValue())),
                () -> assertThat("In 0, 1", board.get(0, 1), is(equalTo(new Road(true, false, true, false)))),
                () -> assertThat("In 1, 1", board.get(1, 1), is(equalTo(new House(8)))),
                () -> assertThat("In 2, 1", board.get(2, 1), is(sameInstance(board.get(1, 1)))),
                () -> assertThat("In 3, 1", board.get(3, 1), is(nullValue())),
                () -> assertThat("In 4, 1", board.get(4, 1), is(nullValue())),
                () -> assertThat("In 0, 2", board.get(0, 2), is(equalTo(new Road(false, false, true, true)))),
                () -> assertThat("In 1, 2", board.get(1, 2), is(sameInstance(board.get(1, 1)))),
                () -> assertThat("In 2, 2", board.get(2, 2), is(sameInstance(board.get(1, 1)))),
                () -> assertThat("In 3, 2", board.get(3, 2), is(nullValue())),
                () -> assertThat("In 4, 2", board.get(4, 2), is(equalTo(new Road(true, true, false, false)))),
                () -> assertThat("In 0, 3", board.get(0, 3), is(nullValue())),
                () -> assertThat("In 1, 3", board.get(1, 3), is(nullValue())),
                () -> assertThat("In 2, 3", board.get(2, 3), is(nullValue())),
                () -> assertThat("In 3, 3", board.get(3, 3), is(equalTo(GoodsSource.BEER))),
                () -> assertThat("In 4, 3", board.get(4, 3), is(equalTo(new Road(true, false, true, false)))),
                () -> assertThat("In 0, 4", board.get(0, 4), is(nullValue())),
                () -> assertThat("In 1, 4", board.get(1, 4), is(nullValue())),
                () -> assertThat("In 2, 4", board.get(2, 4), is(equalTo(new Road(true, true, false, false)))),
                () -> assertThat("In 3, 4", board.get(3, 4), is(equalTo(new Road(false, true, false, true)))),
                () -> assertThat("In 4, 4", board.get(4, 4), is(equalTo(new Road(false, false, true, true)))));
    }

}

คำตอบ

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;

ค่าเวทมนตร์เดียวนี้ป้องกันไม่ให้คุณใช้คลาสที่ถูกต้องสำหรับ Array ค่าเวทมนตร์เป็นสิ่งที่คุณอาจต้องการหลีกเลี่ยง Optional<Square>ถ้าคุณอยากจะหลีกเลี่ยงข้อยกเว้นการใช้งาน แต่ส่วนตัวผมจะโยนข้อยกเว้นและใช้แทนที่จะกลับOptionalnull

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

วิธีนี้เกิดขึ้นมากเกินไปให้แยกออก เหตุใดจึงเกิดขึ้นและกำลังทำอะไรอยู่ ทำไมถึงมีเวทย์มนตร์44 อย่าง?

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แทนพารามิเตอร์บูลีน มันอยู่ใน Effective Java ซึ่งคุณควรอ่าน

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

ดีกว่ามากคุณไม่เห็นด้วยเหรอ?

char previous = (i == 0 ? 'O' : currentLine.charAt(i - 1));
char next = (i == Board.TILE_SIZE - 1 ? 'O' : currentLine.charAt(i + 1));
if ((isHorizontalRoad(previous) || i == 0) && !isHorizontalRoad(next)) {
    currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(true, false, false,
            true);
} else if (!isHorizontalRoad(previous)
        && (isHorizontalRoad(next) || i == Board.TILE_SIZE - 1)) {
    currentTileContent[i][Board.TILE_SIZE - lineCount] = new Road(false, true, true,
            false);
} else {
    throw new IllegalStateException("Unknown combination on ( " + currentLine + ")");
}

หนึ่งค่าจะถูกส่งกลับ: a Road. วิธีการอาจ? ง่ายมากที่จะแยกแยะ

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

ตอนนี้ฉันเข้าใจแล้ว Road, หรือไม่มีอะไรเป็นที่คาดหวัง ยังคงสร้างอินเทอร์เฟซเครื่องหมายเช่นอย่างน้อยที่สุดและมีและใช้งานได้โดยที่คุณไม่จำเป็นต้องใช้เพราะมันน่าเกลียดเกินไปGoodsSourceHouseTileContentRoadGoodsSourceObject

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

ตกลงคุณกำลังสร้างบ้านหลังใหญ่ขึ้น ฉันคิดว่าฉันเป็นคนใจร้ายและสร้างบ้านที่ประกอบด้วยส่วนแยกต่างหาก ฉันหวังว่าบ้านของคุณจะเป็นสี่เหลี่ยม :) แต่จริงๆแล้วให้วิธีการอีกครั้ง

ค่า Enum สามารถเปรียบเทียบได้โดยตรงไม่จำเป็นต้องเปรียบเทียบorderเพื่อความเท่าเทียมกัน

throw new IllegalArgumentException("Should not happen");

ฉันยอมรับข้อยกเว้นดังกล่าวไม่สามารถยอมรับได้

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

หรือpossibleDirections.contains(inDirection)(ดูด้านบน?)