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
We could not resist flying the plane over Watford. See the approach in the screenshot below.

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?