I have a strange problem that I need help with.
This is a very simple application. I have a Rectangle Shape saved in a Car object.
public class Car {
private Rectangle rect;
private TheFourCarDeadlock.direction direction;
public Car(Rectangle r, TheFourCarDeadlock.direction d) {
this.rect = r;
this.direction = d;
}
public void move() {
double xPos;
double yPos;
switch (direction) {
case left:
// make sure x is a positive nr between 0 and 600
xPos = (rect.getX() - 1) % 600;
if (xPos < 0) {
xPos = 600;
}
rect.setX(xPos);
System.out.println(String.format("move left, x: %1.0f", xPos));
break;
case right:
xPos = (rect.getX() + 1) % 600;
rect.setX(xPos);
System.out.println(String.format("move right, x: %1.0f", xPos));
break;
case up:
yPos = (rect.getY() - 1) % 600;
rect.setY(yPos);
break;
case down:
yPos = (rect.getY() + 1) % 600;
rect.setY(yPos);
break;
default:
throw new AssertionError(direction.name());
}
}
}
The class Car move a car object to the left or right depending on the direction that is set.
public class TheFourCarDeadlock extends Application {
int maxWidth = 600;
int maxHeight = 600;
Car carFromLeft;
Car carFromRight;
public static enum direction {
left, right, up, down
};
#Override
public void start(Stage primaryStage) {
// setup
Group root = new Group();
Scene scene = new Scene(root, maxWidth, maxHeight);
// cars
// cars on left side going right
Rectangle leftSide = new Rectangle(0, 350 - 10, 20, 20);
leftSide.setFill(Color.BLUE);
root.getChildren().add(leftSide);
carFromLeft = new Car(leftSide, direction.right);
// cars on right side going left
Rectangle rightSide = new Rectangle(600, 250 + 10, -20, -20);
rightSide.setFill(Color.BLUE);
root.getChildren().add(rightSide);
carFromRight = new Car(rightSide, direction.left);
primaryStage.setTitle("The Four Car Deadlock");
primaryStage.setScene(scene);
primaryStage.show();
Thread t = new Thread(new DrawRunnable());
t.start();
}
public void moveCars() {
carFromLeft.move();
carFromRight.move();
}
private class DrawRunnable implements Runnable {
#Override
public void run() {
try {
while (true) {
Thread.sleep(30);
Platform.runLater(new Runnable() {
#Override
public void run() {
moveCars();
}
});
}
} catch (InterruptedException ex) {
System.out.println("Interrupted");
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
In the main application I create two cars, one going right and one going left. I move each car every 30 ms with a Thread t.
Now, the weird thing that I don't understand is that the carFromLeft works fine and is displayed in the form and then moved to the right as I planned. The carFromRight, that have exactly the same code and made the same way. Just stands still... Even though in the output it prints that the position x of the rectangle is changing like it's supposed to.
In the output I get this:
move left, x: 436
move right, x: 165
move left, x: 435
move right, x: 166
move left, x: 434
move right, x: 167
move left, x: 433
move right, x: 168
move left, x: 432
move right, x: 169
move left, x: 431
move right, x: 170
move left, x: 430
So the rectangles change their x position. One going left and one going right. But only the one moving to the right is updated and drawn in the form... What is going on?
You gave the rect an negative height and width - if you give it a postive one it shows up and moves for me from left to right.
BTW - In JavaFX you should not use threads to implement this feature because you are spamming the system with Platform.runLater calls. You should use an Animation e.g. a TranslateTransition
Related
I'm trying to do a quite easy boardgame (Carcassonne) but I'm having a lot of troubles with the graphic interface. The problem is that I don't see the way to make a relation between the mouse clicks and the gridpane row and columns.
The first tile is given and it's always added to the 100, 100 gridpanes position. You won't see it in the code, but for each tile added if the adjacents are empty it's added a white tile, so it looks like this:
Then, the player is expected to do a legal move ( we're not controlling cheaters, so yeah, it will be a weak game ) in the positions x = 99 y = 100, x = 101 y = 100, x = 100 y = 99, x = 100 y = 101.
But when i click there using the method play() the e.getSceneX(); method returns me the pixel position, and I need a way to convert it to a valid row index. So this is happening:
This is the console output:
tile XCCCC added at 100 100 // this is always given by the program, it's always the same
tile MFFCF added at 391 380
In this case, I clicked to the x = 100 y = 101 gridpane but the mouse returned me the pixel 391, 380.
Any idea?
This is the structure of my code:
public final class GUI extends Application {
private Game _game;
private GridPane _visualBoard;
#Override
public void start(final Stage primaryStage) {
// some stuff setting the gridpane, which will be inside a scrollpane and the scrollpane will be inside a borderpane which will alse have 2 additional VBoxes with the current turn information
play();
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
public void play() {
_visualBoard.setOnMousePressed((MouseEvent e) -> {
double x = e.getSceneX();
double y = e.getSceneY();
_game.doMove(x, y);
});
}
public void insertTile(myTile r, int x, int y) {
myVisualTile rV = new myVisualTile(r);
_visualBoard.add(rV, x, y);
}
And this is the class game:
public class Game {
private GUI _gui;
private List<Player> _players; // An arraylist of players
private int _currentPlayer; // The index of the current player in the ArrayList
private Board _board; // The logical board, totally separeted from the gridpane
private tileStack _stack;
public void doMove(double x, double y) {
int ax = (int) x;
int ay = (int) y;
if (_stack.isEmpty()) {
System.out.println("Game finnished");
//stuff
}
else {
myTile r = _stack.poll(); // takes the first tile of the stack
_gui.insertTile(r, ax, ay);
}
}
public void play() {
_visualBoard.setOnMousePressed((MouseEvent e) -> {
Node source = (Node)e.getTarget() ;
Integer x = GridPane.getColumnIndex(source);
Integer y = GridPane.getRowIndex(source);
_game.doMove(x, y);
});
}
That actualy worked. Thanks everybody!
So I'm trying to program snake on a JFrame and doing all graphical stuff (moving the 'snake', random food generation, etc.) on a JPanel. I'm in the beginning stages so all I'm trying to do right now is move a black square around on my frame using arrow keys. My while loop in the Panel class won't get interrupted by a key press in the Snake class, so is there a way to edit JPanel graphics from the same class with all my other code?
Here's all the code. My Panel class at the bottom follows the template I found here.
public class Snake {
// panel width and height
static int pW;
static int pH;
static int x = 10;
static int y = 10;
static int k;
static JFrame frame = new JFrame("SNAKE");
// getters for panel class
public int getPW() { return pW; }
public int getPH() { return pH; }
public int getX() { return x; }
public int getY() { return y; }
public static void main(String[] args) {
// get screen dimensions
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int sH = (int) screenSize.getHeight();
int sW = (int) screenSize.getWidth();
pW = (int) sW/2;
pH = (int) sH/2;
// initialize frame
frame.setSize (pW/1,pH/1);
frame.setLocation(pW/2,pH/2);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent e) {
k = e.getKeyCode();
switch(k) {
case 38: /* y -= square size */ break; // up
case 40: /* y += square size */ break; // down
case 37: /* x -= square size */ break; // left
case 39: /* x += square size */ break; // right
case 27: System.exit(0);
}
}
});
Panel panel = new Panel();
frame.add(panel);
frame.setVisible(true);
}
}
class Panel extends JPanel {
Snake snake = new Snake();
//square size and separation between squares
int sep = 0;
int size = 50;
// initial location of square on the panel/frame
int x = sep + size;
int y = sep + size;
// holding values to check if x or y have changed
int xH = x;
int yH = x;
public void paint(Graphics g) {
int pW = snake.getPW();
int pH = snake.getPH();
int i; int o;
Color on = Color.BLACK;
Color off = Color.GRAY;
// gray background
g.setColor(Color.GRAY);
g.fillRect(0,0,pW,pH);
// black square initialization
g.setColor(Color.BLACK);
g.fillRect(x, y, size, size);
/* this loop is supposed to check if the black
* rectangle has moved by repeatedly grabbing x & y
* values from the Snake class. When a key is pressed
* and the values change, a gray rectangle is placed at the old location
* and a black one is placed at the new location.
*
* When I run the program, I get stuck in this while loop.
* If I had the while loop in the same class I check for keys,
* I don't think I would have this problem
*/
while(true) {
x = snake.getX();
y = snake.getY();
if(x != xH || y != yH) {
g.setColor(off);
g.fillRect(xH, yH, size, size);
g.setColor(on);
g.fillRect(snake.getX(), snake.getY(), size, size);
xH = x;
yH = y;
}}
}
}
You should never have a while(true) loop in a painting method. This will just cause an infinite loop and your GUI will not be able to respond to events.
Instead you need to add methods to your snake class to move the snake. So when one of the arrow keys is pressed you update the starting position of the snake. Then the method will invoke repaint() and the snake will repaint itself when the paintComponent() method is invoked by Swing.
So your painting code should override paintComponent() not paint() and you should invoke super.paintComponent(g) as the first statement in the method.
Don't call your custom class "Panel", there is an AWT class with that name. Make your class name more descriptive.
I want to know how I can make an interaction when rectangle collapses with a circle . Like some kind of action that happens when circle and rectangle collapses. There is also a problem that circle can go out of frame, which I don't know how to limit it's movement. I'm not even sure if it's possible to do what I want this way.This is supposed to be a game where I need to dodge all rectangles and this is best I could do. Thanks it advance :)
public class Java2 extends Application {
public static final int KRUG_WIDTH = 10;
public static final int PANEL_WIDTH = 600;
public static final int PANEL_HEIGHT = 600;
private int mX = (PANEL_WIDTH - KRUG_WIDTH) / 2;
private int mY = (PANEL_HEIGHT - KRUG_WIDTH) / 2;
Random ran = new Random();
#Override
public void start(Stage primaryStage) {
Rectangle rekt = new Rectangle(20, 20);
Rectangle rekt1 = new Rectangle(20, 20);
Circle r1 = new Circle(mX,mY,KRUG_WIDTH);
Pane root = new Pane(); //PANE
r1.setFill(Color.WHITE);
r1.setStroke(Color.BLACK);
root.getChildren().add(r1);
root.getChildren().add(rekt);
root.getChildren().add(rekt1);
Scene scene = new Scene(root, PANEL_WIDTH, PANEL_HEIGHT);
PathTransition pathTransition = new PathTransition();
Path path = new Path();
//REKT-PATH
pathTransition.setDuration(javafx.util.Duration.millis(600));
pathTransition.setPath(path);
pathTransition.setNode(rekt);
pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransition.setCycleCount(2);
pathTransition.setAutoReverse(true);
pathTransition.setOnFinished(e -> {
pathTransition.setPath(createPath());
pathTransition.play();
});
pathTransition.play();
PathTransition pathTransition1 = new PathTransition();
Path path1 = new Path();
//REKT1-PATH
pathTransition1.setDuration(javafx.util.Duration.millis(550));
pathTransition1.setPath(path1);
pathTransition1.setNode(rekt1);
pathTransition1.setOrientation(
PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransition1.setCycleCount(Timeline.INDEFINITE);
pathTransition1.setAutoReverse(true);
pathTransition1.setOnFinished(e -> {
pathTransition1.setPath(createPath());
pathTransition1.play();
});
pathTransition1.play();
r1.setOnKeyPressed(e -> {
switch (e.getCode()) {
case DOWN: r1.setCenterY(r1.getCenterY()+ 10);
break;
case UP: r1.setCenterY(r1.getCenterY()- 10);
break;
case LEFT: r1.setCenterX(r1.getCenterX() - 10);
break;
case RIGHT: r1.setCenterX(r1.getCenterX() + 10);
break;
case SPACE:
break;
default:
}
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
r1.requestFocus();
}
private Path createPath() {
int loc2 = ran.nextInt(600 - 300 + 1) + 300;
int loc = ran.nextInt(600 - 20 + 1) + 20;
Path path = new Path();
path.getElements().add(new MoveTo(20, 20));
path.getElements().add(new LineTo(loc, loc2));
return path;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Your approach isn't one I would take. Usually you have a timer as a game loop in which all the magic is happening, i. e. move sprites, check collision, update sprites in the UI. In JavaFX this would be an AnimationTimer.
In your code you miss several important things, among them the keyboard or mouse input. Or how you check the collision. There is e. g. an intersects method for the nodes, but that checks only the rectangular bounds of a node. You however have a circle, which makes matters more complex. However, if performance isn't an issue for your game, you could use the Shape's intersect method to create a new shape out of Rectangle and Circle and depending on the result decide whether you have a collision or not.
Some time ago I created example code about how to move sprites on the screen and check if they collide with others.
Regarding bouncing off the scene bounds in that code you simply check the scene bounds and change the movement delta to a bouncing value, i. e. if the sprite moves from left to right (dx is positive) and is at the right side, you set the delta dx to a negative value so that it moves in the opposite direction.
You can also take a look at a simple Pong game which uses a slightly modified version of that engine.
Example for shape intersection with your code:
Shape shape = Shape.intersect(rekt, r1);
boolean intersects = shape.getBoundsInLocal().getWidth() != -1;
if( intersects) {
System.out.println( "Collision");
}
That alone raises the question where you'd put the check. Normally you'd have to perform it when anything moves, i. e. in both path transitions like this
pathTransition.currentTimeProperty().addListener( e -> {
...
});
and in the circle transition. That would be 2 checks too many.
I'm doing a Klondike game. The logic is all working. I'm just having trouble with the UI in javafx.
I've been trying to move/drag the cards from the 'tableau pile' arround without the expected result.
My card is a ImageView with an Image inside. The cards are inside a Pane:
Pane tableau = new Pane();
for (int i = 0; i < n; i++) {
Image img = new Image("resources/images/" + (i + 1) + ".png");
ImageView imgView = new ImageView(img);
imgView.setY(i * 20);
//imgView Mouse Events here
tableau.getChildren().add(imgView);
}
I tried:
imgView.setOnMousePressed((MouseEvent mouseEvent) -> {
dragDelta.x = imgView.getLayoutX() - mouseEvent.getSceneX();
dragDelta.y = imgView.getLayoutY() - mouseEvent.getSceneY();
});
imgView.setOnMouseDragged((MouseEvent mouseEvent) -> {
imgView.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
imgView.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
});
This solution dont work because I'm setting the positions so when release the card wont return to where it was, and because the card is colliding with other UI objects.
Another attempt:
imgView.setOnDragDetected((MouseEvent event) -> {
ClipboardContent content = new ClipboardContent();
content.putImage(img);
Dragboard db = imgView.startDragAndDrop(TransferMode.ANY);
db.setDragView(img, 35, 50);
db.setContent(content);
event.consume();
});
In this solution the problems are: the card comes semitransparent, like moving a file, the cursor becames no/forbidden but other than that it works well: no collisions and the card goes to his original place if I release the mouse.
Another problem is that I dont know if I can move more than 1 card with this solution?
My final question is, How can I move a node (in this case an ImageView) or a group of nodes, from one pile to another, like in a Solitaire Card Game?
For knowing the original card position you should use the setTranslateX (and Y) instead of setLayoutX in your mouse handler. So when the user releases the card, you can simply invoke a Transition and let the card fly back to the layout position. If the user releases the card on a valid place, you set the translate coordinates to 0 and change the layout position or use relocate.
If you want to make the cards semitransparent, you could e. g. change the opacity or apply CSS.
By the way, I wouldn't use Clipboardcontent, it seems inappropriate for your needs.
You can move multiple objects with your mouse handling code. You simple have to apply the translation to multiple objects simultaneously. When you drag a pile, you determine the cards on top of the selected card and apply the transition to all of them.
Here's a quick example to show you how it could look like.
Card.java, you'll use an ImageView.
public class Card extends Rectangle {
static Random rand = new Random();
public Card() {
setWidth(100);
setHeight(200);
Color color = createRandomColor();
setStroke(color);
setFill( color.deriveColor(1, 1, 1, 0.4));
}
public static Color createRandomColor() {
int max = 200;
Color color = Color.rgb( (int) (rand.nextDouble() * max), (int) (rand.nextDouble() * max), (int) (rand.nextDouble() * max));
return color;
}
}
Game.java, the application.
public class Game extends Application {
static List<Card> cardList = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
MouseGestures mg = new MouseGestures();
Group root = new Group();
for( int i=0; i < 10; i++) {
Card card = new Card();
card.relocate( i * 20, i * 10);
mg.makeDraggable(card);
cardList.add( card);
}
root.getChildren().addAll( cardList);
Scene scene = new Scene( root, 1600, 900);
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
// TODO: don't use a static method, I only added for the example
public static List<Card> getSelectedCards( Card currentCard) {
List<Card> selectedCards = new ArrayList<>();
int i = cardList.indexOf(currentCard);
for( int j=i + 1; j < cardList.size(); j++) {
selectedCards.add( cardList.get( j));
}
return selectedCards;
}
}
MouseGestures.java, the mouse handling mechanism.
public class MouseGestures {
final DragContext dragContext = new DragContext();
public void makeDraggable(final Node node) {
node.setOnMousePressed(onMousePressedEventHandler);
node.setOnMouseDragged(onMouseDraggedEventHandler);
node.setOnMouseReleased(onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
dragContext.x = event.getSceneX();
dragContext.y = event.getSceneY();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Node node = (Node) event.getSource();
double offsetX = event.getSceneX() - dragContext.x;
double offsetY = event.getSceneY() - dragContext.y;
node.setTranslateX(offsetX);
node.setTranslateY(offsetY);
// same for the other cards
List<Card> list = Game.getSelectedCards( (Card) node);
for( Card card: list) {
card.setTranslateX(offsetX);
card.setTranslateY(offsetY);
}
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Node node = (Node) event.getSource();
moveToSource(node);
// same for the other cards
List<Card> list = Game.getSelectedCards( (Card) node);
for( Card card: list) {
moveToSource(card);
}
// if you find out that the cards are on a valid position, you need to fix it, ie invoke relocate and set the translation to 0
// fixPosition( node);
}
};
private void moveToSource( Node node) {
double sourceX = node.getLayoutX() + node.getTranslateX();
double sourceY = node.getLayoutY() + node.getTranslateY();
double targetX = node.getLayoutX();
double targetY = node.getLayoutY();
Path path = new Path();
path.getElements().add(new MoveToAbs( node, sourceX, sourceY));
path.getElements().add(new LineToAbs( node, targetX, targetY));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(1000));
pathTransition.setNode(node);
pathTransition.setPath(path);
pathTransition.setCycleCount(1);
pathTransition.setAutoReverse(true);
pathTransition.play();
}
/**
* Relocate card to current position and set translate to 0.
* #param node
*/
private void fixPosition( Node node) {
double x = node.getTranslateX();
double y = node.getTranslateY();
node.relocate(node.getLayoutX() + x, node.getLayoutY() + y);
node.setTranslateX(0);
node.setTranslateY(0);
}
class DragContext {
double x;
double y;
}
// pathtransition works with the center of the node => we need to consider that
public static class MoveToAbs extends MoveTo {
public MoveToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
// pathtransition works with the center of the node => we need to consider that
public static class LineToAbs extends LineTo {
public LineToAbs( Node node, double x, double y) {
super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
}
When you pick a card or a multiple of cards, they'll transition back to their origin.
Here's a screenshot where I dragged the 3rd card from top.
When you check if a card is on a valid destination, you should invoke the fixPosition method. It simply relocates the card, i. e. calculates the position using the translation values, repositions the node and sets its translation to 0.
The tricky part was the PathTransition. It's working from the center of a node, not from the x/y coordinates. Here's a thread regarding that issue to which I replied in case you wish to know more about it.
I'm trying to create a Java applet that bounces several balls within an applet window, each with its own thread. With the code below all the balls are drawn, but only the first one moves. What am I doing wrong?
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static java.awt.Color.*;
public class BouncingBalls extends Applet implements Runnable {
List<Ball> ballList = new ArrayList(); // holds Ball objects
Color[] colors = new Color[]{BLACK, GRAY, WHITE, PINK, RED, ORANGE, YELLOW,
GREEN, BLUE, CYAN}; // array holding available colors
static int width, height; // variables for applet dimensions
int ballCount; // number of balls to be created, set by html parameter
Random random = new Random(); // random number generator
public void init() {
// get window dimensions
width = getSize().width;
height = getSize().height;
//get number of balls from html
String ballCountString = this.getParameter("ballCount");
try {
ballCount = Integer.parseInt(ballCountString);
} catch (NumberFormatException e) {
ballCount = 10; // set to 10 by default
}
for (int i = 0; i < ballCount; i++) {
// randomly assign ballDiameter between 1 and 20
int ballDiameter = random.nextInt(20) + 1;
// create and add balls to ballList
ballList.add(new Ball(
random.nextInt(width - ballDiameter), // set x coordinate
random.nextInt(height - ballDiameter), // set y coordinate
ballDiameter, // set ballDiameter
random.nextInt(ballDiameter) + 1, // deltaX <= ballDiameter
random.nextInt(ballDiameter) + 1, // deltaY <= ballDiameter
colors[i % 10] // use remainder to choose colors[] element
)
);
} // end for
} // end init
public void start() {
for (Ball ball: ballList) {
Thread t;
t = new Thread(this);
t.start();
} // end for
} // end start
public void run() {
for (Ball ball : ballList) {
// infinite loop: ball moves until applet is closed
while (true) {
ball.move();
repaint(); // call paint method to draw circle in new location
// set ball repaint delay using Thread sleep method
try {
Thread.sleep(20); // wait 20 msec before continuing
} catch (InterruptedException e) {
return;
}
} // end while
} // end for
} // end run
public void paint(Graphics g) {
super.paint(g);
for (Ball ball : ballList) {
// set current color
g.setColor(ball.ballColor);
// draw filled oval using current x and y coordinates and diameter
g.fillOval(ball.x, ball.y, ball.diameter, ball.diameter);
} // end for
} // end paint
}
class Ball {
int x, y, // coordinates of upper-left corner of circle
diameter, // circle diameter
deltaX, deltaY; // number of pixels ball moves each time it's repainted
Color ballColor;
public Ball(int x, int y, int diameter, int deltaX, int deltaY,
Color ballColor) {
this.x = x;
this.y = y;
this.diameter = diameter;
this.deltaX = deltaX;
this.deltaY = deltaY;
this.ballColor = ballColor;
} // end Ball constructor
public void move() {
// update x and y coordinates using delta values
x += deltaX;
y += deltaY;
// reverse x direction when ball reaches boundary
if (x >= BouncingBalls.width - diameter || x <= 0){
deltaX = -deltaX;
} // end if
// reverse y direction when ball reaches boundary
if (y >= BouncingBalls.height - diameter || y <= 0) {
deltaY = -deltaY;
} // end if
} // end move
} // end BouncingBalls
Your while(true) should be outside the for loop. It sits on the first ball returned from the iterator.
That being said, you may want to go over your logic for one ball-per-thread. It actually appears to create N number of threads (N being the number of balls) in which each thread will move all the balls instead of just one.
Edit to address my second point:
Let's say you have 10 balls. You start 10 threads, and each thread iterates over all balls.
For instance:
Thread 1:
public void run(){
for(Ball b : ballList){
b.move();
b.repaint();
}
}
Thread 2:
public void run(){
for(Ball b : ballList){
b.move();
b.repaint();
}
}
Thread N:
public void run(){
for(Ball b : ballList){
b.move();
b.repaint();
}
}
This is done because you create threads with the same this instance of a runnable that iterates over each ball.
public void start() {
for (Ball ball: ballList) {
Thread t;
t = new Thread(this);
t.start();
} // end for
} // end start
So I would think, if each ball is supposed to move 1 unit every 20 milliseconds. You should end up seeing every 20 milliseconds moving N*1 units in this case 10 every 20 milliseconds.
Edit - With regards to a suggestion.
Instead of setting this as the runnable you should remove the implementation of Runnable from the this class and create a new Runnable that will take a single Ball as a parameter.
private static class MovingRunnable implements Runnable{
private final Ball b;
private MovingRunnable(Ball b){this.b=b;}
public void run(){
for(;;){
b.move();
b.repaint();
Thread.sleep(20);
}
}
}
Later in your start method
public void start() {
for (Ball ball: ballList) {
Thread t;
t = new Thread(new MovingRunnable(ball));
t.start();
} // end for
} // end start
So here each Ball has it's own thread with it's own Runnable. Each ball will now only invoke move once per thread every 20 milliseconds.
But it still isn't perfect because repaint should only be invoked by the UI thread, having it invoked by each thread can cause different issues (though you may not notice any, it's just worth saying).