/********************************************************************** * * 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 #include "w_virtual_games.h" #include "VirtualGamesInterface.h" #include "trickweighting.h" #include "ai.h" #include "cards_information.h" #include "cards_information.of_player.h" #include "team_information.h" #include "heuristics.h" #include "../../game/game.h" #include "../../game/game_summary.h" #include "../../game/exception.h" #include "../../card/trick.h" #include "../../misc/setting.h" #include "../../ui/ui.h" // whether to save the runtime //#define SAVE_RUNTIME #ifdef RELEASE #undef SAVE_RUNTIME #endif /** ** constructor ** ** @param vgi the vgi ** @param future_limit the future limit ** ** @return - ** ** @author Diether Knof ** ** @version 0.4.4 **/ WVirtualGames::WVirtualGames(VirtualGamesInterface const& vgi, unsigned const& future_limit) : vgi_p(&vgi), future_limit_p(future_limit), end_depth_p(0), player_virt_p(), weighting_p() { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT, "WVirtualGames::WVirtualGames()"); DEBUG_RETURNING(VOID, INFO_W_VIRTUAL_GAMES && INFO_INIT, "WVirtualGames::WVirtualGames()"); } // WVirtualGames::WVirtualGames(unsigned const& future_limit) /** ** ** destructor ** ** @param - ** ** @return - ** ** @version 0.4.4 ** ** @author Diether Knof ** **/ WVirtualGames::~WVirtualGames() { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT, "WVirtualGames::~WVirtualGames()"); for (vector::iterator player = this->player_virt_p.begin(); player != this->player_virt_p.end(); player++) delete *player; DEBUG_RETURNING(VOID, INFO_W_VIRTUAL_GAMES && INFO_INIT, "WVirtualGames::~WVirtualGames()"); } // WVirtualGames::~WVirtualGames() /** ** ** -> result ** ** @param - ** ** @return the vgi ** ** @version 0.4.4 ** ** @author Diether Knof ** **/ VirtualGamesInterface const& WVirtualGames::vgi() const { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::vgi()"); DEBUG_RETURNING(*(this->vgi_p), INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::vgi()"); } // VirtualGamesInterface const& VirtualGames::vgi() const /** ** ** -> result ** ** @param - ** ** @return the limit of tricks to calc ** ** @version 0.4.4 ** ** @author Diether Knof ** **/ unsigned const& WVirtualGames::future_limit() const { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::future_limit()"); DEBUG_RETURNING(this->future_limit_p, INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::future_limit()"); } // unsigned const& VirtualGames::future_limit() const /** ** ** -> result ** ** @param - ** ** @return the maximal depth to calc into the future (in tricks) ** ** @version 0.4.4 ** ** @author Diether Knof ** **/ unsigned const& WVirtualGames::end_depth() const { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::end_depth()"); DEBUG_RETURNING(this->end_depth_p, INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::end_depth()"); } // unsigned const& VirtualGames::end_depth() const /** ** -> result ** ** @param - ** ** @return the hand to be examined ** ** @author Diether Knof ** ** @version 0.7.1 **/ HandCards const& WVirtualGames::hand() const { return this->hand_p; } // Hand const& VirtualGames::hand() const /** ** ** calculates the maximal depth of tricks, ** so that no more of 'future limit' tricks are considered ** took the code from W_virtual::calctricks() ** @see W_virtual::calctricks() ** ** @param - ** ** @return the calculated end depth ** ** @version 0.4.4 ** ** @author Borg Enders ** **/ unsigned const& WVirtualGames::end_depth_calculate() { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::end_depth_calculate()"); // #combination for all tricks unsigned long int alltricks = 1; // #combination for one trick unsigned long int onetrick = 1; Trick const& trick = this->vgi().game().trick_current(); // count the number of card-combinations for the current trick for (unsigned i = trick.actcardno(); i < this->vgi().game().playerno(); i++) onetrick *= this->vgi().handofplayer(trick.player_of_card(i) ).validcards(trick).cardsnumber(); alltricks *= onetrick; // count the number of card-combinations for the other tricks // (since I don't know what color is played, I take all cards of the hand) for (this->end_depth_p = 1; // '<' because also the current trick is counted in tricks_remaining_no (this->end_depth_p < this->vgi().game().tricks_remaining_no()); this->end_depth_p += 1){ onetrick = 1; for (unsigned i = 0; i < this->vgi().game().playerno(); i++) { #ifndef POSTPHONED if (this->vgi().game().trick_current().cardno_of_player(this->vgi().game().player(i)) >= this->vgi().game().trick_current().actcardno()) // the player still has to play a card in the current trick onetrick *= (this->vgi().handofplayer(this->vgi().game().player(i)).cardsnumber() - this->end_depth_p); else // the player has already played a card, so he has one card less // on the hand onetrick *= (this->vgi().handofplayer(this->vgi().game().player(i)).cardsnumber() - (this->end_depth_p - 1)); DEBUG_ASSERTION((onetrick > 0), "WVirtualGames::end_depth_calculate():\n" " onetrick == 0\n" << "cardsnumber = " << this->vgi().handofplayer(this->vgi().game().player(i)).cardsnumber() << '\n' << "end_depth = " << this->end_depth_p << '\n' << "trickno = " << this->vgi().game().trick_current_no() << '\n' << "remaining trickno = " << this->vgi().game().tricks_remaining_no() << '\n' ); #else onetrick *= this->vgi().hand().cardsnumber() - this->end_depth_p; #endif } if (alltricks < this->future_limit_p / onetrick) { alltricks *= onetrick; } else { break; } } // for (this->end_depth_p) this->end_depth_p = min(this->end_depth(), ((this->vgi().last_trick_to_calculate() >= this->vgi().game().trick_current_no()) ? (this->vgi().last_trick_to_calculate() - this->vgi().game().trick_current_no()) : 0)); #ifdef POSTPHONED this->end_depth_p = max(unsigned(2), this->end_depth()); #endif DEBUG_RETURNING(this->end_depth(), INFO_W_VIRTUAL_GAMES && INFO_VALUE, "WVirtualGames::end_depth_calculate()"); } // unsigned const& VirtualGames::end_depth_calculate() /** ** ** creates the game and the players. ** ** @param - ** ** @return - ** ** @exception InvalidGameException the virtual game is invalid ** ** @version 0.5.4 ** ** @author Borg Enders ** @author Diether Knof ** ** @todo future limit for the virtual players ** **/ void WVirtualGames::init() { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_INIT, "WVirtualGames::init()"); // calculate the end depth this->end_depth_calculate(); // free the memory for (vector::iterator player = this->player_virt_p.begin(); player != this->player_virt_p.end(); player++) delete *player; this->player_virt_p.clear(); // set the cards to the valid cards this->hand_p = this->vgi().hand().validcards(this->vgi().game().trick_current() ); // set the weightings to 0 this->weighting_p = vector(this->hand().cardsnumber(), 0); Ai const& real_ai = static_cast(this->vgi().game().player(this->vgi().no())); // create the virtual players // (the players have a hand independent of the hand of the vgi, so that // reordering the card is no problem) for (vector::const_iterator player = this->vgi().game().players_begin(); player != this->vgi().game().players_end(); player++) { this->player_virt_p.push_back(new Ai(real_ai)); // if the player is not the vgi, some values have to change if (*player != &real_ai) { unsigned const no = this->player_virt_p.size() - 1; Ai& ai_virtual = static_cast(*(this->player_virt_p.back())); // set the playernumber ai_virtual.set_no(no); // set the cards information ai_virtual.set_cards_information(real_ai.cards_information()); // set the hand ai_virtual.set_hand(this->vgi().handofplayer(**player)); DEBUG_ASSERTION((ai_virtual.hand().cardsnumber() + 1 // hack (if the player has already played a card in the trick) >= this->vgi().game().tricks_remaining_no()), "WVirtualGames::init():\n" " the hand of the virtual player " << ai_virtual.no() << " is too small: " << ai_virtual.hand().cardsnumber() << " < " << this->vgi().game().tricks_remaining_no() << ":\n" << ai_virtual.hand()); // set the teaminfo ai_virtual.set_teams(real_ai.team_information().guessed_teams()); // change the future limit #ifndef POSTPHONED // *** workaround static_cast(ai_virtual).set_future_limit_for_all_tricks(this->future_limit() / 10); #endif // #ifndef POSTPHONED } // if (**player != this->vgi()) } // for (player) DEBUG_RETURNING(VOID, INFO_W_VIRTUAL_GAMES && INFO_INIT, "WVirtualGames::init()"); } // void WVirtualGames::init() /** ** ** -> result ** ** @param - ** ** @return the card, that makes the most points ** ** @version 0.5.4 ** ** @author Borg Enders ** **/ Card WVirtualGames::best_card() { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION, "WVirtualGames::best_card()"); // create the virtual game and the virtual players this->init(); // calculate the weightings this->weightings_calc(); // search the best valid card // the number of the best card HandCard best_card = this->hand().card(0); int best_weighting = this->weighting_p[0]; // search a better valid card #ifndef RELEASE if (HEURISTIC_OUTPUT) { if (!this->vgi().game().isvirtual()) { cout << this->vgi().no() << std::endl; cout << this->hand().card(0) << " " << this->weighting_p[0] << std::endl; } } #endif for (unsigned c = 1; c < this->hand().cardsnumber(); c++) { #ifndef RELEASE if (HEURISTIC_OUTPUT) { if (!this->vgi().game().isvirtual()) { cout << this->hand().card(c) << " " << this->weighting_p[c] << std::endl; } } #endif if( this->weighting_p[c] > best_weighting // find best weighting || ( this->weighting_p[c] == best_weighting // there is not one best && ( ( this->hand().card(c).less(best_card) // smallest card && ( !this->hand().card(c).isfox() || this->weighting_p[c]>0 ) // no Fox ) || (best_card.isfox() && best_weighting < 0) // replace fox ) ) ) { best_card = this->hand().card(c); best_weighting = this->weighting_p[c]; } // if (better card found) } // for (c < this->hand().cardsnumber()) if( best_weighting < 0 ) { unsigned validcards = 0; for (unsigned c = 0; c < this->hand().cardsnumber(); c++) if( this->weighting_p[c] != INT_MIN ) { validcards++; } #ifdef WORKAROUND if ( (validcards <= 1) && (this->hand().cardsnumber() > 1) ) { #ifndef RELEASE cerr << "WARNING: Virtual_games only one valid card found, " "taking smallest card!" << std::endl; #endif for (unsigned c = 0; c < this->hand().cardsnumber(); c++) if( this->hand().card(c).less(best_card)// smallest card && (!this->hand().card(c).isfox()) ) { best_card = this->hand().card(c); } if ( best_card.istrump() ) { for (unsigned c = 0; c < this->hand().cardsnumber(); c++) { if( this->hand().card(c).istrump() && this->hand().card(c).value() < best_card.value() ) { best_card = this->hand().card(c); } } } } // if ( (validcards == 1) && (this->hand().cardsnumber() > 1) ) #endif } // if( best_weighting < 0 ) DEBUG_RETURNING(best_card, INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION, "WVirtualGames::best_card()"); } // Card WVirtualGames::best_card() /** ** ** calculates the weightings ** and writes the values into the vector 'weighting_p; ** ** @param - ** ** @return - ** ** @version 0.4.4 ** ** @author Borg Enders ** **/ void WVirtualGames::weightings_calc() { DEBUG_CALLING(INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION, "WVirtualGames::weightings_calc()"); ::ui->update(); #ifdef SAVE_RUNTIME // for measuring the runtime ofstream ostr("VirtualGames.time", ios::app); clock_t time_begin = 0; if (!this->vgi().game().isvirtual()) time_begin = clock(); #endif // some statistics static unsigned counter = 0; static unsigned virtual_depth = 0; static unsigned virtual_depth_min = 0; static unsigned counter_last = 0; if (!this->vgi().game().isvirtual()) { counter = 0; virtual_depth = 0; virtual_depth_min = 0; counter_last = 0; } virtual_depth += 1; if (INFO_W_VIRTUAL_GAMES) { if (counter >= counter_last + 500) { counter_last = counter; virtual_depth_min = virtual_depth; cout << "VirtualGames: depth: " << virtual_depth << "\t\t(counter: " << counter << ")" << endl; } if (virtual_depth < virtual_depth_min) { virtual_depth_min = virtual_depth; cout << "VirtualGames: depth: " << virtual_depth << endl; } } // if (INFO_W_VIRTUAL_GAMES) for (unsigned c = 0; c < this->hand().cardsnumber(); c++) { ::ui->ai_test_card(this->hand().card(c), this->vgi().no()); // this->hand() has only valid cards if (INFO_W_VIRTUAL_GAMES) if (!this->vgi().game().isvirtual()) cout << "VirtualGames: calculating card " << this->hand().card(c) << "\t(" << c + 1 << " / " << this->hand().cardsnumber() << ")" << endl; if (INFO_W_VIRTUAL_GAMES) if (!this->vgi().game().isvirtual()) cout << " " << this->hand().card(c) << endl; // create the players vector ai_virt; for (vector::const_iterator ai = this->player_virt_p.begin(); ai != this->player_virt_p.end(); ai++) { ai_virt.push_back(new Ai(*static_cast(*ai))); ai_virt.back()->set_name(this->vgi().game().player((*ai)->no()).name()); } // the copy of the original ai Ai* ai_copy = static_cast(ai_virt[this->vgi().no()]); // create the virtual game Game game_virt(this->vgi().game(), ai_virt); { // play the card 'c' HandCard const card(game_virt.player_current().hand(), this->hand().card(c)); // make swines announcements if ( card.possible_swine() && game_virt.swines_announcement_valid(game_virt.player_current())) { game_virt.swines_announce(game_virt.player_current()); } if ( card.possible_hyperswine() && game_virt.hyperswines_announcement_valid(game_virt.player_current())) game_virt.hyperswines_announce(game_virt.player_current()); // play the card // code copied from 'Game::nextcard()' game_virt.player_current().hand().playcard(card); game_virt.trick_current() += card; game_virt.teaminfo_update(); game_virt.player_current_ = &game_virt.player_following(game_virt.player_current()); for (vector::iterator p = game_virt.players_begin(); p != game_virt.players_end(); p++) { (*p)->card_played(card); } } // play the card 'c' try { // the other virtual players play a card while (!game_virt.trick_current().isfull()) { counter += 1; for (vector::iterator player = game_virt.players_begin(); player != game_virt.players_end(); player++) { (*player)->set_hand(ai_copy->handofplayer(**player)); } // update the cards information for all players CardsInformation cards_information(ai_copy->cards_information()); for (vector::iterator player = game_virt.players_begin(); player != game_virt.players_end(); player++) { cards_information.set_hand(**player, (*player)->hand()); } for (vector::iterator ai = ai_virt.begin(); ai != ai_virt.end(); ai++) { (*ai)->set_hand(cards_information.estimated_hand((*ai)->no())); static_cast(*ai)->set_cards_information(cards_information); } DEBUG_ASSERTION((game_virt.player_current().hand().cardsnumber() > 0), "WVirtualGames::weightings_calc():\n" " current player " << game_virt.player_current().no() << " has an empty hand"); try { #ifdef DKNOF game_virt.player_current().self_check(); #endif game_virt.nextplayer(); } catch (InvalidGameException const& e) { // ToDo: fix cards information // or: remove last played card, // remove it from the hand of the player // and try again cerr << "WVirtualGames: invalid game\n" << " line " << __LINE__ << '\n' << " ignoring card" << endl; throw; } // try } // while (!game_virt.trick_current().isfull()) // first trick is full game_virt.evaluatetrick(); // play tricks till the end depth for (unsigned t = 1; ((t < this->end_depth()) && (game_virt.trick_current_no() < game_virt.trickno())); t++) { if (INFO_W_VIRTUAL_GAMES) if (!this->vgi().game().isvirtual()) cout << " trick depth " << t << " (" << this->end_depth() - 1 << ")" << endl; // set the future limit of the ai, so that they don't calculate too much for (vector::iterator ai = ai_virt.begin(); ai != ai_virt.end(); ai++) static_cast(*ai)->set_last_trick_to_calculate(game_virt.trick_current_no() + (this->end_depth() - t)); // start a new trick game_virt.tricks().push_back(new Trick(game_virt.player_current())); for (vector::iterator p = game_virt.players().begin(); p != game_virt.players().end(); p++) { (*p)->trick_open(game_virt.trick_current()); } while(!game_virt.trick_current().isfull()) { counter += 1; for (vector::iterator player = game_virt.players_begin(); player != game_virt.players_end(); player++) (*player)->set_hand(ai_copy->handofplayer(**player)); // update the cards information for all players CardsInformation cards_information(ai_copy->cards_information()); for (vector::iterator player = game_virt.players_begin(); player != game_virt.players_end(); player++) cards_information.set_hand(**player, (*player)->hand()); for (vector::iterator ai = ai_virt.begin(); ai != ai_virt.end(); ai++) static_cast(*ai)->set_cards_information(cards_information); try { game_virt.nextplayer(); } catch (InvalidGameException const& e) { // ToDo: fix cards information // or: remove last played card, // remove it from the hand of the player // and try again cerr << "WVirtualGames: invalid game\n" << " line " << __LINE__ << '\n' << "ignoring card\n" << endl; throw; } // try } game_virt.evaluatetrick(); } // for (t < this->end_depth()) // finished with creating tricks, // now calc the modi for each trick for (unsigned t = this->vgi().game().trick_current_no(); t < game_virt.trick_current_no(); t++) { // add the modi to the weighting this->weighting_p[c] += TrickWeighting::modi(this->vgi(), game_virt.trick(t), ai_copy->team(), game_virt.trick(t).card_of_player(*ai_copy) ); } // for (t < trick_current_no()) if (game_virt.trick_current_no() < game_virt.trickno() - 1) { // add some points, if the own team is in the back, this->weighting_p[c] += TrickWeighting::backhand(this->vgi(), this->hand().card(c), game_virt ); } // if (game_virt.trick_current_no() < game_virt.trickno() - 1) // game finished, the main reason is the final result... if( game_virt.tricks_remaining_no() == 0 ) { game_virt.finish(); GameSummary const game_summary(game_virt); // the correspoding virtual player to the ai if (game_summary.winnerteam() == TEAM::NOTEAM) { this->weighting_p[c] += 10000 * (game_summary.points(ai_copy->team()) - game_summary.points(opposite(ai_copy->team()))); } else { // if !(game_summary.winnerteam() == TEAM::NOTEAM) if ( game_summary.winnerteam() == ai_copy->team()) this->weighting_p[c] += 10000 * game_summary.points(); else this->weighting_p[c] -= 10000 * game_summary.points(); } // if !(game_summary.winnerteam() == TEAM::NOTEAM) } #if 0 // output of the modi if (this->vgi().game() == ::party.game()) { cout << this->hand().card(c) << ": " << this->weighting_p[c] << endl; } // if (this->vgi().game() == ::party.gam()) #endif // output of the modi } catch (InvalidGameException const& e) { // if the game is invalid, don't take the card this->weighting_p[c] = INT_MIN; #ifndef RELEASE if (!this->vgi().game().isvirtual()) { #ifdef DEBUG_ASSERT #if 0 cerr << "cards information\n"; //cerr << static_cast(game_virt.player_current()).cards_information() << endl; cerr << '\n'; cerr << "hands\n"; for (vector::const_iterator p = game_virt.players_begin(); p != game_virt.players_end(); ++p) cerr << (*p)->name() << '\n' << (*p)->hand(); cerr << '\n'; cerr << "previous trick = " << game_virt.trick_current_no() - 1 << endl; //cerr << game_virt.trick(game_virt.trick_current_no() - 1); cerr << '\n'; cerr << "current trick = " << game_virt.trick_current_no() << endl; cerr << game_virt.trick_current(); cerr << '\n'; cerr << "Hand:\n"; //cerr << game_virt.player_current().hand(); cerr << '\n'; cerr << "last heuristic: " << static_cast(game_virt.player_current()).lastHeuristic_ << endl; cerr << '\n'; cerr << __FILE__ << " line " << __LINE__ << '\n'; cerr << "no valid game found for card '" << this->hand().card(c) << "'" << endl; #endif #endif #if 0 // ??? // 2006-12-03 DK ExceptionTerminateHandler::terminate(); #endif } DEBUG_ASSERTION(this->vgi().game().isvirtual(), "WVirtualGames::weightings_calc():\n" " no valid game found for card '" << this->hand().card(c) << "'"); #endif DEBUG_CAUGHT(); } catch (...) { // free the memory for (vector::iterator ai = ai_virt.begin(); ai != ai_virt.end(); ai++) delete *ai; throw; } // try ::ui->ai_card_weighting(this->weighting_p[c]); // free the memory for (vector::iterator ai = ai_virt.begin(); ai != ai_virt.end(); ai++) delete *ai; if (INFO_W_VIRTUAL_GAMES) if (!this->vgi().game().isvirtual()) cout << "weighting " << this->hand().card(c) << ": " << this->weighting_p[c] << "\n"; } // for (c < this->hand().cardsnumber()) if (INFO_W_VIRTUAL_GAMES) if (!this->vgi().game().isvirtual()) cout << "VirtualGames: counter " << counter << " (" << this->future_limit() << ")" << endl; #ifdef POSTPHONED DEBUG_ASSERTION(((this->end_depth() == 1) || (counter <= this->future_limit())), "WVirtualGames::weightings_calc():\n" " counter is greater than the future limit:\n" " " << counter << " > " << this->future_limit() << "\t\t(end_depth: " << this->end_depth() << ")"); #endif #ifdef SAVE_RUNTIME if (!this->vgi().game().isvirtual()) 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 >= 2000) #endif // #ifdef SAVE_RUNTIME virtual_depth -= 1; DEBUG_RETURNING(VOID, INFO_W_VIRTUAL_GAMES && INFO_OTHER_FUNCTION, "WVirtualGames::weightings_calc()"); } // void WVirtualGames::weightings_calc()