Removing object from a collection while iterating over it - java

I'm trying to remove objects that are outside of the JPanel.
However, when I do that I get this error
and my program crashes.
I was told by my lecturer that it's because two Threads are accessing the ArrayList that stores my objects.
I did to synchronize the functions but it didn't work.
Bullet
public void move(){
if(y< -height){
synchronized (this) {
bullet.remove(this);
}
}
y-=5;
}
Relevant Classes:
Application
import javax.swing.*;
public class Application {
public static String path ="C:\\Users\\jarek\\OneDrive\\NUIG Private\\(2) Semester 2 2019\\Next Generation Technologies II CT255\\Assignment 3\\";
private Application(){
JFrame frame = new JFrame("Ihsan The Defender");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GamePanel gamePanel= new GamePanel();
frame.add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
new Thread(gamePanel).start();
}
public static void main (String args[]){
new Application();
}
}
GamePanel
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.security.Key;
import java.util.ArrayList;
public class GamePanel extends JPanel implements Runnable, KeyListener{
private String path = Application.path;
Image gameOverImg = new ImageIcon(path+"//images//gameover1.png").getImage();
private Ihsan ihsan;
private ArrayList <David> david = new ArrayList<>();
private int enemies=5;
private boolean pause=false;
private boolean gameOver=false;
GamePanel(){
ihsan = new Ihsan(this);
for(int i=0; i<enemies; i++){
david.add(new David(this));
}
setFocusable(true);
requestFocusInWindow();
addKeyListener(this);
}
#Override
public void run() {
while (!pause){
repaint();
for(David david:david){
david.move();
}
for(Bullet bullet:Bullet.bullet){
bullet.move();
}
try{Thread.sleep(30);}
catch (InterruptedException e){}
}
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.GRAY);
g2d.fillRect(0,0 ,getWidth(), getHeight());
for(David david : david){
g2d.drawImage(david.getImg(), david.getX(), david.getY(), null);
}
g2d.drawImage(ihsan.getImg(), ihsan.getX(), ihsan.getY(), null);
for (Bullet bullet:Bullet.bullet){
g2d.drawImage(bullet.getImg(), bullet.getX(), bullet.getY(), null);
}
if(gameOver){
g2d.drawImage(gameOverImg,0,getHeight()/4,null);
}
}
private static final Dimension DESIRED_SIZE = new Dimension(600,700);
#Override
public Dimension getPreferredSize(){
return DESIRED_SIZE;
}
public void setGameOver(boolean gameOver) {
this.gameOver = gameOver;
}
#Override
public void keyPressed(KeyEvent e) {
int key=e.getKeyCode();
if (key==KeyEvent.VK_D || key==KeyEvent.VK_RIGHT){
ihsan.move(4,0);
System.out.println("Right Key");
}
if (key==KeyEvent.VK_A || key== KeyEvent.VK_LEFT){
ihsan.move(-4,0);
System.out.println("Left Key");
}
if(key==KeyEvent.VK_SPACE){
Bullet.bullet.add(new Bullet(this,ihsan.getX()+(ihsan.getWidth()/2), ihsan.getY()));
}
}
#Override
public void keyTyped(KeyEvent e) { }
#Override
public void keyReleased(KeyEvent e) { }
public boolean getGameOver(){
return gameOver;
}
}
Bullet
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class Bullet {
//Environment
public static ArrayList<Bullet> bullet = new ArrayList<>();
private String path = Application.path;
private GamePanel gp;
//properties
private int x,y;
private int width,height;
private int yVector;
private Image image;
Bullet(GamePanel gp, int x, int y){
image = new ImageIcon(path+"\\images\\javaicon.png").getImage();
width=image.getWidth(null);
height=image.getHeight(null);
this.gp=gp;
this.x=x;
this.y=y;
yVector=5;
}
public void move(){
if(y< -height){
bullet.remove(this);
}
y-=5;
}
public Image getImg(){
return image;
}
public int getX(){
return x;
}
public int getY(){
return y;
}
}

Your current problem is not with synchronization, but that you modify the bullet list while iterating over it:
// GamePanel.java#run():
for (Bullet bullet:Bullet.bullet) { //your code is iterating over Bullet.bullet here
bullet.move(); //you call Bullet#move here
}
// Bullet.java#move():
public void move(){
if(y< -height){
bullet.remove(this); //this will remove the current bullet from Bullet.bullet
// ultimately causing the ConcurrrentModificationException in GamePanel.run()
}
y-=5;
}
Synchronization won't help, since both actions occur within the same thread.
To solve this issue the Bullet.move() method needs to return a boolean indicating whether it should be removed from the list. And GamePanel.run() must not use an enhanced for loop but an iterator (removing an element from a list using Iterator.remove() is safe if this is the only active Iterator):
// Bullet.java#move():
public boolean move(){
if(y< -height){
return true; // instruct GamePanel.run() to remove this bullet
}
y-=5;
return false; // keep this bullet
}
// GamePanel.java#run():
Iterator<Bullet> it = Bullet.bullet.iterator();
while (it.hasNext()) {
Bullet bullet = it.next();
if (bullet.move()) { // if bullet should be removed
it.remove(); // remove it from the list
}
}
There are other issues too:
you call #repaint() from your own thread instead of the Swing EDT
repainting iterates over the same Bullet.bullet list without synchronization (which may lead to a ConcurrentModificationException within GamePanel.paint())

The synchronized block must be around every piece of code that accessed or modifies the ArrayList. The object put in the parenthesis must be the same: It’s the lock.
Create a field of type Object named bulletLock for example, and use it as a lock, every time you access bullet.
The error occurs because you’re removing a bullet while another thread is in a for-loop on the list. As there is a concurrent modification, it can’t continue safely.
Another solution would be to make a copy of the ArrayList before your for-loop.

A simple solution to the problem described in Thomas Kläger answer could be:
for(Bullet bullet: new ArrayList(Bullet.bullet) ){ //iterate over a copy
bullet.move();
}
Alternatively:
Iterator<Bullet> it = Bullet.bullet.iterator();
while (it.hasNext()) {
Bullet bullet = it.next();
bullet.move();
}
without changing other parts of the code.

Related

Updating JPanel based on results from slow server (Using threads to not block GUI)

