#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "shootgal.h"
#include "win1632.h"

static char ShootGalClass[32]="ShootGalClass";
HWND        MessageWnd = NULL;
HWND        ScoreWnd = NULL;
BOOL        MessagesOn = FALSE;

int PASCAL WinMain( HINSTANCE, HINSTANCE, LPSTR, int );
static BOOL FirstInstance( HINSTANCE );
static BOOL AnyInstance( HINSTANCE, int );

BOOL _EXPORT FAR PASCAL About( HWND, UINT, WPARAM, LPARAM );
BOOL _EXPORT FAR PASCAL SpeedDlgProc( HWND, UINT, WPARAM, LPARAM );
long _EXPORT FAR PASCAL WindowProc( HWND, UINT, WPARAM, LPARAM );
BOOL TurnMessageWindowOn( HWND );
BOOL TurnScoreWindowOn( HWND );
BOOL _EXPORT FAR PASCAL MessageWindowProc( HWND, UINT, WPARAM, LPARAM );
BOOL _EXPORT FAR PASCAL ScoreProc( HWND, UINT, WPARAM, LPARAM );
static void CheckHit( HDC, POINT );
static void DrawBitmap( HDC, HBITMAP, int, int );
POINT RandPoint( RECT, POINT );
void _EXPORT FAR PASCAL DrawBolt( int, int, LPSTR );
static void ShootBolt( HWND );
static void BoomSound( void );
static void BoltSound( void );

/*
 * WinMain - initialization, message loop
 */
int PASCAL WinMain( HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline,
                    int cmdshow )
/*******************************/
{
    MSG         msg;

    cmdline = cmdline;          /* shut up compiler warning */
    prev_inst = prev_inst;

#ifdef __WINDOWS_386__
    sprintf( ShootGalClass,"ShootGalClass%d", this_inst );
#else
    if( !prev_inst )
#endif
        if( !FirstInstance( this_inst ) ) return( FALSE );

    if( !AnyInstance( this_inst, cmdshow ) ) return( FALSE );

    while( GetMessage( &msg, NULL, 0, 0 ) ) {
        /*
         * check to see if any of the messages are for a modeless dialog box,
         */
        if( ( MessageWnd == NULL || !IsDialogMessage( MessageWnd, &msg ) ) &&
            ( ScoreWnd == NULL || !IsDialogMessage( ScoreWnd, &msg ) ) ) {

            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }

    }

    return( msg.wParam );

} /* WinMain */

/*
 * FirstInstance - register window class for the application,
 *                 and do any other application initialization
 */
static BOOL FirstInstance( HINSTANCE this_inst )
/*******************************************/
{
    WNDCLASS    wc;
    BOOL        rc;

    /*
     * set up and register window classes
     */
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (LPVOID) WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = sizeof( DWORD );
    wc.hInstance = this_inst;
    wc.hIcon = LoadIcon( this_inst, "ShootGalIcon" );
    wc.hCursor = LoadCursor( this_inst, "guncursor" );
    wc.hbrBackground = GetStockObject( WHITE_BRUSH );
    wc.lpszMenuName = "ShootGalMenu";
    wc.lpszClassName = ShootGalClass;
    rc = RegisterClass( &wc );

    return( rc );

} /* FirstInstance */

/*
 * AnyInstance - do work required for every instance of the application:
 *                create the window, initialize data
 */
static BOOL AnyInstance( HINSTANCE this_inst, int cmdshow )
/******************************************************/
{
    HWND        window_handle;
    BITMAP      bitmapbuff;
    extra_data  *edata_ptr;

    /*
     * create main window
     */
    window_handle = CreateWindow(
        ShootGalClass,           /* class */
        "Open Watcom Shooting Gallery - Sample Application",    /* caption */
        WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,          /* style */
        CW_USEDEFAULT,          /* init. x pos */
        CW_USEDEFAULT,          /* init. y pos */
        CW_USEDEFAULT,          /* init. x size */
        CW_USEDEFAULT,          /* init. y size */
        NULL,                   /* parent window */
        NULL,                   /* menu handle */
        this_inst,              /* program handle */
        NULL                    /* create parms */
        );

    if( !window_handle ) return( FALSE );

    /*
     * get a timer
     */
    while( !SetTimer( window_handle, TARGET_TIMER, STD_TARGET_SPEED,
                                                 (LPVOID) NULL ) ) {
        if( MessageBox( window_handle, "Too many timers in use!", NULL,
                        MB_ICONEXCLAMATION | MB_RETRYCANCEL ) == IDCANCEL ) {
            return( FALSE );
        }
    }

    /*
     * initialize data
     */
    edata_ptr = calloc( 1, sizeof( extra_data ) );
    if( edata_ptr == NULL ) return( FALSE );

    edata_ptr->target_bmp = LoadBitmap( this_inst, "target" );
    GetObject( edata_ptr->target_bmp, sizeof( BITMAP ), (LPSTR) &bitmapbuff);

    edata_ptr->sound_on = FALSE;
    edata_ptr->score_on = FALSE;
    edata_ptr->target.x = 0;
    edata_ptr->target.y = 0;
    edata_ptr->size.x = bitmapbuff.bmWidth;
    edata_ptr->size.y = bitmapbuff.bmHeight;
    edata_ptr->aim = edata_ptr->target;
    edata_ptr->bolt = edata_ptr->aim;
    edata_ptr->target_speed = STD_TARGET_SPEED;
    edata_ptr->bolt_speed = STD_BOLT_SPEED;
    GetClientRect( window_handle, &edata_ptr->client_rect );
    edata_ptr->message_window_proc =
             MakeProcInstance( (FARPROC)MessageWindowProc, this_inst );
    edata_ptr->score_window_proc =
             MakeProcInstance( (FARPROC)ScoreProc, this_inst );
    edata_ptr->bolt_icon = LoadIcon( this_inst, "Bolt" );

    /*
     * put a pointer to the above structure in the main window structure
     */
    SetWindowLong( window_handle, EXTRA_DATA_OFFSET, (DWORD) edata_ptr );

    /*
     * display window
     */
    ShowWindow( window_handle, cmdshow );
    UpdateWindow( window_handle );

    return( TRUE );

} /* AnyInstance */

/*
 * About -  processes messages for the about dialogue.
 */
BOOL _EXPORT FAR PASCAL About( HWND window_handle, UINT msg,
                                WPARAM wparam, LPARAM lparam )
/********************************************************/
{
    lparam = lparam;                    /* turn off warning */
    window_handle = window_handle;

    switch( msg ) {
    case WM_INITDIALOG:
        return( TRUE );
    case WM_COMMAND:
        if( LOWORD( wparam ) == IDOK ) {
            EndDialog( window_handle, TRUE );
            return( TRUE );
        }
        break;
    }
    return( FALSE );

} /* About */

/*
 * SpeedDlgProc - processes messages for the speed dialogue box.
 *
 * Allows the user to select speeds for the target or lightning bolt,
 * depending on which selection was chosen.
 */
BOOL _EXPORT FAR PASCAL SpeedDlgProc( HWND dialog_wnd, UINT msg,
                                WPARAM wparam, LPARAM lparam )
/********************************************************/
{
    extra_data              *edata_ptr;
    short                   speed;
    static short            old_speed = 0; /* in case the user hits CANCEL */
    static short            what_are_we_setting = 0;
    static short            fastest_speed = 0;
    static short            slowest_speed = 0;
    HWND                    scrollbar;
    WORD                    scroll_code;

    edata_ptr = (extra_data *) GetWindowLong(
                 GetWindow( dialog_wnd, GW_OWNER ), EXTRA_DATA_OFFSET );

    switch( msg ) {
    case WM_INITDIALOG:
        what_are_we_setting = (short)lparam;
        if( what_are_we_setting == SET_TARGET_SPEED ) {
            SetDlgItemText( dialog_wnd, SET_WHAT, (LPSTR)"Set Target Speed" );
            old_speed = edata_ptr->target_speed;
            fastest_speed = FASTEST_TARGET_SPEED;
            slowest_speed = SLOWEST_TARGET_SPEED;
        } else {
            SetDlgItemText( dialog_wnd, SET_WHAT, (LPSTR)"Set Lightning Bolt Speed" );
            SetDlgItemText( dialog_wnd, TEST, (LPSTR)"Click RIGHT mouse button to test." );
            /*
             * we want fastest on left, slowest on right,
             * so use negative numbers for bolt speed
             */
            old_speed = -edata_ptr->bolt_speed;
            fastest_speed = -FASTEST_BOLT_SPEED;
            slowest_speed = -SLOWEST_BOLT_SPEED;
        }
        scrollbar = GetDlgItem( dialog_wnd, SPEED_SCROLL );
        SetScrollRange( scrollbar, SB_CTL, fastest_speed, slowest_speed, FALSE );
        SetScrollPos( scrollbar, SB_CTL, old_speed, FALSE );
        return( TRUE );

    case WM_RBUTTONDOWN:
        if( what_are_we_setting == SET_BOLT_SPEED ) {
            /*
             * shoot bolt diagonally accross the screen as a test
             */
            edata_ptr->aim.x = edata_ptr->client_rect.left;
            edata_ptr->aim.y = edata_ptr->client_rect.bottom;
            ShootBolt( GetWindow( dialog_wnd, GW_OWNER ) );
        }
        break;
    case WM_HSCROLL:
        speed = ( what_are_we_setting == SET_TARGET_SPEED )
                ? edata_ptr->target_speed : -edata_ptr->bolt_speed;
        scrollbar = GetDlgItem( dialog_wnd, SPEED_SCROLL );
        scroll_code = GET_WM_HSCROLL_CODE( wparam, lparam );
        switch( scroll_code ) {
        case SB_PAGEDOWN:
            speed += 15;    /* note - no break - flow through to next case */
        case SB_LINEDOWN:
            speed = min( slowest_speed, ++speed );
            break;
        case SB_PAGEUP:
            speed -= 15;    /* note - no break - flow through to next case */
        case SB_LINEUP:
            speed = max( fastest_speed, --speed );
            break;
        case SB_TOP:
            speed = fastest_speed;
            break;
        case SB_BOTTOM:
            speed = slowest_speed;
            break;
        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
            speed = GET_WM_HSCROLL_POS( wparam, lparam );
            break;
        default:
            return( FALSE );
        }
        SetScrollPos( scrollbar, SB_CTL, speed , TRUE );
        if( what_are_we_setting == SET_TARGET_SPEED ) {
            edata_ptr->target_speed = speed;

            /* restart timer at new speed */
            KillTimer( GetWindow( dialog_wnd, GW_OWNER), TARGET_TIMER );
            SetTimer( GetWindow( dialog_wnd, GW_OWNER), TARGET_TIMER,
                      speed, (LPVOID) NULL );
        } else {
            /* change speed back to positive value */
            edata_ptr-> bolt_speed = -speed;
        }
        break;
    case WM_COMMAND:
        switch( LOWORD( wparam ) ) {
        case IDOK:
            EndDialog( dialog_wnd, TRUE );
            return( TRUE );
        case IDCANCEL:
            /* change speed back to old value */
            if( what_are_we_setting == SET_TARGET_SPEED ) {
                edata_ptr->target_speed = old_speed;
            } else {
                edata_ptr->bolt_speed = -old_speed;
            }
            EndDialog( dialog_wnd, TRUE );
            return( TRUE );
        break;
        }
    }
    return( FALSE );

} /* SpeedDlgProc */

/*
 * WindowProc - handle messages for the main application window
 */
LONG _EXPORT FAR PASCAL WindowProc( HWND window_handle, UINT msg,
                                     WPARAM wparam, LPARAM lparam )
/*************************************************************/
{
    FARPROC             proc;
    HANDLE              inst_handle;
    extra_data          *edata_ptr;
    HDC                 hdc;
    PAINTSTRUCT         ps;
    RECT                rect;
    HBRUSH              brush;
    WORD                cmd;

    /*
     * if the message window is ON, send all messages we want to display to the
     * message window, so that we can see what is happening
     * ( before we actually process the message )
     */
    if( MessagesOn ) {
        switch( msg ) {
        case WM_COMMAND:
        case WM_MOUSEMOVE:
        case WM_LBUTTONUP:
        case WM_LBUTTONDOWN:
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONUP:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONDBLCLK:
        case WM_KEYDOWN:
        case WM_KEYUP:
        case WM_CHAR:
        case WM_TIMER:
        case WM_HSCROLL:
        case WM_VSCROLL:
            SendMessage( MessageWnd, msg, wparam, lparam );
            break;
        }
    }

    /*
     * now process the message
     */
    switch( msg ) {
    case WM_CREATE:
        inst_handle = GET_HINST( window_handle );
        /*
         * make sure message window is turned OFF to start
         */
        MessagesOn = FALSE;
        CheckMenuItem ( GetMenu( window_handle ), MENU_MESSAGE_WINDOW_ON,
                        MF_BYCOMMAND | MF_UNCHECKED );
        break;
    case WM_LBUTTONDOWN:
        /*
         * zap the target with a lightning bolt!
         */
        edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                            EXTRA_DATA_OFFSET );
        /*
         * set the aim point to where the mouse was just clicked
         * set the bolt start point to the top left corner of the window
         */

        MAKE_POINT( edata_ptr->aim, lparam );
        edata_ptr->bolt.x = edata_ptr->client_rect.right - BOLTWIDTH;
        edata_ptr->bolt.y = edata_ptr->client_rect.top;
        /*
         * shoot the bolt from the current bolt position to the aim point
         */
        ShootBolt( window_handle );
        break;
    case WM_SIZE:
        edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                            EXTRA_DATA_OFFSET );
        /*
         * store the new size of the window
         */
        GetClientRect( window_handle, &edata_ptr->client_rect );
        SetScrollRange( window_handle, SB_HORZ, edata_ptr->client_rect.left,
                        edata_ptr->client_rect.right, TRUE );
        SetScrollRange( window_handle, SB_VERT, edata_ptr->client_rect.top,
                        edata_ptr->client_rect.bottom, TRUE );
        break;
    case WM_COMMAND:
        cmd = LOWORD( wparam );
        switch( cmd ) {
        case MENU_ABOUT:
            inst_handle = GET_HINST( window_handle );
            proc = MakeProcInstance( (FARPROC)About, inst_handle );
            DialogBox( inst_handle,"AboutBox", window_handle, (DLGPROC)proc );
            FreeProcInstance( proc );
            break;
        case MENU_EXIT:
            SendMessage( window_handle, WM_CLOSE, 0, 0L );
            break;
        case MENU_SET_TARGET_SPEED:
            inst_handle = GET_HINST( window_handle );
            proc = MakeProcInstance( (FARPROC)SpeedDlgProc, inst_handle );
            DialogBoxParam( inst_handle,"SpeedDlg", window_handle, (DLGPROC)proc,
                            (DWORD)SET_TARGET_SPEED );
            FreeProcInstance( proc );
            break;
        case MENU_SET_BOLT_SPEED:
            inst_handle = GET_HINST( window_handle );
            proc = MakeProcInstance( (FARPROC)SpeedDlgProc, inst_handle );
            DialogBoxParam( inst_handle,"SpeedDlg", window_handle, (DLGPROC)proc,
                            (DWORD)SET_BOLT_SPEED );
            FreeProcInstance( proc );
            break;
        case MENU_SCORE_WINDOW_ON:
            /*
             * toggle the score window on or off
             */
            edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                                EXTRA_DATA_OFFSET );
            if( !edata_ptr->score_on ) {
                TurnScoreWindowOn( window_handle );
                CheckMenuItem( GetMenu( window_handle ),
                                cmd, MF_BYCOMMAND | MF_CHECKED );
                edata_ptr->score_on = TRUE;
            } else {
                SendMessage( ScoreWnd, WM_CLOSE, 0, 0L );
                CheckMenuItem ( GetMenu( window_handle ), cmd,
                    MF_BYCOMMAND | MF_UNCHECKED );
                edata_ptr->score_on = FALSE;
            }
            break;
        case MENU_SOUND_ON:
            /*
             * toggle the sound on or off
             */
            edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                                EXTRA_DATA_OFFSET );
            if( !edata_ptr->sound_on ) {
                CheckMenuItem ( GetMenu( window_handle ),
                                cmd, MF_BYCOMMAND | MF_CHECKED );
                edata_ptr->sound_on = TRUE;
            } else {
                CheckMenuItem ( GetMenu( window_handle ), cmd,
                    MF_BYCOMMAND | MF_UNCHECKED );
                edata_ptr->sound_on = FALSE;
            }
            break;
        case MENU_MESSAGE_WINDOW_ON:
            /*
             * toggle the message window on or off
             */
            if( !MessagesOn ) {
                TurnMessageWindowOn( window_handle );
                CheckMenuItem( GetMenu( window_handle ),
                                cmd, MF_BYCOMMAND | MF_CHECKED );
                MessagesOn = TRUE;
            } else {
                SendMessage( MessageWnd, WM_CLOSE, 0, 0L );
                CheckMenuItem( GetMenu( window_handle ), cmd,
                    MF_BYCOMMAND | MF_UNCHECKED );
                MessagesOn = FALSE;
            }
            break;
        }
        break;
    case WM_PAINT:
        edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                            EXTRA_DATA_OFFSET );
        hdc = BeginPaint (window_handle, &ps);
        /*
         * paint the invalid area with the background colour
         */
        brush = CreateSolidBrush( BACKGROUND );
        FillRect( hdc, &ps.rcPaint, brush );
        DeleteObject( brush );

        rect.left   = edata_ptr->target.x;
        rect.top    = edata_ptr->target.y;
        rect.right  = rect.left + edata_ptr->size.x;
        rect.bottom = rect.top + edata_ptr->size.y;

        /*
         * if part of the target bitmap is invalid, redraw it
         */
        if( IntersectRect( &rect, &rect, &ps.rcPaint ) ) {
            DrawBitmap( hdc, edata_ptr->target_bmp,
                        edata_ptr->target.x, edata_ptr->target.y );
        }
        EndPaint(window_handle, &ps);
        break;
    case WM_HSCROLL:
    case WM_VSCROLL:
        /*
         * use the scrollbars to move the target around
         */
        {
            short       position; /* the x or y position of the scrollbar */
            short       max;
            short       min;
            WORD        code;
            WORD        pos;

            edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                                EXTRA_DATA_OFFSET );
            if( msg == WM_HSCROLL ) {
                position = edata_ptr->target.x;
                code = GET_WM_HSCROLL_CODE( wparam, lparam );
                pos = GET_WM_HSCROLL_POS( wparam, lparam );
            } else {
                position = edata_ptr->target.y;
                code = GET_WM_VSCROLL_CODE( wparam, lparam );
                pos = GET_WM_VSCROLL_POS( wparam, lparam );
            }

            switch( code ) {
            case SB_PAGEDOWN:
                position += 15;
                break;
            case SB_LINEDOWN:
                position++;
                break;
            case SB_PAGEUP:
                position -= 15;
                break;
            case SB_LINEUP:
                position--;
                break;
            case SB_THUMBPOSITION:
            case SB_THUMBTRACK:
                position = pos;
                break;
            default:
                return( 0L );
            }
            if( msg == WM_HSCROLL ) {
                GetScrollRange( window_handle, SB_HORZ, (LPINT)&min, (LPINT)&max );
                edata_ptr->target.x = max( min( position, max ), min );
                SetScrollPos( window_handle, SB_HORZ, edata_ptr->target.x , TRUE );
            } else {
                GetScrollRange( window_handle, SB_VERT, (LPINT)&min, (LPINT)&max );
                edata_ptr->target.y = max( min( position, max ), min );
                SetScrollPos( window_handle, SB_VERT, edata_ptr->target.y, TRUE );
            }
            InvalidateRect( window_handle, &edata_ptr->client_rect, FALSE );
        }
        break;
    case WM_MOVE_TARGET:

        /*
         * move the target to a random location on the screen
         */

        edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                            EXTRA_DATA_OFFSET );
        rect.left   = edata_ptr->target.x;
        rect.top    = edata_ptr->target.y;
        rect.right  = rect.left + edata_ptr->size.x;
        rect.bottom = rect.top + edata_ptr->size.y;
        InvalidateRect( window_handle, &rect, TRUE );

        edata_ptr->target = RandPoint( edata_ptr->client_rect, edata_ptr->size );

        rect.left   = edata_ptr->target.x;
        rect.top    = edata_ptr->target.y;
        rect.right  = rect.left + edata_ptr->size.x;
        rect.bottom = rect.top + edata_ptr->size.y;
        InvalidateRect( window_handle, &rect, TRUE );

        /* set the scrollbars to indicate the new position */
        SetScrollPos( window_handle, SB_HORZ, edata_ptr->target.x , TRUE );
        SetScrollPos( window_handle, SB_VERT, edata_ptr->target.y, TRUE );

        break;
    case WM_TIMER:
        SendMessage( window_handle, WM_MOVE_TARGET, 0, 0L );
        break;
        break;
    case WM_DESTROY:
        edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                                  EXTRA_DATA_OFFSET );
        KillTimer( window_handle, TARGET_TIMER );       /* Stops the timer */
        #ifndef __NT__
        FreeProcInstance( edata_ptr->message_window_proc );
        FreeProcInstance( edata_ptr->score_window_proc );
        #endif
        PostQuitMessage( 0 );
        break;

    default:
        return( DefWindowProc( window_handle, msg, wparam, lparam ) );
    }
    return( 0L );

} /* WindowProc */

/*
 * TurnMessageWindowOn - create the Message window, which will display
 * messages received by WindowProc
 * this window is a modeless dialog box
 */
BOOL TurnMessageWindowOn( HWND window_handle )
/********************************************/
{

    HANDLE      inst_handle;
    extra_data  *edata_ptr;

    edata_ptr = (extra_data *) GetWindowLong( window_handle, EXTRA_DATA_OFFSET );

    inst_handle = GET_HINST( window_handle );

    MessageWnd = CreateDialog( inst_handle,"MessageWindow", window_handle,
                     (DLGPROC)edata_ptr->message_window_proc );
    return( MessageWnd != NULL );

} /* TurnMessageWindowOn */

/*
 * TurnMessageWindowOn - create the score window, which will display
 * the number of shots the user has taken and what locations they have hit
 * this window is a modeless dialog box
 */
BOOL TurnScoreWindowOn( HWND window_handle )
/******************************************/
{

    HANDLE      inst_handle;
    extra_data  *edata_ptr;

    edata_ptr = (extra_data *) GetWindowLong( window_handle, EXTRA_DATA_OFFSET );

    inst_handle = GET_HINST( window_handle );

    ScoreWnd = CreateDialog( inst_handle,"ScoreWindow", window_handle,
                     (DLGPROC)edata_ptr->score_window_proc );
    return( ScoreWnd != NULL );

} /* TurnScoreWindowOn */

