<aside> ๐Ÿค“

๋ชจ๋“  ๊ฒƒ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ


unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  { ์˜ค๋ธŒ์ ํŠธ ๋ชฉ๋ก }
  TObjectType = (ObjA, ObjB, ObjC, ObjD, ObjMouse, ObjFollow);
  {
    ObjA: Small & Fast
    ObjB: Normal Obj
    ObjC: Random Direction Move
    ObjD: Rotating Object
    ObjFollow: Mouse Following Enemy Object
    ObjMouse: Mouse Following Object
  }

  { ๊ธฐ๋ณธ ์˜ค๋ธŒ์ ํŠธ }
  TBaseObject = class
  public
    ObjType: TObjectType;
    X, Y: Integer;
    Width, Height: Integer;
    SpeedX, SpeedY: Integer;
    IsDead: Boolean;

    constructor Create; virtual;
    procedure Move; virtual;
    property Dead: Boolean read IsDead write IsDead;
  end;

  { A ์˜ค๋ธŒ์ ํŠธ }
  TObjA = class(TBaseObject)
    constructor Create; override;
    procedure Move; override;
  end;

  { B ์˜ค๋ธŒ์ ํŠธ }
  TObjB = class(TBaseObject)
    constructor Create; override;
    procedure Move; override;
  end;

  { C ์˜ค๋ธŒ์ ํŠธ }
  TObjC = class(TBaseObject)
  private
    MoveTime: Integer;  // ์ด๋™ ์‹œ๊ฐ„
    StopTime: Integer;  // ์ •์ง€ ์‹œ๊ฐ„

    IsStopped: Boolean;

    SpeedOptionsX: array[0..6] of Integer;
    SpeedOptionsY: array[0..6] of Integer;

    MaxMoveTime: Integer;
    MaxStopTime: Integer;
  public
    constructor Create; override;
    procedure Move; override;
  end;

  { D ์˜ค๋ธŒ์ ํŠธ }
  TObjD = class(TBaseObject)
  private
    CenterX, CenterY: Integer;  // ํšŒ์ „ ์ค‘์‹ฌ
    Radius: Integer;            // ํšŒ์ „ ๋ฐ˜๊ฒฝ
    Angle: Double;              // ํ˜„์žฌ ํšŒ์ „ ๊ฐ๋„
    AngularSpeed: Double;       // ๊ฐ ์†๋„
  public
    constructor Create; override;
    procedure Move; override;
  end;

  { Follow ์˜ค๋ธŒ์ ํŠธ }
  TObjFollow = class(TBaseObject)
  private
    CurSpeed: Double;
    MaxSpeed: Double;
    SpeedUpInterval: Integer;
    SpeedUpTimer: Integer;
  public
    constructor Create; override;
    procedure Move; override;
  end;

  { Mouse ์˜ค๋ธŒ์ ํŠธ }
  TObjMouse = class(TBaseObject)
  public
    constructor Create; override;
    procedure Move; override;
    procedure SetPosition(AX, AY: Integer);
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    Timer1: TTimer;
    TimerSpawn: TTimer;
    PaintBox1: TPaintBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure TimerSpawnTimer(Sender: TObject);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  private
    Objects: TList;
    MouseFollower: TObjMouse;
    GameOver: Boolean;

    Score: Integer;
    ScoreTimer: Integer;
    MaxScore: Integer;

    BlackHoleCenter: TPoint;
    IsBlackHoleActive: Boolean;
    BlackHoleTimer: Integer;
    BlackHoleDuration: Integer;
    IsBurstActive: Boolean;
    BurstDuration: Integer;

    BlackHoleSpawnTimer: Integer;
    BlackHoleSpawnInterval: Integer;

    function CheckCollision(obj1, obj2: TBaseObject): Boolean;
    procedure CreateRandomObject;
    procedure CreateFollowObject;
    procedure ResetGame;
    procedure UpdateScore;
    procedure RemoveFollowObjects;

    // ๋ธ”๋ž™ํ™€ ํšจ๊ณผ ๊ด€๋ จ ํ•จ์ˆ˜
    procedure ActivateBlackHole(const APoint: TPoint; ATriggeredObj: TObjFollow = nil);
    procedure UpdateBlackHoleEffect;
    procedure DrawBlackHoleEffect;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TBaseObject }
constructor TBaseObject.Create;
begin
  inherited Create;

  { ๊ธฐ๋ณธ ์„ค์ • }
  Width := 10;
  Height := 10;

  SpeedX := 0;
  SpeedY := 0;

  IsDead := False;

  { Form1 ๋ฐ Panel1์ด ์œ ํšจํ•  ๊ฒฝ์šฐ, ๋ฌด์ž‘์œ„ ์œ„์น˜์— ์ƒ์„ฑ }
  if Assigned(Form1) and Assigned(Form1.Panel1) then
  begin
    X := Random(Form1.Panel1.Width - Width);
    Y := Random(Form1.Panel1.Height - Height);
  end
  else
  begin
    { ๊ธฐ๋ณธ ๊ฐ’ }
    X := Random(400);
    Y := Random(300);
  end;
end;

procedure TBaseObject.Move;
var
  DeltaX, DeltaY, Distance: Double;
  EffectSpeed: Double;
