JavaFX Hangman-Spiel startet nicht

PatrickS

Neues Mitglied
Hallo Liebe Community,

ich habe schon viel hier im Forum gelesen und konnte schon viele gute Tipps und Tricks auch herauslesen.
Leider konnte ich zu meinem aktuellen Problem keine Lösung finden.
Es geht darum, das ich im Zuge eines OnlineStudiums ein Hangman Spiel programmiert habe.
So weit so gut, den Quellcode musste ich nur abtippen und hatte dann alles vor mir.

Leider kam aber beim Start dann ein Fehler "java.lang.IllegalAccessError"

Da ich mir nicht sicher war ob ich einen Fehler beim abtippen gemacht habe, hab ich den Quellcode aus dem Studienheft heraus kopiert und es noch einmal probiert. Auch hier das gleiche Ergebnis.
Mein letzter Versuch war, die Projektdateien aus dem Downloadbereich des Onlinestudiums zu holen und in Eclipse zu importieren. Selbst mit den Originaldateien kam wieder der gleiche Fehler.

Jetzt ist meine Hoffnung das hier jemand den Fehler im Code sieht und mir die Augen öffnen kann.

Hier die FXML Main Klasse:

Java:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class HangmanFX extends Application{
    @Override
    public void start(Stage meineStage) throws Exception {
        //die Datei laden
        Parent root = FXMLLoader.load(getClass().getResource("sb_hangman.fxml"));   

        //die Szene erzeugen
        //an den Konstruktor werden der oberste Knoten und die Größe übergeben
        Scene meineScene = new Scene(root, 350, 450);
        
        //den Titel über stage setzen
        meineStage.setTitle("Hangman");
        //die Szene setzen
        meineStage.setScene(meineScene);
        //und anzeigen
        meineStage.show();
    }
    
    public static void main(String[] args) {
        //der Start
        launch(args);
    }
}

Das ist der FXML Controller:

Code:
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;

public class FXMLController {
    //das Kombinationsfeld
    @FXML private ComboBox<String> auswahl;
    //die Labels für die Ausgaben
    @FXML private Label ausgabeText;
    @FXML private Label anzVersuche;   
    //für die Zeichenfläche
    @FXML private Canvas zeichenflaeche;   
    
    //ein Array mit Zeichenketten(!) für die Buchstaben
      private String [] zeichen = new String[26];
      //ein StringBuilder für die Darstellung des Suchwortes
      private StringBuilder anzeige;
      //ein String für das gesuchte Wort im Klartext
      private String suchwort;
      //für die verbleibenden Durchläufe
      private int restDurchlauefe;
      //für die Anzahl der Fehler
      private int fehler;
      //für den Grafikkontext
      private GraphicsContext gc;

    //die Methode zur Auswahl aus dem Kombinationsfeld
    @FXML protected void auswahlNeu(ActionEvent event) {
        //der aktuell ausgewählte Eintrag wird übergeben und ausgewertet
        pruefen(auswahl.getSelectionModel().getSelectedItem());
        //ist das Spiel zu Ende oder nicht?
        gewinnerOderNicht();
    }

    //die Methode zum Beenden
    @FXML protected void beendenKlick(ActionEvent event) {
        Platform.exit();
    }
    
    //die Methode setzt die Initialwerte
    //sie wird automatisch ausgeführt
    @FXML void initialize() {
        int tempIndex = 0;
        //es geht los mit 9 verbleibenden Durchläufe
        restDurchlauefe = 9;
        //die restlichen Durchläufe anzeigen
        anzVersuche.setText(Integer.toString(restDurchlauefe));
        
        //die Liste für das Kombinationsfeld füllen
        for (char temp = 'a'; temp <= 'z'; temp++) {
            zeichen[tempIndex] = Character.toString(temp);
            tempIndex++;
        }
        
        auswahl.getItems().addAll(zeichen);
        //ein Wort ermitteln
        neuesWort();       
        
        //den Grafikkontext beschaffen
        gc = zeichenflaeche.getGraphicsContext2D();
    }   
    
