www.r-krell.de
Webangebot für Schule und Unterricht, Software, Fotovoltaik und mehr

Willkommen/Übersicht  >  Informatik  >  Java-Seite b3) (2018)

Informatik mit Java

Teil b3)_2018
Ergänzung zu Teil b2)_2017,  Programm P9,  Aufgabe 3:
Galgenmann-Spiel mit zusätzlicher Grafik



Eine vollständige Übersicht aller meiner Seiten "Informatik mit Java" gibt's auf der Informatik-Hauptseite! Bezugsseite und Vorgänger dieser Seite ist die Seite b2)_2017.

Auf dieser Seite b3)_2018 finden Sie:


zum Seitenanfang / zum Seitenende



Aufgabenstellung: Übungs-Aufgabe 3 zum Programm P9

Auf der letzten Seite b2)_2017 war bereits das Programm P9 (auch bekannt als Glücksrad, Hangman bzw. Galgenmann/Galgenmännchen) vorgestellt worden: Die Buchstaben eines geheimen Worts werden durch Unterstriche markiert. Die Spielerin bzw. der Spieler kann einen Buchstaben des Alphabets nachfragen bzw. eintippen. Ist der gefragte Buchstabe im Wort vorhanden, wird er dort jeweils stellenrichtig angezeigt. Es geht darum, das gegebene Wort möglichst schnell, d.h. mit möglichst wenig abgefragten Buchstaben, zu erraten. In der nachfolgend dargestellten Variante dürfen maximal 10 Buchstaben erfragt werden. Für jeden gefragten Buchstaben wird ein Strich zu einer Galgenmännchen-Zeichnung hinzu gefügt. Nach 10 Buchstaben bzw. 10 Strichen (wobei der Kopf als ein Strich gilt) ist die Zeichnung fertig und es kann nur noch das ganze Wort erraten werden, ohne dass weitere Buchstaben abgefragt werden dürfen.

Die Bilder zeigen das Spiel nach 7 bzw. 10 erfragten Buchstaben:

Bildschirmfoto: Galgenmann-Spiel nach 7 gefragten Buchstaben   Bildschirmfoto: Galgenmann-Spiel nach 10 gefragten Buchstaben



Das Spiel soll in Java programmiert werden. Im Folgenden wird möglicher Java-Quelltext in zwei Versionen für zwei verschiedenen Grafik-Darstellungen (P9_A3a / _A3b) vorgestellt und besprochen. Gegenüber dem ursprünglichen Programm P9 (siehe unten auf der letzten Seite b2)_2017) kommt jetzt rechts das Rechteck mit der grafischen Darstellung hinzu. Am Ende dieser Seite können die Programme auch ausprobiert werden!




zum Seitenanfang / zum Seitenende

Aufteilung in drei Klassen



Nach den bisherigen Programmierübungen müsste klar sein, dass der Programmtext sinnvollerweise auf mindestens drei Dateien/Klassen verteilt werden sollte:

  1. die Hauptklasse (inkl. der Startmethode main) für die Darstellung der Oberfläche und die Zusammenarbeit mit der Spielfunktion (Klasse 2) sowie der (austauschbaren) Einbindung eines Grafik-Fensters (Klasse 3)
  2. eine weitgehend oberflächenunabhängige Klasse 2 mit der eigentlichen Spiellogik bzw. -funktion, die insbesondere ein zu erratendes Wort bereitstellen kann, erfragte Buchstaben zählen und im Wort anzeigen kann sowie ein- bzw. übergebene Lösungswörter prüft
  3. eine Klasse für das Grafikfenster mit allen für die Darstellung benötigten Methoden, wobei die Bedienung natürlich sehr einfach sein sollte (indem einfach bei nächsterStrich der nächste passende Strich zum Galgen hinzu gezeichnet wird, ohne dass die aufrufende Stelle sagen oder auch nur wissen muss, welcher Strich das ist). Das durch diese Klasse 3 erzeugte Objekt sollte an Stelle eines normalen Java-JPanels in die Oberfläche (Klasse 1) eingebaut werden können, also praktisch ein auf/um die Galgenmännchen-Zeichnung spezialisiertes JPanel werden.

Für die Klasse 3 wird es zwei verschiedene Realisierungen geben: a) mit Strichzeichnungen per Java-Grafik oder b) mit dem Aufruf vorgefertigter Bilder, die vorab mit einem externen Zeichen- bzw. Grafikprogramm erstellt wurden, und jetzt nur passend angezeigt werden. Der Austausch der beiden Varianten 3a) und 3b) soll nicht zu Änderungen der Klassen 1 und 2 zwingen, sieht man von unterschiedlichen Namen ab, d.h. dass einmal die Klasse 3a oder zum anderen die Klasse 3b aufgerufen werden muss, alles andere aber gleich bleiben kann.

Sie als Leserin bzw. Leser sind natürlich herzlich aufgefordert, spätestens jetzt die Lektüre dieser Webseite zu unterbrechen und sich selbst Gedanken über eine passende Realisierung zu machen. Nach Fertigstellung Ihrer eigenen Version könnten Sie danach im Folgenden meinen Ansatz nachlesen, der nicht den Anspruch erhebt, die allein seeligmachende Umsetzung zu sein. Ein Vergleich verschiedener Implementationen hilft immer, wichtige Grundzüge zu erkennen.

Und während im Leistungskurs oder später auch hier im Grundkurs vor der Programmierung ein Modell bzw. Konzept erarbeitet werden soll, wobei graphische Mittel wie z.B. UML-Klassendiagramme sowie eine Schnittstellenbeschreibung für die anstehende Modellierung eingesetzt werden, will ich hier im Anfangsunterricht des ersten Grundkursjahrs noch nicht darauf bestehen: Nach meiner Erfahrungen machen solche Mittel im Unterricht erst Sinn, wenn die Schülerinnen und Schüler erfahren haben, dass Sie ohne Konzept das Zusammenspiel verschiedener Klassen nicht ausreichend durchschauen bzw. planen können. Erst dann sind sie bereit, sich mit Modellierung und Modellierungswerkzeugen zu beschäftigen - vorher werden solche Maßnahmen eher als zusätzliche Schwierigkeit statt als willkommene Hilfe angesehen. (Hinweise auf Modellierungen und geschickte Klassenaufteilung findet man auf vielen meiner Ausführlichen Seiten zur Informatik mit Java c) bis k) sowie DB3 bei den jeweils dort entwickelten Programmen, außerdem natürlich konzentriert auch auf den beiden Seiten zum Software-Engineering SWE und SWE-2 -- auf der letztgenannten Seite SWE-2 konkret am Beispiel eines Kartenspiel-Projekts).




zum Seitenanfang / zum Seitenende

Klasse 1: Oberfläche
P9_A3_Galgenmann.java



Die Programm-Oberfläche wird mit dem im Javaeditor eingebauten GUI-Builder bequem per drag & drop in einen neuen JFrame zusammen gezogen. Der Javaeditor erzeugt dabei den zugehörigen Quelltext automatisch, wobei Namen und Texte im Objekt-Inspektor geändert bzw. eingegeben werden. Nur wenige Zeilen müssen nachher noch "von Hand" in den so erhaltenen Programmtext eingefügt werden. Und wenn die Klasse P9_A3a_Panel.java schon kompiliert wurde, reicht ein Rechtsklick auf das Panel-Icon (das graue Quadrat in der Werkzeugleiste), um an Stelle des normalen JPanels ein P9_A3a_Panel in die Oberfläche einzubinden. (Den sehr empfehlenswerten Javaeditor stelle ich im Teil a) von Informatik mit Java vor; die offizielle Webseite ist javaeditor.org. Die folgende Abbildung zeigen die im Mai 2018 aktuelle Version 15.21 - bei den früher erstellten Programmen in meinem Web-Angebot kamen die damaligen Versionen zum Einsatz).

Bildschirmfoto: Javaeditor mit GUI-Builder für Galgenmann-Programm

Im nachfolgenden, vollständigen Quelltext sind die von Hand eingefügten Zeilen durch rote Zeilennummern gekennzeichnet (wobei Zeile 37 eigentlich doch automatisch erstellt wurde - per Rechtsklick auf das JPanel-Werkzeug, s.o.):


  001 import java.awt.*;
  002 import java.awt.event.*;
  003 import javax.swing.*;
  004 import javax.swing.event.*;
  005 
  006 /**
  007   * P9_A3_Galgenmann 
  008   * Wort-Raten aus erfragten Buchstaben ("Glücksrad", "Galgenmännchen", "Hangman"..)
  009   *
  010   * @version vom 17.5.2018 für http://www.r-krell.de/if-java-b3_2018.htm
  011   * @author  R. Krell (www.r-krell.de)
  012   * 
  013   * Lösung der Übungs-Aufgabe 3 zu P9 auf http://www.r-krell.de/if-java-b2_2017.htm;
  014   * Variante a: mit per Java-Befehlen gezeichnetem Galgenmännchen (in P9_A3a_Panel;
  015   * Variante b: mit Anzeige extern vorgefertigter Galgenbilder (in P9_A3b_Panel;
  016   * (vgl. Zeilen 37 und 51; sonst hier und in P9_A3_GalgenmammFkt alles gleich)
  017   * 
  018   * Der Quelltext dieser Klasse wurde im Wesentlichen mit dem GUI-Builder des Javaeditors
  019   * (javaeditor.org) erzeugt; von Hand nachgetragene Zeilen sind markiert
  
020   * 
  
021   */
  022 
  023 public class P9_A3_Galgenmann extends JFrame {
  024   P9_A3_GalgenmannFkt f = new P9_A3_GalgenmannFkt();  // für a und b gleich
  025 
  026   // Anfang Attribute
  027   private JButton jBtNeu = new JButton();
  028   private JTextField jTfAnzeige = new JTextField();
  029   private JTextField jTfverbraucht = new JTextField();
  030   private JTextField jTfBuchstabe = new JTextField();
  031   private JButton jBtBuchstabe = new JButton();
  032   private JTextField jTfLoesung = new JTextField();
  033   private JButton jBtPruefen = new JButton();
  034   private JLabel jLabel1 = new JLabel();
  035   private JLabel jLabel2 = new JLabel();
  036   private JLabel jLabel3 = new JLabel();
  037   private P9_A3a_Panel galgen = new P9_A3a_Panel();  // bzw. _A3b_ für andere Version
  038   // Ende Attribute
  039   
  040   public P9_A3_Galgenmann() { 
  041     // Frame-Initialisierung
  042     super();
  043     setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
  044     int frameWidth = 444
  045     int frameHeight = 303;
  046     setSize(frameWidth, frameHeight);
  047     Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
  048     int x = (d.width - getSize().width) / 2;
  049     int y = (d.height - getSize().height) / 2;
  050     setLocation(x, y);
  051     setTitle("P9_A3a_Galgenmann  (www.r-krell.de)");   //.. oder _A3b_ bei anderem Galgen
  052     setResizable(false);
  053     Container cp = getContentPane();
  054     cp.setLayout(null);
  055     // Anfang Komponenten
  056     
  057     jBtNeu.setBounds(241623325);
  058     jBtNeu.setText("Neues Spiel / neues Wort");
  059     jBtNeu.setMargin(new Insets(2222));
  060     jBtNeu.addActionListener(new ActionListener() { 
  061       public void actionPerformed(ActionEvent evt) { 
  062         jBtNeu_ActionPerformed(evt);
  063       }
  064     });
  065     cp.add(jBtNeu);
  066     jTfAnzeige.setBounds(245623325);
  067     jTfAnzeige.setEditable(false);
  068     cp.add(jTfAnzeige);
  069     jTfverbraucht.setBounds(2411214525);
  070     jTfverbraucht.setEditable(false);
  071     jTfverbraucht.setFont(new Font("Courier New", Font.PLAIN, 12));
  072     cp.add(jTfverbraucht);
  073     jTfBuchstabe.setBounds(1761122525);
  074     jTfBuchstabe.setHorizontalAlignment(SwingConstants.CENTER);
  075     cp.add(jTfBuchstabe);
  076     jBtBuchstabe.setBounds(2081124925);
  077     jBtBuchstabe.setText("ok");
  078     jBtBuchstabe.setMargin(new Insets(2222));
  079     jBtBuchstabe.addActionListener(new ActionListener() { 
  080       public void actionPerformed(ActionEvent evt) { 
  081         jBtBuchstabe_ActionPerformed(evt);
  082       }
  083     });
  084     cp.add(jBtBuchstabe);
  085     jTfLoesung.setBounds(2416823325);
  086     cp.add(jTfLoesung);
  087     jBtPruefen.setBounds(2420823325);
  088     jBtPruefen.setText("Lösung prüfen");
  089     jBtPruefen.setMargin(new Insets(2222));
  090     jBtPruefen.addActionListener(new ActionListener() { 
  091       public void actionPerformed(ActionEvent evt) { 
  092         jBtPruefen_ActionPerformed(evt);
  093       }
  094     });
  095     cp.add(jBtPruefen);
  096     jLabel1.setBounds(248814419);
  097     jLabel1.setText("gefragte Buchstaben");
  098     cp.add(jLabel1);
  099     jLabel2.setBounds(2414414719);
  100     jLabel2.setText("Lösung / erratenes Wort");
  101     cp.add(jLabel2);
  102     jLabel3.setBounds(176889917);
  103     jLabel3.setText("nächster Bst.");
  104     cp.add(jLabel3);
  105     galgen.setBounds(27216137217);
  106     galgen.setBackground(Color.WHITE);
  107     cp.add(galgen);
  108     // Ende Komponenten
  109     
  110     setVisible(true);
  111   } // end of public P9_A3_Galgenmann
  112   
  113   // Anfang Methoden
  114   
  115   public static void main(String[] args) {
  116     new P9_A3_Galgenmann();
  117   } // end of main
  118   
  119   public void jBtNeu_ActionPerformed(ActionEvent evt) {
  120     f.nächstesWort();
  121     jTfAnzeige.setText(f.anzeige());
  122     jTfverbraucht.setText(f.verwendeteBuchstaben);
  123     jTfBuchstabe.setText("");
  124     jTfLoesung.setText("");
  125     jBtPruefen.setEnabled(true);
  126     jBtBuchstabe.setEnabled(true);
  127     jTfBuchstabe.requestFocus();
  128     galgen.loesche();
  129   } // end of jBtNeu_ActionPerformed
  130 
  131   public void jBtBuchstabe_ActionPerformed(ActionEvent evt) {
  132     if (galgen.zahl() < 10)
  133     {
  134       int alt = f.verwendeteBuchstaben.length();  // Anzahl der bisher gefragten Buchstaben
  135       char bst = (jTfBuchstabe.getText()+"?").charAt(0); // neu gefragter Buchstabe
  136       f.nimmBuchstaben(bst);       // Übergabe des gefragten Buchstabens an die Funktion
  137       int neu = f.verwendeteBuchstaben.length();  // Anzahl jetzt gefragter Buchstaben
  138       if (neu > alt)               // falls gültiger Buchstabe gefragt wurde
  139       {
  140         galgen.nächsterStrich();          // nächsten Strich vom Galgenmann zeichnen
  141         jTfAnzeige.setText(f.anzeige());  // neuen Buchstaben im Wort anzeigen (falls vorhanden)
  142         jTfverbraucht.setText(""+neu+": "+f.verwendeteBuchstaben);  // Zahl und alle bis jetzt..  
  143       }                                   // ..gefragten Buchstaben anzeigen 
  144       jTfBuchstabe.setText("");    // letzten Buchstaben löschen
  145       jTfBuchstabe.requestFocus(); // Cursor in Eingabefeld für weitere Buchstaben 
  146     }
  147     if (galgen.zahl() >= 10)
  148     {
  149       jBtBuchstabe.setEnabled(false);     // OK-Knopf für weitere Buchstaben sperren
  150       jTfLoesung.requestFocus();          // Feld für Lösungseingabe hervorheben
  151     }
  152     
  153   } // end of jBtBuchstabe_ActionPerformed
  154 
  155   public void jBtPruefen_ActionPerformed(ActionEvent evt) {
  156     String lsg = jTfLoesung.getText().trim();
  157     if (f.prüfeLösung(lsg))
  158     {
  159       jTfAnzeige.setText(f.aktuellesWort);   // Wort vollständig anzeigen
  160       jTfLoesung.setText("*"+lsg+"* ist richtig!");  // Lob für richtige Lösung
  161       jBtPruefen.setEnabled(false);       // Weitere Lösungsprüfungen..
  162       jBtBuchstabe.setEnabled(false);     // ..und Buchstabeneingaben abschalten
  163       jBtNeu.requestFocus();              // Neu-Knopf hervorheben
  164     } // end of if
  165     else
  166     {
  167       jTfLoesung.setText("*"+lsg+"* ist falsch!");   // Ablehnen falscher Lösung
  168     }
  169   } // end of jBtPruefen_ActionPerformed
  170 
  171   // Ende Methoden
  172 } // end of class P9_A3_Galgenmann

In den Programmtext wurden einige Feinheiten eingebaut, um die Bedienung zu erleichtern: mit requestFocus wird dafür gesorgt, dass der Eingabe-Cursor anschließend im angegebenen Feld blinkt bzw. der genannte Button vorausgewählt wird. So wird am Anfang (Zeile 127) und bleibt nach der Frage nach einem Buchstaben (Zeile 145) die Buchstaben-Eingabe ausgewählt, damit man ohne zusätzlichen Mausklick den nächsten Buchstaben fragen kann. Nach 10 Buchstaben wird hingegen die Lösungs-Eingabe ausgewählt, weil keine Buchstaben mehr erfragt werden dürfen (Zeile 150 - deshalb wird der zugehörige OK-Knopf dann auch abgeschaltet und nur noch ausgegraut und funktionslos angezeigt: s. Zeile 149). Und nach erfolgreicher Lösung wird man vermutlich ein neues Wort haben wollen - s. Zeile 163. Durch Mausklick kann man die Voreinstellungen natürlich überwinden, also z.B. statt einen weiteren Buchstaben zu erfragen, ins Lösungsfeld klicken und dort die erratene Lösung eingeben.