So I have been looking to update one of my panels in a my client code with data that comes from a server in Indonesia. The delay is rather long (2-8) sec and Im noticing that my UI is freezing during the time it takes for the response to return from the server.
The response will be used to draw some points on a map (not yet implemented).
I have been looking all over the net to find out how to do it and I have come across:
InvokeLater.
SwingWroker.
ScheduledThreadPoolExecutor.
Making the JPanel a runnable to run in its own thread(seems like best option).
http://www.java2s.com/Tutorial/Java/0160__Thread/CreateathreadtoupdateSwing.htm
But tbh most of the data i find is out dated (more than 5 years old).
Here is the JPanel class i want to update based on a server query:
public class MapPanel extends JPanel implements Pointable, Runnable {
private static final long serialVersionUID = 1L;
private List<Shape> shapes = new LinkedList<>();
private State mapPanelState;
public Shape selected;
private BufferedImage image;
public MapPanel() {
Commander.getInstance().addShapeContainer(this);
mapPanelState = NoState.getInstance();
MouseHandler mouseHandler = new MouseHandler(this);
KeyListener keyListener = new KeyListener();
readImage();
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
this.addKeyListener(keyListener);
this.setBackground(Color.white);
this.setFocusable(true);
this.requestFocusInWindow();
}
private void readImage(){
try {
image = ImageIO.read(new File("/MapCoordProject/earthmap1.jpg"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setState(State state) {
mapPanelState = state;
}
public List<Shape> getShapes() { return shapes; }
public void setShapes(List<Shape> shapes) {
this.shapes = shapes;
}
public Shape getLastShape(){ return shapes.get(shapes.size()-1); }
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
for (Shape shape : shapes)
shape.draw((Graphics2D) g);
}
public void select(Point point) {
for (Shape shape : shapes) {
if (shape.intersects(point)) {
selected = shape;
}
}
}
#Override
public Dimension getPreferredSize() {
if (image == null) {
return super.getPreferredSize();
} else {
int w = image.getWidth();
int h = image.getHeight();
return new Dimension(w, h);
}
}
public void pointerDown(Point point) {
mapPanelState.pointerDown(point, this);
}
public void pointerUp(Point point) {
mapPanelState.pointerUp(point, this);
selected = null;
}
public void pointerMoved(Point point, boolean pointerDown) {
mapPanelState.pointerMoved(point, pointerDown, this);
}
#Override
public void run() {
}
}
I want a method that updates the "Shapes" array in a separate thread to stop everything from freezing.
Any suggestions?
Making your JPanel implement Runnable is not the best solution. There is no reason to expose a run() method to other classes.
Instead, create a private void method that takes no arguments. A method reference that refers to that method can act as a Runnable, since it will have the same arguments and return type. You can then pass it to a Thread constructor.
public MapPanel() {
// ...
readImage();
new Thread(this::readShapes, "Reading shapes").start();
// ...
}
private void readShapes() {
try {
List<Shape> newShapes = new ArrayList<>();
URL server = new URL("https://example.com/indonesia/data");
try (InputStream dataSource = server.openStream()) {
while ( /* ... */ ) {
Shape shape = /* ... */;
newShapes.add(shape);
}
}
EventQueue.invokeLater(() -> setShapes(newShapes));
} catch (IOException e) {
e.printStackTrace();
EventQueue.invokeLater(() -> {
JOptionPane.showMessageDialog(getTopLevelContainer(),
"Unable to retrieve data:\n" + e, "Load Error",
JOptionPane.ERROR_MESSAGE);
});
}
}
Notice that calls to methods involving Swing objects are always wrapped in a call to EventQueue.invokeLater, to make sure they run on the correct thread.
It is possible to improve this by creating a progress dialog that shows while the data is being loaded, but that would make this answer much longer and would require more knowledge about the Indonesian API you’re calling.

My 2dGameEngine is very laggy

I originally wrote started writing this on windows, and it ran pretty smoothly, though it occasionaly would freeze up; but, after i switched to linux, the graphics are very very choppy. I expect it worked before because I had the right drivers to my video card, but after the switch, I may be getting less performance from it do to bad drivers. Although, a 2d game engine should be able to run on pretty bad computers. I was wondring why it is running so slow, and how i could make run faster. There are other classes aside from the one below, but the one below contains most of main functions of the program.
package Platformer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import javax.sound.sampled.*;
import javax.sound.sampled.LineEvent.Type;
import javax.swing.JPanel;
import javax.swing.Timer;
public abstract class GameComponent extends JPanel{
//delete this i think
public static int ticks=0;
public Rectangle box;
private final int FPS=40;
public int xMovement=0;
public int yMovement=0;
public final int A=0,D=1,S=2,W=3,SPACE=4;
public boolean[] keyPressed=new boolean[5];
private InputController keyListener;
public ArrayList<Entity> Entities=new ArrayList<Entity>();
HashMap sounds=new HashMap();
public boolean drawLines=false;
public int xAxisLines=40;
public int yAxisLines=27;
Quadtree quad=new Quadtree(0,new Rectangle(0,0,1920,1080));
public GameComponent(){
keyListener=new InputController();
box=new Rectangle(90,90,20,20);
setFocusable(true);
requestFocusInWindow(true);
run();
}
public void run(){
addKeyListener(keyListener);
Timer timer=new Timer(1000/FPS, new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
//movement();
gameLoop();
collisions();
repaint();
ticks++;
if(ticks==360 || ticks==1000){
try{
playSound("spun");
}
catch(Exception e){
e.printStackTrace();
}
}
}
});
timer.start();
}
#Override
public void paint(Graphics g){
super.paint(g);
Graphics2D g2d=(Graphics2D)g.create();
//g2d.setColor(Color.RED);
//g2d.fill(box);
for(Entity entity: Entities){
g2d.drawImage(entity.IMAGE, entity.getX(),entity.getY(), entity.IMAGE.getWidth(), entity.IMAGE.getHeight(), null);
g2d.drawString(String.valueOf(entity.NAME), entity.getX(), entity.getY());
}
///draw lines in window if enabiled
if(drawLines){
drawLines(g2d);
}
///do collisions
g2d.dispose();
}
class InputController implements KeyListener{
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_W:
keyPressed[W]=true;
break;
case KeyEvent.VK_S:
keyPressed[S]=true;
break;
case KeyEvent.VK_A:
keyPressed[A]=true;
break;
case KeyEvent.VK_D:
keyPressed[D]=true;
break;
case KeyEvent.VK_SPACE:
keyPressed[SPACE]=true;
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
switch(e.getKeyCode()){
case KeyEvent.VK_W:
keyPressed[W]=false;
break;
case KeyEvent.VK_S:
keyPressed[S]=false;
break;
case KeyEvent.VK_A:
keyPressed[A]=false;
break;
case KeyEvent.VK_D:
keyPressed[D]=false;
break;
case KeyEvent.VK_SPACE:
keyPressed[SPACE]=false;
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
public abstract void gameLoop();
public void drawLines(Graphics2D g2d){
for(int i=0;i<1920;i+=1920/xAxisLines){
g2d.drawLine(i, 0, i, 1080);
}
for(int i=0;i<1080;i+=1080/yAxisLines){
g2d.drawLine(0, i, 1920, i);
}
}
public void collisions(){
////collisions!
ArrayList<Entity> returnEntities=new ArrayList<Entity>();
quad.clear();
for(int i=0;i<Entities.size();i++){
quad.insert(Entities.get(i));
}
for(int i=0;i <Entities.size();i++){
returnEntities.clear();
returnEntities=quad.retrieve(returnEntities,Entities.get(i));
for(Entity e:returnEntities){
//System.out.println(Entities.get(i).NAME+" could collide with "+e.NAME);
if(Math.abs(Entities.get(i).getX()-e.getX())<= Entities.get(i).IMAGE.getWidth()/2+e.IMAGE.getWidth()/2 && Math.abs(Entities.get(i).getY()-e.getY())<= Entities.get(i).IMAGE.getHeight()/2+e.IMAGE.getHeight()/2 && Entities.get(i)!=e){
//System.out.println(Entities.get(i).NAME+" is colliding with "+e.NAME);
}
}
}
////collisions!
/*
System.out.println(String.valueOf(quad.getIndex(Entities.get(0))));
System.out.println(Entities.get(0).bounds.x);
System.out.println(Entities.get(0).bounds.y);
*/
}
public void playSound(String fileName) throws IOException,
UnsupportedAudioFileException, LineUnavailableException, InterruptedException {
File clipFile=new File("assets//"+fileName+".wav");
class AudioListener implements LineListener {
private boolean done = false;
#Override public synchronized void update(LineEvent event) {
Type eventType = event.getType();
if (eventType == Type.STOP || eventType == Type.CLOSE) {
done = true;
notifyAll();
}
}
public synchronized void waitUntilDone() throws InterruptedException {
while (!done) {}
}
}
class SoundThread implements Runnable {
AudioListener listener = new AudioListener();
AudioInputStream audioInputStream;
public SoundThread() throws UnsupportedAudioFileException, IOException{
audioInputStream = AudioSystem.getAudioInputStream(clipFile);
}
#Override
public void run() {
try{
Clip clip=AudioSystem.getClip();
clip.addLineListener(listener);
clip.open(audioInputStream);
try{
clip.start();
listener.waitUntilDone();
}
finally{
clip.close();
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
try {
audioInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Thread thread=new Thread(new SoundThread());
thread.start();
}
}
Well you have written it very Procedurally, rather than object orientated. you have lots of lists being iterated, Maybe if you refactor it then you may be able to speed it up, Is there anywhere that we can get the code (github etc) as with just this snippet there is not much we can do to help.
Any specific reason for doing Graphics2D g2d=(Graphics2D)g.create() and g2d.dispose();?
Can't you just Graphics2D g2d = (Graphics2D) g;? It will probably improve performance.
TLDR; but managed to find 2 fundamental mistakes:
In Swing never override paint() to do graphics.
Use the override-safe paintComponent(). (In additional to #YoavAharoni's point, don't create new graphic contexts every time it repaints.)
Never make long lasting tasks inside the EDT.
In your case, you call gameLoop(), collisions(), and playSound() - which I assume as time consuming - inside the timer's event method. Instead, use java.util.Timer or threads.
If you must use the EDT, as to render long lasting graphics, use a SwingWorker. (Note: repaint() may be called outside EDT)
I don't know what IDE are you using, but I recommend you to do a profiling. Eclipse has a nice plugin for that.
Yourkit: free trial only, but pretty good https://www.yourkit.com/features/eclipse/
Eclipse java profiler, you can find it on the marketplace
Netbeans https://profiler.netbeans.org/
Once I had to wrote a game myself, and my problem was with the size of the images:
``g2d.drawImage(entity.IMAGE, entity.getX(),entity.getY(), entity.IMAGE.getWidth(), entity.IMAGE.getHeight(), null);``
I had to decrease the size under 40kb for each image to run normally.
Another thing is that you switch of some elments on your UI, and see what makes a big performance boost, then you can tweak that component.

Java how to add the Hero (object) to the game (class)

I have got a problem and I'm totally new at programming I have the object the Hero and I want to use all of his methods in my game class this is what I Programmed so far:
MainGame class:
class MainGame extends JComponent implements ActionListener, KeyListener{
Image Background;
MainGame() throws IOException {
Background = ImageIO.read(getClass().getResource("Background.png"));
}
public static void main (String[] args) throws IOException {
JFrame window = new JFrame("Adventure Times");
MainGame game = new MainGame();
window.add(game);
window.pack();
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setVisible(true);
window.addKeyListener(game);
}
public void paintComponent(Graphics g) {
g.drawImage(Background, 0, 0, null);
}
public Dimension getPreferredSize() { return new Dimension(800, 600);
}
public void actionPerformed(ActionEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_RIGHT);
Hero.moveRight();
}
public void keyReleased(KeyEvent e) {
}
}
Hero class:
public class Hero {
public int HeroX = 0;
public int HeroY = 0;
public int HeroSpeed = 0;
private BufferedImage Hero;
public Hero() {
try {
Hero = ImageIO.read(getClass().getResource("Hero.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void Draw(Graphics g) {
g.drawImage(Hero, HeroX, HeroY, null);
}
public void moveRight() {
HeroX += HeroSpeed;
}
public void moveLeft() {
HeroX -= HeroSpeed;
}
}
To use Hero's methods in your MainGame class, you either need an instance of Hero that can call them, or the method definitions have to include the static keyword. In this application, static doesn't work, and would in fact completely break your Hero class if applied to the methods you have now, so you need to instantiate a Hero. To do this, you need to, within MainGame have the line
Hero achilles = new Hero();
With the code you currently have, however, this will throw an exception in the Hero constructor that your try statement doesn't catch, as it won't be an IOException. The exception will be a result of trying to assign a value to a data type in
Hero = ImageIO.read(getClass().getResource("Hero.png"));
In fact, the code as it is won't even compile because of the attempted definition of Hero as a member of type BufferedImage. This is illegal, as you cannot use a class name as an identifier. This is similar to doing int = 4, which makes no sense. Rename the BufferedImage wherever it's referred to and the code should compile. For instance:
private BufferedImage sprite;
Alternatively, you could name it hero with a lowercase 'h' to avoid the name collision. This is also in line with naming conventions and general best practice for Java, as well as most languages. Class names are usually capitalized, while member names are usually lowercase. For more info on naming conventions, see here.

Something like BeJeweled game in java language

I'm trying to design a game like BeJeweled using java language
This is where I've reached so far :
public class Game {
public static void main(String[] args) throws InterruptedException {
final JFrame window = new JFrame();
window.setSize(508,669);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Grid g=new Grid();
Game.obj(g);
window.add(g);
window.setVisible(true);
window.setResizable(false);
window.repaint(2);
window.addMouseListener(new MouseListener(){
#Override
public void mouseClicked(MouseEvent e) {
int x=e.getX();
int y=e.getY();
for(int i=0;i<10;i++) {
for(int j=0;j<16;j++){
if(x>i*50+5 && x<i*50+54 && y>j*40+26 && y<j*40+26+39){
g.b[i][j]=g.a[i][j];
int q = x;
int w=y;
int r =x;
int t =y;
q-=50;
w-=40;
if( i>0&&g.a[i-1][j]==g.b[i][j]){
g.a[i][j]=0;
g.a[i-1][j]=0;
}
if( j>0&&g.a[i][j-1]==g.b[i][j]){
g.a[i][j]=0;
g.a[i][j-1]=0;
}
r+=50;
t+=40;
if(i<9&&g.a[i+1][j]==g.b[i][j]){
g.a[i][j]=0;
g.a[i+1][j]=0;
}
if(j<15&&g.a[i][j+1]==g.b[i][j]){
g.a[i][j]=0;
g.a[i][j+1]=0;
}
}
}
}
SwingUtilities.updateComponentTreeUI(window);
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
});
}
public static void obj(Grid g){
Random r =new Random();
for(int k=0;k<10;k++)
for(int l=0;l<16;l++)
g.a[k][l]=1+r.nextInt(4);
}
}
class Grid extends JPanel {
private Graphics Graphics;
#Override
public void paint(Graphics g) {
final Graphics g1 = g ;
this.setGraphics(g);
for(int i=0;i<600;i+=50)
for(int j=0;j<400;j+=40)
g.drawRect(i, j, i+50, j+40);
for(int i=0;i<10;i++)
for(int j=0;j<16;j++){
if(a[i][j]== 0) g.setColor(Color.WHITE);
if(a[i][j] == 1) g.setColor(Color.ORANGE);
if(a[i][j] == 2) g.setColor(Color.red);
if(a[i][j] == 3) g.setColor(Color.GREEN);
if(a[i][j] == 4) g.setColor(Color.cyan);
g.fillRect(i*50+1, j*40+1, 49, 39);
}
// Mouselis mouseptr = new Mouselis(g);
// this.addMouseListener(mouseptr);
// this.addMouseMotionListener(mouseptr);
}
public void setGraphics(Graphics Graphics) {
this.Graphics = Graphics;
}
int [][] a= new int[10][16];
int [][] b= new int[10][16];
}
In this design only the up down left right of rectangles are being checked for same color in the method mouse clicked. How can i make it check all the near rectangles for the one having same color ?
Please help Thanks
You have four if statements that check the adjacent rectangles for the same color as the one that was clicked. Here is the last one:
if(j<15&&g.a[i][j+1]==g.b[i][j]){
g.a[i][j]=0;
g.a[i][j+1]=0;
}
j, the vertical grid coordinate, is compared with 15 to be sure that j+1 is within bounds. Also, a rectangle in the 2d-array a is checked to see if it is equal to the rectangle in the 2d-array b that got clicked. If it is, you set it to 0 for Color.WHITE. Because the indices are i and j+1 this checks the rectangle directly below the clicked one.
To check a rectangle that is diagonally adjacent, change both indices by one.
The rectangle to the bottom right is g.a[i+1][j+1]. To access this rectangle without an error you need to be sure that both i+1 and j+1 are within the bounds of the array to avoid an error.
So, the if statement is
if(j<15 && i < 9 && g.a[i+1][j+1]==g.b[i][j]){ ...
You can figure out the rest.
Java is a strong Object-Oriented programming language - especially before Java8: I think that you should really work with more Objects that would hold their own responsibilities and behaviours.
Once you'll have a class that represents a Jewel, just implement some recursive method to know if it's part of some "Jewel streak". For example:
public class Jewel {
private final JewelType type; // Could be an Enum for example
// Many other stuff to add (like a constructor at least!)
public Collection<Jewel> getStreak() {
final Set<Jewel> res = new HashSet<>();
this._getStreak(res);
return res;
}
private void _getStreak(final Set<Jewel> streak) {
// Just leave the method if we've already visited this Jewel
if(!streak.add(this)) {
return;
}
// Assuming that this method returns a Collection of the Jewels that are stored West, North, South and East from this
for(final Jewel neighbour : Grid.getInstance().getJewelsAround(this)) {
if(neighbour.getJewelType().equals(this.getJewelType())) {
neighbour._getStreak(streak);
}
}
}
}
When trying to see if some Jewel is part of a streak that is long enough to be "destroyed", just check something like this:
Jewel b = Grid.getInstance().getJewelForCoordinates(x, y);
final Collection<Jewel> streak = b.getStreak();
if(streak.size() >= Game.MINIMUM_STREAK_SIZE) {
Grid.getInstance().destroyJewels(streak);
}
I just don't even have access to any Java editor/compiler right now so please excuse me, would my code present any mistyping. Even though I recommend to do many changes, I hope it can help you :)
By the way, if you don't want to override all the methods described by the MouseListener interface, use the following:
window.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(final MouseEvent event) {
// Code to be executed on mouseClicked().
}
});
MouseAdapter is an abstract class that implements MouseListener and does nothing when receiving any event. You just have to override the methods that handle the events you're interested in ;)

accessing variables and swing components through different threads

This question is related somewhat to the one i asked HERE.
Now, i have a class "Controller" which consists of the main method and all the swing components. there is a class named "VTOL" which consists of a variable named "altitude"(i have declared this variable volatile as of now).
here is a class that consists of a thread which runs in the background:
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author Vineet
*/
public class Gravity extends Thread {
String altStr;
double alt;
Controller ctrl = new Controller();
#Override
public void run() {
while (true) {
alt=VTOL.altitude;
System.out.println(alt);
alt = alt-0.01;
VTOL.altitude= (int) alt;
altStr=new Integer(VTOL.altitude).toString();
ctrl.lblAltitude.setText(altStr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Firstly, the problem i was facing initially was that i couldnt update value of "altitude" it remained 0 throughout the execution of program. So i declared it as volatile (I dont know if its a good practice)
Secondly, there is a jLabel in Controller class named "lblAltitude", i wish to update its value as its changed in this thread, but somehow thats not happening.
How can i do that?
A solution is to use a SwingPropertyChangeSupport object, to make altitude a "bound" property with this support object, to have your GUI listener to this model class and to thereby notify the GUI of changes in altitude.
e.g.,
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Gravity implements Runnable {
public static final String ALTITUDE = "altitude";
private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
private volatile double altitude;
#Override
public void run() {
while (true) {
double temp = altitude + 10;
setAltitude(temp); // fires the listeners
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
Double oldValue = this.altitude;
Double newValue = altitude;
this.altitude = newValue;
// this will be fired on the EDT since it is a SwingPropertyChangeSupport object
swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.removePropertyChangeListener(listener);
}
}
For a more complete runnable example:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class GravityTestGui extends JPanel {
private static final long ALT_SLEEP_TIME = 400;
private static final double ALT_DELTA = 5;
JLabel altitudeLabel = new JLabel(" ");
private Gravity gravity = new Gravity(ALT_SLEEP_TIME, ALT_DELTA);
public GravityTestGui() {
add(new JLabel("Altitude:"));
add(altitudeLabel);
gravity.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (Gravity.ALTITUDE.equals(pcEvt.getPropertyName())) {
String altText = String.valueOf(gravity.getAltitude());
altitudeLabel.setText(altText);
}
}
});
new Thread(gravity).start();
}
private static void createAndShowGui() {
GravityTestGui mainPanel = new GravityTestGui();
JFrame frame = new JFrame("GravityTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class Gravity implements Runnable {
public static final String ALTITUDE = "altitude";
private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
private volatile double altitude;
private long sleepTime;
private double delta;
public Gravity(long sleepTime, double delta) {
this.sleepTime = sleepTime;
this.delta = delta;
}
#Override
public void run() {
while (true) {
double temp = altitude + delta;
setAltitude(temp); // fires the listeners
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
Double oldValue = this.altitude;
Double newValue = altitude;
this.altitude = newValue;
// this will be fired on the EDT since it is a SwingPropertyChangeSupport object
swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.removePropertyChangeListener(listener);
}
}
Whenever you modify a Swing component, you need to ensure that this event happens in the Event Dispatch Thread (i.e. EDT).
A third approach would be to have your Swing component know about the model, VTOL.
In Gravity, you'd update VTOL.altitude, then call repaint on the component. e.g.
while (true) {
VTOL.altitude -= 0.01;
VTOL.makeAnyOtherChangesHereAsWell();
controller.repaint();
// sleep, break etc. left as an exercise for the reader
}
Then, in the paintComponent() method (or maybe somewhere else in all the paint calls, there's a slight chance it needs to be elsewhere...) of Controller, which you know is running on the EDT
// update my widgets from the VTOL model - may want this in a method
String altStr=new Integer(VTOL.altitude).toString();
this.lblAltitude.setText(altStr);
// may be more, e.g. ...
this.lblFuelSupply.setText(VTOL.getFuelSupply());
super.paintComponent(); // now go draw stuff...
This is a bit tighter coupled than SwingPropertyChangeSupport, but the coupling is all between very related classes, so it is "reasonable", and in some ways this may be "clearer". And the Event Dispatch Queue will combine multiple repaints so this isn't as inefficient as it first appear. If multiple threads are updating stuff and queuing up multiple repaints(), only the last repaint() actually does anything.
A disadvantage is that if your GUI has a gazillion widgets and you update all of them every time this may get a bit slow. But processors are amazingly fast nowadays.

Categories