Related
My partner and I are trying to remake Tetris for our final project of the year in my Computer Science class we currently have a for loop that draws individual rectangles in an overwritten paint method.
private final int spacer = 30;
public int getSpacer()
{
return spacer;
}
public void paint(Graphics g) {
setBackground(Color.GRAY);
for(int i = getHeight()/2 - (spacer * 10); i < getHeight()/2 + (spacer * 10); i += spacer) {
for(int x = getWidth()/2 - (spacer * 5); x < getWidth()/2 + (spacer * 5); x += (spacer)) {
g.drawRect(x, i, (spacer), (spacer));
}
}
setForeground(Color.black);
}
The method basically takes the width and height of the window and makes a 10 x 20 grid of boxes that are 30 units, pixels I think, wide.
We'd like to make a Grid.java class that takes in color, the spacer int, and an x and y int. The constructor for Grid.java should draw the exact same thing as the code above using the for loop, but when we tried it gave us a white screen that would not resize with the window.
private final int spacer = 30;
private static Grid[][] arr = new Grid[10][20];
public int getSpacer()
{
return spacer;
}
public void paint(Graphics g) {
setBackground(Color.GRAY);
int countY = 0;
int countX = 0;
for(int y = getHeight()/2 - (spacer * 10); y < getHeight()/2 + (spacer * 10); y += spacer) {
for(int x = getWidth()/2 - (spacer * 5); x < getWidth()/2 + (spacer * 5); x += spacer) {
arr[countX][countY] = new Grid(x, y, spacer, g);
countX++;
}
countY++;
}
setForeground(Color.black);
}
*Grid.java Class*
package Tetris_Shapes;
import javax.swing.*;
import java.awt.*;
public class Grid {
private int x;
private int y;
private int side;
private Graphics g;
public Grid(int x, int y, int side, Graphics g) {
// g.drawRect(x, y, spacer, spacer);
this.x = x;
this.y = y;
this.side = side;
this.g = g;
paint(this.g);
}
private void paint(Graphics g) {
g.drawRect(x, y, side, side);
}
}
When we try and run this we get the white box that doesn't resize. My question is does anyone know of a way to get a constructor to draw shapes. Thank you in advance, this is pretty niche so I'm also going to apologize in advance.
I'm trying to learn how to make a 2D Game without Game Engines, anyways I already created a background scrolling right now my goal is to make my character jump. But the thing is whenever I start my app the character is spinning up and down and it will just go away to the background.
Here's my character code
public class Deer extends GameCharacter {
private Bitmap spritesheet;
private double dya;
private boolean playing;
private long startTime;
private boolean Jump;
private Animate Animation = new Animate();
public Deer(Bitmap res, int w, int h, int numFrames) {
x = 20;
y = 400;
dy = 0;
height = h;
width = w;
Bitmap[] image = new Bitmap[numFrames];
spritesheet = res;
for (int i = 0; i < image.length; i++)
{
image[i] = Bitmap.createBitmap(spritesheet, i*width, 0, width, height);
}
Animation.setFrames(image);
Animation.setDelay(10);
startTime = System.nanoTime();
}
public void setJump(boolean b){
Jump = b;
}
public void update()
{
long elapsed = (System.nanoTime()-startTime)/1000000;
if(elapsed>100)
{
}
Animation.update();
if(Jump){
dy = (int)(dya+=5.5);
}
else{
dy = (int)(dya+=5.5);
}
if(dy>14)dy = 14;
if(dy>14)dy = -14;
y += dy*2;
dy = 0;
}
public void draw(Canvas canvas)
{
canvas.drawBitmap(Animation.getImage(),x,y,null);
}
public boolean getPlaying(){return playing;}
public void setPlaying(boolean b){playing = b;}
public void resetDYA(){dya = 0;}
}
x - character's horizontal position
y - character's vertical position
dx - character's horizontal acceleration
dy - character's vertical acceleration
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN) {
if(!deer.getPlaying()) {
deer.setPlaying(true);
}
deer.setJump(true);
return true;
}
return super.onTouchEvent(event);
}
I can't say for sure if this is the only problem because you have other suspicious code but it looks like you jump no matter what.
if(Jump){
dy = (int)(dya+=5.5);
} else {
dy = (int)(dya+=5.5);
}
If Jump is true you set the vertical acceleration. But you also set the vertical acceleration to the same value if Jump is false. You also don't show in your code where Jump is ever set to false.
Another odd bit of code is:
if(dy>14)dy = 14;
if(dy>14)dy = -14;
Here, if dy>14 you set it to 14. Then you check dy>14 immediately after. Of course, this time it's false. But because those two conditions are the same the second one will never pass since the one before it ensures it won't. The only other option is they both fail. IOW, you'll never be able to enter the second if.
All that aside, I'm not sure why you're taking this approach. You can simply rely on physics equations with constant acceleration, give an initial velocity, check for a collision with the ground (or at least the original height), and just let it run. For example:
// These are the variables you need.
int x = 200, y0 = 0, y = 0, velocity = 15;
double t = 0.0, gravity = -9.8;
// This is the statement that should run when you update the GUI.
// It is the fundamental equation for motion with constant acceleration.
// The acceleration is the gravitational constant.
y = (int) (y0 + velocity * t + .5 * gravity * t * t);
if (y < 0) {
y = y0 = 0;
//Stop jumping!
Jump = false;
} else {
// Swap the y values.
y0 = y;
// Increase the time with the frame rate.
t += frameRate;
}
// Draw the character using the y value
The best part about this is you don't need to worry about when you get to the maximum height because the equation will automatically bring you down. It also looks more natural as if the mechanics are real. Try it out.
A simple Swing example that you can play around with. Note that the values are different to deal with the way the components are drawn to the screen. Normally, you would deal with that with transformations but this will do for the task.
public class Main {
static Timer timer;
Main() {
JFrame frame = new JFrame("Hello sample");
frame.setSize(new Dimension(550, 550));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel panel = new MyPanel();
frame.add(panel);
frame.setVisible(true);
timer = new Timer(5, (e) -> panel.repaint());
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(Main::new);
}
class MyPanel extends JPanel {
int x = 200, y0 = 300, y = 0, w = 200, h = 200, v = -8;
double t = 0.0, gravity = 9.8;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
y = (int) (y0 + v * t + .5 * gravity * t * t);
if (y > 300) {
y = y0 = 300;
// To prevent it from stopping comment the timer.stop() and
// uncomment the t = 0.0 statements.
//t = 0.0;
timer.stop();
} else {
y0 = y;
t += .025;
}
g.drawOval(x, y, w, h);
}
}
}
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);
}
}
}
I have a problem.
I am a beginner with java, and succeeded up to this point. Add bubbles with random sizes.
Now I need to make the bubbles escaping mouse when he gets near them.
Can anyone give me a hint how?
Thank you.
public class BounceBall extends JFrame {
private ShapePanel drawPanel;
private Vector<NewBall> Balls;
private JTextField message;
// set up interface
public BounceBall() {
super("MultiThreading");
drawPanel = new ShapePanel(400, 345);
message = new JTextField();
message.setEditable(false);
Balls = new Vector<NewBall>();
add(drawPanel, BorderLayout.NORTH);
add(message, BorderLayout.SOUTH);
setSize(400, 400);
setVisible(true);
}
public static void main(String args[]) {
BounceBall application = new BounceBall();
application.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
private class NewBall extends Thread {
private Ellipse2D.Double thisBall;
private boolean ballStarted;
private int size, speed; // characteristics
private int deltax, deltay; // of the ball
public NewBall() {
ballStarted = true;
size = 10 + (int) (Math.random() * 60);
speed = 10 + (int) (Math.random() * 100);
int startx = (int) (Math.random() * 300);
int starty = (int) (Math.random() * 300);
deltax = -10 + (int) (Math.random() * 21);
deltay = -10 + (int) (Math.random() * 21);
if ((deltax == 0) && (deltay == 0)) {
deltax = 1;
}
thisBall = new Ellipse2D.Double(startx, starty, size, size);
}
public void draw(Graphics2D g2d) {
if (thisBall != null) {
g2d.setColor(Color.BLUE);
g2d.fill(thisBall);
}
}
public void run() {
while (ballStarted) // Keeps ball moving
{
try {
Thread.sleep(speed);
} catch (InterruptedException e) {
System.out.println("Woke up prematurely");
}
// calculate new position and move ball
int oldx = (int) thisBall.getX();
int oldy = (int) thisBall.getY();
int newx = oldx + deltax;
if (newx + size > drawPanel.getWidth() || newx < 0) {
deltax = -deltax;
}
int newy = oldy + deltay;
if (newy + size > drawPanel.getHeight() || newy < 0) {
deltay = -deltay;
}
thisBall.setFrame(newx, newy, size, size);
drawPanel.repaint();
}
}
}
private class ShapePanel extends JPanel {
private int prefwid, prefht;
public ShapePanel(int pwid, int pht) {
prefwid = pwid;
prefht = pht;
// add ball when mouse is clicked
addMouseListener(
new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
NewBall nextBall = new NewBall();
Balls.addElement(nextBall);
nextBall.start();
message.setText("Number of Balls: " + Balls.size());
}
});
}
public Dimension getPreferredSize() {
return new Dimension(prefwid, prefht);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (int i = 0; i < Balls.size(); i++) {
(Balls.elementAt(i)).draw(g2d);
}
}
}
}
You should not have a Thread for each individual ball, this will not scale well, the more balls you add, the more threads you add. At some point, the amount of work it takes to manage the threads will exceed the benefit for using multiple threads...
Also, I doubt if your need 1000fps...something like 25fps should be more than sufficient for your simple purposes. This will give the system some breathing room and allow other threads within the system time to execute.
Lets start with a simple concept of a Ball. The Ball knows where it is and which direction it is moving it, it also knows how to paint itself, for example...
public class Ball {
private int x;
private int y;
private int deltaX;
private int deltaY;
private int dimeter;
private Ellipse2D ball;
private Color color;
public Ball(Color color, Dimension bounds) {
this.color = color;
Random rnd = new Random();
dimeter = 5 + rnd.nextInt(15);
x = rnd.nextInt(bounds.width - dimeter);
y = rnd.nextInt(bounds.height - dimeter);
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
int maxSpeed = 10;
do {
deltaX = rnd.nextInt(maxSpeed) - (maxSpeed / 2);
} while (deltaX == 0);
do {
deltaY = rnd.nextInt(maxSpeed) - (maxSpeed / 2);
} while (deltaY == 0);
ball = new Ellipse2D.Float(0, 0, dimeter, dimeter);
}
public void update(Dimension bounds) {
x += deltaX;
y += deltaY;
if (x < 0) {
x = 0;
deltaX *= -1;
} else if (x + dimeter > bounds.width) {
x = bounds.width - dimeter;
deltaX *= -1;
}
if (y < 0) {
y = 0;
deltaY *= -1;
} else if (y + dimeter > bounds.height) {
y = bounds.height - dimeter;
deltaY *= -1;
}
}
public void paint(Graphics2D g2d) {
g2d.translate(x, y);
g2d.setColor(color);
g2d.fill(ball);
g2d.translate(-x, -y);
}
}
Next, we need somewhere for the balls to move within, some kind of BallPit for example...
public class BallPit extends JPanel {
private List<Ball> balls;
public BallPit() {
balls = new ArrayList<>(25);
balls.add(new Ball(Color.RED, getPreferredSize()));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Ball ball : balls) {
ball.update(getSize());
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
for (Ball ball : balls) {
ball.paint(g2d);
}
g2d.dispose();
}
}
This maintains a list of balls, tells them when the need to update and when the need to paint. This example uses a simple javax.swing.Timer, which acts as the central timer which updates the balls and schedules the repaints.
The reason for this is takes care of synchronisation between the updates and the paints, meaning that the balls won't be updating while they are been painted. This is achieved because javax.swing.Timer triggers it's callbacks within the context of the EDT.
See Concurrency in Swing and How to use Swing Timers for more details.
Okay, so that fixes the threading issues, but what about the mouse avoidance...
That's a little more complicated...
What we need to is add a MouseMoitionListener to the BillPit and record the last position of the mouse.
public class BallPit extends JPanel {
//...
private Point mousePoint;
//...
public BallPit() {
//...
MouseAdapter handler = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
#Override
public void mouseExited(MouseEvent e) {
mousePoint = null;
}
};
addMouseListener(handler);
addMouseMotionListener(handler);
//...
The reason for including mouseExit is to ensure that balls don't try and move away from a phantom mouse cursor...
Next, we need to update Ball to have an "area of effect", this is the area around the ball that will trigger a change in movement if the mouse cursor moves within it's range...
public class Ball {
//...
private final Ellipse2D.Float areaOfEffect;
public Ball(Color color, Dimension bounds) {
//...
areaOfEffect = new Ellipse2D.Float(-10, -10, dimeter + 20, dimeter + 20);
}
Now, I also add some additional painting for debug reasons...
public void paint(Graphics2D g2d) {
g2d.translate(x, y);
g2d.setColor(new Color(0, 0, 192, 32));
g2d.fill(areaOfEffect);
g2d.setColor(color);
g2d.fill(ball);
g2d.translate(-x, -y);
}
Next, we need to modify the Ball's update method to accept the mousePoint value...
public void update(Dimension bounds, Point mousePoint) {
PathIterator pathIterator = areaOfEffect.getPathIterator(AffineTransform.getTranslateInstance(x, y));
GeneralPath path = new GeneralPath();
path.append(pathIterator, true);
if (mousePoint != null && path.contains(mousePoint)) {
// Determine which axis is closes to the cursor...
int xDistance = Math.abs(x + (dimeter / 2) - mousePoint.x);
int yDistance = Math.abs(y + (dimeter / 2) - mousePoint.y);
if (xDistance < yDistance) {
// If x is closer, the change the delatX
if (x + (dimeter / 2) < mousePoint.x) {
if (deltaX > 0) {
deltaX *= -1;
}
} else {
if (deltaX > 0) {
deltaX *= -1;
}
}
} else {
// If y is closer, the change the deltaY
if (y + (dimeter / 2) < mousePoint.y) {
if (deltaY > 0) {
deltaY *= -1;
}
} else {
if (deltaY > 0) {
deltaY *= -1;
}
}
}
}
//...Rest of previous method code...
}
Basically, what this is trying to do is determine which axis is closer to the mouse point and in which direction the ball should try and move...it's a little "basic", but gives the basic premise...
Lastly, we need to update the "update" loop in the javax.swing.Timer to supply the additional parameter
for (Ball ball : balls) {
ball.update(getSize(), mousePoint);
}
I'm going to answer this, but I'm very close to issuing a close vote because it doesn't show what you've done so far to attempt this. I would not be surprised if others are closer to the edge than I am on this. At the same time, you've clearly shown your progress before you reached this point, so I'll give you the benefit of the doubt. In the future, I would strongly advise making an attempt and then posting a question that pertains to the specific problem you're having while making that attempt.
You need two things:
The current location of the mouse
A range check and reversal of direction if too close.
The location of the mouse can be achieved by adding two variables (x and y) and, every time the mouse is moved (so add a mouse event listener to your JPanel or something) update those variables with the new location.
Then, you can do a range check (think Pythagorean theorem) on each bubble to make sure they're far enough away. If the bubble is too close, you'll want to check where that bubble would end up if it carried on its current course, as well as where it would end up if it changed X direction, Y direction, or both. Pick the one that ends up being furthest away and set the deltax and deltay to those, and let the calculation carry on as normal.
It sounds like a lot, but those are the two basic components you need to achieve this.
I am trying to make it so that the position of a pixel in an image (int i, int j) determines the color of that pixel. This is for an explosion effect in a java2d game I want to be extra cool by making the colors of the explosion depend on the position of the explosion. What I am doing currently is creating an ArrayList of colors then using i*j as index, testing this out on a 1000x1000 image shows a mirroring along the diagonal, naturally because i*j = j*i around the diagonal as show below.
Knowing that i=0, j=999 is the 1000th pixel while i=999, j=0 is the 999001th pixel how would you get a mapping f(i,j) != f(j,i) of pixels to colors without first storing the colors in a list? The color ordering is very important, that is to say colors are constructed using R,0,0 then 0,G,0 then 0,0,B
Question wasn't clear apparently.
Notice getAllColors, it creates the colors in order and adds them to the list, notice g2d.setColor(i*j), it sets the color in order except it mirrors along the diagonal. I want to know if I can map the colors to an index(in order) without storing it in a list while avoiding mirroring along the diagonal.
Full MCVE
public class AllColors extends JPanel {
private int width, height;
private double colorIncrement;
private List<Color> colors;
public AllColors(int width, int height) {
this.width = width;
this.height = height;
this.colorIncrement = 1.0 / Math.pow(1.0 * width * height, 1.0 / 3);
this.colors = new ArrayList<>(width * height);
getAllColors();
}
#Override
#Transient
public Color getBackground() {
return Color.black;
}
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
// Notice i*j= j*i around diagonal, the problem
g2d.setColor(colors.get(i * j));
g2d.fillRect(i, j, 1, 1);
}
}
}
private void getAllColors() {
for (float R = 0; R < 1.0; R += colorIncrement)
for (float G = 0; G < 1.0; G += colorIncrement)
for (float B = 0; B < 1.0; B += colorIncrement)
colors.add(new Color(R, G, B));
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AllColors allColors = new AllColors(800, 800);
frame.getContentPane().add(allColors);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
}
In the double loop check when i=j then skip the payload.
Knowing that i=0, j=999 is the 1000th pixel while i=999, j=0 is the 999001th pixel how would you get a mapping f(i,j) != f(j,i) of pixels to colors without first storing the colors in a list?
pixel = i * 1000 + j + 1;
As far as storing them in a list is concerned, that may be your best approach, since precalculation can often make things faster. Though I would probably do a two dimensional array. Like:
private void getAllColors() {
colors = new Color[1000][1000];
int i = 0; int j = 0;
loop:
for (float R = 0; R < 1.0; R += colorIncrement) {
for (float G = 0; G < 1.0; G += colorIncrement) {
for (float B = 0; B < 1.0; B += colorIncrement) {
colors[i++][j] = new Color(R, G, B));
if (i == 1000) {
j++;
i = 0;
if (j == 1000) break loop;
}
}
}
}
}