Search Tips Advanced Search |
| Delphi Graphics and Game Programming Exposed with DirectX 7.0 by John Ayres Wordware Publishing, Inc. ISBN: 1556226373 Pub Date: 12/01/99
Search this book:
Previous | Table of Contents | Next
#### Enhancements We’ve created a complete game application that utilizes the vast majority of the techniques covered throughout the book. However, while it serves as a good case study, it certainly is not ready to be shrink-wrapped and put on store shelves. This is by design, as it leaves many opportunities for you, the reader, to exercise some creativity and take this basic game to the next level. The first thing that should be enhanced is the collision detection code. While it is very accurate, it is incredibly math intensive, and a better, more optimized method could probably be implemented. We are also redrawing the entire screen each frame, which could be optimized by only drawing the paddle and any active balls in their new positions using dirty rectangle animation techniques. Some general game enhancements might include the ability to track high scores, or perhaps show some skill statistics during the game over state. Additional balls can easily be activated in response to certain events, as the game already supports such functionality. It would be easy to tie such events to special blocks, where some blocks add another ball or perhaps increase/decrease the size of the paddle. You could even take this one step further and perhaps give the paddle a laser or other such weapon, or even make blocks that are indestructible or take multiple hits to destroy. Perhaps the easiest modification would be to provide better graphics, or even different graphic sets depending on the level. These could even be themed; you could have an ice level, or a lava level, or even a water level. Techniques from the special effects chapter or the palettes chapter could be used to make some blocks glow, rotate, fade in and out, etc. From a usability standpoint, it might be good to provide users with a configuration screen when the application starts so they can specify things like sound volume or skill level. This could include such things as a sound theme, utilizing better sound effects or different sound effects based on the user’s preference or level. Context-sensitive music would also improve the feel of the game. You might also want to support input devices other than the mouse, such as the keyboard or joystick. Providing force feedback support would really add a new dimension to this game. If you want to provide this type of configuration while in full screen video mode, a more general architecture should probably be created to support user interface objects like buttons and menus. A very fun enhancement for this game would be to include multiplayer support. One player could be at the bottom, and the other player could be at the top. This would also allow you to use some artificial intelligence techniques if you allowed the user to play against the computer. Using some of the incredible Internet technology now available in Delphi, it wouldn’t be too hard to make this game playable over the Internet. #### The Case Study Code The case study itself is presented here in its entirety so that you can see an entire game application laid out in all its glory. The code is heavily commented so as to make it easy to understand. This demonstrates how sound, user input, and graphics come together to form a complete, workable, usable game application. Listing 13-1: Delphi Blocks
    unit DelphiBlocksU;

    {******************************************************************************

        Delphi Blocks

        Author: John Ayres

        Based on the baseline architecture with mouse support, this project

        is a ‘Breakout’ style of game.  The object is to hit the moving ball

        with the paddle and destroy all of the blocks on the screen.  If the

        ball goes off of the bottom of the screen, it is lost and a new one is

        created.  You have three balls before the game is over.  This is a simple

        implementation of a game that shows how to use the mouse input

        functionality of DirectInput in a real-world situation.

     ******************************************************************************

        Copyright (c) 1999 by John Ayres, All Rights Reserved

        This code is freeware and can be used for any personal or commercial

        purposes without royalty or license.  Use at your own risk.

     ******************************************************************************}

    interface

    uses

      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

      DDraw, DInput, MouseThreadU, MMSystem, DSound;

    {these constants are used to modify specific attributes of the DirectX

     application, such as the color of the main form, the requested resolution,

     etc.}

    const

      FORMCOLOR    = clBlack;

      FORMBORDER   = bsNone;

      FORMCURSOR   = crNone;

      DXWIDTH      = 640;

      DXHEIGHT     = 480;

      DXCOLORDEPTH = 8;

      BUFFERCOUNT  = 1;

      COOPERATIVELEVEL = DDSCL_FULLSCREEN or DDSCL_ALLOWREBOOT or DDSCL_ALLOWMODEX

                         or DDSCL_EXCLUSIVE;

      SURFACETYPE      = DDSCAPS_COMPLEX or DDSCAPS_FLIP or DDSCAPS_PRIMARYSURFACE;

    const

      {this user-defined message is used to start the flipping loop}

      WM_DIRECTXACTIVATE = WM_USER + 200;

    type

      {controls the overall game state}

      TGameState = (gsIdle, gsPlaying, gsIntermission, gsGameOver);

      TBallMgr = class;

      {defines a ball sprite}

      TBall = class

        XPrev, YPrev,

        XPos, YPos,

        XVel, YVel: Real;

        BoundBox: TRect;

        Index: Integer;

        Manager: TBallMgr;

        procedure Move;

        procedure Draw;

      end;

      {defines the manager that will track all of the ball sprites. note that

       this gives us the ability to add and track several active balls at once}

      TBallMgr = class

        BallList: TList;

        VelCoefficient: Integer;

        FTotalBalls: Integer;

        function GetActiveBalls: Integer;

        constructor Create;

        destructor Destroy; override;

        procedure AddBall(XPos, YPos, XVel, YVel: Real; DecBalls: Boolean);

        procedure KillBall(Idx: Integer);

        procedure MoveBalls;

        procedure DrawBalls;

        procedure Clear;

        property TotalBalls: Integer read FTotalBalls write FTotalBalls;

        property ActiveBalls: Integer read GetActiveBalls;

      end;

      {defines the paddle sprite}

      TPaddle = class

        XPos, YPos: Integer;

        BoundBox: TRect;

        procedure Move;

        procedure Draw;

      end;

      TBlockMgr = class;

      {defines a single block}

      TBlock = class

        GraphicIdx: Integer;

        BoundBox: TRect;

        procedure Draw;

      end;

      {defines the manager that will track all of the blocks}

      TBlockMgr = class

        {this static grid can be modified to increase or decrease the overall

         block area and size}

        BlockGrid: array[0..14, 0..9] of TBlock;

        NumBlocks: Integer;

        destructor Destroy; override;

        procedure Draw;

        procedure InitializeBlocks;

        procedure KillBlock(Row, Col: Integer);

      end;

      {the primary form class}

      TfrmDXAppMain = class(TForm)

        procedure FormCreate(Sender: TObject);

        procedure FormDestroy(Sender: TObject);

        procedure FormKeyDown(Sender: TObject; var Key: Word;

                              Shift: TShiftState);

        procedure FormActivate(Sender: TObject);

      private

        { Private declarations }

        {necessary for our mouse input}

        FMouseThread: TMouseThread;

        {the block manager}

        FBlockMgr: TBlockMgr;

        {the player’s paddle sprite}

        FPaddle: TPaddle;

        {the ball manager}

        FBallMgr: TBallMgr;

        {tracks the current level}

        FLevel: integer;

        {tracks the score}

        FScore: integer;

        {we will need to add an active ball both when the current ball dies as

         well as when the user completes a level and starts a new one. thus,

         sometimes we want to add a ball without decreasing the amount of balls

         available. this variable will track this state}

        FUseBall: Boolean;

        {the current game state}

        FGameState: TGameState;

        {the MCI device ID used for MIDI playback}

        MCIDeviceID: UINT;

        {the MIDI notify message handler}

        procedure MCINotify(var Msg: TMessage); message MM_MCINOTIFY;

        {flips back to the GDI surface to display the exception error message}

        procedure ExceptionHandler(Sender: TObject; ExceptionObj: Exception);

        {the main rendering loop}

        procedure AppIdle(Sender: TObject; var Done: Boolean);

        {intercepts certain messages to provide appropriate functionality}

        procedure AppMessage(var Msg: TMsg; var Handled: Boolean);

        {flips the DirectX surfaces}

        procedure FlipSurfaces;

        {restores any lost surfaces}

        procedure RestoreSurfaces;

        {draws the contents of surfaces}

        procedure DrawSurfaces;

      public

        { Public declarations }

        procedure InitLevel;

        procedure StartGame;

      end;

      procedure PlayGameSound(SoundNum: Integer);

    var

      frmDXAppMain: TfrmDXAppMain;

      {the main DirectDraw interface}

      FDirectDraw: IDirectDraw4;

      {the interfaces for the primary and backbuffer surfaces}

      FPrimarySurface,

      FBackBuffer,

      FMouseCursors,                      // holds mouse cursor graphics

      FGraphics: IDirectDrawSurface4;     // holds the game graphics

      {the overall palette}

      FPalette: IDirectDrawPalette;

      {mouse cursor animation variables}

      FCursorFrame, FCursorFrameCount: Integer;

      {DirectInput objects for mouse functionality}

      FDirectInput: IDirectInput;

      FMouseDevice: IDirectInputDevice;

      {DirectSound object}

      DXSound: IDirectSound;

      {an array of sound buffers}

      Sounds: array[0..3] of IDirectSoundBuffer;

    const

      {these are used in defining events for the mouse input functionality}

      MOUSEEVENT = 0;

      QUITEVENT = 1;

      {these identify the sounds in our sound array}

      BLOCKHIT = 0;

      LOSTBALL = 1;

      SIDEHIT = 2;

      PADDLEHIT = 3;

      {the names of the wave files to be used}

      FileNames: array[BLOCKHIT..PADDLEHIT] of string = (‘bhump.wav’,

                                                         ‘bigboom.wav’,

                                                         ‘hitside.wav’,

                                                         ‘bwik.wav’);

    implementation

    uses

      DXTools, DDUtil, SyncObjs, Math, DSUtil{***}, gensuppt;

    {$R *.DFM}

    { *-->>  BASELINE APPLICATION CODE <<--* }

    { - the callback function used to ensure that the selected graphics mode -

      - is supported by DirectX                                              - }

    function EnumModesCallback(const EnumSurfaceDesc: TDDSurfaceDesc2;

                               Information: Pointer): HResult; stdcall;

    begin

      {if the height, width, and color depth match those specified in the

       constants, then indicate that the desired graphics mode is supported}

      if (EnumSurfaceDesc.dwHeight = DXHEIGHT) and

         (EnumSurfaceDesc.dwWidth = DXWIDTH) and

         (EnumSurfaceDesc.ddpfPixelFormat.dwRGBBitCount = DXCOLORDEPTH) then

        Boolean(Information^) := TRUE;

      Result := DDENUMRET_OK;

    end;

    { -> Events Hooked to the Application <- }

    { - this event is called when an exception occurs, and simply flips back -

      - to the GDI surface so that the exception dialog box can be read      - }

    procedure TfrmDXAppMain.ExceptionHandler(Sender: TObject;

                                             ExceptionObj: Exception);

    begin

      {disconnect the OnIdle event to shut off the rendering loop}

      Application.OnIdle := nil;

      {if the DirectDraw object was successfully created, flip to the GDI surface}

      if Assigned(FDirectDraw) then

        FDirectDraw.FlipToGDISurface;

      {display the exception message}

      MessageDlg(ExceptionObj.Message, mtError, [mbOK], 0);

      {reconnect the OnIdle event to reenter the rendering loop}

      Application.OnIdle := AppIdle;

    end;

    { - handles certain messages that are required to make DirectX function -

      - properly within Delphi                                              - }

    procedure TfrmDXAppMain.AppMessage(var Msg: TMsg; var Handled: Boolean);

    begin

      case Msg.Message of

        WM_ACTIVATEAPP:

          {unhook the OnIdle event when the application is being deactivated.

           this will stop all rendering}

          if not Boolean(Msg.wParam) then

          begin

            FMouseDevice.Unacquire;

            Application.OnIdle := nil;

          end

          else

            {upon activating the application, send ourselves the

             user-defined message}

            PostMessage(Application.Handle, WM_DIRECTXACTIVATE, 0, 0);

        WM_DIRECTXACTIVATE:

          begin

            {upon activating, restore all surfaces (reloading their memory

             as necessary), hook up the OnIdle event, and redraw the contents

             of all surfaces}

            FMouseDevice.Acquire;

            RestoreSurfaces;

            Application.OnIdle := AppIdle;

            DrawSurfaces;

          end;

        WM_SYSCOMMAND:

          begin

            {do not allow a screen saver to kick in}

            Handled := (Msg.wParam = SC_SCREENSAVE);

          end;

      end;

    end;

    { -> Form Events <- }

    { - initialize essential form properties - }

    procedure TfrmDXAppMain.FormCreate(Sender: TObject);

    begin

      {set up the application exception handler}

      Application.OnException := ExceptionHandler;

      {initialize form properties.  note that the FormStyle property must be

       set to fsStayOnTop}

      BorderStyle := bsNone;

      BorderIcons := [];

      FormStyle   := fsStayOnTop;

      Color       := clBlack;

      Cursor      := crNone;

      {initialize the mouse cursor to the top left of the screen}

      CurX := 0;

      CurY := 0;

      {hide the actual Windows mouse cursor}

      ShowCursor(FALSE);

    end;

    { - provides essential cleanup functionality - }

    procedure TfrmDXAppMain.FormDestroy(Sender: TObject);

    var

      iCount: Integer;

    begin

      {disengage our custom exception handler}

      Application.OnException := nil;

      {remember, we do not have to explicitly free the DirectDraw objects,

       as they will free themselves when they go out of context (such as

       when the application is closed)}

      {unacquire the mouse device}

      FMouseDevice.Unacquire;

      {fire the quit event so our mouse input thread will exit}

      SetEvent(FMouseEvents[QUITEVENT]);

      Sleep(100);

      {free any remaining data in the mouse click list}

      for iCount := 0 to MouseClickList.Count-1 do

        TMouseData(MouseClickList[iCount]).Free;

      {free the click list}

      MouseClickList.Free;

      {free our manager objects}

      FBallMgr.Free;

      FBlockMgr.Free;

      FPaddle.Free;

      {make sure that the MIDI device is closed before exiting the program}

      MCISendCommand(MCIDeviceID, MCI_CLOSE, 0, 0);

    end;

    { - this method initializes DirectX and creates all necessary objects - }

    procedure TfrmDXAppMain.FormActivate(Sender: TObject);

    var

      {we can only get a DirectDraw4 interface from the DirectDraw interface, so we

       need a temporary interface}

      TempDirectDraw: IDirectDraw;

      {structures required for various methods}

      DDSurface: TDDSurfaceDesc2;

      DDSCaps: TDDSCaps2;

      {flag used to determine if the desired graphics mode is supported}

      SupportedMode: Boolean;

      {mouse property structure}

      MouseProp: TDIPropDWord;

      SrcRect: TRect;

      iCount: Integer;

      {necessary structures for initializing MIDI output}

      OpenParms: TMCI_Open_Parms;

      StatusParms: TMCI_Status_Parms;

      SetParms: TMCI_Set_Parms;

      PlayParms: TMCI_Play_Parms;

    begin

      {if DirectDraw has already been initialized, exit}

      if Assigned(FDirectDraw) then exit;

      {create a temporary DirectDraw object. this is used to create the

       desired DirectDraw4 object}

      DXCheck( DirectDrawCreate(nil, TempDirectDraw, nil) );

      try

        {we can only get a DirectDraw4 interface through the QueryInterface

         method of the DirectDraw object}

        DXCheck( TempDirectDraw.QueryInterface(IID_IDirectDraw4, FDirectDraw) );

      finally

        {now that we have the DirectDraw4 object, the temporary DirectDraw

         object is no longer needed}

        TempDirectDraw := nil;

      end;

      {set the cooperative level to that defined in the constants}

      DXCheck( FDirectDraw.SetCooperativeLevel(Handle, COOPERATIVELEVEL) );

      {hook up the application message handler}

      Application.OnMessage := AppMessage;

      {call EnumDisplayModes and verify that the desired graphics mode is

       indeed supported}

      FillChar(DDSurface, SizeOf(TDDSurfaceDesc2), 0);

      DDSurface.dwSize   := SizeOf(TDDSurfaceDesc2);

      DDSurface.dwFlags  := DDSD_HEIGHT or DDSD_WIDTH or DDSD_PIXELFORMAT;

      DDSurface.dwHeight := DXHEIGHT;

      DDSurface.dwWidth  := DXWIDTH;

      DDSurface.ddpfPixelFormat.dwSize := SizeOf(TDDPixelFormat_DX6);

      DDSurface.ddpfPixelFormat.dwRGBBitCount := DXCOLORDEPTH;

      SupportedMode := FALSE;

      DXCheck( FDirectDraw.EnumDisplayModes(0, @DDSurface, @SupportedMode,

                                            EnumModesCallback) );

      {if the desired graphics mode is not supported by the DirectX drivers,

       display an error message and shut down the application}

      if not SupportedMode then

      begin

        MessageBox(Handle, PChar(‘The installed DirectX drivers do not support a ’+

                                 ‘display mode of: ’+IntToStr(DXWIDTH)+‘ X ’+

                                 IntToStr(DXHEIGHT)+‘, ’+IntToStr(DXCOLORDEPTH)+

                                 ‘ bit color’), ‘Unsupported Display Mode Error’,

                                 MB_ICONERROR or MB_OK);

        Close;

        Exit;

      end;

      {set the display resolution and color depth to that defined in the constants}

      DXCheck( FDirectDraw.SetDisplayMode(DXWIDTH, DXHEIGHT, DXCOLORDEPTH, 0, 0) );

      {initialize the DDSurface structure to indicate that we will be creating a

       complex flipping surface with one back buffer}

      FillChar(DDSurface, SizeOf(TDDSurfaceDesc2), 0);

      DDSurface.dwSize  := SizeOf(TDDSurfaceDesc2);

      DDSurface.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;

      DDSurface.ddsCaps.dwCaps := SURFACETYPE;

      DDSurface.dwBackBufferCount := BUFFERCOUNT;

      {create the primary surface object}

      DXCheck( FDirectDraw.CreateSurface(DDSurface, FPrimarySurface, nil) );

      {indicate that we want to retrieve a pointer to the back buffer (the surface

       immediately behind the primary surface in the flipping chain) }

      FillChar(DDSCaps, SizeOf(TDDSCaps2), 0);

      DDSCaps.dwCaps := DDSCAPS_BACKBUFFER;

      {retrieve the surface}

      DXCheck( FPrimarySurface.GetAttachedSurface(DDSCaps, FBackBuffer) );

      {at this point, offscreen buffers and other surfaces should be created. other

       DirectDraw objects should be created and initialized as well, such as

       palettes. the contents of all surfaces should also be initialized at this

       point}

      {load the palette for the game’s images}

      FPalette := DDLoadPalette(FDirectDraw, ExtractFilePath(ParamStr(0))+

                                ‘Images.bmp’);

      {attach this palette to the primary surface}

      FPrimarySurface.SetPalette(FPalette);

      {load the actual graphics used in the game}

      FGraphics := DDLoadBitmap(FDirectDraw, ExtractFilePath(ParamStr(0))+

                                ‘Images.bmp’);

      {initialize the surface description structure to create the mouse

       cursor surface}

      FillChar(DDSurface, SizeOf(TDDSurfaceDesc2), 0);

      DDSurface.dwSize   := SizeOf(TDDSurfaceDesc2);

      DDSurface.dwFlags  := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH;

      DDSurface.dwWidth  := 224;

      DDSurface.dwHeight := 32;

      DDSurface.ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN;

      {create the mouse cursor surface}

      DXCheck( FDirectDraw.CreateSurface(DDSurface, FMouseCursors, nil) );

      {copy the mouse cursor graphics into the mouse cursor surface}

      SrcRect := Rect(256, 520, 256+(6*32)+32, 552);

      FMouseCursors.BltFast(0, 0, FGraphics, SrcRect, DDBLTFAST_NOCOLORKEY or

                            DDBLTFAST_WAIT);

      {set the transparent color in both the mouse cursor graphics as well as

       the game graphics}

      DDSetColorKey(FMouseCursors, $00000000);

      DDSetColorKey(FGraphics, $00000000);

      {now that all of DirectDraw is initialized, begin initializing DirectInput}

      {create our mouse click list object}

      MouseClickList := TList.Create;

      {create the DirectInput object}

      DXCheck(DirectInputCreate(hInstance, DIRECTINPUT_VERSION, FDirectInput,nil));

      {create a DirectInput device for the mouse}

      DXCheck( FDirectInput.CreateDevice(GUID_SysMouse, FMouseDevice, nil) );

      {set the appropriate data format for mouse data}

      DXCheck( FMouseDevice.SetDataFormat(c_dfDIMouse) );

      {set the cooperative level. note that we must set a nonexclusive cooperative

       level for mice as the mouse must be shared with other Windows applications}

      DXCheck( FMouseDevice.SetCooperativeLevel(Handle,DISCL_FOREGROUND or

                                                DISCL_NONEXCLUSIVE) );

      {initialize the structure to instruct DirectInput that we want an input

       buffer big enough to hold 64 pieces of input information}

      MouseProp.diph.dwSize := SizeOf(TDIPropDWord);

      MouseProp.diph.dwHeaderSize := SizeOf(TDIPropHeader);

      MouseProp.diph.dwObj := 0;

      MouseProp.diph.dwHow := DIPH_DEVICE;

      MouseProp.dwData := 64;

      {set the actual buffer size property}

      DXCheck( FMouseDevice.SetProperty(DIPROP_BUFFERSIZE^, MouseProp.diph) );

      {create our events, one for indicating new mouse input data, one for

       indicating that the application is terminating}

      FMouseEvents[MOUSEEVENT] := CreateEvent(nil, FALSE, FALSE, ‘MouseEvent’);

      FMouseEvents[QUITEVENT] := CreateEvent(nil, FALSE, FALSE, ‘QuitEvent’);

      {create our critical section synchronization object}

      FCritSec := TCriticalSection.Create;

      {set the event notification}

      DXCheck( FMouseDevice.SetEventNotification(FMouseEvents[MOUSEEVENT]) );

      {create our secondary input thread}

      FMouseThread := TMouseThread.Create(TRUE);

      {we want it to be supremely responsive}

      FMouseThread.Priority := tpTimeCritical;

      {indicate it should free itself when it terminates}

      FMouseThread.FreeOnTerminate := TRUE;

      {post a message that will hook up the OnIdle event and start the

       main rendering loop}

      PostMessage(Handle, WM_ACTIVATEAPP, 1, 0);

      {now, initialize game management objects}

      {create the ball manager}

      FBallMgr := TBallMgr.Create;

      {create and initialize the paddle}

      FPaddle := TPaddle.Create;

      FPaddle.YPos := 420;

      {create and initialize the block manager}

      FBlockMgr := TBlockMgr.Create;

      FBlockMgr.InitializeBlocks;

      {open the MIDI output device, loading the Canyon MIDI song}

      OpenParms.lpstrDeviceType  := ‘sequencer’;

      OpenParms.lpstrElementName := PChar(ExtractFilePath(ParamStr(0))+

                                          ‘custom16.mid’);

      MCISendCommand(0, MCI_OPEN, MCI_OPEN_TYPE or MCI_OPEN_ELEMENT or MCI_WAIT,

                     LongInt(@OpenParms));

      {save a handle to the device}

      MCIDeviceID := OpenParms.wDeviceID;

      {set the time format to milliseconds}

      SetParms.dwTimeFormat := MCI_FORMAT_MILLISECONDS;

      MCISendCommand(MCIDeviceID, MCI_SET, MCI_WAIT or MCI_SET_TIME_FORMAT,

                     LongInt(@SetParms));

      {commence song playback, indicating that we wish to receive an MM_MCINOTIFY

       message when the playback has completed}

      PlayParms.dwCallback := Handle;

      MCISendCommand(MCIDeviceID, MCI_PLAY, MCI_NOTIFY, LongInt(@PlayParms));

      {create the DirectSound object}

      DirectSoundCreate(nil, DXSound, nil);

      {set its cooperative level (we won’t change the primary buffer format, so

       a cooperative level of Normal is all that is needed)}

      DXSound.SetCooperativeLevel(Handle, DSSCL_NORMAL);

      {load in the sounds}

      for iCount := BLOCKHIT to PADDLEHIT do

        Sounds[iCount] := DSLoadSound(DXSound, ExtractFilePath(ParamStr(0))+

                                      FileNames[iCount], FALSE);

      {finally, acquire the mouse and fire off the mouse input thread}

      FMouseDevice.Acquire;

      FMouseThread.Resume;

    end;

    { -> Form Methods <- }

    { - the message handler for the MM_MCINOTIFY message - }

    procedure TfrmDXAppMain.MCINotify(var Msg: TMessage);

    var

      PlayParms: TMCI_Play_Parms;

    begin

      {replay the song from the beginning}

      PlayParms.dwCallback := Handle;

      PlayParms.dwFrom     := 0;

      MCISendCommand(MCIDeviceID, MCI_PLAY, MCI_NOTIFY or MCI_FROM,

                     LongInt(@PlayParms));

    end;

    { - this method is called in order to flip the surfaces - }

    procedure TfrmDXAppMain.FlipSurfaces;

    var

      DXResult: HResult;

    begin

      {perform the page flip. note that the DDFLIP_WAIT flag has been used,

       indicating that the function will not return until the page flip has been

       performed. this could be removed, allowing the application to perform other

       processing until the page flip occurs. however, the application will need

       to continuously call the Flip method to ensure that the page flip takes

       place}

      DXResult := FPrimarySurface.Flip(nil, DDFLIP_WAIT);

      {pause until the flip has been performed}

      while FPrimarySurface.GetFlipStatus(DDGFS_ISFLIPDONE) <> DD_OK do;

      {if the surfaces were lost, restore them. on any other error,

       raise an exception}

      if DXResult = DDERR_SURFACELOST then

        RestoreSurfaces

      else if DXResult <> DD_OK then

        DXCheck(DXResult);

    end;

    { - this method is called when the surface memory is lost  -

      - and must be restored.  surfaces in video memory that   -

      - contain bitmaps must be reinitialized in this function - }

    procedure TfrmDXAppMain.RestoreSurfaces;

    var

      SrcRect: TRect;

    begin

      {restore the primary surface, which in turn restores any implicit surfaces}

      FPrimarySurface._Restore;

      {reload the game images}

      DDReloadBitmap(FGraphics, ExtractFilePath(ParamStr(0))+‘Images.bmp’);

      {restore the mouse cursor surface}

      FMouseCursors._Restore;

      {copy the mouse cursor graphics into the mouse cursor surface}

      SrcRect := Rect(256, 520, 256+(6*32)+32, 552);

      FMouseCursors.BltFast(0, 0, FGraphics, SrcRect, DDBLTFAST_NOCOLORKEY or

                            DDBLTFAST_WAIT);

    end;

    { - this method is continuously called by the application, and provides -

      - the main rendering loop.  this could be replaced by a custom        -

      - while..do loop                                                      - }

    procedure TfrmDXAppMain.AppIdle(Sender: TObject; var Done: Boolean);

    begin

      {indicates that the application should continuously call this method}

      Done := FALSE;

      {if DirectDraw has not been initialized, exit}

      if not Assigned(FDirectDraw) then Exit;

      {draw surface content and flip the surfaces. notice that we must use a

       critical section here, as we may need to access the mouse cursor coordinate

       variables, and we can’t do that if the mouse input thread is accessing

       them at the same time}

      FCritSec.Enter;

      DrawSurfaces;

      FlipSurfaces;

      FCritSec.Leave;

    end;

    { - this method is called when the contents of the surfaces need to be  -

      - drawn. it will be continuously called by the AppIdle method, so any  -

      - rendering or animation could be done within this method             - }

    procedure TfrmDXAppMain.DrawSurfaces;

    var

      SrcRect: TRect;

      SrfcDC: HDC;

      WorkCanvas: TCanvas;

      MouseData: TMouseData;

      iCount: Integer;

    begin

      {copy the background into the back buffer, erasing the last

       frame of animation}

      SrcRect := Rect(0, 0, DXWIDTH, DXHEIGHT);

      FBackBuffer.BltFast(0, 0, FGraphics, SrcRect, DDBLTFAST_NOCOLORKEY or

                          DDBLTFAST_WAIT);

      {draw all of the blocks that are still alive}

      FBlockMgr.Draw;

      {based on the game state...}

      case FGameState of

        gsIdle : begin

                   {if we are idle, we want to show our animated mouse cursor}

                   ShowMouse := TRUE;

                   {draw the Start and Quit buttons}

                   SrcRect := Rect(0, 520, 128, 584);

                   FBackBuffer.BltFast(96, 350, FGraphics, SrcRect,

                                       DDBLTFAST_NOCOLORKEY or DDBLTFAST_WAIT);

                   SrcRect := Rect(128, 520, 256, 584);

                   FBackBuffer.BltFast(416, 350, FGraphics, SrcRect,

                                       DDBLTFAST_NOCOLORKEY or DDBLTFAST_WAIT);

                   {draw the game title}

                   WorkCanvas := TCanvas.Create;

                   FBackBuffer.GetDC(SrfcDC);

                   with WorkCanvas do

                   begin

                     {set up the canvas to use the surface}

                     Handle := SrfcDC;

                     {initialize the canvas font attributes}

                     Font.Name  := ‘Arial’;

                     Font.Color := clRed;

                     Font.Style := [fsBold];

                     Font.Size  := 64;

                     Brush.Style := bsClear;

                     {draw the actual title}

                     TextOut(320-(TextWidth(‘Delphi Blocks!’) div 2), 125,

                             ‘Delphi Blocks!’);

                     {unhook the canvas from the surface}

                     Handle := 0;

                   end;

                   {release the surface device context}

                   FBackBuffer.ReleaseDC(SrfcDC);

                   {free the canvas}

                   WorkCanvas.Free;

                   {determine if the mouse was clicked in one of the buttons}

                   if MouseClickList.Count>0 then

                   begin

                    {while there are any events in the mouse click list, retrieve

                     them all and empty the list}

                     while MouseClickList.Count > 0 do

                     begin

                       {free the current mouse data}

                       if MouseData <> nil then

                       begin

                         MouseData.Free;

                         MouseData := nil;

                       end;

                       {retrieve and delete the click event}

                       MouseData := TMouseData(MouseClickList[0]);

                       MouseClickList.Delete(0);

                     end;

                     {if the quit button was pushed...}

                     if PtInRect(Rect(416, 350, 544, 414),

                        Point(MouseData.XPos, MouseData.YPos)) then

                       Close;

                     {if the start button was pushed...}

                     if PtInRect(Rect(96, 350, 224, 414),

                        Point(MouseData.XPos, MouseData.YPos)) then

                       StartGame;

                     {free the current mouse data object}

                     MouseData.Free;

                     MouseData := nil;

                   end;

                 end;

        gsPlaying : begin

                      {move and draw all of the active balls}

                      FBallMgr.MoveBalls;

                      FBallMgr.DrawBalls;

                      {display a ball for each one the player has left}

                      SrcRect := Rect(252, 507, 261, 516);

                      for iCount := 0 to FBallMgr.TotalBalls-1 do

                        FBackBuffer.BltFast(10+iCount*16, 440, FGraphics, SrcRect,

                                            DDBLTFAST_SRCCOLORKEY or

                                            DDBLTFAST_WAIT);

                      {create a canvas and retrieve a surface device context}

                      WorkCanvas := TCanvas.Create;

                      FBackBuffer.GetDC(SrfcDC);

                      with WorkCanvas do

                      begin

                        {hook the canvas up to the surface}

                        Handle := SrfcDC;

                        {initialize the surface font attributes}

                        Font.Name  := ‘Arial’;

                        Font.Color := clRed;

                        Font.Style := [fsBold];

                        Font.Size  := 12;

                        Brush.Style := bsClear;

                        {draw the number of balls remaining}

                        TextOut(10, 450, ‘Score: ’+IntToStr(FScore));

                        {unhook the canvas from the surface}

                        Handle := 0;

                      end;

                      {release the surface device context}

                      FBackBuffer.ReleaseDC(SrfcDC);

                      {free the canvas}

                      WorkCanvas.Free;

                      {if there are no more blocks alive...}

                      if FBlockMgr.NumBlocks < 1 then

                      begin

                        {increment the current level}

                        Inc(FLevel);

                        {initialize a new level}

                        InitLevel;

                        {and go to intermission}

                        FGameState := gsIntermission;

                      end;

                    end;

        gsIntermission : begin

                           {we are starting to play, so hide the animated

                            mouse cursor}

                           ShowMouse := FALSE;

                           {create a canvas and retrieve a surface device context}

                           WorkCanvas := TCanvas.Create;

                           FBackBuffer.GetDC(SrfcDC);

                           with WorkCanvas do

                           begin

                             {hook the canvas up to the surface}

                             Handle := SrfcDC;

                             {initialize the canvas font attributes}

                             Font.Name  := ‘Arial’;

                             Font.Color := clRed;

                             Font.Style := [fsBold];

                             Font.Size  := 64;

                             Brush.Style := bsClear;

                             {draw the level}

                             TextOut(320-(TextWidth(‘Level ’+IntToStr(FLevel))

                                     div 2), 125, ‘Level ’+IntToStr(FLevel));

                             {unhook the canvas from the surface}

                             Handle := 0;

                           end;

                           {release the surface device context}

                           FBackBuffer.ReleaseDC(SrfcDC);

                           {release the canvas}

                           WorkCanvas.Free;

                           {flip the surfaces so that this new graphic

                            is displayed}

                           FlipSurfaces;

                           {pause for 2 seconds}

                           Sleep(2000);

                           {switch back to playing the game}

                           FGameState := gsPlaying;

                           {add an active ball}

                           FBallMgr.AddBall(DXWIDTH div 2, 300, -1, 1, FUseBall);

                         end;

        gsGameOver : begin

                       {hide the animated mouse cursor}

                       ShowMouse := FALSE;

                       {create a canvas and retrieve a surface device context}

                       WorkCanvas := TCanvas.Create;

                       FBackBuffer.GetDC(SrfcDC);

                       with WorkCanvas do

                       begin

                         {hook the canvas up to the surface}

                         Handle := SrfcDC;

                         {initialize the canvas font attributes}

                         Font.Name  := ‘Arial’;

                         Font.Color := clRed;

                         Font.Style := [fsBold];

                         Font.Size  := 56;

                         Brush.Style := bsClear;

                         {draw the Game Over text}

                         TextOut(320-(TextWidth(‘G A M E   O V E R’) div 2),

                                 125, ‘G A M E   O V E R’);

                         {unhook the canvas from the surface}

                         Handle := 0;

                       end;

                       {release the surface device context}

                       FBackBuffer.ReleaseDC(SrfcDC);

                       {release the canvas}

                       WorkCanvas.Free;

                       {flip the surfaces to display the new graphic}

                       FlipSurfaces;

                       {pause for 2 seconds}

                       Sleep(2000);

                       {and go to idle}

                       FGameState := gsIdle;

                     end;

      end;

      {if we wish to display the mouse cursor...}

      if ShowMouse then

      begin

        {increment the frame count}

        Inc(FCursorFrameCount);

        {when we need to increment the frame number...}

        if FCursorFrameCount mod 7 = 0 then

        begin

          {increment the current mouse cursor frame}

          Inc(FCursorFrame);

          {roll it over if necessary}

          if FCursorFrame > 6 then

            FCursorFrame := 0;

        end;

        {draw cursor to back buffer}

        SrcRect := Rect(FCursorFrame*CURSORWIDTH, 0,

                        (FCursorFrame*CURSORWIDTH)+CURSORWIDTH, CURSORHEIGHT);

        FBackBuffer.BltFast(CurX, CurY, FMouseCursors, SrcRect,

                            DDBLTFAST_SRCCOLORKEY OR DDBLTFAST_WAIT);

      end

      else

      begin

        {move the paddle and draw it to the back buffer}

        FPaddle.Move;

        FPaddle.Draw;

      end;

    end;

    { -> Deletable Events <- }

    ///

    { - as a matter of convenience this framework will terminate when the -

      - Escape key is pressed, but this should probably be deleted and    -

      - replaced with your own terminate methods                          - }

    procedure TfrmDXAppMain.FormKeyDown(Sender: TObject; var Key: Word;

      Shift: TShiftState);

    begin

      if Key = VK_ESCAPE then

        Close;

    end;

    ///

    { *-->>  END BASELINE APPLICATION CODE <<--* }

    {initializes a level}

    procedure TfrmDXAppMain.InitLevel;

    begin

      {reinitialize all of the blocks}

      FBlockMgr.InitializeBlocks;

      {clear any active balls}

      FBallMgr.Clear;

      {set the ball manager’s velocity coefficient based on the current level.

       this makes the balls move faster at higher levels}

      FBallMgr.VelCoefficient := FLevel div 2;

      if FBallMgr.VelCoefficient < 2 then

        FBallMgr.VelCoefficient := 2;

      {we’ll be creating a new ball, but since we’re at the start of a level,

       we don’t want to decrement the available ball count}

      FUseBall := FALSE;

    end;

    {starts a game}

    procedure TfrmDXAppMain.StartGame;

    begin

      {initialize the level count}

      FLevel := 1;

      {initialize the score}

      FScore := 0;

      {initialize the level}

      InitLevel;

      {give the user 3 balls}

      FBallMgr.TotalBalls := 3;

      {upon starting a game, we want to decrement the overall ball count when

       we create a new one}

      FUseBall := TRUE;

      {go to intermission}

      FGameState := gsIntermission;

    end;

    {plays a sound}

    procedure PlayGameSound(SoundNum: Integer);

    begin

      {set the position of the sound to the start}

      Sounds[SoundNum].SetCurrentPosition(0);

      {commence sound playback}

      Sounds[SoundNum].Play(0, 0, 0);

    end;

    { TBall }

    procedure TBall.Draw;

    var

      SrcRect: TRect;

    begin

      {draw the ball to the back buffer}

      SrcRect := Rect(252, 507, 261, 516);

      FBackBuffer.BltFast(Trunc(XPos), Trunc(YPos), FGraphics, SrcRect,

                          DDBLTFAST_SRCCOLORKEY OR DDBLTFAST_WAIT);

    end;

    procedure TBall.Move;

    var

      ISectRect: TRect;

      iRow, iCol: Integer;

      BlockCollision: boolean;

      DeflectAngle: Real;

      {assumes p1-p2, p3-p4 are the lines; returns true if the lines intersect}

      function CheckLineIntersect(P1, P2, P3, P4: TPoint): boolean;

      var

        A, B, C: TPoint;

        Denom: integer;

        Alpha, Beta: double;

      begin

        {assume the line segments do not intersect}

        Result := FALSE;

        {this uses an algorithm found in Graphics Gems III (ISBN 0-12-409673-5) by

         David Kirk (Academic Press) on page 199. the algorithm is by Franklin

         Antonio. basically, it is a quick way to determine if two line segments

         intersect}

        A := Point(P2.X-P1.X, P2.Y-P1.Y);

        B := Point(P3.X-P4.X, P3.Y-P4.Y);

        C := Point(P1.X-P3.X, P1.Y-P3.Y);

        Denom := (A.Y*B.X)-(A.X*B.Y);

        {must check to see if denom is 0 here}

        if Denom <> 0 then

        begin

          {determine the alpha and beta values}

          Alpha := ((B.Y*C.X)-(B.X*C.Y))/Denom;

          Beta := ((A.X*C.Y)-(A.Y*C.X))/Denom;

          {if both alpha and beta are in the range 0<=X<=1, then the lines

           intersect}

          if ((Alpha>=0) and (Alpha<=1)) and ((Beta>=0) and (Beta <= 1)) then

            Result := TRUE;

        end;

      end;

    begin

      {if the game state is in game over, exit immediately}

      if frmDXAppMain.FGameState = gsGameOver then

        Exit;

      {record the previous position of the ball}

      XPrev := XPos;

      YPrev := YPos;

      {update the ball’s position according to its velocity}

      XPos := XPos + XVel;

      YPos := YPos + YVel;

      {determine collision with sides}

      {left}

      if XPos < 5 then

      begin

        XPos := 5;

        XVel := 0 - XVel;

        {indicate we hit the side}

        PlayGameSound(SIDEHIT);

      end;

      {right}

      if XPos > DXWIDTH - 5 - 9 then

      begin

        XPos := DXWIDTH - 5 - 9;

        XVel := 0 - XVel;

        {indicate we hit the side}

        PlayGameSound(SIDEHIT);

      end;

      {top}

      if YPos < 5 then

      begin

        YPos := 5;

        YVel := 0 - YVel;

        {indicate we hit the side}

        PlayGameSound(SIDEHIT);

      end;

      {a collision with the bottom indicates that the ball has died}

      if YPos > DXHEIGHT - 5 - 9 then

      begin

        {delete this ball from the managers list}

        Manager.BallList.Delete(Index);

        {play the lost ball sound}

        PlayGameSound(LOSTBALL);

        {if there are no more active balls...}

        if Manager.ActiveBalls < 1 then

          {...but there are balls left...}

          if Manager.TotalBalls > 0 then

          begin

            {indicate we will decrement the available balls when we create

             a new one}

            frmDXAppMain.FUseBall := TRUE;

            {go to intermission}

            frmDXAppMain.FGameState := gsIntermission;

          end

          else

            {...otherwise, the game is over}

            frmDXAppMain.FGameState := gsGameOver;

        {free this ball}

        Free;

        {the ball has died, so exit}

        Exit;

      end;

      {keep up with the bounding box}

      BoundBox := Rect(Trunc(XPos), Trunc(YPos), Trunc(XPos+9), Trunc(YPos+9));

      {check for collision with paddle}

      if IntersectRect(ISectRect, BoundBox, frmDXAppMain.FPaddle.BoundBox) then

      begin

        {the deflection angle is based on the point where the ball hits the paddle.

         the ball will deflect at a greater angle near the edges, but it will

         deflect almost straight up at the center. this gives the user the

         ability to control the direction of the ball. the deflection angle

         increases from 0 at the very tip to 90 degrees at the center}

        DeflectAngle := (180/92)*(ISectRect.Left-

                                  frmDXAppMain.FPaddle.BoundBox.Left);

        if DeflectAngle > 90 then

          DeflectAngle := 180 - DeflectAngle;

        {reverse the vertical velocity}

        YVel := 0 - Sin(DegToRad(DeflectAngle))*Manager.VelCoefficient;

        {initialize the horizontal velocity based on the deflection angle and

         its current direction}

        if XVel >=0 then

          XVel := Cos(DegToRad(DeflectAngle))*Manager.VelCoefficient

        else

          XVel := 0 - Cos(DegToRad(DeflectAngle))*Manager.VelCoefficient;

        {indicate that we hit the paddle}

        PlayGameSound(PADDLEHIT);

      end;

      {now, check for collisions with blocks}

      for iRow := 0 to 9 do

        for iCol := 0 to 14 do

          {if there is an active block at this position...}

          if (frmDXAppMain.FBlockMgr.BlockGrid[iCol, iRow] <> nil) then

            {perform a quick bounding box collision test}

            if IntersectRect(ISectRect, BoundBox,

                             frmDXAppMain.FBlockMgr.BlockGrid[iCol, iRow].BoundBox)

                             then

            begin

              {the bounding boxes have collided. now, since we want to support

               balls that move at any velocity, we must check the line segment

               formed by the ball’s current and previous location with each line

               segment of the current block. since a ball can strike a block from

               any direction, we must determine which side of the block was struck

               in order to determine which velocity value is to be reversed}

              BlockCollision := FALSE;

              {check bottom lines}

              if CheckLineIntersect(Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Left, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Bottom),

                                    Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Right, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Bottom),

                                    Point(Trunc(XPrev+5), Trunc(YPrev+5)),

                                    Point(Trunc(XPos+5), Trunc(YPos+5))) then

              begin

                {the bottom of the block was struck, so reverse the vertical

                 velocity and signal a collision}

                YVel := 0 - YVel;

                BlockCollision := TRUE;

              end

              else

              {check top lines}

              if CheckLineIntersect(Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Left, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Top),

                                    Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Right, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Top),

                                    Point(Trunc(XPrev+5), Trunc(YPrev+5)),

                                    Point(Trunc(XPos+5), Trunc(YPos+5))) then

              begin

                {the top of the block was struck, so reverse the vertical

                 velocity and signal a collision}

                YVel := 0 - YVel;

                BlockCollision := TRUE;

              end

              else

              {check right lines}

              if CheckLineIntersect(Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Right, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Bottom),

                                    Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Right, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Top),

                                    Point(Trunc(XPrev+5), Trunc(YPrev+5)),

                                    Point(Trunc(XPos+5), Trunc(YPos+5))) then

              begin

                {the right side of the block was struck, so reverse the horizontal

                 velocity and signal a collision}

                XVel := 0 - XVel;

                BlockCollision := TRUE;

              end

              else

              {check left lines}

              if CheckLineIntersect(Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Left, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Bottom),

                                    Point(frmDXAppMain.FBlockMgr.BlockGrid[iCol,

                                    iRow].BoundBox.Left, frmDXAppMain.FBlockMgr.

                                    BlockGrid[iCol, iRow].BoundBox.Top),

                                    Point(Trunc(XPrev+5), Trunc(YPrev+5)),

                                    Point(Trunc(XPos+5), Trunc(YPos+5))) then

              begin

                {the left side of the block was struck, so reverse the horizontal

                 velocity and signal a collision}

                XVel := 0 - XVel;

                BlockCollision := TRUE;

              end;

              {if the ball collided with a block, kill the block}

              if BlockCollision then

              begin

                frmDXAppMain.FBlockMgr.KillBlock(iRow, iCol);

                {indicate that we hit a block}

                PlayGameSound(BLOCKHIT);

                {increase the score}

                frmDXAppMain.FScore := frmDXAppMain.FScore+5*frmDXAppMain.FLevel;

              end;

            end;

    end;

    { TBallMgr }

    procedure TBallMgr.AddBall(XPos, YPos, XVel, YVel: Real; DecBalls: Boolean);

    var

      TempBall: TBall;

    begin

      {if we are to decrement the total ball count, then do so}

      if DecBalls then

        Dec(FTotalBalls);

      {create a ball sprite}

      TempBall := TBall.Create;

      {initialize its position}

      TempBall.XPos  := XPos;

      TempBall.YPos  := YPos;

      TempBall.XPrev := XPos;

      TempBall.YPrev := YPos;

      {initialize its velocity}

      TempBall.XVel  := XVel;

      TempBall.YVel  := YVel;

      {initialize its bounding box}

      TempBall.BoundBox := Rect(Trunc(XPos), Trunc(YPos), Trunc(XPos+9),

                                Trunc(YPos+9));

      {hook it to the manager}

      TempBall.Manager := Self;

      {add the ball to the list}

      BallList.Add(TempBall);

      {set its index in the list}

      TempBall.Index := BallList.Count-1;

    end;

    constructor TBallMgr.Create;

    begin

      {create the ball list}

      BallList := TList.Create;

      {initialize the velocity coefficient. increasing or decreasing this value

       will have a similar effect on the speed of the balls}

      VelCoefficient := 2;

    end;

    destructor TBallMgr.Destroy;

    var

      iCount: Integer;

    begin

      {destroy any balls in the list}

      for iCount := 0 to BallList.Count-1 do

        TBall(BallList[iCount]).Free;

      {free the ball list}

      BallList.Free;

      {finish the destruction}

      inherited Destroy;

    end;

    procedure TBallMgr.DrawBalls;

    var

      iCount: Integer;

    begin

      {draw each ball in the list}

      for iCount := 0 to BallList.Count-1 do

        TBall(BallList[iCount]).Draw;

    end;

    procedure TBallMgr.KillBall(Idx: Integer);

    begin

      {if the index is not greater than the number of balls and there is a

       ball at the indicated index...}

      if (Idx < BallList.Count) and (BallList[Idx] <> nil) then

      begin

        {free and delete this ball}

        TBall(BallList[Idx]).Free;

        BallList.Delete(Idx);

      end;

    end;

    procedure TBallMgr.MoveBalls;

    var

      iCount: Integer;

    begin

      {move all of the balls in the list}

      for iCount := 0 to BallList.Count-1 do

        TBall(BallList[iCount]).Move;

    end;

    procedure TBallMgr.Clear;

    var

      iCount: Integer;

    begin

      {clear all of the balls}

      for iCount := 0 to BallList.Count-1 do

        KillBall(0);

      BallList.Clear;

    end;

    function TBallMgr.GetActiveBalls: Integer;

    begin

      {return the number of balls in the list}

      Result := BallList.Count;

    end;

    { TPaddle }

    procedure TPaddle.Draw;

    var

      SrcRect: TRect;

    begin

      {draw the paddle graphic to the back buffer}

      SrcRect := Rect(252, 500, 339, 506);

      FBackBuffer.BltFast(XPos, YPos, FGraphics, SrcRect,

                          DDBLTFAST_NOCOLORKEY OR DDBLTFAST_WAIT);

    end;

    procedure TPaddle.Move;

    begin

      {to move the paddle, we really just need to set its horizontal position

       based on that of the mouse}

      XPos := CurX;

      {update its bounding box}

      BoundBox := Rect(Trunc(XPos), Trunc(YPos), Trunc(XPos+92), Trunc(YPos+15));

    end;

    { TBlock }

    procedure TBlock.Draw;

    var

      SrcRect: TRect;

    begin

      {draw the indicated block graphic to the screen}

      SrcRect := Rect((GraphicIdx mod 15)*42, (GraphicIdx div 15)*20+480,

                      (GraphicIdx mod 15)*42+42, (GraphicIdx div 15)*20+20+480);

      FBackBuffer.BltFast(BoundBox.Left, BoundBox.Top, FGraphics, SrcRect,

                          DDBLTFAST_NOCOLORKEY or DDBLTFAST_WAIT);

    end;

    { TBlockMgr }

    destructor TBlockMgr.Destroy;

    var

      iCol, iRow: Integer;

    begin

      {destroy all of the blocks}

      for iRow := 0 to 9 do

        for iCol := 0 to 14 do

          if BlockGrid[iCol, iRow] <> nil then

            BlockGrid[iCol, iRow].Free;

      {finish the destruction}

      inherited Destroy;

    end;

    procedure TBlockMgr.Draw;

    var

      iCol, iRow: Integer;

    begin

      {draw all of the blocks}

      for iRow := 0 to 9 do

        for iCol := 0 to 14 do

          if BlockGrid[iCol, iRow] <> nil then

            BlockGrid[iCol, iRow].Draw;

    end;

    procedure TBlockMgr.InitializeBlocks;

    var

      iCol, iRow: Integer;

    begin

      {seed the random number generator}

      Randomize;

      {create a block for each position in the block grid}

      for iRow := 0 to 9 do

        for iCol := 0 to 14 do

        begin

          {create the actual block}

          BlockGrid[iCol, iRow] := TBlock.Create;

          {randomly set it to one of 20 block graphics}

          BlockGrid[iCol, iRow].GraphicIdx := Random(20);

          {initialize its bounding box}

          BlockGrid[iCol, iRow].BoundBox := Rect(iCol*42+5, iRow*20+75,

                                                 (iCol*42+5)+42, (iRow*20+75)+20);

        end;

      {initialize the number of blocks}

      NumBlocks := 150;

    end;

    procedure TBlockMgr.KillBlock(Row, Col: Integer);

    begin

      {free the specified block}

      BlockGrid[Col, Row].Free;

      BlockGrid[Col, Row] := nil;

      {decrement the number of blocks}

      NumBlocks := NumBlocks - 1;

    end;

    end.
Previous | Table of Contents | Next

Products | Contact Us | About Us | Privacy | Ad Info | Home Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.