Zahlenraten - Problem ?

      Zahlenraten - Problem ?

      Hallo liebe Mitglieder und hoffentlich Lazarus-Benutzer. Ich versuche mich seid Tagen schon an meinem (Zahlenraten-Programm), jedoch ohne Erfolg. Ich komme nicht wirklich weiter, weshalb ich hier mal um Hilfe bitten möchte.

      Schreibe ein Programm, bei dem der Computer zunächst eine Zahl zwischen 1 und 100 zufällig auswählt. Der Spieler soll nun versuchen, die gewählte Zahl zu erraten. Nach jedem Rateversuch soll der Computer eine Hilfestellung geben und entweder „zu groß“ oder „zu klein“ ausgeben. Das Spiel endet, wenn die Zahl erraten wurde. Zudem sollten die Versuche am Ende des Spiels angegeben werden.

      Dazu soll die Dateneingabe über eine InputQuery erfolgen.


      Mein bis jetziges Ergebnis ( auch nur durch Hilfe gegebener Informationen im Web);

      Delphi-Code

      1. unit uZahlenraten;
      2. {$mode objfpc}{$H+}
      3. interface
      4. uses
      5. Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
      6. ExtCtrls;
      7. type
      8. { TForm1 }
      9. TForm1 = class(TForm)
      10. Button1: TButton;
      11. Edit1: TEdit;
      12. Label1: TLabel;
      13. Label2: TLabel;
      14. Panel1: TPanel;
      15. procedure Button1Click(Sender: TObject);
      16. private
      17. { private declarations }
      18. public
      19. { public declarations }
      20. end;
      21. var
      22. Form1: TForm1;
      23. implementation
      24. {$R *.lfm}
      25. { TForm1 }
      26. procedure TForm1.Button1Click(Sender: TObject);
      27. var Zufall, Zahl, Vers: Integer; Eingabe: String;
      28. begin
      29. Randomize;
      30. Zufall := Random (100) + 1;
      31. Zahl := 0;
      32. Eingabe := InputBox('Rate mal', 'Zahl eintippen',IntToStr (Zahl));
      33. Zahl := StrToInt (Eingabe);
      34. If Zahl < Zufall Then
      35. Begin
      36. Label1.Caption := 'Deine Zahl ist zu klein!';
      37. Vers := Vers + 1;
      38. End;
      39. If Zahl > Zufall Then
      40. Begin
      41. Label1.Caption := 'Deine Zahl ist zu groß!';
      42. Vers := Vers + 1;
      43. End;
      44. If Zahl = Zufall Then
      45. Begin
      46. Label1.Caption := 'RICHTIG!!!';
      47. ShowMessage ('Du hast ' +IntToStr (Vers)+'mal geraten');
      48. end;
      49. end;
      50. end.
      51. end;
      52. end.



      Ich würde mich über Antworten und Tipps freuen.

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Zuropta“ ()

      Hallo und herzlich willkommen,

      die eigentliche Frage ist doch: Wo ist dein Problem? Dein Code funktioniert nämlich (wobei er schon noch einige Schönheitsfehler aufweist). Allerdings nicht ganz so, wie man denkt. Denn was er tut ist: Ich denke mir eine Zahl und du hast eine Chance. Rätst du falsch, denke ich mir eine neue Zahl und du hast wieder eine Chance. Und so weiter. Und jede neue Chance setzt auch die Anzahl der bisherigen Versuche zurück.
      Der Grund dafür ist ziemlich klar: Wann wird die Zufallszahl generiert? In der Zeile, in der der Variable "Zufall" ein Wert zugewiesen wird. Und das passiert jedes Mal, wenn auf den Button geklickt wird. Also wird die eigentliche Zufallszahl jedes Mal, wenn ich eine Zahl eingeben will, überschrieben. Die Lösung des Problems ist folglich, die Zufallszahl nur einmal zu generieren (etwa im FormCreate oder in einem extra Button dafür). Dann muss diese aber in einer Variable gespeichert werden, die über den Gültigkeitsbereich dieser einen Funktion hinaus erhalten bleibt. Also etwa im private-Abschnitt. Grund ist einfach, dass eine Variable, die du am Anfang einer Funktion deklarierst, auch nur solange erhalten bleibt, wie die Funktion ausgeführt wird. Danach ist sie wieder weg. Stattdessen soll die Variable aber so lange erhalten bleiben, wie das Fenster existiert, also handelt es sich um eine Eigenschaft der Form. Das gleiche gilt natürlich für die Versuche-Anzahl.
      Noch ein paar Anmerkungen:

      Delphi-Code

      1. Randomize; // Randomize initialisiert den Zufallszahlengenerator. Das sollte nur einmal geschehen,
      2. // und zwar im FormCreate der Hauptform oder im Initialization-Abschnitt
      3. Zahl := 0;
      4. Eingabe := InputBox('Rate mal', 'Zahl eintippen',IntToStr (Zahl));
      5. Zahl := StrToInt (Eingabe);
      6. // Dieses ganze Konstrukt ließe sich gewaltig vereinfachen:
      7. Zahl := StrToInt(InputBox('Rate mal', 'Zahl eintippen', '0'));
      8. // Du solltest es aber nicht so vereinfachen, weil du nicht InputBox, sondern InputQuery verwenden solltest:
      9. Eingabe := '0';
      10. If Not InputQuery('Rate mal', 'Zahl eintippen', Eingabe) Then
      11. Exit;
      12. // Schließlich willst du ja, wenn auf "Abbrechen" gedrückt wird, das nicht einfach ignorieren.
      13. Zahl := StrToIntDef(Eingabe, 0); // < eine mögliche Fehlerbehandlung
      14. end.
      15. end; // Das gehört hier nicht mehr hin. Lazarus meldet zwar keinen Fehler, weil alles nach "end." ignoriert wird, aber trozdem.
      16. end.
      Master of the EDH ;)
      Oh, jetzt verstehe ich auch so manche Sachen. Jetzt macht so einiges auch mehr Sinn :P. Ich werde mich mal ans korrigieren machen.

      Vielen dank, für die detailierte Beschreibung.

      //Edit ; Das scheint schwieriger als angenommen :P, ich versuche eine Variable im private Bereich einzufügen , vergeblich. Irgendwie scheint das alles komplizierter zu sein als ich es mir am Anfang gedacht habe. Vor allem weil uns unser Lehrer nicht viel darüber erzählt hat. Wird wohl noch eine Weile dauern , bis es klappt o.o

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Zuropta“ ()

      Mit dem private-Abschnitt ist Folgendes gemeint:

      Delphi-Code

      1. TForm1 = class(TForm)
      2. Button1: TButton;
      3. Edit1: TEdit;
      4. Label1: TLabel;
      5. Label2: TLabel;
      6. Panel1: TPanel;
      7. procedure Button1Click(Sender: TObject);
      8. private
      9. { private declarations }
      10. FZufallszahl: integer; //die zu ratende Zahl hier deklarieren
      11. public
      12. { public declarations }
      13. end;

      FZufallszahl ist somit ein privates Feld des Formulars, üblicherweise benutzt man für Felder den F-Präfix. So, diese Zahl lässt Du einmal am Spielanfang bestimmen und kannst immer wieder darauf zugreifen, ohne dass sie sich zwischenzeitlich ändert.
      10 Minuten Nachdenken ersparen oftmals 10 Stunden Fehlersuche.
      Danke :D, komme der Sache näher xD

      Delphi-Code

      1. unit Unit1;
      2. {$mode objfpc}{$H+}
      3. interface
      4. uses
      5. Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
      6. type
      7. { TForm1 }
      8. TForm1 = class(TForm)
      9. Button1: TButton;
      10. Label1: TLabel;
      11. procedure Button1Click(Sender: TObject);
      12. private
      13. { private declarations }
      14. FZufall,FVers:integer;
      15. public
      16. { public declarations }
      17. end;
      18. var
      19. Form1: TForm1;
      20. implementation
      21. {$R *.lfm}
      22. { TForm1 }
      23. procedure TForm1.Button1Click(Sender: TObject);
      24. var Zahl: Integer; Eingabe: String;
      25. begin
      26. Randomize;
      27. FZufall := Random (100) + 1;
      28. Eingabe := '0';
      29. If Not InputQuery('Rate mal', 'Zahl eintippen', Eingabe) Then
      30. Exit;
      31. Zahl := StrToIntDef(Eingabe, 0);
      32. If Zahl < FZufall Then
      33. Begin
      34. Label1.Caption := 'Deine Zahl ist zu klein!';
      35. FVers := FVers + 1;
      36. End;
      37. If Zahl > FZufall Then
      38. Begin
      39. Label1.Caption := 'Deine Zahl ist zu groß!';
      40. FVers := FVers + 1;
      41. End;
      42. If Zahl = FZufall Then
      43. Begin
      44. Label1.Caption := 'RICHTIG!!!';
      45. ShowMessage ('Du hast ' +IntToStr (FVers)+'mal geraten');
      46. end;
      47. end;
      48. end.



      Irgendwie muss man die richtige Zahl mehrmals eintippen bis es funktioniert. Die Ausgabe der Anzahl der Versuche scheint jedoch zu klappen.

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Zuropta“ ()

      Du ermittelst die Zahl aber bei jedem Click wieder neu, das kann ja nicht im Sinne des Spiels sein. Das Randomize sollte man einmalig ausführen, üblicherweise verlegt man das daher ins OnCreate des Formulars. Und an Deiner Stelle würde ich mir eine Prozedur "NeuesSpiel" o.ä. schreiben, die die Zufallszahl ermittelt und den/die Zähler zurücksetzt. Diese Prozedur kannst Du auch im OnCreate aufrufen, zusätzlich aber könntest Du irgendwann einen weiteren Button einfügen, der ebenfalls diese Prozedur aufruft, damit man das Spiel neu starten kann, ohne das Programm verlassen zu müssen.
      10 Minuten Nachdenken ersparen oftmals 10 Stunden Fehlersuche.
      Wenn etwas gesichert nur einmal pro Anwendungsstart aufgerufen werden soll, dann ist der beste Platz dafür die Projektdatei selber.

      Delphi-Code

      1. program foo;
      2. ...
      3. begin
      4. Randomize();
      5. Application.Initialize;
      6. Application.CreateForm( ... );
      7. Application.Run;
      8. end;

      Mal ganz langsam zum Mitmachen

      Am besten wird es wohl sein, das Problem – das Zahlenratespiel – in logische Abschnitte aufzuteilen:

      Der Spieler soll den Bereich der Zufallszahl angeben, z.B. zwischen 1 und 99 inklusive. Die Zahl darf dann nicht
      1. kleiner als 1 und nicht größer als 99 sein.
      2. Der Spieler soll eine Zufallszahl erzeugen.
      3. Der Spieler soll nun eingeben können, welche Zahl wohl erzeugt wurde.
      4. Das Programm soll anhand der Eingabe mitteilen, ob die Zahl getroffen wurde oder ob sie kleiner oder größer als die erzeugte Zufallszahl ist.

      Als nächstes überlegen wir uns, was wir an visuellen Komponenten benötigen:

      Eine Eingabemöglichkeit für den gültigen Bereich der Zufallszahl. Da es sich bei einem Gültigkeitsbereich um zwei Grenzwerte handelt – kleinste Zahl und größte Zahl –, benötigen wir auch zwei Eingabe-Komponenten. Wir wählen hierzu zwei TEdit, das in den Standard-Komponenten zu finden ist. Das erste nennen wir Ed_Range_Min, das zweite Ed_Range_Max. Man sollte sich gleich zu Beginn angewöhnen, "sprechende" Variablen-Bezeichner zu wählen. Ein Programmcode wird dadurch leichter lesbar und somit auch leichter verständlich und bleibt das auch noch nach Jahren.

      Wir klicken also auf die Komponente TEdit in der Werkzeugleiste (Standard) und platzieren dieses TEdit auf der noch leeren Form. Dann gehen wir sofort in den Objektinspektor geben unter Name den Text "Ed_Range_Min" ein. Da nur Ziffern eingegeben werden sollen, stellen wir die Eigenschaft NumbersOnly auf True. Und weil Zahlen gewöhnlich rechtsbündig dargestellt werden, stellen wir die Eigenschaft Alignment auf taRightJustify.

      Nun wählen wir aus der Werkzeugleiste im Abschnitt Common Controls die Komponente TUpDown und platzieren sie neben dem TEdit mit dem Namen "Ed_Range_Min". Sogleich benennen wir die Komponente um in UD_Range_Min und weisen der Eigenschaft Associate das TEdit zu, indem wir es in der DropDown-Liste auswählen. Damit ist das UpDown mit dem TEdit verbunden. Die Eigenschaft Max von UpDown stellen wir auf 9999, die Eigenschaft Min auf 1.

      Da wir noch eine zweite Edit-UpDown-Gruppe benötigen, klicken wir das TEdit an, um es zu markieren. Durch die hergestellte Verbindung sind damit gleich beide Komponenten markiert und wir kopieren diese mit der Tastenkombination Ctrl-C oder im Bearbeiten-Menü mit dem Befehl Kopieren. Danach klicken wir wieder aufs Formular und betätigen führen Tastenkombination Ctrl-V für Einfügen (Paste) aus, wahlweise auch im Bearbeiten-Menü den Befehl Einfügen. Nun haben wir eine zweite Edit-UpDown-Gruppe und platzieren diese neben der ersten. Das zweite TEdit nennen wir nun Ed_Range_Max, das zweite UpDown UD_Range_Max.

      Auch für die Eingabe des Raters können wir eine Edit-UpDown-Gruppe einsetzen. Also drücken wir noch einmal Ctrl-V, da sich die kopierten Komponenten vermutlich noch im Zwischenspeicher befinden. Diese platzieren wir etwas unter den anderen beiden und benennen sie Ed_Raten bzw. UD_Raten.

      Nun benötigen wir noch drei Buttons, die man bei den Standard-Komponenten findet: einen für das Erzeugen einer neuen Zufallszahl, einen für das Bestätigen der Ratezahl und einen für das Beenden des Programms.

      Den ersten Button platzieren wir neben den beiden Range-Gruppen und nennen in Btn_Zufall. Als Beschriftung (Caption) geben wir "Zufallszahl" ein. Damit der Anwender weiß, was er mit diesem Button machen soll, geben wir in der Eigenschaft Hint den Text "Zufallszahl erzeugen" ein und stellen die Eigenschaft ShowHint auf True. Damit wird beim Platzieren des Mauszeigers auf dem Button ein Hilfetext (Hint) angezeigt.

      Den zweiten Button platzieren wir neben der Rate-Gruppe (Ed_Raten + UD_Raten), benennen ihn als Btn_Raten und beschriften ihn mit "Tipp abgeben". Da man keinen Tipp abgeben kann, bevor nicht eine ZUfallszahl erzeugt wurde, sorgen wir dafür, daß man den Button Btn_Raten erst betätigen kann, nachdem eine Zufallszahl erzeugt wurde. Dazu setzen wir die Eigenschaft Enabled von Btn_Raten auf False. Damit ist er deaktiviert.

      Der dritte Button kommt ganz rechts unten hin, erhält den Namen Btn_Ende und die Beschriftung "Programm beenden". Um das Programm mit der ESC-Taste beenden zu können, stellen wir die Eigenschaft Cancel des dritten Buttons auf True.

      Damit hätten wir die Oberfläche soweit fertig. Doch halt: Es fehlen noch die Beschriftungen für die drei Edit-Felder. Dazu wählen wir in der Werkzeugleiste aus den Standard-Komponenten das TLabel. Da wir gleich drei benötigen, drücken wir beim Anklicken der Komponente in der Werkzeugleiste die Shift-Taste. Dadurch rastet diese Komponente quasi ein und wir können nun mit jedem Klick auf das Formular ein neues Label platzieren. Das machen wir genau dreimal, danach klicken wir den Pfeil ganz links in der Werkzeugleiste, um die Komponente wieder zu entrasten. Das erste Label nennen wir Lbl_Min, das zweite Lbl_Max und das dritte Lbl_Raten. Wo sie platziert werden müssen, dürfte wohl leicht zu erraten sein. Ein viertes Label mit dem Namen "Lbl_Versuche" und der Beschriftung " bisherige Versuche: 0" kann man als Zugabe ganz unten links neben dem Beenden-Button platzieren. Als Hinweis und zur Unterscheidung für den Benutzer, daß es sich hier um ein Ausgabe-Label handelt, weist man diesem Label am besten eine andere Farbe als die des Formulars zu. Zuletzt stellen die Eigenschaft AutoSize auf False und ziehen das Label etwas breiter. Das formular sollte danach ungefähr so aussehen, wie die erste Grafik ZahlenratenForm.jpg.

      Nun geht's ans Eingemachte, sprich: den Code. Wir brauchen als erstes eine Methode, die die Zufallszahl berechnet. Wir drücken also F12 und befinden uns im Code-Editor. Dort gehen wir in den Abschnitt private { private declarations } und geben dort ein: "Procedure Zufahlszahl_Ermitteln;". Danach drücken wir die Tastenkombination Shift-Ctrl-C, was einen Procedur-Rumpf erzeugt. In diesen Rumpf schreiben wir nun unseren Code für die Ermittlung der Zufahlszahl:

      Wie oben bereits jemand beschrieben hat, benötigen wir eine private Variable Zufallszahl, die wir im Private-Abschnitt deklarieren, denn auf diese Variable beziehen wir uns in der Methode Zufahlszahl_Ermitteln: Zufallszahl : Integer;

      Der Random-Befehl, mit dem wir ein Zufallszahl erzeugen wollen, funktioniert so: Wird in Klammern eine ganze Zahl angegeben (Parameter), ist das Ergebnis gleich oder größer 0 und kleiner als die Parameterzahl. Wollen wir z.B. eine Zahl zwischen 1 und 99 haben, dann geben wir 98 an und zählen zum Resultat 1 dazu. Wollen wir eine Zahl zwischen 0 und 99 haben, dann geben wir 100 an und zählen nichts dazu. Verstanden? Ist ja nicht so schwer, oder?

      Wir deklarieren nun in der Procedure drei Variablen als Integer: Eine für die kleinste, eine für die größte Zahl und eine für den Parameter für den Random-Befehl:

      Delphi-Code

      1. procedure TFormMain.Zufahlszahl_Ermitteln;
      2. Var
      3. Min, // kleinste Zahl
      4. Max, // größte Zahl
      5. Rzahl // Random-Zahl
      6. : Integer;


      zwischen begin und end. schreiben wir folgendes:

      Delphi-Code

      1. begin
      2. Min := StrToInt(Ed_Range_Min.Text);
      3. Max := StrToInt(Ed_Range_Max.Text);
      4. Rzahl := Max - Min;
      5. Zufallszahl := Random(Rzahl) + Min;
      6. end;


      Damit ist unsere Zufallsberechnung fast fertig. Es fehlt noch die Initialisierung des Random-Befehls, die jedoch nur einmal erfolgen soll. Deshalb erledigen wir das beim Programmstart in der Ereignisbehandlung zu OnCreate. Dazu wechseln wir mit F12 wieder in den Design-Modus, klicken das Formular an und wählen im Objektinspektor den Reiter Ereignisse. Dort suchen wir nach OnCreate (Liste ist alphabetisch) und machen einen Doppelklick in die DropDown-Liste. Die Folge ist, daß ein weiterer Methodenrumpf erstellt wird, in den wir einfach nur Randomize; reinschreiben. Mehr braucht es nicht:

      Delphi-Code

      1. procedure TFormMain.FormCreate(Sender: TObject);
      2. begin
      3. Randomize;
      4. end;


      Als nächstes machen wir uns daran, die Ereignisbehandlung für den Klick auf den Button Btn_Zufall zu erstellen. Doppelklick auf den Button erzeugt den Procedur-Rumpf, und dort rufen wir erst einmal die private Methode Zufahlszahl_Ermitteln; auf, die wir gerade erst erstellt haben. Bei dieser Gelegenheit sollten wir auch das Label, das die Anzahl der Rate-Versuche anzeigt, zurücksetzen. Weil wir aber den Text für das Label voraussichtlich öfter als nur einmal benötigen, empfiehlt es sich, diesen in einer Konstante festzulegen. Das machen wir wieder im Privat-Abschnitt. Auch benötigen wir noch eine Integer-Variable, die die Anzahl der Versuche speichert. Der Private-Abschnitt sollte dann so aussehen:

      Delphi-Code

      1. private { private declarations }
      2. AusgabeText = ' bisherige Versuche: ';
      3. Versuche,
      4. Zufallszahl : Integer;
      5. Procedure Zufahlszahl_Ermitteln;
      6. public { public declarations }


      Die Ereignisbehandlung für Btn_Zufall:

      Delphi-Code

      1. procedure TFormMain.Btn_ZufallClick(Sender: TObject);
      2. begin
      3. Zufahlszahl_Ermitteln;
      4. Versuche := 0;
      5. Lbl_Versuche.Caption := AusgabeText + '0';
      6. Btn_Raten.Enabled := True;
      7. end;


      So, ich glaube, ab hier kommst du alleine weiter. Das Konzept sollte jetzt hinreichend klar sein, die Vorgehensweise ebenso, und was noch fehlt, nämlich die Ereignisbehandlung für Btn_Raten, kriegst du ganz bstimmt selber hin. Ein paar Fallstricke warten auch noch auf dich: Was machst du, wenn der Spieler für die kleinste Zahl eine größere Zahl angibt als für die größte Zahl? Vielleicht kann man das ja auch verhindern?

      Viel Spaß beim weiterprogrammieren :D

      (Die Wahrheit ist natürlich viel trivialer: Ich hab keinen Bock mehr, weil mir gleich die Augen zufallen und mein Bett so laut ruft, daß ich mich nicht mehr konzentrieren kann. Hat mir aber dennoch Spaß gemacht, mal so ausführlich eine Anleitung zu schreiben, und das auch noch über das mir eher ungewohnte Lazarus bzw. CodeTyphon).
      Bilder
      • ZahlenratenForm.jpg

        82,62 kB, 477×203, 144 mal angesehen