begin
  { Form1 ๋ฐ Panel1์ด ์œ ํšจํ•  ๊ฒฝ์šฐ }
  if not (Assigned(Form1) and Assigned(Form1.Panel1)) then Exit;

  { ObjMouse๋Š” ๋ชจ๋“  ํšจ๊ณผ ๋ฌด์‹œ }
  if ObjType = ObjMouse then
  begin
    Exit;
  end;

  { ๋ธ”๋ž™ํ™€ ์ƒ์„ฑ ์‹œ }
  if Form1.IsBlackHoleActive then
  begin
    { ๋ธ”๋ž™ํ™€์˜ ์ค‘์‹ฌ์œผ๋กœ ๋Œ์–ด๋‹น๊ฒจ์ง }
    DeltaX := Form1.BlackHoleCenter.X - (X + Width div 2);
    DeltaY := Form1.BlackHoleCenter.Y - (Y + Height div 2);
    Distance := Sqrt(DeltaX * DeltaX + DeltaY * DeltaY);

    { ๋ธ”๋ž™ํ™€์ด ๋Œ์–ด๋‹น๊ธฐ๋Š” ์†๋„ ์กฐ์ ˆ }
    EffectSpeed := 20;

    { ๋ธ”๋ž™ํ™€๊ณผ์˜ ๊ฑฐ๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ์ค‘์•™์— ๋ฐฐ์น˜ํ•จ }
    if Distance < EffectSpeed then
    begin
      X := Form1.BlackHoleCenter.X - Width div 2;
      Y := Form1.BlackHoleCenter.Y - Height div 2;
      SpeedX := 0;
      SpeedY := 0;
    end

    { ๊ฐ€๊น์ง€ ์•Š์œผ๋ฉด ๊ณ„์† ์ด๋™ }
    else if Distance > 0 then
    begin
      SpeedX := Round(DeltaX / Distance * EffectSpeed);
      SpeedY := Round(DeltaY / Distance * EffectSpeed);
    end

    { ์ •ํ™•ํžˆ ์ค‘์•™์— ์˜ค๋ฉด ๋ฉˆ์ถค }
    else
    begin
      SpeedX := 0;
      SpeedY := 0;
    end;

    Inc(X, SpeedX);
    Inc(Y, SpeedY);
  end

  { ๋ฒ„์ŠคํŠธ ์ค‘์ผ ์‹œ }
  else if Form1.IsBurstActive then
  begin
    { ๋ธ”๋ž™ํ™€์˜ ์ค‘์‹ฌ์ ์—์„œ ๋ฉ€๋ฆฌ ๋ฉ€๋ ค๋‚จ (ํ˜„์žฌ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฐฉํ–ฅ ์„ค์ •) }
    DeltaX := (X + Width div 2) - Form1.BlackHoleCenter.X;
    DeltaY := (Y + Height div 2) - Form1.BlackHoleCenter.Y;
    Distance := Sqrt(DeltaX * DeltaX + DeltaY * DeltaY);

    { ๋ฐ€๋ ค๋‚˜๋Š” ์†๋„ ์กฐ์ • }
    EffectSpeed := 30;

    { ๋ธ”๋ž™ํ™€๊ณผ์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ์†๋„ ์กฐ์ • }

    { ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ๋žœ๋คํ•œ ๋ฐฉํ–ฅ์œผ๋กœ ๋ฐ€๋ ค๋‚จ }
    if Distance < 10 then
    begin
        DeltaX := Random(200) - 100;
        DeltaY := Random(200) - 100;

        Distance := Sqrt(DeltaX * DeltaX + DeltaY * DeltaY);
    end;

    if Distance > 0 then
    begin
      SpeedX := Round(DeltaX / Distance * EffectSpeed);
      SpeedY := Round(DeltaY / Distance * EffectSpeed);
    end

    { ์ •ํ™•ํžˆ ์ค‘์‹ฌ์ด๋ฉด ๋žœ๋คํ•˜๊ฒŒ ๋ฐ€๋ ค๋‚จ }
    else
    begin
      SpeedX := Random(20) - 10;
      SpeedY := Random(20) - 10;
    end;

    Inc(X, SpeedX);
    Inc(Y, SpeedY);

    { ๋ฒ„์ŠคํŠธ ์ค‘์—๋Š” ๋ฒฝ์— ๋‹ฟ์•„๋„ ์ œ๊ฑฐ X -> ์ถฉ๋Œ ๋กœ์ง X }
  end

  { ์ผ๋ฐ˜ ์ด๋™ ์‹œ }
  else
  begin
    { ํ˜„์žฌ ์†๋„ ๋งŒํผ ์ด๋™ }
    Inc(X, SpeedX);
    Inc(Y, SpeedY);

    { ๋ฒฝ์— ๋‹ฟ์•˜์„ ๊ฒฝ์šฐ ํšŒ์ „ }
    if X < 0 then
    begin
      X := 0;
      SpeedX := -SpeedX;
    end;

    if Y < 0 then
    begin
      Y := 0;
      SpeedY := -SpeedY;
    end;

    if X > Form1.Panel1.Width - Width then
    begin
      X := Form1.Panel1.Width - Width;
      SpeedX := -SpeedX;
    end;

    if Y > Form1.Panel1.Height - Height then
    begin
      Y := Form1.Panel1.Height - Height;
      SpeedY := -SpeedY;
    end;
  end;
