예제 #1
0
파일: main.cpp 프로젝트: frechilla/blockem
/// @brief what can I say? This is the main function!
int main(int argc, char **argv)
{
    // fancy error messages creation with snprintf
    char errorStringBuffer[ERROR_STRING_BUFFER_SIZE];

    // Init type system as soon as possible
    g_type_init ();

    // SINGLETON creation is not thread safe. Ensure they are instantiated
    // before the app creates extra threads
    // BlockemConfig singleton contains general purpose configuration data that
    // will be loaded from the config xml file
    // It MUST be instantiated before i18n in windows because it loads the
    // preferred language
    BlockemConfig::Instance();

    // enable internationalisation support. It MUST be called before
    // ProcessCommandLine
    I18nInit();

    // parse command line options. options will be saved in a bunch of static
    // global variables (scope to this file only) defined at the beginning of
    // this file (they all start with 'g_')
    ProcessCommandLine(argc, argv);

    if (g_version)
    {
        // "version" option takes priority. If it set nothing else will be done
        std::cout << "Blockem, version " << VERSION << " (git commit " << GIT_COMMIT << ")" << std::endl;
        std::cout << "  Compiled " << COMPILETIME << std::endl << std::endl;
        std::cout << "Copyright (C) 2009-2013 Faustino Frechilla" << std::endl;
        std::cout << "Blockem is open source software, see http://blockem.sourceforge.net/"
                  << std::endl << std::endl;
    }
    else if (g_mode == 0)
    {
        //////////////////
        // GUI MODE

        // gtkmm can do strange stuff if its internals are not initialised early
        // enough
        Gtk::Main::init_gtkmm_internals();

        // initialise singletons before extra threads are created.
        // Singleton creation is not thread safe
        // Game1v1Config data. Used to store GUI related configuration for 1vs1 games
        Game1v1Config::Instance();
        Game4PlayersConfig::Instance();
        GameTotalAllocationConfig::Instance();
        GameChallengeConfig::Instance();

        // g_thread_supported returns TRUE if the thread system is initialised,
        // and FALSE if it is not. Initiliase gthreads only if they haven't been
        // initialised already. Since glib 2.24.0 (from its changelog):
        // "the requirements for g_thread_init() have been relaxed slightly, it
        // can be called multiple times, and does not have to be the first call.
        // GObject now links to GThread and threads are enabled automatically
        // when g_type_init() is called"
        // for backwards compatibility call to g_thread_init only if it hasn't
        // been called already
        if(!g_thread_supported())
        {
            // Initialise gthreads even before gtk_init
            g_thread_init(NULL);
        }

        // This call is needed only if extra threads (not the main thread)
        // updates directly the GUI widgets using gdk_threads_enter
        // and gdk_threads_leave
        // It's not really needed since there's only 1 extra thread
        // (apart from the main one) and it doesn't update the GUI
        // straight away. It uses signals for that matter (see comment
        // in MainWindow::WorkerThread_computingFinished)
        //gdk_threads_init();

        // this should be called before starting manipulating GUI stuff
        Gtk::Main kit(argc, argv);

        // create a new gtk builder. add_from_string will load the definitions
        Glib::RefPtr<Gtk::Builder> gtkBuilder = Gtk::Builder::create();
        gtkBuilder->set_translation_domain(GETTEXT_PACKAGE);

        try
        {
            // Load the GUI from the xml code embedded in the executable file
            if (!gtkBuilder->add_from_string(
            // see src/gui_glade.h for more information
                    reinterpret_cast<const char *>(__BIN_GUI_GLADE_START__),
                    reinterpret_cast<gsize>(__BIN_GUI_GLADE_SIZE__)))
            {
                FatalError(
                    argv[0],
                    // i18n TRANSLATORS: Please leave the word Gtkbuilder as it is since it's the name
                    // i18n of the component that failed
                    // i18n Thank you for contributing to this project
                    _("Could not load Gtkbuilder definitions"),
                    GUI_EXCEPTION_ERR);
            }
        }
#ifdef GLIBMM_EXCEPTIONS_ENABLED
        catch(const Glib::MarkupError& ex)
        {
            FatalError(argv[0], ex.what().c_str(), GUI_EXCEPTION_ERR);
        }
        catch(const Gtk::BuilderError& ex)
        {
            FatalError(argv[0], ex.what().c_str(), GUI_EXCEPTION_ERR);
        }
#endif // GLIBMM_EXCEPTIONS_ENABLED
        catch (const GUIException& ex)
        {
            FatalError(argv[0], ex.what(), GUI_EXCEPTION_ERR);
        }

        MainWindow *pMainWindow = NULL;
        try
        {
            // first of all retrieve the Gtk::window object
            gtkBuilder->get_widget_derived(GUI_MAIN_WINDOW_NAME, pMainWindow);
            if (pMainWindow == NULL)
            {
                throw new GUIException(
                            e_GUIException_GTKBuilderErr,
                            __FILE__,
                            __LINE__);
            }

            // if gdk_threads_enter and gdk_threads_leave were to be used
            // the Gtk::Main::run loop should be surrounded by
            // gdk_threads_enter and gdk_threads_leave
            // http://tadeboro.blogspot.com/2009/06/multi-threaded-gtk-applications.html
            kit.run(*pMainWindow);
        }
        catch (const GUIException& ex)
        {
            FatalError(argv[0], ex.what(), GUI_EXCEPTION_ERR);
        }
        catch (const std::exception& ex)
        {
            FatalError(argv[0], ex.what(), GUI_EXCEPTION_ERR);
        }

        if (pMainWindow)
        {
            // valgrind won't be happy if all this memory is not freed
            delete (pMainWindow);
        }

        // GUI MODE
        //////////////////
    }
    else // (g_mode != 0)
    {
        //////////////////
        // CONSOLE mode

        // do whatever we are requested to do per file passed as parameter to the
        // application. App will finish without running any GUI code

        if (g_mode == 1)
        {
            // 1 player total allocation (--mode=1)

            if ( (g_rows == GOPTION_INT_NOT_SET) ||
                 (g_columns == GOPTION_INT_NOT_SET) )
            {
                FatalError(
                    argv[0],
                    _("Number of rows and number of columns must be specified in mode '1'"),
                    TOTAL_ALLOC_BAD_OPTIONS_ERR);
            }

            if ( (g_rows <= 0) || (g_columns <= 0) )
            {
                FatalError(
                    argv[0],
                    _("Number of rows and number of columns must be a greater than 1"),
                    TOTAL_ALLOC_BAD_OPTIONS_ERR);
            }

            if ( (g_startingRow != g_startingColumn) &&
                 ( (g_startingRow == GOPTION_INT_NOT_SET) ||
                   (g_startingColumn == GOPTION_INT_NOT_SET) ) )
            {
                // only one of the 2 options starting row and starting columns was set
                FatalError(
                    argv[0],
                    _("Both starting row and column must be set to configure a starting coordinate"),
                    TOTAL_ALLOC_BAD_OPTIONS_ERR);
            }

            Coordinate startingCoord;
            if ( (g_startingRow != GOPTION_INT_NOT_SET) &&
                 (g_startingColumn != GOPTION_INT_NOT_SET) )
            {
                if ( (g_startingRow >= g_rows) || (g_startingRow < 0) ||
                     (g_startingColumn >= g_columns) || (g_startingColumn < 0) )
                {
                    FatalError(
                        argv[0],
                        _("Application cannot start outside the board boundaries"),
                        TOTAL_ALLOC_BAD_OPTIONS_ERR);
                }

                // overwrite uninitialised starting coordinate with the
                // user-selected one
                startingCoord.m_row = g_startingRow;
                startingCoord.m_col = g_startingColumn;
            }

            GameTotalAllocation theGame(g_rows, g_columns, startingCoord);
            if (theGame.Solve())
            {
                std::cout << std::endl;
                // i18n TRANSLATORS: This string is to be printed when the applications finds
                // i18n a solution to a one-player total-allocation game
                // i18n Thank you for contributing to this project
                std::cout << _("SOLVED!") << std::endl;

                // print solved board on the screen
                theGame.GetBoard().PrintBoard(std::cout);
            }
            else
            {
                std::cout << std::endl;
                if (startingCoord.Initialised())
                {
                    // i18n TRANSLATORS: This string is to be printed when the applications cannot find
                    // i18n a solution to a one-player total-allocation game. The integers printed on the screen
                    // i18n are (respectively) number of rows, number of columns, starting row and starting column
                    // i18n A typical string to be printed:
                    // i18n "Could not allocate all the pieces in this 8x8 board, starting from row 0, column 2"
                    // i18n Thank you for contributing to this project
                    printf(_("Could not allocate all the pieces in this %dx%d board, starting from row %d, columnd %d\n"),
                        g_rows,
                        g_columns,
                        g_startingRow,
                        g_startingColumn);
                }
                else
                {
                    // i18n TRANSLATORS: This string is to be printed when the applications cannot find
                    // i18n a solution to a one-player total-allocation game starting from anywhere in the board
                    // i18n Thank you for contributing to this project
                    printf(_("Could not allocate all the pieces in this %dx%d board starting from anywhere in the board\n"),
                        g_rows,
                        g_columns);
                }
            }
        }
        else if (g_mode == 2)
        {
            // 1vs1 Game (--mode=2)

            if ( (g_blockemfilePath == NULL) || (g_blockemfilePath[0] == NULL) )
            {
                FatalError(
                    argv[0],
                    _("At least one file with a 1vs1Game saved must be specified"),
                    GAME1V1_BAD_OPTIONS_ERR);
            }

            if ( (g_heuristic <  Heuristic::e_heuristicStartCount) ||
                 (g_heuristic >= Heuristic::e_heuristicCount) )
            {
                snprintf(errorStringBuffer,
                         ERROR_STRING_BUFFER_SIZE,
                         // i18n TRANSLATORS: Please, leave that %d as it is. It will be replaced
                         // i18n by the type of heuristic used by the user
                         // i18n Thank you for contributing to this project
                         _("Invalid heuristic type (%d)"),
                         g_heuristic);

                FatalError(
                    argv[0],
                    errorStringBuffer,
                    GAME1V1_BAD_OPTIONS_ERR);
            }

            // this little piece of magic uses the g_heuristic index to pick the expected evaluation
            // function. Have a look at heuristic.h
            Heuristic::EvalFunction_t heuristic = Heuristic::m_heuristicData[g_heuristic].m_evalFunction;
            // i18n TRANSLATORS: This string shows the chosen heuristic's name to the user
            // i18n the 1st %s will be replaced by the heuristic's name, and %d by its index
            // i18n Thank you for contributing to this project
            printf(_("Evaluation function set to %s (%d)\n"),
                   _(Heuristic::m_heuristicData[g_heuristic].m_name),
                   g_heuristic);

            if (g_depth <= 0)
            {
                FatalError(
                    argv[0],
                    _("Depth in 1vs1Game mode must be set to a positive value"),
                    GAME1V1_BAD_OPTIONS_ERR);
            }
            else if ( (g_depth & 0x01) == 0)
            {
                std::cerr << argv[0]
                          << ": "
                          << _("Warning: For better results you might want to set the depth to an odd number")
                          << std::endl;
            }

            // go through all the filenames array. Each file will be loaded into a gam1v1 and the next
            // move will be calculated and printed in the screen
            Game1v1 theGame;
            for (int32_t fileIndex = 0; g_blockemfilePath[fileIndex] != NULL; fileIndex++)
            {
                if (!g_file_test(g_blockemfilePath[fileIndex], G_FILE_TEST_IS_REGULAR))
                {
                    fprintf(stderr,
                            // i18n TRANSLATORS: This string is shown when a file specified by the user
                            // i18n cannot be loaded into the application. The 1st %s will be replaced
                            // i18n by the name of the binary (normally "./blockem"), the 2nd one by
                            // i18n the file that couldn't be accessed. Bear in mind the '\n' character should be there
                            // i18n in the translated version of the string too
                            // i18n Thank you for contributing to this project
                            _("%s: Error: '%s' doesn't exist. Trying next file...\n"),
                            argv[0],
                            g_blockemfilePath[fileIndex]);

                    continue;
                }

                std::ifstream cin;
                cin.open(g_blockemfilePath[fileIndex]);
                if(!cin)
                {
                    fprintf(stderr,
                            // i18n TRANSLATORS: This string is shown when a file specified by the user
                            // i18n could not be opened to be read. The 1st %s will be replaced
                            // i18n by the name of the binary (normally "./blockem"), the 2nd one by
                            // i18n the file that couldn't be opened. Bear in mind the '\n' character should be there
                            // i18n in the translated version of the string too
                            // i18n Thank you for contributing to this project
                            _("%s: Error: '%s' could not be opened. Trying next file...\n"),
                            argv[0],
                            g_blockemfilePath[fileIndex]);

                    continue;
                }

                theGame.Reset();
                if (theGame.LoadGame(cin) == false)
                {
                    fprintf(stderr,
                            // i18n TRANSLATORS: This string is shown when a file specified by the user
                            // i18n does not contain a valid 1vs1 game. The 1st %s will be replaced
                            // i18n by the name of the binary (normally "./blockem"), the 2nd one by
                            // i18n the file that couldn't be opened. Bear in mind the '\n' character should be there
                            // i18n in the translated version of the string too
                            // i18n Thank you for contributing to this project
                            _("%s: Error: '%s' does not contain a valid 1vs1Game. Trying next file...\n"),
                            argv[0],
                            g_blockemfilePath[fileIndex]);

                    cin.close();
                    continue;
                }
                cin.close();

                // i18n TRANSLATORS: '%s' will be replaced here by the path to the file
                // i18n successfully loaded. Bear in mind the '\n' character should be there
                // i18n in the translated version of the string too
                // i18n Thank you for contributing to this project
                printf (_("Game succesfully loaded from '%s'\n"),
                        g_blockemfilePath[fileIndex]);

                // print current game on the screen
                theGame.SaveGame(std::cout);

                //std::cout << std::endl << std::endl;
                //theGame.GetMe().PrintNucleationPoints(std::cout);

                //std::cout << std::endl << std::endl;
                //theGame.GetOpponent().PrintNucleationPoints(std::cout);

                // these 2 variables will save the result calculated by minimax
                Piece resultPiece(e_noPiece);
                Coordinate resultCoord;

                // dummy volatile because no one will change it
                volatile sig_atomic_t dummyAtomic = 0;

                int32_t minimaxWinner =
                    theGame.MinMax(
                            heuristic,
                            g_depth,
                            Game1v1::e_Game1v1Player1,
                            resultPiece,
                            resultCoord,
                            dummyAtomic);

                // i18n TRANSLATORS: '%d' will be replaced here by the value of the winning
                // i18n move calculated by the minimax engine. Bear in mind the '\n' character
                // i18n should be there in the translated version of the string too
                // i18n Thank you for contributing to this project
                printf(_("Winning evaluation function value: %d\n"), minimaxWinner);

                if (resultPiece.GetType() == e_noPiece)
                {
                    std::cout << std::endl;
                    // i18n TRANSLATORS: This string will be printed on the screen when the minimax
                    // i18n engine cannot put down a piece. It is left up to the translator to decide
                    // i18n to decide the formatting of the message, though this english version
                    // i18n could be used as the template
                    // i18n Thank you for contributing to this project
                    std::cout << _(" ===================================================\n"\
                                   " === END OF THE GAME. NO PIECE COULD BE PUT DOWN ===\n"\
                                   " ===================================================\n");
                }
                else
                {
                    // out down the result piece on the board
                    theGame.PutDownPiece(
                            resultPiece,
                            resultCoord,
                            Game1v1::e_Game1v1Player1);

                    // print the final game on the screen
                    theGame.SaveGame(std::cout);
                }

                //theGame.GetMe().PrintNucleationPoints(std::cout);
                //theGame.GetOpponent().PrintNucleationPoints(std::cout);

            } // for (int32_t fileIndex = 0; g_blockemfilePath[fileIndex] != NULL; fileIndex++)
        }
        else // (g_mode != 1 && g_mode != 2)
        {
            snprintf(errorStringBuffer,
                     ERROR_STRING_BUFFER_SIZE,
                     // i18n TRANSLATORS: Please, leave that %d as it is. It will be replaced
                     // i18n by the mode picked by the user
                     // i18n Thank you for contributing to this project
                     _("Invalid --mode option (%d)"),
                     g_mode);

            FatalError(
                argv[0],
                errorStringBuffer,
                BAD_MODE_OPTION_ERR);
        }

        // CONSOLE mode
        //////////////////
    }

    return 0;
}