Save UK

Plane flies over UK and shoots down aliens

Introduction

Peter used his own software (not shown here) to divide a map of the UK into over 1600 .png image files. The files take a while to load but thereafter the program is slick and responsive. The robust code is helpfully commented and should prove to be useful for reference.

As you pilot the plane over a map of the UK and part of France you are continuously shooting and need to direct your bullets at aliens. A small scale map shows your position and the positions of all of the aliens. Control the plane with the arrow keys. You are the pilot and the up, down, left and right keys cause the plane to accelerate, decelerate, turn left and turn right, respectively. Press p to pause.

The first screenshot shows the plane near the beginning of the game. The map on the right indicates the positions of the plane and aliens in yellow and red, respectively.

Program SaveUK in action

Program SaveUK in action

We could not resist flying the plane over Watford. See the approach in the screenshot below.

Approaching Watford

Approaching Watford

Download here a zip file containing the main code (SaveUK.txt), 13 bitmap image files, and a folder named images containing over 1600 png images (tiles) comprising the main map. You should unzip the files so that the images folder and the bitmaps are in the program folder. In order to run the program you will need to use SDL files and additional libraries. (See Getting Started with SDL for download details of SDL.dll, SDL.pas) and other Pascal header files for SDL libraries.

The program requires the libraries SDL.dll, SDL_gfx.dll SDL_image.dll, sdl_ttf.dll, libfreetype-6.dll and zlib1.dll. You can put these files in the program folder or with thousands of other libraries in your system folder if they are not already in your system. See the introduction to SpaceShooter for individual download details and/or download several at once in a zip file of libraries that are required by the Elysian Pascal game development framework.

The Program