    //die Methode ermittelt zufällig ein Wort
    private void neuesWort() {
        int zufall = 0;
        //die Wortliste
        String[] woerter ={"Test", "Automobil", "Versuch", "Hund", "Katze",
                "Ziege", "Maus", "Elefant", "Isopropylalkohol", "Schwimmbad"};
        //ein zufälliges Wort ermitteln
        //dazu wird eine zufällige Zahl zwischen 0 und 1 ermitteln und mit der Länge von woerter multipliziert
        zufall = (int)(Math.random() * woerter.length);
        
        //das Suchwort und die Zeichen für die Anzeige setzen
        suchwort = new String(woerter[zufall]);
        anzeige = new StringBuilder(suchwort);
        
        //alle Zeichen in der Anzeige ersetzen durch *
        for (int zeichen = 0; zeichen < suchwort.length(); zeichen++)
            anzeige.setCharAt(zeichen, '*');

        //die Sternchen anzeigen
        ausgabeText.setText(anzeige.toString());
    }
    
    //die Methode zum Prüfen
    private void pruefen(String auswahlZeichen) {
        char zeichen;
        int treffer = 0;
        //das ausgewählte Zeichen aus dem Kombinationsfeld umbauen
        zeichen = auswahlZeichen.charAt(0);
        //gibt es das Zeichen auch im Suchwort?
        //dabei vergleichen wir nur die Kleinbuchstaben
        treffer = suchwort.toLowerCase().indexOf(zeichen);
        //wenn wir nichts gefunden haben
        if (treffer < 0) {
            //1 von den verbleibenden Durchläufen abziehen
            restDurchlauefe--;
            //die restlichen Durchläufe anzeigen
            anzVersuche.setText(Integer.toString(restDurchlauefe));
            //die Fehler für die Anzeige erhöhen und den Galgen zeichnen
            erhoeheFehler();
        }
        else {
            //nach weiteren Vorkommen suchen
            while (treffer >= 0) {
                //das Zeichen aus der entsprechenden Position im Suchwort anzeigen
                anzeige.setCharAt(treffer, suchwort.charAt(treffer));
                //treffer erhöhen und dann weitersuchen
                treffer++;
                treffer = suchwort.toLowerCase().indexOf(zeichen,treffer);
            }
            //das geänderte Wort anzeigen
            ausgabeText.setText(anzeige.toString());
        }
    }
    
    private void gewinnerOderNicht() {
        //die Linienbreite auf 1 setzen
        gc.setLineWidth(1);
        //ist das Spiel zu Ende?
        if (restDurchlauefe == 0) {
            gc.strokeText("Das gesuchte Wort war: " + suchwort, 20, 100);
            Platform.exit();
        }
        //ist das Wort erraten worden?
        if (anzeige.toString().equals(suchwort)) {
            gc.strokeText("Hurra! Sie haben gewonnen!", 20, 100);
            Platform.exit();
        }
    }
    
    //Fehler hochzählen und den Galgen zeichnen
    private void erhoeheFehler() {
        fehler = fehler + 1;
        gc.setLineWidth(4);
        
        //je nach Wert von fehler zeichnen
        switch (fehler) {
        case 1:
            gc.strokeLine(10, 10, 10, 200);
            break;
        case 2:
            gc.strokeLine(10, 10, 100, 10);
            break;
        case 3:
            gc.strokeLine(40, 10, 10, 40);
            break;
        case 4:
            gc.strokeLine(100, 10, 100, 50);
            break;
        case 5:
            gc.strokeLine(70, 50, 130, 50);
            break;
        case 6:
            gc.strokeLine(130, 50, 130, 110);
            break;
        case 7:
            gc.strokeLine(130, 110, 70, 110);
            break;
        case 8:
            gc.strokeLine(70, 110, 70, 50);
            break;
        case 9:
            gc.strokeLine(0, 200, 20, 200);
            break;
        }
    }
}

Und hier noch die FXML Datei mit der UI:

Code:
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FXMLController">
   <top>
      <GridPane BorderPane.alignment="CENTER">
        <columnConstraints>
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="10.0" />
          <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <Label text="Das gesuchte Wort" />
            <Label text="Verbleibende Versuche:" GridPane.rowIndex="1" />
            <Label fx:id="ausgabeText" GridPane.columnIndex="1" />
            <Label fx:id="anzVersuche" GridPane.columnIndex="1" GridPane.rowIndex="1" />
         </children>
      </GridPane>
   </top>
   <bottom>
      <HBox prefHeight="41.0" prefWidth="600.0" spacing="150.0" BorderPane.alignment="CENTER">
         <children>
            <Label text="Ihre Auswahl:" />
            <ComboBox fx:id="auswahl" onAction="#auswahlNeu" prefWidth="150.0" />
            <Button mnemonicParsing="false" onAction="#beendenKlick" text="Beenden" />
         </children>
      </HBox>
   </bottom>
   <center>
      <Canvas fx:id="zeichenflaeche" height="300.0" width="300.0" BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

Anfangs hatte ich das Problem den Controller mit der UI zu verbinden (mache das über den SceneBuilder Version 11.0.0).
Es hat erst geklappt als ich das Standardpaket in Java verwendet habe, spezifische Pakete konnte ich nicht erstellen.

Ich hoffe ich habe nicht zu verwirrend geschrieben. Da das hier mein erster Post ist, muss ich mich noch etwas einarbeiten.

Achja, hier noch die Fehlermeldung die angezeigt wird:

Code:
Exception in Application start method
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
    at java.base/java.lang.Thread.run(Thread.java:830)
Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module @0x19901c63) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module @0x19901c63
    at com.sun.javafx.fxml.FXMLLoaderHelper.<clinit>(FXMLLoaderHelper.java:38)
    at javafx.fxml.FXMLLoader.<clinit>(FXMLLoader.java:2056)
    at HangmanFX.start(HangmanFX.java:11)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    ... 1 more
Exception running application HangmanFX
 

KonradN

Super-Moderator
Mitarbeiter
So Stacktraces liest man von Unten nach Oben, denn eine Exception basiert oft auf anderen Exceptions und wird daher weiter gegeben. Die eigentliche Problematik findet sich somit in der untersten Exception:

Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module @0x19901c63) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module @0x19901c63

Du hast also hier ein Problem mit dem Java Modul System. Der Code wird also vermutlich für Java 8 geschrieben worden sein und Du nutzt ein späteres Java. Generell wäre hier ggf. die Frage, welche Java und JavaFX Versionen Du genau nutzt und wie Du alles baust und dann auch startest.

Aber unabhängig davon: Du kannst beim Start Module öffnen und so. Du hast in der Nachricht 4 wichtige Informationen:
  • Welches Modul (javafx.graphics)
  • welches package (com.sun.javafx.util)
  • für welches Modul (unnamed module)
  • exportieren
muss.

Ich habe die Teile mal fett gemacht in der Exception.

Der Parameter beim Java Aufruf wäre dann also ein --add-export javafx.graphics/com.sun.javafx.util=ALL-UNNAMED

Siehe dazu z.B. Five Command Line Options To Hack The Java Module System // nipafx

Noch als Anmerkung: Mit aktuellen Versionen sollte so ein Problem nicht auftreten. (Aber durchaus Probleme, bei denen ein --add-opens notwendig wird, da der FXMLLoader etwas nutzt, das als "Deep Reflection" bezeichnet wird und daher ein opens auf das Package vom Controller benötigt.)
 

PatrickS

Neues Mitglied
Hallo KonradN,

vielen dank für die schnelle Antwort.

Zu deinen Fragen:

Ich benutze:

Java:
openjdk 13.0.1 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)

JavaFX:
openjfx 13.0.1

Leier verstehe ich nicht was du damit meinst, wie ich alles baue und starte.

Ich habe die Parameter in die VM-Argumente eingetragen. Jetzt erscheint zwar der Fehlercode nicht mehr, dafür aber ein Fehlerfenster beim Start.

1683193227853.png

Ich habe schon über cmd mit dem Befehl: java --version geprüft ob java korrekt installiert ist und über die Systemvariablen Arbeitsspeicher mit _JAVA_OPTIONS: -Xmx512m zugewiesen (die Tipps konnte ich zumindest dazu finden über Suchmaschinen)
Leider ohne einen Erfolg.

UPDATE:

habe mich in dem Artikel den du mir geschickt hast einmal eingelesen.
Das Argument musste --add-exports heisen. Nachdem ich das S eingefügt habe, hat es mir wieder eine Reihe Fehler angezeigt. Danke deiner Beschreibung wie ich eine Argument Variable erstelle. Konnte ich jetzt nach 2-3 weiteren Zeilen das Programm zum laufen bekommen.

Vielen Lieben Dank für die Hilfe.
 
Zuletzt bearbeitet:

