Java Rundenbasiertes Spiel mit Swing GUI verbinden

insXicht

Lt. Commander
Registriert
Mai 2011
Beiträge
1.095
Hallo,

ich bin gerade dabei ein kleines rundenbasiertes Spiel in Java zu programmieren.
Auf der Konsole ist das Spiel schon fertig und komplett spielbar.
Jetzt habe ich jedoch eine Swing GUI gebastelt in der das Spiel ablaufen soll.

Momentan hat das Spiel eine Zentrale Verwaltungsklasse mit einer play() Methode, die
mit while Schleifen das rundenbasierte Spielen ermöglicht.
Diese play() Methode arbeitet mit readLine() und ist somit über Konsoleneingaben spielbar.
Wie stelle ich es nun an, dass anstatt einer Konsoleneingabe (z.B. "Zug beendet"), ich den
Befehl über einen JButton in der GUI der play() Methode übergeben kann.

Prinzipiell müsste ich ja über die actionPerformed() Methode der GUI der play() Methode sagen,
was sie machen soll. Da hänge ich allerdings fest.
 
Zuletzt bearbeitet:
Ja genau, ich arbeite mit ActionListener, allerdings weiß ich nicht wie ich der play() Methode in der Zentralen Verwaltung über den
ActionListener in der GUI den Befehl übergebe.
Wie gesagt bisher läuft alles über Texteingabe mithilfe von readLine().
 
Kannste mal ein Beispiel des Quellcodes posten? Kann es noch nicht so ganz nachvollziehen.
 
