Obersver - Observable, Bomberman

Status
Nicht offen für weitere Antworten.

Lexi

Bekanntes Mitglied
Wie ihr der Überschrift sicherlich schon entnommen habt, schlage ich mich grad mit dem Observer Pattern rum. Ich glaube die grundsätzliche Funktionsweise dieses Patterns verstanden zu haben, bin mir allerdings nicht so sicher, ob ich das hier sinnvoll implementiert habe.

Später möchte ich von außen auf die PlayGround Klasse zugreifen. Wenn ich die placeBomb(Player p) Methode aufrufe, dann wird die placeBomb Methode der Klasse Player aufgerufen, die wiederum die Bombe startet und sie in die Liste der gerade tickenden Bomben des entsprechenden Payers aufnimmt. Nach 4 Sekunden explodiert die Bombe, d.h. sie benachrichtigt Player sie wieder aus der Liste der gerade tickender Bomben rauszunehmen und sie benachrichtigt zusätzlich PlayGround, sodass dort die entsprechenden Maßnahmen getroffen werden.

Meine Frage an euch wäre jetzt erstmal, ob ich mit dieser Implementierung schon auf dem richtigen Wege bin, oder ob ich das ganze nochmal komplett anders machen sollte.

Java:
public class PlayGround 
implements Observer{
	
	private Player player1;
           private Player player2;
	
	public PlayGround(int width, int height) {	
		player = new Player(); // default values	
	}
	
	public void placeBomb(Player p){
		p.placeBomb(this);
	}
	
	@Override
	public void update(Observable o, Object arg1) {
		Bomb b = (Bomb)arg1;
                      //explosionssachen machen
	}
}

Java:
public class Player 
implements Observer{

	private List<Bomb> bombs;

	public Player(){
		bombs = new LinkedList<Bomb>();
	}
	
	@Override
	public void update(Observable o, Object arg) {
		bombs.remove((Bomb)arg);
	}
	
	public void placeBomb(PlayGround playG){
		Bomb b = new Bomb(this, playG);
		bombs.add(b);
		b.start();
           }
	
}

