//-----------------------------------------------------------------------------
// File: ffdonuts.cpp
//
// Desc: DirectInput Semantic Mapper version of Donuts game
//
// Copyright (C) 1995-2000 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------
#define STRICT
#define INITGUID
#include <windows.h>
#include <cguid.h>
#include <mmsystem.h>
#include <ddraw.h>
#include <dsound.h>
#include <stdio.h>
#include "DDUtil.h"
#include "DIUtil.h"
#include "DSUtil.h"
#include "resource.h"
#include "ffdonuts.h"


// DirectDraw objects
LPDIRECTDRAW7        g_pDD             = NULL;
LPDIRECTDRAWPALETTE  g_pArtPalette     = NULL;
LPDIRECTDRAWPALETTE  g_pSplashPalette  = NULL;
LPDIRECTDRAWSURFACE7 g_pddsFrontBuffer = NULL;
LPDIRECTDRAWSURFACE7 g_pddsBackBuffer  = NULL;
LPDIRECTDRAWSURFACE7 g_pddsDonut       = NULL;
LPDIRECTDRAWSURFACE7 g_pddsPyramid     = NULL;
LPDIRECTDRAWSURFACE7 g_pddsCube        = NULL;
LPDIRECTDRAWSURFACE7 g_pddsSphere      = NULL;
LPDIRECTDRAWSURFACE7 g_pddsShip        = NULL;
LPDIRECTDRAWSURFACE7 g_pddsNumbers     = NULL;
LPDIRECTDRAWSURFACE7 g_pddsDIConfig    = NULL;

// DirectSound and DSUtil sound objects
LPDIRECTSOUND g_pDS                  = NULL;
SoundObject*  g_pBeginLevelSound     = NULL;
SoundObject*  g_pEngineIdleSound     = NULL;
SoundObject*  g_pEngineRevSound      = NULL;
SoundObject*  g_pSkidToStopSound     = NULL;
SoundObject*  g_pShieldBuzzSound     = NULL;
SoundObject*  g_pShipExplodeSound    = NULL;
SoundObject*  g_pFireBulletSound     = NULL;
SoundObject*  g_pShipBounceSound     = NULL;
SoundObject*  g_pDonutExplodeSound   = NULL;
SoundObject*  g_pPyramidExplodeSound = NULL;
SoundObject*  g_pCubeExplodeSound    = NULL;
SoundObject*  g_pSphereExplodeSound  = NULL;

#if 0 //not used for mapper
BOOL    g_bEngineIdle = FALSE;
BOOL    g_bShieldsOn  = FALSE;
BOOL    g_bThrusting  = FALSE;

DWORD   lastInput        = 0;
BOOL    g_bLastShield    = FALSE;
#endif //not used for mapper
BOOL    lastThrust       = FALSE;
FLOAT   g_fShowDelay     = 0.0f;
HWND    g_hWndMain;
BOOL    g_bIsActive;
BOOL    g_bIsReady       = FALSE;
BOOL    g_bMouseVisible;
DWORD   g_dwLastTickCount;
DWORD   g_dwScore        = 0;
int     g_ProgramState;
DWORD   g_dwLevel        = 0;
DWORD   g_dwFillColor;
BOOL    g_bLeaveTrails   = FALSE;
DWORD   g_dwScreenWidth  = 1024;
DWORD   g_dwScreenHeight = 768;
DWORD   g_dwScreenDepth  = 16;
BOOL    g_bSoundEnabled  = FALSE;

DisplayObject* g_pDisplayList = NULL; // Display List
DisplayObject* g_pShip        = NULL; // Main ship
DisplayObject* g_pShipsArray[NUMBER_OF_PLAYERS]; //array for all the players' ships

extern IDirectInput8*       g_pDI;    // DirectInput object
extern DWORD g_dwNumDevices[NUMBER_OF_PLAYERS];
extern IDirectInputDevice8* g_pDevices[NUMBER_OF_PLAYERS][MAX_INPUT_DEVICES+1];
extern DIACTIONFORMAT diActF;
extern LPTSTR lpUserName;
DWORD dwInputState[NUMBER_OF_PLAYERS]; //to persist the input state


BOOL CALLBACK ConfigureDevicesCallback(LPVOID lpVoid, LPVOID lpUserData)
{
        UpdateFrame((LPDIRECTDRAWSURFACE7)lpVoid);
        return TRUE;
}




//-----------------------------------------------------------------------------
// Name: SetupGame()
// Desc:
//-----------------------------------------------------------------------------
VOID SetupGame()
{
    AdvanceLevel();

    // Set the palette
    g_pddsFrontBuffer->SetPalette( g_pArtPalette );
}




//-----------------------------------------------------------------------------
// Name: MainWndproc()
// Desc: Callback for all Windows messages
//-----------------------------------------------------------------------------
long FAR PASCAL MainWndproc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC         hdc;

    switch( msg )
    {
        case WM_ACTIVATEAPP:
            g_bIsActive = (BOOL) wParam;
            if( g_bIsActive )
            {
                g_bMouseVisible   = FALSE;
                g_dwLastTickCount = GetTickCount();
                g_bLeaveTrails    = FALSE;

                // Need to reacquire the device
#if 0 //not used in the Semantic Mapper demo
                if( g_pdidJoystick )
                    g_pdidJoystick->Acquire();
#endif //not used in the Semantic Mapper demo
            }
            else
            {
                g_bMouseVisible = TRUE;
            }
            break;

        case WM_CREATE:
            break;

        case WM_SETCURSOR:
            if( !g_bMouseVisible && g_bIsReady )
                SetCursor(NULL);
            else
                SetCursor(LoadCursor( NULL, IDC_ARROW ));
            return TRUE;

        case WM_KEYDOWN:
            switch( wParam )
            {
                case VK_F3:
                    if( g_bSoundEnabled )
                        DestroySound();
                    else
                        InitializeSound( hWnd );

                    break;
                             
           
                case VK_ESCAPE:
                case VK_F12:
                    PostMessage( hWnd, WM_CLOSE, 0, 0 );
                    return 0;

                case VK_F1:
                    g_bLeaveTrails = !g_bLeaveTrails;
                    break;

                case VK_F2:
		{
					
			DIUtil_ConfigureDevices(hWnd, g_pddsDIConfig, DICD_DEFAULT);
			break;

		}
		case VK_F4:
		{
			DIUtil_ConfigureDevices(hWnd, g_pddsDIConfig, DICD_EDIT);
			break;
		}
                 }
            break;

#if 0 //not used in the Semantic Mapper demo
        case WM_KEYUP:
            switch( wParam )
            {
                case VK_NUMPAD7:
                    lastInput &= ~KEY_SHIELD;
                    break;
                case VK_NUMPAD5:
                    lastInput &= ~KEY_STOP;
                    break;
                case VK_DOWN:
                case VK_NUMPAD2:
                    lastInput &= ~KEY_DOWN;
                    break;
                case VK_LEFT:
                case VK_NUMPAD4:
                    lastInput &= ~KEY_LEFT;
                    break;
                case VK_RIGHT:
                case VK_NUMPAD6:
                    lastInput &= ~KEY_RIGHT;
                    break;
                case VK_UP:
                case VK_NUMPAD8:
                    lastInput &= ~KEY_UP;
                    break;
                case VK_NUMPAD3:
                    lastInput &= ~KEY_THROW;
                    break;
                case VK_SPACE:
                    lastInput &= ~KEY_FIRE;
                    break;

            }
            break;
#endif //not used in the Semantic Mapper demo

        case WM_ERASEBKGND:
            return 1;

        case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            return 1;

        case WM_DESTROY:
#if 0 //not used for mapper
            lastInput=0;
#endif //not used for mapper
            DestroyGame();
            PostQuitMessage( 0 );
            break;
    }

    return (LONG) DefWindowProc( hWnd, msg, wParam, lParam );
}




