unit UGame;

interface

uses
  Squall,
  JEN_Header,
  JEN_Math,
  UWorm;

const
  WorldSize = 2048;

type
  TGameStatus = (gsGame, gsOver);
  TGame = class(TInterfacedObject, IGame)
  constructor Create;
  private
    FChannel      : LongInt;
    FStatus       : TGameStatus;
    FScores       : Integer;
    FCounter      : Single;
    FCouterShader : IShaderProgram;
    FCounterTex   : ITexture;
    FGroundTex    : ITexture;
    FGameOverTex  : ITexture;
    FRockTex      : ITexture;
    FFoodTex      : array[1..3] of ITexture;
    FTrigerTime   : LongInt;
  public
    Camera        : TVec2f;
    Worm          : TWorm;
    FoodCount     : Integer;
    RockCount     : Integer;
    Food          : array[1..12] of record
      Pos  : TVec2f;
      Kind : Byte;
    end;
    Rock          : array[1..256] of record
      Pos   : TVec2f;
      Angle : Single;
    end;
    GameStartTime : LongInt;
    GameTime      : LongInt;
    function GeneratePos: TVec2f;
    procedure AddRock(const Pos: TVec2f);
    procedure AddFood(const Pos: TVec2f);
    procedure EatFood(id: Integer);
    procedure EatRock(id: Integer);
    procedure LoadContent; stdcall;
    procedure OnUpdate(dt: LongInt); stdcall;
    procedure OnRender; stdcall;
    procedure Close; stdcall;
    procedure Quit;
    procedure Restart;
  end;

implementation

uses
  UMain;

constructor TGame.Create;
begin
end;

procedure TGame.LoadContent;
var
  Res : IShaderResource;
begin
  FChannel := SQUALL_Sample_Play(SQUALL_Sample_LoadFile('Media\Juha Kaunisto - First Time.mp3.mp3', 1, 0), 1, 0, 0);

  ResMan.Load('Media\Food1.dds', FFoodTex[1]);
  ResMan.Load('Media\Food2.dds', FFoodTex[2]);
  ResMan.Load('Media\Food3.dds', FFoodTex[3]);
  ResMan.Load('Media\Rock.dds', FRockTex);
  ResMan.Load('Media\Ground.dds', FGroundTex);
  ResMan.Load('Media\GameOver.dds',FGameOverTex);
  ResMan.Load('Media\Counter.dds', FCounterTex);
  ResMan.Load('Media\CounterShader.xml', Res);

  FCouterShader := Res.Compile;

  Restart;
end;

procedure TGame.Restart;
var
  i : Integer;
begin
  SQUALL_Channel_SetPlayPositionMs(FChannel, 0);
  SQUALL_Channel_Start(FChannel);

  GameStartTime := Utils.Time;
  FStatus       := gsGame;
  FScores       := 0;
  FCounter      := 0;
  FTrigerTime   := 0;
  Camera        := Vec2f((WorldSize-Screen_Width)/2, (WorldSize-Screen_Height)/2);
  if Assigned(Worm) then
    Worm.Free;
  Worm := TWorm.Create;

  FoodCount     := 0;
  RockCount     := 0;
  GameTime      := 0;

  for I := 0 to 6 do
    AddRock(Vec2f(Random*Screen_Width, Random*Screen_Height) + Camera);

  AddFood(Vec2f(Random*Screen_Width, Random*Screen_Height) + Camera);
  AddFood(GeneratePos);
  AddFood(GeneratePos);
end;

procedure TGame.Quit;
begin
  FStatus := gsOver;
end;

procedure TGame.OnUpdate(dt: LongInt);
var
  Scores : Integer;
  Pos   :  TVec2f;
begin
  if FStatus = gsOver then
  begin
    if Input.Hit[ikMouseL] then
      Restart;
    Exit;
  end;
  FTrigerTime := FTrigerTime + dt;
  GameTime := Utils.Time - GameStartTime;
  Scores := FScores + GameTime div 15;
  FCounter := FCounter + Sign(Scores - FCounter)*Min(Abs(Scores - FCounter)/1000*dt, Abs(Scores - FCounter));

  if FTrigerTime > 150 then
  begin
    if Random(100) > 92 then
      AddFood(GeneratePos());

    if Random(100) > 85 then
      AddRock(GeneratePos());

    FTrigerTime := FTrigerTime - 150;
  end;

  Worm.OnUpdate(dt);
  Pos := Worm.HeadPos +(Vec2f(Input.Mouse.Pos.x, Input.Mouse.Pos.y) - Vec2f(Screen_Width, Screen_Height)*0.5 )*0.2 - Vec2f(Screen_Width, Screen_Height)*0.5;
  Pos := Pos.Min(Vec2f(WorldSize-Screen_Width, WorldSize-Screen_Height)).Max(Vec2f(0,0));
  Camera := Camera.Lerp(Pos, dt/1000);