Das Zusammenspiel mit den beiden anderen Klassen bzw. den dadurch erzeugten Objekten f und galgen lässt sich aus den sprechenden Namen der dort selbstprogrammierten Methoden und dem Kommentar entnehmen. Besonders hingewiesen wird nur auf Zeile 140: durch den Aufruf galgen.nächsterStrich() soll das in Zeile 37 eingebundene erweiterte JPanel namens galgen seinen internen Strichzähler erhöhen sowie die entsprechende Grafik erzeugen und darstellen. Der dafür nötige Programmtext steht aber nicht hier, sondern gekapselt in der Klasse P9_A3a_Panel bzw. P9_A3b_Panel (s.u.)! Hier, in der oben dargestellten zentralen Klasse, sollen eben keine Details der Grafik behandelt werden, sodass die beiden Grafikpanels a und b mit ihren ganz unterschiedlichen Anzeigemechanismen problemlos ausgetauscht werden können.




zum Seitenanfang / zum Seitenende

Klasse 2: Spiellogik bzw. -funktion
P9_A3_GalgenmannFkt.java



Hier müssen jetzt die Funktionen hinterlegt bzw. programmiert werden, die oben (z.B. in den Zeilen 120 und 122 oder auch 134, 136.. der vorstehenden Klasse 1) vom Objekt f verlangt werden. Dabei ist f das nach dem folgendem Bauplan erzeugte Objekt, das alle dort beschriebenen Eigenschaften und Fähigkeiten hat.

Der zugehörige Quelltext kann vollständig von Hand in eine neue Editor-Seite (Datei > Neu > Java) eingetippt werden. Auf Wunsch erzeugt der Java-Editor aber auch halbautomatisch-interaktiv die Rahmen der benötigten Klasse mit Attributen und allen benötigten Methoden, die anschließend 'nur noch' mit dem Programmtext gefüllt werden müssen. Aber schon der leere Rahmen kann kompiliert werden, sodass damit auch der Quelltext der Klasse 1 fehlerfrei kompilierbar ist - auch wenn das so erzeugte Programm noch nicht richtig funktioniert. Ein solcher Rahmen wird mit dem Befehl UML > Neue Klasse erzeugt. Da in Zeile 120 des obigen Programmtextes der 1. Klasse der Aufruf f.nächstesWort() ohne Ausgabe und ohne Zuweisung erfolgt, scheint die Methode nächstesWort keinen Rückgabewert zu benötigen (Rückgabe void bei einer "Prozedur"). Durch den Gebrauch von f.verwendeteBuchstaben() als Argument in setText oben in Zeile 122 wird hingegen offenbar verlangt, dass die Methode verwendeteBuchstaben einen Text zurück gibt, also einen Rückgabewert vom Typ String hat. Sie muss deshalb als Funktion mit passendem Rückgabe-Typ angelegt werden, wie im Bild gezeigt ist:

Bildschirmfoto: Javaeditor mit UML-Klassen-Editor für GalgenmannFkt



Tatsächlich habe ich im Folgenden den gerade gezeigten UML-Klasseneditor des Javaeditors selbst nicht benutzt, sondern den Quelltext lieber vollständig von Hand eingegeben. [Der Java-Editor verfügt nicht nur praktisch über alle von BlueJ bekannten Möglichkeiten inkl. dem Testen von Klassen ohne main-Methode; er kann noch mehr und vereint den Programmtext-Editor mit Syntax-Hervorhebung, Blockstrukturdarstellung, Code-Vervollständigung u.v.a.m. noch mit einem GUI-Builder, dem UML-Klasseneditor und einem Struktogrammeditor. Die letzten drei integrierten Zusatzwerkzeuge erzeugen jeweils automatisch passenden Quelltext! Und natürlich lassen sich auch von händisch eingetipptem Programmtext UML-Diagramme zeichnen (s.u.), während der Struktogramm-Editor z.B. auf meiner folgenden Seite b4)_2018 zum Einsatz kommt]




