3
\$\begingroup\$

As a hobby project I implemented Game of Life in Java using Swing. I don't have much experience with Java using Swing and would like to get feedback on the code. One thing in particular that I would like to improve is how cells at the edges of the board is handled, right now they just get stuck.

All code and some tests is available here if anyone prefers GitHub.

App

public class App {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new MainView());
    }

}

MainView

public class MainView implements Runnable, ActionListener {

    private final Timer timer;
    private final BoardView board;

    public MainView() {
        Dimension boardSize = new Dimension(1200, 1000);
        Dimension buttonSize = new Dimension(150, 40);
        Dimension menuSize = new Dimension(200, 1000);

        timer = new Timer(20, this);

        board = new BoardView(boardSize);
        board.setPreferredSize(boardSize);
        board.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));

        JPanel sideMenu = new JPanel();
        sideMenu.setPreferredSize(menuSize);
        sideMenu.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
        sideMenu.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));

        JButton resetButton = new JButton("Reset");
        resetButton.setPreferredSize(buttonSize);
        resetButton.addActionListener(e -> board.resetBoard());

        JToggleButton drawButton = new JToggleButton("Draw");
        drawButton.setPreferredSize(buttonSize);
        drawButton.addActionListener(e -> 
            board.setDrawMode(drawButton.isSelected()));

        JButton startButton = new JButton("Start");
        startButton.setPreferredSize(buttonSize);
        startButton.addActionListener(e -> {
            board.setDrawMode(false);
            drawButton.setSelected(false);
            timer.start();
        });

        JButton stopButton = new JButton("Stop");
        stopButton.setPreferredSize(buttonSize);
        stopButton.addActionListener(e -> timer.stop());

        sideMenu.add(startButton);
        sideMenu.add(stopButton);
        sideMenu.add(resetButton);
        sideMenu.add(drawButton);

        JFrame mainFrame = new JFrame("Conway's Game of Life");
        mainFrame.setResizable(false);
        mainFrame.setDefaultCloseOperation
            (WindowConstants.EXIT_ON_CLOSE);            
        mainFrame.setLayout(new BoxLayout(
            mainFrame.getContentPane(),
            BoxLayout.LINE_AXIS));
        mainFrame.add(sideMenu);
        mainFrame.add(board);
        mainFrame.pack();
        mainFrame.setVisible(true);
    }

    @Override
    public void run() {

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        board.nextGeneration();
    }

}

BoardView

public class BoardView extends JPanel {

    private final Level currentLevel;
    private Point lastChangedPoint;
    private boolean drawMode;

    BoardView(Dimension boardSize) {
        this.currentLevel = new Level(boardSize.width, boardSize.height);
        MouseInputAdapter mouseInputAdapter = new MouseInputAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (drawMode) {
                    int width = e.getX() / currentLevel.getCellSize();
                    int height = e.getY() / currentLevel.getCellSize();

                    if (currentLevel.cellIsInBounds(width, height)) {
                        Point currentPoint = new Point(width, height);

                        if (!currentPoint.equals(lastChangedPoint)) {
                            currentLevel.changeCellStateAt(width, height);
                            repaint();
                        }

                        lastChangedPoint = currentPoint;
                    }
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                lastChangedPoint.setLocation(-1, -1);
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                if (drawMode) {      
                    setCursor(Cursor.getPredefinedCursor(
                        Cursor.HAND_CURSOR));
                }
            }

            @Override
            public void mouseExited(MouseEvent e) {
                setCursor(Cursor.getDefaultCursor());
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                mousePressed(e);
            }
        };
        addMouseListener(mouseInputAdapter);
        addMouseMotionListener(mouseInputAdapter);
    }

    public void setDrawMode(boolean drawMode) {
        this.drawMode = drawMode;
    }

    public void nextGeneration() {
        currentLevel.updateAllCells();
        currentLevel.advanceAllCells();
        repaint();
    }

    public void resetBoard() {
        currentLevel.resetLevel();
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        drawCells(g);
        drawGrid(g);
    }

    private void drawCells(Graphics g) {
        int heightPosition = 0;
        int widthPosition = 0;

        for (int height = 0; height < currentLevel.getLevelSize().height; height++) {
            for (int width = 0; width < currentLevel.getLevelSize().width; width++) {
                if (currentLevel.getCellAt(width, height).isAlive()) {
                    g.setColor(Color.BLACK);
                } else {
                    g.setColor(Color.WHITE);
                }

                g.fillRect(widthPosition, heightPosition, widthPosition + currentLevel.getCellSize(), heightPosition
                    + currentLevel.getCellSize());

                widthPosition += currentLevel.getCellSize();
            }

            heightPosition += currentLevel.getCellSize();
            widthPosition = 0;
        }
    }

    private void drawGrid(Graphics g) {
        g.setColor(Color.LIGHT_GRAY);
        for (int width = currentLevel.getCellSize(); width < getSize().width; width += currentLevel.getCellSize()) {
            g.drawLine(width, 0, width, getSize().height - 1);
        }

        for (int height = currentLevel.getCellSize(); height < getSize().height; height += currentLevel.getCellSize()) {
            g.drawLine(0, height, getSize().width - 1, height);
        }
    }

}