end;

procedure TGame.OnRender;
var
  i,j    : Integer;
  Scores : Integer;
  Move   : Single;

  function SmootherStep(edge0, edge1, x: Single): Single;
  begin
    result := clamp((x - edge0)/(edge1 - edge0), 0, 1);
    result := result*result*result*(result*(result*6 - 15) + 10);
  end;
begin
 // Render.Clear(True,False,False);
  //Render2d.DrawSprite(GroundTex, 0, 0, Screen_Width, Screen_Height, clWhite);

  for I := 0 to Round(WorldSize/64) do
    for j := 0 to Round(WorldSize/64) do
      Render2d.DrawSprite(FGroundTex, I*64 -Camera.x, J*64-Camera.y, 64, 64, clWhite);

  Worm.OnRenderTrace;

  for I := 1 to RockCount do
    Render2d.DrawSprite(FRockTex, Rock[I].Pos.x-Camera.x-10, Rock[I].Pos.y-Camera.y-10, 20, 20, clWhite, Rock[i].Angle);

  for I := 1 to FoodCount do
    Render2d.DrawSprite(FFoodTex[Food[i].Kind], Food[I].Pos.x-Camera.x-8, Food[I].Pos.y-Camera.y-8, 16, 16, clWhite);

  Worm.OnRender;

  Scores := Round(FCounter);
  for I := 5 downto 0 do
  begin
    Move := Frac(FCounter / Pow(10, 6-I));
    Scores := Scores div 10;
    Render2d.DrawSpriteAdv(FCouterShader, FCounterTex, nil, nil, I*32, 0, 32, 26,Vec4f((Scores mod 10)/10 + SmootherStep(0.9, 1.0, Move)/10, 0, 0 ,0), clWhite, clWhite, clWhite);
  end;

  if FStatus = gsOver then
    Render2d.DrawSprite(FGameOverTex, 256, 320, 512, 128, clWhite);
end;

procedure TGame.Close;
begin
  Worm.Free;
  Display  := nil;
  Utils    := nil;
  Input    := nil;
  Render   := nil;
  Render2d := nil;
  ResMan   := nil;
end;

function TGame.GeneratePos;
var
  i : Integer;
  Pos : TVec2f;
begin
  Result := Vec2f(40+Random*(WorldSize-80), 40+Random*(WorldSize-80));
  Pos := Result - Camera;

  if (Pos.x > 0) and (Pos.Y > 0) and (Pos.x< Screen_Width) and (Pos.y<Screen_Height) then
    Exit(GeneratePos);


  for I := 1 to RockCount do
    if (Result - Rock[I].Pos).LengthQ < 2000 then
      Exit(GeneratePos);

  for I := 1 to FoodCount do
    if (Result - Food[I].Pos).LengthQ < 2000 then
      Exit(GeneratePos);

end;
procedure TGame.AddRock(const Pos : TVec2f);
begin
  if RockCount = High(Rock) then
    Exit;

  Inc(RockCount);
  Rock[RockCount].Pos := Pos;
  Rock[RockCount].Angle := Random * 360;
end;

procedure TGame.AddFood(const Pos : TVec2f);
begin
  if FoodCount = High(Food) then
    Exit;

  Inc(FoodCount);
  Food[FoodCount].Pos := Pos;
  case Random(100) of
  0..75   : Food[FoodCount].Kind := 1;
  76..95  : Food[FoodCount].Kind := 2;
  96..100 : Food[FoodCount].Kind := 3;
  end;
end;

procedure TGame.EatFood(id: Integer);
begin
  Inc(FScores,Game.Food[id].Kind*1500);
  Food[id].Pos := Food[FoodCount].Pos;
  Food[id].Kind := Food[FoodCount].Kind;
  Dec(FoodCount);
end;

procedure TGame.EatRock(id: Integer);
begin
  Inc(FScores,1500);
  Rock[id].Pos := Rock[RockCount].Pos;
  Rock[id].Angle := Rock[RockCount].Angle;
  Dec(RockCount);
end;

end.
