Игра в жизнь переписана на два класса, Петри и клетки


Выполнение: игра в жизнь выполнены с петлями и логическое массива

Предложенный palacsint я пошел и переписал свою реализацию игры "Жизнь" и добывают все на два класса:

  • Петри

    • Родитель, который содержит массив ячеек
    • Задает массив и инициализирует каждую ячейку
    • Начинается эволюция
  • Клетки

    • Сохраняет текущее значение
    • Содержит ссылки на соседей это

Основной массив ячеек-это еще одномерным, потому что я нахожу его удобным, чтобы легко добраться до всех клеток, с помощью простого цикла foreach. Клетки рогового мертвая границы и никогда не менять есть состояние, в основном, чтобы облегчить обработку массива.

Работает это так:

  • Инициировать Петри
  • Набор массива
  • Добавить ячейки в массив
  • Инициализировать все ячейки
    • Клетка получает ее соседей из родительского Петри, кроме случаев, когда границы клетки, то он ничего не делает.
  • В Петри стартует эволюции
    • Клетка проверяет его соседи через два статических метода, countAliveNeighbors и checkSurvival
    • Это значение сохраняется в переменную
  • В Петри заканчивается эволюция
    • Ячейка перекрывает это старое значение с новым из приведенного выше цикла

Это несколько уродливых вывертов:

  • Обработка границы-клетки чувствует себя немного странно, но пока я об этом думаю, я мог бы удалить их полностью
  • Это необходимо, чтобы сохранить новое значение ячейки в другую переменную, там делает эволюции в два этапа

Ладно, хватит болтать, вот код:

public class PetriDish {

    private Cell[] cells;
    private int width;
    private int height;
    private long generation;
    private long duration;

    public Cell[] getCells() {
        return cells;
    }

    public Cell getCell(int x, int y) {
        return cells[y * width + x];
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

    public long getGeneration() {
        return generation;
    }

    public long getDuration() {
        return duration;
    }

    public PetriDish(int width, int height) {
        this.width = width;
        this.height = height;

        this.cells = new Cell[width * height];

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                cells[y * width + x] = new Cell(this, x, y,
                        x == 0 || y == 0 || x == width - 1 || y == height - 1);
            }
        }

        for (Cell cell : cells) {
            cell.prepare();
        }
    }

    public void doEvolution() {
        long start = System.currentTimeMillis();

        for (Cell cell : cells) {
            cell.startEvolution();
        }
        for (Cell cell : cells) {
            cell.finishEvolution();
        }

        generation++;
        duration = System.currentTimeMillis() - start;
    }
}

public class Cell {

    private PetriDish parent;
    private int x;
    private int y;
    private boolean value;
    private boolean nextValue;
    private boolean isBorderCell;
    private List<Cell> neighbors;

    public boolean getValue() {
        return value;
    }

    public void setValue(boolean value) {
        if (!isBorderCell) {
            this.value = value;
        }
    }

    public Cell(PetriDish parent, int x, int y, boolean isBorderCell) {
        this.parent = parent;
        this.x = x;
        this.y = y;

        this.isBorderCell = isBorderCell;
        this.neighbors = new ArrayList<Cell>();
    }

    public void prepare() {
        if (!isBorderCell) {
            for (int neighborX = x - 1; neighborX <= x + 1; neighborX++) {
                for (int neighborY = y - 1; neighborY <= y + 1; neighborY++) {
                    if (neighborX != x || neighborY != y) {
                        neighbors.add(parent.getCell(neighborX, neighborY));
                    }
                }
            }
        }
    }

    public void startEvolution() {
        nextValue = checkSurvival(value, countAliveNeighbors(neighbors));
    }

    public void finishEvolution() {
        value = nextValue;
    }

    private static int countAliveNeighbors(Iterable<Cell> neighbors) {
        int neighborsAlive = 0;

        for (Cell neighbor : neighbors) {
            if (neighbor.value) {
                neighborsAlive++;
            }
        }

        return neighborsAlive;
    }

    private static boolean checkSurvival(boolean isAlive, int neighborsAlive) {
        switch (neighborsAlive) {
            case 0:
            case 1:
                return false;

            case 2:
                return isAlive;

            case 3:
                return true;

            default:
                return false;
        }
    }
}

Проекта можно найти на GitHub и должен Слик для визуализации и ввода.



