# Vier gewinnt: GUI aktualisiert sich nicht



## Anyone (21. Nov 2012)

Guten Abend,

im Rahmen meines Studiums müssen wir als Hausaufgabe ein Vier-Gewinnt spiel mit Java programmieren. Uns wurden zwei Möglichkeiten der Implementierung gegeben:

a) Vier-Gewinnt ohne Computerspieler (also zwei menschliche Spieler gegeneinander)
b) Vier-Gewinnt mit Computerspieler

Ich habe mich für die Variante mit dem Computerspieler entschieden. Zwei Schwierigkeitsgrade stehen zur Auswahl: Einfach und Mittel. Beim einfachen Schwierigkeitsgrad werden Steine durch den Computer per Zufall gesetzt. Die mittlere Schwierigkeitsstufe geht dabei folgendermaßen vor:

1. Kann der Computer durch setzen eines Steines gewinnen? Wenn ja, dann tut er dies.
2. Kann der Computer durch setzen eines Steines den Sieg des menschlichen Spielers verhindern? Wenn ja, dann tut er dies.
3. Falls 1. und 2. nicht zutreffen wird ein Stein zufällig gesetzt.

Das Spiel funktioniert tadellos. Nun möchte ich das Spiel auch grafisch ein wenig ansprechender machen. Momentan wird nach dem Stein setzen des menschlichen Spielers *sofort* der Stein des Computerspielers gesetzt. Ich würde gerne eine Zeitspanne von einer halben Sekunde dazwischen schalten. Das mit der halben Sekunde funktioniert auch prima, allerdings wartet das Programm die halbe Sekunde ab und setzt dann beide Steine (den des menschlichen Spielers und den des Computers).

Hier sind die entscheidenden Quellcodezeilen:

Klasse *PitchModel*

```
public void set(int column, Player humanPlayer)
	{
		if (!this.areColumnsFull()) {
			if (!this.isColumnFull(column)) {
				// human Player turn
				boolean humanVictory = this.setStone(column, humanPlayer);
				
				if (!humanVictory) {
					try {
						// computer Player turn
						this.strategy.setPitch(this);
						this.setStone(this.strategy.perform(), this.strategy.getComputerPlayer());	
						Thread.sleep(500);
					}
					catch (InterruptedException e) {
						
					}
				}
			} 	
		}
		else {
			PitchEvent event = new PitchEvent();
			event.setState(ConnectFourConstants.TIED);
			this.notify(event);
		}
	}

	public boolean setStone(int column, Player player)
	{
		PitchEvent event = new PitchEvent();
		int affectedRow = 0;
		int affectedColumn = column;
		
		for (int i = 0; i < this.rows; i++) {
			if (i < this.rows-1) {
				if (pitch[i+1][column] == 0) {
					continue;
				}
				else {
					pitch[i][column] = player.getId();
					affectedRow = i;
					break;
				}
			}
			else {
				if (pitch[i][column] == 0) {
					pitch[i][column] = player.getId();
					affectedRow = i;
				}
			}
		}
		
		if (this.isWon(affectedRow, affectedColumn, player)) {
			event.setState(ConnectFourConstants.WIN);
			event.setPlayer(player);
			event.setAffectedRow(affectedRow);
			event.setAffectedColumn(affectedColumn);
			
			this.notify(event);
			return true;
		}
		
		event.setState(ConnectFourConstants.UNDECIDED);
		event.setPlayer(player);
		event.setAffectedRow(affectedRow);
		event.setAffectedColumn(affectedColumn);
		
		this.notify(event);
		return false;
	}

	public void notify(PitchEvent event)
	{
		this.view.refresh(event);
	}
```

und die Methode der View-Klasse, die für das visuelle Setzen der Steine verantwortlich ist:


```
public void refresh(PitchEvent event)
	{
		if (event.getState() == ConnectFourConstants.WIN) {
			ImageIcon stone = event.getPlayer().getStone();
			JButton button = this.fields[event.getAffectedRow()][event.getAffectedColumn()];
			button.setEnabled(true);
			button.setIcon(stone);
			
			if (!event.getPlayer().getName().equals(this.playerNameText.getText())) {
				JOptionPane.showMessageDialog(null, "Sie haben leider verloren!");
				System.exit(0);
			}
			else {
				JOptionPane.showMessageDialog(null, "Herzlichen Glückwunsch, Sie haben gewonnen!");	
				System.exit(0);
			}
		}
		
		if (event.getState() == ConnectFourConstants.TIED) {
			JOptionPane.showMessageDialog(null, "Unentschieden!");
			System.exit(0);
		}
		
		if (event.getState() == ConnectFourConstants.UNDECIDED) {
			ImageIcon stone = event.getPlayer().getStone();
			JButton button = this.fields[event.getAffectedRow()][event.getAffectedColumn()];
			button.setEnabled(true);
			button.setIcon(stone);
		}
	}
```

Vielleicht könntet ihr mir helfen, warum der die GUI nicht vor dem Thread.sleep und danach aktualisiert, sondern erst nach dem Thread.sleep? Wäre euch sehr dankbar.


----------



## ruerob (21. Nov 2012)

Guten Abend,

ich glaube, deine bisherige Methode sorgt dafür, dass beide Steine gesetzt werden und dann der Thread für 500 millis eingeschläfert wird, was dazu führt, dass das Zeichnen des Spielfeldes auch nicht mehr erledigt werden kann, bis der Thread aufwacht. Leider kenn ich mich mit Buttons und so nicht so gut aus und weiß daher nichts darüber, ob da bei dir jetzt extra Threads fürs Zeichnen angelegt werden. Ich würde es jetzt erstmal über folgende Schleife probieren:



```
// computer Player turn
                        int time = System.currentTimeMillis()+500;
                        while(time>=System.currentTimeMillis()){
                            Thread.sleep(10);
                        }

                        this.strategy.setPitch(this);
                        this.setStone(this.strategy.perform(), this.strategy.getComputerPlayer());
```

Ich kann dir jetzt aber nicht versprechen, dass das funktioniert.

Viel Glück,

ruerob.


----------



## Anyone (21. Nov 2012)

Danke für den netten Versuch. Der Timer funktioniert zwar, jedoch wird die GUI wieder nur einmal aktualisiert.


----------



## Volvagia (21. Nov 2012)

Wird set in einen separaten Thread ausgeführt?
Falls nicht ist es wohl wieder das alte Problem. Der EDT ändert alles, kann aber wärend des Wartens nicht neu zeichnen.


----------



## Anyone (21. Nov 2012)

Der sonstige Programmcode enthält keinerlei Threads. Also gibt es für dieses Problem keine Lösung?


----------



## ruerob (21. Nov 2012)

Vielleicht hilft es dir, wenn du in der refresh Funktion in Zeile 8 noch ein button.repaint(); setzt.


----------



## Anyone (21. Nov 2012)

ruerob hat gesagt.:


> Vielleicht hilft es dir, wenn du in der refresh Funktion in Zeile 8 noch ein button.repaint(); setzt.



Den Gedanken hatte ich gestern auch, allerdings mit demselben Ergebnis.


----------



## Volvagia (21. Nov 2012)

Wenn set von einen EventListener etc. abgearbeitet wird erledigt das der Event Dispatcher. Dieser arbeitet hintereinander eine Warteschlange von Events ab. Auch zeichnen. Falls dein Event ihn blockiert kann er in der Zwischenzeit das Zeichnen-Event nicht abarbeiten.

Du könntest es mal ganz grob so versuchen:


```
public void set(int column, Player humanPlayer)
{
	//Eingabe blockieren, damit in der Zwischenzeit nichts geändert werden kann
	
	if (!this.areColumnsFull()) {
    	if (!this.isColumnFull(column)) {
    		new SwingWorker<Void, Void>() {
    			protected Void doInBackground() throws Exception {
    				// human Player turn
                    boolean humanVictory = this.setStone(column, humanPlayer);
                    
                    if (!humanVictory) {
                        // computer Player turn
                    	try {
                            Thread.sleep(500);
                        }
                        catch (InterruptedException e) {
                            
                        }
                    	this.strategy.setPitch(this);
                        this.setStone(this.strategy.perform(), this.strategy.getComputerPlayer());
                    }
    	        }
    			protected void done() {
    				try {
    					get();
    				} catch(InterruptedException e) {
    				} catch (ExecutionException e) {
    					e.printStackTrace();
					}
    				
    				//Eingabe Freigeben
    			}
    		}.execute();
    	}
	} else {
		PitchEvent event = new PitchEvent();
		event.setState(ConnectFourConstants.TIED);
		this.notify(event);
	}
}
```

Dabei musst du aber aufpassen, dass alles, das GUI-Änderungen vornimmt auch im EDT durchgeführt wird.


----------



## Anyone (21. Nov 2012)

Ich danke dir vielmals, mit deinem Lösungsweg hat das prima funktioniert. Schön, dass man immer wieder neue Sachen dazu lernen kann.


----------



## ruerob (21. Nov 2012)

Ich habs, schreibe

```
button.paintImmediately(button.getBounds());
```
in Zeile 8, dann müsste es funktionieren, wenn du das Thread.sleep(500); ausführst, bevor du den Stein des Computerspielers setzt.

Viel Erfolg,

ruerob

EDIT: Viel zu spät...


----------



## Anyone (21. Nov 2012)

ruerob hat gesagt.:


> Ich habs, schreibe
> 
> ```
> button.paintImmediately(button.getBounds());
> ...



Nein, das funktioniert nicht  Trotzdem auch ein Danke an dich.


----------



## ruerob (21. Nov 2012)

Es funktioniert. Habs grad selbst getestet.


----------



## Anyone (21. Nov 2012)

ruerob hat gesagt.:


> Es funktioniert. Habs grad selbst getestet.



Bei mir funktioniert es dennoch nicht. Es scheint wohl wirklich an dem EDT gelegen zu haben


----------