end;

{ TObjA }
constructor TObjA.Create;
begin
  inherited Create; // Base์˜ Create ์‚ฌ์šฉ
  ObjType := ObjA;

  Width := 50;
  Height := 50;

  SpeedX := 10;
  SpeedY := 10;
end;

procedure TObjA.Move;
begin
  inherited Move;   // Base์˜ Move ์‚ฌ์šฉ
end;

{ TObjB }
constructor TObjB.Create;
begin
  inherited Create; // Base์˜ Create ์‚ฌ์šฉ
  ObjType := ObjB;

  Width := 75;
  Height := 75;

  SpeedX := 5;
  SpeedY := 5;
end;

procedure TObjB.Move;
begin
  inherited Move;   // Base์˜ Move ์‚ฌ์šฉ
end;

{ TObjC }
constructor TObjC.Create;
begin
  inherited Create; // Base์˜ Create ์‚ฌ์šฉ
  ObjType := ObjC;

  Width := 35;
  Height := 35;

  { ์†๋„ ๋ฒ”์œ„ ์ง€์ • }
  SpeedOptionsX[0] := -30;
  SpeedOptionsX[1] := -15;
  SpeedOptionsX[2] := -5;
  SpeedOptionsX[3] := 0;
  SpeedOptionsX[4] := 5;
  SpeedOptionsX[5] := 15;
  SpeedOptionsX[6] := 30;

  SpeedOptionsY[0] := -30;
  SpeedOptionsY[1] := -15;
  SpeedOptionsY[2] := -5;
  SpeedOptionsY[3] := 0;
  SpeedOptionsY[4] := 5;
  SpeedOptionsY[5] := 15;
  SpeedOptionsY[6] := 30;

  { ์ง€์ •ํ•œ ์†๋„ ๋ฒ”์œ„์—์„œ ๋žœ๋คํ•œ ๊ฐ’ ์ง€์ • }
  SpeedX := SpeedOptionsX[Random(Length(SpeedOptionsX))];
  SpeedY := SpeedOptionsY[Random(Length(SpeedOptionsY))];

  MoveTime := 0;
  StopTime := 0;

  IsStopped := False;

  MaxMoveTime := 0;
  MaxStopTime := 0;
end;

procedure TObjC.Move;
begin
  { ๋ธ”๋ž™ํ™€ ๋ฐœ๋™ ์‹œ Base์˜ Move ์‚ฌ์šฉ }
  if Form1.IsBlackHoleActive or Form1.IsBurstActive then
  begin
    inherited Move;
  end

  { ๋ธ”๋ž™ํ™€์ด ๋ฐœ๋™๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋งŒ C๋งŒ์˜ Move ์‚ฌ์šฉ }
  else
  begin
    { ๋ฉˆ์ถฐ์žˆ์„ ๋•Œ }
    if IsStopped then
    begin
      { ๋ฉˆ์ถœ ์‹œ๊ฐ„ ์ง€์ • }
      if MaxStopTime = 0 then
        MaxStopTime := 5 + Random(16);

      Inc(StopTime);

      { ๋ฉˆ์ถฐ์•ผ ํ•  ์‹œ๊ฐ„๋™์•ˆ ๋ฉˆ์ถ˜ ๋’ค }
      if StopTime >= MaxStopTime then
      begin
        { ์†๋„๋ฅผ ๋‹ค์‹œ ์ง€์ •ํ•˜๊ณ  ๋ฉˆ์ถ”๊ธฐ ์ค‘์ง€ }
        SpeedX := SpeedOptionsX[Random(Length(SpeedOptionsX))];
        SpeedY := SpeedOptionsY[Random(Length(SpeedOptionsY))];

        StopTime := 0;
        MaxStopTime := 0;

        IsStopped := False;
      end;
    end
    { ์›€์ง์ผ ๋•Œ }
    else
    begin
      inherited Move;

      { ์ด๋™ํ•  ์‹œ๊ฐ„ ์ง€์ • }
      if MaxMoveTime = 0 then
        MaxMoveTime := 10 + Random(51);

      { ์ด๋™ ์‹œ๊ฐ„ ์ฆ๊ฐ€ }
      Inc(MoveTime);

      { ์ด๋™ํ•  ์‹œ๊ฐ„๋งŒํผ ์›€์ง์ด๋ฉด ์ •์ง€ }
      if MoveTime >= MaxMoveTime then
      begin
        SpeedX := 0;
        SpeedY := 0;

        MoveTime := 0;
        MaxMoveTime := 0;

        IsStopped := True;
      end;
    end;
  end;
end;

{ TObjD }
constructor TObjD.Create;
begin
  inherited Create;
  ObjType := ObjD;

  Width := 30;
  Height := 30;

  if Assigned(Form1) and Assigned(Form1.Panel1) then
  begin
    { ์ค‘์•™ ์œ„์น˜ ์กฐ์ • }
    CenterX := Random(Form1.Panel1.Width - Width) + Width div 2;
    CenterY := Random(Form1.Panel1.Height - Height) + Height div 2;
  end
  else
  begin
    { ๊ธฐ๋ณธ ๊ฐ’ }
    CenterX := Random(400) + 15;
    CenterY := Random(300) + 15;
  end;

  { Radius ์ดˆ๊ธฐ ๊ฐ’ ์„ค์ • (5 ~ 50) }
  Radius := 5 + Random(46);

  { ์ดˆ๊ธฐ ๊ฐ๋„ ์„ค์ • }
  Angle := 0;

  { AngularSpeed ์ดˆ๊ธฐ ๊ฐ’ ์„ค์ • (Pi/60 ~ Pi/15) }
  AngularSpeed := (Pi / 60) + (Random * (Pi / 15 - Pi / 60));

  SpeedX := 0;
  SpeedY := 0;

  IsDead := False;
