unit uGameTypes;

interface

uses  QuadEngine,
      uSMath,
      uCommonGameTypes,
      uEnemies;

type
  cPlayer = class
    private
    protected
      avRect    : rRect;
      Pos       : rVec2f;
      SPos      : rVec2f;

      Health    : Word;
      MaxHealth : Word;

      _Score          : LongInt;
      Weapon          : ePlayerWeaponType;
      nrmBullet       : rBulletInitParam;
      advBullet       : rBulletInitParam;
      hvyBullet       : rBulletInitParam;
      lastShootTime   : Double;
      lastShootTime_2 : Double;

      stStartMove   : Boolean;
      stDead        : Boolean;
      stDeadMove    : Boolean;
      startTime     : Double;

      sprite    : IQuadTexture;
      frame     : Word;
      lastFUpdt : Double;

      kUpPress    : Boolean;
      kDownPress  : Boolean;
      kLeftPress  : Boolean;
      kRightPress : Boolean;
      kAPress : Boolean;
      kSPress  : Boolean;

      theGame   : TObject;
      deadPosS  : rVec2f;
      deadPosF  : rVec2f;

      procedure Turn( left: Boolean; dt: Double );
      procedure Go( up: Boolean; dt: Double );
      procedure Shoot( altrShoot: Boolean; currentTime: Double );
    public
      constructor Create;
      destructor  Free;

      procedure Init( param: pPlayerInitParam );
      procedure Draw;
      procedure Update( dt: Double; currentTime: Double );
      procedure UpdateFrames( dt: Double; currentTime: Double );

      function  CollideVsBullet( bullet: pBullet ): Boolean;
      function  CollideVsEnemy( enemy: cEnemy ): Boolean;
      procedure GetDamage( dam: Word );
      procedure AddScore( points: LongInt );

      procedure OnKeyDown( key: Byte );
      procedure OnKeyUp( key: Byte );

      property  Position  : rVec2f  read Pos;
      property  Dead      : Boolean read stDead;
      property  DeadMove  : Boolean read stDeadMove;
      property  Score     : LongInt read _Score;
  end;

  cGame = class
    private
    protected
      Player  : cPlayer;

      rules       : array of rRuleEnemyWave;
      currentRule : Word;
      fstRule     : Word;
      lastWaveEnd : Double;

      Enemies : array of cEnemy;
      EnemiesNum    : Word;
      EnemiesMaxNum : Word;

      bulletHead  : pBullet;
      bulletTail  : pBullet;
      avBulRec    : rRect;

      explosionHead : pExplosion;
      explosionTail : pExplosion;

      bossAlive : Boolean;
      bossNum   : Word;
    public
      constructor Create;
      destructor  Destroy;

      procedure Init( param: Pointer = nil );
      procedure Draw;
      procedure Update( dt, currentTime : Double );
      procedure UpdateFrames( dt, currentTime : Double );

      procedure OnKeyDown( key: Byte );
      procedure OnKeyUp( key: Byte );

      procedure AddBullet( const param: pBulletInitParam; pos: rVec2f; enmBul: Boolean; curTime: Double );
      procedure AddExplosion( const param: pExplosionInitParam; pos: rVec2f; curTime: Double );

      function  GetScore(): LongInt;
      function  GetPlayerLife(): Word;
      function  GetPlayerPosition(): rVec2f;
      function  GetGameOverStatus(): Boolean;
      function  GetBossHealth(): Word;

      property  Boss      : Boolean read bossAlive;
  end;

implementation

uses  Windows,
      direct3d9,
      uUtils,
      uVar,
      uConst;

const
  PLAYER_START_MOVE_TIME  = 2.500;
  PLAYER_DEAD_MOVE_TIME   = 3.500;
  PLAYER_FRAME_UPDT_TIME  = 0.025;
  PLAYER_MAX_FRAME        = 21;
  PLAYER_STEP_H           : rVec2f = ( X: 220; Y: 0 );
  PLAYER_STEP_V           : rVec2f = ( X: 0; Y: 330 );
  PLAYER_SHOOT_DT         = 0.15;

////////////////////////////////////////////////////////////////////////////////
///  cPlayer  //////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
{$REGION '  cPlayer  '}
constructor cPlayer.Create;
begin
  avRect  := Rect( Vec2fZero, Vec2fZero );
  Pos     := Vec2f( -AppScrW, -AppScrH );
  SPos    := Vec2fZero;

  Health    := 0;
  MaxHealth := 0;

  _Score := 0;

  Weapon := wNormal;

  stStartMove := False;
  stDead      := False;
  stDeadMove  := False;

  sprite    := Player;
  frame     := 0;
  lastFUpdt := 0;

  kUpPress    := False;
  kDownPress  := False;
  kLeftPress  := False;
  kRightPress := False;
  kAPress := False;
  kSPress  := False;

  nrmBullet.shape   := sCircle;
  nrmBullet.circle  := Circle( Vec2f( 0, 0 ), 7 );
  nrmBullet.sprite  := Bullet_1;
  nrmBullet.frame   := 0;
  nrmBullet.maxFrame:= 7;
  nrmBullet.frameDT := 0.2;
  nrmBullet.speed   := Vec2f( 400, 0 );
  nrmBullet.damage  := 1;

  advBullet.shape   := sCircle;
  advBullet.circle  := Circle( Vec2f( 0, 0 ), 7 );
  advBullet.sprite  := Bullet_4;
  advBullet.frame   := 0;
  advBullet.maxFrame:= 7;
  advBullet.frameDT := 0.2;
  advBullet.speed   := Vec2f( 0, 0 );
  advBullet.damage  := 2;

  lastShootTime   := 0;
  lastShootTime_2 := 0;
end;

destructor cPlayer.Free;
begin

end;

procedure cPlayer.Init(param: pPlayerInitParam);
begin
  avRect    := param.rect;
  SPos      := param.sposition;
  Health    := param.health;
  MaxHealth := param.maxHealth;
  startTime := param.time;
  theGame   := param.game;

  stStartMove := True;
end;

procedure cPlayer.Draw;
begin
  sprite.DrawRot( Pos.X, Pos.Y, 90, 1.0, $FFFFFFFF, frame );
end;

procedure cPlayer.Update(dt: Double; currentTime: Double);
begin
  if stStartMove then begin
    Pos := SPos.Lerp( avRect.Center - Vec2f( avRect.Width * 0.35, 0 ),
                      ( currentTime - startTime ) / PLAYER_START_MOVE_TIME );
    if currentTime - startTime >= PLAYER_START_MOVE_TIME then begin
      stStartMove := False;
      Pos := avRect.Center - Vec2f( avRect.Width * 0.35, 0 );
    end;
  end;

  if stDeadMove then begin
    Pos := deadPosS.Lerp( deadPosF, ( currentTime - startTime ) / PLAYER_DEAD_MOVE_TIME );
    if currentTime - startTime >= PLAYER_START_MOVE_TIME then begin
      stDeadMove := False;
      Pos := deadPosF;
    end;
  end;

  if stStartMove then Exit;
  if stDead then Exit;

  if kAPress then begin
    if currentTime - lastShootTime >= PLAYER_SHOOT_DT then begin
      lastShootTime := currentTime;
      Shoot( False, currentTime );
    end;
  end;

  if kSPress then begin
    if currentTime - lastShootTime_2 >= 4.5 * PLAYER_SHOOT_DT then begin
      lastShootTime_2 := currentTime;
      Shoot( True, currentTime );
    end;
  end;

  if kLeftPress then  Turn( True,   dt );
  if kRightPress then Turn( False,  dt );
  if kUpPress then    Go  ( True,   dt );
  if kDownPress then  Go  ( False,  dt );
end;

procedure cPlayer.UpdateFrames(dt: Double; currentTime: Double);
begin
  if currentTime - lastFUpdt >= PLAYER_FRAME_UPDT_TIME then begin
    Inc( frame );
    lastFUpdt := currentTime;
    if frame > PLAYER_MAX_FRAME then frame := 0;
  end;
end;

function  cPlayer.CollideVsBullet(bullet: pBullet): Boolean;
var A, B : rVec2f;
begin
  Result := False;
  if bullet.EnemyBullet then begin
    A := Pos - Vec2f( 30, 40.5 );
    B := A + Vec2f( 60, 81 );
    Result := Result or bullet.CollideWithRect( Rect( A, B ) );

    A := Pos - Vec2f( 43, 21.5 );
    B := A + Vec2f( 86, 43 );
    Result := Result or bullet.CollideWithRect( Rect( A, B ) );
  end;
end;

function  cPlayer.CollideVsEnemy(enemy: cEnemy): Boolean;
var A, B : rVec2f;
begin
  A := Pos - Vec2f( 30, 40.5 );
  B := A + Vec2f( 60, 81 );
  Result := enemy.CollideVsRect( Rect( A, B ) );

  A := Pos - Vec2f( 43, 21.5 );
  B := A + Vec2f( 86, 43 );
  Result := Result or enemy.CollideVsRect( Rect( A, B ) );
end;

procedure cPlayer.GetDamage(dam: Word);
begin
  if dam < Health then begin
    Health := Health - dam;
  end else begin
    Health    := 0;
    stDead    := True;
    stDeadMove:= True;
    deadPosS  := Pos;
    deadPosF  := Pos - Vec2f( avRect.Width * 1.5, -AppScrH * 0.5 );
    startTime := Timer.GetWholeTime;
  end;
end;

procedure cPlayer.AddScore(points: Integer);
begin
  _score := _score + points;
end;

procedure cPlayer.OnKeyDown(key: Byte);
begin
  case key of
    K_LEFT: begin
      if not kLeftPress then begin
        kLeftPress := True;
      end;
    end;
    K_UP: begin
      if not kUpPress then begin
        kUpPress := True;
      end;
    end;
    K_RIGHT: begin
      if not kRightPress then begin
        kRightPress := True;
      end;
    end;
    K_DOWN: begin
      if not kDownPress then begin
        kDownPress := True;
      end;
    end;
    K_A: begin
      if not kAPress then begin
        kAPress := True;
      end;
    end;
    K_S: begin
      if not kSPress then begin
        kSPress := True;
      end;
    end;
  end;
end;

procedure cPlayer.OnKeyUp(key: Byte);
begin
  case key of
    K_LEFT: begin
      kLeftPress := False;
    end;
    K_UP: begin
      kUpPress := False;
    end;
    K_RIGHT: begin
      kRightPress := False;
    end;
    K_DOWN: begin
      kDownPress := False;
    end;
    K_A: begin
      kAPress := False;
    end;
    K_S: begin
      kSPress := False;
    end;
  end;

end;

procedure cPlayer.Turn(left: Boolean; dt: Double);
var NPos : rVec2f;
begin
  if left then begin
    NPos := Pos - PLAYER_STEP_H * dt;
    if pointInRect( NPos, avRect ) then begin
      Pos := NPos;
    end else begin
      Pos.X := avRect.X;
    end;
  end else begin
    NPos := Pos + PLAYER_STEP_H * dt;
    if pointInRect( NPos, avRect ) then begin
      Pos := NPos;
    end else begin
      Pos.X := avRect.X + avRect.Width;
    end;
  end;
end;

procedure cPlayer.Go(up: Boolean; dt: Double );
var NPos : rVec2f;
begin
  if up then begin
    NPos := Pos - PLAYER_STEP_V * dt;
    if pointInRect( NPos, avRect ) then begin
      Pos := NPos;
    end else begin
      Pos.Y := avRect.Y;
    end;
  end else begin
    NPos := Pos + PLAYER_STEP_V * dt;
    if pointInRect( NPos, avRect ) then begin
      Pos := NPos;
    end else begin
      Pos.Y := avRect.Y + avRect.Height;
    end;
  end;
end;

procedure cPlayer.Shoot( altrShoot: Boolean; currentTime: Double );
var BPos  : rVec2f;
    Spd   : rVec2f;
begin
  if stStartMove then Exit;
  if not altrShoot then begin
    BPos := Vec2f( 35, -2 ) + Pos;
    ( theGame as cGame).AddBullet( @nrmBullet, BPos, False, currentTime );
  end else begin
    BPos  := Vec2f( 15, -24 ) + Pos;
    Spd   := Vec2f( 400, 0 );
    advBullet.speed := Spd.Rotate( 18 * DegToRad );
    ( theGame as cGame).AddBullet( @advBullet, BPos, False, currentTime );

    BPos := Vec2f( 35, -2 ) + Pos;
    Spd   := Vec2f( 400, 0 );
    advBullet.speed := Spd;
    ( theGame as cGame).AddBullet( @advBullet, BPos, False, currentTime );

    BPos := Vec2f( 15, 22 ) + Pos;
    Spd   := Vec2f( 400, 0 );
    advBullet.speed := Spd.Rotate( -18 * DegToRad );
    ( theGame as cGame).AddBullet( @advBullet, BPos, False, currentTime );
  end;
end;
{$ENDREGION}
////////////////////////////////////////////////////////////////////////////////
///  cGame  ////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
{$REGION '  cGame  '}
constructor cGame.Create;
begin
  EnemiesMaxNum := 4;
  EnemiesNum    := 0;
  SetLength( Enemies, EnemiesMaxNum );

  Player := cPlayer.Create;

  bulletHead := nil;
  bulletTail := nil;

  explosionHead := nil;
  explosionTail := nil;

  SetLength( rules, 0 );
  currentRule := 0;

  bossAlive := False;
  bossNum   := 0;
end;

destructor cGame.Destroy;
var i   : Integer;
    buf : pBullet;
    del : pBullet;
begin
  for i := 0 to EnemiesNum - 1 do begin
    Enemies[ i ].Free;
  end;
  SetLength( Enemies, 0 );

  Player.Free;

  SetLength( rules, 0 );

  buf := bulletHead;
  while buf <> nil do begin
    del := buf;
    buf := buf.NextBullet;
    del.Delete( bulletHead, bulletTail );
  end;                                   
end;

procedure cGame.Init(param: Pointer);
var playerInitParam : rPlayerInitParam;
    enemyInitParam  : rEnemyInitParam;
    num : Integer;
begin
  avBulRec := Rect( Vec2f( -32, -32 ), AppScrResf + Vec2f( 32, 32 ) );

  playerInitParam.rect      := Rect( Vec2f( 35, 45 ), Vec2f( 350, AppScrH - 45 ) );
  playerInitParam.sposition := playerInitParam.rect.Center - Vec2f( playerInitParam.rect.Width, 0 );
  playerInitParam.health    := 100;
  playerInitParam.maxHealth := 100;
  playerInitParam.time      := Timer.GetWholeTime;
  playerInitParam.game      := Self;

  Player.Init( @playerInitParam );

  //////////////////////////////////////////////////////////////////////////////
  ///  enemy
  SetLength( rules, 3 );
  currentRule := 0;
  fstRule     := 0;
  {$REGION '  Wave 1  '}
  SetLength( rules[ currentRule ].enemyType, 24 );
  SetLength( rules[ currentRule ].addParam, 24 );
  SetLength( rules[ currentRule ].addDT, 24 );
  rules[ currentRule ].enemyNum   := 24;
  rules[ currentRule ].current    := 0;
  rules[ currentRule ].lastAdd    := 0;
  rules[ currentRule ].waitWaveDT := 5000;
  //0
  num := 0;
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 0;
  //1
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //2
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 12;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //3
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 13;
  rules[ currentRule ].addDT    [ num ] := 0;
  //4
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //5
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //6
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 12;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //7
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 13;
  rules[ currentRule ].addDT    [ num ] := 0;
  //8
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 2500;
  //9
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //10
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //11
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //12
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 4000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 4;
  rules[ currentRule ].addDT    [ num ] := 4000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 5;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 6;
  rules[ currentRule ].addDT    [ num ] := 1000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 7;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 4;
  rules[ currentRule ].addDT    [ num ] := 1000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 5;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  {$ENDREGION}
  //
  {$REGION '  Wave 2  '}
  Inc( currentRule );
  SetLength( rules[ currentRule ].enemyType, 23 );
  SetLength( rules[ currentRule ].addParam, 23 );
  SetLength( rules[ currentRule ].addDT, 23 );
  rules[ currentRule ].enemyNum   := 23;
  rules[ currentRule ].current    := 0;
  rules[ currentRule ].lastAdd    := 0;
  rules[ currentRule ].waitWaveDT := 8000;
  //
  //0
  num := 0;
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 0;
  //1
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //2
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 1000;
  //3
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //4
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 1000;
  //5
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //6
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 4;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 6000;
  //7
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 6000;
  //8
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 3;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //9
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //10
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //11
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //12
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //13
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 3000;
  //14
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 12;
  rules[ currentRule ].addDT    [ num ] := 8000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 1;
  rules[ currentRule ].addParam [ num ] := 13;
  rules[ currentRule ].addDT    [ num ] := 0;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 5000;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 1;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  inc( num );
  rules[ currentRule ].enemyType[ num ] := 2;
  rules[ currentRule ].addParam [ num ] := 2;
  rules[ currentRule ].addDT    [ num ] := 2200;
  //
  {$ENDREGION}
  //
  {$REGION '  Wave 3  '}
  Inc( currentRule );
  SetLength( rules[ currentRule ].enemyType, 14 );
  SetLength( rules[ currentRule ].addParam, 14 );
  SetLength( rules[ currentRule ].addDT, 14 );
  rules[ currentRule ].enemyNum   := 14;
  rules[ currentRule ].current    := 0;
  rules[ currentRule ].lastAdd    := 0;
  rules[ currentRule ].waitWaveDT := 10000;
  //
  rules[ currentRule ].enemyType[ 0 ] := 1;
  rules[ currentRule ].addParam[ 0 ]  := 6;
  rules[ currentRule ].addDT[ 0 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 1 ] := 1;
  rules[ currentRule ].addParam[ 1 ]  := 7;
  rules[ currentRule ].addDT[ 1 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 2 ] := 1;
  rules[ currentRule ].addParam[ 2 ]  := 4;
  rules[ currentRule ].addDT[ 2 ]     := 1200;
  //
  rules[ currentRule ].enemyType[ 3 ] := 1;
  rules[ currentRule ].addParam[ 3 ]  := 5;
  rules[ currentRule ].addDT[ 3 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 4 ] := 1;
  rules[ currentRule ].addParam[ 4 ]  := 6;
  rules[ currentRule ].addDT[ 4 ]     := 1200;
  //
  rules[ currentRule ].enemyType[ 5 ] := 1;
  rules[ currentRule ].addParam[ 5 ]  := 7;
  rules[ currentRule ].addDT[ 5 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 6 ] := 1;
  rules[ currentRule ].addParam[ 6 ]  := 4;
  rules[ currentRule ].addDT[ 6 ]     := 1200;
  //
  rules[ currentRule ].enemyType[ 7 ] := 1;
  rules[ currentRule ].addParam[ 7 ]  := 5;
  rules[ currentRule ].addDT[ 7 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 8 ] := 1;
  rules[ currentRule ].addParam[ 8 ]  := 6;
  rules[ currentRule ].addDT[ 8 ]     := 1200;
  //
  rules[ currentRule ].enemyType[ 9 ] := 1;
  rules[ currentRule ].addParam[ 9 ]  := 7;
  rules[ currentRule ].addDT[ 9 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 10 ] := 1;
  rules[ currentRule ].addParam[ 10 ]  := 1;
  rules[ currentRule ].addDT[ 10 ]     := 2800;
  //
  rules[ currentRule ].enemyType[ 11 ] := 1;
  rules[ currentRule ].addParam[ 11 ]  := 2;
  rules[ currentRule ].addDT[ 11 ]     := 0;
  //
  rules[ currentRule ].enemyType[ 12 ] := 1;
  rules[ currentRule ].addParam[ 12 ]  := 1;
  rules[ currentRule ].addDT[ 12 ]     := 2800;
  //
  rules[ currentRule ].enemyType[ 13 ] := 1;
  rules[ currentRule ].addParam[ 13 ]  := 2;
  rules[ currentRule ].addDT[ 13 ]     := 0;
  {$ENDREGION}
  //

  currentRule := 0;
  lastWaveEnd := Timer.GetWholeTime;

  Inc( EnemiesNum );
  enemyInitParam.game := Self;
  enemyInitParam.moveType := 3;
  Enemies[ EnemiesNum - 1 ] := cEnemy_1.Create;
  Enemies[ EnemiesNum - 1 ].Init( @enemyInitParam );
end;

procedure cGame.Draw;
var i   : Integer;
    buf : pBullet;
    ebuf: pExplosion;
begin
  //EnterCriticalSection( Section_FrameUpdt );
  Quad.SetTextureFiltering( D3DTEXF_POINT );
    Quad.SetBlendMode( qbmSrcAlphaAdd );
    buf := bulletHead;
    while buf <> nil do begin
      buf.Draw;
      buf := buf.NextBullet;
    end;

    Quad.SetBlendMode( qbmSrcAlpha );
    Player.Draw;

    for i := 0 to EnemiesNum - 1 do begin
      //Quad.SetBlendMode( qbmSrcAlpha );
      Enemies[ i ].Draw( 1 );
    end;
    Quad.SetBlendMode( qbmSrcAlphaAdd );
    for i := 0 to EnemiesNum - 1 do begin
      Enemies[ i ].Draw( 2 );
    end;

    Quad.SetBlendMode( qbmSrcAlphaAdd );
    ebuf := explosionHead;
    while ebuf <> nil do begin
      ebuf.Draw;
      ebuf := ebuf.NextExplosion;
    end;
  Quad.SetTextureFiltering( D3DTEXF_LINEAR );
  //LeaveCriticalSection( Section_FrameUpdt );
end;

procedure cGame.Update(dt: Double; currentTime: Double);
var i, j  : Integer;
    buf   : pBullet;
    del   : pBullet;
    ebuf  : pExplosion;
    edel  : pExplosion;
    param : rEnemyInitParam;
    eparam: rExplosionInitParam;
begin
  ///update
  {$REGION '  UPDATE  '}
  Player.Update( dt, currentTime );

  for i := 0 to EnemiesNum - 1 do begin
    Enemies[ i ].Update( dt, currentTime );
  end;

  buf := bulletHead;
  while buf <> nil do begin
    buf.Update( dt, currentTime );
    buf := buf.NextBullet;
  end;

  ebuf := explosionHead;
  while ebuf <> nil do begin
    ebuf.Update( dt, currentTime );
    ebuf := ebuf.NextExplosion;
  end;
  {$ENDREGION}

  ///collide
  {$REGION '  COLLIDE  '}
  buf := bulletHead;
  while buf <> nil do begin
    if not buf.EnemyBullet then begin
      for i := 0 to EnemiesNum - 1 do begin
        if Enemies[ i ].CollideVsBullet( buf ) then begin
          buf.Dead := True;
          Enemies[ i ].GetDamage( buf.Damage );
          eparam.sprite   := Explosion_9;
          eparam.frame    := 1;
          eparam.maxFrame := 11;
          eparam.frameDT  := 0.03;
          eparam.speed    := Vec2f( 100, 0 );
          AddExplosion( @eparam, buf.Position, currentTime );
          if Enemies[ i ].Dead then begin
            Player.AddScore( Enemies[ i ].Points );
          end;
        end;
      end;
    end else begin
      if Player.CollideVsBullet( buf ) then begin
        buf.Dead := True;
        Player.GetDamage( buf.Damage );

        eparam.sprite   := Explosion_7;
        eparam.frame    := 0;
        eparam.maxFrame := 53;
        eparam.frameDT  := 0.02;
        eparam.speed    := Vec2f( -100, 150 );

        AddExplosion( @eparam, buf.Position, currentTime );
      end;
    end;
    if not buf.CollideWithRect( avBulRec ) then begin
      buf.Dead := True;
    end;
    buf := buf.NextBullet;
  end;

  for i := 0 to EnemiesNum - 1 do begin
    if not Enemies[ i ].Dead then begin
      if Player.CollideVsEnemy( Enemies[ i ] ) then begin
        Player.GetDamage( Enemies[ i ].Damage );
        Enemies[ i ].Kill;

        eparam.sprite   := Explosion_11;
        eparam.frame    := 0;
        eparam.maxFrame := 10;
        eparam.frameDT  := 0.03;
        eparam.speed    := Vec2f( -100, 100 );

        AddExplosion( @eparam, Enemies[ i ].GetPosition, currentTime );

        Player.AddScore( Enemies[ i ].Points );
      end;
    end;
  end;
  {$ENDREGION}

  ///kill
  {$REGION '  KILL  '}
  EnterCriticalSection( Section_EnemyDel );
    for i := 0 to EnemiesNum - 1 do begin
      if Enemies[ i ].Dead then begin
        Enemies[ i ].Free;
        if i = bossNum then bossAlive := False;
        for j := i to EnemiesNum - 2 do begin
          Enemies[ j ] := Enemies[ j + 1 ];
        end;
        Dec( EnemiesNum );
        if bossAlive and ( bossNum >= EnemiesNum ) then Dec( bossNum );
      end;
    end;

    if Player.DeadMove and bossAlive then begin
      bossAlive := False;
      Enemies[ bossNum ].Kill;
    end;

    buf := bulletHead;
    while buf <> nil do begin
      del := buf;
      buf := buf.NextBullet;
      if del.Dead then del.Delete( bulletHead, bulletTail );
    end;

    ebuf := explosionHead;
    while ebuf <> nil do begin
      edel := ebuf;
      ebuf := ebuf.NextExplosion;
      if edel.Dead then edel.Delete( explosionHead, explosionTail );
    end;
  LeaveCriticalSection( Section_EnemyDel );
  {$ENDREGION}

  if Player.Dead then Exit;

  ///add new
  {$REGION '  ADD NEW  '}
  if currentTime - lastWaveEnd >= rules[ currentRule ].waitWaveDT * 0.001 then begin
    with rules[ currentRule ] do begin
      while True do begin
        if currentTime - lastAdd >= addDT[ current ] * 0.001 then begin
          lastAdd := currentTime;
          Inc( EnemiesNum );
          if EnemiesNum > EnemiesMaxNum then begin
            EnemiesMaxNum := Round( EnemiesMaxNum * 1.5 );
            SetLength( Enemies, EnemiesMaxNum );
          end;
          param.game      := Self;
          param.moveType  := addParam[ current ];
          case enemyType[ current ] of
            1: begin
              Enemies[ EnemiesNum - 1 ] := cEnemy_1.Create;
              Enemies[ EnemiesNum - 1 ].Init( @param );
            end;

            2: begin
              Enemies[ EnemiesNum - 1 ] := cEnemy_2.Create;
              Enemies[ EnemiesNum - 1 ].Init( @param );
            end;

            3: begin
              Enemies[ EnemiesNum - 1 ] := cEnemy_3.Create;
              Enemies[ EnemiesNum - 1 ].Init( @param );
            end;

            4: begin
              Enemies[ EnemiesNum - 1 ] := cBoss_1.Create;
              Enemies[ EnemiesNum - 1 ].Init( @param );
              bossAlive := True;
              bossNum   := EnemiesNum - 1;
            end;

            else begin
              Dec( EnemiesNum );
            end;
          end;
          Inc( current );
          if current >= enemyNum then begin
            current := 0;
            lastWaveEnd := currentTime;
            Inc( currentRule );
            if currentRule >= Length( rules ) then currentRule := fstRule;
            Break;
          end;
          if addDT[ current ] = 0 then Continue else Break;
        end else Break;
      end;
    end;
  end;
  {$ENDREGION}
end;

procedure cGame.UpdateFrames(dt: Double; currentTime: Double);
var i   : Integer;
    buf : pBullet;
    ebuf: pExplosion;
begin
  //EnterCriticalSection( Section_FrameUpdt );
    Player.UpdateFrames( dt, currentTime );

    EnterCriticalSection( Section_EnemyDel );
      for i := 0 to EnemiesNum - 1 do begin
        Enemies[ i ].UpdateFrame( dt, currentTime );
      end;

      buf := bulletHead;
      while buf <> nil do begin
        buf.UpdateFrames( dt, currentTime );
        buf := buf.NextBullet;
      end;

      ebuf := explosionHead;
      while ebuf <> nil do begin
        ebuf.UpdateFrames( dt, currentTime );
        ebuf := ebuf.NextExplosion;
      end;
    LeaveCriticalSection( Section_EnemyDel );
  //LeaveCriticalSection( Section_FrameUpdt );
end;

function cGame.GetBossHealth(): Word;
begin
  if bossAlive then Result := Enemies[ bossNum ].Life else Result := 0;
end;

procedure cGame.AddBullet(const param: pBulletInitParam; pos: rVec2f; enmBul: Boolean; curTime: Double);
begin
  bulletTail.AddToTail( bulletHead, bulletTail ).Init( param, pos, enmBul, curTime );
end;

procedure cGame.AddExplosion(const param: pExplosionInitParam; pos: rVec2f; curTime: Double);
begin
  explosionTail.AddToTail( explosionHead, explosionTail ).Init( param, pos, curTime );
end;

function cGame.GetScore(): LongInt;
begin
  Result := Player.Score;
end;

function cGame.GetPlayerLife(): Word;
begin
  Result := Player.Health;
end;

function cGame.GetPlayerPosition(): rVec2f;
begin
  Result := Player.Position;
end;

function cGame.GetGameOverStatus(): Boolean;
begin
  Result := Player.Dead and ( EnemiesNum = 1 );
end;

procedure cGame.OnKeyDown(key: Byte);
begin
  Player.OnKeyDown( key );
end;

procedure cGame.OnKeyUp(key: Byte);
begin
  Player.OnKeyUp( key );
end;
{$ENDREGION}
////////////////////////////////////////////////////////////////////////////////
end.