001 public class P9_A3_GalgenmannFkt
002 // eigentliche Spielfunktion von Galgenmann, unabhängig von der Oberfläche und
003 // davon, ob/wie Grafik angezeigt wird -- 4/2017 & 5/2018 r-krell.de
004 {
005   String[] wort = {"Ratespiel","Buchstabe","Wegesrand","Butterbrot","Turboabitur","Suppenteller",
006     "Musikakademie","Altbierglas","Nacktschnecke","Computermaus","Kaffeemaschine","Kuchengabel",
007     "Trinkbecher","Rotstift","Niedrigpreis","Schlussverkauf","Campingplatz","Ledersofa",
008     "Bleistiftspitzer","Filialleiterin"};    // bel. erweiterbare Wortliste
009   int max = wort.length;      // Anzahl der vorstehend genannten, möglichen Wörter
010   int nr = 0;                 // Anzahl der bereits verwendeten Wörter
011   
012   String aktuellesWort = "";  // aktuell ausgewähltes/verwendetes Wort
013   boolean[] sichtbZeichen = new boolean[26]; 
014   String verwendeteBuchstaben = "";
015   
016       
017   public void nächstesWort()  // wählt zufällig ein bisher nicht benutzes Wort
018   {
019     String gefunden = "";
020     if (nr<max)               // sofern noch nicht alle Wörter verwendet wurden
021     {
022       int pos;
023       do {
024         pos = (int)(Math.random() * max); // zufällige Position in der Wortliste 
025       } while (wort[pos].equals("")); // bei gelöschten Wörtern erneut versuchen
026       gefunden = wort[pos];   // zufällig gefundenes Wort zwischenspeichern
027       wort[pos]="";           // gefundenes Wort aus der Wortliste löschen
028       nr++;                   // Anzahl verwendeter Wörter um 1 erhöhen
029     }
030     aktuellesWort = gefunden.toUpperCase(); // gefundenes Wort wird aktuelles Wort
031     keineBuchstabenBenutzt(); // für neues aktuelle Wort wurden noch keine..
032   }                           // ..Buchstaben geraten: verwendete Buchstaben löschen
033 
034   public void keineBuchstabenBenutzt()    // löscht verwendete Buchstaben
035   {
036     for (int i=0; i<26; i++)    // setzt alle 26 Buchstaben A .. Z des Alphabets
037     {
038       sichtbZeichen[i] = false// auf bisher nicht gefragt und daher nicht anzuzeigen
039     } // end of for
040     verwendeteBuchstaben = "";  // Liste der bisher gefragten Buchstaben leeren
041   }
042   
043   public void nimmBuchstaben (char bst) // prüft und verwendet eingegebenes Zeichen
044   {
045     if ('a' <= bst && bst <='z')   // verwandelt ggf. Kleinbuchstaben in Großbuchstaben
046     {
047       bst = (char)((int)bst-(int)'a'+(int)'A');
048     } // end of if
049     if ('A' <= bst && bst <= 'Z')  // nur bei echten Buchstaben (ungültige Zeichen werden ignoriert!)
050     {
051       sichtbZeichen[(int)bst-(int)'A']=true;   // Buchstabe als sichtbar markieren und
052       verwendeteBuchstaben = verwendeteBuchstaben + bst; // zu verwendeten Buchstaben dazu
053     } // end of if
054   }
055   
056   public String anzeige () // Wort aus Unterstrichen und gefragten/verwendeten Buchstaben 
057   {                        // jeweils mit einem Leerzeichen Zwischenraum
058     String aus = "";
059     for (int i=0; i<aktuellesWort.length() ;i++) 
060     {
061       if (sichtbZeichen[(int)aktuellesWort.charAt(i)-(int)'A'])
062       {
063         aus = aus + " " + aktuellesWort.charAt(i); // sichtbarer Buchstabe
064       } // end of if
065       else 
066       {
067         aus = aus + " _";  // Leerzeichen für noch nicht gefragten Buchstaben
068       } // end of if-else
069     } // end of for   
070     return (aus);
071   }
072   
073   public boolean prüfeLösung (String eingabe)  // Prüfen des eingegeben Lösungsversuchs 
074   {
075     return (eingabe.equalsIgnoreCase(aktuellesWort));
076   }
077 }
 

Die gleiche Klasse wurde schon in der Original-Version des Programms P9 verwendet, da die Spiellogik unabhängig von der Oberfläche und der Grafik ist.

In der Methode nimmBuchstaben (ab Zeile 43 im gelb unterlegten Text) wird überprüft, ob der übergebene Buchstabe bst gültig ist. Dabei gilt auch ein schon früher erfragter Buchstabe als weitere gültige Frage (und bst wird nochmal zur Liste der bereits gefragten Buchstaben hinzugefügt): Ein Spieler, der nicht aufpasst - und sich auch nicht die sogar angezeigten gefragten Buchstaben anguckt -- soll für die Wiederholung bestraft werden. Unbeabsichtigt zu langes (=doppeltes) Drücken des OK-Knopfs schadet hingegen nicht, weil der gefragte Buchstabe sofort aus dem Eingabefeld entfernt wird (Zeile 144 in Klasse 1) und dann dort nichts mehr steht, was beim erneuten Knopfdruck bzw. Aufruf von jBtBuchstabe_ActionPerformed genommen werden könnte (bzw. ein '?' genommen wird: Zeile 135 in Klasse 1). Ziffern, Leer- und Sonderzeichen werden aber einfach ignoriert, nicht gezählt und nicht extra gemeldet. Während sich das bei P9 bewährt hat, wurde bei meinem ersten Versuch der Klasse P9_A3_Galgenmann gelegentlich der Galgen weiter gezeichnet, obwohl kein gefragter Buchstabe hinzu gekommen war. Mit den Zeilen 134, 137 und 138 frage ich jetzt in Klasse 1 ab, ob die Funktion f einen gültigen Buchstaben erkannt hat (wodurch neu größer wird als alt) und zeichne nur noch dann den nächsten Strich vom Galgen, wenn wirklich ein echter Buchstabe eingegeben wurde. Ausgelöst wird das Zeichnen dann von Zeile 140 in Klasse 1; programmiert wird das Zeichnen in der nächsten Klasse, der Klasse 3.




zum Seitenanfang / zum Seitenende

Klasse 3: Grafikpanel
P9_A3a_Panel.java bzw. P9_A3a_Panel.java



Im Anfangsunterricht muss nicht unbedingt der Vererbungsmechanismus ausführlich thematisiert werden: Es reicht zu wissen, dass man das von Java mitgelieferte JPanel durch eigene Methoden (wie nächsterStrich oder zahl) ergänzen und die vorhandene Methode paint so abändern (überschreiben) kann, dass sie nicht nur das leere Panel, sondern darauf auch das Galgenmännchen zeichnen kann. Außerdem muss einmal mitgeteilt werden, dass die Methode paint nicht direkt aufgerufen wird. Besser soll die im System vorhandene (nicht selbst zu [über-]schreibende) Methode repaint aufgerufen werden. repaint erledigt neben weiteren benötigten Hiuntergrund-Aufgaben immer auch den Aufruf von paint (weswegen die paint-Mathode innen zwar abgeändert werden kann, aber nicht anders benannt werden darf). repaint wird auch automatisch vom Computer aufgerufen, wenn das Fenster mit dem Programm verdeckt war oder der Bildschirm erneuert wird. Deswegen ist es sinnvoll, mit paint immer die gesamte bisherige Galgenfigur zu zeichnen und nicht nur den letzten Strich.



a) Grafik durch Zeichnen einzelner Striche mit Java