end;

procedure TObjD.Move;
var
  NewX, NewY: Integer;
begin
  if IsDead then Exit;

  { ๋ธ”๋ž™ํ™€ ๋ฐ ๋ฒ„์ŠคํŠธ ์ค‘์ด๋ฉด Base์˜ Move ์‚ฌ์šฉ }
  if Form1.IsBlackHoleActive or Form1.IsBurstActive then
  begin
    inherited Move;
  end

  { ์ผ๋ฐ˜ ์ด๋™ ์‹œ }
  else
  begin
    if not (Assigned(Form1) and Assigned(Form1.Panel1)) then Exit;

    { ๊ฐ์„ ๊ณ„์† ๋ฒŒ๋ฆฌ๋ฉด์„œ ๋น™๊ธ€๋น™๊ธ€ ๋Œ๊ฒŒ ํ•จ }
    Radius := Radius + 1;
    if Radius > (Form1.Panel1.Width div 2) then
      Radius := 10;

    Angle := Angle + AngularSpeed;
    if Angle > 2 * Pi then
      Angle := Angle - 2 * Pi;

    NewX := CenterX + Round(Radius * Cos(Angle));
    NewY := CenterY + Round(Radius * Sin(Angle));

    { ๋ฒฝ์— ๋‹ฟ์œผ๋ฉด ์ œ๊ฑฐ }
    if (NewX < 0) or (NewY < 0) or
        (NewX > Form1.Panel1.Width - Width) or
        (NewY > Form1.Panel1.Height - Height) then
    begin
      IsDead := True;
      Exit;
    end;

    X := NewX;
    Y := NewY;

    { ๋‹ค๋ฅธ ์˜ค๋ธŒ์ ํŠธ๋“ค ๊ฐ„์˜ ์ถฉ๋Œ ๋ฌด์‹œ -> ์ถฉ๋Œ ๋กœ์ง X }
  end;
end;

{ TObjFollow }
constructor TObjFollow.Create;
begin
  inherited Create;
  ObjType := ObjFollow;

  Width := 40;
  Height := 40;

  { ์ดˆ๊ธฐ ์†๋„ }
  CurSpeed := 5.0;

  { ์†๋„ ์ฆ๊ฐ€ ๊ฐ„๊ฒฉ (0.5์ดˆ๋งˆ๋‹ค) }
  SpeedUpInterval := Round(500 / Form1.Timer1.Interval);

  { Interval์ด 0์ด ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•จ }
  if SpeedUpInterval = 0 then SpeedUpInterval := 1;

  { ์†๋„ ์ฆ๊ฐ€ ํƒ€์ด๋จธ ์„ค์ • }
  SpeedUpTimer := 0;

  { ์ตœ๋Œ€ ์†๋„ ์ œํ•œ }
  MaxSpeed := 30.0;
end;

procedure TObjFollow.Move;
var
  TargetX, TargetY: Integer;
  DeltaX, DeltaY: Double;
  Distance: Double;
begin
  if IsDead then Exit;
  if not (Assigned(Form1) and Assigned(Form1.Panel1) and Assigned(Form1.MouseFollower)) then Exit;

  if not (Form1.IsBlackHoleActive or Form1.IsBurstActive) then
  begin
    { ๋งˆ์šฐ์Šค ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ }
    TargetX := Form1.MouseFollower.X + Form1.MouseFollower.Width div 2;
    TargetY := Form1.MouseFollower.Y + Form1.MouseFollower.Height div 2;

    { Follow์˜ ์ค‘์‹ฌ์  }
    DeltaX := TargetX - (X + Width div 2);
    DeltaY := TargetY - (Y + Height div 2);

    { ๋งˆ์šฐ์Šค์™€์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ }
    Distance := Sqrt(DeltaX * DeltaX + DeltaY * DeltaY);

    { ๋งˆ์šฐ์Šค์™€์˜ ๊ฑฐ๋ฆฌ๊ฐ€ 0์ด ์•„๋‹ ๋•Œ }
    if Distance > 0 then
    begin
      { ์†๋„ ์ง€์ • (๋ฐฉํ–ฅ๋„ ๊ฐ™์ด ์ง€์ •) }
      SpeedX := Round(DeltaX / Distance * CurSpeed);
      SpeedY := Round(DeltaY / Distance * CurSpeed);
    end
    else
    begin
      SpeedX := 0;
      SpeedY := 0;
    end;

    { ์ด๋™ }
    Inc(X, SpeedX);
    Inc(Y, SpeedY);

    { ์†๋„๋ฅผ ์ ์ง„์ ์œผ๋กœ ์ฆ๊ฐ€ }
    Inc(SpeedUpTimer);
    if SpeedUpTimer >= SpeedUpInterval then
    begin
      { 0.5์”ฉ ์ฆ๊ฐ€ }
      CurSpeed := CurSpeed + 0.5;

      { ์ตœ๋Œ€ ์†๋„ ์ œํ•œ }
      if CurSpeed > MaxSpeed then
        CurSpeed := MaxSpeed;

      SpeedUpTimer := 0;
    end;
  end

  { ๋ธ”๋ž™ํ™€ ๋ฐ ๋ฒ„์ŠคํŠธ ์ค‘์—๋Š” Base์—์„œ Move๋ฅผ ํ˜ธ์ถœํ•ด ๋Œ๋ ค๊ฐ€๊ฒŒ ํ•จ }
  else
  begin
    inherited Move;
  end;
