Thema: Delphi Memory-Spiel: Ideen

Einzelnen Beitrag anzeigen

Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#64

AW: Memory-Spiel: Ideen

  Alt 5. Feb 2016, 13:18
Da ich mich gerade mit der StateMachine befasst habe hier ein Zahlen-Paar-Ratespiel mit der StateMachine realisiert.

Der gesamte Ablauf-Plan wird in der StateMachine abgebildet. Der Rest ist nur noch Reaktion auf den Wechsel der States.

Der Quellcode ist keine CopyPaste-Vorlage für den TE, sondern als Anhaltspunkt für den benötigten Ablaufplan gedacht. Im Anhang befindet sich die lauffähige EXE.
Delphi-Quellcode:
unit ZahlenPaareRaten.Forms.MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes,

  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,

  stateless; // => https://github.com/SirRufo/stateless

type
  TForm1 = class( TForm )
    CardPanel: TGridPanel;
    TriggerTimer: TTimer;
    Label1: TLabel;
    procedure TriggerTimerTimer( Sender: TObject );
    procedure FormShow( Sender: TObject );
    procedure Label1Click( Sender: TObject );
  private type
{$SCOPEDENUMS ON}
    TState = ( Start, Memoize, Running, FirstDrawn, SecondDrawn, NoMatch, Match, Finished );
    TTrigger = ( StartGame, Run, DrawCard, DrawNext );
    TCardState = ( Bottom, Up, Hidden );
{$SCOPEDENUMS OFF}
    TStateMachine = TStateMachine<TState, TTrigger>;
  private
    FCardStates : array [ 0 .. 15 ] of TCardState;
    FCards : array [ 0 .. 15 ] of Integer;
    FState : TStateMachine;
    FDrawCardTrigger : TStateMachine.TTriggerWithParameters<Integer>;
    FTriggerTimerTrigger: TTrigger;
    procedure ConfigureStateMachine( State: TStateMachine );
    procedure ResetGame;
    procedure PresentCards;
    procedure BuildCardControls;
    procedure StartTriggerTimer( Trigger: TTrigger; Interval: Cardinal );
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  published
    procedure CardClick( Sender: TObject );
  end;

var
  Form1: TForm1;

implementation

uses
  Enumerables; // nicht enthalten

{$R *.dfm}
{ TForm1 }

procedure TForm1.AfterConstruction;
begin
  inherited;
  FState := TStateMachine.Create( TState.Start );
  FDrawCardTrigger := FState.SetTriggerParameters<Integer>( TTrigger.DrawCard );

  ConfigureStateMachine( FState );

  BuildCardControls;
  ResetGame;
end;

procedure TForm1.BeforeDestruction;
begin
  FState.Free;
  inherited;
end;

procedure TForm1.BuildCardControls;
var
  Row : Integer;
  Col : Integer;
  Control: TButton;
begin
  for Row := 0 to CardPanel.RowCollection.Count - 1 do
    for Col := 0 to CardPanel.ColumnCollection.Count - 1 do
      begin
        Control := TButton.Create( Self );
        Control.Parent := CardPanel;
        Control.Margins.SetBounds( 3, 3, 3, 3 );
        Control.AlignWithMargins := True;
        Control.Align := alClient;
        Control.OnClick := CardClick;
      end;
end;

procedure TForm1.CardClick( Sender: TObject );
var
  Control : TControl absolute Sender;
  CardIndex: Integer;
begin
  if not( Sender is TControl )
  then
    Exit;

  CardIndex := CardPanel.ControlCollection.IndexOf( Control );
  if ( CardIndex >= 0 ) and FState.CanFire( TTrigger.DrawCard )
  then
    FState.Fire<Integer>( FDrawCardTrigger, CardIndex );
end;

procedure TForm1.ConfigureStateMachine( State: TStateMachine );
var
  firstCard, secondCard, matchCount: Integer;
