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!