end;

{ TObjMouse }
constructor TObjMouse.Create;
begin
  inherited Create;

  ObjType := ObjMouse;
  Width := 25;
  Height := 25;

  { ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ • }
  if Assigned(Form1) and Assigned(Form1.Panel1) then
  begin
    X := Form1.Panel1.Width div 2 - Width div 2;
    Y := Form1.Panel1.Height div 2 - Height div 2;
  end
  else
  begin
    { ๊ธฐ๋ณธ ๊ฐ’ }
    X := 200;
    Y := 150;
  end;
end;

procedure TObjMouse.Move;
begin
  // ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ๋Š” Move์—์„œ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ
  // ์œ„์น˜๋Š” SetPosition์œผ๋กœ๋งŒ ์„ค์ •๋จ
end;

{ ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ ์œ„์น˜ ์„ค์ • }
procedure TObjMouse.SetPosition(AX, AY: Integer);
begin
  { ๋งˆ์šฐ์Šค ์œ„์น˜๋กœ ์ด๋™ }
  X := AX - Width div 2;
  Y := AY - Height div 2;
end;

{ TForm1 }

{ ์ ์ˆ˜ ์—…๋ฐ์ดํŠธ }
procedure TForm1.UpdateScore;
begin
  Inc(ScoreTimer);
  { ์•ฝ 1์ดˆ๋งˆ๋‹ค ์ ์ˆ˜ ์ฆ๊ฐ€ }
  if ScoreTimer >= Round(1000 / Timer1.Interval) then
  begin
    Inc(Score);
    ScoreTimer := 0;
  end;
end;

{ ์˜ค๋ธŒ์ ํŠธ ์ถฉ๋Œ ์ฒดํฌ }
function TForm1.CheckCollision(obj1, obj2: TBaseObject): Boolean;
begin
  Result := (obj1.X < obj2.X + obj2.Width) and
            (obj1.X + obj1.Width > obj2.X) and
            (obj1.Y < obj2.Y + obj2.Height) and
            (obj1.Y + obj1.Height > obj2.Y);
end;

{ ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” }
procedure TForm1.ResetGame;
var
  i: Integer;
begin
  { ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ์˜ค๋ธŒ์ ํŠธ ์ œ๊ฑฐ }
  if Assigned(Objects) then
  begin
    for i := Objects.Count - 1 downto 0 do
    begin
      if TBaseObject(Objects[i]).ObjType <> ObjMouse then
      begin
        TObject(Objects[i]).Free;
        Objects.Delete(i);
      end;
    end;
  end;

  { ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ ์œ„์น˜ ์ดˆ๊ธฐํ™” }
  if Assigned(MouseFollower) then
  begin
    MouseFollower.X := Panel1.Width div 2 - MouseFollower.Width div 2;
    MouseFollower.Y := Panel1.Height div 2 - MouseFollower.Height div 2;
  end;

  { ์ดˆ๊ธฐ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ }
  CreateRandomObject;

  { ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” }
  GameOver := False;

  if Score > MaxScore then
    MaxScore := Score;

  Score := 1;
  ScoreTimer := 0;

  { ๋ธ”๋ž™ํ™€ ์ดˆ๊ธฐํ™” }
  IsBlackHoleActive := False;
  IsBurstActive := False;
  BlackHoleTimer := 0;
  BlackHoleSpawnTimer := 0;

  { ํƒ€์ด๋จธ ์ดˆ๊ธฐํ™” }
  Timer1.Enabled := True;
  TimerSpawn.Enabled := True;
end;

{ ์˜ค๋ธŒ์ ํŠธ ์†Œํ™˜ }
procedure TForm1.CreateRandomObject;
var
  obj: TBaseObject;
  ObjTypeIndex: Integer;
  i: Integer;
begin
  { ์˜ค๋ธŒ์ ํŠธ 3๊ฐœ ์†Œํ™˜ }
  for i := 0 to 2 do
  begin
    // ObjMouse์™€ ObjFollow๋ฅผ ์ œ์™ธํ•œ ํƒ€์ž…๋“ค ์ค‘ ์„ ํƒ (ObjA, ObjB, ObjC, ObjD)
    ObjTypeIndex := Random(Ord(ObjMouse));

    { ์˜ค๋ธŒ์ ํŠธ ์†Œํ™˜ }
    case TObjectType(ObjTypeIndex) of
      ObjA: obj := TObjA.Create;
      ObjB: obj := TObjB.Create;
      ObjC: obj := TObjC.Create;
      ObjD: obj := TObjD.Create;
    else
      obj := TObjA.Create;
    end;

    Objects.Add(obj);
  end;
end;

{ ObjFollow ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ }
procedure TForm1.CreateFollowObject;
var
  obj: TBaseObject;
begin
  obj := TObjFollow.Create;
  Objects.Add(obj);
end;

{ ObjFollow ์ œ๊ฑฐ }
procedure TForm1.RemoveFollowObjects;
var
  i: Integer;
  obj: TBaseObject;
begin
  { ์ธ๋ฑ์Šค๊ฐ€ ๊ผฌ์ด์ง€ ์•Š๊ฒŒ ์—ญ์ˆœ ์กฐํšŒ }
  for i := Objects.Count - 1 downto 0 do
  begin
    obj := TBaseObject(Objects[i]);

    { ObjFollow๋ฉด }
    if obj.ObjType = ObjFollow then
    begin
      { ์ œ๊ฑฐ }
      Objects.Delete(i);
      obj.Free;
    end;
  end;
end;

{ ๋ธ”๋ž™ํ™€ ํ™œ์„ฑํ™” ํ•จ์ˆ˜ }
procedure TForm1.ActivateBlackHole(const APoint: TPoint; ATriggeredObj: TObjFollow = nil);
begin
  if not IsBlackHoleActive then
  begin
    { ๋ธ”๋ž™ํ™€ ์ƒ์„ฑ }
    IsBlackHoleActive := True;

    BlackHoleTimer := 0;
    BlackHoleCenter := APoint;
  end;
end;

{ ๋ธ”๋ž™ํ™€ ๋ฐ ํญ๋ฐœ ํšจ๊ณผ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜ }
procedure TForm1.UpdateBlackHoleEffect;
begin
  { ๋ธ”๋ž™ํ™€ ์ƒ์„ฑ ์‹œ }
  if IsBlackHoleActive then
  begin
    Inc(BlackHoleTimer);

    { ๋ธ”๋ž™ํ™€ ์ง„ํ–‰ ์ข…๋ฃŒ }
    if BlackHoleTimer >= BlackHoleDuration then
    begin
      { ๋ฒ„์ŠคํŠธ ์‹œ์ž‘ }
      IsBlackHoleActive := False;
      IsBurstActive := True;
      BlackHoleTimer := 0;
    end;
  end;

  { ๋ฒ„์ŠคํŠธ ์‹œ์ž‘ ์‹œ }
  if IsBurstActive then
  begin
    Inc(BlackHoleTimer);

    { ๋ฒ„์ŠคํŠธ ์ง„ํ–‰ ์ข…๋ฃŒ }
    if BlackHoleTimer >= BurstDuration then
    begin
      IsBurstActive := False;
      BlackHoleTimer := 0;

      RemoveFollowObjects;
    end;
  end;
end;

{ ๋ธ”๋ž™ํ™€ ์ดํŽ™ํŠธ ๊ทธ๋ฆฌ๊ธฐ }
procedure TForm1.DrawBlackHoleEffect;
var
  EffectRadius: Integer;
begin
  if IsBlackHoleActive then
  begin
    { ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ดํŽ™ํŠธ ํฌ๊ธฐ ๋ณ€๊ฒฝ }
    EffectRadius := Round(5 + (BlackHoleTimer / BlackHoleDuration) * 75); // 5 ~ 75

    { ๋ธ”๋ž™ํ™€ ๊ทธ๋ฆฌ๊ธฐ }
    PaintBox1.Canvas.Pen.Color := clPurple;
    PaintBox1.Canvas.Brush.Color := clBlack;

    PaintBox1.Canvas.Ellipse(
      BlackHoleCenter.X - EffectRadius,
      BlackHoleCenter.Y - EffectRadius,
      BlackHoleCenter.X + EffectRadius,
      BlackHoleCenter.Y + EffectRadius
    );
  end;
end;

{ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ }
procedure TForm1.FormCreate(Sender: TObject);
begin
  { ๊ฒŒ์ž„์— ์“ฐ์ด๋Š” ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™” }
  Objects := TList.Create;

  GameOver := False;

  Score := 1;
  ScoreTimer := 0;

  Randomize;

  { ๊ฒŒ์ž„ ํƒ€์ด๋จธ }
  Timer1.Interval := 30; // 30ms = ์•ฝ 33 FPS
  Timer1.Enabled := True;

  { Spawn ํƒ€์ด๋จธ }
  TimerSpawn.Interval := 10000; // 10์ดˆ
  TimerSpawn.Enabled := True;

  { ๋ธ”๋ž™ํ™€ ๋ฐ ํญ๋ฐœ ๊ด€๋ จ ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™” }
  IsBlackHoleActive := False;
  IsBurstActive := False;
  BlackHoleTimer := 0;

  { ๋ธ”๋ž™ํ™€ ๋ฐ ๋ฒ„์ŠคํŠธ ํšจ๊ณผ }
  BlackHoleDuration := Round(2000 / Timer1.Interval); // 2์ดˆ
  BurstDuration := Round(1000 / Timer1.Interval);     // 1์ดˆ

  { ๋ธ”๋ž™ํ™€ ์ƒ์„ฑ ์‹œ๊ฐ„ (25์ดˆ๋งˆ๋‹ค) }
  BlackHoleSpawnInterval := Round(25000 / Timer1.Interval);
  BlackHoleSpawnTimer := 0;

  { ์œˆ๋„์šฐ ์„ค์ • }
  WindowState := wsMaximized; // ์ „์ฒดํ™”๋ฉด
  Cursor := crNone;           // ๋งˆ์šฐ์Šค ์ปค์„œ ์ˆจ๊น€
  PaintBox1.Cursor := crNone; // PaintBox์—์„œ๋„ ์ปค์„œ ์ˆจ๊น€
  DoubleBuffered := True;     // ๊นœ๋นก์ž„ ๋ฐฉ์ง€

  { ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ }
  MouseFollower := TObjMouse.Create;
  Objects.Add(MouseFollower);

  { ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ }
  CreateRandomObject;
end;

{ ํ”„๋กœ์ ํŠธ ์ข…๋ฃŒ ์‹œ }
procedure TForm1.FormDestroy(Sender: TObject);
var
  i: Integer;
begin
  { List ํ•ด์ œ }
  if Assigned(Objects) then
  begin
    for i := 0 to Objects.Count - 1 do
      TObject(Objects[i]).Free;
    Objects.Free;
  end;
end;

{ ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ผ ๋•Œ๋งˆ๋‹ค }
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  { ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ ์œ„์น˜ ์ดˆ๊ธฐํ™” }
  if Assigned(MouseFollower) then
    MouseFollower.SetPosition(X, Y);
end;

{ Spawn ํƒ€์ด๋จธ }
procedure TForm1.TimerSpawnTimer(Sender: TObject);
begin
  if not GameOver then
  begin
    CreateRandomObject;
    CreateFollowObject;
  end;
end;

{ ํƒ€์ด๋จธ ์ด๋ฒคํŠธ }
procedure TForm1.Timer1Timer(Sender: TObject);
var
  i, j: Integer;
  obj: TBaseObject;
  obj1, obj2: TBaseObject;
begin
  if Objects = nil then Exit;
  if GameOver then Exit;

  { ์ ์ˆ˜ ์—…๋ฐ์ดํŠธ }
  UpdateScore;

  { ๋ธ”๋ž™ํ™€ ๋ฐœ๋™ }
  Inc(BlackHoleSpawnTimer);
  if (not IsBlackHoleActive) and (BlackHoleSpawnTimer >= BlackHoleSpawnInterval) then
  begin
    { ํ™”๋ฉด ์ค‘์•™์— ๋ธ”๋ž™ํ™€ ์ƒ์„ฑ }
    ActivateBlackHole(Point(Panel1.Width div 2, Panel1.Height div 2));
    BlackHoleSpawnTimer := 0;
  end;

  { ๋ธ”๋ž™ํ™€ ์ดํŽ™ํŠธ ๋ฐœ๋™ }
  UpdateBlackHoleEffect;

  { ๋ฐฐ๊ฒฝ ์ดˆ๊ธฐํ™” }
  PaintBox1.Canvas.Brush.Color := clBlack;
  PaintBox1.Canvas.FillRect(Rect(0, 0, Panel1.Width, Panel1.Height));

  { ํ™”๋ฉด์— ์žˆ๋Š” ๋ชจ๋“  ์˜ค๋ธŒ์ ํŠธ ์ด๋™ }
  for i := 0 to Objects.Count - 1 do
  begin
    obj := TBaseObject(Objects[i]);
    obj.Move; // TBaseObject.Move์—์„œ ๋ธ”๋ž™ํ™€/๋ฒ„์ŠคํŠธ ๋กœ์ง์„ ์ฒ˜๋ฆฌ
  end;

  // ์ฃฝ์€ ์˜ค๋ธŒ์ ํŠธ ์‚ญ์ œ ์ฒ˜๋ฆฌ: ์—ญ์ˆœ์œผ๋กœ ์ˆœํšŒํ•˜๋ฉฐ IsDead๊ฐ€ True์ธ ๊ฐ์ฒด ์‚ญ์ œ
  for i := Objects.Count - 1 downto 0 do
  begin
    obj := TBaseObject(Objects[i]);
    if obj.IsDead then
    begin

      Objects.Delete(i);
      obj.Free;
    end;
  end;

  { ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ œ์™ธํ•œ ๋‹ค๋ฅธ ์˜ค๋ธŒ์ ํŠธ๋ผ๋ฆฌ์˜ ์ถฉ๋Œ }
  if not (IsBlackHoleActive or IsBurstActive) then
  begin
    for i := 0 to Objects.Count - 2 do
    begin
      for j := i + 1 to Objects.Count - 1 do
      begin
        Obj1 := TBaseObject(Objects[i]);
        Obj2 := TBaseObject(Objects[j]);

        // ObjD, ObjFollow, ObjMouse๋Š” ์ถฉ๋Œ ๋ฌด์‹œ
        if (Obj1.ObjType = ObjD) or (Obj2.ObjType = ObjD) or
            (Obj1.ObjType = ObjFollow) or (Obj2.ObjType = ObjFollow) or
            (Obj1.ObjType = ObjMouse) or (Obj2.ObjType = ObjMouse) then
          Continue;

        { ์ถฉ๋Œํ•œ ์˜ค๋ธŒ์ ํŠธ๋งŒ ์„ ํƒ (ObjA, ObjB, ObjC ๊ฐ„์˜ ์ถฉ๋Œ๋งŒ) }
        if CheckCollision(Obj1, Obj2) then
        begin
          { ์–‘์ชฝ ๋ชจ๋‘ ํšŒ์ „ }
          Obj1.SpeedX := -Obj1.SpeedX;
          Obj1.SpeedY := -Obj1.SpeedY;

          Obj2.SpeedX := -Obj2.SpeedX;
          Obj2.SpeedY := -Obj2.SpeedY;

          { ์„œ๋กœ๋ฅผ ์‚ด์ง์‹ ๋ฐ€์–ด์„œ ๊ฒน์น˜๋Š” ์ƒํ™ฉ ๋ฐฉ์ง€ }
          Obj1.X := Obj1.X + Obj1.SpeedX;
          Obj1.Y := Obj1.Y + Obj1.SpeedY;
          Obj2.X := Obj2.X + Obj2.SpeedX;
          Obj2.Y := Obj2.Y + Obj2.SpeedY;
        end;
      end;
    end;
  end;

  { ๋งˆ์šฐ์Šค ์˜ค๋ธŒ์ ํŠธ์™€ ๋‹ค๋ฅธ ์˜ค๋ธŒ์ ํŠธ๋“ค ๊ฐ„์˜ ์ถฉ๋Œ ์ฒ˜๋ฆฌ (ObjD, ObjFollow ํฌํ•จ) }
  if Assigned(MouseFollower) then
  begin
    for i := 0 to Objects.Count - 1 do
    begin
      obj := TBaseObject(Objects[i]);
      if (obj.ObjType <> ObjMouse) then
      begin
        if CheckCollision(MouseFollower, obj) then
        begin
          GameOver := True;
          Timer1.Enabled := False;
          TimerSpawn.Enabled := False;

          ShowMessage('๊ฒŒ์ž„ ์˜ค๋ฒ„!' + sLineBreak + '์ตœ์ข… ์ ์ˆ˜: ' + IntToStr(Score) + sLineBreak + '๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ํ™•์ธ์„ ๋ˆ„๋ฅด์„ธ์š”.');
          ResetGame;
          Exit;
        end;
      end;
    end;
  end;

  { ํ™”๋ฉด์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ์˜ค๋ธŒ์ ํŠธ ๋Œ€์ƒ }
  for i := 0 to Objects.Count - 1 do
  begin
    obj := TBaseObject(Objects[i]);

    { ์˜ค๋ธŒ์ ํŠธ ๋ณ„ ์ƒ‰์ƒ ์ง€์ • }
    case obj.ObjType of
      ObjA: PaintBox1.Canvas.Brush.Color := clRed;
      ObjB: PaintBox1.Canvas.Brush.Color := clGreen;
      ObjC: PaintBox1.Canvas.Brush.Color := clBlue;
      ObjD: PaintBox1.Canvas.Brush.Color := clYellow;
      ObjFollow: PaintBox1.Canvas.Brush.Color := clLime; // ObjFollow ์ƒ‰์ƒ
      ObjMouse: PaintBox1.Canvas.Brush.Color := clWhite;  // ๋งˆ์šฐ์Šค ๋”ฐ๋ผ์˜ค๋Š” ํฐ์ƒ‰ ์˜ค๋ธŒ์ ํŠธ
    else
      PaintBox1.Canvas.Brush.Color := clBlack;
    end;

    { ์˜ค๋ธŒ์ ํŠธ ๊ทธ๋ฆฌ๊ธฐ }
    PaintBox1.Canvas.FillRect(Rect(obj.X, obj.Y, obj.X + obj.Width, obj.Y + obj.Height));
  end;

  { ๋ธ”๋ž™ํ™€ ์ดํŽ™ํŠธ ๊ทธ๋ฆฌ๊ธฐ }
  DrawBlackHoleEffect;

  { ์ ์ˆ˜ ํ‘œ์‹œ }
  PaintBox1.Canvas.Font.Color := clWhite;
  PaintBox1.Canvas.Font.Size := 20;
  PaintBox1.Canvas.Font.Style := [fsBold];
  PaintBox1.Canvas.Brush.Style := bsClear; // ํˆฌ๋ช… ๋ฐฐ๊ฒฝ
  PaintBox1.Canvas.TextOut(20, 20, '์ ์ˆ˜: ' + IntToStr(Score));
  PaintBox1.Canvas.Brush.Style := bsSolid; // ๋‹ค์‹œ ์ฑ„์šฐ๊ธฐ ๋ชจ๋“œ๋กœ ๋ณต์›

  PaintBox1.Canvas.Font.Color := clWhite;
  PaintBox1.Canvas.Font.Size := 20;
  PaintBox1.Canvas.Font.Style := [fsBold];
  PaintBox1.Canvas.Brush.Style := bsClear; // ํˆฌ๋ช… ๋ฐฐ๊ฒฝ
  PaintBox1.Canvas.TextOut(20, 50, '์ตœ๊ณ  ์ ์ˆ˜: ' + IntToStr(MaxScore));
  PaintBox1.Canvas.Brush.Style := bsSolid; // ๋‹ค์‹œ ์ฑ„์šฐ๊ธฐ ๋ชจ๋“œ๋กœ ๋ณต์›
end;

end.2

</aside>