Swing timer for display grid - java

I'm getting the feeling that I have no idea how swing Timer works. I'm still new to the Java GUI API, and the program I'm writing is just to test myself and help me familiarize myself more with its inner workings.
What it's supposed to do is wait until the user presses the Start button, then iterate the display (a grid of white or black JPanels), which displays a simple cellular automata simulation at a 1 second interval, and pauses when the Pause button is pressed (same as the Start button, but changes name). Each cell in the grid is supposed to start with a random color (white/black). What it's instead doing is to pause for a half second or so, then "run" for another half second, then pause, then run, 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'm planning on adding more features later to give the user more control over various variables such as the grid size and iteration speed, but I want to get the core functionality of the display working. I'm fairly sure the issue is in how I'm using the Timer class since it's the timing that's broken.
My first question is: Am I using the Timer class right? If so, then what is the issue? If not, how should I be using it?
Update
That's a good idea, MadProgrammer, and it's good to know I'm using Timer correctly. I realized that the part where it was "running" was actually how long it took each individual cell to update its color, so really my program is just absurdly slow and inefficient as it is now.
Here's my idea to improve the speed and efficiency. Mainly, I would use the timer delay to process the output of the next iteration, then the next time the timer "fires" I would change a "tick" variable that each cell would use as their signal to change color, as suggested. To accomplish this, I've added a timer to each cell (how good/bad an idea is this?) that kill time for a bit, then, in a blocking while loop, wait to see that the internal "tick" is equivalent to the global "tick" and immediately change color when that happens.
The end result is that it freezes as soon as it starts.
This is the timer I added to the Cell class constructor:
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've modified 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();
}
});
Other than these changes, I removed the setColor() method in the Cell class. Can anyone point out the mistake that I'm making?
UPDATE 2
I should have updated earlier, but simply put, I discovered this is entirely the wrong way to do it. Instead of making a panel full of components and changing their backgrounds, you should instead just paint 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 timer "tick" you first repaint the grid, then you process the next iteration to be painted on the next tick. Far more efficient, and updates instantly.
My I used a modified JPanel as the main grid component which implements an ActionListener to process every action the user performs on the rest of the gui as well as each timer tick:
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'd have to set the board panel as the action listener for the timer.

Your usage of the Timer class in the first part of the question indeed looks correct. What is happening with a java.swing.Timer is that the ActionListener is triggered on the Event Dispatch Thread at specific intervals, specified with the delay parameter.
This also means that the code you put in the ActionListener should execute quickly. While your ActionListener code is executing, the UI cannot update as the UI thread (the Event Dispatch Thread) is occupied executing the ActionListener code. This is clearly documented in the javadoc of that class.
Although all Timers perform their waiting using a single, shared thread (created by the first Timer object that executes), the action event handlers for Timers execute on another thread -- the event-dispatching thread. This means that the action handlers for Timers can safely perform operations on Swing components. However, it also means that the handlers must execute quickly to keep the GUI responsive.
This is exactly what you encountered in your first update
new Timer(500, new ActionListener(){
public void actionPerformed(ActionEvent e){
//...
while (c_tick != tick){}
//...
}
});
With the while loop here you are blocking the Event Dispatch Thread. The c_tick != tick check will never change as the variables involved are only adjusted on the EDT, and you are blocking it with the loop.
Your second update seems to suggest everything is working now by switching from a panel. There are however two weird looking things:
The catch ConcurrentModificationException cme code block. In the code you posted I cannot immediately spot where you would encounter a ConcurrentModificationException. Remember that Swing is single-threaded. All actions which could interact with Swing components should be executed on the EDT, making the chance on encountering a ConcurrentModificationException a lot smaller compared to a multi-threaded application.
You stated
Of course, you'd have to set the board panel as the action listener for the timer
This seems untrue. Whatever ActionListener attached to the Timer needs to swap the current grid and the next grid, and calculate the next grid. Once the next grid is calculated, it needs to schedule a repaint of the grid panel. Whether or not this ActionListener is an anonymous/inner/separate class or the grid panel itself is irrelevant (at least functionality wise, design wise I would never opt to let the grid panel be a listener).
Side note: when you need to swap the current and new grid you use the following code
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
currentGrid[y][x] = newGrid[y][x];
}
}
If you still have performance problems, you can try using System.arrayCopy which is probably much faster then looping over the array manually.

