Einzelnen Beitrag anzeigen

Benutzerbild von Sir Rufo
Sir Rufo

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

AW: Eine Projektarbeit

  Alt 15. Dez 2014, 14:26
Hier mal ein kleines Beispiel, wie man so etwas aufbauen und vor allem die Arbeit aufteilen kann:

Wir haben das Spiel Fang-Den-Button (ja, sehr sinnvoll)

Auf dem Spielfeld soll ein Button erscheinen und nach einer bestimmten Zeit die Position wechseln. Ziel ist es den Button zu treffen und nicht das Spielfeld. Für jeden getroffenen Button erhält man 10 Punkte. Trifft man mehr als 10 mal daneben, dann ist das Spiel vorbei.

Gemeinerweise verändert sich bei jedem Positionswechsel auch die Größe des Buttons und nach jedem Treffer wechselt der Button immer schneller seine Position.

Die Arbeit soll aufgeteilt werden, so dass einer sich um die Oberfläche und der Andere sich um die Logik kümmern kann. Dazu vereinbart man, was man wie und wo erwartet und skizziert sich folgende abstrakte Klasse (die kann quasi nix, gibt aber den Rahmen vor):
Delphi-Quellcode:
unit FangDenButton;

interface

uses
  System.Types,
  System.Classes;

type
  TFangDenButton = class abstract
  private
    FOnChange: TNotifyEvent; { System.Classes.TNotifyEvent }
    FPlaygroundSize: TPoint; { System.Types.TPoint }
    procedure SetOnChange( const Value: TNotifyEvent );
    procedure SetPlaygroundSize( const Value: TPoint );
  protected
    // Wird etwas geändert, dann diese Methode aufrufen,
    // damit die Anzeige darauf reagieren kann
    procedure NotifyChange( );
  protected
    // Konkrete Ableitungen müssen diese Methoden mit Leben füllen
    procedure DoButtonCatched; virtual; abstract;
    procedure DoButtonMissed; virtual; abstract;
    procedure DoStart; virtual; abstract;
    procedure DoSetPlaygroundSize( const Value: TPoint ); virtual;
    function GetPunkte: Integer; virtual; abstract;
    function GetPosition: TPoint; virtual; abstract;
    function GetButtonSize: TPoint; virtual; abstract;
    function GetSpielAktiv: Boolean; virtual; abstract;
  public
    // Button getroffen
    procedure ButtonCatched;
    // Button nicht getroffen
    procedure ButtonMissed;
    // Startet das Spiel
    procedure Start;

    // Ereignis bei einer Änderung
    property OnChange: TNotifyEvent read FOnChange write SetOnChange;
    // ANzahl der Punkte
    property Punkte: Integer read GetPunkte;
    // Position des Buttons
    property Position: TPoint read GetPosition;
    // Größe des buttons
    property ButtonSize: TPoint read GetButtonSize;
    // Größe des Spielfelds (wird von der Anzeige geliefert)
    property PlaygroundSize: TPoint read FPlaygroundSize write SetPlaygroundSize;
    // Ist das Spiel am laufen?
    property SpielAktiv: Boolean read GetSpielAktiv;
  end;

implementation

{ TFangDenButton }

procedure TFangDenButton.ButtonCatched;
begin
  DoButtonCatched;
end;

procedure TFangDenButton.ButtonMissed;
begin
  DoButtonMissed;
end;

procedure TFangDenButton.DoSetPlaygroundSize( const Value: TPoint );
begin
end;

procedure TFangDenButton.NotifyChange;
begin
  if Assigned( FOnChange )
  then
    FOnChange( Self );
end;

procedure TFangDenButton.SetOnChange( const Value: TNotifyEvent );
begin
  FOnChange := Value;
  if Assigned( FOnChange )
  then
    FOnChange( Self );
end;

procedure TFangDenButton.SetPlaygroundSize( const Value: TPoint );
begin
  if FPlaygroundSize <> Value
  then
    begin
      FPlaygroundSize := Value;
      // Wir benachrichtigen mal nach innen, wer weiß ob das benötigt wird
      DoSetPlaygroundSize( Value );
      // Vorsichtshalber informieren wir mal die Anzeige, man kann nie wissen :o)
      NotifyChange;
    end;
end;

procedure TFangDenButton.Start;
begin
  DoStart;
end;

end.
Nun können beide loslegen. Der für die Anzeige baut sich jetzt eine minimale funktionierende Klasse, damit er seine Anzeige auch testen kann
Delphi-Quellcode:
unit FangDenButtonTest;

interface

uses
  System.Types,
  FangDenButton;

type
  TFangDenButtonTest = class( TFangDenButton )
  private
    // Punkte
    FPunkte: Integer;
    // Spielstatus
    FSpielAktiv: Boolean;
  protected
    procedure DoButtonCatched; override;
    procedure DoButtonMissed; override;
    procedure DoSetPlaygroundSize( const Value: TPoint ); override;
    procedure DoStart; override;
    function GetButtonSize: TPoint; override;
    function GetPosition: TPoint; override;
    function GetPunkte: Integer; override;
    function GetSpielAktiv: Boolean; override;
  end;

implementation

{ TFangDenButtonTest }

procedure TFangDenButtonTest.DoButtonCatched;
begin
  inherited;
  // Punkte hochzählen
  Inc( FPunkte );
  // Anzeige benachrichtigen
  NotifyChange;
end;

procedure TFangDenButtonTest.DoButtonMissed;
begin
  inherited;
  // Punkte herunterzählen
  Dec( FPunkte );
  // Anzeige benachrichtigen
  NotifyChange;
end;

procedure TFangDenButtonTest.DoSetPlaygroundSize( const Value: TPoint );
begin
  inherited;
  // Anzeige benachrichtigen
  NotifyChange;
end;

procedure TFangDenButtonTest.DoStart;
begin
  inherited;
  // Punkte zurücksetzen, wenn das Spiel aktiv war
  if not FSpielAktiv
  then
    FPunkte := 0;
  // Einfaches umschalten zwischen an und aus
  FSpielAktiv := not FSpielAktiv;
  // Anzeige benachrichtigen
  NotifyChange;
end;

function TFangDenButtonTest.GetButtonSize: TPoint;
begin
  // Der Button bekommt die halbe Breite und Höhe des Spielfelds
  Result := TPoint.Create( PlaygroundSize.X div 2, PlaygroundSize.Y div 2 );
end;

function TFangDenButtonTest.GetPosition: TPoint;
begin
  // Der Button kommt in die Mitte des Spielfelds
  // eigentlich ( ( Spielfeld.Breite - Button-Breite ) / 2 )
  // aber da der halb so groß ist wie das Spielfeld (s.o.)
  // können wir auch vereinfacht ( Spielfeld.Breite / 4 ) nehmen
  Result := TPoint.Create( PlaygroundSize.X div 4, PlaygroundSize.Y div 4 );
end;

function TFangDenButtonTest.GetPunkte: Integer;
begin
  // Punkte zurückliefern
  Result := FPunkte;
end;

function TFangDenButtonTest.GetSpielAktiv: Boolean;
begin
  // Spielstatus zurückliefern
  Result := FSpielAktiv;
end;

end.
Damit baut er sich die Anzeige, die minimal so aussehen sollte
Delphi-Quellcode:
unit Form.Main;

interface

uses
  FangDenButton,

  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons;

type
  TForm1 = class( TForm )
  private
    // Die Spiel-Instanz
    FGame: TFangDenButton;
    // Benachrichtigungs-Methode wenn sich am Spiel etwas ändert
    procedure GameOnChange( Sender: TObject );
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  FangDenButtonTest;

{ TForm1 }

procedure TForm1.AfterConstruction;
begin
  inherited;
  // Spiel-Instanz erzeugen (ist erstmal die Test-Klasse)
  FGame := TFangDenButtonTest.Create;
  // Mit dem OnChange-Event verbinden
  FGame.OnChange := GameOnChange;
end;

procedure TForm1.BeforeDestruction;
begin
  // Spiel-Instanz wieder aufräumen
  FGame.Free;
  inherited;
end;

procedure TForm1.GameOnChange( Sender: TObject );
begin
  // Wird aufgerufen, wenn sich am Spiel etwas geändert hat
end;

end.
An Ende könnte das dann so aussehen
Delphi-Quellcode:
unit Form.Main;

interface

uses
  FangDenButton,

  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons;

type
  TForm1 = class( TForm )
    PunkteLabel: TLabel;
    PlayGroundPanel: TPanel;
      { OI: OnClick => PlaygroundPanelClick }
      { OI: OnDblClick => PlaygroundPanelClick }
      { OI: OnResize => PlaygroundPanelResize }
    HeaderPanel: TPanel;
    StartButton: TButton;
      { OI: OnClick => StartButtonClick }
    FangButton: TSpeedButton;
      { OI: OnClick => FangButtonClick }
    procedure FangButtonClick( Sender: TObject );
    procedure PlayGroundPanelClick( Sender: TObject );
    procedure StartButtonClick( Sender: TObject );
    procedure PlayGroundPanelResize( Sender: TObject );
  private
    FGame: TFangDenButton;
    procedure GameOnChange( Sender: TObject );
    procedure NotifyPlaygroundSize;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  FangDenButtonTest;

{ TForm1 }

procedure TForm1.AfterConstruction;
begin
  inherited;
  FGame := TFangDenButtonTest.Create;
  FGame.OnChange := GameOnChange;
  NotifyPlaygroundSize;
end;

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

procedure TForm1.FangButtonClick( Sender: TObject );
begin
  FGame.ButtonCatched;
end;

procedure TForm1.GameOnChange( Sender: TObject );
begin

  PunkteLabel.Caption := IntToStr( FGame.Punkte );

  FangButton.Visible := FGame.SpielAktiv;
  FangButton.Left := FGame.Position.X;
  FangButton.Top := FGame.Position.Y;
  FangButton.Width := FGame.ButtonSize.X;
  FangButton.Height := FGame.ButtonSize.Y;
end;

procedure TForm1.NotifyPlaygroundSize;
begin
  FGame.PlaygroundSize := TPoint.Create( PlayGroundPanel.Width, PlayGroundPanel.Height );
end;

procedure TForm1.PlayGroundPanelClick( Sender: TObject );
begin
  FGame.ButtonMissed;
end;

procedure TForm1.PlayGroundPanelResize( Sender: TObject );
begin
  NotifyPlaygroundSize;
end;

procedure TForm1.StartButtonClick( Sender: TObject );
begin
  FGame.Start;
end;

end.
Der Kollege mit der Logik ist dann auch soweit und bringt uns seine Unit FangDenButtonKonkret mit
Delphi-Quellcode:
unit FangDenButtonKonkret;

interface

uses
  System.Types,
  Vcl.ExtCtrls,
  FangDenButton;

type
  TFangDenButtonKonkret = class( TFangDenButton )
  private
    FTimer: TTimer; { Vcl.ExtCtrls.TTimer }
    FSpielAktiv: Boolean;
    FPunkte: Integer;
    FDaneben: Integer;
    FButtonSize: TPoint;
    FPosition: TPoint;
    procedure TimerCalled( Sender: TObject );
    procedure NewPosition;
    procedure SetButtonSize( ASize: Integer );
  protected
    procedure DoButtonCatched; override;
    procedure DoButtonMissed; override;
    procedure DoSetPlaygroundSize( const Value: TPoint ); override;
    procedure DoStart; override;
    function GetButtonSize: TPoint; override;
    function GetPosition: TPoint; override;
    function GetPunkte: Integer; override;
    function GetSpielAktiv: Boolean; override;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

implementation

uses
  System.Math;

{ TFangDenButtonKonkret }

procedure TFangDenButtonKonkret.AfterConstruction;
begin
  inherited;
  FTimer := TTimer.Create( nil );
  FTimer.Enabled := False;
  FTimer.OnTimer := TimerCalled;
end;

procedure TFangDenButtonKonkret.BeforeDestruction;
begin
  FTimer.Free;
  inherited;
end;

procedure TFangDenButtonKonkret.DoButtonCatched;
begin
  inherited;
  if FSpielAktiv
  then
    begin
      FTimer.Enabled := False;
      FPunkte := FPunkte + 10;
      FTimer.Interval := Max( 150, FTimer.Interval - 50 );
      NewPosition;
      NotifyChange;
      FTimer.Enabled := True;
    end;
end;

procedure TFangDenButtonKonkret.DoButtonMissed;
begin
  inherited;
  if FSpielAktiv
  then
    begin
      Inc( FDaneben );
      if FDaneben = 10
      then
        begin
          FSpielAktiv := False;
        end;
      NotifyChange;
    end;
end;

procedure TFangDenButtonKonkret.DoSetPlaygroundSize( const Value: TPoint );
begin
  inherited;
  NewPosition;
end;

procedure TFangDenButtonKonkret.DoStart;
begin
  inherited;
  if not FSpielAktiv
  then
    begin
      FSpielAktiv := True;
      FPunkte := 0;
      FDaneben := 0;

      SetButtonSize( 100 );

      NewPosition;

      FTimer.Interval := 1000;
      FTimer.Enabled := True;
      NotifyChange;
    end;
end;

function TFangDenButtonKonkret.GetButtonSize: TPoint;
begin
  Result := FButtonSize;
end;

function TFangDenButtonKonkret.GetPosition: TPoint;
begin
  Result := FPosition;
end;

function TFangDenButtonKonkret.GetPunkte: Integer;
begin
  Result := FPunkte;
end;

function TFangDenButtonKonkret.GetSpielAktiv: Boolean;
begin
  Result := FSpielAktiv;
end;

procedure TFangDenButtonKonkret.NewPosition;
begin
  SetButtonSize( Random( 100 ) + 50 );
  FPosition.X := Random( PlaygroundSize.X - FButtonSize.X );
  FPosition.Y := Random( PlaygroundSize.Y - FButtonSize.Y );
  NotifyChange;
end;

procedure TFangDenButtonKonkret.SetButtonSize( ASize: Integer );
begin
  FButtonSize.X := ASize;
  FButtonSize.Y := ASize;
  NotifyChange;
end;

procedure TFangDenButtonKonkret.TimerCalled( Sender: TObject );
begin
  NewPosition;
end;

end.
Das ist nett, die werden wir doch gleich mal testen. Dazu müssen wir in unsere Anzeige nur ganz wenig ändern:
Delphi-Quellcode:
implementation

{$R *.dfm}

uses
  FangDenButtonTest,
  { Unit einbinden }
  FangDenButtonKonkret;

{ TForm1 }

procedure TForm1.AfterConstruction;
begin
  inherited;
  // FGame := TFangDenButtonTest.Create;
  // Statt der Test-Klasse, die Konkrete-Klasse
  FGame := TFangDenButtonKonkret.Create;
  FGame.OnChange := GameOnChange;
  NotifyPlaygroundSize;
end;
Und laufen lassen ...

Was sagt die Maus dazu?
Zitat:
Das war Delphi!
und eignet sich daher nicht zum Copy-Paste verwenden mit Lazarus/FreePascal!
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