Vier gewinnt: GUI aktualisiert sich nicht

Anyone

Mitglied
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
Java:
	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:

Java:
	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.
 
Zuletzt bearbeitet:
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:


Java:
                        // 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.
 
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.
 
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:

Java:
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.
 
Ich danke dir vielmals, mit deinem Lösungsweg hat das prima funktioniert. Schön, dass man immer wieder neue Sachen dazu lernen kann. 🙂
 
Ich habs, schreibe
Java:
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...
 
Ich habs, schreibe
Java:
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...

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

Neue Themen


Zurück
Oben