program SaveUK;
{
    Copyright (c) 2011 Peter Hearnshaw

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
uses
  SysUtils, math, sdl, SDL_image,  sdlutils, sdl_gfx,  sdl_ttf;

var
  i, a : integer;
  alienHealth : integer;
  key : Uint32;
  totalDead : boolean;
  screen, picture, bullet, alien1, alien2, theuk, me, alien2health1, alien2health2,
    alien2health3, frontPage, deadPage, winPage, rotatedPicture, alienDot, fontface : pSDL_SURFACE;
  SrcRect : TSDL_Rect;
  tileX, tileY : integer;
  keystates : PUint8;
  AllTiles : array[1000 .. 1036, 650 .. 695] of pSDL_SURFACE;
  xRotate, xRotateMovement, power : real;
  rotated_width, rotated_height : integer;
  bullets : array[1 .. 4, 1 .. 20] of real;
  bulletWait, bulletMaxWait : integer;
  agility : real;
  NewestBullet : integer = 0;
  textBig : boolean;
  timeLeft : integer;
  alienDestX, alienDestY, levelNumber, aliensKilledThisLevel : integer;
  alienX, alienY, distance : real;
  aliens : array[1 .. 7, 1 .. 300] of real;
  numberOfAliensOnEachLevel : array[1 .. 4] of integer = (20, 100, 140, 295);
  loaded_font, loaded_font_big : pointer;
  timeLeftLevels : array[1 .. 4] of integer = (2300, 2000, 2850, 6500);
  colour_font : pSDL_COLOR;

procedure writeText(txtStr : PChar; xShift : integer; yShift : integer);
begin
  colour_font^.r := 255;
  colour_font^.g := 0;
  colour_font^.b := 0;
  if (textBig = True) then
    fontface := TTF_RENDERTEXT_SOLID(loaded_font_big, txtStr, colour_font^);
  if (textBig = False) then
    fontface := TTF_RENDERTEXT_SOLID(loaded_font, txtStr, colour_font^);
  SrcRect.x := xShift;
  SrcRect.y := yShift;
  SDL_BLITSURFACE(fontface, nil, screen, @SrcRect);
  textBig := False;
end;

procedure startScreen;
var
  myEvent : TSDL_Event;
  startGame : boolean;
begin
  repeat
    startGame := False;
    SDL_PollEvent(@MyEvent);
    if MyEvent.type_ = SDL_MOUSEBUTTONDOWN then
      begin
        if (MyEvent.motion.x > 337) and (MyEvent.motion.x < 607) and
           (MyEvent.motion.y > 343) and (MyEvent.motion.y < 397) then
          startGame := True;
      end;
    sleep(20);
    SrcRect.x := 190;
    SrcRect.y := 125;
    SDL_Blitsurface(frontPage, nil, screen, @SrcRect);
    SDL_PumpEvents();
    SDL_Flip(screen);
    //reset the screen by pasting a rectangle over it
    SDL_FillRect(screen, pSDLRect(0, 0, 959, 600), $882683);
  until (startGame = True);
end;

procedure winScreen;
var
  myEvent : TSDL_Event;
  startGame : boolean;
begin
  repeat
    startGame := False;
    SDL_PollEvent(@MyEvent);
    if MyEvent.type_ = SDL_MOUSEBUTTONDOWN then
      begin
        if (MyEvent.motion.x > 337) and (MyEvent.motion.x < 607) and
           (MyEvent.motion.y > 343) and (MyEvent.motion.y < 397) then
          begin
            startGame := True;
            totalDead := True;
          end;
      end;
    sleep(20);
    SrcRect.x := 190;
    SrcRect.y := 125;
    //reset the screen by pasting a rectangle over it
    SDL_FillRect(screen, pSDLRect(0, 0, 959, 600), $882683);
    SDL_Blitsurface(winPage, nil, screen, @SrcRect);
    SDL_PumpEvents();
    SDL_Flip(screen);
  until (startGame = True);
end;

procedure deadScreen;
var
  myEvent : TSDL_Event;
  startGame : boolean;
begin
  repeat
    startGame := False;
    SDL_PollEvent(@MyEvent);
    if MyEvent.type_ = SDL_MOUSEBUTTONDOWN then
      begin
        if (MyEvent.motion.x > 337) and (MyEvent.motion.x < 607) and
           (MyEvent.motion.y > 343) and (MyEvent.motion.y < 397) then
          begin
            startGame := True;
            totalDead := True;
          end;
      end;
    sleep(20);
    SrcRect.x := 190;
    SrcRect.y := 125;
    SDL_Blitsurface(deadPage, nil, screen, @SrcRect);
    SDL_PumpEvents();
    SDL_Flip(screen);
    //reset the screen by pasting a rectangle over it
    SDL_FillRect(screen, pSDLRect(0, 0, 959, 600), $882683);
  until (startGame = True);
end;

procedure loadAllImagesFromFile;
var
  i, a : integer;
const
  TILES_FROM_X = 1000;
  TILES_FROM_Y = 650;
begin
  for i := 0 to 35 do
    begin
      writeText(pchar(inttostr(round((i / 35) * 100))), 410, 300);
      writeText('percent loaded', 450, 300);
      SDL_Flip(screen);
      //reset the screen by pasting a rectangle over it
      SDL_FillRect(screen, pSDLRect(0, 0, 600, 600), $000000);
      for a := 0 to 45 do
        begin
          AllTiles[i + TILES_FROM_X][a + TILES_FROM_Y] :=
            IMG_LOAD(pchar('images\' + inttostr(i + TILES_FROM_X) + inttostr(a + TILES_FROM_Y) + '.png'));
        end;
    end;
end;

procedure newLevelProcedure;
begin
  for i := 1 to numberOfAliensOnEachLevel[levelNumber] do  //make aliens
    begin
      case levelNumber of  //level specific things
        1 : begin
              alienX := 1021700 + random(20000) - 10000;
              alienY := 679400 + random(20000);
              aliens[3][i] := 1021700 + random(500) - 250; //London
              aliens[4][i] := 679400 + random(500) - 250;
              aliens[6][i] := 1;
            end;
        2 : begin
              aliens[6][i] := 1;
              if (random(20) = 1) then
                aliens[6][i] := 2;
              alienX := 1021700 + random(20000) - 10000;
              alienY := 679400 + random(20000) - 10000;
              aliens[3][i] := 1021700 + random(500) - 250;
              aliens[4][i] := 679400 + random(500) - 250;
            end;
        3 : begin
              aliens[6][i] := 1;
              if (random(8) = 1) then
                aliens[6][i] := 2;
              alienX := 1018000 + random(20000) - 10000;  //start from anywhere
              alienY := 672000 + random(20000) - 10000;
              case (random(3)) of
                0 : begin
                      aliens[3][i] := 1021700 + random(500) - 250;  //London
                      aliens[4][i] := 679400 + random(500) - 250;
                    end;
                1 : begin
                      aliens[3][i] := 1011500 + random(500) - 250;  //Birmingham
                      aliens[4][i] := 670300 + random(500) - 250;
                    end;
                2 : begin
                      aliens[3][i] := 1009500 + random(500) - 250;  //Manchester
                      aliens[4][i] := 661000 + random(500) - 250;
                    end;
              end;
            end;
        4 : begin
              alienX := 1018000 + random(40000) - 20000;  //start from anywhere
              alienY := 672000 + random(40000) - 20000;
              aliens[6][i] := 1;
              if (random(2) = 1) then
                aliens[6][i] := 2;
              case (random(7)) of
                0 : begin
                      aliens[3][i] := 1021700 + random(500) - 250;  //London
                      aliens[4][i] := 679400 + random(500) - 250;
                    end;
                1 : begin
                      aliens[3][i] := 1011500 + random(500) - 250;  //Birmingham
                      aliens[4][i] := 670300 + random(500) - 250;
                    end;
                2 : begin
                      aliens[3][i] := 1009500 + random(500) - 250;  //Manchester
                      aliens[4][i] := 661000 + random(500) - 250;
                    end;
                3 : begin
                      aliens[3][i] := 1007700 + random(500) - 250;  //Bristol
                      aliens[4][i] := 680000 + random(500) - 250;
                    end;
                4 : begin
                      aliens[3][i] := 1013500 + random(500) - 250;  //Leeds
                      aliens[4][i] := 658000 + random(500) - 250;
                    end;
                5 : begin
                      aliens[3][i] := 1014300 + random(500) - 250;  //Southampton
                      aliens[4][i] := 685000 + random(500) - 250;
                    end;
                6 : begin
                      aliens[3][i] := 1029800 + random(500) - 250;  //Norwich
                      aliens[4][i] := 669000 + random(500) - 250;
                    end;
              end;
            end;
      end;
      //standard things
      aliens[1][i] := alienX;
      aliens[2][i] := alienY;
      aliens[5][i] := 1;
      aliens[7][i] := 3;
    end;
end;

procedure playingTheGame;
begin
  for i := 1 to 20 do
    bullets[1][i] := 0;
  for i := 1 to 300 do
    aliens[5][i] := 0;

  tileX := 1022000;
  tileY := 679500;

  alienX := 1004500;
  alienY := 657600;
  totalDead := False;
  levelNumber := 1;
  aliensKilledThisLevel := 0;
  newLevelProcedure;

  agility := 0.6;
  bulletWait := 0;
  bulletMaxWait := 5;

  timeLeft := timeLeftLevels[levelNumber];
  xRotateMovement := 0;
  power := 0;

  repeat
    for i := 0 to 3 do
      begin
        for a := 0 to 3 do
          begin
            picture := AllTiles[(tileX div 1000) + a][(tiley div 1000) + i];
            SrcRect.x := -100 + (a * 256) - round((tileX mod 1000) * 0.256);
            SrcRect.y := -100 + (i * 256) - round((tileY mod 1000) * 0.256);
            SDL_Blitsurface(picture, nil, screen, @SrcRect);
          end;
      end;

    ROTOZOOMSURFACESIZEXY(70, 55, xRotate, 1, 1, rotated_width, rotated_height);
    SrcRect.x := 300 - round(rotated_width / 2);
    SrcRect.y := 300 - round(rotated_height / 2);

    Picture := SDL_DisplayFormat(rotatedPicture);
    SDL_SetColorKey(Picture, SDL_SRCCOLORKEY, key);
    Picture := ROTOZOOMSURFACE(Picture, xRotate, 1, 1);
    SDL_Blitsurface(Picture, nil, screen, @SrcRect);

    for i := 1 to 20 do
      begin
        //find when on map bullet is. +300 is moving from top left of screen (as recorded) to middle
        if (bullets[1][i] > 0) then  //if there is a bullet recorded for this array row
          begin
            SrcRect.x := round((bullets[1][i] - tilex) * 0.256) + 300;
            //*0.256 is because each one increment of tilex stands for 0.256 pixels
            SrcRect.y := round((bullets[2][i] - tiley) * 0.256) + 300;
            SDL_Blitsurface(bullet, nil, screen, @SrcRect);
            bullets[1][i] := bullets[1][i] +
                             round((bullets[4][i] + 55) * sin(((bullets[3][i] - 90) * 3.14) / 180));
            bullets[2][i] := bullets[2][i] +
                             round((bullets[4][i] + 55) * cos(((bullets[3][i] - 90) * 3.14) / 180));
            //bullets[4][i] is momentum plane has when bullet shot
          end;
      end;

    if (aliensKilledThisLevel = numberOfAliensOnEachLevel[levelNumber]) then
      //completed level
      begin
        if (levelNumber < 4) then
          begin
            writeln('Level ' + inttostr(levelNumber) + ' Complete');
            inc(levelNumber);
            aliensKilledThisLevel := 0;
            newLevelProcedure;
            timeLeft := timeLeftLevels[levelNumber];
          end
        else
          begin
            winScreen;
          end;
      end;
    for i := 1 to numberOfAliensOnEachLevel[levelNumber] do
      begin
        if (aliens[5][i] = 1) then  //if it is alive
          begin
            alienX := aliens[1][i];
            alienY := aliens[2][i];
            alienDestX := round(aliens[3][i]);
            alienDestY := round(aliens[4][i]);
            distance := sqrt((alienDestX - alienX) * (alienDestX - alienX) +
                             (alienDestY - alienY) * (alienDestY - alienY));
            alienX := alienX + 8 * cos(arccos((alienDestX - alienX) / distance));
            alienY := alienY + 8 * sin(arcsin((alienDestY - alienY) / distance));
            aliens[1][i] := alienX;
            aliens[2][i] := alienY;
            SrcRect.x := round((alienX - tilex) * 0.256) + 300;
            //*0.256 is because each one increment of tilex stands for 0.256 pixels
            SrcRect.y := round((alienY - tiley) * 0.256) + 300;
            if (aliens[6][i] = 1) then
              begin
                SDL_Blitsurface(alien1, nil, screen, @SrcRect);
                if (alienX + 200 > tileX) and (alienX < tileX) and
                  (alienY + 200 > tileY) and (alienY < tileY) then
                  begin
                    power := 60;
                    xRotate := random(360);
                  end;
              end;
            if (aliens[6][i] = 2) then
              begin
                SDL_Blitsurface(alien2, nil, screen, @SrcRect);
                alienHealth := round(aliens[7][i]);
                SrcRect.x := round((alienX - tilex) * 0.256) + 330;
                SrcRect.y := round((alienY - tiley) * 0.256) + 280;
                case alienHealth of
                  1 : SDL_Blitsurface(alien2health3, nil, screen, @SrcRect);
                  2 : SDL_Blitsurface(alien2health2, nil, screen, @SrcRect);
                  3 : SDL_Blitsurface(alien2health1, nil, screen, @SrcRect);
                end;
                if (alienX + 370 > tileX) and (alienX < tileX) and
                   (alienY + 370 > tileY) and (alienY < tileY) then
                  begin
                    power := 60;
                    xRotate := random(360);
                  end;
              end;
            for a := 1 to 20 do
              begin          //check for each bullet if it has hit an alien
                if (aliens[6][i] = 1) then    //small aliens
                  begin
                    if (alienX + 200 > bullets[1][a]) and  (alienX < bullets[1][a]) and
                       (alienY + 200 > bullets[2][a]) and (alienY < bullets[2][a]) then
                      begin
                        //+25 is to centre alien hitTesting around centre of alien not top left corner of alien.
                        bullets[1][a] := -1;
                        aliens[5][i] := 0;
                        inc(aliensKilledThisLevel);
                      end;
                  end;
                if (aliens[6][i] = 2) then   //large aliens
                  begin
                    if (alienX + 370 > bullets[1][a]) and  (alienX < bullets[1][a]) and
                       (alienY + 370 > bullets[2][a]) and (alienY < bullets[2][a]) then
                      begin
                        //+61 is to centre alien hitTesting around centre of alien not top left corner of alien.
                        bullets[1][a] := -1;
                        aliens[7][i] := aliens[7][i] - 1;
                        if (aliens[7][i] = 0) then
                          begin
                            aliens[5][i] := 0;
                            inc(aliensKilledThisLevel);
                          end;
                      end;
                  end;
              end;
          end;
      end;

    SrcRect.x := 600;
    SrcRect.y := 0;
    SDL_Blitsurface(theUK, nil, screen, @SrcRect);
    SrcRect.x := 685 + round((tileX - 1000000) / 121);
    SrcRect.y := 50 + round((tileY - 650000) / 121);
    SDL_Blitsurface(me, nil, screen, @SrcRect);

    for i := 1 to numberOfAliensOnEachLevel[levelNumber] do
      begin
        if (aliens[5][i] = 1) then
          begin
            SrcRect.x := 685 + round((aliens[1][i] - 1000000) / 121);
            SrcRect.y := 50 + round((aliens[2][i] - 650000) / 121);
            if (SrcRect.y < 411) then
              SDL_Blitsurface(alienDot, nil, screen, @SrcRect);
          end;
      end;

    writeText('Round: ', 610, 450);
    writeText(pchar(inttostr(levelNumber)), 700, 450);
    writeText('Time Left: ', 610, 550);

    textBig := False;
    if (timeLeft < 800) then
      begin
        textBig := True;
      end;
    writeText(pchar(inttostr(timeLeft)), 740, 550);
    case levelnumber of
      1 : begin
            writeText('Aliens have attacked France.', 610, 480);
            writeText('They are coming from the south towards', 610, 495);
            writeText('London. Save London!', 610, 510);
          end;
      2 : begin
            writeText('Aliens are now attacking from all sides.', 610, 480);
            writeText('Watch out for bigger aliens.', 610, 495);
          end;
      3 : begin
            writeText('Aliens are getting angry and ', 610, 480);
            writeText('are attacking London, Birmingham and', 610, 495);
            writeText('Manchester.', 610, 510);
          end;
      4 : begin
            writeText('Aliens are attacking more UK cities.', 610, 480);
          end;
    end;

    SDL_Flip(screen);
    SDL_Delay(30);
    //reset the screen by pasting a rectangle over it
    SDL_FillRect(screen, pSDLRect(0, 0, 600, 600), $000000);

    power := power * 0.99;

    tileX := tileX + round(power * sin(((xRotate - 90) * 3.14) / 180));
    tileY := tileY + round(power * cos(((xRotate - 90) * 3.14) / 180));

    xRotateMovement := xRotateMovement * 0.9;
    xRotate := xRotate + xRotateMovement;

    keystates := (SDL_GetKeyState(nil));
    SDL_PumpEvents();

    inc(bulletWait);
    if (bulletWait = bulletMaxWait) then
      begin
        bulletWait := 0;
        if (NewestBullet < 20) then
          begin
            inc(NewestBullet);
            bullets[3][NewestBullet] := xRotate;
            bullets[1][NewestBullet] := tilex;
            bullets[2][NewestBullet] := tiley;
            bullets[4][NewestBullet] := power;
            //same momentum bullet will have
          end
        else
          begin
            NewestBullet := 0;
          end;
      end;
    if (keystates[SDLK_UP] <> 0) then
      begin
        power := power + 1.4;
      end;
    if (keystates[SDLK_DOWN] <> 0) then
      begin
        power := power * 0.95;
      end;
    if (keystates[SDLK_LEFT] <> 0) then
      begin
        xRotateMovement := xRotateMovement + agility;
      end;
    if (keystates[SDLK_RIGHT] <> 0) then
      begin
        xRotateMovement := xRotateMovement - agility;
      end;
    if (keystates[SDLK_P] <> 0) then
      begin
        textBig := True;
        writeText('PAUSED', 250, 220);
        writeText('Press space to continue', 250, 270);
        SDL_Flip(screen);
        repeat
          keystates := (SDL_GetKeyState(nil));
          SDL_PumpEvents();
          sleep(20);
        until keystates[SDLK_SPACE] <> 0;
      end;
    if (keystates[SDLK_ESCAPE] <> 0) then
      begin
        SDL_QUIT; //QUITS SDL, Important in clearing up old stored data
        halt;
      end;
    dec(timeLeft);
    if (timeLeft < 0) then
      deadScreen;
    writeln(tileX);
    if (tileX < 1000600) then
      tileX := 1000600;
    if (tileX > 1032700) then
      tileX := 1032700;

  until totalDead = True;
end;

begin
  randomize;

  //text init
  TTF_INIT;
  loaded_font := TTF_OPENFONT('C:\WINDOWS\fonts\tahoma.ttf', 17);
  loaded_font_big := TTF_OPENFONT('C:\WINDOWS\fonts\tahoma.ttf', 30);
  NEW(colour_font);

  SrcRect.x := 0;
  SrcRect.y := 0;
  SrcRect.w := 60;
  SrcRect.h := 57;

  SDL_INIT(SDL_INIT_VIDEO);  //OPEN SDL
  screen := SDL_SETVIDEOMODE(959, 600, 32, SDL_SWSURFACE);

  loadAllImagesFromFile;

  frontPage := SDL_LOADBMP('FrontPage.bmp');
  deadPage := SDL_LOADBMP('DeadPage.bmp');
  winPage := SDL_LOADBMP('DonePage.bmp');

  rotatedPicture := SDL_LOADBMP('plane.bmp');
  bullet := SDL_LOADBMP('bullet.bmp');
  key := SDL_MAPRGB(bullet^.format, 255, 255, 255);
  SDL_SetColorKey(bullet, SDL_SRCCOLORKEY, key);
  alien1 := SDL_LOADBMP('alien1.bmp');
  SDL_SetColorKey(alien1, SDL_SRCCOLORKEY, key);
  alien2 := SDL_LOADBMP('alien2.bmp');
  SDL_SetColorKey(alien2, SDL_SRCCOLORKEY, key);
  alien2health1 := SDL_LOADBMP('alien2health1.bmp');
  alien2health2 := SDL_LOADBMP('alien2health2.bmp');
  alien2health3 := SDL_LOADBMP('alien2health3.bmp');
  theuk := SDL_LOADBMP('theUK.bmp');
  me := SDL_LOADBMP('me.bmp');
  alienDot := SDL_LOADBMP('alienDot.bmp');
  repeat
    startScreen;
    playingTheGame;
  until 1 = 2;
end.

Remarks

Could you use this program to help you to code your own map game?

Programming - a skill for life!

Fourteen programs (with five web versions) including 3D-Driving, GASP and Knowledge by Peter Hearnshaw