Handskizze: Galgenfigur als Entwurf für Zeichnung auf Grafik-PanelAuf Kästchenpapier habe ich zunächst eine grobe Skizze des zu zeichnenden Galgenmännchens angefertigt, um die Koordinaten der Endpunkte der einzelnen Striche (Strecken) ablesen zu können. Dabei habe ich bewusst darauf verzichtet, die echte Pixelzahl des in der Oberflächenklasse per Mausklick recht willkürlich aufgezogenen Panels zu verwenden, sondern gebe im Folgenden die Endpunkte 'relativ' an: so beginnt der Rumpf nicht im Punkt (7|5), sondern die x-Koordinate wird mit 7 Elfteln der Breite b und die y-Koordinate mit 5 Siebzehnteln der Höhe h angegeben: drawLine ((int)(7.0*b/11.0),(int)(5.0*h/17.0), (int)(8.0*b/11.0),(int)(9.0*h/17.0)), weil ich zufällig ein Seitenverhältnis von 11 zu 17 gewählt hatte. Die vielen (int)s führen ein TypeCasting auf die nächste, nicht größere ganze Zahl durch, d.h. schneiden die bei der Division evtl. auftretenden Kommastellen zum Schluss ab. Damit vorher aber richtig mit Kommastellen gerechnet wird, sind die Koordinaten als Dezimalzahlen 7.0 statt 7 angegeben. Der in Java als Rumpf des Männchens gezeichnete Strich ist übrigens etwas kürzer ausgefallen als geplant: nach meiner Skizze müsste der untere Punkt bei (8|10) bzw.(int)(8.0*b/11.0),(int)(10.0*h/17.0) liegen (statt wie in Zeile 71 programmiert bei (8|9)). Da die letztlich in der Oberfläche (Klasse 1, Zeile 105) gewählte Bildgröße nicht genau das Seitenverhältnis 11 zu 17 hat, erscheint im Programm die Figur etwas schmaler als auf dem Papier und der Kopf wirkt länglich statt kreisrund. Andererseits würde auch bei einer Vergrößerung oder Verkleinerung des Grafikpanels in (oder mit) der Oberfläche die Figur immer bildfüllend angepasst gezeichnet und es müssen nicht immer wieder Pixel gezählt und verwendet werden.




001 import java.awt.*;
002 import javax.swing.*;
003 import javax.swing.event.*;
004 
005 public class P9_A3a_Panel extends JPanel   // erweitert/ändert normales Java-JPanel 
006 // spezielles Grafik-Panel für die Galgenmann-Oberfläche zur Anzeige des Galgenmanns
007 // aus Linien und einem Kreis per Java-Zeichenbefehlen -- 17.5.2018  r-krell.de
008 {
009   int zahl = 0;   // Anzahl der bereits gezeichneten Striche
010   
011   public void loesche()   // löscht das Panel, in dem alles weiß überstrichen wird
012   {
013     zahl = 0;
014     setBackground(Color.WHITE);
015     repaint();   // Systembefehl repaint ruft Methode paint zum Zeichnen auf
016   }
017   
018   public void nächsterStrich()
019   {
020     if (zahl<10)
021     {
022       zahl++;    // erhöht Anzahl der gezeichneten bzw. zu zeichnenden Striche
023       repaint(); // und ruft indirekt nachfolgende Zeichenmethode paint auf
024     }
025   }
026   
027   public int zahl()   // gibt auf Wunsch die Zahl der gezeichneten Striche aus
028   {
029     return (zahl);
030   }
031   
032 //  @Override    // kann verdeutlichen, dass eine JPanel-Methode überschrieben wird
033   public void paint (Graphics g) // erweitert Standard-Methode um Galgenmann-Striche
034   {
035     super.paintComponent(g); // übergeordnete Graphikmethode für Hintergrund, Ränder..)
036     Graphics2D g2d = (Graphics2D) g;
037     g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
038       RenderingHints.VALUE_ANTIALIAS_ON);
039     g2d.setStroke(new BasicStroke(3 /*, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND */ ));
040     int h = getHeight();     // misst Höhe h und ..
041     int b = getWidth ();     // .. Breite b des Panels/Graphikfensters auf Oberfläch
042     
043     if (zahl > 4)            // ändert Zeichenfarbe fürs Männchen auf blau
044     {
045       g2d.setColor (Color.BLUE);  
046     }
047     else                     // während der Galgen und der Strick schwarz sind
048     {
049       g2d.setColor (Color.BLACK);  
050     }  
051   
052     switch (zahl)
053     // Achtung: Alle Fälle ohne "break", damit auch jeweils alle früheren Teile 
054     // nochmal gezeichnet werden. Wichtig, falls Fenster durch anderes Programm
055     // verdeckt war. 
056     {
057       case 10:   setBackground(new Color(0xFFAFAF)); // farbiger Hintergrund signalisiert Ende
058                  g2d.drawLine ((int)(8*b/11.0),(int)(9.0*h/17.0),
059                    (int)(9.0*b/11.0),(int)(14.0*h/17.0));  // re. Bein  
060                       
061       case  9:   g2d.drawLine ((int)(8.0*b/11.0),(int)(9.0*h/17.0),
062                    (int)(6*b/11.0),(int)(14.0*h/17.0)); // li. Bein  
063                            
064       case  8:   g2d.drawLine ((int)(7.3*b/11.0),(int)(6.0*h/17.0),
065                    (int)(10.0*b/11.0),(int)(9.5*h/17.0));  // re. Arm  
066         
067       case  7:   g2d.drawLine ((int)(7.3*b/11.0),(int)(6.0*h/17.0),
068                    (int)(5.0*b/11.0),(int)(10.0*h/17.0));  // li. Arm  
069         
070       case  6:   g2d.drawLine ((int)(7.0*b/11.0),(int)(5.0*h/17.0),
071                    (int)(8.0*b/11.0),(int)(9.0*h/17.0));   // Rumpf  
072     
073       case  5:   g2d.drawArc ((int)(5.0*b/11.0),(int)(4.0*h/17.0),
074                    (int)(2.0*b/11.0),(int)(2.0*h/17.0),0,360);  // Kopf
075       
076       case  4:   g2d.setColor (Color.BLACK);         // Zeichenfarbe für Galgen und Seil
077                  g2d.setStroke(new BasicStroke(1));  // Seil wird dünner gezeichnet
078                  g2d.drawLine((int)(7.0*b/11.0),(int)(1.0*h/17.0),
079                    (int)(7.0*b/11.0),(int)(5.0*h/17.0));   // Seil        
080         
081       case  3:   g2d.setStroke(new BasicStroke(3));   // breitere Strichstärke für Galgen
082                  g2d.drawLine((int)(1.0*b/11.0),(int)(3.0*h/17.0),
083                    (int)(3.0*b/11.0),(int)(1.0*h/17.0));   // Galgen-Aussteifungs-Schräge  
084               
085       case  2:   g2d.drawLine((int)(1.0*b/11.0),(int)(1.0*h/17.0),
086                    (int)(8.0*b/11.0),(int)(1.0*h/17.0));   // Galgen-Arm (horizontaler Ausleger) 
087         
088       case  1:   g2d.drawLine((int)(1.0*b/11.0),(int)(16.0*h/17.0),
089                    (int)(1.0*b/11.0),(int)(1.0*h/17.0));   // Galgen-Vertikale
090         
091     } // end of switch    
092   }
093 }
 