Here is a game of life that updates the screen every half second in a conventional Java Swing manner.
It would be pretty simple to add controls for setting grid size and update rate and also an edit mode where the animation stops and cells can be set with the mouse. To change the update rate, call lifePane.run(newUpdateInterval) or lifePane.run(0) to pause. Call lifePane.setGenSize(width, height) to change the grid.
The main value in using a separate thread for the generation computation (as has been suggested, but I haven't done here) is that the animation will continue while you manipulate the GUI. For example if you use a slider to control speed, the animation will not pause is it will ifgenerations are computed in the UI thread.
Addition For grins, I added controls and used a java.utils.timer rather than Swing timer to get the effect of an extra thread for the rendering in this Gist.
But if you don't mind the pause while manipulating "mouse down" GUI items, single threading is fine. My old laptop runs a generation size of 1000x1000 at 20 updates per second in the Swing event thread with the GUI still behaving very nicely.
The method update() fills in the next generation from the current one and then swaps buffers. The override of paintComponent just draws the current generation. With this combination, all the timer needs to do is update and repaint.
Other conventions that may be useful to you are the method of handling window resizing and organizing the neighbor computation. Knowing good idioms helps avoid verbose code.
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class Life {
protected LifePane lifePane;
public static class LifePane extends JComponent {
private int rows, cols;
private byte[][] thisGen, nextGen;
private Timer timer;
public LifePane(int rows, int cols) {
setGenSize(rows, cols);
}
public final void setGenSize(int rows, int cols) {
this.rows = rows;
this.cols = cols;
thisGen = new byte[rows][cols];
nextGen = new byte[rows][cols];
Random gen = new Random();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
thisGen[i][j] = toByte(gen.nextBoolean());
}
}
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
// Clear the background.
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// Set the 1-valued cells black.
g.setColor(Color.BLACK);
int y0 = 0;
for (int i = 1; i < rows; i++) {
int y1 = i * height / (rows - 1);
int x0 = 0;
for (int j = 1; j < cols; j++) {
int x1 = j * width / (cols - 1);
if (thisGen[i][j] != 0) {
g.fillRect(x0, y0, x1 - x0, y1 - y0);
}
x0 = x1;
}
y0 = y1;
}
}
/**
* Make the next generation current.
*/
private void swapGens() {
byte [][] tmp = thisGen;
thisGen = nextGen;
nextGen = tmp;
}
private static byte toByte(boolean booleanVal) {
return booleanVal ? (byte) 1 : (byte) 0;
}
// Implementation of Conway's Game of Life rules.
private void updateCell(int x0, int x, int x1, int y0, int y, int y1) {
int n = thisGen[y0][x0] + thisGen[y0][x] + thisGen[y0][x1] +
thisGen[y] [x0] + thisGen[y] [x1] +
thisGen[y1][x0] + thisGen[y1][x] + thisGen[y1][x1];
nextGen[y][x] =
(thisGen[y][x] == 0) ? toByte(n == 3) : toByte(n >> 1 == 1);
}
private void updateRow(int y0, int y, int y1) {
updateCell(cols - 1, 0, 1, y0, y, y1);
for (int j = 1; j < cols - 1; ++j) {
updateCell(j - 1, j, j + 1, y0, y, y1);
}
updateCell(cols - 2, cols - 1, 0, y0, y, y1);
}
// Update the grid as a toroid and swap buffers.
public void update() {
updateRow(rows - 1, 0, 1);
for (int i = 1; i < rows - 1; i++) {
updateRow(i - 1, i, i + 1);
}
updateRow(rows - 2, rows - 1, 0);
swapGens();
}
/**
* Run the life instance with given update interval.
*
* #param updateInterval interval in milliseconds, <= 0 to stop
* #return this
*/
public LifePane run(int updateInterval) {
if (timer != null) {
timer.stop();
timer = null;
}
if (updateInterval > 0) {
timer = new Timer(updateInterval, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
});
timer.start();
}
return this;
}
}
public void run(int width, int height, int updateInterval) {
JFrame frame = new JFrame("Life");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
lifePane = new LifePane(width, height).run(updateInterval);
frame.setContentPane(lifePane);
frame.setPreferredSize(new Dimension(1024, 800));
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Life().run(100, 100, 500);
}
});
}
}

