www.r-krell.de |
Webangebot für Schule und Unterricht, Software, Fotovoltaik und mehr |
Willkommen/Übersicht > Informatik > Java-Seite b2) (2017)
Teil b2)_2017
Fortsetzung: Einführung in das Programmieren mit Swing-Oberflächen
Eine vollständige Übersicht aller meiner Seiten "Informatik mit Java" gibt's auf der Informatik-Hauptseite! Unmittelbarer Vorgänger ist die Seite b1)_2017.
Auf dieser Seite b2)_2017 finden Sie:
Und nach jedem Programm gibt's vertiefende bzw. weiterführende Übungsaufgaben!
zum Seitenanfang / zum Seitenende
Nach den ersten Programmierversuchen auf der Seite b1) soll jetzt etwas mehr darauf geachtet werden, nicht nur lauffähige, sondern auch 'schöne' Programme zu schreiben, die übersichtlich, dadurch besser verständlich und damit weniger fehleranfällig und auch später noch wartbar sind. Folgende Prinzipien helfen dabei:
zum Seitenanfang / zum Seitenende
Auf der Seite b1) gab es am Ende eine Arbeitsblatt mit einer Zusammenfassung aller Kontrollstrukturen. Da die dort erwähnten Wiederholungsschleifen noch nicht in einem Programm vorkamen, soll das hier passieren. Gesucht ist ein Programm, das den Benutzer einmal, ein paar mal oder oft grüßt.
Die unten als erste angegeben main-Methode wird automatisch vom Javaeditor erzeugt. Sie startet das ganze Programm und wird nicht verändert.
Die in allen Fällen gleiche Erzeugung der Anrede wird in eine eigene Methode ausgelagert, die so nur einmal geschrieben werden braucht, aber von allen drei Knöpfen benutzt/aufgerufen werden kann. Eigentlich sind nur zwei Geschlechter vorgesehen. Es werden aber außer den verlangten Kleinbuchstaben m und w auch Großbuchstaben sowie f bzw. F für die weibliche Form der Anrede anerkannt; alle übrigen Angaben werden als sächlich interpretiert. Da somit drei Fälle unterschieden werden, wird eine switch-case-Anweisungen für die mehrseitige Verzweigung verwendet. Bei den leeren Fällen geht - wegen fehlendem break - das Programm automatisch in die nächstecase-Zeile (auch wenn die neue case-Bedingung nicht zutrifft), sodass hier mehrere verschiedene Angaben mit der gleichen Anweisung behandelt werden können. Alle nicht explizit genannten Geschlechtseingaben landen im default-Fall. Dort würde sogar auch die Eingabe "männlich" landen, weil kein passender Fall vorgesehen ist.
Die Methode anrede hat als Rückgabetyp einen String: an die aufrufende Stelle wird der mit return zurückgegebene Wert der lokalen Variablen ansprache übergeben. Dadurch kann der Aufruf anrede() in den ActionPerformed-Methoden der drei Knöpfe wie eine Variable verwendet werden. Da die Methode anrede nur innerhalb dieser Klasse/Datei verwendet wird, braucht sie nicht öffentlich (public) zu sein; ihre Sichtbarkeit wurde auf private eingeschränkt.
Im Programm ist übrigens - wie oben an den abgebildeten Beispielen zu sehen - "einmal" immer als einmal, "mehrfach" als dreimal und "öfter" als genau 11-mal angelegt. Während man drei Gruß-Anweisungen für den "mehrfach"-Knopf noch dreimal von Hand in den Programmtext schreiben kann, sind elf Kopien des gleichen Befehls für den "öfter"-Knopf unschön. Hier ist eine Wiederholstruktur eleganter und übersichtlicher. Und weil man schon beim Eintritt in die Schleife weiß, wie oft gegrüßt werden soll, bietet sich die Zähl- bzw. for-Schleife an.
// Anfang Methoden public static void main(String[] args) { new P5_MehrfachGruss(); } // end of main private String anrede() // erstellt Anrede aus Textfeld-Eingaben { String name = jTfName.getText(); String geschlecht = jTfGeschlecht.getText(); String ansprache; switch (geschlecht) { case "M" : ; case "m" : ansprache = ", lieber "+name; break; case "F" : ; case "f" : ; case "W" : ; case "w" : ansprache = ", liebe " +name; break; default : ansprache = ", liebes "+name; break; } // end of switch return (ansprache); } public void jBtEinmal_ActionPerformed(ActionEvent evt) { jTaAusgabe.setText("Viele Grüße"+anrede()+"\n"); } // end of jBtEinmal_ActionPerformed public void jBtMehrfach_ActionPerformed(ActionEvent evt) { jTaAusgabe.setText("Viele Grüße"+anrede()+"\n"); jTaAusgabe.append ("Viele Grüße"+anrede()+"\n"); jTaAusgabe.append ("Viele Grüße"+anrede()+"\n"); } // end of jBtMehrfach_ActionPerformed public void jBtOefter_ActionPerformed(ActionEvent evt) { jTaAusgabe.setText(""); for (int i=0; i<11; i=i+1) { jTaAusgabe.append ("Viele Grüße"+anrede()+"\n"); } // end of for } // end of jBtOefter_ActionPerformed // Ende Methoden |
Das Programm lässt sich durch eine zweite private Methode übrigens noch übersichtlicher gestalten, indem auch ein Gruß und drei Grüße durch ein- bzw. dreimalige Wiederholung der selben Anweisung erzeugt werden, die auch bei der 11-maligen Wiederholung verwendet wird. Dann muss der Methode grüßen beim Aufruf in Klammern die benötigte Zahl der Grüße bzw. Wiederholungen mitgegeben werden, die im Parameter anzahl (vom Typ Ganzzahl bzw. int) gespeichert und dann innerhalb der Methode aus dieser Variablen abgelesen wird:
private String anrede() // erstellt Anrede aus Textfeld-Eingaben { String name = jTfName.getText(); String geschlecht = jTfGeschlecht.getText(); String ansprache; switch (geschlecht) { case "M" : ; case "m" : ansprache = ", lieber "+name; break; case "F" : ; case "f" : ; case "W" : ; case "w" : ansprache = ", liebe " +name; break; default : ansprache = ", liebes "+name; break; } // end of switch return (ansprache); } private void grüßen (int anzahl) // schreibt anzahl Grüße ins Ausgabe-Area { jTaAusgabe.setText(""); for (int i=0; i<anzahl; i=i+1) { jTaAusgabe.append ("Viele Grüße"+anrede()+"\n"); } // end of for } public void jBtEinmal_ActionPerformed(ActionEvent evt) { grüßen (1); } // end of jBtEinmal_ActionPerformed public void jBtMehrfach_ActionPerformed(ActionEvent evt) { grüßen (3); } // end of jBtMehrfach_ActionPerformed public void jBtOefter_ActionPerformed(ActionEvent evt) { grüßen(11); } // end of jBtOefter_ActionPerformed |
Entsprechend dem Grundsatz, Methoden übersichtlich zu halten und deshalb nicht mehrere Aufgaben auf einmal machen zu lassen, bleibt es bei zwei Methoden. Es wird nicht versucht, beide Methoden anrede und grüßen zu einer zusammen zu fassen, obwohl das möglich (aber eben nicht sinnvoll) wäre.
zum Seitenanfang / zum Seitenende
Park-, Fahrschein- oder Verkaufsautomaten geben in der Regel Wechselgeld zurück, wenn man mehr Geld einwirft, als man bezahlen muss. Hier soll Hartgeld in möglichst großen Stücken zurück gegeben werden, wobei der Benutzer sowohl den zu zahlenden Betrag als auch den eingeschobenen bzw. eingeworfenen Betrag selbst festlegen kann. Bei einem echten Automat würde statt der Nennung einer zurückgegebenen Münze diese tatsächlich in den Ausgabeschacht fallen: Jede Zeile würde eine entsprechende Münze freigeben. Deshalb wird jede Münze hier auch einzeln genannt und nicht pauschal etwa "3 x 5 € + 1 x 2 €" angezeigt.
Der [Neu]-Knopf soll alle Eingaben löschen, d.h. alle Textfelder und das Ausgabe-Area leeren. Bei [Abbruch] soll der gegebene Betrag unverändert zurück gegeben werden, ohne dass eine Zahlung erfolgt. Insofern kann der zu zahlende Betrag noch im entsprechenden Textfeld stehen bleiben, während der gegebene Betrag als Rückzahlung im Ausgabe-Area auftaucht (und aus 'gegeben' gelöscht werden muss, damit er nicht doch noch zur Zahlung verwendet werden kann).
Das Verhalten beim Druck auf die Schaltfläche [Zahlung & Wechseklgeld] wird durch das abgebildete Beispiel deutlich. Da aus zwei verschiedenen Textfeldern jeweils Kommazahlen (die in Java double heißen) ausgelesen werden müssen, wird dafür einmal eine eigene Methode liesZahl erstellt, die mehrfach aufgerufen werden kann - wobei jeweils das auszulesende Textfeld in den Klammern als Parameter übergeben wird. Wie in Aufgabe 2 nach dem Programm P5 schon erwähnt, ist eine Umwandlung des Textes in eine Zahl nötig, was hier mit Double.parseDouble(.. gelingt. Mit der zusätzlichen umschließenden try-catch-Anweisung wird verhindert, dass das Programm bei einer Fehleingabe abbricht. Vielmehr wird ein beim Umwandlungs-Versuch entstehender Fehler aufgefangen, indem dann die Meldung aus dem catch-Block erfolgt. So funktioniert der Automat weiter und dem Benutzer wird geholfen, ihn richtig zu bedienen. Hier die entsprechende Methode:
private double liesZahl (JTextField tf) { double zahl = 0; try { zahl = Double.parseDouble(tf.getText()); } catch (NumberFormatException e) { jTaAusgabe.append("** nur Ziffern und Dezimalpunkt erlaubt **\n"); } if ((zahl < 0.10) || (zahl > 50)) { zahl = 0; jTaAusgabe.append("\n** nur Werte von 0.10 bis 50.00 erlaubt **\n"); } // end of if return (zahl); } |
Wichtig ist jetzt natürlich die Ausgabe des Rückgelds. Man macht sich schnell klar, dass von einer Sorte auch mehrere Münzen zurück gegeben werden können. Insofern scheint eine Wiederholstruktur für jede Münzsorte angebracht. Die Schleife kann selbst dann verwendet werden, wenn von bestimmten Münzen (etwa den 1-€-Münzen) höchstens eine ausgegeben wird, weil sonst vorher eine größere Münze ausgegeben worden wäre. Weil sich der noch zurück zu gebende Betrag während der Ausgabe ändert (mit jeder ausgespuckten Münze verringert sich ja die restliche Rückgabe), empfiehlt sich eine while-Schleife mit vorgeschalteter Kontrolle, zumal von manchen Münzen eventuell auch gar keine zurück gegeben wird.
Bitte versuchen Sie selbst, den Automaten entsprechend zu programmieren, bevor Sie meine nachfolgende Lösung lesen - wobei ich den [Zahlung & Wechseklgeld]-Knopf programmintern jBtOkay genannt habe. Ein \n im Ausgabetext wechselt in eine neue Zeile.
public void jBtOkay_ActionPerformed(ActionEvent evt) { double zuzahlen = liesZahl (jTfZuZahlen); double gegeben = liesZahl (jTfGegeben); if (zuzahlen*gegeben == 0) { jTaAusgabe.append ("Bitte geben Sie gültige Beträge ein\nund versuchen Sie es erneut!\n\n"); } else if (gegeben < zuzahlen) { jTaAusgabe.setText ("Sie müssen mindestens "+zuzahlen+" € geben!\n" + "Bitte erhöhen Sie den Betrag und versuchen es erneut!\n\n"); } else { jTaAusgabe.setText ("Zahlung erfolgt. Bitte entnehmen Sie Ihr Wechselgeld:\n"); double zurück = gegeben - zuzahlen + 0.0001; while (zurück >= 5) { jTaAusgabe.append (" 5-€-Münze\n"); zurück = zurück - 5; } // end of while while (zurück >= 2) { jTaAusgabe.append (" 2-€-Münze\n"); zurück = zurück - 2; } // end of while while (zurück >= 1) { jTaAusgabe.append (" 1-€-Münze\n"); zurück = zurück - 1; } // end of while while (zurück >= 0.5) { jTaAusgabe.append (" 50-Cent-Stück\n"); zurück = zurück - 0.5; } // end of while while (zurück >= 0.2) { jTaAusgabe.append (" 20-Cent-Stück\n"); zurück = zurück - 0.2; } // end of while while (zurück >= 0.1) { jTaAusgabe.append (" 10-Cent-Stück\n"); zurück = zurück - 0.1; } // end of while if (zurück > 0.009) { jTaAusgabe.append ("Keine Rückgabe von Beträgen unter 10 Cent.\n\n"); } // end of if } } // end of jBtOkay_ActionPerformed |
In der Lösung fällt auf, dass anfangs einmal zum Rückgabebetrag zurück ein Hundertsel Cent addiert wird, der gar nicht zurück gegeben werden kann: double zurück = gegeben - zuzahlen + 0.0001;. Verzichten Sie zunächst auf diesen Zusatz und probieren Sie das Programm mehrfach aus. Dann sollte Ihnen bald auffallen, dass bei manchen Kombinationen von zu zahlenden und gegebenen Beträgen ein 10-Cent-Stück zu wenig ausgegeben wird. Sie merken, dass Digitalrechner (also Computer oder Taschenrechner) mit Kommazahlen oft nur ungenau umgehen können. Bei der Verwandlung mancher endlicher Dezimalbrüche (wie etwa 0.10) in eine Dualzahl müsste eigentlich ein unendlich langer periodischer Dualbruch entstehen, der technisch irgendwo abgeschnitten werden muss. D.h. die im Computer gespeicherte und verwendete Dualzahl entspricht nicht genau dem eingegebenen Dezimalwert. In der Folge kann zurück auch gelegentlich einen Wert von dezimal 0.1000000002 oder auch 0.09999999 annehmen. Der erste Fall schadet hier nicht. Aber während wir Menschen wissen, dass eine Neunerperiode gleich einer glatten Eins in der nächsthöheren Stelle ist, hält der Computer im zweiten Fall zurück >= 0.1 für falsch und gibt kein 10-Cent-Stück mehr zurück. Der Zusatz sorgt dafür, dass immer ein bisschen mehr Geld da ist und die Rückgabe deshalb auch bei ungenauer Rechnung funktioniert. In kaufmännischen Programmen müssen besondere Vorkehrungen getroffen werden, dass kein Geld verloren geht (oder aus unerklärlichen Quellen dazu kommt)! Generell sollte man immer im Hinterkopf behalten, dass die Verwendung von Kommazahlen unvermeidlich Ungenauigkeiten und Fehler bedingt. Insbesondere sollte man nie auf die exakte Gleichheit zweier Kommazahlen hoffen; statt if (x==0.10) muss immer einen Bereich abgefragt werden wie etwa if (x>0.09999 && x < 0.100001).... Im vorgestellten Text steht in der letzten if-Abfrage dann auch if (zurück > 0.009) und nicht etwa if (zurück > 0), weil der Hinweis nur erscheinen soll, wenn vermutlich noch mindestens 1 Cent zurück zu geben wäre und nicht nur eine kleine Ungenauigkeit oder der Zusatz von 0.0001 € übrig ist.
zum Seitenanfang / zum Seitenende
Die Schülerinnen und Schüler haben das Quiz mit viel Spaß programmiert. Die Oberfläche wurde mit dem im Javaeditor eingebauten GUI-Builder als JFrame rasch erstellt. Dann wurde viel Ehrgeiz auf das Erstellen eines eigenen Fragekatalogs verwendet. Das Verhalten des Programms wird durch die gezeigten Beispiel-Bildschirme deutlich:
Es ist klar, dass zu einer jeden Quizfrage immer sechs Element gehören: die eigentliche Frage, die vier Antwort-Vorgaben A bis D und ein Buchstabe, der sagt, welche der vier Vorgaben richtig ist. Bei Fernseh-Shows wie "Wer wird Millionär?" käme als siebtes Element eine Preisangabe hinzu, weil leichte Fragen eben nur als 50-€- oder 100-€-Frage verwendet werden sollen, während schwerere Fragen für höhere Preiskategorien taugen. Um dem Computer dieses Schema bei zu bringen, muss man eine eigene Klasse sozusagen als Musterbauplan für eine (und damit für jede) Frage erstellen. Dies geschieht in einer eigenen Textdatei (Im Javaeditor durch Klick aufs weiße Blatt oben links oder durch Datei > Neu > Java erzeugt).
In den Zeilen 5 bis 10 wird dem Computer mitgeteilt, welche Bestandteile zu einer Frage (künftiger Typ P7_EineFrage) gehören. Für richtig reicht eigentlich ein Datenfeld vom Typ char, das genau ein einziges Zeichen aufnehmen kann. Da aber von der Eingabe ein String abgelesen wird, wurde hier auch der String-Typ verwendet. Eine neue, noch leere Frage kann von anderer Stelle ab sofort immer mitP7_EineFrage frage1 = new P7_EineFrage(); erzeugt werden. Allerdings müssten dann noch die 6 Bestandteile einzeln gefüllt werden, etwa mit frage1.frage = "Wie viele Monde hat Jupiter?"; frage1.antwortA = "einen"; frage1.antwortB="sieben"; ... . Ein Konstruktor (= Methode mit gleichem Namen wie die Klasse und ohne Angabe des Rückgabetyps) erleichtert das Füllen, weil in der runden Klammer alle 6 Inhalte gemeinsam übergeben werden können (Zeile 12) und dann vom Programm den Attributen zugewiesen werden (Zeilen 14 bis 19). Jetzt kann bequem mit einem Befehl ein neues Frageobjekt frage1 nach diesem Bauplan erzeugt und gefüllt werden: P7_EineFrage frage1 = new P7_EineFrage("Wie viele Monde hat Jupiter?", "einen", "sieben", .. );
Für das Quiz ist es unangenehm, wenn die einzelnen Fragen alle verschiedene Namen tragen - wie etwa frage1, frage2, .. , schwereFrage, .. usw. Man weiß nach einer Frage nicht sicher, wie die nächste Frage heißt, die nun geholt und angezeigt werden soll. Deshalb bietet sich eine besondere Datenstruktur an: die Reihung (englisch als array bezeichnet), äußerlich erkennbar an eckigen Klammern. Hier können viele gleichartige Objekte unter einem gemeinsamen Namen zusammengefasst werden. Es muss allerdings anfangs angegeben werden, wie viele Objekte maximal in der Reihung untergerbacht werden sollen. Soll z.B. die Reihung fragenkatalog für bis zu sechs Fragen vom oben beschriebenen Typ P7_EineFrage ausgelegt werden, so wird definiert P7_EineFrage[ ] fragenkatalog = new P7_EineFrage[6]; . Danach stehen 6 gleichartige Speicherplätze an den Stellen fragenkatalog[0], fragenkatalog[1] bis fragenkatalog[5] zur Verfügung, die gefüllt und anschließend verwendet werden können. Auf den ersten Blick scheint fragenkatalog[0], fragenkatalog[1],.. keinen Vorteil gegenüber der sogar kürzen Schreibweise frage1, frage2,.. zu bieten. Aber in die eckigen Klammern der Reihung kann auch eine Variable eingesetzt werden, die später Werte zwischen 0 und 5 annimmt. Dadurch können Reihungen gut mit for-Schleifen bearbeitet werden.
Aber zuerst muss die Reihung erzeugt und gefüllt werden. Auch das geschieht am besten in einer eigenen Datei als neue Klasse (hier: P7_AlleFragen), wobei die Reihung jetzt nicht fragenkatalog, sondern schlicht alle heißt. Um später die Zahl der Fragen leichter erhöhen (und abfragen) zu können, wird eine Variable anzahl verwendet. In Zeile 6 sieht man dann schon die Verwendung dieser Variablen in der eckigen Klammer; Zeile 22 zeigt ein weiteres Beispiel: Wird irgendwann mal anzahl=5000 gesetzt, wird automatisch der Schlusstext in alle[4999] gespeichert (mehr später in P7_Quiz):
Damit sind die Vorarbeiten erledigt; jetzt muss dafür gesorgt werden, dass die Fragen auch in die Oberfläche kommen und dort ordentlich angezeigt werden. Dazu muss die Klasse P7_Quiz mit den oben gezeigten Labeln, Textfeldern und Knöpfen zusammen geklickt und gezogen sein. Es reicht allerdings nicht, nur Text in die ActionPerformed-Methoden der Knöpfe zu schreiben - es müssen auch einige globale Attribute definiert und der Oberfläche gesagt werden, dass sie ein/das Objekt vom Typ P7_AlleFragen verwenden soll, das ich einfach q (für Quiz) genannt habe. Jedenfalls werden die eingerahmten Zeilen von Hand zum automatisch erzeugten Text hinzu geschrieben. Die Attribute lfdNr, geantwortet und pkt dienen der Verwaltung der Fragen und der Statistik; in Zeile 20 wird in das Attribut max die Fragenzahl aus dem Attribut anzahl vom P7_AlleFragen-Objekt q übernommen. Wird später mal in P7_AlleFragen die Fragenzahl erhöht, brauchen in P7_Quiz keinerlei Änderungen vorgenommen werden: Das Quiz bekommt hier mit, wie viele Fragen es dort gibt und stellt sie alle).
Das Ausgeben der verschiedenen Fragebestandteile in den richtigen Textfeldern der Oberfläche geschieht im Prinzip immer gleich (höchstens mit anderen Inhalten, nämlich denen der jeweils nächsten Frage) zu Beginn des Programms, beim Betätigen der [ohne Antwort zu nächsten Frage]-Schaltfläche oder nach dem Druck auf [weiter] (während nach dem Einloggen der Lösung mit [angeklickte Antwort bestätigen] noch keine neue Frage kommen soll, damit man sehen kann, ob die Antwort richtig war). Weil also an drei Stellen gleiche Befehle benötigt werden, werden sie sinnvollerweise in einer eigenen (privaten) Methode zeigeNächsteFrage zusammengefasst. Die Methode kann dann von den verschiedenen Orten aus aufgerufen werden. Der farbig hinterlegte Text zeigt die drei Aufrufe (rote Schrift) und die Knopf-Methoden von P7_Quiz:
... // Ende Komponenten setVisible(true); zeigeNächsteFrage(); // damit bei Progammstart eine Frage erscheint } // end of public P7_Quiz // Anfang Methoden ... public void jBtUeberspringen_ActionPerformed(ActionEvent evt) { zeigeNächsteFrage (); } // end of jBtUeberspringen_ActionPerformed public void jBtAntworten_ActionPerformed(ActionEvent evt) { String antwort = buttonGroup_a_bis_d_getSelectedRadioButtonLabel(); if (antwort.equals("-")) { jTfErgebnis.setText("Erst A, B, C oder D wählen!"); } else { geantwortet = geantwortet + 1; // es wurde hiermit eine Antwort mehr gegeben if (antwort.equalsIgnoreCase(q.alle[lfdNr].richtig)) { jTfErgebnis.setText(antwort+" ist richtig!"); pkt = pkt + 1; // für die richtige Antwort gibt es einen Punkt dazu } else { jTfErgebnis.setText(antwort+" ist falsch ("+q.alle[lfdNr].richtig+" ist richtig)"); } // keine Punkte für falsche Antwort! jTfPunkte.setText (""+pkt+" von "+geantwortet); jBtWeiter.setEnabled(true); // [weiter]-Knopf als einzigen aktivieren jBtUeberspringen.setEnabled(false); // [ohne Antwort..]-Knopf deaktivieren jBtAntworten.setEnabled(false); // Antwort-Knopf selbst deaktivieren, damit .. } // .. Antwort nicht wiederholt werden kann .. } // end of jBtAntworten_ActionPerformed // .. und nochmal Punkte bringt. public void jBtWeiter_ActionPerformed(ActionEvent evt) { zeigeNächsteFrage (); } // end of jBtWeiter_ActionPerformed |
Eigentlich sollte die Leserin/der Leser selbst die Methode zeigeNächsteFrage erstellen können. Nur zur anschließenden Kontrolle gebe ich sie nachfolgend an:
private void zeigeNächsteFrage () { if (lfdNr < max) { lfdNr = lfdNr + 1; // Nummer der nächsten Frage jTfFrage.setText(q.alle[lfdNr].frage); jTf_a.setText(q.alle[lfdNr].antwortA); jTf_b.setText(q.alle[lfdNr].antwortB); jTf_c.setText(q.alle[lfdNr].antwortC); jTf_d.setText(q.alle[lfdNr].antwortD); jRB_nichts.setSelected(true); jTfErgebnis.setText(""); jTfPunkte.setText (""+pkt+" von "+geantwortet); jBtWeiter.setEnabled(false); // [weiter]-Knopf deaktiviert if (lfdNr < max-1) { // Falls es noch Fragen gibt, .. jBtUeberspringen.setEnabled(true); // .. werden diese beiden Knöpfe .. jBtAntworten.setEnabled(true); // .. wieder aktiviert } // end of if } // end of if } |
Das Aktivieren und Deaktivieren der verschiedenen Knöpfe ist in den oben gezeigten Beispielen vom Programmablauf gut zu erkennen.
In der Methode zeigeNächsteFrage ist vielleicht die Zeile jRB_nichts.setSelected(true); aufgefallen - und bei den Beispielbildern, dass nach der Anzeige einer neuen Frage keine der vier Antworten angeklickt ist, obwohl doch bei den Radiobuttons einer JButtongroup normalerweise genau ein Radiobutton aktiviert sein sollte. Ich habe einfach in der Oberfläche noch einen fünften Radiobutton (mit dem Namen jRB_nichts) an eine freie Stelle auf der Oberfläche platziert, ihn im Objektinspektor in die gleiche JButtongroup buttonGroup_a_bis_d wie alle übrigen Radiobuttons eingebunden und dort durch Visible = false als unsichtbar markiert. Man sieht den fünften Radiobutton also während der Programmausführung nicht, aber wenn er mit jRB_nichts.setSelected(true) aktiviert wird, schalten sich automatisch alle sichtbaren Knöpfe aus!
zum Seitenanfang / zum Seitenende
Die Idee zu diesem Anagramm-Spiel hat eine Schülerin beigesteuert: Die Buchstaben eines Wortes werden wild vermischt und der Mensch vorm Bildschirm versucht, trotzdem das Wort zu raten:
Damit auch wiederholtes Spielen nicht langweilig wird, soll das Programm intern über eine größere Wortliste verfügen. Die Wörter sollen aber nicht nach jedem Start immer in der gleichen Reihenfolge kommen (wie das bei den Fragen in P7 noch der Fall war), sondern in zufälliger Folge erscheinen. Und das an Stelle eines gewählten Wortes angezeigte Durcheinander soll auch nicht immer gleich für dieses Wort sein, sondern möglichst jedesmal anders gemischt werden. Insofern spielt der Zufall hier eine doppelte Rolle.
Nachdem beim Quiz P7 schon weitere Klassen in eigenen Dateien verwendet wurden, sollen auch hier die Oberfläche und der Programmtext der Wortliste mit ihren zufälligen Reihenfolgen und Mischungen in zwei getrennten Dateien stehen. Das macht das Programm übersichtlicher (mehrere kleine Einheiten sind überschaubarer als ein großes Programm) und erlaubt, später jeden Teil einzeln verbessern zu können, ohne an der anderen Datei etwas zu ändern. So könnte die Oberfläche modernisiert werden, ohne die Wörter und die Mischprogramme überhaupt betrachten zu müssen. Oder die Wortliste wird erweitert, das Mischverfahren verbessert usw., ohne dass man sich das Programm für die Oberfläche ansehen muss -- und ohne dass man bei der Änderung versehentlich die Oberfläche beschädigen kann.
Die einzelnen Wörter werden in einer Reihung (Array) untergebracht (vgl. Text bei P7). Hier kann die Reihung namens reihe sogar direkt durch Angabe (Aufzählen) aller Wörter in einem Schritt definiert, erzeugt und gefüllt werden. Die Programmiererin bzw. der Programmierer muss nicht mal die eingetippten Wörter zählen, sondern lässt sich einfach von Java die Länge der Reihung nennen (-> Variable max).
Dann werden zwei Methoden benötigt: zufallswort sucht ein beliebiges Wort aus der reihe heraus (und sperrt es für die spätere Verwendung, damit es bei einem erneuten Aufruf von zufallswort nicht nochmal erscheint), während vermischt die Buchstaben eines übergebenen Wortes zufällig neu anordnet und so ein Durcheinander erzeugt. Das Mischen der Buchstaben kann leider nicht dem von Kindern gerne ausgeführten Verfahren - jeden Buchstaben auf ein einzelnes Kärtchen schreiben, alle Kärtchen auf den Tisch legen und dann die Kärtchen durch 'Herumrühren' mit beiden Händen in Unordnung bringen - programmiert werden. Java kann nicht mehrere Sachen gleichzeitig machen. Statt dessen wird hier eine zufällige Nummer zufall gezogen und dann der Buchstabe von der entsprechenden Position aus dem Wort (bzw. seiner Kopie) heraus genommen und an eine neue Stelle gelegt. Das verbleibende Wort wird zusammen geschoben und wieder wird daraus ein Buchstabe von einer beliebigen Position ausgewählt und als nächster Buchstabe an das neu entstehende Wort angelegt. So wird das alte Wort immer kürzer und das neue Wort immer länger, bis es zum Schluss alle Buchstaben der ursprünglichen Worts in neuer, zufälliger Reihenfolge enthält.
Natürlich wären auch andere Verfahren möglich: Merkt man sich, welche Zufallsposition schon gezogen wurde, und wählt sie später nicht nochmal (bzw. zieht schnell eine weitere Zufallszahl, falls man doch mal eine bereits verwendete Zahl erhält), dann kann man sich das Zusammenschieben des alten Wortes sparen. Diese Idee wird im vorgestellten Programm nicht beim Mischen. sondern - um zwei etwas unterschiedliche Ansätze in einem Programm zu zeigen - bei der Auswahl eines Worts aus der Reihung verwendet: die reihe wird nicht kürzer, aber ein bereits verwendetes Wort muss gemerkt bzw. markiert werden, hier durch Überschreiben mit dem Leertext "". Natürlich hätte auch jeder andere Text - wie etwa "** schon verwendet **" zum Kenntlichmachen verwendeter Wörter genommen werden können (sofern der spezielle Text nicht auch als gültiger Begriff in der Wortliste auftaucht oder auftauchen darf).
Auch hier gäbe es noch weitere Möglichkeiten zum Markieren, z.B. eine zusätzliche Reihung vom Typ boolean[ ]. Der Fantasie des Programmierers bzw. der Programmiererin sind kaum Grenzen gesetzt. Allerdings sollte man bei zusätzlichen Datenstrukturen im Hinterkopf behalten, dass ein Synchronisierungsproblem entstehen kann. Im Sinne der Objektorientierung wäre deshalb eine eigene Klasse Ratebegriff besser, die als ein Attribut das zu ratende Wort und als zweites Attribut einen boolean-Wert schonVerwendet hat. Die Reihung müsste dann ähnlich wie in P7_AlleFragen vom Typ Ratebegriff[ ] sein.
Hier aber jetzt meine Version der Funktionsklasse-Klasse. In der Oberfläche muss dann eine Objekt vom Typ P8_AnagarammFkt erzeugt werden (vgl. P7_Quiz). Dass durch zufallswort die Wortliste reihe zerstört wird, stört nicht, wenn bei einem neuen Spiel das ganze Programm neu gestartet oder zumindest das Objekt vom Typ P8_AnagarammFkt neu erzeugt (und somit die reihe neu erstellt) wird. Theoretisch kann die do-while-Schleife in zufallswort beliebig oft durchlaufen werden; praktisch ergibt sich auch bei ungünstigem Zufall keine merkbare Verzögerung bei den letzten Wörtern.
public class P8_AnagrammFkt { String[] reihe = {"Test","Buchstabe","Haus","Mensch","Schülerin","Gefühl","Musik","Altbier"}; int max = reihe.length; // Gesamt-Anzahl der eingegebenen/vorhandenen Wörter/Begriffe int nr = 0; // Anzahl der schon verwendeten Wörter (anfangs 0) public String zufallswort() // gibt ein aus reihe zufällig ausgewähltes Wort zurück { String gefunden = ""; if (nr < max) { int pos; do { pos = (int)(Math.random() * max); // zufällige Ganzzahl zwischen 0 und max-1 } while (reihe[pos].equals("")); // falls Wort schon verwendet, erneut suchen gefunden = reihe[pos]; reihe[pos]= ""; // lässt gefundenes Wort in Reihung, löscht aber seine Buchstaben nr = nr + 1; } return (gefunden); // gibt das an zufälliger pos gefundene Wort zurück } public String vermischt (String wort) // vermischt die Buchstaben des übergebenen Worts .. { // .. und gibt so entstandenes neues Wort zurück String kopie = new String(wort); // übernimmt wort in Variable kopie String neu = ""; // sammelt Buchstaben aus kopie für Ergebnis-Anagramm while (kopie!=null && kopie.length()>0) { int zufall = (int)(Math.random() * kopie.length()); // Zufallszahl zw. 0 und Kopielänge-1 neu = neu + kopie.charAt(zufall); // zufälliges Zeichen aus kopie hinten an neu kopie = kopie.substring(0,zufall) + kopie.substring(zufall+1); // ^ trennt aus kopie den nach neu übertragenen Buchstaben heraus (wort bleibt!) } // end of while return (neu); } } |
zum Seitenanfang / zum Seitenende
Beim diesem Spiel ist wieder ein Wort zu erraten. Anders als in P8 kennt man die enthaltenen Buchstaben zunächst aber nicht. Vielmehr sieht man anfangs für jeden Buchstaben nur einen Unterstrich. Dann darf nach einem Zeichen des Alphabets gefragt werden. Ist der gefragte Buchstabe ein- oder mehrfach im Wort, wird er dort aufgedeckt. Es geht darum, das Wort möglichst schnell zu erraten, bevor allzu viele Buchstaben erfragt wurden. Das Spiel ist etwa aus der gerade reaktivierten Fernsehshow "Glücksrad" in Erinnerung; unter Schülerinnen und Schülern ist es aber eher als "Hangman" oder "Galgenmännchen" bekannt: Dann wird für jeden gefragten Buchstaben ein Element eines Strichmännchens am Galgen gezeichnet. Nach 10 Buchstaben ist die Zeichnung fertig. Wenn der/die Ratende das Wort dann immer noch nicht vollständig nennen kann, hat er/sie verloren.
Damit man leichter sieht, wie viele Buchstaben schon gefragt wurden, steht deren Anzahl vor der Aufzählung der gefragten Buchstaben im entsprechenden Textfeld.
Die Realisierung sollte wieder in einer eigenen, von der Oberfläche getrennten Klasse P9_GalgenmammFkt eine Wortliste enthalten sowie eine Methode, um aus der Liste ein zufälliges Wort als neues Wort auswählen zu können. Darüber hinaus müssen dort die bereits gefragten Buchstaben gesammelt werden können und es sollte eine Methode geben, die an Stelle des Wortes die Kombination aus Unterstrichen und vorhandenen gefragten Buchstaben zurück gibt. Wegen ähnlicher Aufgaben in P8 sollte nicht alles schwer sein; als Anregung für die letztgenannte Anzeigefunktion habe ich meinen Schülerinnen und Schüler einige 'passende' Aufgaben gestellt:
Übungsblatt P9_Aufgaben.pdf (46 kByte)
Nachtrag vom Februar 2019: Die Funktion des Programms P9 wurde hier etwas verkürzt dargestellt und entspricht damit nicht dem üblichen Spiel "Galgenmännchen". Auf der nächsten Seite ( Seite b3)_2018 ) wurden deswegen Änderungen vorgenommen!
zum Seitenanfang / zum Seitenende
zurück zur Informatik-Hauptseite
zurück zur vorigen Seite (Programme P1 bis P4) Seite b1)_2017
zur Vertiefungsseite (Lösung zu Aufgabe 3 von P9) Seite b3)_2018
weiter zur (über-)nächsten Seite (Programme P10 bis P12) Seite b4)_2018