Subversion Repositories Games.Chess Giants

Rev

Rev 177 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // window_main.cpp
  2.  
  3. #include "../common.h"
  4.  
  5.  
  6. // global variables used in this module only
  7. static wchar_t folder_path[MAX_PATH];
  8.  
  9.  
  10. // prototypes of local functions
  11. static bool Button_IsHovered (guibutton_t *button, int gui_x, int gui_y);
  12. static bool Button_UpdateHoverState (guibutton_t *button, int gui_x, int gui_y);
  13.  
  14.  
  15. LRESULT CALLBACK WindowProc_Main (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  16. {
  17.    // this is the main message handler for the program
  18.  
  19.    static wchar_t fen_string[128];
  20.    static char selectable_parts[14] = "PRNBQK kqbnrp";
  21.    static int prevgui_x = 0;
  22.    static int prevgui_y = 0;
  23.    static bool ctrl_pressed = false;
  24.    static bool rbutton_pushed = false;
  25.  
  26.    unsigned short wParam_hiword;
  27.    unsigned short wParam_loword;
  28.    int prev_hovered_position[2];
  29.    player_t *current_player;
  30.    player_t *opposite_player;
  31.    player_t *local_player;
  32.    player_t *remote_player;
  33.    ccreply_t *entered_ccreply;
  34.    boardmove_t *current_move;
  35.    float board_x;
  36.    float board_y;
  37.    int gui_x;
  38.    int gui_y;
  39.    int line;
  40.    int column;
  41.    int index_line;
  42.    int index_column;
  43.    int part_index;
  44.    int part_color;
  45.    int part_count;
  46.    int viewer_index;
  47.    RECT rect;
  48.    POINT point;
  49.  
  50.    // filter out the commonly used message values
  51.    wParam_hiword = HIWORD (wParam);
  52.    wParam_loword = LOWORD (wParam);
  53.  
  54.    // get current and opposite players and see if we're online
  55.    current_player = Player_GetCurrent ();
  56.    opposite_player = Player_GetOpposite ();
  57.    remote_player = Player_FindByType (PLAYER_INTERNET);
  58.  
  59.    ////////////////////////////////////////////////////////////////////////////////////////////////
  60.    // has the window just been fired up ?
  61.    if (message == WM_CREATE)
  62.    {
  63.       the_scene.gui.is_entering_text = false; // we are NOT entering text yet
  64.  
  65.       // call the default window message processing function to keep things going
  66.       return (DefWindowProc (hWnd, message, wParam, lParam));
  67.    }
  68.  
  69.    ////////////////////////////////////////////////////////////////////////////////////////////////
  70.    // else is the window being resized ?
  71.    else if ((message == WM_SIZE) || (message == WM_WINDOWPOSCHANGED))
  72.    {
  73.       GetWindowRect (hMainWnd, &rect); // grab the current window size and update the options accordingly
  74.       options.want_maximized = (rect.right - rect.left > GetSystemMetrics (SM_CXMAXIMIZED) * 90 / 100) && (rect.bottom - rect.top > GetSystemMetrics (SM_CYMAXIMIZED) * 80 / 100);
  75.       if (!options.want_maximized)
  76.       {
  77.          options.window_width = max (rect.right - rect.left, 640); // min window width
  78.          options.window_height = max (rect.bottom - rect.top, 480); // min window height (only save them when the window is not maximized)
  79.       }
  80.  
  81.       return (0); // don't let Windows do the default processing on this message
  82.    }
  83.  
  84.    ////////////////////////////////////////////////////////////////////////////////////////////////
  85.    // else have we clicked on the close button ?
  86.    else if (message == WM_CLOSE)
  87.    {
  88.       if ((the_board.game_state == STATE_PLAYING) && ((the_board.move_count > 1) || (remote_player != NULL)))
  89.          DialogBox_Quit (); // if a game has started OR if we are online against somebody, ask for confirmation
  90.       else
  91.          is_dialogbox_quit_validated = true; // if game hasn't started yet, quit without question
  92.  
  93.       return (0); // don't let Windows do the default processing on this message
  94.    }
  95.  
  96.    ////////////////////////////////////////////////////////////////////////////////////////////////
  97.    // else is the user closing its session OR the window is being destroyed ?
  98.    else if ((message == WM_QUERYENDSESSION) || (message == WM_ENDSESSION) || (message == WM_DESTROY))
  99.       terminate_everything = true; // suicide (for some reason, PostQuitMessage() is unreliable !?)
  100.  
  101.    /////////////////
  102.    // MENU EVENTS //
  103.    /////////////////
  104.  
  105.    ///////////////
  106.    // menu command
  107.    else if (message == WM_COMMAND)
  108.    {
  109.       // ANY menu choice makes the "new game" / "open game" front GUI button disappear
  110.       GUIBUTTON_DISABLE (the_scene.gui.newgamebutton);
  111.       GUIBUTTON_DISABLE (the_scene.gui.opengamebutton);
  112.  
  113.       // game menu, new game
  114.       if (wParam_loword == MENUID_GAME_NEWGAME)
  115.       {
  116.          if ((the_board.game_state == STATE_PLAYING) && (the_board.move_count > 1))
  117.             DialogBox_Resign (RESIGNTYPE_NEWGAME); // if a game is playing, ask to resign first
  118.          else
  119.             DialogBox_NewGame (); // game not started yet, show the new game dialog box
  120.       }
  121.  
  122.       // game menu, setup start position
  123.       else if (wParam_loword == MENUID_GAME_SETUPPOSITION)
  124.          Board_EnterSetupPosition (&the_board); // enter setup position mode
  125.  
  126.       // game menu, load game
  127.       else if (wParam_loword == MENUID_GAME_LOAD)
  128.       {
  129.          if ((the_board.game_state == STATE_PLAYING) && (the_board.move_count > 1))
  130.             DialogBox_Resign (RESIGNTYPE_LOADGAME); // if a game is playing, ask to resign first
  131.          else
  132.             DialogBox_Load (); // fire up the load dialog box
  133.       }
  134.  
  135.       // game menu, save game
  136.       else if (wParam_loword == MENUID_GAME_SAVE)
  137.       {
  138.          if (save_pathname[0] != 0)
  139.             is_dialogbox_save_validated = true; // if the filename is known, save it directly
  140.          else
  141.             DialogBox_Save (false); // else fire up the save dialog box
  142.       }
  143.  
  144.       // game menu, save as
  145.       else if (wParam_loword == MENUID_GAME_SAVEAS)
  146.          DialogBox_Save (false); // fire up the save dialog box
  147.  
  148.       // game menu, save position as
  149.       else if (wParam_loword == MENUID_GAME_SAVEPOSITIONAS)
  150.          DialogBox_SavePosition (); // fire up the save position dialog box
  151.  
  152.       // game menu, pause
  153.       else if (wParam_loword == MENUID_GAME_PAUSE)
  154.       {
  155.          is_paused ^= true; // toggle game pause state on/off
  156.          CheckMenuItem (GetMenu (hWnd), MENUID_GAME_PAUSE, (is_paused ? MF_CHECKED : MF_UNCHECKED)); // highlight the menu entry as necessary
  157.          the_board.reevaluate = true; // evaluate board again
  158.       }
  159.  
  160.       // game menu, resign
  161.       else if (wParam_loword == MENUID_GAME_RESIGN)
  162.          DialogBox_Resign (RESIGNTYPE_UNDEFINED); // if a game has started, ask for confirmation
  163.  
  164.       // game menu, statistics (stats are only available in online mode)
  165.       else if (wParam_loword == MENUID_GAME_STATISTICS)
  166.       {
  167.          local_player = Player_FindByType (PLAYER_HUMAN);
  168.          if (local_player != NULL)
  169.             PlayerCard_FindOrCreate (local_player->name); // display player's own player card
  170.       }
  171.  
  172.       // game menu, options
  173.       else if (wParam_loword == MENUID_GAME_OPTIONS)
  174.          DialogBox_Options (); // fire up the options dialog box
  175.  
  176.       // game menu, quit
  177.       else if (wParam_loword == MENUID_GAME_QUIT)
  178.       {
  179.          if ((the_board.game_state == STATE_PLAYING) && ((the_board.move_count > 1) || (remote_player != NULL)))
  180.             DialogBox_Quit (); // if a game has started OR if we are online against somebody, ask for confirmation
  181.          else
  182.             is_dialogbox_quit_validated = true; // if game hasn't started yet, quit without question
  183.       }
  184.  
  185.       // move menu, cancel last move
  186.       else if (wParam_loword == MENUID_MOVE_CANCELLASTMOVE)
  187.          the_board.players[current_viewer].wants_cancel = true; // remember this player wants to cancel his last move
  188.  
  189.       // move menu, suggest move
  190.       else if (wParam_loword == MENUID_MOVE_SUGGESTMOVE)
  191.          the_board.players[current_viewer].wants_hint = true; // remember this player wants a hint
  192.  
  193.       // move menu, comment move
  194.       else if (wParam_loword == MENUID_MOVE_COMMENTMOVE)
  195.          DialogBox_Comment (); // fire up the comment dialog box
  196.  
  197.       // move menu, go to move
  198.       else if (wParam_loword == MENUID_MOVE_GOTOMOVE)
  199.          DialogBox_GoToMove (); // fire up the go to move dialog box
  200.  
  201.       // chessboard menu, swap sides
  202.       else if (wParam_loword == MENUID_CHESSBOARD_SWAPSIDES)
  203.          the_board.want_playerswap = true; // remember board sides are to be swapped
  204.  
  205.       // chessboard menu, rename players
  206.       else if (wParam_loword == MENUID_CHESSBOARD_RENAMESIDES)
  207.          DialogBox_RenameSides(); // fire up the rename sides dialog box
  208.  
  209.       // chessboard menu, beginning of game
  210.       else if (wParam_loword == MENUID_CHESSBOARD_BEGINNINGOFGAME)
  211.       {
  212.          the_board.viewed_move = 0; // enter view mode and go to the beginning of the game
  213.          Audio_PlaySoundAtCenter (SOUNDTYPE_CLICK); // make a click sound at the center of the board
  214.          the_board.reevaluate = true; // evaluate board again
  215.       }
  216.  
  217.       // chessboard menu, previous move (only if arrow is enabled)
  218.       else if ((wParam_loword == MENUID_CHESSBOARD_PREVIOUSMOVE) && (the_scene.gui.larrow.state != 0))
  219.       {
  220.          the_board.viewed_move--; // enter view mode and go back one move
  221.          Audio_PlaySoundAtCenter (SOUNDTYPE_CLICK); // make a click sound at the center of the board
  222.          the_board.reevaluate = true; // evaluate board again
  223.       }
  224.  
  225.       // chessboard menu, next move (only if arrow is enabled)
  226.       else if ((wParam_loword == MENUID_CHESSBOARD_NEXTMOVE) && (the_scene.gui.rarrow.state != 0))
  227.       {
  228.          the_board.viewed_move++; // enter view mode and go forward one move
  229.          Audio_PlaySoundAtCenter (SOUNDTYPE_CLICK); // make a click sound at the center of the board
  230.          the_board.reevaluate = true; // evaluate board again
  231.       }
  232.  
  233.       // chessboard menu, current state of game
  234.       else if (wParam_loword == MENUID_CHESSBOARD_CURRENTSTATEOFGAME)
  235.       {
  236.          the_board.viewed_move = the_board.move_count - 1; // enter view mode and go to the current state of the game
  237.          Audio_PlaySoundAtCenter (SOUNDTYPE_CLICK); // make a click sound at the center of the board
  238.          the_board.reevaluate = true; // evaluate board again
  239.       }
  240.  
  241.       // chessboard menu, top view
  242.       else if (wParam_loword == MENUID_CHESSBOARD_TOPVIEW)
  243.       {
  244.          // cycle through both players and change their view angles EXCEPT the opponent if he's human
  245.          for (viewer_index = 0; viewer_index < 2; viewer_index++)
  246.             if ((the_board.players[viewer_index].type == PLAYER_COMPUTER)
  247.                 || (the_board.players[viewer_index].type == PLAYER_INTERNET)
  248.                 || (viewer_index == current_viewer))
  249.             {
  250.                the_board.players[viewer_index].view_pitch = 89.99f;
  251.                the_board.players[viewer_index].view_yaw = (current_viewer == COLOR_BLACK ? 90.0f : -90.0f);
  252.                the_board.players[viewer_index].view_distance = 58.0f;
  253.             }
  254.       }
  255.  
  256.       // chessboard menu, default view
  257.       else if (wParam_loword == MENUID_CHESSBOARD_DEFAULTVIEW)
  258.       {
  259.          // cycle through both players and change their view angles EXCEPT the opponent if he's human
  260.          for (viewer_index = 0; viewer_index < 2; viewer_index++)
  261.             if ((the_board.players[viewer_index].type == PLAYER_COMPUTER)
  262.                 || (the_board.players[viewer_index].type == PLAYER_INTERNET)
  263.                 || (viewer_index == current_viewer))
  264.             {
  265.                the_board.players[viewer_index].view_pitch = 55.0f;
  266.                the_board.players[viewer_index].view_yaw = (current_viewer == COLOR_BLACK ? 90.0f : -90.0f);
  267.                the_board.players[viewer_index].view_distance = 70.0f;
  268.             }
  269.       }
  270.  
  271.       // chessboard menu, reset view
  272.       else if (wParam_loword == MENUID_CHESSBOARD_RESETVIEW)
  273.       {
  274.          // cycle through both players and change their view angles EXCEPT the opponent if he's human
  275.          for (viewer_index = 0; viewer_index < 2; viewer_index++)
  276.             if ((the_board.players[viewer_index].type == PLAYER_COMPUTER)
  277.                 || (the_board.players[viewer_index].type == PLAYER_INTERNET)
  278.                 || (viewer_index == current_viewer))
  279.             {
  280.                the_board.players[viewer_index].view_pitch = the_board.players[current_viewer].custom_pitch;
  281.                the_board.players[viewer_index].view_yaw = the_board.players[current_viewer].custom_yaw;
  282.                the_board.players[viewer_index].view_distance = the_board.players[current_viewer].custom_distance;
  283.             }
  284.       }
  285.  
  286.       // chessboard menu, zoom in
  287.       else if (wParam_loword == MENUID_CHESSBOARD_ZOOMIN)
  288.       {
  289.          // scroll up & save this as the new custom distance
  290.          the_board.players[current_viewer].view_distance = max (48.0f, the_board.players[current_viewer].view_distance - 2.0f);
  291.          the_board.players[current_viewer].custom_distance = the_board.players[current_viewer].view_distance;
  292.          the_scene.update = true; // update the 3D scene
  293.       }
  294.  
  295.       // chessboard menu, zoom out
  296.       else if (wParam_loword == MENUID_CHESSBOARD_ZOOMOUT)
  297.       {
  298.          // scroll down & save this as the new custom distance
  299.          the_board.players[current_viewer].view_distance = min (100.0f, the_board.players[current_viewer].view_distance + 2.0f);
  300.          the_board.players[current_viewer].custom_distance = the_board.players[current_viewer].view_distance;
  301.          the_scene.update = true; // update the 3D scene
  302.       }
  303.  
  304.       // chessboard menu, change appearance
  305.       else if (wParam_loword == MENUID_CHESSBOARD_CHANGEAPPEARANCE)
  306.          DialogBox_ChangeAppearance ();
  307.  
  308.       // chessboard menu, display windows desktop
  309.       else if (wParam_loword == MENUID_CHESSBOARD_DISPLAYWINDOWSDESKTOP)
  310.          ShowWindow (hWnd, SW_MINIMIZE);
  311.  
  312.       // internet menu, show online players
  313.       else if (wParam_loword == MENUID_INTERNET_SHOWONLINEPLAYERS)
  314.          Window_Opponents ();
  315.  
  316.       // internet menu, show sought games
  317.       else if (wParam_loword == MENUID_INTERNET_SHOWSOUGHTGAMES)
  318.          Window_Sought ();
  319.  
  320.       // internet menu, seek game
  321.       else if (wParam_loword == MENUID_INTERNET_SEEKGAME)
  322.          DialogBox_SendSeek ();
  323.  
  324.       // internet menu, chatter channels
  325.       else if (wParam_loword == MENUID_INTERNET_CHATTERCHANNELS)
  326.          Window_ChatterChannels ();
  327.  
  328.       // internet menu, enter chat text
  329.       else if (wParam_loword == MENUID_INTERNET_ENTERCHATTEXT)
  330.          PostMessage (hWnd, WM_CHAR, L' ', 0); // emulate a space bar hit
  331.  
  332.       // internet menu, display player card
  333.       else if (wParam_loword == MENUID_INTERNET_DISPLAYPLAYERCARD)
  334.          DialogBox_PlayerInfoName ();
  335.  
  336.       // internet menu, display your card
  337.       else if (wParam_loword == MENUID_INTERNET_DISPLAYYOURCARD)
  338.       {
  339.          local_player = Player_FindByType (PLAYER_HUMAN);
  340.          if (local_player != NULL)
  341.             PlayerCard_FindOrCreate (local_player->name); // display player's own player card
  342.       }
  343.  
  344.       // internet menu, MOTD
  345.       else if (wParam_loword == MENUID_INTERNET_MOTD)
  346.          Window_MOTD ();
  347.  
  348.       // help menu, help
  349.       else if (wParam_loword == MENUID_HELP_HELP)
  350.       {
  351.          swprintf_s (folder_path, WCHAR_SIZEOF (folder_path), L"%s\\Chess Giants (%s).html", app_path, languages[language_id].name);
  352.          if (_waccess (folder_path, 0) != 0)
  353.             swprintf_s (folder_path, WCHAR_SIZEOF (folder_path), L"%s\\Chess Giants (English).html", app_path);
  354.          ShellExecute (NULL, L"open", folder_path, NULL, NULL, SW_MAXIMIZE); // fire up the help file
  355.       }
  356.  
  357.       // help menu, get chess games
  358.       else if (wParam_loword == MENUID_HELP_GETCHESSGAMES)
  359.          ShellExecute (NULL, L"open", L"http://www.chessgames.com", NULL, NULL, SW_MAXIMIZE); // fire up the browser
  360.  
  361.       // help menu, add/modify visual themes
  362.       else if (wParam_loword == MENUID_HELP_ADDMODIFYVISUALTHEMES)
  363.       {
  364.          swprintf_s (folder_path, WCHAR_SIZEOF (folder_path), L"%s\\themes", app_path);
  365.          ShellExecute (NULL, L"open", folder_path, NULL, NULL, SW_SHOWNORMAL); // fire up Windows Explorer
  366.       }
  367.  
  368.       // help menu, add/modify engines
  369.       else if (wParam_loword == MENUID_HELP_ADDMODIFYENGINES)
  370.       {
  371.          swprintf_s (folder_path, WCHAR_SIZEOF (folder_path), L"%s\\engines", app_path);
  372.          ShellExecute (NULL, L"open", folder_path, NULL, NULL, SW_SHOWNORMAL); // fire up Windows Explorer
  373.       }
  374.  
  375.       // help menu, add/modify translations
  376.       else if (wParam_loword == MENUID_HELP_ADDMODIFYTRANSLATIONS)
  377.       {
  378.          swprintf_s (folder_path, WCHAR_SIZEOF (folder_path), L"%s\\data\\languages", app_path);
  379.          ShellExecute (NULL, L"open", folder_path, NULL, NULL, SW_SHOWNORMAL); // fire up Windows Explorer
  380.       }
  381.  
  382.       // help menu, report a problem to the author
  383.       else if (wParam_loword == MENUID_HELP_REPORTAPROBLEM)
  384.       {
  385.          // send the game engine history to the author and display an error message if failed
  386.          if (MessageBox (hMainWnd, LOCALIZE (L"ReportAProblem"), LOCALIZE (L"ImportantMessage"), MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3) == IDYES)
  387.             if (Debug_SendLogToAuthor ("user request", true)) // with user description
  388.                MessageBox (hMainWnd, LOCALIZE (L"MessageSent"), PROGRAM_NAME, MB_ICONINFORMATION | MB_OK);
  389.             else
  390.                MessageBox (hMainWnd, LOCALIZE (L"NoInternetConnection"), LOCALIZE (L"FatalError"), MB_ICONWARNING | MB_OK);
  391.       }
  392.  
  393.       // help menu, about
  394.       else if (wParam_loword == MENUID_HELP_ABOUT)
  395.          DialogBox_About (); // show the About dialog box
  396.  
  397.       // call the default window message processing function to keep things going
  398.       return (DefWindowProc (hWnd, message, wParam, lParam));
  399.    }
  400.  
  401.    /////////////////////////////////////////////////////
  402.    // ctrl key press or release (while not in animation)
  403.    else if ((message == WM_KEYDOWN) && (wParam == VK_CONTROL) && (animation_endtime + 1.0f < current_time))
  404.    {
  405.       ctrl_pressed = true; // remember the ctrl key is pressed
  406.  
  407.       // call the default window message processing function to keep things going
  408.       return (DefWindowProc (hWnd, message, wParam, lParam));
  409.    }
  410.    else if ((message == WM_KEYUP) && (wParam == VK_CONTROL) && (animation_endtime + 1.0f < current_time))
  411.    {
  412.       ctrl_pressed = false; // remember the ctrl key is released
  413.       rbutton_pushed = false; // remember button is released
  414.       the_scene.update = true; // update the 3D scene
  415.  
  416.       // call the default window message processing function to keep things going
  417.       return (DefWindowProc (hWnd, message, wParam, lParam));
  418.    }
  419.  
  420.    //////////////////
  421.    // MOUSE EVENTS //
  422.    //////////////////
  423.  
  424.    ////////////////////////////////////////////////////////////////////////////////////////////////
  425.    // left mouse button push
  426.    else if (message == WM_LBUTTONDOWN)
  427.    {
  428.       // are we in animation OR are mouse commands NOT allowed ?
  429.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  430.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  431.  
  432.       // are we in board closeup mode OR are we online AND do we NOT have the right to select anything ?
  433.       if (((current_distance == CLOSEUP_VIEW_DISTANCE) && (current_pitch == CLOSEUP_VIEW_PITCH))
  434.           || ((remote_player != NULL) && !remote_player->is_in_game))
  435.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default window message processing function to keep things going
  436.  
  437.       // is the ctrl key pressed (emulates a right click) ?
  438.       if (ctrl_pressed)
  439.       {
  440.          prevgui_x = GET_X_LPARAM (lParam); // remember mouse coordinates
  441.          prevgui_y = GET_Y_LPARAM (lParam);
  442.  
  443.          // if we click something, stop moving the table immediately
  444.  
  445.          // cycle through both players and change their view angles EXCEPT the opponent if he's human
  446.          for (viewer_index = 0; viewer_index < 2; viewer_index++)
  447.             if ((the_board.players[viewer_index].type == PLAYER_COMPUTER)
  448.                 || (the_board.players[viewer_index].type == PLAYER_INTERNET)
  449.                 || (viewer_index == current_viewer))
  450.             {
  451.                the_board.players[viewer_index].view_pitch = current_pitch;
  452.                the_board.players[viewer_index].view_yaw = current_yaw;
  453.             }
  454.  
  455.          rbutton_pushed = true; // remember button is clicked
  456.       }
  457.  
  458.       // call the default window message processing function to keep things going
  459.       return (DefWindowProc (hWnd, message, wParam, lParam));
  460.    }
  461.  
  462.    ////////////////////////////////////////////////////////////////////////////////////////////////
  463.    // left mouse button release
  464.    else if (message == WM_LBUTTONUP)
  465.    {
  466.       // are we in animation OR are mouse commands NOT allowed ?
  467.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  468.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  469.  
  470.       // is the ctrl key pressed (emulates a right click) ?
  471.       if (ctrl_pressed)
  472.       {
  473.          rbutton_pushed = false; // remember button is released
  474.          the_scene.update = true; // update the 3D scene
  475.  
  476.          // call the default window message processing function to keep things going
  477.          return (DefWindowProc (hWnd, message, wParam, lParam));
  478.       }
  479.  
  480.       prevgui_x = GET_X_LPARAM (lParam); // remember mouse coordinates
  481.       prevgui_y = GET_Y_LPARAM (lParam);
  482.  
  483.       // get mouse coordinates
  484.       gui_x = GET_X_LPARAM (lParam);
  485.       gui_y = GET_Y_LPARAM (lParam);
  486.  
  487.       // is the "new game" button displayed AND is the mouse hovering it ?
  488.       if ((the_scene.gui.newgamebutton.state != 0) && Button_IsHovered (&the_scene.gui.newgamebutton, gui_x, gui_y))
  489.       {
  490.          GUIBUTTON_DISABLE (the_scene.gui.newgamebutton); // hide the "new game" button
  491.          GUIBUTTON_DISABLE (the_scene.gui.opengamebutton); // hide the "open game" button
  492.          DialogBox_NewGame (); // fire up the "new game" dialog box
  493.       }
  494.  
  495.       // is the "open game" button displayed AND is the mouse hovering it ?
  496.       if ((the_scene.gui.opengamebutton.state != 0) && Button_IsHovered (&the_scene.gui.opengamebutton, gui_x, gui_y))
  497.       {
  498.          GUIBUTTON_DISABLE (the_scene.gui.newgamebutton); // hide the "new game" button
  499.          GUIBUTTON_DISABLE (the_scene.gui.opengamebutton); // hide the "open game" button
  500.          DialogBox_Load (); // fire up the "open game" dialog box
  501.       }
  502.  
  503.       // is the chat button displayed AND is the mouse hovering it ?
  504.       if ((the_scene.gui.chatbutton.state != 0) && Button_IsHovered (&the_scene.gui.chatbutton, gui_x, gui_y))
  505.       {
  506.          // find or create the corresponding interlocutor structure and fire it up
  507.          if (remote_player != NULL)
  508.             Interlocutor_FindOrCreate (remote_player->name);
  509.       }
  510.  
  511.       // is the games button displayed AND is the mouse hovering it ?
  512.       if ((the_scene.gui.gamesbutton.state != 0) && Button_IsHovered (&the_scene.gui.gamesbutton, gui_x, gui_y))
  513.          Window_Sought (); // if so, display the sought games window
  514.  
  515.       // is the people button displayed AND is the mouse hovering it ?
  516.       if ((the_scene.gui.peoplebutton.state != 0) && Button_IsHovered (&the_scene.gui.peoplebutton, gui_x, gui_y))
  517.          Window_Opponents (); // if so, display the opponents window
  518.  
  519.       // are we in board closeup mode OR are we online AND do we NOT have the right to select anything ?
  520.       if (((current_distance == CLOSEUP_VIEW_DISTANCE) && (current_pitch == CLOSEUP_VIEW_PITCH))
  521.           || ((remote_player != NULL) && !remote_player->is_in_game))
  522.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default window message processing function to keep things going
  523.  
  524.       // handle game history navigation
  525.       if ((the_scene.gui.llarrow.state != 0) && Button_IsHovered (&the_scene.gui.llarrow, gui_x, gui_y))
  526.          SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_BEGINNINGOFGAME, NULL); // send a "jump to beginning" event
  527.       else if ((the_scene.gui.larrow.state != 0) && Button_IsHovered (&the_scene.gui.larrow, gui_x, gui_y))
  528.          SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_PREVIOUSMOVE, NULL); // send a "previous move" event
  529.       else if ((the_scene.gui.rarrow.state != 0) && Button_IsHovered (&the_scene.gui.rarrow, gui_x, gui_y))
  530.          SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_NEXTMOVE, NULL); // send a "next move" event
  531.       else if ((the_scene.gui.rrarrow.state != 0) && Button_IsHovered (&the_scene.gui.rrarrow, gui_x, gui_y))
  532.          SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_CURRENTSTATEOFGAME, NULL); // send a "jump to end" event
  533.  
  534.       // is the parts selection line displayed AND is the mouse anywhere near it ?
  535.       if (the_scene.gui.is_partspick_displayed && Render_IsMouseInBox (gui_x, gui_y, 0.0f, 0.0f, 100.0f, 11.0f))
  536.       {
  537.          // for each selectable part, if the mouse is on it, mark it as selected
  538.          for (part_index = 0; part_index < 13; part_index++)
  539.             if (Render_IsMouseInBox (gui_x, gui_y, part_index * (100.0f / 13.0f), 0, 100.0f / 13.0f, 11.0f))
  540.             {
  541.                the_scene.gui.partspick_selectedpart = selectable_parts[part_index]; // mark it as selected
  542.                Audio_PlaySoundAtCenter (SOUNDTYPE_CLICK); // make a click sound at the center of the board
  543.                break; // no need to search further if one selection was found
  544.             }
  545.       }
  546.  
  547.       // if we are not allowed to select anything, don't even try
  548.       if (is_paused || (current_player->type != PLAYER_HUMAN) || (the_board.viewed_move != the_board.move_count - 1)
  549.           || ((the_board.game_state != STATE_SETUPPOSITION) && (the_board.game_state != STATE_PLAYING))
  550.           || ((remote_player != NULL) && !remote_player->is_in_game))
  551.       {
  552.          the_scene.update = true; // update the 3D scene
  553.  
  554.          // call the default window message processing function to keep things going
  555.          return (DefWindowProc (hWnd, message, wParam, lParam));
  556.       }
  557.  
  558.       // it's a single click. The user clicked on a square.
  559.  
  560.       // figure out the coordinates on table
  561.       Render_MouseToFloor (gui_x, gui_y, &board_x, &board_y);
  562.  
  563.       // translate them to board coordinates
  564.       the_board.hovered_position[0] = (int) floor ((20.0f - board_y) / 5.0f);
  565.       the_board.hovered_position[1] = 7 - (int) floor ((20.0f - board_x) / 5.0f);
  566.       highlight_endtime = 0; // stop highlighting anything
  567.  
  568.       // if it's outside the table, end the job
  569.       if (!IS_VALID_POSITION (the_board.hovered_position))
  570.       {
  571.          the_scene.update = true; // update the 3D scene
  572.  
  573.          // call the default window message processing function to keep things going
  574.          return (DefWindowProc (hWnd, message, wParam, lParam));
  575.       }
  576.  
  577.       // we clicked a valid slot on the table
  578.  
  579.       current_move = &the_board.moves[the_board.viewed_move]; // quick access to current move
  580.  
  581.       ///////////////////////////////////
  582.       // are we in parts placement mode ?
  583.       if (the_board.game_state == STATE_SETUPPOSITION)
  584.       {
  585.          // quick access to line and column
  586.          line = the_board.hovered_position[0];
  587.          column = the_board.hovered_position[1];
  588.  
  589.          // figure out the color of the part we are placing
  590.          if (the_scene.gui.partspick_selectedpart == tolower (the_scene.gui.partspick_selectedpart))
  591.             part_color = COLOR_BLACK; // black color
  592.          else
  593.             part_color = COLOR_WHITE; // white color
  594.  
  595.          // in parts placement mode, we must not allow for the creation of a deliring chessboard. A few rules have to be respected.
  596.          // for each color:
  597.          // - no more than 16 parts per color, i.e. the equivalent of 2 rows
  598.          // - no more than 8 pawns
  599.          // - no more than 10 knights, i.e. 2 genuine + 8 promoted
  600.          // - no more than 10 bishops, i.e. 2 genuine + 8 promoted
  601.          // - no more than 10 rooks,   i.e. 2 genuine + 8 promoted
  602.          // - no more than 9 queens,   i.e. 1 genuine + 8 promoted
  603.          // - no more than 1 king
  604.          // - no pawns on the 1st (back) or the 8th (promotion) row
  605.          // and also:
  606.          // - the side not on move must not be in check (TODO)
  607.  
  608.          // count limits
  609.          part_count = 0; // count the number of pieces of the color we want to place
  610.          for (index_line = 0; index_line < 8; index_line++)
  611.             for (index_column = 0; index_column < 8; index_column++)
  612.                if ((current_move->slots[index_line][index_column].part != PART_NONE) && (current_move->slots[index_line][index_column].color == part_color))
  613.                   part_count++;
  614.          if (part_count >= 16) // if we already have 16 parts of this color...
  615.          {
  616.             Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, line, column); // play the "illegal move" sound
  617.             Scene_AddCCAnnouncement (&the_scene, NULL, LOCALIZE (L"NoMoreThan16PiecesInTotal"));
  618.             return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  619.          }
  620.  
  621.          // are we erasing the king ? if so, replace it at his default location
  622.          if ((current_move->slots[line][column].part == PART_KING)
  623.              && (   ((current_move->slots[line][column].color == COLOR_BLACK) && (the_scene.gui.partspick_selectedpart != 'k'))
  624.                  || ((current_move->slots[line][column].color == COLOR_WHITE) && (the_scene.gui.partspick_selectedpart != 'K'))))
  625.          {
  626.             // is it ALREADY the king's default location ? if so, silently prevent the user from doing that
  627.             if ((line == (current_move->slots[line][column].color == COLOR_BLACK ? 7 : 0)) && (column == 4))
  628.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  629.  
  630.             // replace the king at his default location
  631.             Move_SetSlot (current_move, (current_move->slots[line][column].color == COLOR_BLACK ? 7 : 0), 4, current_move->slots[line][column].color, PART_KING);
  632.          }
  633.  
  634.          // place the selected part on the board
  635.          #define COUNT_PARTS(part_type) part_count = 0; for (index_line = 0; index_line < 8; index_line++) for (index_column = 0; index_column < 8; index_column++) if ((current_move->slots[index_line][index_column].color == part_color) && (current_move->slots[index_line][index_column].part == (part_type))) part_count++;
  636.          if (tolower (the_scene.gui.partspick_selectedpart) == 'p')
  637.          {
  638.             COUNT_PARTS (PART_PAWN); // count the number of pawns of the color we want to place
  639.             if ((part_count >= 8) || (line == 0) || (line == 7)) // if we already have 8 pawns of this color OR if we are attempting to place a pawn on the 1st or 8th line...
  640.             {
  641.                Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, line, column); // play the "illegal move" sound
  642.                Scene_AddCCAnnouncement (&the_scene, NULL, LOCALIZE (L"NoMoreThan8Pawns"));
  643.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  644.             }
  645.             Move_SetSlot (current_move, line, column, part_color, PART_PAWN); // pawn
  646.          }
  647.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'r')
  648.          {
  649.             COUNT_PARTS (PART_ROOK); // count the number of rooks of the color we want to place
  650.             if (part_count >= 10) // if we already have 10 rooks of this color (2 genuine + 8 promoted)...
  651.             {
  652.                Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, line, column); // play the "illegal move" sound
  653.                Scene_AddCCAnnouncement (&the_scene, NULL, LOCALIZE (L"NoMoreThan10Rooks"));
  654.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  655.             }
  656.             Move_SetSlot (current_move, line, column, part_color, PART_ROOK); // rook
  657.          }
  658.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'n')
  659.          {
  660.             COUNT_PARTS (PART_KNIGHT); // count the number of bishops of the color we want to place
  661.             if (part_count >= 10) // if we already have 10 knights of this color (2 genuine + 8 promoted)...
  662.             {
  663.                Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, line, column); // play the "illegal move" sound
  664.                Scene_AddCCAnnouncement (&the_scene, NULL, LOCALIZE (L"NoMoreThan10Knights"));
  665.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  666.             }
  667.             Move_SetSlot (current_move, line, column, part_color, PART_KNIGHT); // knight
  668.          }
  669.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'b')
  670.          {
  671.             COUNT_PARTS (PART_BISHOP); // count the number of bishops of the color we want to place
  672.             if (part_count >= 10) // if we already have 10 bishops of this color (2 genuine + 8 promoted)...
  673.             {
  674.                Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, line, column); // play the "illegal move" sound
  675.                Scene_AddCCAnnouncement (&the_scene, NULL, LOCALIZE (L"NoMoreThan10Bishops"));
  676.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  677.             }
  678.             Move_SetSlot (current_move, line, column, part_color, PART_BISHOP); // bishop
  679.          }
  680.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'q')
  681.          {
  682.             COUNT_PARTS (PART_QUEEN); // count the number of queens of the color we want to place
  683.             if (part_count >= 9) // if we already have 9 queens of this color (1 genuine + 8 promoted)...
  684.             {
  685.                Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, line, column); // play the "illegal move" sound
  686.                Scene_AddCCAnnouncement (&the_scene, NULL, LOCALIZE (L"NoMoreThan9Queens"));
  687.                return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message proc
  688.             }
  689.             Move_SetSlot (current_move, line, column, part_color, PART_QUEEN); // queen
  690.          }
  691.          else if (tolower (the_scene.gui.partspick_selectedpart) == 'k')
  692.          {
  693.             // parse the board for other kings of the same color and erase them
  694.             for (index_line = 0; index_line < 8; index_line++)
  695.                for (index_column = 0; index_column < 8; index_column++)
  696.                   if ((current_move->slots[index_line][index_column].color == part_color)
  697.                       && (current_move->slots[index_line][index_column].part == PART_KING))
  698.                      memset (&current_move->slots[index_line][index_column], 0, sizeof (boardslot_t)); // erase this slot
  699.  
  700.             Move_SetSlot (current_move, line, column, part_color, PART_KING); // king
  701.          }
  702.          else
  703.             Move_SetSlot (current_move, line, column, 0, PART_NONE); // no part
  704.  
  705.          // figure out which sound to play
  706.          if (the_scene.gui.partspick_selectedpart != ' ')
  707.             Audio_PlaySoundAtSlot (SOUNDTYPE_MOVE, line, column); // play a move sound when placing a part
  708.  
  709.          the_scene.update = true; // update the 3D scene
  710.  
  711.          // call the default window message processing function to keep things going
  712.          return (DefWindowProc (hWnd, message, wParam, lParam));
  713.       }
  714.       // end of parts placement mode
  715.       //////////////////////////////
  716.  
  717.       // does a selection NOT exist yet ?
  718.       if (!IS_VALID_POSITION (the_board.selected_position))
  719.       {
  720.          // is there a selectable part at this location ?
  721.          if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].part != PART_NONE)
  722.             && (current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].color == Board_ColorToMove (&the_board)))
  723.          {
  724.             // mark the selected position as selected (and remember it)
  725.             the_board.selected_position[0] = the_board.hovered_position[0];
  726.             the_board.selected_position[1] = the_board.hovered_position[1];
  727.          }
  728.  
  729.          the_scene.update = true; // update the 3D scene
  730.  
  731.          // call the default window message processing function to keep things going
  732.          return (DefWindowProc (hWnd, message, wParam, lParam));
  733.       }
  734.  
  735.       // a selection exists already
  736.  
  737.       // is it the slot that was previously selected ? (i.e, user wants to "unselect" it)
  738.       if ((the_board.hovered_position[0] == the_board.selected_position[0]) && (the_board.hovered_position[1] == the_board.selected_position[1]))
  739.       {
  740.          // forget the selected position
  741.          the_board.selected_position[0] = -1;
  742.          the_board.selected_position[1] = -1;
  743.  
  744.          the_scene.update = true; // update the 3D scene
  745.  
  746.          // call the default window message processing function to keep things going
  747.          return (DefWindowProc (hWnd, message, wParam, lParam));
  748.       }
  749.  
  750.       // else is it another part of the same color ? (i.e, user wants to change the part he selected)
  751.       else if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].part != PART_NONE)
  752.                && (current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].color == Board_ColorToMove (&the_board)))
  753.       {
  754.          // mark the selected position as selected (and remember it)
  755.          the_board.selected_position[0] = the_board.hovered_position[0];
  756.          the_board.selected_position[1] = the_board.hovered_position[1];
  757.  
  758.          the_scene.update = true; // update the 3D scene
  759.  
  760.          // call the default window message processing function to keep things going
  761.          return (DefWindowProc (hWnd, message, wParam, lParam));
  762.       }
  763.  
  764.       // else is it a possible move ?
  765.       else if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].flags & FLAG_POSSIBLEMOVE)
  766.                || (current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].flags & FLAG_TAKEABLE))
  767.       {
  768.          // are we in check after the move ? (FIXME: call EP version of this func for en passant moves)
  769.          if (Move_IsColorInCheckAfterTestMove (current_move, the_board.selected_position[0], the_board.selected_position[1], the_board.hovered_position[0], the_board.hovered_position[1], Board_ColorToMove (&the_board)))
  770.          {
  771.             Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, the_board.hovered_position[0], the_board.hovered_position[1]); // play the "illegal move" sound
  772.  
  773.             the_scene.update = true; // update the 3D scene
  774.  
  775.             // call the default window message processing function to keep things going
  776.             return (DefWindowProc (hWnd, message, wParam, lParam));
  777.          }
  778.  
  779.          ////////////////////
  780.          // movement is valid
  781.  
  782.          // do it
  783.          Board_AppendMove (&the_board, the_board.selected_position[0], the_board.selected_position[1], the_board.hovered_position[0], the_board.hovered_position[1], PART_NONE, NULL);
  784.          current_move = &the_board.moves[the_board.move_count - 1]; // update current move pointer
  785.  
  786.          // are we in internet mode ?
  787.          if (remote_player != NULL)
  788.             the_board.reevaluate = false; // if so, don't reevaluate the board yet, let the server do it
  789.  
  790.          // was it a pawn being promoted ? if so, display the dialog box and wait for the reply
  791.          if ((current_move->slots[the_board.hovered_position[0]][the_board.hovered_position[1]].part == PART_PAWN)
  792.              && ((the_board.hovered_position[0] == 0) || (the_board.hovered_position[0] == 7)))
  793.             DialogBox_PawnPromotion (); // display the pawn promotion dialog box
  794.  
  795.          // else it was a normal move
  796.          else
  797.          {
  798.             Board_SetSelectedAndHovered (&the_board, -1, -1, -1, -1); // forget the hovered and selected positions
  799.             opposite_player->should_wakeup = true; // and switch players
  800.          }
  801.  
  802.          the_scene.update = true; // update the 3D scene
  803.          animation_endtime = current_time + ANIMATION_DURATION; // play animation now
  804.  
  805.          // call the default window message processing function to keep things going
  806.          return (DefWindowProc (hWnd, message, wParam, lParam));
  807.       }
  808.  
  809.       // else it's another location
  810.  
  811.       Audio_PlaySoundAtSlot (SOUNDTYPE_ILLEGALMOVE, the_board.hovered_position[0], the_board.hovered_position[1]); // play the "illegal move" sound
  812.  
  813.       the_scene.update = true; // update the 3D scene
  814.  
  815.       // call the default window message processing function to keep things going
  816.       return (DefWindowProc (hWnd, message, wParam, lParam));
  817.    }
  818.  
  819.    ////////////////////////////////////////////////////////////////////////////////////////////////
  820.    // right mouse button push
  821.    else if (message == WM_RBUTTONDOWN)
  822.    {
  823.       // are we in animation OR are mouse commands NOT allowed ?
  824.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  825.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  826.  
  827.       prevgui_x = GET_X_LPARAM (lParam); // remember mouse coordinates
  828.       prevgui_y = GET_Y_LPARAM (lParam);
  829.  
  830.       // if we click something, stop moving the table immediately
  831.  
  832.       // cycle through both players and change their view angles EXCEPT the opponent if he's human
  833.       for (viewer_index = 0; viewer_index < 2; viewer_index++)
  834.          if ((the_board.players[viewer_index].type == PLAYER_COMPUTER)
  835.                || (the_board.players[viewer_index].type == PLAYER_INTERNET)
  836.                || (viewer_index == current_viewer))
  837.          {
  838.             the_board.players[viewer_index].view_pitch = current_pitch;
  839.             the_board.players[viewer_index].view_yaw = current_yaw;
  840.          }
  841.  
  842.       rbutton_pushed = true; // remember button is clicked
  843.  
  844.       // call the default window message processing function to keep things going
  845.       return (DefWindowProc (hWnd, message, wParam, lParam));
  846.    }
  847.  
  848.    ////////////////////////////////////////////////////////////////////////////////////////////////
  849.    // right mouse button release
  850.    else if (message == WM_RBUTTONUP)
  851.    {
  852.       // are we in animation OR are mouse commands NOT allowed ?
  853.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  854.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  855.  
  856.       rbutton_pushed = false; // remember button is released
  857.       the_scene.update = true; // update the 3D scene
  858.  
  859.       // call the default window message processing function to keep things going
  860.       return (DefWindowProc (hWnd, message, wParam, lParam));
  861.    }
  862.  
  863.    ////////////////////////////////////////////////////////////////////////////////////////////////
  864.    // left mouse button DOUBLE-click
  865.    else if (message == WM_LBUTTONDBLCLK)
  866.    {
  867.       // are we in animation OR are mouse commands NOT allowed ?
  868.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  869.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  870.  
  871.       // get mouse coordinates
  872.       gui_x = GET_X_LPARAM (lParam);
  873.       gui_y = GET_Y_LPARAM (lParam);
  874.  
  875.       // are we in game and did we click the move comments area ?
  876.       if ((the_board.game_state >= STATE_PLAYING) && (the_board.viewed_move > 0) && Render_IsMouseInBox (gui_x, gui_y, 10.0f, 0.0f, 80.0f, 10.0f))
  877.       {
  878.          DialogBox_Comment (); // fire up the comment dialog box
  879.          return (DefWindowProc (hWnd, message, wParam, lParam)); // call the default window message processing function to keep things going
  880.       }
  881.  
  882.       // call the default window message processing function to keep things going
  883.       return (DefWindowProc (hWnd, message, wParam, lParam));
  884.    }
  885.  
  886.    ////////////////////////////////////////////////////////////////////////////////////////////////
  887.    // mouse move (while not in animation)
  888.    else if (message == WM_MOUSEMOVE)
  889.    {
  890.       // are we in animation OR are mouse commands NOT allowed ?
  891.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  892.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  893.  
  894.       // get mouse coordinates
  895.       gui_x = GET_X_LPARAM (lParam);
  896.       gui_y = GET_Y_LPARAM (lParam);
  897.  
  898.       // handle button update status
  899.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.llarrow, gui_x, gui_y);
  900.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.larrow, gui_x, gui_y);
  901.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.rarrow, gui_x, gui_y);
  902.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.rrarrow, gui_x, gui_y);
  903.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.newgamebutton, gui_x, gui_y);
  904.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.opengamebutton, gui_x, gui_y);
  905.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.chatbutton, gui_x, gui_y);
  906.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.gamesbutton, gui_x, gui_y);
  907.       the_scene.update |= Button_UpdateHoverState (&the_scene.gui.peoplebutton, gui_x, gui_y);
  908.  
  909.       // are we in board closeup mode OR are we online AND do we NOT have the right to select anything ?
  910.       if (((current_distance == CLOSEUP_VIEW_DISTANCE) && (current_pitch == CLOSEUP_VIEW_PITCH))
  911.           || ((remote_player != NULL) && !remote_player->is_in_game))
  912.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default window message processing function to keep things going
  913.  
  914.       // is the parts selection line displayed AND is the mouse anywhere in it ?
  915.       if (the_scene.gui.is_partspick_displayed && Render_IsMouseInBox (gui_x, gui_y, 0.0f, 0.0f, 100.0f, 11.0f))
  916.       {
  917.          // for each selectable part...
  918.          for (part_index = 0; part_index < 13; part_index++)
  919.          {
  920.             // is the mouse hovering it ?
  921.             if (Render_IsMouseInBox (gui_x, gui_y, part_index * (100.0f / 13.0f), 0, 100.0f / 13.0f, 11.0f))
  922.             {
  923.                // was it NOT hovered before ?
  924.                if (the_scene.gui.partspick_hoveredpart != selectable_parts[part_index])
  925.                {
  926.                   the_scene.gui.partspick_hoveredpart = selectable_parts[part_index]; // mark it as hovered
  927.                   the_scene.update = true; // update the scene
  928.                }
  929.             }
  930.  
  931.             // else was it hovered before ?
  932.             else if (the_scene.gui.partspick_hoveredpart == selectable_parts[part_index])
  933.             {
  934.                the_scene.gui.partspick_hoveredpart = 0; // clear the hovered part
  935.                the_scene.update = true; // update the scene
  936.             }
  937.          }
  938.       }
  939.       else
  940.          the_scene.gui.partspick_hoveredpart = 0; // clear the hovered part
  941.  
  942.       // if right button was clicked, compute new pitch and yaw
  943.       if (rbutton_pushed)
  944.       {
  945.          // cycle through both players and change their view angles EXCEPT the opponent if he's human
  946.          for (viewer_index = 0; viewer_index < 2; viewer_index++)
  947.             if ((the_board.players[viewer_index].type == PLAYER_COMPUTER)
  948.                 || (the_board.players[viewer_index].type == PLAYER_INTERNET)
  949.                 || (viewer_index == current_viewer))
  950.             {
  951.                the_board.players[viewer_index].view_pitch += (gui_y - prevgui_y) * 0.3f;
  952.                if (the_board.players[viewer_index].view_pitch < MIN_VIEW_PITCH)
  953.                   the_board.players[viewer_index].view_pitch = MIN_VIEW_PITCH; // wrap angles around so that they
  954.                if (the_board.players[viewer_index].view_pitch > MAX_VIEW_PITCH)
  955.                   the_board.players[viewer_index].view_pitch = MAX_VIEW_PITCH; // stay in the min-max pitch bounds
  956.  
  957.                the_board.players[viewer_index].view_yaw += (gui_x - prevgui_x) * 0.3f;
  958.                the_board.players[viewer_index].view_yaw = WrapAngle (the_board.players[viewer_index].view_yaw);
  959.  
  960.                // save these as the new custom angles
  961.                the_board.players[viewer_index].custom_pitch = the_board.players[viewer_index].view_pitch;
  962.                the_board.players[viewer_index].custom_yaw = the_board.players[viewer_index].view_yaw;
  963.  
  964.                // when moving the table around, jump to ideal angles immediately
  965.                current_pitch = the_board.players[viewer_index].view_pitch;
  966.                current_yaw = the_board.players[viewer_index].view_yaw;
  967.             }
  968.  
  969.          the_scene.update = true; // button was clicked, update the 3D scene
  970.       }
  971.  
  972.       // else it's just the mouse wandering around ; have we the right to select something ?
  973.       else if ((the_board.viewed_move == the_board.move_count - 1) && (current_player->type == PLAYER_HUMAN) && (highlight_endtime < current_time))
  974.       {
  975.          // save the old positions
  976.          prev_hovered_position[0] = the_board.hovered_position[0];
  977.          prev_hovered_position[1] = the_board.hovered_position[1];
  978.  
  979.          // figure out the coordinates on table
  980.          Render_MouseToFloor (gui_x, gui_y, &board_x, &board_y);
  981.  
  982.          // translate them to board coordinates
  983.          the_board.hovered_position[0] = (int) floor ((20.0f - board_y) / 5.0f);
  984.          the_board.hovered_position[1] = 7 - (int) floor ((20.0f - board_x) / 5.0f);
  985.  
  986.          // do they differ from last time ?
  987.          if ((the_board.hovered_position[0] != prev_hovered_position[0]) || (the_board.hovered_position[1] != prev_hovered_position[1]))
  988.             the_scene.update = true; // if so, update scene
  989.       }
  990.  
  991.       // has the user the right to leave a comment AND is there no comment yet ?
  992.       if ((the_board.game_state >= STATE_PLAYING) && (the_board.viewed_move > 0)
  993.           && ((the_board.moves[the_board.viewed_move].comment == NULL) || (the_board.moves[the_board.viewed_move].comment[0] == 0)))
  994.       {
  995.          // is the mouse above the comments zone ? if so, display a dimmed hint text
  996.          if (Render_IsMouseInBox (gui_x, gui_y, 30.0f, 0.0f, 40.0f, 10.0f))
  997.          {
  998.             if (!the_scene.gui.comment_text.is_displayed)
  999.             {
  1000.                Scene_UpdateText (&the_scene.gui.comment_text, RGBA_TO_RGBACOLOR (255, 255, 255, 127), DURATION_INFINITE, false, L"\n\n%s", LOCALIZE (L"DoubleClickToEnterComment"));
  1001.                the_scene.update = true; // and update the scene
  1002.             }
  1003.          }
  1004.          else
  1005.          {
  1006.             if (the_scene.gui.comment_text.is_displayed)
  1007.             {
  1008.                the_scene.gui.comment_text.is_displayed = false; // if not, erase the hint text
  1009.                the_scene.update = true; // and update the scene
  1010.             }
  1011.          }
  1012.       }
  1013.  
  1014.       // remember these coordinates for next time
  1015.       prevgui_x = gui_x;
  1016.       prevgui_y = gui_y;
  1017.  
  1018.       // call the default window message processing function to keep things going
  1019.       return (DefWindowProc (hWnd, message, wParam, lParam));
  1020.    }
  1021.  
  1022.    ////////////////////////////////////////////////////////////////////////////////////////////////
  1023.    // mouse scroll
  1024.    else if (message == WM_MOUSEWHEEL)
  1025.    {
  1026.       // are we in animation OR are mouse commands NOT allowed ?
  1027.       if ((animation_endtime + 1.0f >= current_time) || (command_ignoretime >= current_time))
  1028.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default message proc to keep things going
  1029.  
  1030.       // are we in board closeup mode OR are we online AND do we NOT have the right to select anything ?
  1031.       if (((current_distance == CLOSEUP_VIEW_DISTANCE) && (current_pitch == CLOSEUP_VIEW_PITCH))
  1032.           || ((remote_player != NULL) && !remote_player->is_in_game))
  1033.          return (DefWindowProc (hWnd, message, wParam, lParam)); // if so, call the default window message processing function to keep things going
  1034.  
  1035.       // get mouse coordinates (strangely enough, the WM_MOUSEWHEEL message outputs ABSOLUTE coordinates, and we need to convert them...)
  1036.       point.x = GET_X_LPARAM (lParam);
  1037.       point.y = GET_Y_LPARAM (lParam);
  1038.       ScreenToClient (hWnd, &point);
  1039.       gui_x = point.x;
  1040.       gui_y = point.y;
  1041.  
  1042.       // is a game history displayed and are we (roughly) hovering this area ?
  1043.       if (the_scene.gui.history_text.is_displayed && Render_IsMouseInBox (gui_x, gui_y, 90.0f, 10.0f, 10.0f, 40.0f))
  1044.       {
  1045.          // we want to scroll through the game history rather than zooming in/out. Scroll up or scroll down ?
  1046.          if ((GET_WHEEL_DELTA_WPARAM (wParam) > 0) && (the_scene.gui.larrow.state != 0))
  1047.             SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_PREVIOUSMOVE, NULL); // send a "previous move" event
  1048.          else if ((GET_WHEEL_DELTA_WPARAM (wParam) < 0) && (the_scene.gui.rarrow.state != 0))
  1049.             SendMessage (hWnd, WM_COMMAND, MENUID_CHESSBOARD_NEXTMOVE, NULL); // send a "next move" event
  1050.  
  1051.          return (DefWindowProc (hWnd, message, wParam, lParam)); // and call the default window message processing function to keep things going
  1052.       }
  1053.  
  1054.       // we want to zoom in/out of the scene. Scroll up or scroll down ?
  1055.       if (GET_WHEEL_DELTA_WPARAM (wParam) > 0)
  1056.          the_board.players[current_viewer].view_distance = max (MIN_VIEW_DISTANCE, the_board.players[current_viewer].view_distance - 2.0f);
  1057.       else if (GET_WHEEL_DELTA_WPARAM (wParam) < 0)
  1058.          the_board.players[current_viewer].view_distance = min (MAX_VIEW_DISTANCE, the_board.players[current_viewer].view_distance + 2.0f);
  1059.  
  1060.       // save this as the new custom distance
  1061.       the_board.players[current_viewer].custom_distance = the_board.players[current_viewer].view_distance;
  1062.  
  1063.       // when moving the table around, jump to ideal angles immediately
  1064.       current_distance = the_board.players[current_viewer].view_distance;
  1065.  
  1066.       the_scene.update = true; // update the 3D scene
  1067.  
  1068.       // call the default window message processing function to keep things going
  1069.       return (DefWindowProc (hWnd, message, wParam, lParam));
  1070.    }
  1071.  
  1072.    ////////////////////////////////////////////////////////////////////////////////////////////////
  1073.    // keyboard release
  1074.    else if (message == WM_CHAR)
  1075.    {
  1076.       // are we in position setup mode ?
  1077.       if (the_board.game_state == STATE_SETUPPOSITION)
  1078.       {
  1079.          // is it the enter key ? if so, exit setup mode and start playing
  1080.          if (wParam == L'\r')
  1081.          {
  1082.             current_move = &the_board.moves[the_board.viewed_move]; // quick access to current move
  1083.  
  1084.             // make sure there is at least one king of each color (i.e. two kings)
  1085.             part_index = 0;
  1086.             for (line = 0; line < 8; line++)
  1087.                for (column = 0; column < 8; column++)
  1088.                   if (current_move->slots[line][column].part == PART_KING)
  1089.                      part_index++; // there's one king more on this board
  1090.             if (part_index != 2)
  1091.                return (DefWindowProc (hWnd, message, wParam, lParam)); // when at least one king is missing, we just can't leave edition mode
  1092.  
  1093.             // (in)validate the castling positions
  1094.             if ((current_move->slots[0][0].color == COLOR_WHITE) && (current_move->slots[0][0].part == PART_ROOK)
  1095.                 && (current_move->slots[0][4].color == COLOR_WHITE) && (current_move->slots[0][4].part == PART_KING))
  1096.                current_move->sides[COLOR_WHITE].longcastle_allowed = true; // white castling queenside allowed
  1097.             else
  1098.                current_move->sides[COLOR_WHITE].longcastle_allowed = false; // white castling queenside no longer possible
  1099.             if ((current_move->slots[0][7].color == COLOR_WHITE) && (current_move->slots[0][7].part == PART_ROOK)
  1100.                 && (current_move->slots[0][4].color == COLOR_WHITE) && (current_move->slots[0][4].part == PART_KING))
  1101.                current_move->sides[COLOR_WHITE].shortcastle_allowed = true; // white castling kingside allowed
  1102.             else
  1103.                current_move->sides[COLOR_WHITE].shortcastle_allowed = false; // white castling kingside no longer possible
  1104.             if ((current_move->slots[7][0].color == COLOR_BLACK) && (current_move->slots[7][0].part == PART_ROOK)
  1105.                 && (current_move->slots[7][4].color == COLOR_BLACK) && (current_move->slots[7][4].part == PART_KING))
  1106.                current_move->sides[COLOR_BLACK].longcastle_allowed = true; // white castling queenside allowed
  1107.             else
  1108.                current_move->sides[COLOR_BLACK].longcastle_allowed = false; // white castling queenside no longer possible
  1109.             if ((current_move->slots[7][7].color == COLOR_BLACK) && (current_move->slots[7][7].part == PART_ROOK)
  1110.                 && (current_move->slots[7][4].color == COLOR_BLACK) && (current_move->slots[7][4].part == PART_KING))
  1111.                current_move->sides[COLOR_BLACK].shortcastle_allowed = true; // white castling kingside allowed
  1112.             else
  1113.                current_move->sides[COLOR_BLACK].shortcastle_allowed = false; // white castling kingside no longer possible
  1114.  
  1115.             current_move->color = COLOR_BLACK; // so that game starts with white
  1116.  
  1117.             // validate this board in Forsyth-Edwards Notation and reset it
  1118.             Move_DescribeInFEN (current_move);
  1119.             wcscpy_s (fen_string, WCHAR_SIZEOF (fen_string), current_move->fen_string); // have a copy of fen string
  1120.             Board_Reset (&the_board, fen_string);
  1121.  
  1122.             the_board.game_state = STATE_PLAYING; // start the game now
  1123.             the_board.reevaluate = true; // evaluate board again
  1124.  
  1125.             the_scene.gui.central_text.disappear_time = current_time + 1.0f; // fade out help text now (FIXME: ugly)
  1126.             the_scene.update = true; // update the 3D scene
  1127.  
  1128.             // call the default window message processing function to keep things going
  1129.             return (DefWindowProc (hWnd, message, wParam, lParam));
  1130.          }
  1131.       }
  1132.  
  1133.       // else are we in internet mode ?
  1134.       else if (remote_player != NULL)
  1135.       {
  1136.          entered_ccreply = &the_scene.gui.entered_ccreply; // quick access to entered ccreply
  1137.          local_player = Player_FindByType (PLAYER_HUMAN); // quick access to local player
  1138.          if (local_player == NULL)
  1139.             return (DefWindowProc (hWnd, message, wParam, lParam)); // theoretically impossible condition, but better be sure
  1140.          if (selected_chatterchannel == NULL)
  1141.             return (DefWindowProc (hWnd, message, wParam, lParam)); // theoretically impossible condition, but better be sure
  1142.  
  1143.          // are we NOT entering text yet ?
  1144.          if (!the_scene.gui.is_entering_text)
  1145.          {
  1146.             // is it the space bar ?
  1147.             if (wParam == L' ')
  1148.             {
  1149.                the_scene.gui.is_entering_text = true; // remember we are entering text
  1150.  
  1151.                // reset the entered text buffer
  1152.                entered_ccreply->text = (wchar_t *) SAFE_malloc (1, sizeof (wchar_t), false);
  1153.                entered_ccreply->text[0] = 0; // only the null terminator will fit
  1154.                entered_ccreply->text_length = 0; // and set its length to zero
  1155.             }
  1156.          }
  1157.  
  1158.          // else we are currently in text entering mode
  1159.          else
  1160.          {
  1161.             // is the typed character a printable character ?
  1162.             if (iswprint (wParam))
  1163.             {
  1164.                // if so, reallocate space in the typed buffer (include null terminator)
  1165.                entered_ccreply->text = (wchar_t *) SAFE_realloc (entered_ccreply->text, entered_ccreply->text_length + 1, entered_ccreply->text_length + 1 + 1, sizeof (wchar_t), false);
  1166.                swprintf_s (&entered_ccreply->text[entered_ccreply->text_length], 1 + 1, L"%c", wParam); // append character
  1167.                entered_ccreply->text_length++; // buffer holds now one character more
  1168.             }
  1169.  
  1170.             // else is the typed character a backspace AND is there text to erase ?
  1171.             else if ((wParam == 0x08) && (entered_ccreply->text_length > 0))
  1172.             {
  1173.                // if so, reallocate space in the typed buffer (include null terminator)
  1174.                entered_ccreply->text = (wchar_t *) SAFE_realloc (entered_ccreply->text, entered_ccreply->text_length + 1, entered_ccreply->text_length + 1 - 1, sizeof (wchar_t), false);
  1175.                entered_ccreply->text[entered_ccreply->text_length - 1] = 0; // backspace, delete one character
  1176.                entered_ccreply->text_length--; // buffer holds now one character less
  1177.             }
  1178.  
  1179.             // else is the typed character the escape key ?
  1180.             else if (wParam == 0x1b)
  1181.             {
  1182.                SAFE_free ((void **) &entered_ccreply->text); // reset the entered text buffer
  1183.                entered_ccreply->text_length = 0; // and set its length to zero
  1184.                the_scene.gui.is_entering_text = false; // and exit from the text entering mode
  1185.             }
  1186.  
  1187.             // else is the typed character the enter key ?
  1188.             else if (wParam == 0x0d)
  1189.                the_scene.gui.is_entering_text = false; // enter, exit from the text entering mode (this will validate our reply)
  1190.  
  1191.             // else it's an illegal character
  1192.             else
  1193.                Audio_PlaySoundAtCenter (SOUNDTYPE_ILLEGALMOVE); // illegal character, beep the illegal move sound at the center of the board
  1194.          }
  1195.  
  1196.          the_scene.update = true; // update the 3D scene
  1197.  
  1198.          // call the default window message processing function to keep things going
  1199.          return (DefWindowProc (hWnd, message, wParam, lParam));
  1200.       }
  1201.    }
  1202.  
  1203.    ////////////////////////////////////////////////////////////////////////////////////////////////
  1204.    // mouse move outside the window
  1205.    else if (message == WM_NCMOUSEMOVE)
  1206.    {
  1207.       rbutton_pushed = false; // remember right button is released
  1208.  
  1209.       // call the default window message processing function to keep things going
  1210.       return (DefWindowProc (hWnd, message, wParam, lParam));
  1211.    }
  1212.  
  1213.    ////////////////////////////////////////////////////////////////////////////////////////////////
  1214.    // window repaint
  1215.    else if (message == WM_PAINT)
  1216.    {
  1217.       the_scene.update = true; // update the 3D scene
  1218.  
  1219.       // call the default window message processing function to keep things going
  1220.       return (DefWindowProc (hWnd, message, wParam, lParam));
  1221.    }
  1222.  
  1223.    // call the default window message processing function to keep things going
  1224.    return (DefWindowProc (hWnd, message, wParam, lParam));
  1225. }
  1226.  
  1227.  
  1228. static bool Button_IsHovered (guibutton_t *button, int gui_x, int gui_y)
  1229. {
  1230.    // handy wrapper that returns whether a particular GUI button is hovered when the mouse is at the given coordinates
  1231.  
  1232.    return (Render_IsMouseInBox (gui_x, gui_y, button->left, button->top, button->width, button->height));
  1233. }
  1234.  
  1235.  
  1236. static bool Button_UpdateHoverState (guibutton_t *button, int gui_x, int gui_y)
  1237. {
  1238.    // this function updates the hover state of a GUI button, and returns TRUE if the
  1239.    // scene needs to be updated, FALSE otherwise.
  1240.  
  1241.    // is the button displayed ?
  1242.    if (button->state != 0)
  1243.    {
  1244.       // is the mouse hovering it ?
  1245.       if (Render_IsMouseInBox (gui_x, gui_y, button->left, button->top, button->width, button->height))
  1246.       {
  1247.          // was it NOT hovered before ?
  1248.          if (button->state != 2)
  1249.          {
  1250.             button->state = 2; // mark it as hovered
  1251.             return (true); // return TRUE so as to update the scene
  1252.          }
  1253.       }
  1254.  
  1255.       // else was it hovered before ?
  1256.       else if (button->state == 2)
  1257.       {
  1258.          button->state = 1; // else mark it as not hovered
  1259.          return (true); // return TRUE so as to update the scene
  1260.       }
  1261.    }
  1262.  
  1263.    return (false); // no need to update the scene
  1264. }
  1265.