Level

public class Level {

    private final int CELL_SIZE = 10;
    private Cell[][] cells;
    private Dimension levelSize;

    public Level(int width, int height) {
        this.levelSize = new Dimension(width / CELL_SIZE, height / CELL_SIZE);
        cells = new Cell[levelSize.width][levelSize.height];
        populateLevel();
    }

    public Dimension getLevelSize() {
        return levelSize;
    }

    public int getCellSize() {
        return CELL_SIZE;
    }

    public void resetLevel() {
        for (int height = 0; height < levelSize.height; height++) {
            for (int width = 0; width < levelSize.width; width++) {
                getCellAt(width, height).kill();
            }
        }
    }

    public Cell getCellAt(int width, int height) {
        return cells[width][height];
    }

    public void updateAllCells() {
        for (int height = 0; height < levelSize.height; height++) {
            for (int width = 0; width < levelSize.width; width++) {
                getCellAt(width, height).setNumberOfNeighbors(countAliveNeighbors(width, height));
            }
        }
    }

    public void advanceAllCells() {
        for (int height = 0; height < levelSize.height; height++) {
            for (int width = 0; width < levelSize.width; width++) {
                getCellAt(width, height).advanceOnce();
            }
        }
    }

    public int countAliveNeighbors(int width, int height) {
        int count = 0;

        for (int i = -1; i < 2; i++) {
            for (int j = -1; j < 2; j++) {
                if (!(i == 0 && j == 0) && cellIsInBounds(width + i, height + j)) {
                    if (getCellAt(width + i, height + j).isAlive()) {
                        count++;
                    }
                }
            }
        }

        return count;
    }

    public boolean cellIsInBounds(int width, int height) {
        return (width >= 0 && height >= 0) &&
                (width < levelSize.width && height < levelSize.height);
    }

    public void changeCellStateAt(int width, int height) {
        if (getCellAt(width, height).isAlive()) {
            getCellAt(width, height).kill();
        } else {
            getCellAt(width, height).birth();
        }
    }

    private void populateLevel() {
        for (int height = 0; height < levelSize.height; height++) {
            for (int width = 0; width < levelSize.width; width++) {
                cells[width][height] = new Cell();
            }
        }
    }

}

Cell

public class Cell {

    private boolean living;
    private int numberOfNeighbors;

    public boolean isAlive() {
        return living;
    }

    public void kill() {
        living = false;
    }

    public void birth() {
        living = true;
    }

    public int getNumberOfNeighbors() {
        return numberOfNeighbors;
    }

    public void setNumberOfNeighbors(int numberOfNeighbors) {
        this.numberOfNeighbors = numberOfNeighbors;
    }

    public void advanceOnce() {
        if (isAlive() && (getNumberOfNeighbors() < 2 || getNumberOfNeighbors() > 3)) {
                kill();
        } else if (getNumberOfNeighbors() == 3) {
                birth();
        }
    }

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.