Scorch2d

program scorch2d;

{$IFDEF Win32}
  {$IFDEF FPC}
    {$APPTYPE GUI}     // FreePascal/Lazarus
  {$ELSE}
    {$APPTYPE CONSOLE} // Delphi
  {$ENDIF}
{$ENDIF}
{$APPTYPE GUI}

uses
  SysUtils,
  // JEDI-SDL
  sdl,
  sdlutils,
  // Scorch 2D
  AdvancedMathUnit,
  GraphicsUnit,
  FontUnit,
  GameObjectUnit,
  GameConstantsUnit;

type
  // Game Modes
  TGameMode = (gmMainMenu, gmAiming, gmShooting, gmMenu, gmQuit);

  // Game Controls
  TGameControls = Record
    Up, Down, Left, Right: Boolean;
    FastUp, FastDown: Boolean;
    Fire, Select, Menu: Boolean;
  end;
  
var
  GAME_FPS: Cardinal = 30; {30}

  // Video Screens
  GameScreen: PSDL_Surface; // Main GameScreen!

  // Font Data
  GameFont: TFont;
  
  // Game Data
  GameMode: TGameMode = gmAiming;
  RunClock: Integer;
  WhosTurn: Integer;

  GameInput: TGameControls;
  
  // Level Data
  Level: TBattlefield;

  // Tank Data
  NumberOfTanks: Integer;
  Tanks: Array[0 .. 3] of TTank;
  
  // Shot Data
  Shot: TShot;

function Get_FPS(fps: Cardinal): UInt32;
begin
     if (fps > 0) then
        Result := 1000 div fps
     else
         Result := 0;
end;


procedure doGameInput;
var
  event: TSDL_Event;
  TurretEndX, TurretEndY: Real;
begin
     while (SDL_PollEvent(@event) > 0) do
     begin
          case (event.type_) of
            SDL_KEYDOWN : case (event.key.keysym.sym) of
                            SDLK_UP       : GameInput.Up := True;
                            SDLK_PAGEUP   : GameInput.FastUp := True;
                            SDLK_DOWN     : GameInput.Down := True;
                            SDLK_PAGEDOWN : GameInput.FastDown := True;
                            SDLK_LEFT     : GameInput.Left := True;
                            SDLK_RIGHT    : GameInput.Right := True;
                            SDLK_SPACE,
                            SDLK_RETURN   : GameInput.Fire := True;
                            SDLK_TAB      : GameInput.Select := True;
                            SDLK_ESCAPE   : GameInput.Menu := True;
                          end;
            SDL_KEYUP   : case (event.key.keysym.sym) of
                            SDLK_UP       : GameInput.Up := False;
                            SDLK_PAGEUP   : GameInput.FastUp := False;
                            SDLK_DOWN     : GameInput.Down := False;
                            SDLK_PAGEDOWN : GameInput.FastDown := False;
                            SDLK_LEFT     : GameInput.Left := False;
                            SDLK_RIGHT    : GameInput.Right := False;
                            SDLK_SPACE,
                            SDLK_RETURN   : GameInput.Fire := False;
                            SDLK_TAB      : GameInput.Select := False;
                            SDLK_ESCAPE   : GameInput.Menu := False;
                          end;
          end;
     end;
     
     // -- Game Key Actions -- //
     if (GameInput.Menu) then
        GameMode := gmQuit;

     if (GameInput.Up) then
     begin
          Tanks[WhosTurn].AimPower := Tanks[WhosTurn].AimPower + 1;
          if (Tanks[WhosTurn].AimPower > ShotMaxPower) then
             Tanks[WhosTurn].AimPower := ShotMaxPower;
     end;
     if (GameInput.FastUp) then
     begin
          Tanks[WhosTurn].AimPower := Tanks[WhosTurn].AimPower + 10;
          if (Tanks[WhosTurn].AimPower > ShotMaxPower) then
             Tanks[WhosTurn].AimPower := ShotMaxPower;
     end;
     if (GameInput.Down) then
     begin
          Tanks[WhosTurn].AimPower := Tanks[WhosTurn].AimPower - 1;
          if (Tanks[WhosTurn].AimPower < ShotMinPower) then
             Tanks[WhosTurn].AimPower := ShotMinPower;
     end;
     if (GameInput.FastDown) then
     begin
          Tanks[WhosTurn].AimPower := Tanks[WhosTurn].AimPower - 10;
          if (Tanks[WhosTurn].AimPower < ShotMinPower) then
             Tanks[WhosTurn].AimPower := ShotMinPower;
     end;

     if (GameInput.Left) then
     begin
          Tanks[WhosTurn].ChangeTurret(Tanks[WhosTurn].AimAngle + 1);
     end;
     if (GameInput.Right) then
     begin
          Tanks[WhosTurn].ChangeTurret(Tanks[WhosTurn].AimAngle - 1);
     end;

     if (GameInput.Fire) then
     begin
          Shot := TShot.Init(Tanks[WhosTurn], 10);
          RunClock := 0;
          GameMode := gmShooting;
     end;

     // -- Reset Game Keys -- //
     GameInput.Fire := False;
     GameInput.Select := False;
     GameInput.Menu := False;