Code:
System.out.println("Welche Handlung soll ausgeführt werden? ");
System.out.println("'bewegen'(nur 1x pro Zug, regenerieren wenn Zielfeld = Startfeld), 'ablegen'(Gegenstand ablegen) oder 'fertig'(beendet den Zug)");
eingabe = in.readLine();
if (eingabe.equals("bewegen") && darfBewegen == true) {
     try {
		System.out.println("x-Koordinate des Zielfeldes?");
		eingabe = in.readLine();
		int xPosZiel = Integer.parseInt(eingabe);

		System.out.println("y-Koordinate des Zielfeldes?");
		eingabe = in.readLine();
		int yPosZiel = Integer.parseInt(eingabe);

		bewegen(spielbrett.getFigurenliste().get(i), yPosZiel, xPosZiel);

Das ist ein Auszug aus der play() Methode in der Zentralen Verwaltungsklasse, die hier auf readLine() Eingaben auf der Konsole wartet.

Jetzt will ich die Werte nicht über die Konsole übergeben sondern über den entsprechenden JButton in der GUI.
Dort habe ich das Spielbrett durch JButtons realisiert. Jedes Spielfeld hat einnen JButton der als ActionCommand die Koordinaten des Spielfeldes zurückgibt. Wie bekomme ich das jetzt mit der play() Methode verbunden.
 
Die Logik deines Spiels enthält Aufrufe von readLine, richtig?

Wenn das so ist, musst du diese Aufrufe ersetzen.
Am besten abstrahierst du von der eigentlichen Eingabe, so dass du eine Klasse hast, die allgemein Benutzeriengaben gehandelt. Eine Subklasse davon behandelt dann Konsoleneingaben und eine andere GUI-Eingaben. Deiner Logik übergibst du dann ein Objekt der jeweiligen Klasse (je nachdem ob du GUI oder Konsole willst) (Stichwort: Strategy Pattern).
Die für die GUI zuständige Klasse verbindest du dann mit dem GUI.

In der Main-Klasse kannst du dann z. B. die Klassen entsprechend erzeugen und konfigurieren, je nachdem ob GUI oder Konsole genutzt werden soll (z. B. indem du ein Argument beim Starten übergibst und in der main-Methode prüfst).

Zusammengefasst:
Refactoring durchführen:
1. von readLine() abstrahieren durch neue Oberklasse A
2. play()-Methode mit Hilfe dieser Oberklasse schreiben
3. Subklasse von A mit readLine() schreiben
4. Prüfen, ob noch alles wie vorher funktioniert
5. Subklasse von A für die GUI Eingabe
6. GUI mit Klasse aus 5. verbinden (am einfachsten: in den actionPerformed Methoden der ActionListener Methoden der Klasse aus 5. aufrufen).
7. Der Logik jetzt die Subklasse aus 5. statt der aus 3. übergeben

Je nachdem, in wie weit du in deinem Entwurf Kopplung etc. beachtet hast und ob du sinnvolle Abstrationen bereits benutzt hast, ist das mehr oder weniger aufwendig.

EDIT: nach Überfliegend es Quelltextes
Aus der play()- Methode muss alles mit System.out.println() und readLine() heraus. Das ist Darstellung und hat nichts mit der Logik zu tun! Das packst du in eine eigene Klasse samt Oberklasse (wie oben beschrieben). Dadurch trennst du die Logik von der Darstellung und kannst somit die Logik unabhängig von der Darstellung nutzen.
So wie es jetzt ist, kannst du die Logik nämlich für das GUI nochmal komplett neu schreiben, da du auf dem GUI mit System.out.println() und readLine() nichts anfangen kannst.
 
Zuletzt bearbeitet:
Konsoleneingaben sollen in Zukunft nicht mehr möglich, bzw. nötig sein. Alles soll über die GUI laufen.
Momentan wird bei der Erstellung der GUI gleich im Konstruktor der GUI die Zentrale Verwaltungsklasse(dort sitzt die play() Methode und alle Objekte des Spiels) erzeugt, welche wierderum in ihrem Konstruktor ein Spielbrett aus Spielfeldern erzeugt.

Fehlermeldungen sollen über Popups in der GUI und Textausgabe über eine JTextArea laufen, was sowiet auch schon funktioniert.

Nur das umschreiben von der Funktionalität über readLine() hin zu ActionListener bereitet mir Schwierigkeiten.
 
Nochmal mein Edit von oben, falls nicht gelesen:
EDIT: nach Überfliegend es Quelltextes
Aus der play()- Methode muss alles mit System.out.println() und readLine() heraus. Das ist Darstellung und hat nichts mit der Logik zu tun! Das packst du in eine eigene Klasse samt Oberklasse (wie oben beschrieben). Dadurch trennst du die Logik von der Darstellung und kannst somit die Logik unabhängig von der Darstellung nutzen.
So wie es jetzt ist, kannst du die Logik nämlich für das GUI nochmal komplett neu schreiben, da du auf dem GUI mit System.out.println() und readLine() nichts anfangen kannst.

JETZT DER NEUE TEIL:
Wenn Konsole nicht mehr möglich sein soll, muss halt einfach alles, was die Konsole verwendet, entsprechend geändert werden. Also jedes System.out und jedes readLine mit entsprechenden GUI Methoden ändern. Dafür kann es dann sinnvoll sein, die Logik (falls nicht bereits gemacht) in mehrere Methoden aufzuteilen, so dass dann jeder Button einfach die ensprechende Methode aufruft.

Aus Entwurfssicht ist es aber nicht wünschenswert, GUI und Logik stark zu koppeln, da man die Logik dann nicht mehr ohne die GUI verwenden kann. Deshalb sollte z. B. die Logik niemals (bzw. in den allermeisten Fällen) direkt das GUI ansprechen, sondern stattdessen sollte das Observer-Pattern verwendet werden. Ist zwar umständlicher, liefert aber bessere Wartbarkeit und Wiederverwendbarkeit.
 
Zuletzt bearbeitet:
insXicht schrieb:
Code:
System.out.println("Welche Handlung soll ausgeführt werden? ");
System.out.println("'bewegen'(nur 1x pro Zug, regenerieren wenn Zielfeld = Startfeld), 'ablegen'(Gegenstand ablegen) oder 'fertig'(beendet den Zug)");
eingabe = in.readLine();
if (eingabe.equals("bewegen") && darfBewegen == true) {
     try {
		System.out.println("x-Koordinate des Zielfeldes?");
		eingabe = in.readLine();
		int xPosZiel = Integer.parseInt(eingabe);

		System.out.println("y-Koordinate des Zielfeldes?");
		eingabe = in.readLine();
		int yPosZiel = Integer.parseInt(eingabe);

		bewegen(spielbrett.getFigurenliste().get(i), yPosZiel, xPosZiel);

Um bei meinem Codebeispiel zu bleiben, mir wäre es am liebsten wenn ich es so hinbekommen würde, dass in diesem speziellen Fall Zuerst in der JTextArea der GUI Gefragt wird "Welche Handlung soll ausgeführt werden? Um die Figur zu bewegen, klicke auf das entsprechende Spielfeld. Wenn du einen Gegenstand ablegen möchtest klicke auf 'Ablegen' und wenn dein Zug beendet ist klicke auf 'Zug Beenden' ".
Dann klickt man Beispielsweise auf den JButton der das Spielfeld darstellt und der play() Methode wird irgendwie übergeben, dass der Spieler sich bewegen möchte und an welche Position. Usw...
Dabei dürfen in diesem Fall GUI und Logik ruhig gern gekoppelt werden, auch wenn das nicht optimal ist.
 
Ich habe da schnell mal was zusammengeklickt:

Main.java:

Code:
package game;

import javax.swing.JDialog;

public class Main {

  /**
   * Launch the application.
   */
  public static void main(String[] args) {
    try {

      Game game = new Game();

      GameDialog dialog = new GameDialogImpl(game);
      dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
      dialog.setVisible(true);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

}

GameDialog.java
Code:
package game;

import java.awt.BorderLayout;
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextField;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.BoxLayout;

public abstract class GameDialog extends JDialog {

  /**
   * 
   */
  private static final long serialVersionUID = 1L;
  private final JPanel contentPanel = new JPanel();
  protected JTextField textField;
  protected JTextField textOut;

  /**
   * Create the dialog.
   */
  public GameDialog() {
    setBounds(100, 100, 450, 300);
    getContentPane().setLayout(new BorderLayout());
    contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
    getContentPane().add(contentPanel, BorderLayout.CENTER);
    {
      textField = new JTextField();
      textOut = new JTextField();
      textOut.setEditable(false);
      contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.X_AXIS));
      contentPanel.add(textField);
      contentPanel.add(textOut);
      textField.setColumns(10);
    }
    {
      JButton btnNewButton = new JButton("New button");
      btnNewButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {

          btnNewButtonActionPerformed(e);
        }
      });
      contentPanel.add(btnNewButton);
    }
    {
      JPanel buttonPane = new JPanel();
      buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
      getContentPane().add(buttonPane, BorderLayout.SOUTH);
      {
        JButton okButton = new JButton("OK");
        okButton.setActionCommand("OK");
        buttonPane.add(okButton);
        getRootPane().setDefaultButton(okButton);
      }
      {
        JButton cancelButton = new JButton("Cancel");
        cancelButton.setActionCommand("Cancel");
        buttonPane.add(cancelButton);
      }
    }
  }

  protected abstract void btnNewButtonActionPerformed(ActionEvent e);

}


GameDialogImpl.java
Code:
package game;

import java.awt.event.ActionEvent;

public class GameDialogImpl extends GameDialog {

  private static final long serialVersionUID = 1L;
  private Game game;

  public GameDialogImpl(Game game) {
    this.game = game;

    game.setGameDialogImpl(this);

    textOut.setText("Welche Handlung soll ausgeführt werden? ");
    //System.out.println("'bewegen'(nur 1x pro Zug, regenerieren wenn Zielfeld = Startfeld), 'ablegen'(Gegenstand ablegen) oder 'fertig'(beendet den Zug)");

  }

  @Override
  protected void btnNewButtonActionPerformed(ActionEvent e) {

    this.game.setStuff(textField.getText());

    this.game.doStuff();

  }

  public void setOutText(String text) {
    textOut.setText(text);
  }

}


Game.java
Code:
package game;

public class Game {

  private GameDialogImpl gameDialogImpl;
  private String eingabe;
  private boolean darfBewegen = true;

  public void setStuff(String eingabe) {
    this.eingabe = eingabe;

  }

  public void doStuff() {

    if (eingabe.equals("bewegen") && darfBewegen == true) {

      this.gameDialogImpl.setOutText("Er bewegt sich!");
    }

  }

  public void setGameDialogImpl(GameDialogImpl gameDialogImpl) {

    this.gameDialogImpl = gameDialogImpl;

  }

}

EDIT: Und nochmal CODE Erweitert.
 
Zuletzt bearbeitet: (Angepasst)
Genereller Aufbau (ohne auf Kopplung etc. zu achten)
- Klasse Spiellogik
- Klasse GUI
- Die beiden Klassen besitzen eine Instanz der jeweils anderen Klasse
- Jedes Mal, wenn Text auf der Konsole ausgegeben wird in der Spiellogik, wird myGUI.getTextFeldXYZ.setText("Bla") aufgerufen (analog für JTextAreas oder JOpenPanes etc.)
- Jeder Button bekommt einen Actionlistener. In der actionPerformed-Methode des Listeners rufst du dann die jeweils passende Methode der Spiellogik auf, die zu der Situation passt.

Wenn man das Spielfeld (oder ander Buttons) in mehreren Situationen anklicken kann, bei denen jeweils etwas anderes passiert, muss der Zustand des Spiels irgendwo erfasst werden und die Logik muss dann entscheiden, was zu tun ist.

Anstelle von einer while()-Schleife in der Logik, könntest du eventuell auch die gesamte Logik in mehrere Teil-Methoden aufteilen. Über die GUI-Buttons wird dann immer die jeweils passende Methode aufgerufen. So kannst du dir ein Thread.sleep sparen, sofern das Spiel wirklich nur schrittweise abläuft und zu jedem Zeitpunkt nur bestimmte Benutzereingaben möglich sind.
So wird beim starten der GUI startGame() aufgerufen. Diese Methode ändert dann den Zustand von Spiellogik und GUI. Ein Klick auf einen Button ruft dann die nächste Methode auf usw.
 
Zurück
Oben