KonradN

Super-Moderator
Mitarbeiter
Das sieht erst einmal so aus, als ob es ein Problem mit der Java Installation gibt.

Meine Empfehlung wäre, dass Du alle Java Installationen deinstallierst. Statt dessen installierst Du ein aktuelles OpenJDK.

Leier verstehe ich nicht was du damit meinst, wie ich alles baue und starte.
Was machst Du genau um die Software zu übersetzen? Nutzt Du eine Entwicklungsumgebung? Welche? Wie sieht das Projekt aus?

Davon ist dann Abhängig, was Du genau installieren solltest:

a) Meine Empfehlung wäre immer, dass Du ein Standard OpenJDK installierst. Ich nutze sehr gerne Adoptium von Adoptium.net. Als Version sollte man entweder 17 (aktuelle LTS Version) oder 20 (aktuelle Java Version) nutzen. Und die Projekte sollten dann ein Maven (oder Gradle) Projekt sein. Dann hast Du das Projekt als eine Text Datei, die man auch einfach teilen kann und Anpassungen sind da sehr einfach. Wie so ein Start aussehen könnte, werde ich weiter unten erläutern.

b) Eine weitere Möglichkeit wäre, ein OpenJDK zu installieren, das auch ein JavaFX enthält. Das vereinfacht es, weil man keine JavaFX Abhängigkeiten angeben muss. Das ist aber nur gut, so lange man wirklich kein Projekt hat, mit dem Abhängigkeiten gut und einfach verwalten kann. Das wäre z.B. Download Java | Java 8, Java 11, Java 17, Java 20 - OpenJDK Builds for Linux, Windows & macOS (bell-sw.com)

c) Man kann natürlich mit dem Standard OpenJDK arbeiten und dann in der Entwicklungsumgebung alles eintragen. Im Getting Starten auf openjfx.io ist das im Detail erklärt. Aber das hat eine Komplexität, die gerade am Anfang schlicht unnötig ist und die Fehlersuche / Unterstützung im Forum ist einfach mangelhaft (Dann darf man mit vielen Bildschirmfotos arbeiten oder so ...)

Meine Empfehlung geht daher ganz klar in Richtung a). Wenn es noch ok wäre, dann würde ich empfehlen:
1. Alles an Java / JavaFX Deinstallieren und dann Adoptium OpenJDK installieren (incl. aller Optionen - kann nicht schaden) (Hinweis am Ende beachten!)
2. Lade IntelliJ herunter so Du das noch nicht nutzt. Die Community Edition ist alles was Du brauchst!
3. Entweder ein Schnellstart über das Getting Started auf openjfx.io bezüglich Maven projekt oder lade einfach das Projekt von kneitzel/JavaFXMavenApp: Simple JavaFX App with Maven (github.com) herunter. Da ist zwar einiges mehr drin, aber damit kannst Du super starten und dann auch nach und nach immer mehr "erforschen" weil da viel drin ist.

Der große Vorteil ist, dass Du mit dem "Maven Weg" hier sehr gut Unterstützung bekommen kannst. Nicht nur von mir sondern von einigen Leuten hier im viel Erfahrung. Und es ist einfach, die pom.xml des Projekts zu posten und dann können wir einfach Anpassungen vorschlagen.



Jetzt am Ende noch ein Hinweis zu der Java Version, die man installiert. Die Hinweise zu Adoptium setzen voraus, dass Du Windows oder einen macOS Rechner nutzt. Unter Linux lädst Du nichts herunter. Da verwendest Du einfach das openjdk aus dem Linux Repository!
 

M.L.

Top Contributor
Grundsätzlich funktioniert das Programm (Testumgebung: IntelliJ, OpenJDK 17, JavaFX vorab installiert). Die IDE-Vorgabe ist aber wohl an mehreren Stellen zu modifizieren: (HelloApplication.java)
Code:
 Scene meineScene = new Scene(/*root,*/ fxmlLoader.load() , 350, 450);
sowie (hello-view.fxml)
Code:
 <BorderPane ... fx:controller="com.example.hangman.HelloController">

[ATTACH type="full"]21072[/ATTACH]
 

Anhänge

  • IntelliJ_HangmanFX.JPG
    IntelliJ_HangmanFX.JPG
    82,8 KB · Aufrufe: 0

Neue Themen


Oben