end;

procedure GameCycle;
begin
     Shot.Update(Level);

     if (Shot.Remove) then
     begin
          // Destroy Shot
          Shot.Free;
          
          // Switch back to Aiming game mode
          GameMode := gmAiming;

          // Next Tank's Turn!
          inc(WhosTurn);
          if (WhosTurn > NumberOfTanks - 1) then
             WhosTurn := 0;
     end;
     
     inc(RunClock);
end;

procedure DrawScreen;
var
  i: Integer;
begin
     Level.DrawSky(GameScreen);
     Level.DrawLand(GameScreen);

     for i := 0 to NumberOfTanks - 1 do
         Tanks[i].Draw(GameScreen);

     if (GameMode = gmShooting) then
        Shot.Draw(GameScreen);

     GameFont.DrawText(GameScreen, 10, 10, 'Tank ' + IntToStr(WhosTurn) + ' is firing!');
     GameFont.DrawText(GameScreen, 10, 22, 'Power: ' + IntToStr(Round(Tanks[WhosTurn].AimPower)));
     GameFont.DrawText(GameScreen, 10, 34, 'Angle: ' + IntToStr(Round(Tanks[WhosTurn].AimAngle)));

     GameFont.DrawText(GameScreen, 10, GameScreen.h - 23, 'RunClock = ' + IntToStr(RunClock));
     GameFont.DrawText(GameScreen, 10, GameScreen.h - 36, 'Facing = ' + IntToStr(Tanks[WhosTurn].Facing));

     SDL_Flip(GameScreen);
end;

// -- INIT & DEINIT -- //
procedure ProgramCreate;
var
  Buffer, s: Word;
begin
     // Update Random Seed value (Helps make random values more random!)
     Randomize;
     DecodeTime(Time, Buffer, Buffer, Buffer, s);
     RandSeed := s;

     // Init Video Mode //
     if (SDL_Init(SDL_INIT_VIDEO) <> 0) then
        Halt;

     // Set the title bar in environments that support it
     SDL_WM_SetCaption('Scorch 2D', nil);

     // Set Video Mode
     GameScreen := SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE);
     if (GameScreen = nil) then
     begin
          SDL_Quit;
          Halt;
     end;

     // Load Standard Game Font
     GameFont := TFont.LoadFromFile(8, 12, $ffffff, 'images/SystemFontWhite.bmp');
     
     // Hide Cursor
     SDL_ShowCursor(SDL_DISABLE);
end;
// Deinit Program
procedure ProgramClose;
begin
     // Show Cursor
     SDL_ShowCursor(SDL_ENABLE);

     // Unload Game Font
     GameFont.Free;

     // Shutdown SDL
     SDL_Quit;
end;
// -- End of INIT & DEINIT -- //


 // ~~~ | ------------ | ~~~ //
 // === | MAIN PROGRAM | === //
 // ~~~ | ------------ | ~~~ //
begin
     ProgramCreate;

     // Create Battlefield
     Level := TBattlefield.Init(GameScreen.w, GameScreen.h, $229900, $0000ee, True, 'DesertEclipse.bmp');
     Level.GenerateLand(LandHighest, LandLowest, LandVariation);
     Level.SmoothenLand(LandSmoothing);

     // Place Tanks
     NumberOfTanks := 4;
     Tanks[0] := TTank.Init(13, -1, -8, 10, $008442, 'TK-1.bmp');
     Tanks[1] := TTank.Init(13, 0, -8, 10, $e74218, 'TK-2.bmp');
     Tanks[2] := TTank.Init(13, -4, -8, 11, $8400ff, 'TK-3.bmp');
     Tanks[3] := TTank.Init(13, -5, -8, 12, $ffff84, 'TK-4.bmp');

     Level.ScatterTanks(Tanks, 4, TankMinDistance, TankPlaceGap);

     Level.SmoothenLand(1);

     GameMode := gmAiming;
     
     RunClock := 0;
     repeat
       SDL_Delay(Get_FPS(GAME_FPS));

       if (GameMode = gmAiming) then
          doGameInput;

       if (GameMode = gmShooting) then
          GameCycle;

       DrawScreen;
     until (GameMode = gmQuit); // Exit when any key is pressed!

     Tanks[0].Free;
     
     Level.Free;
     
     ProgramClose;
end.
Programming - a skill for life!

Part 4 of republished guest tutorial by Jason McMillen