/********************************************************************** * * * FreeDoko a Doppelkopf-Game * * Copyright (C) 2001-2007 by Diether Knof and Borg Enders * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You can find this license in the file 'gpl.txt'. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Contact: * Diether Knof dknof@gmx.de * Borg Enders borg@borgsoft.de * ********************************************************************/ #include "constants.h" #include "gametree.h" #include "trickweighting.h" #include "VirtualGamesInterface.h" #include "ai.h" #include "cards_information.h" #include "team_information.h" #include "../../card/trick.h" #include "../../game/exception.h" #include "../../game/game_summary.h" #include "../../ui/ui.h" #include "../../utils/string.h" #ifdef USE_THREADS #include #endif static unsigned counter = 0; // whether to save the runtime //#define SAVE_RUNTIME #ifdef RELEASE #undef SAVE_RUNTIME #endif // the multiplier for a game point #define GAME_POINTS_MULTIPLIER 10000 #define CHECK_MODUS(modus) \ if ( ((modus >= 100 * GAME_POINTS_MULTIPLIER) && (modus != INT_MAX)) \ || ((modus <= -100 * GAME_POINTS_MULTIPLIER) && (modus != INT_MIN))) { \ cerr << '\n'; \ cerr << "bad modus : " << modus << '\n'; \ DEBUG_ASSERTION(false, "GameTree: bad modus = " << modus); \ } else // if a type does always lead to an invalid game exception, take the next one namespace CARDS_TESTS { enum CardsTest { VALID_CARDS, ALL_CARDS, FINISHED }; // enum CardsTest } // namespace CARDS_TESTS using CARDS_TESTS::CardsTest; #ifdef USE_THREADS class GametreeData { public: // constructor GametreeData(Gametree const& gametree, Card const& card) : gametree(&gametree), card(card), game(NULL), players(), modus(INT_MAX), finished(false) { } // destructor ~GametreeData() { for (vector::iterator p = this->players.begin(); p != players.end(); ++p) delete *p; delete this->game; return ; } // ~GametreeData() public: // the corresponding gametree class Gametree const* gametree; // the played card Card card; // the virtual game Game* game; // the virtual players vector players; // calculated modus int modus; // whether the thread is finished bool finished; private: // unused GametreeData(); GametreeData(GametreeData const&); GametreeData& operator=(GametreeData const&); }; // class GametreeData static void* Gametree_thread_routine(void* arg); #endif /** ** constructor ** ** @param gametree_interface the interface ** @param future_limit the future limit ** @param aitype whether to use heuristics ** @param rating the rating type ** ** @return - ** ** @author Diether Knof ** ** @version 0.7.3 **/ Gametree::Gametree(GametreeInterface const& gametree_interface, unsigned const future_limit, AiType const aitype, Rating::Type const rating) : gametree_interface_(&gametree_interface), future_limit_(future_limit), end_depth_(0), aitype_(aitype), rating_(rating) { this->end_depth_calculate(future_limit); return ; } // Gametree::Gametree(GametreeInterface gametree_interface, unsigned future_limit, AiType aitype, Rating::Type rating) /** ** destructor ** ** @param - ** ** @return - ** ** @author Diether Knof ** ** @version 0.5.4 **/ Gametree::~Gametree() { } /** ** -> result ** ** @param - ** ** @return whether to use heuristics ** ** @author Diether Knof ** ** @version 0.7.3 **/ bool Gametree::with_heuristics() const { switch (this->aitype()) { case AITYPE::GAMETREE: case AITYPE::GAMETREE_FOR_TEAM: return false; case AITYPE::GAMETREE_WITH_HEURISTICS: return true; default: DEBUG_ASSERTION(false, "Gametree::with_heuristics()\n" " unsupported aitype '" << this->aitype() << "'"); return false; } // switch (this->aitype()) } // bool Gametree::with_heuristics() const /** ** -> result ** ** @param - ** ** @return whether the teampartner plays for the player ** ** @author Diether Knof ** ** @version 0.7.3 **/ bool Gametree::for_team() const { switch (this->aitype()) { case AITYPE::GAMETREE: case AITYPE::GAMETREE_WITH_HEURISTICS: return false; case AITYPE::GAMETREE_FOR_TEAM: return true; default: DEBUG_ASSERTION(false, "Gametree::for_team()\n" " unsupported aitype '" << this->aitype() << "'"); return false; } // switch (this->aitype()) } // bool Gametree::for_team() const /** ** Calculates and sets the end depth (how many tricks in the future are ** to be calculated). ** ** @param future_limit the future limit ** ** @return the calculated end depth ** ** @author Diether Knof ** ** @version 0.5.4 **/ unsigned const& Gametree::end_depth_calculate(unsigned future_limit) { this->end_depth_ = 0; { // count the number of card-combinations for the current trick Trick const& current_trick = this->gametree_interface().game().trick_current(); for (unsigned i = current_trick.actcardno(); i < this->gametree_interface().game().playerno(); i++) { if (this->gametree_interface().value(Aiconfig::FAIRPLAYHANDS)) { future_limit /= this->gametree_interface().handofplayer(current_trick.player_of_card(i) ).cards_single().cardsnumber(); } else { future_limit /= this->gametree_interface().handofplayer(current_trick.player_of_card(i) ).validcards(current_trick).cardsnumber(); } } // for (i) } // count the number of card-combinations for the current trick // calculate for all following tricks the number of cards of each player while (future_limit > 0) { this->end_depth_ += 1; if (this->end_depth_ == this->gametree_interface().game().tricks_remaining_no()) break; for (vector::const_iterator player = this->gametree_interface().game().players_begin(); player != this->gametree_interface().game().players_end(); player++) { if (this->gametree_interface().game().trick_current().cardno_of_player(**player) >= this->gametree_interface().game().trick_current().actcardno()) { // the player still has to play a card in the current trick future_limit /= (this->gametree_interface().handofplayer(**player ).cardsnumber() - this->end_depth_); } else { // the player has already played a card, so he has one card less // on the hand future_limit /= (this->gametree_interface().handofplayer(**player ).cardsnumber() - (this->end_depth_ - 1)); } } // for (player \in this->gametree_interface.game().player) } // while (future_limit > 0) this->end_depth_ = min(this->end_depth_, ( (this->gametree_interface().last_trick_to_calculate() >= this->gametree_interface().game().trick_current_no()) ? (this->gametree_interface().last_trick_to_calculate() - this->gametree_interface().game().trick_current_no()) : 0) ); if (INFO_GAMETREE) if (!this->gametree_interface().game().isvirtual()) cout << "Player " << this->gametree_interface().no() << ": " << "Gametree: " << "end depth = " << this->end_depth() << endl; // always calculate the current trick if (this->end_depth_ == 0) this->end_depth_ = 1; return this->end_depth(); } // unsigned const& VirtualGames::end_depth_calculate(unsigned future_limit) /** ** Calculates the best card ** ** @param - ** ** @return the best card to play ** ** @author Diether Knof ** ** @version 0.5.4 **/ Card Gametree::best_card() const { #ifdef USE_THREADS // in a normal game use the thread support if (!this->gametree_interface().game().isvirtual() && (THREADS >= 2)) return this->best_card_threads(); #endif #ifdef OUTDATED #if defined(WORKAROUND) && defined(RELEASE) HandCards const valid_cards = this->gametree_interface().hand().validcards(this->gametree_interface().game().trick_current()); #else HandCards const valid_cards = (!this->gametree_interface().game().isvirtual() ? this->gametree_interface().hand() : this->gametree_interface().handofplayer(this->gametree_interface().game().player(this->gametree_interface().no())) ).validcards(this->gametree_interface().game().trick_current()); #endif #else // 'local_hand' is needed because the cards of 'valid_cards' // need a reference hand. Hand const local_hand(this->gametree_interface().handofplayer(this->gametree_interface().game().player(this->gametree_interface().no()))); HandCards const valid_cards = local_hand.validcards(this->gametree_interface().game().trick_current()); this->gametree_interface().game().player(this->gametree_interface().no()).self_check(); #endif counter = 0; #ifdef SAVE_RUNTIME // for measuring the runtime ofstream ostr("GameTree.time", ios::app); clock_t const time_begin = clock(); #endif HandCard card_best(this->gametree_interface().hand()); int modus_best = INT_MIN; // now calculate for all cards the modus for (HandCards::const_iterator c = valid_cards.begin(); c != valid_cards.end(); ++c) { #if defined(WINDOWS) && defined(WORKAROUND) HandCard const hc(*c); HandCard const* const c = &hc; #endif // Create new players. // The hand is set during the recursion. vector player_virt; Ai const& real_ai = dynamic_cast(this->gametree_interface().game().player(this->gametree_interface().no())); for (vector::const_iterator player = this->gametree_interface().game().players_begin(); player != this->gametree_interface().game().players_end(); player++) { if (this->with_heuristics()) { player_virt.push_back(real_ai.Ai::clone()); dynamic_cast(player_virt.back())->set_aitype_for_all_tricks(AITYPE::NO_CHOOSEBESTCARD); } else { // if !(this->with_heuristics()) if ((*player)->no() == this->gametree_interface().no()) player_virt.push_back(real_ai.Ai::clone()); else player_virt.push_back(real_ai.Player::clone()); } // if !(this->with_heuristics()) player_virt.back()->set_no(player_virt.size() - 1); player_virt.back()->set_name((*player)->name()); player_virt.back()->set_team(real_ai.team_information().guessed_team(player_virt.size() - 1)); } // for (player) // create a virtual game Game virt_game(this->gametree_interface().game(), player_virt); // set all hands for (vector::const_iterator player = virt_game.players_begin(); player != virt_game.players_end(); player++) { (*player)->set_hand(this->gametree_interface().handofplayer(this->gametree_interface().game().player((*player)->no()))); (*player)->self_check(); } try { { // play the card HandCard const card(virt_game.player_current().hand(), *c); if ( card.possible_swine() && !card.isswine()) { virt_game.swines_announce(virt_game.player_current()); } if ( card.possible_hyperswine() && !card.ishyperswine()) virt_game.hyperswines_announce(virt_game.player_current()); virt_game.player_current().hand().playcard(card); virt_game.trick_current() += card; virt_game.teaminfo_update(); virt_game.player_current_ = &virt_game.player_following(virt_game.player_current()); for (vector::iterator p = virt_game.players_begin(); p != virt_game.players_end(); p++) (*p)->card_played(card); } // play the card 'c' ::ui->ai_test_card(*c, this->gametree_interface().no()); #ifdef OUTDATED // 0.7.3 by rated_modus()' int const modus = this->maxmin_modi(virt_game, this->end_depth(), INT_MIN, INT_MAX); #endif int const modus = this->rated_modus(virt_game, this->end_depth()); ::ui->ai_card_weighting(modus); // this is needed when 'gametree' is called from 'virtual games' if (this->gametree_interface().game().isvirtual() && ( (modus == INT_MIN) || (modus == INT_MAX)) ) DEBUG_THROW( InvalidGameException, InvalidGameException() ); #ifdef RELEASE DEBUG_ASSERTION(((modus != INT_MIN) && (modus != INT_MAX)), "GameTree::bestcard()\n" " No valid game found.\n" " Card: " << *c); #else if ( (modus == INT_MIN) || (modus == INT_MAX)) { cerr << "GameTree-error\n"; DEBUG_ASSERTION(((modus != INT_MIN) && (modus != INT_MAX)), "GameTree::bestcard()\n" " No valid game found.\n" " Card: " << *c); } #endif if (INFO_GAMETREE) if (!this->gametree_interface().game().isvirtual()) cout << setw(12) << *c << ": " << setw(8) << modus << endl; // test, whether the card is better: // - the modus is better // - the modus is equal take the lower card // - the modus is equal, take the color card with the greater/lower value // if the trick goes to the own/opposite team so far if ( (modus > modus_best) || ( (modus == modus_best) && ( (c->less(card_best)) || ( !card_best.istrump() && !this->gametree_interface().game().trick_current().isstartcard() && ( (::maybe_to_team(this->gametree_interface().teamofplayer(this->gametree_interface().game().trick_current().winnerplayer())) == this->gametree_interface().team()) == (c->value() > card_best.value()) ) ) ) ) ) { modus_best = modus; card_best = *c; } // if (modus > modus_best) } catch (...) { // delete the virtual players for (vector::iterator player = player_virt.begin(); player != player_virt.end(); player++) delete *player; throw; } // try // delete the virtual players for (vector::iterator player = player_virt.begin(); player != player_virt.end(); player++) delete *player; if (::game_status != GAMESTATUS::GAME_PLAY) break; } // for (c \in valid_hand.cards()) #ifdef SAVE_RUNTIME if (counter >= 2000) { unsigned const used_time = ((clock() - time_begin) / (CLOCKS_PER_SEC / 1000)); ostr << setw(8) << counter << "\t" << setw(8) << used_time << "\t" << setw(8) << (used_time * 1000 / counter) << endl; } // if (counter >= 200) #endif // #ifdef SAVE_RUNTIME #ifndef RELEASE #if 0 DEBUG_ASSERTION( (this->end_depth() == 1) || (counter <= this->future_limit() ), "GameTree::best_card():\n" " counter > future limit: " << counter << " > " << this->future_limit() << '\n' << " end depth = " << this->end_depth() ); #endif #endif #ifdef WORKAROUND if (!card_best && this->with_heuristics()) { // ToDo // sometimes no card is returned, so use the gametree without the heuristics // (perhaps the heuristic makes the hands invalid) return Gametree(this->gametree_interface(), this->future_limit() / 2, AITYPE::GAMETREE, this->rating()).best_card(); } #endif DEBUG_ASSERTION(card_best, "Gametree::best_card():\n" " no best card found.\n" << (this->with_heuristics() ? "with heuristics" : "without heuristics") << "\n" << "Hand:\n" << this->gametree_interface().hand()); return card_best; } // Card VirtualGames::best_card() const #ifdef USE_THREADS /** ** Calculates the best card (with thread support) ** ** @param - ** ** @return the best card to play ** ** @author Diether Knof ** ** @version 0.7.3 **/ Card Gametree::best_card_threads() const { // 'local_hand' is needed because the cards of 'valid_cards' // need a reference hand. Hand const local_hand(this->gametree_interface().handofplayer(this->gametree_interface().game().player(this->gametree_interface().no()))); HandCards const valid_cards = local_hand.validcards(this->gametree_interface().game().trick_current()); this->gametree_interface().game().player(this->gametree_interface().no()).self_check(); counter = 0; // change the ui so that there is no thread clashing UI* main_ui = ::ui; ::ui = UI::new_(UI_TYPE::DUMMY); #ifdef SAVE_RUNTIME // for measuring the runtime ofstream ostr("GameTree.time", ios::app); clock_t const time_begin = clock(); #endif HandCard card_best(this->gametree_interface().hand()); int modus_best = INT_MIN; // the thread ids and the corresponding data list > threads; // now calculate for all cards the modus for (HandCards::const_iterator c = valid_cards.begin(); c != valid_cards.end(); ++c) { { // check that the number of threads does not exceed 'THREADS' while ( !threads.empty() && (threads.size() + 1 >= THREADS)) { main_ui->sleep(10); #if 1 // check whether a thread is finished for (list >::iterator t = threads.begin(); t != threads.end(); ++t) { if (t->second->finished) { pthread_join(t->first, NULL); int const modus = t->second->modus; Card const& card = t->second->card; main_ui->ai_test_card(card, this->gametree_interface().no()); main_ui->ai_card_weighting(modus); // test, whether the card is better if ( (modus > modus_best) || ( (modus == modus_best) && (card.less(card_best)) ) ) { modus_best = modus; card_best = card; } // if (modus improved) delete t->second; threads.erase(t); break; } // if (t->second->finished) } // for (t \in threads) #else pthread_join(threads.front().first, NULL); int const modus = threads.front().second->modus; Card const& card = threads.front().second->card; main_ui->ai_test_card(card, this->gametree_interface().no()); main_ui->ai_card_weighting(modus); // test, whether the card is better if ( (modus > modus_best) || ( (modus == modus_best) && (card.less(card_best)) ) ) { modus_best = modus; card_best = card; } // if (modus improved) delete threads.front().second; threads.pop_front(); #endif } // while (threads.size() + 1 >= THREADS) } // check that the number of threads does not exceed 'THREADS' GametreeData* data = new GametreeData(*this, *c); #if defined(WINDOWS) && defined(WORKAROUND) HandCard const hc(*c); HandCard const* const c = &hc; #endif // Create new players. // The hand is set during the recursion. vector& player_virt = data->players; Ai const& real_ai = dynamic_cast(this->gametree_interface().game().player(this->gametree_interface().no())); for (vector::const_iterator player = this->gametree_interface().game().players_begin(); player != this->gametree_interface().game().players_end(); player++) { if (this->with_heuristics()) { player_virt.push_back(real_ai.Ai::clone()); dynamic_cast(player_virt.back())->set_aitype_for_all_tricks(AITYPE::NO_CHOOSEBESTCARD); } else { // if !(this->with_heuristics()) if ((*player)->no() == this->gametree_interface().no()) player_virt.push_back(real_ai.Ai::clone()); else player_virt.push_back(real_ai.Player::clone()); } // if !(this->with_heuristics()) player_virt.back()->set_no(player_virt.size() - 1); player_virt.back()->set_name((*player)->name()); player_virt.back()->set_team(real_ai.team_information().guessed_team(player_virt.size() - 1)); } // for (player) // create a virtual game data->game = new Game(this->gametree_interface().game(), player_virt); Game& virt_game = *data->game; // set all hands for (vector::const_iterator player = virt_game.players_begin(); player != virt_game.players_end(); player++) { (*player)->set_hand(this->gametree_interface().handofplayer(this->gametree_interface().game().player((*player)->no()))); (*player)->self_check(); } try { { // play the card HandCard const card(virt_game.player_current().hand(), *c); if ( card.possible_swine() && !card.isswine()) virt_game.swines_announce(virt_game.player_current()); if ( card.possible_hyperswine() && !card.ishyperswine()) virt_game.hyperswines_announce(virt_game.player_current()); virt_game.player_current().hand().playcard(card); virt_game.trick_current() += card; virt_game.teaminfo_update(); virt_game.player_current_ = &virt_game.player_following(virt_game.player_current()); for (vector::iterator p = virt_game.players_begin(); p != virt_game.players_end(); p++) (*p)->card_played(card); } // play the card 'c' { // create a new thread // the thread id pthread_t thread = 0; int const error = pthread_create(&thread, NULL, Gametree_thread_routine, data); if (error) { main_ui->error("error creating a thread: " + DK::Utils::String::to_string(error)); } threads.push_back(pair(thread, data)); } // create a new thread } catch (...) { { // cancel all remaining threads while (!threads.empty()) { pthread_cancel(threads.front().first); pthread_testcancel(); delete threads.front().second; threads.pop_front(); } // while (!threads.empty()) } // cancel all remaining threads // set the ui back delete ::ui; ::ui = main_ui; throw; } // try if (::game_status != GAMESTATUS::GAME_PLAY) break; } // for (c \in valid_hand.cards()) if (::game_status != GAMESTATUS::GAME_PLAY) { // cancel all remaining threads while (!threads.empty()) { pthread_cancel(threads.front().first); pthread_testcancel(); delete threads.front().second; threads.pop_front(); } // while (!threads.empty()) } // if (::game_status != GAMESTATUS::GAME_PLAY) { // wait for all remaining threads while (!threads.empty()) { main_ui->sleep(10); #if 1 // check whether a thread is finished for (list >::iterator t = threads.begin(); t != threads.end(); ++t) { if (t->second->finished) { pthread_join(t->first, NULL); int const modus = t->second->modus; Card const& card = t->second->card; main_ui->ai_test_card(card, this->gametree_interface().no()); main_ui->ai_card_weighting(modus); // test, whether the card is better if ( (modus > modus_best) || ( (modus == modus_best) && (card.less(card_best)) ) ) { modus_best = modus; card_best = card; } // if (modus improved) delete t->second; threads.erase(t); break; } // if (t->second->finished) } // for (t \in threads) #else pthread_join(threads.front().first, NULL); int const modus = threads.front().second->modus; Card const& card = threads.front().second->card; main_ui->ai_test_card(card, this->gametree_interface().no()); main_ui->ai_card_weighting(modus); // test, whether the card is better if ( (modus > modus_best) || ( (modus == modus_best) && (card.less(card_best)) ) ) { modus_best = modus; card_best = card; } // if (modues improved) delete threads.front().second; threads.pop_front(); #endif } // while (!threads.empty()) } // wait for all remaining threads // set the ui back delete ::ui; ::ui = main_ui; #ifdef SAVE_RUNTIME if (counter >= 2000) { unsigned const used_time = ((clock() - time_begin) / (CLOCKS_PER_SEC / 1000)); ostr << setw(8) << counter << "\t" << setw(8) << used_time << "\t" << setw(8) << (used_time * 1000 / counter) << endl; } // if (counter >= 200) #endif // #ifdef SAVE_RUNTIME #ifndef RELEASE #if 0 DEBUG_ASSERTION( (this->end_depth() == 1) || (counter <= this->future_limit() ), "GameTree::best_card():\n" " counter > future limit: " << counter << " > " << this->future_limit() << '\n' << " end depth = " << this->end_depth() ); #endif #endif #ifdef WORKAROUND if ( !card_best && this->with_heuristics()) { // ToDo // sometimes no card is returned, so use the gametree without the heuristics // (perhaps the heuristic makes the hands invalid) return Gametree(this->gametree_interface(), this->future_limit() / 2, AITYPE::GAMETREE, this->rating()).best_card(); } #endif DEBUG_ASSERTION(card_best, "Gametree::best_card():\n" " no best card found.\n" << (this->with_heuristics() ? "with heuristics" : "without heuristics") << "\n" << "Hand:\n" << this->gametree_interface().hand()); return card_best; } // Card VirtualGames::best_card() const #endif // #ifdef USE_THREADS /** ** Returns the rated modi. ** From players of the same team, the modi is calculated positiv, ** for players of the opposite team, the modi is calculated negative ** ** @param virt_game the virtual game ** @param tricks_to_calc how many tricks to be calced ** ** @return the maximal minimum modi ** ** @author Diether Knof ** ** @version 0.7.3 **/ int Gametree::rated_modus(Game& virt_game, unsigned const tricks_to_calc) const { ::ui->update(); if (::game_status != GAMESTATUS::GAME_PLAY) return 0; // the correspoding virtual player to the ai Ai const& virt_ai = dynamic_cast(virt_game.player(this->gametree_interface().no())); // the team of the ai Team const ai_team = virt_ai.team(); // full trick or finished game if (virt_game.trick_current().isfull()) { int const modus = full_trick_modi(virt_game, tricks_to_calc); #ifdef DKNOF CHECK_MODUS(modus); #endif return modus; } // Here we are in the middle of a trick. // Try all cards and take the worst/best modi if the player is in the // opposite/same team. // The player is in the same team, if the team of the ai is known to all // and the team of the player is the team of the ai. bool same_team = false; // first look, whether the team of the ai is known to all if (virt_game.teaminfo(virt_ai) == ai_team) { // next test, whether the team of the player is the same of the ai if (virt_game.teaminfo(virt_game.player_current()) == ai_team) same_team = true; else if ((virt_game.teaminfo(virt_game.player_current()) == TEAM::UNKNOWN) && (this->gametree_interface().teamofplayer(virt_game.player(virt_game.player_current().no())) == ai_team)) // The team of the player is not known, // but the interface says, that the teams are the same. same_team = true; } // if (team of virt_ai known) // Invert the modus if the counting is for the opposite players; bool const invert_modus = ( (virt_game.player_current().no() != this->gametree_interface().no()) && ( !this->for_team() || !same_team) ); // Now test all the cards and calculate the modus // first set the hand of the player Hand hand; try { hand = virt_ai.handofplayer(virt_game.player_current()); } catch (InvalidGameException e) { cerr << "\n\n\n\n\n\n\n\n\n\n\n\n\n"; cerr << "invalid handofplayer:\n"; cerr << this->gametree_interface().game(); cerr << dynamic_cast(this->gametree_interface().game().player(this->gametree_interface().no())).cards_information(); // nothing to do, because the modus cannot get changed throw; } // catch() if (this->with_heuristics()) { // update/set the hand of the current player virt_game.player_current().set_hand(hand); // card that is played HandCard const card = dynamic_cast(virt_game.player_current()).card_get(); if (card) { // create a new virtual game vector player_virt; for (vector::const_iterator player = virt_game.players_begin(); player != virt_game.players_end(); player++) { player_virt.push_back((*player)->clone()); player_virt.back()->set_no(player_virt.size() - 1); player_virt.back()->set_hand(virt_ai.handofplayer(virt_ai.game().player(player_virt.size() - 1))); } // for (player) Game* const virt_game_2 = new Game(virt_game, player_virt); unsigned const counter_bak = counter; try { // play the card try { HandCard const card2(virt_game_2->player_current().hand(), card); if ( card2.possible_swine() && !card2.isswine()) virt_game_2->swines_announce(virt_game_2->player_current()); if ( card2.possible_hyperswine() && !card2.ishyperswine()) virt_game_2->hyperswines_announce(virt_game_2->player_current()); virt_game_2->player_current().hand().playcard(card2); virt_game_2->trick_current() += card2; virt_game_2->teaminfo_update(); virt_game_2->player_current_ = &virt_game_2->player_following(virt_game_2->player_current()); for (vector::iterator p = virt_game_2->players_begin(); p != virt_game_2->players_end(); p++) (*p)->card_played(card2); int modus; try { ::ui->ai_test_card(card, virt_game.player_current().no()); modus = this->rated_modus(*virt_game_2, tricks_to_calc); #ifdef DKNOF CHECK_MODUS(modus); #endif if ( (modus == INT_MIN) || (modus == INT_MAX)) DEBUG_THROW( InvalidGameException, InvalidGameException() ); ::ui->ai_card_weighting(modus); } catch(...) { ::ui->ai_card_weighting(INT_MIN); throw; } // try // delete the created virtual players and virtual game for (vector::iterator player = player_virt.begin(); player != player_virt.end(); player++) delete *player; delete virt_game_2; #ifdef DKNOF CHECK_MODUS(modus); #endif return modus; } catch (...) { // delete the created virtual players and virtual game for (vector::iterator player = player_virt.begin(); player != player_virt.end(); player++) delete *player; delete virt_game_2; } // try } catch (InvalidGameException const& e) { counter = counter_bak; DEBUG_CAUGHT(); } catch (...) { throw; } // try // The card could not be played, // so remove it from the hand and look at all other cards. hand.remove(card); } // if (card) } // if (this->with_heuristics()) // this mark is taken if no valid game has been found and not all cards // have been tested (only the valid cards) CardsTest cards_test = ((!this->gametree_interface().value(Aiconfig::FAIRPLAYHANDS) || (hand.cardsnumber() == virt_game.tricks_remaining_no() + 1) || ( this->for_team() && same_team) ) ? CARDS_TESTS::VALID_CARDS : CARDS_TESTS::ALL_CARDS); // When the ai does not 'know' the hand of the player // all cards have to be viewed, else there can result only invalid games. HandCards valid_cards = ((cards_test == CARDS_TESTS::VALID_CARDS) ? hand.validcards(virt_game.trick_current()) : hand.cards_single()); // the rating used for the weightings Rating* const rating = Rating::new_(this->rating()); DEBUG_ASSERTION(rating, "Gametree::rated_modus()\n" " rating '" << this->rating() << "' not created"); // The sum of the modi is used in order to catch special points // in the case that not all teams are known (see below) int modi_sum = 0; // the number of modi in 'modi_sum' int modi_num = 0; do { // now test all cards for (HandCards::const_iterator c = valid_cards.begin(); c != valid_cards.end(); ++c) { // create a new virtual game vector player_virt; for (vector::const_iterator player = virt_game.players_begin(); player != virt_game.players_end(); player++) { player_virt.push_back((*player)->clone()); player_virt.back()->set_no(player_virt.size() - 1); #ifdef OUTDATED if (player_virt.back()->type() == Player::AI) dynamic_cast(player_virt.back())->set_hand(virt_ai.handofplayer(virt_ai.game().player(player_virt.size() - 1)), !this->gametree_interface().value(Aiconfig::FAIRPLAYHANDS)); else #endif player_virt.back()->set_hand(virt_ai.handofplayer(virt_ai.game().player(player_virt.size() - 1))); } // for (player) Game* const virt_game_2 = new Game(virt_game, player_virt); try { { // now play the card HandCard const card2(virt_game_2->player_current().hand(), *c); if ( card2.possible_swine() && !card2.isswine()) virt_game_2->swines_announce(virt_game_2->player_current()); if ( card2.possible_hyperswine() && !card2.ishyperswine()) virt_game_2->hyperswines_announce(virt_game_2->player_current()); virt_game_2->player_current().hand().playcard(card2); virt_game_2->trick_current() += card2; virt_game_2->teaminfo_update(); virt_game_2->player_current_ = &virt_game_2->player_following(virt_game_2->player_current()); try { for (vector::iterator p = virt_game_2->players_begin(); p != virt_game_2->players_end(); p++) (*p)->card_played(card2); } catch (...) { throw; } } // play the card 'c' #ifndef RELEASE if (virt_game_2->tricks_remaining_no() > 1) for (vector::const_iterator player = virt_game_2->players_begin(); player != virt_game_2->players_end(); player++) DEBUG_ASSERTION(((*player)->hand().cardsnumber() >= 1), "Gametree::rated_modus():\n" " empty hand.\n" "Game: \n"); #endif // get the modi through the recursion ::ui->ai_test_card(*c, virt_game.player_current().no()); try { int const modus = this->rated_modus(*virt_game_2, tricks_to_calc); ::ui->ai_card_weighting(modus); if ( (modus != INT_MIN) && (modus != INT_MAX)) { // the rating is from the view of the player of the card and not // from the ai if (invert_modus) rating->add(-modus); else rating->add(modus); modi_sum += modus; modi_num += 1; } } catch (...) { ::ui->ai_card_weighting(INT_MIN); throw; } } catch (InvalidGameException const& e) { // nothing to do DEBUG_CAUGHT(); } catch (...) { // delete the created virtual players and virtual game for (vector::iterator player = player_virt.begin(); player != player_virt.end(); player++) delete *player; delete virt_game_2; delete rating; throw; } // try // delete the created virtual players and virtual game for (vector::iterator player = player_virt.begin(); player != player_virt.end(); player++) delete *player; delete virt_game_2; } // for (c \in valid_cards) if ((cards_test == CARDS_TESTS::VALID_CARDS) && ( (rating->value() == INT_MIN) || (rating->value() == INT_MAX) )) { // test only the cards that have not been tested, yet HandCards remaining_cards = hand.cards_single(); remaining_cards.remove(valid_cards); valid_cards = remaining_cards; cards_test = CARDS_TESTS::ALL_CARDS; continue; } // if (no valid game found) cards_test = CARDS_TESTS::FINISHED; } while (cards_test < CARDS_TESTS::FINISHED); int const modus = ( invert_modus ? -rating->value() : rating->value()); #ifdef DKNOF CHECK_MODUS(modus); #endif delete rating; if ( (modus == INT_MIN) || (modus == INT_MAX)) { return modus; } else { // the sum is added because else in the end when not all teams are known // special points (i.e. charlie) could be lost // note: when the game is not finished, 'modi_sum' < 1000 return (modus + modi_sum / modi_num / 1000); } } // int Gametree:rated_modus(Game& virt_game, unsigned tricks_to_calc) const /** ** Returns the rated modi. ** From players of the same team, the modi is calculated positiv, ** for players of the opposite team, the modi is calculated negative ** ** @param virt_game the virtual game ** @param tricks_to_calc how many tricks to be calced ** ** @return the modi for the full trick ** ** @author Diether Knof ** ** @version 0.7.3 **/ int Gametree::full_trick_modi(Game& virt_game, unsigned const tricks_to_calc) const { counter += 1; // the correspoding virtual player to the ai Ai const& virt_ai = dynamic_cast(virt_game.player(this->gametree_interface().no())); try { virt_game.evaluatetrick(); } catch (...) { throw; } if (tricks_to_calc > 1) { virt_game.tricks().push_back(new Trick(virt_game.player_current())); for (vector::iterator p = virt_game.players().begin(); p != virt_game.players().end(); p++) (*p)->trick_open(virt_game.trick_current()); int const modus = rated_modus(virt_game, tricks_to_calc - 1); #ifdef DKNOF CHECK_MODUS(modus); #endif return modus; } // game finished, the main interest is the final result... if (virt_game.tricks_remaining_no() == 0) { virt_game.finish(); GameSummary const game_summary(virt_game); if (game_summary.winnerteam() == TEAM::NOTEAM) { return (GAME_POINTS_MULTIPLIER * (game_summary.points(virt_ai.team()) - game_summary.points(opposite(virt_ai.team())))); } else { // if !(game_summary.winnerteam() == TEAM::NOTEAM) if (::maybe_to_team(game_summary.winnerteam()) == virt_ai.team()) return (GAME_POINTS_MULTIPLIER * game_summary.points() + game_summary.trick_points(virt_ai.team())); else return (-GAME_POINTS_MULTIPLIER * game_summary.points() + game_summary.trick_points(virt_ai.team())); } // if !(game_summary.winnerteam() == TEAM::NOTEAM) } // if (virt_game.tricks_remaining_no() == 0) // some tricks has been calculated, get the modus from them. //#define DKNOF_TEST #ifdef DKNOF_TEST bool DEBUG_OUT = false; if ( ( (virt_game.trick_current_no() == 11) && ( (virt_game.trick_current().card(0) == Card(Card::HEART, Card::KING)) && (virt_game.trick_current().card(1) == Card(Card::CLUB, Card::TEN)) && (virt_game.trick_current().card(2) == Card(Card::HEART, Card::TEN)) ) ) ) DEBUG_OUT = true; if (DEBUG_OUT) { cout << "--\n"; cout << virt_game.trick(9).card_of_player(virt_ai) << ", " << virt_game.trick_current().card_of_player(virt_ai) << endl; cout << virt_game.trick_current_no() << endl; } #endif int modus = 0; // sum up the modi for all tricks for (unsigned t = this->gametree_interface().game().trick_current_no(); t < virt_game.trick_current_no(); t++) { modus += TrickWeighting::modi(this->gametree_interface(), virt_game.trick(t), virt_ai.team(), virt_game.trick(t).card_of_player(virt_ai)); #ifdef DKNOF_TEST if (DEBUG_OUT) { cout << virt_game.trick(t); cout << "modus = " << TrickWeighting::modi(this->gametree_interface(), virt_game.trick(t), virt_ai.team(), virt_game.trick(t).card_of_player(virt_ai)) << " (" << virt_game.trick(t).winnerteam() << ")" << endl; } #endif } // copied from 'WVirtualGames' // add some points, if the own team is in the back, if (virt_game.real_trick_current_no() < virt_game.trickno() - 1) { modus += TrickWeighting::backhand(this->gametree_interface(), virt_game.trick_current().card_of_player(virt_ai), virt_game); } // if (virt_game.trick_current_no() < virt_game.trickno() - 1) #ifdef DKNOF CHECK_MODUS(modus); #endif return modus; } // int Gametree::full_trick_modi(Game& virt_game, unsigned tricks_to_calc) const #ifdef USE_THREADS /** ** calculates the modus for the given game ** ** @param arg thread data ** contains the gametree, the virtual game, the end depth ** and the modus which is calculated ** ** @return NULL ** ** @author Diether Knof ** ** @version 0.7.3 **/ void* Gametree_thread_routine(void* arg) { GametreeData* data = static_cast(arg); data->modus = data->gametree->rated_modus(*data->game, data->gametree->end_depth()); data->finished = true; return NULL; } // static void* Gametree_thread_routine(void* arg) #endif // #ifdef USE_THREADS