Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - "rotierende zufallszahl"


mick - Sa 28.03.09 00:08
Titel: "rotierende zufallszahl"
hallo zusammen.

ich programmiere ein primitives zufallszahlenspiel. nach einem klick auf den start/stop-button soll in einem panel eine zufallszahl "rotieren", d.h. im paar millisekundentakt wird darin eine neu generierte zufallszahl zw. 1 und 6 angezeigt (auf der gleichen stelle wie die vorige, man sieht also immer nur eine ziffer). dies soll prinzipiell unendlich lange laufen, zumindest so lange, bis der user wieder auf den SELBEN start/stop-button klickt (im spielerischen sinne des programms in dem moment, wenn die hoechste zufallszahl im panel angezeigt wurde, aber soviel nur zur prog-logik nebenbei). mein problem: wie formuliere ich das? wenn man naemlich auf den start/stop-button zum ersten mal drueckt, das onclick-ereignis ausgeloest wird und die schleife zur zufallszahlenerzeugung und -ausgabe durchlaeuft, soll abgebrochen werden durch erneuten klick auf den selben button. doch dessen onclick-ereignis wird als prozedur doch grade ausgefuehrt. wie kann das klappen? eine spezielle bedingung in der repeat-schleife? fuer profis sicherlich ein leichtes, dies zu loesen, ich danke im voraus.

sofern nicht zu verwirrend, hier ein quellcode-auszug:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
procedure TForm1.BitBtnStartStopClick(Sender: TObject);

  procedure Verzoegerung;
  const
    Dauer = 70;  { Verzoegerung in Millisekunden }
  var
    ZeitAnfang,ZeitEnde: Longint;
  begin
    ZeitAnfang := GetTickCount;
    repeat
      Application.ProcessMessages;  { Sichert, dass andere Programme weiterlaufen }
      ZeitEnde := GetTickCount;
    until (ZeitEnde-ZeitAnfang > Dauer);
  end;

  procedure Auswertung;
  begin
    {  Auswertung }
    if User > Computer then  { Sieg User }
      MessageDlg('Herzlichen Glückwunsch!' + #13 + 'Sie haben eine ' + IntToStr(User) +
      ' gewürfelt, der Computer nur eine ' + IntToStr(Computer) + '.' + #13 + #13 +
      'Sie haben gewonnen!',mtInformation,[mbOK],0)
    else if User < Computer then  { Sieg Computer }
      MessageDlg('Schade!' + #13 + 'Sie haben eine ' + IntToStr(User) +
      ' gewürfelt, der Computer hingegen eine ' + IntToStr(Computer) + '.' + #13 + #13 +
      'Sie haben leider verloren.',mtInformation,[mbOK],0)
    else  { Unentschieden }
      MessageDlg('Unentschieden!' + #13 + 'Der Computer hat ebenso wie Sie eine ' + IntToStr(User) +
      ' gewürfelt.' + #13 + #13 +
      'Entscheiden Sie das Duell in einer erneuten Runde!',mtInformation,[mbOK],0);
  end;
  
var
  i: Integer;  
begin
  if BitBtnStartStop.Caption = '&Start' then begin  { Starten des User-Wuerfelwurfs }
    { BitButton }
    BitBtnStartStop.Kind := bkCancel;
    BitBtnStartStop.Caption := '&Stop';

    { Wuerfeln }
    User := Random(6) + 1(* BAUSTELLE!!! HIER "ROTIERT" Z.Z. NOCH NICHTS, NUR SIMPLE ERZEUGUNG EINER Z-ZAHL *)
    PnlUser.Caption := IntToStr(User);
  end
  else begin  { Starten des Computer-Wuerfelwurfs }
    { BitButton }
    BitBtnStartStop.Enabled := False;

    { Wuerfeln }
    for i := 1 to 50 do begin  { 50 Zufallszahlen erzeugen }
      Computer := Random(6) + 1;
      PnlComputer.Caption := IntToStr(Computer);
      Verzoegerung;
    end;

    Auswertung;
  end;
end;


screenshot:

Moderiert von user profile iconNarses: Bild als Anhang hochgeladen

ciao,
ltmick.


jaenicke - Sa 28.03.09 00:23

Zur "Verzögerung" nimm am besten dieses Delay:
http://www.delphipraxis.net/topic7355.html

Was du machen müsstest ist eine Schleife, die bis zum Abbruch läuft. Das geht z.B. mit Tag. Beispiel, hier zähle ich einfach eine Zahl hoch:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
procedure TForm1.Button1Click(Sender: TObject);

  procedure Delay(Milliseconds: Integer);
  var
    Tick: DWord;
    Event: THandle;
  begin
    Event := CreateEvent(nil, False, False, nil);
    try
      Tick := GetTickCount + DWord(Milliseconds);
      while (Milliseconds > 0and
            (MsgWaitForMultipleObjects(1, Event, False, Milliseconds, QS_ALLINPUT) <> WAIT_TIMEOUT) do
      begin
        Application.ProcessMessages;
        if Application.Terminated then Exit;
        Milliseconds := Tick - GetTickcount;
      end;
    finally
      CloseHandle(Event);
    end;
  end;

var
  Value: Integer;
begin
  if not (Sender is TButton) then
    Exit;
  if TButton(Sender).Tag = 1 then
  begin
    TButton(Sender).Tag := 0;
    TButton(Sender).Caption := 'Start';
  end
  else
  begin
    Value := 0;
    TButton(Sender).Tag := 1;
    TButton(Sender).Caption := 'Abbrechen';
    while TButton(Sender).Tag <> 0 do
    begin
      Inc(Value);
      Caption := IntToStr(Value);
      Delay(100);
    end;
  end;
end;


GTA-Place - Sa 28.03.09 11:14

Ein wenig OT: Ein großes Lob für die Formulierung der Frage und für den bisherigen Quelltext. So gut formatiert sieht man das selten!

Und die Lösung hat ja jaenicke nun auch schon formuliert :)


mick - Sa 28.03.09 18:31

vielen dank fuer die antwort. habe es jetzt folgendermassen umgesetzt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
procedure TForm1.BitBtnStartStopClick(Sender: TObject);

  procedure Verzoegerung;
  const
    Dauer = 100;  { Verzoegerung in Millisekunden }
  var
    ZeitAnfang,ZeitEnde: Longint;
  begin
    ZeitAnfang := GetTickCount;
    repeat
      Application.ProcessMessages;  { Sichert, dass andere Programme weiterlaufen }
      ZeitEnde := GetTickCount;
    until (ZeitEnde-ZeitAnfang > Dauer);
  end;

  procedure Auswertung;
  begin
    {  Auswertung }
    if User > Computer then  { Sieg User }
      MessageDlg('Herzlichen Glückwunsch!' + #13 + 'Sie haben eine ' + IntToStr(User) +
        ' gewürfelt, der Computer nur eine ' + IntToStr(Computer) + '.' + #13 + #13 +
        'Sie haben gewonnen!',mtInformation,[mbOK],0)
    else if User < Computer then  { Sieg Computer }
      MessageDlg('Schade!' + #13 + 'Sie haben eine ' + IntToStr(User) +
        ' gewürfelt, der Computer hingegen eine ' + IntToStr(Computer) + '.' + #13 + #13 +
        'Sie haben leider verloren.',mtInformation,[mbOK],0)
    else  { Unentschieden }
      MessageDlg('Unentschieden!' + #13 + 'Der Computer hat ebenso wie Sie eine ' + IntToStr(User) +
        ' gewürfelt.' + #13 + #13 +
        'Entscheiden Sie das Duell in einer erneuten Runde!',mtInformation,[mbOK],0);
  end;

var
  i: Integer;
begin
  (* if not (Sender is TBitBtn) then Exit; *)

  { Anm.: Nummer des BitBtn-Prozeduraufrufs zaehlen bzw. erkennen mittels einer Flag (Wert) in der Eigenschaft "Tag". }
  if BitBtnStartStop.Tag = 0 then begin  { Starten des User-Wuerfelwurfs }
    { BitButton }
    BitBtnStartStop.Tag := 1;
    BitBtnStartStop.Kind := bkCancel;
    BitBtnStartStop.Caption := '&Stop';

    { Wuerfeln }
    while BitBtnStartStop.Tag <> 0 do begin
      User := Random(6) + 1;
      PnlUser.Caption := IntToStr(User);
      Verzoegerung;
    end;
  end
  else begin  { Starten des Computer-Wuerfelwurfs }
    { BitButton }
    BitBtnStartStop.Tag := 0;
    BitBtnStartStop.Enabled := False;

    { Wuerfeln }
    for i := 1 to 50 do begin  { 50 Zufallszahlen erzeugen }
      Computer := Random(6) + 1;
      PnlComputer.Caption := IntToStr(Computer);
      Verzoegerung;
    end;

    Auswertung;
  end;
end;


werde trotzdem meine art der verzoegerung nutzen - dies ist vllt. nicht so effizient, aber leichter verstaenidlich und ich denke fuer diesen zweck zielfuehrend, oder?

darueber hinaus: wie zu sehen, habe ich


Delphi-Quelltext
1:
if not (Sender is TBitBtn) then Exit;                    


einkommentiert. es klappt naemlich auch so. oder brauche ich das wirklich? habe naemlich noch nicht ganz verstanden, warum das ganze funktioniert. die eigenschaft "tag" ist mir klar - hier als eine art flag genutzt. aber wie funktioniert das mit den onclick-ereignissen? ueberlagert der zweite klick den ersten? und wird der erste nach beendigung des zweiten an der stelle fortgesetzt, wo er durch den zweiten klick unterbrochen wurde, oder wie?

danke fuer die nachhilfe,

ciao,
mick.


JayEff - Sa 28.03.09 18:33

user profile iconmick hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
Application.ProcessMessages;  { Sichert, dass andere Programme weiterlaufen }                    

Genau genommen sorgt diese Zeile dafür, dass dein Programm (nicht andere ;) ) die Nachrichten, die es bekommt (wie zum Beispiel auch Maus-Ereignisse!) weiterhin verarbeitet, also es sorgt dafür, dass dein Programm nicht komplett einfriert, während die Schleife läuft.


jaenicke - Sa 28.03.09 18:36

user profile iconmick hat folgendes geschrieben Zum zitierten Posting springen:
werde trotzdem meine art der verzoegerung nutzen - dies ist vllt. nicht so effizient, aber leichter verstaenidlich und ich denke fuer diesen zweck zielfuehrend, oder?
Das funktioniert auch, lastet aber den Rechner mehr aus.

user profile iconmick hat folgendes geschrieben Zum zitierten Posting springen:
darueber hinaus: wie zu sehen, habe ich


Delphi-Quelltext
1:
if not (Sender is TBitBtn) then Exit;                    


einkommentiert. es klappt naemlich auch so. oder brauche ich das wirklich?
Dann bekommst du Probleme, wenn da aus Versehen mal ein falschs Objekt drin steckt. Wenn du die Methode z.B. auch einem Click-Ereignis einer anderen Komponente zuweist.
Als Sender wird nämlich die auslösende Komponente übergeben (es sei denn du rufst die Methode mit einem anderen Parameter manuell auf).

user profile iconmick hat folgendes geschrieben Zum zitierten Posting springen:
ueberlagert der zweite klick den ersten?
Ja. Wie JayEff schon sagte werden die Botschaften bei dem ProcessMessages verarbeitet und dabei eben auch der neue Klick registriert und verarbeitet.


mick - Sa 28.03.09 18:59

danke fuer den hinweis mit


Delphi-Quelltext
1:
Application.ProcessMessages;                    


habe es in meinen unterlagen geaendert.

dann aber nochmals zu den ereignissen: der zweite klick ueberlagert also den ersten. die prozedur des ersten klicks wird nach beendigung der prozedur des zweiten klick dort weiter ausgefuehrt, wo sie unterbrochen wurde. ok. wenn dem so ist, koennte dann nicht folgender unerwuenschter effekt auftreten? sagen wir, in


Delphi-Quelltext
1:
2:
3:
4:
5:
while BitBtnStartStop.Tag <> 0 do begin
  User := Random(6) + 1;
  PnlUser.Caption := IntToStr(User);
  Verzoegerung;
end;


wird die while-schleife dann unterbrochen, wenn grade der befehl


Delphi-Quelltext
1:
User := Random(6) + 1;                    


ausgefuehrt wird. dann kommt der durchlauf fuer den zweiten klick, und wenn danach an der zuvor unterbrochenen stelle in obiger while-schleife weitergemacht wird, muesste doch noch


Delphi-Quelltext
1:
2:
PnlUser.Caption := IntToStr(User);
Verzoegerung;


ausgefuehrt werden. das wiederum haette zur folge, dass ganz am ende nochmals eine unerwuenschte ausgabe im PnlUser getaetigt wuerde, oder?

ciao,
mick.


jaenicke - Sa 28.03.09 19:06

Die "Unterbrechung" passiert genau da, wo du die Abarbeitung der angekommenen Mausbotschaften erlaubst. Nämlich mit Application.ProcessMessages in Verzoegerung.


mick - Sa 28.03.09 19:40

ah, dann ist eine "unterbrechung" also nur bei


Delphi-Quelltext
1:
Application.ProcessMessages;                    


moeglich - mein oben geschilderter fall kann also nicht eintreten. das bedeutet auch, dass wenn ich die zeile oben aus dem code loesche, mein programm zwar noch compiliert wird, aber beim button-klick abstuerzen wird.

danke,

ciao,
mick.


JayEff - Sa 28.03.09 19:44

user profile iconmick hat folgendes geschrieben Zum zitierten Posting springen:
aber beim button-klick abstuerzen wird.
Nein, nur einfrieren, bis die Schleife abgearbeitet ist - was sie nie sein wird, da der User nicht auf den Button klicken kann ;) Also, ja, Windows wird "Reagiert nicht mehr" melden. Aber abstürzen ist so ein böses Wort, lass uns lieber sagen, es hängt in einer Endlosschleife fest :zustimm:


jaenicke - Sa 28.03.09 19:44

Abstürzen ist das falsche Wort, nicht mehr reagieren wird das Programm dann.


mick - Sa 28.03.09 19:52

super, leute, jetzt hab ich's. danke fuer eure hilfe,

ciao,
mick.