I get the feeling that I have no idea how the Swing timer works. I'm still new to the Java GUI API, and the program I am writing is just to test myself and help me get to know her internal developments better.
What he has to do is wait until the user presses the Start button, and then repeat the display (white or black JPanels grid), which displays a simple simulation of cellular machines with an interval of 1 second and pauses when there is a pause (similar to the button "Start", but changes the name). Each grid cell should start with a random color (white / black). Instead, it should stop for half a second or so, then “start” for another half second, then pause, then start, and so on and so forth.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class CA_Driver extends JFrame{ private JPanel gridPanel, buttonPanel; private JButton start_pause, pause; private static Timer timer; private Color black = Color.black; private Color white = Color.white; static Color[][] currentGrid, newGrid; static Cell[][] cellGrid; static boolean run, stop; static int height = 20, width = 30, state; public CA_Driver(){ stop = false; run = false; currentGrid = new Color[height][width]; newGrid = new Color[height][width]; cellGrid = new Cell[height][width]; //Initialize grid values for (int x = 0; x < currentGrid.length; x++) for (int y = 0; y < currentGrid[x].length; y++){ int z = (int) (Math.random() * 2); if (z == 0) currentGrid[x][y] = newGrid[x][y] = white; else currentGrid[x][y] = newGrid[x][y] = black; } //Create grid panel gridPanel = new JPanel(); gridPanel.setLayout(new GridLayout(height,width)); //Populate grid for (int x = 0; x < newGrid.length; x++) for (int y = 0; y < newGrid[x].length; y++){ cellGrid[x][y] = new Cell(x,y); cellGrid[x][y].setBackground(newGrid[x][y]); int z = (int) Math.random(); if (z == 0) cellGrid[x][y].setBackground(black); else cellGrid[x][y].setBackground(currentGrid[x][y]); gridPanel.add(cellGrid[x][y]); } //Create buttons state = 0; start_pause = new JButton(); start_pause.setText("Start"); start_pause.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { if (state == 0) { start_pause.setText("Pause"); run = true; timer.start(); state += 1; } else { start_pause.setText("Start"); run = false; timer.stop(); state -= 1; } } }); buttonPanel = new JPanel(new BorderLayout()); buttonPanel.add(start_pause, BorderLayout.NORTH); // buttonPanel.add(pause, BorderLayout.EAST); //Initialize and display frame this.add(gridPanel, BorderLayout.NORTH); this.add(buttonPanel, BorderLayout.SOUTH); this.setDefaultCloseOperation(EXIT_ON_CLOSE); //this.setSize(500, 500); pack(); this.setVisible(true); //Initialize timer timer = new Timer(1000, new ActionListener(){ public void actionPerformed(ActionEvent arg0) { for (int x = 0; x < cellGrid.length; x++) for (int y = 0; y < cellGrid[x].length; y++){ cellGrid[x][y].setColor(); currentGrid[x][y] = newGrid[x][y]; } //Display processing for next frame for (int x = 0; x < currentGrid.length; x++) for (int y = 0; y < currentGrid[x].length; y++){ int b = checkNeighbors(y,x); if (b > 4 || b < 2) newGrid[x][y] = black; else newGrid[x][y] = white; } if(!run) timer.stop(); } }); } public static void main(String[] args) { new CA_Driver(); } private int checkNeighbors(int w, int h){ int b = 0; //Top Left if((w != 0) && (h != 0) && (currentGrid[h - 1][w - 1] == black)) b++; //Top Middle if((h != 0) && (currentGrid[h - 1][w] == black)) b++; //Top Right if((w != width - 1) && (h != 0) && (currentGrid[h - 1][w + 1] == black)) b++; //Middle Left if((w != 0) && (currentGrid[h][w - 1] == black)) b++; //Middle Right if((w != width - 1) && (currentGrid[h][w + 1] == black)) b++; //Bottom left if((w != 0) && (h != height - 1) && (currentGrid[h + 1][w - 1] == black)) b++; //Bottom Middle if((h != height - 1) && (currentGrid[h + 1][w] == black)) b++; //Bottom Right if((w != width - 1) && (h != height - 1) && (currentGrid[h + 1][w + 1] == black)) b++; return b; } private class Cell extends JPanel{ private Color c; private int posx, posy; public Cell(int x, int y){ posx = x; posy = y; } public Point getLocation(){ return new Point(posx, posy); } public void setColor(){ c = newGrid[posx][posy]; setBackground(c); } public Dimension getPreferredSize(){ return new Dimension(10,10); } } }
This is the timer section:
timer = new Timer(1000, new ActionListener(){ public void actionPerformed(ActionEvent arg0) { for (int x = 0; x < cellGrid.length; x++) for (int y = 0; y < cellGrid[x].length; y++){ cellGrid[x][y].setColor(); currentGrid[x][y] = newGrid[x][y]; } //Display processing for next frame for (int x = 0; x < currentGrid.length; x++) for (int y = 0; y < currentGrid[x].length; y++){ int b = checkNeighbors(y,x); if (b > 4 || b < 2) newGrid[x][y] = black; else newGrid[x][y] = white; } if(!run) timer.stop(); } });
I plan to add additional features later to give the user more control over various variables, such as grid size and iteration speed, but I want the basic display functionality to work. I am sure the problem is how I use the Timer class, since this is the time that broke.
My first question is: am I using the Timer class correctly? If so, what is the problem? If not, how should I use it?
Update This is a good idea, MadProgrammer, and it's good to know that I am using Timer correctly. I realized that the part where it was “launched” was actually the time it took for each individual cell to refresh its color, so really my program is simply absurdly slow and inefficient, as it is now.
Here is my idea to improve speed and efficiency. Basically, I would use a timer delay to handle the output of the next iteration, and then when the timer “fires”, I would change the “tick” variable that each cell would use as its signal to change the color, as suggested. To do this, I added a timer to each cell (is it good or bad, is this an idea?), Which kills time a bit, and then in the lock cycle while expects the internal "tick" to be equivalent to the global "tick" and immediately change the color, when it will happen.
The end result is that it freezes as soon as it starts.
This is the timer that I added to the constructor of the Cell class:
c_timer = new Timer(500, new ActionListener(){ public void actionPerformed(ActionEvent e){ c_timer.stop(); while (c_tick != tick); setBackground(currentGrid[posx][posy]); c_tick = 1 - c_tick; if(run) timer.restart(); } }); c_timer.start();
And this is how I changed the global timer:
timer = new Timer(1000, new ActionListener(){ public void actionPerformed(ActionEvent arg0) { for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) currentGrid[y][x] = newGrid[y][x]; tick = 1 - tick; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++){ if (b[y][x] > 6 || b[y][x] < 1) newGrid[y][x] = white; else newGrid[y][x] = black; } for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) b[y][x] = checkNeighbors(x,y); if(!run) timer.stop(); } });
In addition to these changes, I removed the setColor() method in the Cell class. Can anyone point out the error I'm making?
UPDATE 2
I had to upgrade earlier, but just to put it, I found that this is not at all the way to do it. Instead of creating a panel with components and changing their backgrounds, you should simply draw the panel with a grid:
@Override public void paintComponent(Graphics g){ super.paintComponent(g); for (int h = 0; h < board_size.height; h++){ for (int w = 0; w < board_size.width; w++){ try{ if (grid[h][w] == BLACK) g.setColor(BLACK); else g.setColor(WHITE); g.fillRect(h * cell_size, w * cell_size, cell_size, cell_size); } catch (ConcurrentModificationException cme){} } } }
On each tick checkbox, you first redraw the grid, then process the next iteration, which will be drawn on the next tick. Much more efficient and instantly updated.
My I used a modified JPanel as the main component of the grid that implements an ActionListener to handle each action that the user performs on the rest of the gui, as well as for each timer:
public void actionPerformed(ActionEvent e) { //Timer tick processing: count surrounding black cells, define next iteration //using current rule set, update master grid if (e.getSource().equals(timer)){ //Processing for each tick } else if(e.getSource()... //Process events dispached by other components in gui }
Of course, you will need to set the panel pane as an action performer for the timer.