/*
 * processes messages for the score dialog box
 */
BOOL _EXPORT FAR PASCAL ScoreProc( HWND window_handle, UINT msg,
                                            WPARAM wparam, LPARAM lparam )
/********************************************************************/
{

    extra_data *edata_ptr;

    wparam = wparam;
    lparam = lparam;

    edata_ptr = (extra_data *) GetWindowLong(
                GetWindow( window_handle, GW_OWNER ), EXTRA_DATA_OFFSET );

    switch (msg) {
    case WM_INITDIALOG:
        SetDlgItemInt( window_handle, SHOTS, 0, FALSE );
        SetDlgItemInt( window_handle, YELLOW, 0, FALSE );
        SetDlgItemInt( window_handle, RED, 0, FALSE );
        SetDlgItemInt( window_handle, BLUE, 0, FALSE );
        SetDlgItemInt( window_handle, BLACK, 0, FALSE );
        SetDlgItemInt( window_handle, WHITE, 0, FALSE );
        SetDlgItemInt( window_handle, MISSED, 0, FALSE );
        return( TRUE );
    case WM_CLOSE:
        CheckMenuItem ( GetMenu( GetWindow( window_handle, GW_OWNER ) ),
            MENU_SCORE_WINDOW_ON, MF_BYCOMMAND | MF_UNCHECKED );
        DestroyWindow( window_handle );
        /* not EndDialog, since this is a MODELESS dialog box */
        edata_ptr->score_on = FALSE;
        ScoreWnd = NULL;
        break;
    }
    return( FALSE );
} /* ScoreProc */