//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Application entry point
//-----------------------------------------------------------------------------
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow )
{

    // Register the window class
    WNDCLASS wndClass = { CS_DBLCLKS, (WNDPROC)MainWndproc, 0, 0, hInstance,
                          LoadIcon( hInstance, MAKEINTRESOURCE(DONUTS_ICON) ),
                          LoadCursor( NULL, IDC_ARROW ),
                          (HBRUSH)GetStockObject( BLACK_BRUSH ),
                          NULL, TEXT("DonutsClass") };
    RegisterClass( &wndClass );

    // Create our main window
    g_hWndMain = CreateWindowEx( 0, TEXT("DonutsClass"), TEXT("Donuts"),
                                 WS_VISIBLE|WS_POPUP|WS_CAPTION|WS_SYSMENU,
                                 0, 0, GetSystemMetrics(SM_CXSCREEN),
                                 GetSystemMetrics(SM_CYSCREEN), NULL, NULL,
                                 hInstance, NULL );
    if( NULL == g_hWndMain )
        return FALSE;
    UpdateWindow( g_hWndMain );

    g_bIsReady  = TRUE;

    if( FAILED( InitializeGame( g_hWndMain ) ) )
    {
        DestroyWindow( g_hWndMain );
        return FALSE;
    }

    while( 1 )
    {
        MSG msg;

        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( FALSE == GetMessage( &msg, NULL, 0, 0 ) )
                return (int) msg.wParam;

            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else if( g_bIsActive )
        {
            UpdateFrame();
        }
        else
        {
            WaitMessage();
        }
    }
}




//-----------------------------------------------------------------------------
// Name: DestroyGame()
// Desc:
//-----------------------------------------------------------------------------
VOID DestroyGame()
{
    DestroySound();
    DestroyInput();
}

//-----------------------------------------------------------------------------
// Name: UpdateShips()
// Desc:
//-----------------------------------------------------------------------------
VOID UpdateShips()
{
	//the main ship will always be there
	//but update all the others, based on whether
	//the particular user has any devices
	for (int pl = 1; pl < NUMBER_OF_PLAYERS; pl ++)
	{
		//if the corresponding player is still playing
		if (g_pDevices[pl][0] != NULL)
		{
			//if not created already, create new
			if (g_pShipsArray[pl] == NULL)
			{
				g_pShipsArray[pl] = CreateObject(OBJ_SHIP, (FLOAT)(g_dwScreenWidth/(pl+1)-16), (FLOAT)(g_dwScreenHeight/(pl+1)-16), 0, 0, pl);
			}
			//if already created, keep it
		}
		else //player is gone
		{
			//if there is a ship for this player, delete it
			if (g_pShipsArray[pl] != NULL)
			{	
				DeleteFromList(g_pShipsArray[pl]);
				g_pShipsArray[pl] = NULL;
			}
		}

	}
}


//-----------------------------------------------------------------------------
// Name: InitializeGame()
// Desc:
//-----------------------------------------------------------------------------
HRESULT InitializeGame( HWND hWnd )
{
    LPDIRECTDRAW    pDD;
    HRESULT         hr;
    DDSURFACEDESC2  ddsd;

    // Create DirectDraw
    if( SUCCEEDED( hr = DirectDrawCreate( NULL, &pDD, NULL ) ) )
    {
        // Get DirectDraw4 interface
        hr = pDD->QueryInterface( IID_IDirectDraw7, (VOID**)&g_pDD );
        pDD->Release();
    }
    if( FAILED(hr) )
    {
        CleanupAndDisplayError("DirectDrawCreate Failed!");
        return E_FAIL;
    }

    // Set the cooperative level for fullscreen mode
    hr = g_pDD->SetCooperativeLevel( hWnd,
                            DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( FAILED(hr) )
    {
        CleanupAndDisplayError("SetCooperativeLevel Failed");
        return E_FAIL;
    }

    // Set the display mode
    hr = g_pDD->SetDisplayMode( g_dwScreenWidth, g_dwScreenHeight,
                                g_dwScreenDepth, 0, 0 );
    if( FAILED(hr) )
    {
        CleanupAndDisplayError("SetDisplayMode Failed!");
        return E_FAIL;
    }

    // Create surfaces
    ZeroMemory( &ddsd, sizeof( ddsd ) );
    ddsd.dwSize         = sizeof( ddsd );
    ddsd.dwFlags        = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
                          DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;

    // Create the front buffer
    hr = g_pDD->CreateSurface( &ddsd, &g_pddsFrontBuffer, NULL );
    if( FAILED(hr) )
    {
        CleanupAndDisplayError("CreateSurface FrontBuffer Failed!");
        return E_FAIL;
    }

    // Get a pointer to the back buffer
    DDSCAPS2 ddscaps;
    ddscaps.dwCaps  = DDSCAPS_BACKBUFFER;
    ddscaps.dwCaps2 = 0;
    ddscaps.dwCaps3 = 0;
    ddscaps.dwCaps4 = 0;
    hr = g_pddsFrontBuffer->GetAttachedSurface( &ddscaps, &g_pddsBackBuffer );
    if( FAILED(hr) )
    {
        CleanupAndDisplayError("GetAttachedSurface Failed!");
        return E_FAIL;
    }

    ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth        = 320;

    // Create the surfaces for the object images. (We're OR'ing the HRESULTs
    // together, so we only need to check for failure once).
    ddsd.dwHeight = 384;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsDonut, NULL );

    ddsd.dwHeight = 128;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsPyramid, NULL );

    ddsd.dwHeight = 32;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsCube, NULL );

    ddsd.dwHeight = 32;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsSphere, NULL );

    ddsd.dwHeight = 256;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsShip, NULL );

    ddsd.dwHeight = 16;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsNumbers, NULL );

    ddsd.dwWidth = 640;
    ddsd.dwHeight = 480;
    hr |= g_pDD->CreateSurface( &ddsd, &g_pddsDIConfig, NULL );

    if( FAILED(hr) )
    {
        CleanupAndDisplayError("Could not create surfaces!");
        return E_FAIL;
    }

    if( FAILED( RestoreSurfaces() ) )
    {
        CleanupAndDisplayError("RestoreSurfaces Failed!");
        return E_FAIL;
    }

    //zero out the ship ptrs
    for (int ship = 0; ship < NUMBER_OF_PLAYERS; ship++)
    {
	g_pShipsArray[ship] = NULL;
    }

    // Add a ship to the displaylist
    g_pDisplayList = new DisplayObject;
    g_pDisplayList->next        = NULL;
    g_pDisplayList->prev        = NULL;
    g_pDisplayList->type        = OBJ_SHIP;
    g_pDisplayList->pddsSurface = g_pddsShip;
    g_pShip = g_pDisplayList;

    //and save the ptr
    g_pShipsArray[0] = g_pShip;

    //initialize input
    for (ship = 0; ship < NUMBER_OF_PLAYERS; ship ++)
    {
	dwInputState[ship] = 0;
    }

    g_dwLastTickCount = GetTickCount();

    if( FAILED( InitializeInput( hWnd ) ) )
        return E_FAIL;
    //if( FAILED( InitializeSound( hWnd ) ) )
    //    return E_FAIL;

    g_ProgramState = PS_SPLASH;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CleanupAndDisplayError()
// Desc:
//-----------------------------------------------------------------------------
VOID CleanupAndDisplayError( CHAR* strError )
{
    CHAR buffer[80];
    sprintf( buffer, "Error: %s\n", strError );
    OutputDebugString( buffer );

    // Make the cursor visible
    SetCursor( LoadCursor( NULL, IDC_ARROW ) );
    g_bMouseVisible = TRUE;

    if( g_pddsDonut )
        g_pddsDonut->Release();

    if( g_pddsPyramid )
        g_pddsPyramid->Release();

    if( g_pddsCube )
        g_pddsCube->Release();

    if( g_pddsSphere )
        g_pddsSphere->Release();

    if( g_pddsShip )
        g_pddsShip->Release();

    if( g_pddsNumbers )
        g_pddsNumbers->Release();

    if( g_pddsDIConfig )
        g_pddsDIConfig->Release();

    if( g_pddsFrontBuffer )
        g_pddsFrontBuffer->Release();

    if( g_pArtPalette )
        g_pArtPalette->Release();

    if( g_pSplashPalette )
        g_pSplashPalette->Release();

    if( g_pDD )
        g_pDD->Release();

    MessageBox( g_hWndMain, strError, "ERROR", MB_OK );
}




