Subversion Repositories Games.Chess Giants

Rev

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