Java:
public class Bomb 
implements Runnable{
	Observable obs = new Observable();
	
	private Point position;	
	
	public Bomb(Player owner, PlayGround playG) {
		position = owner.getPosition();
		obs.addObserver(owner);
		obs.addObserver(playG);
	}
	
	public void start(){
		run();
	}
	
	public void run(){
		try{
			Thread.sleep(4000);
		}catch(InterruptedException ie){
			System.out.println("Da hat mich was beim schlafen gestört !");
			ie.printStackTrace();
		}
		obs.notifyObservers(this);
	}
 
S

SlaterB

Gast
funktionieren wird es wohl, paar Punkte kommen mir da aber merkwürdig vor:

1. ein Runnable-Interface ist kein Selbstzweck, das macht nur Sinn, wenn auch einen Thread startest,
run() von Runnable selber aufzurufen oder gar selber die fehlende start()-Methode zu ergänzen, bringt keine Nebenläufigkeit sondern nur Verwirrung

2. für jede Bombe einen eigenen Thread als auch einen eigenen Observer ist beides eher Verschwendung,
bei derart vielen gleichartigen und auch noch nur temporär existierenden Dingen verwende lieber EIN langlebiges Verwaltungsobjekt,
z.B. Player, was jetzt nicht heißen soll, dass es nur einen Player geben darf, aber zumindest nur ein Player für eine ganze Menge Bomben dieses Spielers,
adde dort die Bomben zusammen mit einem Zeitstempel in eine Liste (Zeit könnte in der Bombe gespeichert sein),

lasse nur in Player einen Thread laufen, der alle 50ms oder noch öfter nachschaut, ob gerade eine Bombe fertig ist,
man kann auch das sleep() des EINEN Überwachungshreads (pro Player) optimieren, indem man die Zeit ausrechnet, bis die nächste Bombe bereit ist und genau so lange schläft,

allerdings müsste man dann die Zugriffe auf die Bombenliste synchronisieren, nicht das eine neue eingefügt wird, während der Thread gerade eine entfernt, nicht ganz einfach

jedenfalls wäre dann nur Player EIN Observable (pro Player), Playgroud könnte sich daran anmelden

3. kennst du JButton mit ActionListener? warum hat JButton kein Observer/ Observable?
das allgemeine Konstrukt funktioniert zwar, aber man kann es auch leicht nachbauen, man braucht nur ein neues Interface BombListener mit irgendeiner Methode, Player hält eine Liste davon und informiert diese (allerdings wieder Synchronisation nötig)

dann müsste Playground nicht auf Bomb casten,
und wenn Playground auch mehrere Dinge observed, gäbe es eindeutige unterschiedliche Methoden

4. hier könnte gar bisher ganz auf Observable verzichtet werden, Player kennt ja bereits Playground, also könnte Player auch direkt
playGround.heyDortExplodiertWas(bomb);
aufrufen,
 

Lexi

Bekanntes Mitglied
Die Liste habe ich ja bereits. Jedes Player Objekt hat eine Liste in der es seine momentan tickenden Bomben unterbringt.
Zeitstempel = Zeit zu der die Bombe in die Liste eingefügt wurde ?

lasse nur in Player einen Thread laufen, der alle 50ms oder noch öfter nachschaut, ob gerade eine Bombe fertig ist,
man kann auch das sleep() des EINEN Überwachungshreads (pro Player) optimieren, indem man die Zeit ausrechnet, bis die nächste Bombe bereit ist und genau so lange schläft,
Ist das mit dem Thread jetzt vernünftig gestartet ?
Java:
bombs = Collections.synchronizedList(new ArrayList<Bomb>());
		controller = new Thread(new ControllerJob());
		controller.start();
Java:
	class ControllerJob implements Runnable{
		@Override
		public void run() {
			while(true){ //Ist das so glücklich gelöst ( das true )?
				long cTime = System.currentTimeMillis();
				for(Bomb b : bombs){
					if(cTime - b.getTimeWhenAdded() >= 4000){
						b.setExploded();
						//hier wird dann der PlayGround informiert
						bombs.remove(b);
					}
				}
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

3. kennst du JButton mit ActionListener? warum hat JButton kein Observer/ Observable?
das allgemeine Konstrukt funktioniert zwar, aber man kann es auch leicht nachbauen, man braucht nur ein neues Interface BombListener mit irgendeiner Methode, Player hält eine Liste davon und informiert diese (allerdings wieder Synchronisation nötig)

dann müsste Playground nicht auf Bomb casten,
und wenn Playground auch mehrere Dinge observed, gäbe es eindeutige unterschiedliche Methoden

Ich habe das mal versuch umzusetzen, dashier ist dabei rausgekommen:

Player hat eine Liste mit BombListenern und stellt Methoden zum hinzufügen, entfernen und informieren von Listenern:

Java:
public class Player {

	private List<BombListener> listeners = Collections.synchronizedList(new ArrayList<BombListener>());

	public void informListeners(Bomb b){
		for(BombListener bl : listeners){
			bl.explodiere(b);
		}
	}
	
	public void addBombListener(BombListener bl){
		listeners.add(bl);
	}
	
	public void removeBombListener(BombListener bl){
		listeners.remove(bl);
	}

Mein Interface:
Java:
public interface BombListener {
	public void explodiere(Bomb b);
}

PlayGround implementiert das Interface:
Java:
public class PlayGround 
implements BombListener{	
	private Player[] players;

	public PlayGround() {	

		for(int i = 0; i < players.length; i++){
			players[i] = new Player(); //default values
			players[i].addBombListener(this);
		}
    } 
    @Override
	public void explodiere(Bomb b) {
		//informationen aus b holen
		//und explosionssachen machen
	}
 
S

SlaterB

Gast
> Zeitstempel = Zeit zu der die Bombe in die Liste eingefügt wurde ?

oder die Zeit, zu der sie fällig ist, ist ja egal und umrechenbar

> Ist das mit dem Thread jetzt vernünftig gestartet ?

sieht gut aus, while(true) ist ok
 

Lexi

Bekanntes Mitglied
Was sagst du zu der BombListener Geschichte bzw dazu, wie ich sie umgesetzt habe ?
 

Lexi

Bekanntes Mitglied
Wenn ich meine PlayGround Klasse mit folgender main Methode Teste bekomme ich die unten aufgeführte Exception. Zeile 85 ist im unten stehenden Code die Zeile 6. Da scheint irgendwas mit der for Schleife nicht zu funktionieren.

Java:
	public static void main(String... args){
		PlayGround pl = new PlayGround();
		pl.placeBomb(1); // parameter gibt an welcher Player eine Bombe platzieren soll
	}

Code:
Exception in thread "Thread-1"
java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
	at java.util.AbstractList$Itr.next(Unknown Source)
	at de.lexi.Player$ControllerJob.run(Player.java:85)


Java:
	class ControllerJob implements Runnable{
		@Override
		public void run() {
			while(true){ 
				long cTime = System.currentTimeMillis();
				for(Bomb b : bombs){
					if(cTime - b.getTimeWhenAdded() >= 4000){
						b.setExploded();
						informListeners(b);
						bombs.remove(b);
					}
				}
				
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

Wenn irgendwas kompilierbares benötigt wird, bitte bescheid sagen.
 

Noctarius

Top Contributor
Du kannst beim Durchlaufen einer For-Each Schleife keine Objekte aus der Liste löschen.

Machbar wäre es über einen Iterator;

Java:
Iterator iterator = bombs.iterator();
while (iterator.hasNext()) {
    Bomb bomb = iterator.next();
    ... do something ...

    iterator.remove();
}
 

Lexi

Bekanntes Mitglied
Java:
Iterator<Bomb> it = bombs.iterator();
				while(it.hasNext()){
					Bomb b = it.next();
					b.setExploded();
					informListeners(b);
					bombs.remove(b);
				}
So sieht mein Code jetzt aus, allerdings wirft er immer noch die gleiche Exception.

Meine Lösung ist jetzt einen zweiten Durchlauf hinzuzufügen, bei dem jede Bombe, für die isExploded() true zurückliefert, entfernt wird.

Edit: Quark, meine vorgeschlagene Lösung funktioniert natürlich genauso wenig wie die anderen, da ich ja auch beim zweiten Durchlauf in einer For-Schleife, bzw einem Iterator lösche -.-

Edit Nr 2: So, jetzt habe ich es :D Man muss einfach eine normale for-Schleife benutzen:
Java:
for(int i = 0; i < bombs.size(); i++){
	if(bombs.get(i).isExploded())
	bombs.remove(bombs.get(i));
}

Die Frage ist natürlich jetzt, woran das liegt ^^
Ich habe da so eine Vermutung, weiß aber nicht ob ich das verständlich ausdrücken kann.
Also ich denke, dass ein Iterator oder eine erweiterte for-Schleife eine Art Cursor haben, der den Index des Elements speichert, das von der iterator.next() Methode zurückgeliefert wird.
Angenommen dieser Cursor zeigt jetzt 5 an, d.h. das Element das als nächstes geliefert werden würde ist das bei Index 5.
Wenn ich jetzt aber hingehe und das Element bei Index 3 lösche, dann rutscht der Rest der Liste nach und das Element was vorher bei Index 5 war ist jetzt bei Index 4. Leider wird der Cursor jetzt nicht auf 4 gesetzt, was bedeutet, dass die Methode iterator.next() beim nächsten Aufruf das Element zurückliefert, welches vorher bei Index 6 war und jetzt nachgerutscht ist. Somit wurde ein Element übersprungen, was zu Problemen führen könnte, also löst Java eine Exception aus.

Sollte sich das alles anders verhalten, dann klärt mich bitte auf.
 
Zuletzt bearbeitet:

Noctarius

Top Contributor
du musst das remove auf dem Iterator ausführen NICHT AUF DER LISTE!

Schau nochmal in mein Beispiel.

Ein Iterator ist anders implementiert. Nehmen wir 2 einfache Beispiele, fangen wir mit der extended For an (for-each):

Die For-Each wird intern in eine normale For-Schleife übersetzt was bedeutet, wir benutzen einen Index welcher hochgezählt wird.
Nimmst du jetzt ein Element raus, stimmt dieser Index nicht mehr und das richtige Element wäre nicht mehr x sondern x-1. Ein weiteres Objekt raus wäre es x-2 usw. also generell gesagt x-n.
Dies wird von Listen, Sets und Maps nicht unterstützt, da n nicht gespeichert wird. Die Frage hier wäre ja auch, wann soll x-n endgültig wieder ausgerechnet und gespeichert werden, da du mit remove() keinen direkten Zusammenhang zu der Schleife hast.

Der Iterator arbeitet intern als eine Art verkettete Liste wo jedes Element seinen Vorgänger und Nachfolger kennt. Er wird als separate Abbildung der Liste erstellt, wenn du ihn dir mit iterator() holst.
Löscht du nun ein Element aus dem Iterator (das solltest du oben ändern, weil eine Reflektion der Änderungen von Liste nach Iterator nicht erforderlich ist aber von Iterator nach Liste schon) werden bei 2 Elementen Vor- und Nachgänger geändert und das zu löschende Element entfernt und falls es das aktuelle Element war wird die interne Variable welche das zuletzt zurückgegebene Element auf den Vorgänger gesetzt.
Da wird das Vorhandensein eines weiteren Elementes mit hasNext() quasi von Hand prüfen, wurde hier kein Index benötigt der jetzt falsch sein könnte.

Zuletzt zurückgegeben: Element1
Element1 -> Element2 -> Element3 -> Element4

hasNext(): true
next(): Element2

Zuletzt zurückgegeben: Element2
Element1 -> Element2 -> Element3 -> Element4

remove()

Zuletzt zurückgegeben: Element1
Element1 -> Element3 -> Element4

hasNext(): true
next(): Element3

Ansich ganz einfach ;)
 
Zuletzt bearbeitet:
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben