unit GameModeGame;

interface

uses
  QuadEngine, QuadEngine.Color, GameModeManager, Resources, Vec2f, BASS, SysUtils;

type
  TGameBlock = record
    State: Byte;
    Fade: Double;
    IsToKill: Boolean;
  end;

  TFallingBlock = record
    State: Byte;
    Position: TVec2f;
    Dy: Single;
    Active: Boolean;
  end;

  TGameModeGame = class sealed(TCustomGameMode)
  private
  const
    SizeX = 16;
    SizeY = 10;
    CellSize = 44;
  var
    FField: array[0..SizeX - 1, 0..SizeY - 1] of TGameBlock;
    FFalling: array[Byte] of TFallingBlock;
    FPiece: array[0..1, 0..1] of TFallingBlock;
    FXpos, FYPos: Single;
    FIsFalling: Boolean;
    FSpeed: Boolean;

    FTime: Double;
    FDanceTimer: Double;
    FDanceFloorIndex: Word;

    FLinePosition: TVec2f;

    FScore: Cardinal;
    FGameTime: Double;
    FHiScore: Cardinal;
    FLevel: Integer;

    FDeletedBlocks: Integer;
    FDeletedInfo: Boolean;

    Cooldown: Double;
    spectr : array[0..1023] of Single;
    procedure AddToFalling(X, Y: Integer);
    procedure AddNewFigure;
  public
    constructor Create;

    procedure OnDraw; override;
    procedure OnKeyDown(Key: Word); override;
    procedure OnKeyUp(Key: Word); override;
    procedure OnProcess(Delta: Double); override;
  end;

implementation

procedure TGameModeGame.AddToFalling(X, Y: Integer);
var
  i : Byte;
begin
  if FField[X, Y].State = 0 then
    Exit;

  i := 0;
  while FFalling[i].Active do
  begin
    Inc(i);
  end;

  FFalling[i].State := FField[X, Y].State;
  FFalling[i].Position := TVec2f.Create(X * CellSize + 292, Y * CellSize + 152);
  FFalling[i].Dy := 160;
  FFalling[i].Active := True;

  FField[X, Y].State := 0;
  FField[X, Y].Fade := 0;
  FField[X, Y].IsToKill := False;
end;

procedure TGameModeGame.AddNewFigure;
var
  i, j: Integer;
begin
  for i := 0 to 1 do
    for j := 0 to 1 do
      FPiece[i, j].State := random(2) + 1;

  FXpos := 7 * CellSize;
  FYPos := 0;
end;

constructor TGameModeGame.Create;
var
  i, j: Integer;
begin
  for i := 0 to SizeX - 1 do
    for j := 0 to SizeY - 1 do
    begin
      FField[i, j].State := 0;
      FField[i, j].Fade := 0;
      FField[i, j].IsToKill := False;
    end;

  for i := 0 to 255 do
    FFalling[i].Active := False;

  AddNewFigure;

  FScore := 0;
  FGameTime := 0.0;
  FLevel := 1;
  FDeletedBlocks := 0;

  FDanceTimer := 18;
  FTime := 27;
  BASS_ChannelSetAttribute(TGlobals.music, BASS_ATTRIB_VOL, 0.1);
  BASS_ChannelSetPosition(TGlobals.music, BASS_ChannelSeconds2Bytes(TGlobals.music, FTime), BASS_POS_BYTE);

  FLinePosition := TVec2f.Create(292 - 128, 151);
end;

procedure TGameModeGame.OnDraw;
var
  i, j: Integer;
begin
  TGlobals.QuadRender.SetBlendMode(qbmNone);
  TGlobals.QuadRender.RectangleEx(TVec2f.Create(0, TGlobals.ScreenHeight / 2), TVec2f.Create(TGlobals.ScreenWidth, TGlobals.ScreenHeight), $FF000000, $FF000000, $FF141923, $FF141923);
  TGlobals.QuadRender.RectangleEx(TVec2f.Zero, TVec2f.Create(TGlobals.ScreenWidth, TGlobals.ScreenHeight / 2), $FF141923, $FF141923, $FF000000, $FF000000);


  TGlobals.QuadRender.SetBlendMode(qbmSrcAlphaAdd);
  for i:= 0 to 1023 do
  begin
    TGlobals.QuadRender.Rectangle(TVec2f.Create(i * 3, 360), TVec2f.Create(i * 3 + 3, 360 + spectr[i]), $FFcdddff);
    TGlobals.QuadRender.Rectangle(TVec2f.Create(i * 3, 360 - spectr[i]), TVec2f.Create(i * 3 + 3, 360), $FFcdddff);
  end;

  TGlobals.QuadRender.SetBlendMode(qbmSrcAlphaAdd);
  if Cooldown > -0.3 then
    TTextures.DanceFloor[FDanceFloorIndex].Draw(TVec2f.Zero, TQuadColor.Create(0.3, 0.7, 1.0, Cooldown + 0.3));

  TGlobals.QuadRender.SetBlendMode(qbmSrcAlphaAdd);
  TTextures.Fog.DrawRotAxis(TVec2f.Zero, -FDanceTimer*1.1 + 45, 1.0, TVec2f.Create(TGlobals.ScreenWidth, TGlobals.ScreenHeight), TQuadColor.Create(1.0, 0.0, 0.0, spectr[50] / 32));
  TTextures.Fog.DrawRotAxis(TVec2f.Zero, -FDanceTimer + 45, 0.7, TVec2f.Create(TGlobals.ScreenWidth, TGlobals.ScreenHeight), TQuadColor.Create(0.0, 0.0, 1.0, spectr[100] / 32));
  TTextures.Fog.DrawRotAxis(TVec2f.Zero, -FDanceTimer/2 + 45, 0.5, TVec2f.Create(TGlobals.ScreenWidth, TGlobals.ScreenHeight),TQuadColor.Create(1.0, 1.0, 1.0, spectr[150] / 32));

  TTextures.Fog2.DrawRotAxis(TVec2f.Zero, -FDanceTimer*1.1 - 45, 1.0, TVec2f.Create(0, TGlobals.ScreenHeight), TQuadColor.Create(1.0, 0.0, 0.0, spectr[50] / 32));
  TTextures.Fog2.DrawRotAxis(TVec2f.Zero, -FDanceTimer - 45, 0.7, TVec2f.Create(0, TGlobals.ScreenHeight), TQuadColor.Create(0.0, 0.0, 1.0, spectr[100] / 32));
  TTextures.Fog2.DrawRotAxis(TVec2f.Zero, -FDanceTimer/2 - 45, 0.5, TVec2f.Create(0, TGlobals.ScreenHeight),TQuadColor.Create(1.0, 1.0, 1.0, spectr[150] / 32));


   // dancer
  if ((FTime > 59) and (FTime < 89)) or ((FTime > 125) and (FTime < 155)) then
  begin
    TGlobals.QuadRender.SetBlendMode(qbmSrcAlpha);
    TTextures.Dance[Trunc(FDanceTimer)].DrawRot(TVec2f.Create(200, 400), 1.0, 2.0, $FFcdddff);
  end;


  TGlobals.QuadRender.SetBlendMode(qbmAdd);
  TTextures.GameOverlay.Draw(TVec2f.Zero, $FF888888);

  TGlobals.QuadRender.SetBlendMode(qbmSrcAlpha);
  for i := 0 to SizeX - 1 do
    for j:= 0 to SizeY - 1 do
    begin
      case FField[i, j].State of
        1: TTextures.Blocks.DrawFrame(TVec2f.Create(i * CellSize + 292, j * CellSize + 152), 0);
        2: TTextures.Blocks.DrawFrame(TVec2f.Create(i * CellSize + 292, j * CellSize + 152), 1);
        3: TTextures.Blocks.DrawFrame(TVec2f.Create(i * CellSize + 292, j * CellSize + 152), 2);
      end;

      if FField[i, j].IsToKill and (FField[i, j].Fade >= 0) then
        TTextures.Blocks.DrawFrame(TVec2f.Create(i * CellSize + 292, j * CellSize + 152), 2, TQuadColor.Create(1.0, 1.0, 1.0, 0.0).Lerp(TQuadColor.Create(1.0, 1.0, 1.0, 1.0), FField[i, j].Fade + 0.7));
    end;

  for i := 0 to 255 do
  if FFalling[i].Active then
  begin
    case FFalling[i].State of
      1: TTextures.Blocks.DrawFrame(FFalling[i].Position, 0);
      2: TTextures.Blocks.DrawFrame(FFalling[i].Position, 1);
      3: TTextures.Blocks.DrawFrame(FFalling[i].Position, 2);
    end;
  end;

  for i := 0 to 1 do
    for j := 0 to 1 do
      case FPiece[i, j].State of
        1: TTextures.Blocks.DrawFrame(TVec2f.Create(i * CellSize + FXPos + 292, (j - 2) * CellSize + FYPos + 152), 0);
        2: TTextures.Blocks.DrawFrame(TVec2f.Create(i * CellSize + FXPos + 292, (j - 2) * CellSize + FYPos + 152), 1);
      end;

  TGlobals.QuadRender.SetClipRect(292, 0, 996, TGlobals.ScreenHeight);
  TGlobals.QuadRender.SetBlendMode(qbmAdd);
  TTextures.Line.Draw(FLinePosition, $FF6e9eff);
  TGlobals.QuadRender.FlushBuffer;
  TGlobals.QuadRender.SkipClipRect;

  TGlobals.QuadRender.SetBlendMode(qbmSrcAlpha);
  TTextures.Font.TextOut(TVec2f.Create(1100, 235), 1.3, '1');
  TTextures.Font.TextOut(TVec2f.Create(1100, 322), 1.3, PChar(IntToStr(FScore)));
  TTextures.Font.TextOut(TVec2f.Create(1100, 410), 1.3, '1111780');

  if FDeletedInfo and (FDeletedBlocks > 0) then
  begin
    TGlobals.QuadRender.SetBlendMode(qbmSrcAlpha);
    TTextures.Font.TextOut(TVec2f.Create(760 + (FLinePosition.X - 800), 322), 5 - FLinePosition.X / 300, PChar('+' + IntToStr(FDeletedBlocks * FDeletedBlocks)), TQuadColor.Create(1.0, 0.7, 0.3, 1.0 - (FLinePosition.X - 996 + 128) / 256) , qfaLeft);
  end;
end;

procedure TGameModeGame.OnKeyDown(Key: Word);
var
  i, j : Integer;
  tmp: Byte;
begin
  case Key of
    $26:
      begin
        tmp := FPiece[0, 0].State;
        FPiece[0, 0].State := FPiece[0, 1].State;
        FPiece[0, 1].State := FPiece[1, 1].State;
        FPiece[1, 1].State := FPiece[1, 0].State;
        FPiece[1, 0].State := tmp;
      end;
    $25:
      begin
        if Trunc(FXpos) div CellSize > 0 then
        begin
          FXpos := FXpos - CellSize;
          BASS_ChannelPlay(TGlobals.sfx, False);
        end;
      end;
    $27:
      begin
        if Trunc(FXpos) div CellSize < 14 then
        begin
          FXpos := FXpos + CellSize;
          BASS_ChannelPlay(TGlobals.sfx, False);
        end;
      end;
    $28:
      begin
        FSpeed := True;
        FIsfalling := True;
      end;
  end;
end;

procedure TGameModeGame.OnKeyUp(Key: Word);
begin

end;

procedure TGameModeGame.OnProcess(Delta: Double);
var
  i, j, k: Integer;
begin
  inherited;

  FLinePosition.X := FLinePosition.X + Delta * 200;
  if (FLinePosition.X > (996 + 128)) then
  begin
    FLinePosition.X := 292 - 128;
    FIsFalling := True;

    for i := 0 to SizeX - 1 do
      for j := 0 to SizeY - 1 do
        if FField[i, j].IsToKill then
        begin
          FField[i, j].IsToKill := False;
          FField[i, j].Fade := 0.3;
        end;

    FScore := FScore + FDeletedBlocks * FDeletedBlocks;
    FDeletedBlocks := 0;
    FDeletedInfo := False;
  end;

  if (FLinePosition.X > 996 - 128) then
    FDeletedInfo := True;

  FDanceTimer := FDanceTimer + Delta * 25.5;
  if FDanceTimer > 24 then
    FDanceTimer := FDanceTimer - 24;

 //FTime := FTime + Delta;
  FTime := BASS_ChannelBytes2Seconds(TGlobals.music, BASS_ChannelGetPosition(TGlobals.music, BASS_POS_BYTE));
  if (FTime > (4 * 60 + 27)) then
    BASS_ChannelSetPosition(TGlobals.music, BASS_ChannelSeconds2Bytes(TGlobals.music, 13), BASS_POS_BYTE);

  for i := 0 to SizeX - 1 do
  begin
    for j := SizeY - 2 downto 0 do
      if FField[i, j].State > 0 then
      begin
        if FField[i, j + 1].State = 0 then
          AddToFalling(i, j);
      end;
  end;

  // block
  if FIsFalling then
  begin
    if FSpeed then
      FYPos := FYPos + 500 * Delta
    else
      FYPos := FYPos + 100 * Delta;

    for i := 0 to 1 do
    if ((round(FYPos) div CellSize) > 9) or (FField[round(FXpos) div CellSize + i, round(FYPos) div CellSize].State > 0)  then
    begin
      for k := 0 to 1 do
        for j:= 0 to 1 do
          FField[Round(FXpos) div CellSize + k, Round(FYPos) div CellSize + j - 2].State:= FPiece[k, j].State;
      AddToFalling(Round(FXpos) div CellSize + i, Round(FYPos) div CellSize - 2);
      AddNewFigure;
      FIsFalling := False;
      FSpeed := False;
      Break;
    end;

  end;

      // falling ones
  for i := 0 to 255 do
  if FFalling[i].Active then
  begin
    FFalling[i].Dy := FFalling[i].Dy * 1.1;
    FFalling[i].Position.Y := FFalling[i].Position.Y + FFalling[i].Dy * Delta;

    if ((Trunc(FFalling[i].Position.Y) - 152) div CellSize >= 9) or (FField[(Trunc(FFalling[i].Position.X) - 292) div CellSize, (Trunc(FFalling[i].Position.Y) - 152) div CellSize + 1].State > 0) then
    begin
      FFalling[i].Active := False;
      FField[(Trunc(FFalling[i].Position.X) - 292) div CellSize, (Trunc(FFalling[i].Position.Y) - 152) div CellSize].State := FFalling[i].State;
    end;
  end;

  // Matched blocks
  for i := 0 to SizeX - 2 do
    for j := 0 to SizeY - 2 do
    begin
      if (FField[i, j].State in [1, 2]) and
        (FField[i + 1, j].State = FField[i, j].State) and
        (FField[i, j + 1].State = FField[i, j].State) and
        (FField[i + 1, j + 1].State = FField[i, j].State) then
        begin
          if not FField[i, j].IsToKill then
            FField[i, j].Fade := 0.3;
          if not FField[i + 1, j].IsToKill then
            FField[i + 1, j].Fade := 0.3;
          if not FField[i, j + 1].IsToKill then
            FField[i, j + 1].Fade := 0.3;
          if not FField[i + 1, j + 1].IsToKill then
            FField[i + 1, j + 1].Fade := 0.3;

          FField[i, j].IsToKill := True;
          FField[i + 1, j].IsToKill := True;
          FField[i, j + 1].IsToKill := True;
          FField[i + 1, j + 1].IsToKill := True;
        end;
    end;

  // kill some blocks
  for i := 0 to SizeX - 1 do
  for j := 0 to SizeY - 1 do
  if FField[i, j].IsToKill then
  begin
    if FField[i, j].Fade > 0 then
      FField[i, j].Fade := FField[i, j].Fade - Delta
    else
        FField[i, j].Fade := 0;
  end;

  if (FLinePosition.X > 292) and (FLinePosition.X < (292 + SizeX * CellSize)) then
  for j := 0 to SizeY - 1 do
  begin
    if FField[round(FLinePosition.X + 128 - 292) div CellSize, j].IsToKill and
      (FField[round(FLinePosition.X + 128 - 292) div CellSize, j].Fade <= 0.05) then
      begin
        Inc(FDeletedBlocks);
        for k := j - 1 downto 0 do
          AddToFalling(round(FLinePosition.X - 292) div CellSize, k);

        FField[round(FLinePosition.X + 128 - 292) div CellSize, j].State := 0;
        FField[round(FLinePosition.X + 128 - 292) div CellSize, j].Fade := 0;
        FField[round(FLinePosition.X + 128 - 292) div CellSize, j].IsToKill := False;
      end;
  end;


  BASS_ChannelGetData(TGlobals.music, @spectr, BASS_DATA_FFT2048);


  for i:= 0 to 1024 do
    spectr[i] := spectr[i] * 256;


  Cooldown := Cooldown - Delta;
  if Cooldown < 0 then
  if spectr[5] > 100 then
  begin
    Cooldown := 0.3;
    Inc(FDanceFloorIndex);
    if FDanceFloorIndex > 4 then
      FDanceFloorIndex := 0;
  end;
end;

end.
