Game of Life на Java, перенаселение, но не могу понять почему

Это домашнее задание. Я включил соответствующий код внизу.

Проблема: при попытке разрешить пользователю изменять размер сетки, она теперь сильно переполняется.

Снимки экрана: «Перенаселение» — http://i.imgur.com/zshAC6n.png «Желаемое население» — http://i.imgur.com/5Rf6P42.png< /а>

Предыстория: это версия игры Конвея "Жизнь". В классе мы завершили 3 класса: LifeState, который обрабатывает логику игры, LifePanel, который представляет собой JPanel, содержащий игру, и драйвер, который создает JFrame и добавляет LifePanel. Задача состояла в том, чтобы превратить его в полноценное приложение с графическим интерфейсом пользователя с различными требованиями. Мое решение состояло в том, чтобы расширить JFrame и выполнять большую часть моей работы в этом классе.

Инициализация LifePanel вне прослушивателя действий дает нормальное заполнение, но инициализация LifePanel в прослушивателе действий «переполняет» сетку.

Вопрос. Почему происходит перенаселение?

Класс LifePanel

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;

public class LifePanel extends JPanel implements MouseListener
{
private int row;
private int col;
private int scale;
private LifeState life;
boolean state;
boolean wrap;
int delay;
Timer timer;

public LifePanel(int r, int c, int s, int d)
{
    row = r;
    col = c;
    scale = s;
    delay = d;
    life = new LifeState(row,col);
    Random rnd = new Random();
    for(int i=0;i<row;i++)
        for(int j=0;j<col;j++)
            life.setCell(i,j,rnd.nextBoolean());
    timer = new Timer(delay, new UpdateListener());
    setPreferredSize( new Dimension(scale*row, scale*col));
    addMouseListener(this);
    timer.start();
}

public void paintComponent(Graphics g)
{
    super.paintComponent(g);
    for(int i=0;i<row;i++)
        for(int j=0;j<col;j++)
            if(life.getCell(i,j))
                g.fillRect(scale*i,scale*j,scale,scale);
}

public int getRow() {
    return row;
}

public void setRow(int row) {
    this.row = row;
}

public int getCol() {
    return col;
}

public void setCol(int col) {
    this.col = col;
}

public int getScale() {
    return scale;
}

public void setScale(int scale) {
    this.scale = scale;
}

public int getDelay() {
    return delay;

}

public void setDelay(int delay) {
    this.delay = delay;
    timer.setDelay(delay);
}

public void pauseGame(){
    timer.stop();
}
public void playGame(){
    timer.restart();
}
public void setInitState(boolean set){
    state = set;
    if(state){
      timer.stop();
    }
}
public void setWrap(boolean set){
    wrap = set;
    if(wrap){
    //implement allow wrap
    }
}

@Override
public void mouseClicked(MouseEvent e) {
  if(state){
    int x=e.getX(); 
    int y=e.getY();
    boolean isFilled;
    isFilled = life.getCell(x,y);
    //Test pop-up
    JOptionPane.showMessageDialog(this, x+","+y+"\n"+life.getCell(x,y));
    if(isFilled){
      life.setCell(x,y,false);
    }else{
      life.setCell(x,y,true);
    }
    repaint();
  }
}


@Override
public void mousePressed(MouseEvent e) {}

@Override
public void mouseReleased(MouseEvent e) {}

@Override
public void mouseEntered(MouseEvent e) {}

@Override
public void mouseExited(MouseEvent e) {}

private class UpdateListener implements ActionListener
{
    public void actionPerformed(ActionEvent e)
    {
        life.iterate();
        repaint();
    }
}


}

Класс LifeFrame

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*; 