I'm planning on adding more features later to give the user more control over various variables such as the grid size and iteration speed, but I want to get the core functionality of the display working. I'm fairly sure the issue is in how I'm using the Timer class since it's the timing that's broken.
This is a good strategy, the program runs well, but it could be more efficient and scalable.
For example, I recommend using a custom SwingWorker class to execute your computation, and then send a message back to the UI.
Here is an example of how I would create this in a SwingWorker.
Here is addition information available from the Oracle resources site: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
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 stop;
static int height = 20, width = 30, state;
boolean run;
private synchronized boolean getRun()
{
return run;
}
private synchronized void setRun(boolean run)
{
this.run = run;
}
/**
* http://docs.oracle.com/javase/tutorial/uiswing/concurrency/simple.html
*
*/
SwingWorker worker = createNewWorker();
private SwingWorker createNewWorker()
{
return
new SwingWorker<Void, Void>()
{
protected Void doInBackground() throws Exception
{
while(getRun())
{
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;
}
}
}
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}
#Override
protected void done()
{
super.done();
}
};
}
public CA_Driver()
{
stop = false;
setRun(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");
setRun(true);
worker = createNewWorker();
worker.execute();
// timer.start();
state += 1;
}
else
{
start_pause.setText("Start");
setRun(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);
worker.execute();
/*
//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(!getRun())
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);
}
}
}

Related

Multiple bouncing dots issue

I trying to animate 50 bouncing dots with different colours. I am able to make the dots move but the dots drag on like it is being painted. I do not know why it turned to be like this
This is my codes for DotsPanel:
public DotsPanel(){
frameSize = 300;
setBounds (100, 100,frameSize,frameSize );
setPreferredSize (new Dimension(frameSize, frameSize));
setVisible(true);
x = 0;
y = 40;
moveX = moveY = 3;
for (int i = 0; i < dot.length; i++)
{
dot[i] = new Dot (frameSize, frameSize);
}
timer = new Timer(DELAY, new TimerListener());
timer.start();
}
private class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent arg0) {
for (int i = 0; i < dot.length; i++)
{
dot[i].animate();
}
repaint();
}
}
public void paintComponent(Graphics g){
for (int i = 0; i < dot.length; i++)
{
dot[i].draw(g);
}
}
public void run ()
{
while(true)
{
for (int i = 0; i < dot.length; i++)
{
dot[i].animate();
}
repaint();
}
}
This is the Dot class:
public Dot(int width, int height) {
this.width = width;
this.height = height;
x = r.nextInt(width - d);
y = r.nextInt(height - d);
speed = r.nextInt(10);
red = r.nextInt(256);
green = r.nextInt(256);
blue = r.nextInt(256);
moveX = moveY = 3;
}
public void animate ()
{
x += moveX;
y += moveY;
if (x <= 0 || x >= 300)
moveX = moveX * -1;
if (y <= 0 || y >= 300)
moveY = moveY * -1;
}
public void draw(Graphics g) {
g.setColor(new Color(red, green, blue));
//g.setColor(Color.BLUE);
g.fillOval(x, y, d, d);
}
I do not know which part went wrong. I am guessing is the draw method. I tried using paint instead of paintComponent but still the same result. And if i remove the while loop, the dots do not animate, I did this according to a tutorial i seen in youtube, the tutorial works well though.
Well, I guess it's simply because you don't "clear" the previous drawing each time a new one is drawn. As a result, you see all your previous graphics being continuously stacked !
EDIT (correct solution by camickr in comments below) :
The solution is to invoke super.paintComponent(g) at the top of the method to invoke the default painting functionality, which for a JPanel, will paint the background for you.

Block Collision Code Troubleshooting

I'm finishing up a coding assignment that requires me to set an array of blocks in motion, bouncing off of the window and each other, but unfortunately I'm totally lost as to where to go next. Any help would be appreciated (still getting the hang of coding, so I'm looking for all the help possible). Specifically, I need the rectangles to appear first, and then troubleshoot movement, and finally help with collision detection.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Driver implements ActionListener
{
private JFrame window;
private Timer timer;
private ChaseBlock[] blocks = new ChaseBlock[15];
// constants for graphics
private final int windowSize = 500;
private final int blockSize = 20;
/**
* Simple initiating main().
*
* #param args Not used.
*/
public static void main( String[] args )
{
Driver d = new Driver();
d.createWindow();
}
/**
* Set up the basic graphical objects.
*/
private void createWindow()
{
// create the window
window = new JFrame( "The Great Chase" );
window.setVisible( true );
window.setLayout( null );
window.getContentPane().setBackground( Color.white );
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setLocation( 50, 50 );
window.setSize(
windowSize + window.getInsets().left + window.getInsets().right,
windowSize + window.getInsets().top + window.getInsets().bottom );
window.setResizable( false );
window.repaint();
timer = new Timer(10, this);
timer.start();
blocks[1].setBackground(Color.blue);
window.repaint();
addBlocks();
}
private void addBlocks() {
for (int i = 0; i < blocks.length; i++) {
int x = i * blockSize + 10 * (i +1);
blocks[i] = new ChaseBlock(x, windowSize / x - blockSize / 2, blockSize, windowSize);
}
for (int i = 0; i < blocks.length; i++) {
window.add(blocks[i]);
}
}
private void animate() {
for (int i = 0; i < blocks.length; i++) {
blocks[i].move();
for (int b1 = 0; b1 < blocks.length; b1++)
for (int b2 = 0; b2 < blocks.length; b2++)
if (b1 != b2)
blocks[b1].checkCollision(blocks[b2]);
}
}
public void actionPerformed (ActionEvent e) {
animate();
}
}
This is the driver class that we use, with a Rectangle class also. Near to the bottom, the goal is to add the rectangles and make them move. My problem here is that the rectangles do not show up whatsoever or move.
import java.awt.Color;
public class ChaseBlock extends Rectangle {
private int dX, dY;
private int windowWidth = 500;
private int windowHeight = 500;
public ChaseBlock(int x, int y, int w, int h) {
super(x, y, w, h );
if (Math.random() < 0.5) {
dX = -1;
dY = -1;
setBackground(Color.green);
} else
dX = 1;
dY = 1;
setBackground(Color.blue);
}
public void move() {
setLocation(getX(), getY() + 5);
if(getX() < 0 || getY() + getWidth() >= windowWidth) {
dX = dX * -1;
}
if (getY() < 0 || getY() + getHeight() >= windowHeight) {
dY = dY * -1;
}
}
public void checkCollision(ChaseBlock blocks) {
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
boolean hit = false;
}
}
This is my class to define the movement and everything else. My problems here are that I need to use the checkCollision method to manage the collisions between the blocks themselves and the window, and in addition set colors for all the blocks.
First of all, the first condition for collision checks with window edges is incorrect. It should be
if(getX() < 0 || getX() + getWidth() >= windowWidth)
To detect collisions between two Axis Aligned Bounding Boxes (which is what you have as Rectangles), you just have to check whether the first's min and max points (x,y and x+h, y+h) are inside the second as follows:
if(a.x + a.h < b.x or a.x > b.x + b.h) return false;
if(a.y + a.h < b.y or a.y > b.y + b.h) return false;
return true;
If you want to find out which face (or which direction) the collision takes place on, you'll have to use the more long and slightly more complicated Separating Axis Theorem.

Button To Reset The View Of JFrame

I have to write a program that will generate 20 random circles with random radius lengths. If any of these circles intersect with another, the circle must be blue, and if it does not intersect, the color is red. I must also place a button on the JFrame. If this button is pressed, it needs to clear out the JFrame, and generate a new set of 20 circles following the same color rules. I am extremely new to Java Swing and am really stuck. I have everything working except the button. I cannot get a new set of circles to generate. Any help would be greatly appreciated. Thank You.
import java.awt.Graphics;
import javax.swing.JPanel;
import java.util.Random;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class IntersectingCircles extends JPanel
{
private int[] xAxis = new int [20]; // array to hold x axis points
private int[] yAxis = new int [20]; // array to hold y axis points
private int[] radius = new int [20]; // array to hold radius length
public static void main (String[] args)
{
JFrame frame = new JFrame("Random Circles");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add (new IntersectingCircles());
frame.pack();
frame.setVisible(true);
}
public IntersectingCircles()
{
setPreferredSize(new Dimension(1000, 800)); // set window size
Random random = new Random();
// Create coordinates for circles
for (int i = 0; i < 20; i++)
{
xAxis[i] = random.nextInt(700) + 100;
yAxis[i] = random.nextInt(500) + 100;
radius[i] = random.nextInt(75) + 10;
}
}
public void paintComponent(Graphics g)
{
// Add button to run again
JButton btnAgain = new JButton("Run Again");
btnAgain.setBounds(850, 10, 100, 30);
add(btnAgain);
btnAgain.addActionListener(new ButtonClickListener());
// Determine if circles intersect, create circles, color circles
for (int i = 0; i < 20; i++)
{
int color = 0;
for (int h = 0; h < 20; h++)
{
if(i != h)
{
double x1 = 0, x2 = 0, y1 = 0, y2 = 0, d = 0;
x1 = (xAxis[i] + radius[i]);
y1 = (yAxis[i] + radius[i]);
x2 = (xAxis[h] + radius[h]);
y2 = (yAxis[h] + radius[h]);
d = (Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1)*(y2 - y1))));
if (d > radius[i] + radius[h] || d < (Math.abs(radius[i] - radius[h])))
{
color = 0;
}
else
{
color = 1;
break;
}
}
}
if (color == 0)
{
g.setColor(Color.RED);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
else
{
g.setColor(Color.BLUE);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
}
}
private class ButtonClickListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
String action = e.getActionCommand();
if(action.equals("Run Again"))
{
new IntersectingCircles();
}
}
}
}
Suggestions:
Give your class a method that creates the random circles and calls repaint()
This method should create the circles and add them into an ArrayList.
Consider using Ellipse2D to represent your circles, and so you'd have an ArrayList<Ellipse2D>.
Call this method in your class constructor.
Call it again in the button's ActionListener.
Never add button's or change the state of your class from within your paintComponent method. This method is for drawing the circles and drawing them only and nothing more. Your way you will be creating the button each time the paintComponent method is called, so you could be potentially needlessly creating many JButtons
and needlessly slowing down your time-critical painting method.
Instead add the button in the constructor.
Be sure to call super.paintComponent(g) in your paintComponent method as its first call. This will clear the old circles when need be.
Also in paintComponent, iterate through the ArrayList of circles, drawing each one.
After some searching on this website, i found what i needed. Everything seems to work now. Thanks.
import java.awt.Graphics;
import javax.swing.JPanel;
import java.util.Random;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class IntersectingCircles extends JPanel
{
private int[] xAxis = new int [20]; // array to hold x axis points
private int[] yAxis = new int [20]; // array to hold y axis points
private int[] radius = new int [20]; // array to hold radius length
public static void main (String[] args)
{
JFrame frame = new JFrame("Random Circles");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(1000, 800));
ActionListener runAgain = new ActionListener()
{
#Override
public void actionPerformed(ActionEvent c)
{
frame.getContentPane().add(new IntersectingCircles());
frame.pack();
}
};
JButton btnAgain = new JButton("Run Again");
btnAgain.setBounds(850, 10, 100, 30);
btnAgain.addActionListener(runAgain);
frame.add(btnAgain);
frame.getContentPane().add (new IntersectingCircles());
frame.pack();
frame.setVisible(true);
}
public IntersectingCircles()
{
Random random = new Random();
// Create coordinates for circles
for (int i = 0; i < 20; i++)
{
xAxis[i] = random.nextInt(700) + 100;
yAxis[i] = random.nextInt(500) + 100;
radius[i] = random.nextInt(75) + 10;
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine if circles intersect, create circles, color circles
for (int i = 0; i < 20; i++)
{
int color = 0;
for (int h = 0; h < 20; h++)
{
if(i != h)
{
double x1 = 0, x2 = 0, y1 = 0, y2 = 0, d = 0;
x1 = (xAxis[i] + radius[i]);
y1 = (yAxis[i] + radius[i]);
x2 = (xAxis[h] + radius[h]);
y2 = (yAxis[h] + radius[h]);
d = (Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1)*(y2 - y1))));
if (d > radius[i] + radius[h] || d < (Math.abs(radius[i] - radius[h])))
{
color = 0;
}
else
{
color = 1;
break;
}
}
}
if (color == 0)
{
g.setColor(Color.RED);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
else
{
g.setColor(Color.BLUE);
g.drawOval(xAxis[i], yAxis[i], radius[i] * 2, radius[i] * 2);
}
}
}
}
private class ButtonClickListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
repaint();
}
}
Maybe you can have a try...