Die Mehrfach-Verzweigung switch..case wurde extra absteigend angelegt und ohne break geschrieben, damit bei jeder (Strich-)zahl immer auch alle vorangegangenen Striche mit niedrigerer Nummer nochmal gezeichnet werden, damit nie etwas fehlt, wenn das Bild zwischenzeitlich von einem anderen Programm verdeckt war.

In Zeile 39 könnte beim Einstellen der Strickdicke mit setStroke auch die Form des Zeichenwerkzeugs und die Art der Verbindung aneinander stoßender Linien gewählt werden. Die Standardeinstellungen bewähren sich aber, sodass zusätzliche Parameter wieder auskommentiert wurden. Auch die Zeilen 37 und 38 wären bei dieser einfachen Zeichnung wohl entbehrlich: bei schrägen Linien soll damit die sonst eventuell etwas stärker sichtbare Treppenstruktur reduziert werden.

Und natürlich könnte der jetzt auf zwei Zeilen verteilte Befehl setRenderingHint in den Zeilen 37 und 38 ebenso in eine Zeile geschrieben werden wie alle drawLine-Befehle in der Mehrfach-Verzweigung.




zum Seitenanfang / zum Seitenende



b) Grafik durch Anzeigen vorgefertigter Bilder

Wer sich nicht mit Koordinaten und dem drawLine-Befehl beschäftigen will, kann alternativ das Galgenmännchen auch ganz anders darstellen. In einem beliebigen Mal- oder Zeichen-Programm wird das Galgenmännchen einfach Strich für Strich gezeichnet und nach jedem Strich das Bild unter einem neuen Dateinamen gespeichert. Wer mit einem geeigneten Grafikprogramm vertraut ist, kann so einigermaßen schnell und ohne langes Nachdenken eine Bilderserie erzeugen (am besten wird die Bildgröße genau passend zur Größe des Panels galgen in Zeile 105 von Klasse 1 gewählt!). Die Bilder könnten etwa wie folgt aussehen, wobei auch ein leeres Bild gespeichert wurde und das letzte Bild andere Farben hat, um auf das Ende der Buchstaben-Abfrage hinzuweisen.

Bildschirmfoto: Windows-Explorer zeigt Serie von 11 Galgen-Bildern



Weiterhin ist es vorteilhaft, die Bilder fortlaufend zu nummerieren, statt Dateinamen wie "eins.gif", "zwei.gif",.. oder .. "mit_linkem_Bein.gif", "mit_rechtem_Bein.gif",.. zu verwenden. Dann kann nämlich im Programmtext auf eine Mehrfachauswahl verzichtet werden. Weil die Bilder schon vollständig sind, könnten die Fälle in einer switch..case-Anweisung sowohl aufsteigend wie absteigend notiert werden, müssten aber immer mit break abgeschlossen werden. Aber mit Nummer kann das Programm den richtigen Dateinamen mit immer der gleichen, einen Zeile 41 erzeugen:




001 import java.awt.*;
002 import javax.swing.*;
003 import javax.swing.event.*;
004 import javax.imageio.*;
005 
006 public class P9_A3b_Panel extends JPanel   // erweitert/ändert normales Java-JPanel 
007 // spezielles Grafik-Panel für die Galgenmann-Oberfläche zur Anzeige des Galgenmanns
008 // aus vorgefertigten Bildern -- 17.5.2018  r-krell.de
009 {
010   int zahl = 0;   // Anzahl der bereits gezeichneten Striche
011   
012   public void loesche()   // löscht das Panel, in dem alles weiß überstrichen wird
013   {
014     zahl = 0;
015     setBackground(Color.WHITE);
016     repaint();   // Systembefehl repaint ruft Methode paint zum Zeichnen auf
017   }
018   
019   public void nächsterStrich()
020   {
021     if (zahl<10)
022     {
023       zahl++;    // erhöht Anzahl der gezeichneten bzw. zu zeichnenden Striche
024       repaint(); // und ruft indirekt nachfolgende Zeichenmethode paint auf
025     }
026   }
027   
028   public int zahl()   // gibt auf Wunsch die Zahl der gezeichneten Striche aus
029   {
030     return (zahl);
031   }
032   
033 //  @Override    // kann verdeutlichen, dass eine JPanel-Methode überschrieben wird
034   public void paint (Graphics g) // erweitert Standard-Methode um Galgenmann-Bildanzeige
035   { 
036     if (zahl >=0 && zahl <= 10)
037     {
038       Image bild = null;    // kein Bild, falls fertiges Bild nicht gefunden wird 
039       super.paintComponent(g);
040       Graphics2D g2d = (Graphics2D) g;
041       String dateiname = "bilder/g_"+zahl+".gif";  // mit Name des Unterverzeichnis ..
042                                            // .. unter dem Ordner des Programmtextes
043       try
044       {
045         bild = ImageIO.read(getClass().getResource(dateiname));
046       }                                    // Befehl zum Holen des Bildes
047       catch (Exception ex)
048       {
049         System.out.println("** Grafikprobleme ["+dateiname+"]: "+ex);   // falls Bilddatei nicht .. 
050       }                                    // .. gefunden/geholt werden konnte
051       
052       if ( bild != null )
053       {
054         g2d.drawImage(bild, 00this );  // zeigt gefundenes Bild ab linker oberer Ecke (0|0) ..
055       }                                    // .. in diesem Panel ('this') an
056     }
057   }  
058 }
 

