# Spiel verbessern - ConcurrentModificationException



## ARadauer (7. Jul 2009)

Ich hab vor ein paar Tagen auf Facebook so ein Spiel mit Bällen die man zu paltzen bringen muss gespielt.. hab mir geadacht.. das könnte ich ja mal in Java probieren...

Ich mach eigentlich sonst nichts mit Spielprogrammierung, ich arbeite eher im Web und Datenbankbereich.

Also hab ich mir Quxlis Tutorial geschnappt und hab das geschrieben....


```
package bouncing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Bouncing extends JPanel implements Runnable, MouseListener{

   private static final long serialVersionUID = -2897573490118370605L;

   boolean running = true;
   Random rnd = new Random();

   //zur Berechnung der FPS und Animation
   long delta = 0;
   long last = 0;
   long fps = 0;

   
   //Anzahl der Bälle
   int numBalls = 200;

   //Liste mit den Bällen
   ArrayList<Ball> balls = new ArrayList<Ball>();

   
   public static void main(String[] args) {
      new Bouncing(800,600);
   }

   public Bouncing(int w, int h){
      this.setPreferredSize(new Dimension(w,h));
      JFrame frame = new JFrame("Bouncing");
      frame.setLocation(100, 100);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(this);
      frame.pack();
      frame.setVisible(true);

      this.addMouseListener(this);

      init();
   }



   private void init(){
      last = System.nanoTime();

      setBackground(Color.black); 
      
      //Bälle erzeugen
      while(balls.size()<numBalls)
         createBall();

      Thread t = new Thread(this);
      t.start();
   }

   /**
    * Methode erzeugt einen zufälligen Ball und fügt ihn in die Liste der Bälle ein
    */
   public void createBall(){

      int x = rnd.nextInt(this.getWidth());
      int y = rnd.nextInt(this.getHeight());

      int r = rnd.nextInt(255);
      int g = rnd.nextInt(255);
      int b = rnd.nextInt(255);

      int dx = rnd.nextInt(200)-100;
      int dy = rnd.nextInt(200)-100;


      Ball ball = new Ball(x,y, 7, 7,this, new Color(r,g,b));
      ball.setDx(dx);
      ball.setDy(dy);

      balls.add(ball);
   }

   public void run() {
      while(running){

         computeDelta();
         doLogic();
         moveObjects();
         repaint();
         try {
            Thread.sleep(10);
         } catch (InterruptedException e) {}
      }    
      
      System.out.println("Spiel zu Ende....");
   }

   
   private void doLogic() {
      try {
         for(Ball move: balls)
            move.doLogic(delta);

         
         
         //zusammen gestoßene Bälle sterben lassen
         ArrayList<Ball> trash = new ArrayList<Ball>();
         for(int i = 0; i < balls.size(); i++){
            for(int j = i+1; j < balls.size(); j++){
               if(balls.get(i).colidedWith(balls.get(j))){
                  trash.add(balls.get(i));
                  trash.add(balls.get(j));
               }
            }
         }
         for(Ball s: trash)
            s.die();

         
         //Bälle die wir löschen können, die löschen wir aus der Liste
         for(Ball s: balls){
            if(s.isCanDelete())
               balls.remove(s);
         }
         
         if(balls.size()<1)
            running = false;
         
      } catch (ConcurrentModificationException e) {
         e.printStackTrace();
      }

   }

   /**
    * Bälle bewegen
    */
   private void moveObjects() {
      for(Ball move: balls)
         move.move(delta);      
   }

   /**
    * zeichnen
    */
   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);      

      g.setColor(Color.red);
      g.drawString("FPS: "+Long.toString(fps), 20, 10);

      g.drawString("Balls: "+balls.size(), 100, 10);

      try {
         for(Ball draw: balls)
            draw.drawObject(g);
      }  catch (ConcurrentModificationException e) {
         e.printStackTrace();
      }
   }

   /**
    * delta und fps berechnen
    */
   private void computeDelta() {
      delta = System.nanoTime() - last;
      last = System.nanoTime();
      fps = ((long) 1e9)/delta;

   }

   
   public void mouseClicked(MouseEvent e) {
      Ball ball = new Ball(e.getX(),e.getY(), 7, 7,this, Color.WHITE);
      ball.setDead(true);
      balls.add(ball);
   }

   public void mouseEntered(MouseEvent e) {}

   public void mouseExited(MouseEvent e) {}

   public void mousePressed(MouseEvent e) {}

   public void mouseReleased(MouseEvent e) {}

}
```