BOOL _EXPORT FAR PASCAL MessageWindowProc( HWND window_handle, UINT msg,
                                            WPARAM wparam, LPARAM lparam )
/********************************************************************/
{
    char textbuffer[48];
    /* buffer to hold strings before they are sent to dialog box controls */

    static unsigned long timercount = 0;
    /* counter - incremented each time a timer message is recieved */


    /*
     * other than WM_INITDIALOG & WM_CLOSE, all messages should be displayed
     * in the dialog box, in the appropriate location
     * so use wsprintf to format the message & then send the string to a
     * static text control
     */
    switch (msg) {
    case WM_INITDIALOG:
        return( TRUE );
    case WM_CLOSE:
        CheckMenuItem ( GetMenu( GetWindow( window_handle, GW_OWNER ) ),
            MENU_MESSAGE_WINDOW_ON, MF_BYCOMMAND | MF_UNCHECKED );
        DestroyWindow( window_handle );
        /* not EndDialog, since this is a MODELESS dialog box */
        MessagesOn = FALSE;
        MessageWnd = NULL;
        break;
    case WM_MOUSEMOVE:
        wsprintf( textbuffer, "WM_MOUSEMOVE: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, MOUSE_MOVE_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_LBUTTONDOWN:
        wsprintf( textbuffer, "WM_LBUTTONDOWN: %x, %x, %x", wparam,
                  (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, L_BUTTON_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_LBUTTONUP:
        wsprintf( textbuffer, "WM_LBUTTONUP: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, L_BUTTON_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_LBUTTONDBLCLK:
        wsprintf(textbuffer, "WM_LBUTTONDBLCLK: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, L_BUTTON_BOX, (LPSTR)textbuffer );
        return( TRUE );
    case WM_RBUTTONDOWN:
        wsprintf(textbuffer, "WM_RBUTTONDOWN: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, R_BUTTON_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_RBUTTONUP:
        wsprintf(textbuffer, "WM_RBUTTONUP: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, R_BUTTON_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_RBUTTONDBLCLK:
        wsprintf(textbuffer, "WM_RBUTTONDBLCLK: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, R_BUTTON_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_KEYDOWN:
        wsprintf(textbuffer, "WM_KEYDOWN: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, KEY_UP_OR_DOWN_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_KEYUP:
        wsprintf(textbuffer, "WM_KEYUP: %x, %x, %x", wparam,
                (int)(short)LOWORD(lparam ), (int)(short)HIWORD(lparam ) );
        SetDlgItemText( window_handle, KEY_UP_OR_DOWN_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_CHAR:
        wsprintf(textbuffer, "WM_CHAR: %c, %x, %x", wparam , LOWORD(lparam ), HIWORD(lparam ));
        SetDlgItemText( window_handle, CHARACTER_BOX, (LPSTR)textbuffer );
        return( TRUE );

    case WM_TIMER:
        {
            extra_data *edata_ptr;

            edata_ptr = (extra_data *) GetWindowLong(
                GetWindow( window_handle, GW_OWNER ), EXTRA_DATA_OFFSET );

            timercount += edata_ptr->target_speed;
            wsprintf(textbuffer, "WM_TIMER: %d seconds", timercount / 1000 );
            SetDlgItemText( window_handle, TIMER_BOX, (LPSTR)textbuffer );
            return( TRUE );
        }
    case WM_HSCROLL:
    case WM_VSCROLL:
        {
            char        *buff1;
            char        *buff2;
            WORD        code;

            if( msg == WM_HSCROLL ) {
                buff1 = "WM_HSCROLL";
                code = GET_WM_HSCROLL_CODE( wparam, lparam );
            } else {
                buff1 = "WM_VSCROLL";
                code = GET_WM_VSCROLL_CODE( wparam, lparam );
            }
            switch( code ){
            case SB_LINEUP:
                buff2 = "SB_LINEUP";
                break;
            case SB_LINEDOWN:
                buff2 = "SB_LINEDOWN";
                break;
            case SB_PAGEUP:
                buff2 = "SB_PAGEUP";
                break;
            case SB_PAGEDOWN:
                buff2 = "SB_PAGEDOWN";
                break;
            case SB_THUMBPOSITION:
                buff2 = "SB_THUMBPOSITION";
                break;
            case SB_THUMBTRACK:
                buff2 = "SB_THUMBTRACK";
                break;
            case SB_ENDSCROLL:
                buff2 = "SB_ENDSCROLL";
                break;
            default :
                buff2 = "unknown";
                break;
            }
            wsprintf(textbuffer, "%s: %s, %x, %x",
                (LPSTR)buff1, (LPSTR)buff2, (int)(short)LOWORD(lparam ),
                (int)(short)HIWORD(lparam ));
            SetDlgItemText( window_handle, SCROLL_BOX, (LPSTR)textbuffer );
            return( TRUE );
        }
    }
    return ( FALSE );
} /* MessageWindowProc */

/*
 * calculate where on the target the bolt hit
 * this is based on the colours of the target rings
 */
static void CheckHit( HDC hdc, POINT hit_point )
/**********************************************/
{
    DWORD               colour;
    unsigned short      points;
    unsigned short      dialog_item;
    BOOL                translated;

    colour = GetPixel( hdc, hit_point.x, hit_point.y );

    switch( colour ) {
    case RING1:
        dialog_item = YELLOW;
        break;
    case RING2:
        dialog_item = RED;
        break;
    case RING3:
        dialog_item = BLUE;
        break;
    case RING4:
        dialog_item = BLACK;
        break;
    case RING5:
        dialog_item = WHITE;
        break;
    case BACKGROUND:
        dialog_item = MISSED;
        break;
    }

    /*
     * increment # of hits on location
     */
    points = GetDlgItemInt( ScoreWnd, dialog_item, &translated, FALSE );
    points++;
    SetDlgItemInt( ScoreWnd, dialog_item, points, FALSE );

    /*
     * increment # of shots
     */
    points = GetDlgItemInt( ScoreWnd, SHOTS, &translated, FALSE );
    points++;
    SetDlgItemInt( ScoreWnd, SHOTS, points, FALSE );

    return;

} /* CheckHit */

/*
 * display a bitmap on the given DC, at device coordinates x,y
 * make the bitmap the same size as the source bitmap
 */
static void DrawBitmap( HDC hdc, HBITMAP bitmap, int x, int y )
/*****************************************************************/
{
    BITMAP      bitmapbuff;
    HDC         memorydc;
    POINT       origin;
    POINT       size;

    memorydc = CreateCompatibleDC( hdc );
    SelectObject( memorydc, bitmap );
    SetMapMode( memorydc, GetMapMode( hdc ) );
    GetObject( bitmap, sizeof( BITMAP ), (LPSTR) &bitmapbuff );

    origin.x = x;
    origin.y = y;
    size.x = bitmapbuff.bmWidth;
    size.y = bitmapbuff.bmHeight;

    DPtoLP( hdc, &origin, 1 );
    DPtoLP( memorydc, &size, 1 );

    BitBlt( hdc, origin.x, origin.y, size.x, size.y, memorydc, 0, 0, SRCCOPY);
    DeleteDC( memorydc );
} /* DrawBitmap */

/*
 * pick a random point so that a bitmap with width & height specified by
 * the second parameter will fit inside the given rectangle
 */
POINT RandPoint( RECT rect, POINT size )
/**************************************/
{
    POINT random_point;
    short width;    /* width of the area to pick from */
    short height;   /* height of the area to pick from */

    width = rect.right - rect.left - size.x;
    height = rect.bottom - rect.top - size.y;

    random_point.x = rand() % width + rect.left;
    random_point.y = rand() % height + rect.top;

    return( random_point );
} /* RandPoint */

/*
 * Draws an icon at the coordinates x,y, after copying a saved bitmap back
 * onto the screen at the coordinates where the last icon was drawn and
 * saving the area under the icon to a DC in memory
 */
void DoDrawBolt( int x, int y, icon_mover * mover)
/************************************************/
{

    /*
     * to make this look fast, even on slow machines, only redraw the icon
     * when the x coordinate is a multiple of the bolt speed
     * thus if speed = 5, the icon will be redrawn every 5th pixel it moves
     */
    if( x % mover->speed != 0 ) return;

    /* if it is not the first iteration */
    if( mover->last.x != -1 || mover->last.y != -1 ) {
        /* redraw the area that the last icon covered */
        BitBlt( mover->screen_dc, mover->last.x, mover->last.y, mover->size.x,
                mover->size.y, mover->storage_dc, 0, 0, SRCCOPY);
    }

    /* save the area that will be under the icon to a DC in memory */
    BitBlt( mover->storage_dc, 0, 0, mover->size.x, mover->size.y,
            mover->screen_dc, x, y, SRCCOPY);

    DrawIcon( mover->screen_dc, x, y, mover->icon );

    /* save the coordinates for next time */
    mover->last.x = x;
    mover->last.y = y;

} /* DoDrawBolt */

/*
 * called by windows from LineDDA funcion
 */
void _EXPORT FAR PASCAL DrawBolt( int x, int y, LPSTR dataptr )
/*************************************************************/
{

    DoDrawBolt( x, y, (icon_mover *) dataptr );

} /* DrawBolt */

/*
 * "shoot" a bolt from the current bolt location to the "aim" location
 */
static void ShootBolt( HWND window_handle )
/*****************************************/
{
    extra_data  *edata_ptr;
    HDC         hdc;
    HBITMAP     screensavebmp;
    MSG         msg;
    FARPROC     proc;
    HANDLE      inst_handle;
    icon_mover  mover;

    edata_ptr = (extra_data *) GetWindowLong( window_handle,
                                        EXTRA_DATA_OFFSET );

    hdc = GetDC( window_handle );
    inst_handle = GET_HINST( window_handle );

    /* set up an "icon_mover" struct to give all needed data to DrawBolt */
    mover.icon = edata_ptr->bolt_icon;
    mover.size.x = BOLTWIDTH;
    mover.size.y = BOLTHEIGHT;
    mover.last.x = -1;
    mover.last.y = -1;
    mover.screen_dc = hdc;
    mover.storage_dc = CreateCompatibleDC( hdc );
    mover.speed = edata_ptr->bolt_speed;

    screensavebmp = CreateCompatibleBitmap( hdc, mover.size.x, mover.size.y );
    SelectObject( mover.storage_dc, screensavebmp );

    if( edata_ptr->sound_on ) {
        BoltSound();
    }
    /*
     * for each point on the line between the points BOLT and AIM,
     * call DrawBolt
     * use aim - boltheight so that the bottom left corner of the bolt icon
     * stops at the point AIM
     */
    proc = MakeProcInstance( (FARPROC)DrawBolt, inst_handle );
    LineDDA( edata_ptr->bolt.x, edata_ptr->bolt.y, edata_ptr->aim.x,
             edata_ptr->aim.y - BOLTHEIGHT, (LINEDDAPROC)proc, (LPARAM)(LPVOID)&mover );
    FreeProcInstance( proc );

    /*
     * remove all WM_LBUTTONDOWN messages from the application queue which
     * occured while the bolt was "in the air"
     * this prevents the shots from building up - only 1 can happen at a time
     */
    while( PeekMessage( &msg, window_handle, WM_LBUTTONDOWN, WM_LBUTTONDOWN,
                        PM_REMOVE ) );

    /* make sure the bolt is drawn at the final location regardless of speed */
    mover.speed = 1;
    DoDrawBolt( edata_ptr->aim.x, edata_ptr->aim.y - BOLTHEIGHT, &mover );
    if( edata_ptr->sound_on ) {
        BoomSound();
    }
    /* redraw the background behind the icon */
    BitBlt( mover.screen_dc, mover.last.x, mover.last.y, mover.size.x,
            mover.size.y, mover.storage_dc, 0, 0, SRCCOPY);

    CheckHit( hdc, edata_ptr->aim );

    DeleteDC( mover.storage_dc );
    DeleteObject( screensavebmp );
    ReleaseDC(window_handle, hdc);

} /* ShootBolt */

/*
 * make a sound like thunder
 * this function is called after each time BoltSound is called
 * Sound is not implemented for the NT version
 */
static void BoomSound( void )
/***************************/
{
#ifndef __NT__
    short i;
    short low;
    short high;

    StopSound();
    SetVoiceQueueSize( 1, 600 );
    SetVoiceAccent( 1, 120, 50, S_STACCATO, 0 );

    for( i=0; i < 1000; i++ ) {
        /*
         * create a random low-pitched sound
         */
        high = rand() % 200 + 1;
        low = rand() % high + 1;
        SetVoiceSound( 1, MAKELONG( low, high ), 1 );
    }
    StartSound();
    WaitSoundState( S_QUEUEEMPTY ); /* wait for the sound to finish */
    StopSound();
    CloseSound();
#endif
} /* BoomSound */

/*
 * create a tone that goes from high-pitched to low-pitched
 * Sound is not implemented for the NT version
 */
static void BoltSound( void )
/***************************/
{
#ifndef __NT__
    short i;

    OpenSound();
    SetVoiceAccent( 1, 120, 50, S_LEGATO, 0 );
    for( i=84; i > 0; i-- ) {
        SetVoiceNote( 1, i, 128, 1 );
        /* place a 128th note of value i into voice queue 1 */
    }
    StartSound();
#endif
} /* BoltSound */