public class LifeFrame extends JFrame implements ActionListener{ 

JMenuBar menuBar; 
JMenu mainMenu, helpMenu; 
JMenuItem restartItem, quitItem, helpItem; 
JButton stopButton, playButton, pauseButton, startButton; 
CardLayout cardLayout = new MyCardLayout(); 
CardLayout cardLayout2 = new MyCardLayout(); 
SetupPanel setupPanel; //panel for input 
LifePanel gamePanel;  //game panel 
JPanel controls = new JPanel(); //controls for game 
JPanel controls2 = new JPanel(); //controls for input panel 
JPanel cardPanel = new JPanel(cardLayout); 
JPanel cardPanel2 = new JPanel(cardLayout2); 
int gridRow=480; 
int gridCol=480; 
int scale=1; 
int delay=2;
boolean setState = false; 
boolean setWrap = false; 

public LifeFrame() { 
    setTitle("Game of Life"); 
    setLayout(new BorderLayout()); 

    //Add the Panels 
    setupPanel = new SetupPanel(); 
    gamePanel = new LifePanel(gridRow,gridCol,scale,delay); 
    cardPanel.add(setupPanel, "1");
    cardPanel.add(gamePanel, "2");
    add(cardPanel, BorderLayout.NORTH); 

    cardPanel2.add(controls2, "1"); 
    cardPanel2.add(controls, "2"); 
    add(cardPanel2, BorderLayout.SOUTH); 
    //init menu 
    menuBar = new JMenuBar(); 

    //button listener setup 
    stopButton = new JButton("Stop"); 
    pauseButton = new JButton("Pause"); 
    playButton = new JButton("Play"); 
    startButton = new JButton("Start"); 
    stopButton.addActionListener(this); 
    pauseButton.addActionListener(this); 
    playButton.addActionListener(this); 
    startButton.addActionListener(this); 
    //menu listener setup 
    restartItem = new JMenuItem("Restart", KeyEvent.VK_R); 
    quitItem = new JMenuItem("Quit", KeyEvent.VK_Q); 
    helpItem = new JMenuItem("Help", KeyEvent.VK_H); 
    restartItem.addActionListener(this); 
    quitItem.addActionListener(this); 
    helpItem.addActionListener(this); 
    //add buttons 
    controls.add(stopButton); 
    controls.add(pauseButton); 
    controls.add(playButton); 
    controls2.add(startButton); 
    //build the menus 
    mainMenu = new JMenu("Menu"); 
    mainMenu.setMnemonic(KeyEvent.VK_M); 
    helpMenu = new JMenu("Help"); 
    helpMenu.setMnemonic(KeyEvent.VK_H); 
    menuBar.add(mainMenu); 
    menuBar.add(helpMenu); 
    setJMenuBar(menuBar); 
    //add JMenuItems 
    restartItem.getAccessibleContext().setAccessibleDescription("Return to setup screen"); 
    mainMenu.add(restartItem); 
    mainMenu.add(quitItem); 
    helpMenu.add(helpItem); 


    this.addWindowListener(new WindowAdapter(){ 
        public void windowClosing(WindowEvent e){ 
            System.exit(0); 
        } 
    }); 

    pack(); 
    setLocationRelativeTo(null); 
    setVisible(true); 
    setDefaultCloseOperation(EXIT_ON_CLOSE); 
} 
@Override 
public void actionPerformed(ActionEvent e) { 
    try{ 
    gridRow = setupPanel.getRowSize(); 
    gridCol = setupPanel.getColSize(); 
    scale = setupPanel.getScale(); 
    delay = setupPanel.getDelay(); 
    setWrap = setupPanel.getSetWrap(); 
    setState = setupPanel.getSetState(); 
    }catch (NumberFormatException n){ 
        JOptionPane.showMessageDialog(LifeFrame.this, "Make sure the fields contain only digits and are completed!"); 
        return; 
    } 
    if(e.getSource() == pauseButton){ 
        gamePanel.pauseGame(); 
    }else if(e.getSource() == playButton){ 
        gamePanel.playGame(); 
    }else if(e.getSource() == quitItem){ 
        System.exit(0); 
    }else if(e.getSource() == restartItem || e.getSource() == stopButton){ 
        cardLayout.show(cardPanel, "1"); 
        cardLayout2.show(cardPanel2, "1"); 
        pack();
        setLocationRelativeTo(null);
    }else if(e.getSource() == helpItem){ 
        String helpText = "Help\nPlease make sure every field is completed and contains only digits\nCurrent Stats:\nGrid Size: "+gamePanel.getRow()+" by "+gamePanel.getCol()+"\nScale: "+ gamePanel.getScale() +"\nDelay: "+gamePanel.getDelay()+"\nManual Initial State: "+setState+"\nEnable Wrapping: "+setWrap;
        JOptionPane.showMessageDialog(LifeFrame.this, helpText); 
    }else if(e.getSource() == startButton){ 

        gamePanel = new LifePanel(gridRow,gridCol,scale,delay); 
        cardPanel.add(gamePanel, "2");
        /*
         * Alternate solution, throws array index out of bounds due to array usage in the LifePanel, but properly 
         * populates the grid.
         * 
        gamePanel.setRow(gridRow);
        gamePanel.setCol(gridCol);
        gamePanel.setScale(scale);
        gamePanel.setDelay(delay);
        */
        if(setWrap){ 
            gamePanel.setWrap(true); 
            gamePanel.playGame(); 
        }else if(setState){ 
            gamePanel.setInitState(true); 
        }else{ 
            gamePanel.setWrap(false); 
            gamePanel.setInitState(false); 
            gamePanel.playGame(); 
        }
        gamePanel.repaint(); 
        cardLayout.show(cardPanel, "2"); 
        cardLayout2.show(cardPanel2, "2"); 
        pack();
        setLocationRelativeTo(null);
    } 
} 
public static class MyCardLayout extends CardLayout { 

    @Override 
    public Dimension preferredLayoutSize(Container parent) { 

        Component current = findCurrentComponent(parent); 
        if (current != null) { 
            Insets insets = parent.getInsets(); 
            Dimension pref = current.getPreferredSize(); 
            pref.width += insets.left + insets.right; 
            pref.height += insets.top + insets.bottom; 
            return pref; 
        } 
        return super.preferredLayoutSize(parent); 
    } 