```
package bouncing;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;

/**
 * Spielball
 *
 */
public class Ball extends Rectangle2D.Double {


   private static final long serialVersionUID = -6285058019006719750L;

  
   Bouncing p;

   //Richtung
   double dx;
   double dy;
   
   
   //kann dieser Ball gelöscht werden
   private boolean canDelete;
   
   //Fortschritt der explusion
   private int dieStatus =0; 
   private boolean dead;   
   
   
   Color color;

   public Ball(double x, double y, double w, double h, Bouncing p, Color color) {
      this.x = x;
      this.y = y;
      this.height = h;
      this.width = w;
      this.p = p;   
      this.color = color;
   }

   
   public void drawObject(Graphics g) {
      Color old = g.getColor();
      g.setColor(color);
      g.drawOval((int)x, (int)y, (int)width, (int)height);
      g.setColor(old);
   }



   /**
    * Kollisionsdetection mit den Wänden
    * @param delta
    */
   public void doLogic(long delta) {
      //Wenn wir an den wänden anstoßen, wechseln wir die Richtung
      if(x<0 || (x+width)>p.getWidth())
         dx *=-1;
      
      if(y<0 || (y+height)>p.getHeight())
         dy *=-1;
   }

   /**
    * Bewegung und Animation
    * @param delta
    */
   public void move(long delta) {     
      
      //sind wir tot, explodieren wir...
      if(dead){
         double expansion = 40*(delta/1e9);
         
         width = width+expansion;
         height = height+expansion;
         x = x-expansion/2;
         y = y-expansion/2;
         
         dieStatus++;
         if(dieStatus==250)
            setCanDelete(true);
      }else{ // sonst bewegen wir uns
         if(dy!=0)
            y += dy*(delta/1e9);
         
         if(dx!=0)
            x += dx*(delta/1e9);
      }
      
   }
   
   /**
    * wir sind können nur zusammenstoßen wenn wir tot sind
    * @param s
    * @return
    */
   public boolean colidedWith(Ball s){
      return (this.intersects(s) && (this.isDead() || s.isDead()));
   }
   
   
   public void die() {
      if(!dead){
         setDead(true);
      }
   }
   
   //Getter und Setter
   public double getDx() {
      return dx;
   }
   public void setDx(double dx) {
      this.dx = dx;
   }
   public double getDy() {
      return dy;
   }
   public void setDy(double dy) {
      this.dy = dy;
   }


   public boolean isCanDelete() {
      return canDelete;
   }


   public void setCanDelete(boolean canDelete) {
      this.canDelete = canDelete;
   }

   public boolean isDead() {
      return dead;
   }

   public void setDead(boolean dead) {
      this.dead = dead;
   }



}
```

Ziel ist es theoritisch durch klicks alle Bälle zu zerstören... ist noch nicht fertig...

1. Grundlegend hab ich jetzt ein Problem mit ConcurrentModificationException.
Passiert weil ich die Liste veränder sie aber gleichzeitig von einem anderen Thread (EDT??) gelesen wird.. oder?
Was mach ich dagegen?

2. Wie kann ich das ganze einwenig "sauberer" zeichnen, das scheint mir noch ein wenig unsauber und nicht so "smooth" wie ich es gerne hätte... Gibts da was in Richtung Antialiasing?

Danke für eure Tipps....


----------



## ARadauer (7. Jul 2009)

btw da kann man ziehmliche coole sachen machen...
zeile 74 im Ball
schreibt mal
  double expansion = 40*dieStatus*(delta/1e9);


wow psydelic!!!


----------



## ARadauer (7. Jul 2009)

double expansion = Math.pow(2*(dieStatus/10), 2)*(delta/1e9);

geil!


----------



## Marco13 (7. Jul 2009)

Zu 1: Ja, catchen sollte man die jedenfalls nicht  Im allgemeinen tritt sie auf, wenn eine Collection von zwei Threads verwendet wird, und mindestens einer davon die Collection verändert. Also bei sowas wie

```
private List<Ball> balls = new ArrayList<Ball>();

public void paintComponent(Graphics g)
{
    ...
    for (Ball ball : ball) ball.paint(g);
}

void gameLogicInOwnThread()
{
    balls.remove(0);
}
```
haut's ihn halt ggf. beim drüberiterieren in der for-Schleife raus.

Verhindern kann man das, indem man die Collection (und die Zugriffe beim drüberiterieren) synchronized macht - am Beispiel also sowas wie

```
private List<Ball> balls = Collections.synchronizedList(new ArrayList<Ball>());

public void paintComponent(Graphics g)
{
    ...
    synchronized(balls)
    {
        for (Ball ball : ball) ball.paint(g);
    }
}

void gameLogicInOwnThread()
{
    balls.remove(0);
}
```

Zu 2: Müßt' ich's mal starten, wenn ich Zeit hab ... bei Gelegenheit...


----------



## ARadauer (7. Jul 2009)

danke marco.
ich hab jetzt die liste syncronisiert, einen syncroniced block ums painten und meine for each umgebaut.


```
//            for(Ball s: balls){
//               if(s.isCanDelete())
//                  balls.remove(s);
//            }
            
            for( int i  = balls.size()-1; i>=0; i-- ){
               if(balls.get(i).isCanDelete())
                  balls.remove(i);;
            }
```
jetzt klappts...


----------



## Civilazi (7. Jul 2009)

```
for(Ball s: balls){
            if(s.isCanDelete())
               balls.remove(s);
         }
```

Allein da wirst du schon eine ConcurrentModificationException bekommen, auch bei nur einem Thread. 
mögliche Lösung: mit index-for-Schleife über balls drüberlaufen und den index nach dem löschen entsprechend anpassen oder einmal drüberlaufen, die zu löschenden Bälle in ner weiteren Liste merken und dann balls.removeAll(liste).

EDIT: Ok, ganz knapp zu spät =)


----------



## Landei (7. Jul 2009)

ARadauer hat gesagt.:


> danke marco.
> ich hab jetzt die liste syncronisiert, einen syncroniced block ums painten und meine for each umgebaut.



Wenn die Performance darunter nicht zu sehr leidet...  

Alternativ: Java hat inzwischen threadsichere Collections, die auch intern ohne synchronized auskommen, und zwar unter java.util.concurrent. Hier könnte z.B. eine CopyOnWriteArrayList das richtige sein (braucht aber potentiell mehr Speicher).


----------



## Marco13 (7. Jul 2009)

Zu 2 Müßtest du nochmal genauer sagen, was du meinst - die Bewegung ist ja (zumindest bei mir) flüssig - Antialiasing kann man ggf. mit 
      ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
in der paintComponent einschalten.


----------



## ARadauer (7. Jul 2009)

@Landei: CopyOnWriteArrayList? Hammer!!!

@marco: 
((Graphics2D)g).setRenderingHint(RenderingHints.KE Y_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
genau, das habe ich gesucht!

danke


----------