//-----------------------------------------------------------------------------
// Name: DisplaySplashScreen()
// Desc:
//-----------------------------------------------------------------------------
HRESULT DisplaySplashScreen()
{
    HBITMAP hbm;
    hbm = (HBITMAP)LoadImage( GetModuleHandle( NULL ), "SPLASH", IMAGE_BITMAP,
                              0, 0, LR_CREATEDIBSECTION );
    if( NULL == hbm )
        return E_FAIL;

    // Set the palette before loading the splash screen
    g_pddsFrontBuffer->SetPalette( g_pSplashPalette );

    // If the surface is lost, DDCopyBitmap will fail and the surface will
    // be restored in FlipScreen().
    DDUtil_CopyBitmap( g_pddsBackBuffer, hbm, 0, 0, 0, 0 );

    // Done with the bitmap
    DeleteObject( hbm );

    // Show the backbuffer contents on the frontbuffer
    if( FAILED( FlipScreen() ) )
        return E_FAIL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: PlayPannedSound()
// Desc: Play a sound, but first set the panning according to where the
//       object is on the screen.
//-----------------------------------------------------------------------------
VOID PlayPannedSound( SoundObject* pSound, DisplayObject* pObject )
{
    if( NULL == pObject )
        return;

    DWORD dwCenter = ( pObject->rcDst.right + pObject->rcDst.left ) / 2;
    FLOAT fCenter  = ( 2.0f * ((FLOAT)dwCenter)/g_dwScreenWidth ) - 1.0f;

    DSUtil_PlayPannedSound( pSound, fCenter );
}




//-----------------------------------------------------------------------------
// Name: UpdateFrame()
// Desc:
//-----------------------------------------------------------------------------
VOID UpdateFrame(LPDIRECTDRAWSURFACE7 lpddsConfigDlg)
{
    static FLOAT fLevelIntroTime;

    switch( g_ProgramState )
    {
        case PS_SPLASH:
            // Display the splash screen
            //DisplaySplashScreen();
            g_ProgramState = PS_BEGINREST;
            SetupGame();

            break;

        case PS_ACTIVE:
            UpdateDisplayList();
            CheckForHits();
            DrawDisplayList(lpddsConfigDlg);

            if( IsDisplayListEmptyExceptShips() )
            {
                DSUtil_StopSound( g_pEngineIdleSound );
                DSUtil_StopSound( g_pEngineRevSound );

#if 0 //not used for the mulit-user mapper
                g_bEngineIdle  = FALSE;
                g_bThrusting   = FALSE;
                lastThrust     = FALSE;
                g_bLastShield  = FALSE;
#endif //not used for the mulit-user mapper
                g_ProgramState = PS_BEGINREST;
                AdvanceLevel();
            }
            break;

        case PS_BEGINREST:
            DSUtil_PlaySound(g_pBeginLevelSound, 0);
            fLevelIntroTime = GetTickCount()/1000.0f;
            g_ProgramState  = PS_REST;

            // Fall through to PS_REST state

        case PS_REST:
            // Show the Level intro screen for 3 seconds
            if( ( GetTickCount()/1000.0f - fLevelIntroTime ) > 3.0f )
            {
                DSUtil_PlaySound( g_pEngineIdleSound, DSBPLAY_LOOPING );
#if 0 //not used for the mulit-user mapper
                g_bEngineIdle     = TRUE;
#endif //not used for the mulit-user mapper
                g_dwLastTickCount = GetTickCount();
                g_ProgramState    = PS_ACTIVE;
            }
            else
            {
                DisplayLevelIntroScreen( g_dwLevel );
            }
            break;
    }
}




//-----------------------------------------------------------------------------
// Name: DisplayLevelIntroScreen()
// Desc:
//-----------------------------------------------------------------------------
VOID DisplayLevelIntroScreen( DWORD dwLevel )
{
    // Erase the screen
    EraseScreen();

    // Output the bitmapped letters for the word "Level"
    CHAR buffer[10];
    buffer[0] = 10 + '0';
    buffer[1] = 11 + '0';
    buffer[2] = 12 + '0';
    buffer[3] = 11 + '0';
    buffer[4] = 10 + '0';
    buffer[5] = '\0';
    BltBitmappedText( buffer, g_dwScreenWidth/2-64, g_dwScreenHeight/2-8 );

    // Output the bitmapped numbers for the level number
    buffer[0] = (CHAR)(dwLevel/100) + '0';
    buffer[1] = (CHAR)(dwLevel/ 10) + '0';
    buffer[2] = (CHAR)(dwLevel% 10) + '0';
    buffer[3] = '\0';
    BltBitmappedText( buffer, g_dwScreenWidth/2+22, g_dwScreenHeight/2-8 );

    // Show the backbuffer screen on the front buffer
    FlipScreen();
}




//-----------------------------------------------------------------------------
// Name: BltBitmappedText()
// Desc:
//-----------------------------------------------------------------------------
VOID BltBitmappedText( CHAR* num, int x, int y )
{
    for( CHAR* c = num; *c != '\0'; c++ )
    {
        HRESULT hr = DDERR_WASSTILLDRAWING;
        RECT    rcSrc;

        rcSrc.left   = (*c - '0')*16;
        rcSrc.top    = 0;
        rcSrc.right  = rcSrc.left + 16;
        rcSrc.bottom = rcSrc.top + 16;

        while( hr == DDERR_WASSTILLDRAWING )
        {
            hr = g_pddsBackBuffer->BltFast( x, y, g_pddsNumbers, &rcSrc,
                                            DDBLTFAST_SRCCOLORKEY );
        }

        x += 16;
    }
}




//-----------------------------------------------------------------------------
// Name: CheckForHits()
// Desc:
//-----------------------------------------------------------------------------
VOID CheckForHits()
{
    DisplayObject* pObject;
    DisplayObject* bullet;

    // Update screen rects
    for( pObject = g_pDisplayList; pObject; pObject = pObject->next )
    {
        int frame = (DWORD)pObject->frame;

        switch( pObject->type )
        {
            case OBJ_DONUT:
                pObject->rcDst.left   = (DWORD)pObject->posx;
                pObject->rcDst.top    = (DWORD)pObject->posy;
                pObject->rcDst.right  = pObject->rcDst.left + DONUT_WIDTH;
                pObject->rcDst.bottom = pObject->rcDst.top + DONUT_HEIGHT;
                pObject->rcSrc.left   = DONUT_WIDTH * (frame % 5);
                pObject->rcSrc.top    = DONUT_HEIGHT * (frame /5);
                pObject->rcSrc.right  = pObject->rcSrc.left + DONUT_WIDTH;
                pObject->rcSrc.bottom = pObject->rcSrc.top + DONUT_HEIGHT;
                break;

            case OBJ_PYRAMID:
                pObject->rcDst.left   = (DWORD)pObject->posx;
                pObject->rcDst.top    = (DWORD)pObject->posy;
                pObject->rcDst.right  = pObject->rcDst.left + PYRAMID_WIDTH;
                pObject->rcDst.bottom = pObject->rcDst.top + PYRAMID_HEIGHT;
                pObject->rcSrc.left   = PYRAMID_WIDTH * (frame % 10);
                pObject->rcSrc.top    = PYRAMID_HEIGHT * (frame /10);
                pObject->rcSrc.right  = pObject->rcSrc.left + PYRAMID_WIDTH;
                pObject->rcSrc.bottom = pObject->rcSrc.top + PYRAMID_HEIGHT;
                break;

            case OBJ_SPHERE:
                pObject->rcDst.left   = (DWORD)pObject->posx;
                pObject->rcDst.top    = (DWORD)pObject->posy;
                pObject->rcDst.right  = pObject->rcDst.left + SPHERE_WIDTH;
                pObject->rcDst.bottom = pObject->rcDst.top + SPHERE_HEIGHT;
                pObject->rcSrc.left   = SPHERE_WIDTH * (frame % 20);
                pObject->rcSrc.top    = SPHERE_HEIGHT * (frame /20);
                pObject->rcSrc.right  = pObject->rcSrc.left + SPHERE_WIDTH;
                pObject->rcSrc.bottom = pObject->rcSrc.top + SPHERE_HEIGHT;
                break;
            case OBJ_CUBE:
                pObject->rcDst.left   = (DWORD)pObject->posx;
                pObject->rcDst.top    = (DWORD)pObject->posy;
                pObject->rcDst.right  = pObject->rcDst.left + CUBE_WIDTH;
                pObject->rcDst.bottom = pObject->rcDst.top + CUBE_HEIGHT;
                pObject->rcSrc.left   = CUBE_WIDTH * (frame % 20);
                pObject->rcSrc.top    = CUBE_HEIGHT * (frame /20);
                pObject->rcSrc.right  = pObject->rcSrc.left + CUBE_WIDTH;
                pObject->rcSrc.bottom = pObject->rcSrc.top + CUBE_HEIGHT;
                break;

            case OBJ_SHIP:
                pObject->rcDst.left   = (DWORD)pObject->posx;
                pObject->rcDst.top    = (DWORD)pObject->posy;
                pObject->rcDst.right  = pObject->rcDst.left + SHIP_WIDTH;
                pObject->rcDst.bottom = pObject->rcDst.top + SHIP_HEIGHT;
#if 0 //not used for the mulit-user mapper
                if( g_bLastShield )
                    pObject->rcSrc.top = SHIP_HEIGHT * (frame / 10) + 128;
                else
#endif //not used for the mulit-user mapper
                    pObject->rcSrc.top = SHIP_HEIGHT * (frame /10);
                pObject->rcSrc.left   = SHIP_WIDTH * (frame % 10);
                pObject->rcSrc.right  = pObject->rcSrc.left + SHIP_WIDTH;
                pObject->rcSrc.bottom = pObject->rcSrc.top + SHIP_HEIGHT;
                break;

            case OBJ_BULLET:
                frame = (DWORD)pObject->frame/20 % 4;
                pObject->rcDst.left   = (DWORD)pObject->posx;
                pObject->rcDst.top    = (DWORD)pObject->posy;
                pObject->rcDst.right  = pObject->rcDst.left + BULLET_WIDTH;
                pObject->rcDst.bottom = pObject->rcDst.top + BULLET_HEIGHT;
                pObject->rcSrc.left   = BULLET_XOFFSET + frame*4;
                pObject->rcSrc.top    = BULLET_YOFFSET;
                pObject->rcSrc.right  = pObject->rcSrc.left + BULLET_WIDTH;
                pObject->rcSrc.bottom = pObject->rcSrc.top + BULLET_HEIGHT;
                break;
        }
    }
    for( bullet = g_pDisplayList; bullet; bullet = bullet->next )
    {
        // Only bullet objects and the ship (if shields are on) can hit
        // other objects. Skip all others.
        if( (bullet->type != OBJ_BULLET) && (bullet->type != OBJ_SHIP) )
            continue;

#if 0 //not used for the mulit-user mapper
        // The ship cannot hit anything with shields on
        if( bullet->type == OBJ_SHIP && TRUE == g_bLastShield )
            continue;
#endif //not used for the mulit-user mapper

        BOOL bBulletHit = FALSE;
        WORD wBulletX   = (WORD)(bullet->rcDst.left + bullet->rcDst.right) / 2;
        WORD wBulletY   = (WORD)(bullet->rcDst.top + bullet->rcDst.bottom) / 2;

        for( pObject = g_pDisplayList; pObject; pObject = pObject->next )
        {
            FLOAT left   = (FLOAT)pObject->rcDst.left;
            FLOAT right  = (FLOAT)pObject->rcDst.right;
            FLOAT top    = (FLOAT)pObject->rcDst.top;
            FLOAT bottom = (FLOAT)pObject->rcDst.bottom;

            // Only trying to hit explodable targets
            if( ( pObject->type != OBJ_DONUT ) &&
                ( pObject->type != OBJ_PYRAMID ) &&
                ( pObject->type != OBJ_SPHERE ) &&
				( pObject->type != OBJ_SHIP ) &&
                ( pObject->type != OBJ_CUBE ) )
                continue;

            if( ( wBulletX >= left ) && ( wBulletX < right ) &&
                ( wBulletY >= top ) && ( wBulletY < bottom ) )
            {
                // The object was hit
                switch( pObject->type )
                {
                    case OBJ_DONUT:
                        PlayPannedSound( g_pDonutExplodeSound, pObject );
                        CreateObject( OBJ_PYRAMID, left, top, -1.0f, -1.0f, NO_PLAYER);
                        CreateObject( OBJ_PYRAMID, left, top, -1.0f, -1.0f, NO_PLAYER );
                        CreateObject( OBJ_PYRAMID, left, top, -1.0f, -1.0f, NO_PLAYER );
                        g_dwScore += 10;
                        break;

                    case OBJ_PYRAMID:
                        PlayPannedSound( g_pPyramidExplodeSound, pObject );
                        CreateObject( OBJ_SPHERE, left, top, -1.0f, -1.0f, NO_PLAYER );
                        CreateObject( OBJ_CUBE,   left, top, -1.0f, -1.0f, NO_PLAYER );
                        CreateObject( OBJ_CUBE,   left, top, -1.0f, -1.0f, NO_PLAYER );
                        g_dwScore += 20;
                        break;

                    case OBJ_CUBE:
                        PlayPannedSound( g_pCubeExplodeSound, pObject );
                        CreateObject( OBJ_SPHERE, left, top, -1.0f, -1.0f, NO_PLAYER );
                        CreateObject( OBJ_SPHERE, left, top, -1.0f, -1.0f, NO_PLAYER );
                        g_dwScore += 40;
                        break;

                    case OBJ_SPHERE:
                        PlayPannedSound( g_pSphereExplodeSound, pObject );
                        g_dwScore += 20;
                        break;

					case OBJ_SHIP:
						//can't be hit by bullets that come from us
						if (bullet->Player != pObject->Player)
						{
						if(pObject->bVisible )
						{
							// Ship has exploded
							PlayPannedSound( g_pShipExplodeSound, pObject);
							if( g_dwScore < 150 )
								g_dwScore = 0;
							else
								g_dwScore -= 150;

							FLOAT x = pObject->posx;
							FLOAT y = pObject->posy;

							CreateObject( OBJ_SPHERE, x, y, -1.0f, -1.0f, NO_PLAYER);
							CreateObject( OBJ_SPHERE, x, y, -1.0f, -1.0f, NO_PLAYER);
							CreateObject( OBJ_SPHERE, x, y, -1.0f, -1.0f, NO_PLAYER);
							CreateObject( OBJ_SPHERE, x, y, -1.0f, -1.0f, NO_PLAYER);
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );
							CreateObject( OBJ_BULLET, x, y, rnd()/2, rnd()/2, bullet->Player );

							// Delay for 2 seconds before displaying ship
							InitializeShips();
							g_fShowDelay = 2.0f;
							pObject->bVisible = FALSE;
							}
						}
						break;

                }


				//only delete non-"ships"
				if (pObject->type != OBJ_SHIP)
				{
					DisplayObject* pVictim = pObject;
					pObject = pObject->prev;
					DeleteFromList( pVictim );
					bBulletHit = TRUE;
				}
            }
        }

    }
}