2429
3
задан 9 декабря 2011 в 08:12 Источник Поделиться
Комментарии
1 ответ


  1. Геттер-сеттер методы в Петри и клетка должна быть объявлена после строителей. (Проверка кодирования Java конвенций, 3.1.3 класса и интерфейса деклараций.) Я предпочитаю устанавливать их в конце занятия (после всех других методов).

  2. Я бы проверил входные значения:

    import static com.google.common.base.Preconditions.*;
    ...

    public PetriDish(final int width, final int height) {
    checkArgument(width > 3, "width has to be at least 3");
    checkArgument(height > 3, "height has to be at least 3");
    ...
    }

    Конечно же простой , если и бросить новый IllegalArgumentException , даже если вы не хотите включать гуава-библиотеки.


  3. Передав это другому методу/классу в конструкторе, как правило, не очень хорошая идея:

    cells[y * width + x] = new Cell(this, x, y, x == 0 || y == 0 
    || x == width - 1 || y == height - 1);

    Если конструктор поздно бросает исключение созданный сотового объектов есть ссылка на не полностью сконструированный Петри объекта. (Может быть, ячейка хранит ссылку в статическом поле, передает его прослушиватель события или одноэлементный класс и т. д.) Я не уверен, но, вероятно, я хотел создать метод init() метод для этого логику.


  4. Читать следующий вызов конструктора не самая простая задача:

    new Cell(this, x, y, x == 0 || y == 0 || x == width - 1 || y == height - 1);

    Я бы создает локальную переменную для последнего параметра:

    final boolean isBorderCell = x == 0 || y == 0 || x == width - 1 
    || y == height - 1;
    cells[y * width + x] = new Cell(this, x, y, isBorderCell);

    В isBorderCell способ будет лучшим:

    private boolean isBorderCell(final int width, final int height, final int x, 
    final int y) {
    if (x == 0) {
    return true;
    }
    if (y == 0) {
    return true;
    }
    if (x == width - 1) {
    return true;
    }
    if (y == height - 1) {
    return true;
    }
    return false;
    }

    Я думаю, что это легче читать, чем длинные логическое выражение.


  5. Я бы переименовать ячейку.подготовить() в методе init(). Это более распространенная.

  6. В подготовке метод может быть в Петри классе и может вызвать клеток.setNeighbors(). Если вы переместите его клеток не должны иметь Петри поля и параметр конструктора на всех.

  7. countAliveNeighbors должен быть нестатическим и должны использовать соседей поля напрямую:

    private int countAliveNeighbors() {
    ...
    }

    То же самое верно для checkSurvival способ. На самом деле, я бы переименовать его в calcNextState:

    private boolean calcNextState() {
    final int neighborsAlive = countAliveNeighbors();
    switch (neighborsAlive) {
    ...
    }
    }

  8. Использовать геттеры вместо прямого доступа к полю в countAliveNeighbors способ:

    if (neighbor.getValue()) {
    neighborsAlive++;
    }

    (В чем дело с Java по полям?)


  9. Вместо логическое значение флага я бы использовать перечислимый с живых и мертвых ценностей. Это будет более значимым (без истинной и ложной магии "чисел").

  10. В isBorderCell флаг в клетке не пахнет. Создать ячейку интерфейса:

    public interface Cell {
    void prepare();
    void startEvolution();
    void finishEvolution();
    boolean getValue();
    void setValue(final boolean value);
    }

    и переименовать текущую ячейку в NormalCell и создать пустой BorderCell класс:

    public class BorderCell implements Cell {

    @Override
    public void prepare() {
    }

    @Override
    public void startEvolution() {
    }

    @Override
    public void finishEvolution() {
    }

    @Override
    public boolean getValue() {
    return false;
    }

    @Override
    public void setValue(boolean value) {
    throw new UnsupportedOperationException();
    }
    }

    Заводской метод полезен в Петри:

    private Cell createCell(int x, int y) {
    if (isBorderCell(width, height, x, y)) {
    return new BorderCell();
    } else {
    return new NormalCell(this, x, y);
    }
    }

    После этого снимите borderCell флаг и его использования от NormalCell класс. (Проверить замена условной логики на цену код с полиморфизмом в рефакторинг: улучшение дизайна существующего кода Мартин Фаулер)


  11. Если вы хотите избавиться от nextValue поля в ячейке попробовать изменить doEvolution способ. Он может вернуть новый Петри с новой клетки, которые содержат состояние следующего поколения. Это поможет хранить полную историю (например в списке), но я думаю, что нынешняя конструкция с nextValue поля тоже все в порядке (если вам не нужна полная история конечно).

4
ответ дан 16 декабря 2011 в 01:12 Источник Поделиться