    public Component findCurrentComponent(Container parent) { 
        for (Component comp : parent.getComponents()) { 
            if (comp.isVisible()) { 
                return comp; 
            } 
        } 
        return null; 
    } 

} 
}

Спасибо, что прочитали все это, и заранее за любую помощь/совет, который вы предлагаете.

РЕДАКТИРОВАТЬ: добавлены снимки экрана и уточнен вопрос.


person user2272115    schedule 16.04.2013    source источник
comment
Это слишком много кода для вопроса StackOverflow. Вы запускали это в отладчике IDE и прослеживали код при изменении размера сетки? Здесь никто не собирается тратить несколько часов на чтение и понимание кода для его отладки. Я предлагаю вам сузить проблему, прежде чем публиковать здесь.   -  person Jim Garrison    schedule 17.04.2013
comment
По крайней мере, логика, определяющая численность населения, является единственным кодом ценности, все Swing/и т.д. вещи должны быть полностью изолированы от того, что происходит на самом деле.   -  person Dave Newton    schedule 17.04.2013
comment
Конструктор LifePanel инициализирует каждую ячейку с помощью Random.nextBoolean(). Таким образом, около половины ячеек в сетке будут активны, что обычно не является полезным начальным состоянием. Уточните, пожалуйста, что вы имеете в виду под перенаселением.   -  person Jim Garrison    schedule 17.04.2013
comment
Я беспокоился, что это слишком сложно, а в классе мы никогда не отлаживали и даже не видели ничего отлаживаемого. Я пытался читать об этом, но мои попытки не очень помогли мне. Если у вас есть ресурс, который предлагает обучение по отладке для нового программиста, я был бы признателен.   -  person user2272115    schedule 17.04.2013
comment
Если вы используете Eclipse, вам нужно будет установить начальную точку останова, а затем запустить приложение, используя опцию Отладка как... на значке с зеленой ошибкой.   -  person PM 77-1    schedule 17.04.2013
comment
Я использую Drjava, как того требует мой класс. Я также добавил снимки экрана, чтобы продемонстрировать проблему.   -  person user2272115    schedule 17.04.2013
comment
Я также попытался уточнить свой вопрос, чтобы сделать его более управляемым.   -  person user2272115    schedule 17.04.2013
comment
Я пробовал ваш код, и у меня не было перенаселения   -  person Joan    schedule 17.04.2013
comment
@Joan В классе LifeFrame внутри actionlistener есть прокомментированный код в событии кнопки запуска, если вы прокомментируете сеттеры под комментарием блока и раскомментируете две строки над ним, это продемонстрирует переполнение.   -  person user2272115    schedule 17.04.2013
comment
У меня есть комментарии сеттеров. Я использовал код, который вы разместили   -  person Joan    schedule 17.04.2013
comment
Если вы выберете «Установить начальное состояние» и запустите игру, будет ли она выглядеть как «Перенаселение» — i.imgur.com/zshAC6n .png или желаемое население-i.imgur.com/5Rf6P42.png?   -  person user2272115    schedule 17.04.2013
comment
Я получаю это изображение   -  person Joan    schedule 17.04.2013
comment
Запускаю программу с панелью жизней в паузе (таймер убираю), и получаю картинку перенаселения, если панель запущена, почти сразу получает нужную популяцию   -  person Joan    schedule 17.04.2013


Ответы (1)


В зависимости от того, как вы инициализируете LifePanel

Random rnd = new Random();
for(int i=0;i<row;i++)
    for(int j=0;j<col;j++)
        life.setCell(i,j,rnd.nextBoolean());

то, что вы называете «перенаселением», является ожидаемым состоянием. Вышеприведенный код установит около 1/2 ячеек в состояние «живых» (или «занятых»), как выглядит ваше «перенаселенное» состояние.

Скриншот «Желаемая популяция» содержит много артефактов «жизни», таких как «ульи», «планеры», «светофоры» и т. д., и был либо создан вручную, либо является результатом выполнения нескольких итераций на изначально 50% случайной популяции. При 50% занятом населении первое поколение приведет к полной очистке («смерти») многих-многих ячеек из-за правил близости.

Самое главное, учтите, что при запуске ваша программа не рисует первоначальную конфигурацию. По крайней мере, одна итерация происходит перед первым вызовом repaint().

Я не думаю, что ваш код вообще сломан, просто вы ожидаете, как будет выглядеть начальная популяция.

person Jim Garrison    schedule 16.04.2013
comment
Мое решение состояло в том, чтобы итерировать рост, прежде чем я перекрашу, чтобы соответствовать желаемой популяции. Я до сих пор не смог определить, почему желаемый рост происходит при инициализации в конструкторе, а перенаселение (не перенаселение в соответствии с тем, как я заполняю сетку) происходит в прослушивателе действий. Тем не менее код идентичен, но каким-то образом он повторяется около 150 раз при инициализации в конструкторе. - person user2272115; 23.04.2013