//-----------------------------------------------------------------------------
// Name: EraseScreen()
// Desc:
//-----------------------------------------------------------------------------
VOID EraseScreen()
{
    // Erase the background
    DDBLTFX ddbltfx;
    ZeroMemory( &ddbltfx, sizeof(ddbltfx) );
    ddbltfx.dwSize      = sizeof(ddbltfx);
    ddbltfx.dwFillColor = g_dwFillColor;

    while( 1 )
    {
        HRESULT hr = g_pddsBackBuffer->Blt( NULL, NULL, NULL, DDBLT_COLORFILL,
                                            &ddbltfx );
        if( SUCCEEDED(hr) )
            break;

        if( hr == DDERR_SURFACELOST )
            if( FAILED( RestoreSurfaces() ) )
                return;

        if( hr != DDERR_WASSTILLDRAWING )
            return;
    }
}




//-----------------------------------------------------------------------------
// Name: FlipScreen()
// Desc:
//-----------------------------------------------------------------------------
HRESULT FlipScreen()
{
        HRESULT hr = DDERR_WASSTILLDRAWING;

    // Flip the surfaces
        while( DDERR_WASSTILLDRAWING == hr )
        {
                hr = g_pddsFrontBuffer->Flip( NULL, 0 );

                if( hr == DDERR_SURFACELOST )
                        if( FAILED( RestoreSurfaces() ) )
                                return E_FAIL;
        }

        return hr;
}