Chessboard game java

the code im trying to manipulate is the paint method... i'm trying to get it to show a chess board by filling in the squares evenly, but when i run the programme and move the slider to a even number it gives me one column with black one colum with empty etc.
when at an odd number it is the chess board
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
public class Blobs extends JFrame implements ActionListener, ChangeListener {
private MyCanvas canvas = new MyCanvas();
private JSlider sizeSl = new JSlider(0, 20, 0);
private JButton reset = new JButton("RESET");
private int size = 0; // number of lines to draw
public static void main(String[] args) {
new Blobs();
}
public Blobs() {
setLayout(new BorderLayout());
setSize(254, 352);
setTitle("Blobs (nested for)");
sizeSl.setMajorTickSpacing(5);
sizeSl.setMinorTickSpacing(1);
sizeSl.setPaintTicks(true);
sizeSl.setPaintLabels(true);
add("North", sizeSl);
sizeSl.addChangeListener(this);
add("Center", canvas);
JPanel bottom = new JPanel();
bottom.add(reset);
reset.addActionListener(this);
add("South", bottom);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
size = 0;
sizeSl.setValue(0);
canvas.repaint();
}
public void stateChanged(ChangeEvent e) {
size = sizeSl.getValue();
canvas.repaint();
}
private class MyCanvas extends Canvas {
#Override
public void paint(Graphics g) {
int x, y;
int n = 0;
for (int i = 0; i < size; i++) {
//n = 1 + i;
for (int j = 0; j < size; j++) {
n++;
x = 20 + 10 * i;
y = 20 + 10 * j;
//g.fillOval(x, y, 10, 10);
g.drawRect(x, y, 10, 10);
if (n % 2 == 0) {
g.fillRect(x, y, 10, 10);
}
}
}
}
}
}
The problem is that you count the number of drawn rectangles with n. This does not work for odd numbers. Easy fix:
for (int i = 0; i < size; i++) {
n = (i % 2);
This resets your counter n for each row alternately to 0 and 1.
If you lay out sequentially the squares of an NxN chess board with N being even, every N squares you'll get two equal ones in a row.
Therefore, if size is even you must adjust your method accordingly:
for (int i = 0; i < size; i++) {
n += size % 2 + 1;
for (int j = 0; j < size; j++) {
n++;
//...

Java Circle to Circle collision detection

I am making a circle to circle collision detection program. I can get the balls to move around but when the collision is detected, the balls are quite far overlapped. Any suggestions? Thanks in advance!
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.lang.Math;
public class ShapePanel extends JPanel{
private JButton button, startButton, stopButton;
private JTextField textField;
private JLabel label;
private Timer timer;
private final int DELAY = 10;
ArrayList<Shape> obj = new ArrayList<Shape>();
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ShapePanel());
frame.pack();
frame.setVisible(true);
}
public ShapePanel(){
JPanel controlPanel = new JPanel();
DrawingPanel dpanel = new DrawingPanel();
controlPanel.setPreferredSize(new Dimension(100,400));
button = new JButton("Add Shape");
startButton = new JButton("Start");
stopButton = new JButton("Stop");
textField = new JTextField(2);
label = new JLabel("Count:");
controlPanel.add(button);
controlPanel.add(label);
controlPanel.add(textField);
controlPanel.add(startButton);
controlPanel.add(stopButton);
add(controlPanel);
add(dpanel);
ButtonListener bListen = new ButtonListener();
button.addActionListener(bListen);
startButton.addActionListener(bListen);
stopButton.addActionListener(bListen);
timer = new Timer(DELAY, bListen);
}
private class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e){
if (e.getSource() == button){
obj.add(new Shape());
if (obj.get(obj.size()-1).y > 200){
obj.get(obj.size()-1).moveY = -obj.get(obj.size()-1).moveY;
}
}else if (e.getSource() == timer){
for (int i = 0; i < obj.size(); i++){
obj.get(i).move();
}
for (int i = 0; i < obj.size(); i++){
for (int j = i + 1; j < obj.size(); j++){
if (Math.sqrt(Math.pow((double)obj.get(i).centerCoordX - (double)obj.get(j).centerCoordX,2)) +
Math.pow((double)obj.get(i).centerCoordY - (double)obj.get(j).centerCoordY,2) <= obj.get(i).radius + obj.get(j).radius){
timer.stop();
}
}
}
}else if (e.getSource() == startButton){
timer.start();
}else if (e.getSource() == stopButton){
timer.stop();
}
repaint();
}
}
private class DrawingPanel extends JPanel{
DrawingPanel(){
setPreferredSize(new Dimension(400,400));
setBackground(Color.pink);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
for(int i = 0; i < obj.size(); i++){
obj.get(i).display(g);
}
}
}
}
import java.awt.*;
import java.util.*;
public class Shape{
public int x, y, width, height, moveX = 1, moveY = 1, centerCoordX, centerCoordY, radius;
private Color colour;
public boolean reverse = false, sameDirection = true;
Random generator = new Random();
public int randomRange(int lo, int hi){
return generator.nextInt(hi-lo)+lo;
}
Shape(){
width = randomRange(30, 50);
if (width % 2 != 0){
width = randomRange(30, 50);
}
height = width;
radius = width/2;
x = randomRange(0, 400-width);
y = randomRange(0, 400-height);
colour = new Color(generator.nextInt(256),generator.nextInt(256),generator.nextInt(256));
}
public void display(Graphics g){
g.setColor(colour);
g.fillOval(x, y, width, height);
}
void move(){
x += moveX;
y += moveY;
centerCoordX = x + width/2;
centerCoordY = y + height/2;
if(x >= 400-width){
moveX = -moveX;
}if(x <= 0){
moveX = -moveX;
}if(y >= 400-height){
moveY = -moveY;
}if (y <= 0){
moveY = -moveY;
}
}
}
So much uncommented code!
The balls are just colliding if their centres are within the sum of the radii. Let r1 and r2 be the ball radii, and x1, y1 the position of the centre of ball1; similarly x2, y2 for ball2.
Measure the square of the distance between the centres as (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1). (Pythagoras).
They have collided if this is less than or equal to (r1 + r2) * (r1 + r2).
The key thing here is that there is no need to compute the square roots which is an expensive computational task.
I'd say it probably due to your circles moving too fast or your time step is too high. Reduce your time step. A better approach is use a physics library like Box2D. Have a look at libgdx, that a java library that has it included.
its discussed a bit in this link
You should check the colision after each move.
for (int i = 0; i < obj.size(); i++)
{
obj.get(i).move();
for (int j = 0; j < obj.size(); j++)
{
if (Math.sqrt(Math.pow((double)obj.get(i).centerCoordX - (double)obj.get(j).centerCoordX,2)) +
Math.pow((double)obj.get(i).centerCoordY - (double)obj.get(j).centerCoordY,2) <= obj.get(i).radius + obj.get(j).radius && i!=j)
{
timer.stop();
}
}
}
You need something like this.
public boolean overlaps (Circle c1, Circle c2) {
float dx = c1.x - c2.x;
float dy = c1.y - c2.y;
float distance = dx * dx + dy * dy;
float radiusSum = c1.radius + c2.radius;
return distance < radiusSum * radiusSum;
}

Categories

Resources