Die Zeilen 1 bis 34 sind natürlich genau wie bei der Variante a). Auch gilt alles dort gesagte zum Einsatz von repaint. Der Quelltext der Methode paint ist ohne switch..case kürzer; allerdings sieht man hier eine try..catch-Anweisung. Weil Java Dateioperationen wie das Holen von Bildern für besonders fehleranfällig hält - vielleicht wurde der Ordner mit den Bildern versehentlich gelöscht, wurde der USB-Stick mit den Dateien gar nicht eingesteckt, sind die Dateinamen doch anders oder wurden die Bilder in einem von Java nicht unterstützten Format abgespeichert -- darf die Zeile 45 nicht einfach so hin hingeschrieben werden, sondern kann nur versuchsweise (im try-Block) ausprobiert werden. Wenn das Bild nicht geholt werden kann, springt das Programm in den catch-Block, hier zu einer Fehlermeldung. Ein erfolgreich geholtes Bild wird schließlich in Zeile 54 angezeigt (wobei drawImage aus Zeile 54 eigentlich auch direkt nach Zeile 45 im try-Block stehen könnte, was die if-Abfrage in Zeile 52 überflüssig macht).

Jetzt muss in der Klasse 1 nur noch die Zeile 37 (und der Titel in Zeile 51) auf _A3b_ abgeändert werden, und das Galgenmann-Spiel kann mit dieser Grafik gespielt werden.




zum Seitenanfang / zum Seitenende

 UML-Diagramm zum Zusammenspiel der Klassen



Das nachfolgende, mit dem Javaeditor erstellte UML-Diagramm verdeutlicht am Beispiel von P9_A3a nochmal den Zusammenhang zwischen den drei gerade vollständig vorgestellten Klassen. Der Javaeditor zeigt auch die Attribute an (hier f und galgen), die der Verknüpfung der Klassen durch Kompositionen dienen, und hebt sie sogar fett hervor. Normalerweise werden aber die von Java bzw. Swing mitgelieferten Oberklassen JFrame und JPanel nicht dargestellt; sie wurden hier extra angedeutet.

Bildschirmansicht: Javeeditor mit UML-Diagramm von P9_A3a


zum Seitenanfang / zum Seitenende

 Ausprobieren von P9_A3a und P9_A3b



Zum Ausprobieren biete ich die beiden lauffähigen, spielbaren Galgenmann-Programme an, die hier entwickelt wurden. Das Aussehen von P3_A3b kennen Sie ja bereits vom Anfang dieser Seite; die andere Version sieht fast genau so aus. Es lässt sich am fertigen Programm nicht ohne Weiteres erkennen, wie die Grafik erzeugt wird.

Wenn Ihr Browser das zulässt (und Sie die Java-JRE 1.8 oder höher installiert haben), können Sie jedes der beiden Programme direkt hier aus dem Browser heraus starten ("Webstart"). Gibt es Probleme oder wollen Sie Galgenmännchen auch unabhängig vom Besuch dieser Webseite spielen, empfiehlt sich das Herunterladen der jar-Datei(en). Der Download sollte immer gelingen. Bei installierter JRE können Sie die herunter geladene (=gespeicherte) jar-Datei später auf Ihrem Computer per Doppelklick ausführen und so das Galgenmännchen spielen.

[Die kostenlose, aktuelle JRE (Java Runtime Environment; Java Ausführungs-Umgebung) erhalten Sie immer von Oracle (wo im Juli 2018 noch die 32-Bit-Version 1.8 bzw. "Version 8 Update 171" vom April 2018 angeboten wurde), oder z.B. von Computerbild (Version 9.0 = 1.9) oder von heise (verschiedene Versionen; im Juni 2018 konnte auch die eher unnötige 64-Bit-Version von Java 1.10 bzw. "JRE 10.0.1" bezogen werden].



Webstart
(jnlp-Datei nicht speichern/herunterladen, sondern mit dem Launcher öffnen und Risiko akzeptieren zum Ausführen)
Download
Wort-Raten mit Java-Grafik Webstart   P9_A3a_Galgenmann.jnlp P9_A3a_Galgenmann(r-krell.de).jar   (12kB)
Wort-Raten mit DiaShow Webstart   P9_A3b_Galgenmann.jnlp P9_A3b_Galgenmann(r-krell.de).jar   (35kB)


zum Seitenanfang / zum Seitenende


zurück zur Informatik-Hauptseite

zurück zur vor-vorigen Seite (Programme P1 bis P4)  Seite b1)_2017
zurück zur vorigen Seite (Programme P5 bis P9)  Seite b2)_2017
weiter zur nächsten Seite (Programme P10 bis P12)  Seite b4)_2018


zum Anfang dieser Seite
Willkommen/Übersicht  -  Was ist neu?  -  Software  -  Mathematik  -  Physik  -  Informatik  -   Schule: Lessing-Gymnasium und -Berufskolleg  -  Fotovoltaik  -  & mehr  -  Kontakt: e-Mail,  News-Abo, Gästebuch, Impressum  -  Grußkarten, site map, Download und Suche

Diese Seite ist Teil des Webangebots http://www.r-krell.de. Sie können diese Seite per e-Mail weiter empfehlen (tell a friend).