//-----------------------------------------------------------------------------
// Name: DrawDisplayList()
// Desc:
//-----------------------------------------------------------------------------
VOID DrawDisplayList(LPDIRECTDRAWSURFACE7 lpConfigDlgSurf)
{
    if( FALSE == g_bLeaveTrails )
        EraseScreen();

    char scorebuf[11];
    int  rem;

    rem = g_dwScore;
    scorebuf[0] = rem/10000000 + '0';
    rem = g_dwScore % 10000000;
    scorebuf[1] = rem/1000000 + '0';
    rem = g_dwScore % 1000000;
    scorebuf[2] = rem/100000 + '0';
    rem = g_dwScore % 100000;
    scorebuf[3] = rem/10000 + '0';
    rem = g_dwScore % 10000;
    scorebuf[4] = rem/1000 + '0';
    rem = g_dwScore % 1000;
    scorebuf[5] = rem/100 + '0';
    rem = g_dwScore % 100;
    scorebuf[6] = rem/10 + '0';
    rem = g_dwScore % 10;
    scorebuf[7] = rem + '0';
    scorebuf[8] = '\0';

    // Draw the sound icon, if sound is enabled
    if( g_bSoundEnabled )
    {
        scorebuf[8]  = 14 + '0';
        scorebuf[9]  = 13 + '0';
        scorebuf[10] = '\0';
    }

    // Display the score
    BltBitmappedText( scorebuf, 10, g_dwScreenHeight-26 );

    // Display all visible objects in the display list
    for( DisplayObject* pObject = g_pDisplayList; pObject;
         pObject = pObject->next )
    {
        if( !pObject->bVisible )
            continue;

        HRESULT hr = DDERR_WASSTILLDRAWING;

        if( pObject->rcSrc.right - pObject->rcSrc.left == 0 )
            continue; // New objects do not have a src rect yet.

        while( hr == DDERR_WASSTILLDRAWING )
        {
            hr = g_pddsBackBuffer->BltFast( pObject->rcDst.left,
                                   pObject->rcDst.top, pObject->pddsSurface,
                                   &pObject->rcSrc, DDBLTFAST_SRCCOLORKEY );

        }
    }

    // Flip the backbuffer contents to the front buffer
                if (lpConfigDlgSurf)
                {
                        g_pddsBackBuffer->BltFast((g_dwScreenWidth-640)/2, (g_dwScreenHeight-480)/2, lpConfigDlgSurf, NULL, DDBLTFAST_WAIT);
                }

    FlipScreen();
}




//-----------------------------------------------------------------------------
// Name: DeleteFromList()
// Desc:
//-----------------------------------------------------------------------------
VOID DeleteFromList( DisplayObject* pObject )
{
    if( pObject->next )
        pObject->next->prev = pObject->prev;
    if( pObject->prev )
        pObject->prev->next = pObject->next;
    delete( pObject );
}




//-----------------------------------------------------------------------------
// Name: UpdateDisplayList()
// Desc:
//-----------------------------------------------------------------------------
VOID UpdateDisplayList()
{
    DisplayObject* thisnode;
    FLOAT          maxx, maxy;
    FLOAT          maxframe;
    DWORD          input[NUMBER_OF_PLAYERS];//= lastInput;
    for (int pl = 0; pl < NUMBER_OF_PLAYERS; pl ++)
    {
	input[pl] = 0;
    }

    // Update the game clock parameters
    DWORD dwTickCount = GetTickCount();
    FLOAT fTickDiff   = ( dwTickCount - g_dwLastTickCount )/1000.0f;
    g_dwLastTickCount = dwTickCount;

    // Read input from the players' devices
    GetDeviceInput(input);

    for (int pl = 0; pl < NUMBER_OF_PLAYERS; pl ++)
    {
		BOOL bShieldState = FALSE;
		BOOL bThrustState = FALSE;
		BOOL bBounce      = FALSE;
		LONG lAngle       = 0;

	    //if KEY_QUIT is pressed, exit
	    if (input[pl] & KEY_QUIT)
	    {
	       PostMessage( g_hWndMain, WM_CLOSE, 0, 0 );
	       return;
	    }

	    //if KEY_DISPLAY is pressed, call ConfigureDevices() in normal mode
	    if (input[pl] & KEY_DISPLAY)
	    {
		    //call ConfigureDevices() in default mode
		    DIUtil_ConfigureDevices(g_hWndMain, g_pddsDIConfig, DICD_DEFAULT);
	    }

	    //if KEY_EDIT is pressed, call ConfigureDevices() in edit mode
	    if (input[pl] & KEY_EDIT)
	    {
		    //call ConfigureDevices() in edit mode
		    DIUtil_ConfigureDevices(g_hWndMain, g_pddsDIConfig, DICD_EDIT);
	    }

	if( g_fShowDelay > 0.0f )
	{
	    g_fShowDelay -= fTickDiff;

	    if( g_fShowDelay < 0.0f )
	    {
		InitializeShips();
#if 0 //not used for the mulit-user mapper
		g_bLastShield = FALSE;
#endif //not used for the mulit-user mapper
		g_fShowDelay  = 0.0f;
		if (g_pShipsArray[pl] != NULL)
		{
		    g_pShipsArray[pl]->bVisible = TRUE;
		}
	    }
	}

	// Update the ships
	if (g_pShipsArray[pl] != NULL)
	{
	    if( g_pShipsArray[pl]->bVisible )
	    {
		g_pShipsArray[pl]->posx += g_pShipsArray[pl]->velx * fTickDiff;
		g_pShipsArray[pl]->posy += g_pShipsArray[pl]->vely * fTickDiff;
	    }
	    if( g_pShipsArray[pl]->posx > (FLOAT)(MAX_SCREEN_X - SHIP_WIDTH) )
	    {
		g_pShipsArray[pl]->posx = (FLOAT)(MAX_SCREEN_X - SHIP_WIDTH);
		g_pShipsArray[pl]->velx = -g_pShipsArray[pl]->velx;

		// Bounce off the right edge of the screen
		bBounce = TRUE;
		lAngle  = 90;
	    }
	    else if( g_pShipsArray[pl]->posx < 0 )
	    {
		g_pShipsArray[pl]->posx = 0;
		g_pShipsArray[pl]->velx = -g_pShipsArray[pl]->velx;

		// Bounce off the left edge of the screen
		bBounce = TRUE;
		lAngle  = 270;
	    }

	    if( g_pShipsArray[pl]->posy > (FLOAT)(MAX_SCREEN_Y - SHIP_HEIGHT) )
	    {
		g_pShipsArray[pl]->posy = (FLOAT)(MAX_SCREEN_Y - SHIP_HEIGHT);
		g_pShipsArray[pl]->vely = -g_pShipsArray[pl]->vely;

		// Bounce off the bottom edge of the screen
		bBounce = TRUE;
		lAngle  = 180;
	    }
	    else if( g_pShipsArray[pl]->posy < 0 )
	    {
		g_pShipsArray[pl]->posy =0;
		g_pShipsArray[pl]->vely = -g_pShipsArray[pl]->vely;

		// Bounce off the top edge of the screen
		bBounce = TRUE;
		lAngle  = 0;
	    }

	    if( bBounce )
	    {
		// "Bounce" the ship
		PlayPannedSound( g_pShipBounceSound, g_pShipsArray[pl] );
	#if 0 //not used in the Semantic Mapper demo
		DIUtil_PlayDirectionalEffect( g_pBounceFFEffect, lAngle );
	#endif //not used in the Semantic Mapper demo
	    }

	    bShieldState = ( !g_pShipsArray[pl]->bVisible || ( input[pl] & KEY_SHIELD ) );

#if 0 //not used for the mulit-user mapper
	    if( bShieldState != g_bLastShield )
	    {
#endif //not used for the mulit-user mapper
		if( bShieldState && g_pShipsArray[pl]->bVisible )
		{
		    DSUtil_PlaySound( g_pShieldBuzzSound, DSBPLAY_LOOPING );
#if 0 //not used for the mulit-user mapper
		    g_bShieldsOn = TRUE;
#endif //not used for the mulit-user mapper
		}
		else
		{
		    DSUtil_StopSound( g_pShieldBuzzSound );
#if 0 //not used for the mulit-user mapper
		    g_bShieldsOn = FALSE;
#endif //not used for the mulit-user mapper
		}
#if 0 //not used for the mulit-user mapper
		g_bLastShield = bShieldState;
	    }
#endif //not used for the mulit-user mapper
	    if( bShieldState )
	    {
		input[pl] &= ~(KEY_FIRE);
	    }

	    if( input[pl] & KEY_FIRE )
	    {
		// Add a bullet to the scene
		if( g_pShipsArray[pl]->bVisible )
		{
		    // Bullets cost one score point
		    if( g_dwScore )
			g_dwScore--;

		    // Play the "fire" sound
		    DSUtil_PlaySound( g_pFireBulletSound, 0);

		    // Play the "fire" forcefeedback effect
	#if 0 //not used in the Semantic Mapper demo
		    DIUtil_PlayEffect( g_pFireFFEffect );
	#endif //not used in the Semantic Mapper demo

		    // Add a bullet to the display list
		    CreateObject( OBJ_BULLET,
				 Dirx[(int)g_pShipsArray[pl]->frame]*6.0f + 16.0f + g_pShipsArray[pl]->posx,
				 Diry[(int)g_pShipsArray[pl]->frame]*6.0f + 16.0f + g_pShipsArray[pl]->posy,
				 Dirx[(int)g_pShipsArray[pl]->frame]*500.0f,
				 Diry[(int)g_pShipsArray[pl]->frame]*500.0f,
				 pl);
		}
	    }

	    if( input[pl] & KEY_LEFT )
	    {
		g_pShipsArray[pl]->frame -= 1.0;
		if( g_pShipsArray[pl]->frame < 0.0 )
		    g_pShipsArray[pl]->frame += MAX_SHIP_FRAME;
	    }
	    if( input[pl] & KEY_RIGHT )
	    {
		g_pShipsArray[pl]->frame += 1.0;
		if( g_pShipsArray[pl]->frame >= MAX_SHIP_FRAME )
		    g_pShipsArray[pl]->frame -= MAX_SHIP_FRAME;
	    }
	    if( input[pl] & KEY_UP )
	    {
		g_pShipsArray[pl]->velx += Dirx[(int)g_pShipsArray[pl]->frame] * 10.0f;
		g_pShipsArray[pl]->vely += Diry[(int)g_pShipsArray[pl]->frame] * 10.0f;
		bThrustState = TRUE;
	    }
	    if( input[pl] & KEY_DOWN )
	    {
		g_pShipsArray[pl]->velx -= Dirx[(int)g_pShipsArray[pl]->frame] * 10.0f;
		g_pShipsArray[pl]->vely -= Diry[(int)g_pShipsArray[pl]->frame] * 10.0f;
		bThrustState = TRUE;
	    }

	    if( bThrustState != lastThrust )
	    {
		if( bThrustState )
		{
		    input[pl] &= ~KEY_STOP;
		    DSUtil_StopSound( g_pSkidToStopSound );
		    DSUtil_PlaySound( g_pEngineRevSound, DSBPLAY_LOOPING );
#if 0 //not used for the mulit-user mapper
		    g_bThrusting = TRUE;
#endif //not used for the mulit-user mapper
		}
		else
		{
		    DSUtil_StopSound( g_pEngineRevSound );
#if 0 //not used for the mulit-user mapper
		    g_bThrusting = FALSE;
#endif //not used for the mulit-user mapper
		}

		lastThrust = bThrustState;
	    }

	    if( input[pl] & KEY_STOP )
	    {
		if( g_pShipsArray[pl]->velx || g_pShipsArray[pl]->vely )
		    PlayPannedSound( g_pSkidToStopSound, g_pShipsArray[pl] );

		g_pShipsArray[pl]->velx = 0.0f;
		g_pShipsArray[pl]->vely = 0.0f;
	    }
	}
    }

    for( thisnode = g_pDisplayList->next; thisnode;  )
    {
	//for things other than ships, which were handled above
	if (thisnode->type != OBJ_SHIP)
	{
	    thisnode->posx  += thisnode->velx  * fTickDiff;
	    thisnode->posy  += thisnode->vely  * fTickDiff;
	    thisnode->frame += thisnode->delay * fTickDiff;
	    switch( thisnode->type )
	    {
		case OBJ_DONUT:
		    maxx     = (FLOAT)(MAX_SCREEN_X - DONUT_WIDTH);
		    maxy     = (FLOAT)(MAX_SCREEN_Y - DONUT_HEIGHT);
		    maxframe = MAX_DONUT_FRAME;
		    break;
		case OBJ_PYRAMID:
		    maxx     = (FLOAT)(MAX_SCREEN_X - PYRAMID_WIDTH);
		    maxy     = (FLOAT)(MAX_SCREEN_Y - PYRAMID_HEIGHT);
		    maxframe = MAX_PYRAMID_FRAME;
		    break;
		case OBJ_SPHERE:
		    maxx     = (FLOAT)(MAX_SCREEN_X - SPHERE_WIDTH);
		    maxy     = (FLOAT)(MAX_SCREEN_Y - SPHERE_HEIGHT);
		    maxframe = MAX_SPHERE_FRAME;
		    break;
		case OBJ_CUBE:
		    maxx     = (FLOAT)(MAX_SCREEN_X - CUBE_WIDTH);
		    maxy     = (FLOAT)(MAX_SCREEN_Y - CUBE_HEIGHT);
		    maxframe = MAX_CUBE_FRAME;
		    break;
		case OBJ_BULLET:
		    maxx     = (FLOAT)(MAX_SCREEN_X - BULLET_WIDTH);
		    maxy     = (FLOAT)(MAX_SCREEN_Y - BULLET_HEIGHT);
		    maxframe = MAX_BULLET_FRAME;

		    // If bullet "expired", removed it from list
		    if( thisnode->frame >= MAX_BULLET_FRAME )
		    {
			DisplayObject* pVictim = thisnode;
			thisnode = thisnode->next;
			DeleteFromList( pVictim );
			continue;
		    }
		    break;
	    }
	    if( thisnode != g_pDisplayList )
	    {
		if( thisnode->posx > maxx )
		{
		    thisnode->posx = maxx;
		    thisnode->velx = -thisnode->velx;
		}
		else if ( thisnode->posx < 0 )
		{
		    thisnode->posx =0;
		    thisnode->velx = -thisnode->velx;
		}
		if( thisnode->posy > maxy )
		{
		    thisnode->posy = maxy;
		    thisnode->vely = -thisnode->vely;
		}
		else if ( thisnode->posy < 0 )
		{
		    thisnode->posy =0;
		    thisnode->vely = -thisnode->vely;
		}
		if( thisnode->frame >= maxframe )
		{
		    thisnode->frame -= maxframe;
		}
	    }
	}
	thisnode = thisnode->next;
    }
}




//-----------------------------------------------------------------------------
// Name: IsDisplayListEmpty()
// Desc:
//-----------------------------------------------------------------------------
BOOL IsDisplayListEmpty()
{
    DisplayObject* pObject = g_pDisplayList->next;

    while( pObject )
    {
        if( pObject->type != OBJ_BULLET )
            return FALSE;

        pObject = pObject->next;
    }
    return TRUE;
}

//-----------------------------------------------------------------------------
// Name: IsDisplayListEmptyExceptShips()
// Desc:
//-----------------------------------------------------------------------------
BOOL IsDisplayListEmptyExceptShips()
{
    DisplayObject* pObject = g_pDisplayList->next;

    while( pObject )
    {
        if(( pObject->type != OBJ_BULLET ) && (pObject->type != OBJ_SHIP))
            return FALSE;

        pObject = pObject->next;
    }
    return TRUE;
}


//-----------------------------------------------------------------------------
// Name: InitializeShips()
// Desc:
//-----------------------------------------------------------------------------
VOID InitializeShips()
{
	for (int ship = 0; ship < NUMBER_OF_PLAYERS; ship++)
	{
		if (g_pShipsArray[ship] != NULL)
		{
			g_pShipsArray[ship]->posx  = (FLOAT)(g_dwScreenWidth/(ship+2)-16); // Center the ship
			g_pShipsArray[ship]->posy  = (FLOAT)(g_dwScreenHeight/(ship+2)-16);
			g_pShipsArray[ship]->frame = 0.0f;
			g_pShipsArray[ship]->velx  = 0.0f;                          // Not moving
			g_pShipsArray[ship]->vely  = 0.0f;
			g_pShipsArray[ship]->bVisible = TRUE;
			g_pShipsArray[ship]->Player = ship;
		}
	}
}




//-----------------------------------------------------------------------------
// Name: AdvanceLevel()
// Desc:
//-----------------------------------------------------------------------------
VOID AdvanceLevel()
{
    // Up the level
    g_dwLevel++;

    // Clear any stray objects (anything but the ships) out of the display list
    while( (g_pShip->next != NULL) && (g_pShip->next->type != OBJ_SHIP ))
    {
        DeleteFromList( g_pShip->next );
    }

    // Create donuts for the new level
    for( WORD i=0; i<(2*g_dwLevel-1); i++ )
    {
        FLOAT x = rnd( 0.0f, (FLOAT)(MAX_SCREEN_X - DONUT_WIDTH) );
        FLOAT y = rnd( 0.0f, (FLOAT)(MAX_SCREEN_Y - DONUT_HEIGHT) );

        CreateObject( OBJ_DONUT, x, y, 0.0f, 0.0f, NO_PLAYER );
    }

    // Delay for 2 seconds before displaying ships
    InitializeShips();
    g_fShowDelay = 2.0f;
}




//-----------------------------------------------------------------------------
// Name: CreateObject()
// Desc:
//-----------------------------------------------------------------------------
DisplayObject* CreateObject( SHORT type, FLOAT x, FLOAT y, FLOAT vx, FLOAT vy, int Player )
{
    // Create a new display object
    DisplayObject* pObject = new DisplayObject;
    if( NULL == pObject )
        return NULL;

    // Add the object to the global display list
    AddToList( pObject );

    // Set object attributes
    pObject->bVisible = TRUE;
    pObject->type = type;
    pObject->posx = x;
    pObject->posy = y;
    pObject->velx = vx;
    pObject->vely = vy;
	pObject->Player = NO_PLAYER;

    switch( type )
    {
        case OBJ_DONUT:
            pObject->velx  = rnd( -50.0f, 50.0f );
            pObject->vely  = rnd( -50.0f, 50.0f );
            pObject->frame = rnd( 0.0f, 30.0f );
            pObject->delay = rnd( 3.0f, 12.0f );
            pObject->pddsSurface = g_pddsDonut;
            break;

        case OBJ_PYRAMID:
            pObject->velx  = rnd( -75.0f, 75.0f );
            pObject->vely  = rnd( -75.0f, 75.0f );
            pObject->frame = rnd( 0.0f, 30.0f );
            pObject->delay = rnd( 12.0f, 40.0f );
            pObject->pddsSurface = g_pddsPyramid;
            break;

        case OBJ_SPHERE:
            pObject->velx  = rnd( -150.0f, 150.0f );
            pObject->vely  = rnd( -150.0f, 150.0f );
            pObject->frame = rnd( 0.0f, 30.0f );
            pObject->delay = rnd( 60.0f, 80.0f );
            pObject->pddsSurface = g_pddsSphere;
            break;

        case OBJ_CUBE:
            pObject->velx  = rnd( -200.0f, 200.0f );
            pObject->vely  = rnd( -200.0f, 200.0f );
            pObject->frame = rnd( 0.0f, 30.0f );
            pObject->delay = rnd( 32.0f, 80.0f );
            pObject->pddsSurface = g_pddsCube;
            break;
			 
		case OBJ_SHIP:
            pObject->frame = 0.0f;
			pObject->posx  = x;
			pObject->posy  = y;
			pObject->velx  = 0.0f;                          // Not moving
			pObject->vely  = 0.0f;
			pObject->Player = Player;						//which player the ship belongs to
            pObject->pddsSurface = g_pddsShip;
            break;


        case OBJ_BULLET:
            pObject->frame =    0.0f;
            pObject->delay = 1000.0f;
			pObject->Player = Player;						//which player's ship shot the bullet
            pObject->pddsSurface = g_pddsNumbers;
            break;
    }

    return pObject;
};




//-----------------------------------------------------------------------------
// Name: AddToList()
// Desc:
//-----------------------------------------------------------------------------
VOID AddToList( DisplayObject* pObject )
{
    pObject->next = g_pDisplayList->next;
    pObject->prev = g_pDisplayList;

    if( g_pDisplayList->next )
        g_pDisplayList->next->prev = pObject;
    g_pDisplayList->next = pObject;
}




//-----------------------------------------------------------------------------
// Name: RestoreSurfaces()
// Desc:
//-----------------------------------------------------------------------------
HRESULT RestoreSurfaces()
{
    HRESULT hr;
    HBITMAP hbm;

    if( FAILED( hr = g_pddsFrontBuffer->Restore() ) )
        return E_FAIL;
    if( FAILED( hr = g_pddsDonut->Restore() ) )
        return E_FAIL;
    if( FAILED( hr = g_pddsPyramid->Restore() ) )
        return E_FAIL;
    if( FAILED( hr = g_pddsCube->Restore() ) )
        return E_FAIL;
    if( FAILED( hr = g_pddsSphere->Restore() ) )
        return E_FAIL;
    if( FAILED( hr = g_pddsShip->Restore() ) )
        return E_FAIL;
    if( FAILED( hr = g_pddsNumbers->Restore() ) )
        return E_FAIL;

    // Create and set the palette for the splash bitmap
    g_pSplashPalette = DDUtil_LoadPalette( g_pDD, "SPLASH" );

    // Create and set the palette for the art bitmap
    g_pArtPalette = DDUtil_LoadPalette( g_pDD, "DONUTS8" );

    if( NULL == g_pSplashPalette || NULL == g_pArtPalette )
    {
        CleanupAndDisplayError("Could not load palettes");
        return E_FAIL;
    }

    // Set the palette before loading the art
    g_pddsFrontBuffer->SetPalette( g_pArtPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle(NULL), "DONUTS8", IMAGE_BITMAP, 0, 0,
                              LR_CREATEDIBSECTION );
    if( NULL == hbm )
    {
        CleanupAndDisplayError("Could not load bitmap");
        return E_FAIL;
    }

    // Copy the bitmaps. (We're OR'ing the hresults together so we only have to
    // do one check for a failure case.)
    hr |= DDUtil_CopyBitmap( g_pddsDonut,   hbm, 0,   0, 320, 384 );
    hr |= DDUtil_CopyBitmap( g_pddsPyramid, hbm, 0, 384, 320, 128 );
    hr |= DDUtil_CopyBitmap( g_pddsSphere,  hbm, 0, 512, 320,  32 );
    hr |= DDUtil_CopyBitmap( g_pddsCube,    hbm, 0, 544, 320,  32 );
    hr |= DDUtil_CopyBitmap( g_pddsShip,    hbm, 0, 576, 320, 256 );
    hr |= DDUtil_CopyBitmap( g_pddsNumbers, hbm, 0, 832, 320,  16 );

    if( FAILED(hr) )
    {
        CleanupAndDisplayError("Could not copy bitmaps to surfaces");
        DeleteObject( hbm );
        return E_FAIL;
    }

    // Done with the bitmap
    DeleteObject( hbm );

    // Set colorfill colors and color keys according to bitmap contents
    g_dwFillColor = DDUtil_ColorMatch( g_pddsDonut, CLR_INVALID );

    DDUtil_SetColorKey( g_pddsDonut,   CLR_INVALID );
    DDUtil_SetColorKey( g_pddsPyramid, CLR_INVALID );
    DDUtil_SetColorKey( g_pddsCube,    CLR_INVALID );
    DDUtil_SetColorKey( g_pddsSphere,  CLR_INVALID );
    DDUtil_SetColorKey( g_pddsShip,    CLR_INVALID );
    DDUtil_SetColorKey( g_pddsNumbers, CLR_INVALID );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: rnd()
// Desc:
//-----------------------------------------------------------------------------
FLOAT rnd( FLOAT low, FLOAT high )
{
    return low + ( high - low ) * ((FLOAT)rand())/RAND_MAX;
}




//-----------------------------------------------------------------------------
// Name: InitializeSound()
// Desc:
//-----------------------------------------------------------------------------
HRESULT InitializeSound( HWND hWnd )
{
    g_bSoundEnabled = FALSE;

    if( FAILED( DirectSoundCreate( NULL, &g_pDS, NULL ) ) )
        //return E_FAIL;
        //per Marcus's request, to run on his machine
                return S_FALSE;

    if( FAILED( g_pDS->SetCooperativeLevel( hWnd, DSSCL_NORMAL ) ) )
    {
        g_pDS->Release();
        g_pDS = NULL;

        return E_FAIL;
    }

    g_pBeginLevelSound     = DSUtil_CreateSound( g_pDS, "BeginLevel",      1 );
    g_pEngineIdleSound     = DSUtil_CreateSound( g_pDS, "EngineIdle",      1 );
    g_pEngineRevSound      = DSUtil_CreateSound( g_pDS, "EngineRev",       1 );
    g_pSkidToStopSound     = DSUtil_CreateSound( g_pDS, "SkidToStop",      1 );
    g_pShieldBuzzSound     = DSUtil_CreateSound( g_pDS, "ShieldBuzz",      1 );
    g_pShipExplodeSound    = DSUtil_CreateSound( g_pDS, "ShipExplode",     1 );
    g_pFireBulletSound     = DSUtil_CreateSound( g_pDS, "Gunfire",        25 );
    g_pShipBounceSound     = DSUtil_CreateSound( g_pDS, "ShipBounce",      4 );
    g_pDonutExplodeSound   = DSUtil_CreateSound( g_pDS, "DonutExplode",   10 );
    g_pPyramidExplodeSound = DSUtil_CreateSound( g_pDS, "PyramidExplode", 12 );
    g_pCubeExplodeSound    = DSUtil_CreateSound( g_pDS, "CubeExplode",    15 );
    g_pSphereExplodeSound  = DSUtil_CreateSound( g_pDS, "SphereExplode",  10 );

    g_bSoundEnabled = TRUE;

    // Start sounds that should be playing
#if 0 //not used for the mulit-user mapper
    if( g_bEngineIdle ) DSUtil_PlaySound( g_pEngineIdleSound, DSBPLAY_LOOPING );
    if( g_bShieldsOn )  DSUtil_PlaySound( g_pShieldBuzzSound, DSBPLAY_LOOPING );
    if( g_bThrusting )  DSUtil_PlaySound( g_pEngineRevSound,  DSBPLAY_LOOPING );
#endif //not used for the mulit-user mapper

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DestroySound()
// Desc:
//-----------------------------------------------------------------------------
VOID DestroySound()
{
    if( g_pDS )
    {
        DSUtil_DestroySound( g_pBeginLevelSound );
        DSUtil_DestroySound( g_pEngineIdleSound );
        DSUtil_DestroySound( g_pEngineRevSound );
        DSUtil_DestroySound( g_pSkidToStopSound );
        DSUtil_DestroySound( g_pShieldBuzzSound );
        DSUtil_DestroySound( g_pShipExplodeSound );
        DSUtil_DestroySound( g_pFireBulletSound );
        DSUtil_DestroySound( g_pShipBounceSound );
        DSUtil_DestroySound( g_pDonutExplodeSound );
        DSUtil_DestroySound( g_pPyramidExplodeSound );
        DSUtil_DestroySound( g_pCubeExplodeSound );
        DSUtil_DestroySound( g_pSphereExplodeSound );

        g_pDS->Release();
    }

    g_pBeginLevelSound     = NULL;
    g_pEngineIdleSound     = NULL;
    g_pEngineRevSound      = NULL;
    g_pSkidToStopSound     = NULL;
    g_pShieldBuzzSound     = NULL;
    g_pShipExplodeSound    = NULL;
    g_pFireBulletSound     = NULL;
    g_pShipBounceSound     = NULL;
    g_pDonutExplodeSound   = NULL;
    g_pPyramidExplodeSound = NULL;
    g_pCubeExplodeSound    = NULL;
    g_pSphereExplodeSound  = NULL;

    g_pDS           = NULL;
    g_bSoundEnabled = FALSE;
}


//-----------------------------------------------------------------------------
// Name: InitializeInput()
// Desc:
//-----------------------------------------------------------------------------
HRESULT InitializeInput( HWND hWnd )
{
    // Initialize DirectInpu
    if( FAILED( DIUtil_Initialize( hWnd ) ) )
    {
        MessageBox( hWnd, "Can't Initialize DirectInput", "TDonuts", MB_OK );
        return E_FAIL;
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: DestroyInput()
// Desc:
//-----------------------------------------------------------------------------
VOID DestroyInput()
{
    // Release DirectInput
    DIUtil_CleanupDirectInput();
}



//-----------------------------------------------------------------------------
// Name: GetDeviceInput(input[])
// Desc: Processes data from the input device.  Uses GetDeviceData().
//-----------------------------------------------------------------------------
VOID GetDeviceInput(DWORD input[NUMBER_OF_PLAYERS])
{
   HRESULT     hr = S_OK;

   for (int pl = 0; pl < NUMBER_OF_PLAYERS; pl++)
   {
		printf("pl = %i", pl);

       for (int dv = 0; dv < (int) g_dwNumDevices[pl]; dv++)
       {

		   printf("dv = %i", dv);

	   if (g_pDevices[pl][dv] != NULL)
	   {
		DWORD dwItems = 10;
		DIDEVICEOBJECTDATA rgdod[10];
		hr = g_pDevices[pl][dv]->Poll();
		if (SUCCEEDED(hr))
		{
		    hr = g_pDevices[pl][dv]->GetDeviceData(
			  sizeof(DIDEVICEOBJECTDATA),
			  rgdod,
			  &dwItems,
			  0);
		    if (SUCCEEDED(hr))
		    {
		      //get the sematics codes
		      for (int j=0; j < (int)dwItems; j++)
		      {
			    //for axes, do axes stuff
			    if (rgdod[j].uAppData & AXIS_MASK)
			    {
				    //blow out all of the axis data
				    dwInputState[pl] &= ~(KEY_RIGHT);
				    dwInputState[pl] &= ~(KEY_LEFT);
				    dwInputState[pl] &= ~(KEY_UP);
				    dwInputState[pl] &= ~(KEY_DOWN);

				    if( ((int)rgdod[j].dwData) > 10 )
				    {
					   dwInputState[pl] |= ( rgdod[j].uAppData == AXIS_LR ) ? KEY_RIGHT : KEY_DOWN;
				    }
				    if( ((int)rgdod[j].dwData) < -10 )
				    {
					   dwInputState[pl] |= ( rgdod[j].uAppData == AXIS_LR ) ? KEY_LEFT : KEY_UP;
				    }

							    
			    }
			    else //do buttons
			    {
				  //if rgdod[j].dwData & 0x80, it means the button went down,
				    //else it went up
				  if (rgdod[j].dwData & 0x80)
				  {
				       //set the new state
				       dwInputState[pl] |= rgdod[j].uAppData;
				  }
				  else
				  {
					dwInputState[pl] &= ~(rgdod[j].uAppData);
				  }
			    }
			    }
			  }
		     }

	   }

	 }
	    //set the new state
	if (SUCCEEDED(hr))
	{
	    input[pl] = dwInputState[pl];
	}

   }

}