begin

  State.Configure( TState.Start )
  {} .OnEntry(
    procedure
    begin
      matchCount := 0;
      ResetGame;
      PresentCards;
    end )
  {} .Permit( TTrigger.StartGame, TState.Memoize )
  {end};

  State.Configure( TState.Memoize )
  {} .OnEntry(
    procedure
    var
      I: Integer;
    begin
      Label1.Caption := 'Merke dir jetzt die Karten';
      for I := 0 to 15 do
        FCardStates[ I ] := TCardState.Up;
      PresentCards;
      StartTriggerTimer( TTrigger.Run, 6000 );
    end )
  {} .OnExit(
    procedure
    var
      I: Integer;
    begin
      for I := 0 to 15 do
        FCardStates[ I ] := TCardState.Bottom;
      PresentCards;
    end )
  {} .Permit( TTrigger.Run, TState.Running )
  {end};

  State.Configure( TState.Running )
  {} .OnEntry(
    procedure
    begin
      Label1.Caption := 'Wähle die erste Karte';
      PresentCards;
    end )
  {} .PermitDynamic<Integer>( FDrawCardTrigger,
    function( const c: Integer ): TState
    begin
      if FCardStates[ c ] = TCardState.Bottom
      then
        Result := TState.FirstDrawn
      else
        Result := TState.Running;
    end )
  {end};

  State.Configure( TState.FirstDrawn )
  {} .OnEntry(
    procedure
    begin
      Label1.Caption := 'Wähle die zweite Karte';
    end )
  {} .OnEntryFrom<Integer>( FDrawCardTrigger,
    procedure( const c: Integer )
    begin
      firstCard := c;
      FCardStates[ c ] := TCardState.Up;
      PresentCards;
    end )
  {} .PermitDynamic<Integer>( FDrawCardTrigger,
    function( const c: Integer ): TState
    begin
      if FCardStates[ c ] = TCardState.Bottom
      then
        begin
          if FCards[ firstCard ] = FCards[ c ]
          then
            Result := TState.Match
          else
            Result := TState.NoMatch;
        end
      else
        Result := TState.FirstDrawn;
    end )
  {end};

  State.Configure( TState.SecondDrawn )
  {} .OnEntryFrom<Integer>( FDrawCardTrigger,
    procedure( const c: Integer )
    begin
      secondCard := c;
      FCardStates[ c ] := TCardState.Up;
      PresentCards;
    end )
  {} .PermitDynamic( TTrigger.DrawNext,
    function( ): TState
    begin
      if matchCount = 8
      then
        Result := TState.Finished
      else
        Result := TState.Running;
    end )
  {end};

  State.Configure( TState.NoMatch )
  {} .SubstateOf( TState.SecondDrawn )
  {} .OnEntry(
    procedure
    begin
      Label1.Caption := 'Schade, die Karten passen nicht';
      StartTriggerTimer( TTrigger.DrawNext, 2000 );
    end )
  {} .OnExit(
    procedure
    begin
      FCardStates[ firstCard ] := TCardState.Bottom;
      FCardStates[ secondCard ] := TCardState.Bottom;
    end )
  {end};

  State.Configure( TState.Match )
  {} .SubstateOf( TState.SecondDrawn )
  {} .OnEntry(
    procedure
    begin
      Label1.Caption := 'Karten passen zusammen';
      Inc( matchCount );
      StartTriggerTimer( TTrigger.DrawNext, 500 );
    end )
  {} .OnExit(
    procedure
    begin
      FCardStates[ firstCard ] := TCardState.Hidden;
      FCardStates[ secondCard ] := TCardState.Hidden;
    end )
  {end};

  State.Configure( TState.Finished )
  {} .OnEntry(
    procedure
    begin
      Label1.Caption := 'Spiel beendet. Klicke hier um nochmal zu spielen';
      PresentCards;
    end )
  {} .Permit( TTrigger.StartGame, TState.Start )
  {end};
end;

procedure TForm1.FormShow( Sender: TObject );
begin
  PresentCards;
end;

procedure TForm1.Label1Click( Sender: TObject );
begin
  if FState.CanFire( TTrigger.StartGame )
  then
    FState.Fire( TTrigger.StartGame );
end;

procedure TForm1.PresentCards;
var
  Item : TCollectionItem;
  Button: TButton;
begin
  for Item in CardPanel.ControlCollection do
    begin
      Button := TButton( TControlItem( Item ).Control );
      case FCardStates[ Item.Index ] of
        TCardState.Bottom:
          begin
            Button.Visible := True;
            Button.Caption := '?';
          end;
        TCardState.Up:
          begin
            Button.Visible := True;
            Button.Caption := IntToStr( FCards[ Item.Index ] );
          end;
        TCardState.Hidden:
          begin
            Button.Visible := False;
          end;
      end;
    end;
end;

procedure TForm1.ResetGame;
var
  I : Integer;
  lcards: TArray<Integer>;
begin
  lcards := Enumerable
  {} .Range( 1, 99 ) // Zahlen von 1 bis 99
  {} .Shuffle( ) // mischen
  {} .Take( 8 ) // 8 nehmen
  {} .ToArray( ) // in ein Array
  {end};

  lcards := Enumerable
  {} .From<Integer>( lcards ) // die 8 Zahlen von eben
  {} .Concat( lcards ) // mit den 8 Zahlen von eben verbinden (jetzt haben wir die Paare)
  {} .Shuffle( ) // mischen
  {} .ToArray( ) // in ein Array
  {end};

  for I := 0 to 15 do
    begin
      FCardStates[ I ] := TCardState.Bottom;
      FCards[ I ] := lcards[ I ];
    end;

  Label1.Caption := 'Zum Starten hier drücken';
end;

procedure TForm1.StartTriggerTimer( Trigger: TTrigger; Interval: Cardinal );
begin
  TriggerTimer.Enabled := False;
  TriggerTimer.Interval := Interval;
  FTriggerTimerTrigger := Trigger;
  TriggerTimer.Enabled := True;
end;

procedure TForm1.TriggerTimerTimer( Sender: TObject );
begin
  TriggerTimer.Enabled := False;
  if FState.CanFire( FTriggerTimerTrigger )
  then
    begin
      FState.Fire( FTriggerTimerTrigger );
    end;
end;

end.
Angehängte Dateien
Dateityp: zip ZahlenPaareRaten.zip (985,4 KB, 7x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat