/**********************************************************************
 *
 *
 *   FreeDoko a Doppelkopf-Game
 *    
 *   Copyright (C) 2001-2006  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 <pthread.h>
#endif

static unsigned counter = 0;

// whether to save the runtime
//#define SAVE_RUNTIME

#ifdef RELEASE
#undef SAVE_RUNTIME
#endif

// 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<Player*>::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<Player*> 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<Player*>::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())
      clog << "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*> player_virt;
    Ai const& real_ai
      = dynamic_cast<Ai const&>(this->gametree_interface().game().player(this->gametree_interface().no()));
    for (vector<Player*>::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<Aiconfig*>(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<Player*>::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<Player*>::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())
	  clog << *c << ":\t" << modus << endl;

#ifndef RELEASE
      if (!this->gametree_interface().game().isvirtual())
	clog << *c << ": " << modus << endl;
#endif

      // test, whether the card is better
      if ( (modus > modus_best)
	  || ( (modus == modus_best)
	      && (c->less(card_best))
	     )
	 ) {
	modus_best = modus;
	card_best = *c;
      } // if (modus > modus_best)

    } catch (...) {
      // delete the virtual players
      for (vector<Player*>::iterator player = player_virt.begin();
	   player != player_virt.end();
	   player++)
	delete *player;

      throw;
    } // try

    // delete the virtual players
    for (vector<Player*>::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<pair<pthread_t, GametreeData*> > 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<pair<pthread_t, GametreeData*> >::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*>& player_virt = data->players;
    Ai const& real_ai
      = dynamic_cast<Ai const&>(this->gametree_interface().game().player(this->gametree_interface().no()));
    for (vector<Player*>::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<Aiconfig*>(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<Player*>::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<Player*>::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<pthread_t, GametreeData*>(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<pair<pthread_t, GametreeData*> >::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

#ifdef OUTDATED
// 0.7.3 by rated_modus()
/**
 ** Returns the maximal minimum modi.
 ** From players of the same team, the best modi is returned,
 ** for players of the opposite team, the worst modi is returned.
 **
 ** @param      virt_game	the virtual game
 ** @param      tricks_to_calc	how many tricks to be calced
 ** @param      lb		lower bound for the modi
 ** @param      ub		upper bound for the modi
 **
 ** @return     the maximal minimum modi
 **
 ** @author     Diether Knof
 **
 ** @version    0.5.4
 **
 ** @todo	use lower and upper bound for faster calculating
 **/
int
Gametree::maxmin_modi(Game& virt_game, unsigned const tricks_to_calc,
		      int lb, int ub) const
{
  ::ui->update();
  if (::game_status != GAMESTATUS::GAME_PLAY)
    return 0;

  // the correspoding virtual player to the ai
  Ai const& virt_ai
    = dynamic_cast<Ai const&>(virt_game.player(this->gametree_interface().no()));
  // the team of the ai
  Team const ai_team = virt_ai.team();

  if (virt_game.trick_current().isfull()) {
    counter += 1;

    try {
      virt_game.evaluatetrick();
    } catch (...) {
      throw;
    }

    if (tricks_to_calc > 1) {
      virt_game.tricks().push_back(new Trick(virt_game.player_current()));
      for (vector<Player*>::iterator p = virt_game.players().begin();
	   p != virt_game.players().end();
	   p++)
	(*p)->trick_open(virt_game.trick_current());
      int const modi = maxmin_modi(virt_game, tricks_to_calc - 1, lb, ub);
      return modi;
    } else { // if !(tricks_to_calc > 1)
      int modi = 0;

      // 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);

	modi = 1000 * game_summary.points(this->gametree_interface().no());
	if (game_summary.winnerteam() == TEAM::NOTEAM) {
	  modi = 10000 * (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())
	    modi = 10000 * game_summary.points();
	  else
	    modi = -10000 * game_summary.points();
	} // if !(game_summary.winnerteam() == TEAM::NOTEAM)
      } else { // if !(virt_game.tricks_remaining_no() == 0)
	// add the modi for all tricks
	for (unsigned t = this->gametree_interface().game().trick_current_no();
	     t < virt_game.trick_current_no();
	     t++) {

	  modi += TrickWeighting::modi(this->gametree_interface(),
				       virt_game.trick(t),
				       virt_ai.team(),
				       virt_game.trick(t).card_of_player(virt_ai));
	}
	// copied from 'WVirtualGames'
	// add some points, if the own team is in the back,
	if (virt_game.trick_current_no() < virt_game.trickno() - 1) {
	  modi += 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)
      } // if !(virt_game.tricks_remaining_no() == 0)

      return modi;
    } // if (tricks_to_calc > 1)
  } // if (!virt_game.trick_current().isfull())

  // 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)

  if (same_team)
    lb = INT_MIN;
  else
    ub = INT_MAX;


  // Now test all the cards and calc the maxmin modus

  // this ensures, that the modi can be improved
  int maxmin_modi = (same_team ? INT_MIN : INT_MAX);
  HandCard card; // card that is played

  // 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<Ai const&>(this->gametree_interface().game().player(this->gametree_interface().no())).cards_information();
    // nothing to do, because 'maxmin_modi' 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 = dynamic_cast<Ai&>(virt_game.player_current()).card_get();
    if (card) {
      // create a new virtual game
      vector<Player*> player_virt;
      for (vector<Player*>::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<Player*>::iterator p = virt_game_2->players_begin();
	       p != virt_game_2->players_end();
	       p++)
	    (*p)->card_played(card2);

	  int modi;
	  try {
	    ::ui->ai_test_card(card, virt_game.player_current().no());
	    modi = this->maxmin_modi(*virt_game_2, tricks_to_calc, lb, ub);

	    if ((modi == INT_MIN)
		|| (modi == INT_MAX))
	      DEBUG_THROW( InvalidGameException, InvalidGameException() );

	    ::ui->ai_card_weighting(modi);
	  } catch(...) {
	    ::ui->ai_card_weighting(INT_MIN);
	    throw;
	  } // try

	  // delete the created virtual players and virtual game
	  for (vector<Player*>::iterator player = player_virt.begin();
	       player != player_virt.end();
	       player++)
	    delete *player;
	  delete virt_game_2;

	  return modi;
	} catch (...) {
	  // delete the created virtual players and virtual game
	  for (vector<Player*>::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

    } // if (card)

  } // if (this->with_heuristics())

  if (card)
    hand.remove(card);


  // 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)
	|| 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());


  int modi_sum = 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*> player_virt;
      for (vector<Player*>::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<Ai*>(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<Player*>::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<Player*>::const_iterator player
	       = virt_game_2->players_begin();
	       player != virt_game_2->players_end();
	       player++)
	    DEBUG_ASSERTION(((*player)->hand().cardsnumber() >= 1),
			    "Gametree::maxminmodi():\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 modi = this->maxmin_modi(*virt_game_2, tricks_to_calc, lb, ub);
	  ::ui->ai_card_weighting(modi);
	  modi_sum += modi;
	  if ((modi != INT_MIN) && (modi != INT_MAX))
	    // think about this contition ;-)
	    if ((modi > maxmin_modi) == same_team) {
	      maxmin_modi = modi;
	      card = *c;
	      if (same_team) {
		if (modi > lb)
		  lb = modi;
	      } else { // if !(same_team)
		if (modi < ub)
		  ub = modi;
	      } // if !(same_team)
	    } // if (better modus)
	} catch (...) {
	  ::ui->ai_card_weighting(INT_MIN);
	  throw;
	}
      } catch (InvalidGameException const& e) {
	// nothing to do, because 'maxmin_modi' cannot get changed
	DEBUG_CAUGHT();
      } catch (...) {
	// delete the created virtual players and virtual game
	for (vector<Player*>::iterator player = player_virt.begin();
	     player != player_virt.end();
	     player++)
	  delete *player;
	delete virt_game_2;

	cerr << __LINE__ << " " << *c << endl;
	throw;
      } // try

      // delete the created virtual players and virtual game
      for (vector<Player*>::iterator player = player_virt.begin();
	   player != player_virt.end();
	   player++)
	delete *player;
      delete virt_game_2;

      if (same_team) {
	if (maxmin_modi >= ub)
	  break;
      } else { // if !(same_team)
	if (maxmin_modi <= lb)
	  break;
      } // if !(same_team)
    } // for (c \in valid_cards)

    if ((cards_test == CARDS_TESTS::VALID_CARDS)
	&& ((maxmin_modi == INT_MIN)
	    || (maxmin_modi == 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);

  if ((maxmin_modi == INT_MIN)
      || (maxmin_modi == INT_MAX))
    return maxmin_modi;
  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 maxmin_modi + modi_sum / 1000;
} // int VirtualGames::maxmin_modi(Game& virt_game, unsigned tricks_to_calc, int lb, int ub) const
#endif // #ifndef OUTDATED

/**
 ** 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<Ai const&>(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())
    return full_trick_modi(virt_game, tricks_to_calc);

  // 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<Ai const&>(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<Ai&>(virt_game.player_current()).card_get();
    if (card) {
      // create a new virtual game
      vector<Player*> player_virt;
      for (vector<Player*>::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<Player*>::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);

	    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<Player*>::iterator player = player_virt.begin();
	       player != player_virt.end();
	       player++)
	    delete *player;
	  delete virt_game_2;

	  return modus;
	} catch (...) {
	  // delete the created virtual players and virtual game
	  for (vector<Player*>::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;
  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*> player_virt;
      for (vector<Player*>::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<Ai*>(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<Player*>::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<Player*>::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);
	  modi_sum += 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);
	  }
	} 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<Player*>::iterator player = player_virt.begin();
	     player != player_virt.end();
	     player++)
	  delete *player;
	delete virt_game_2;
	delete rating;

	cerr << "Gametree: error: " << __LINE__ << " " << *c << endl;
	throw;
      } // try

      // delete the created virtual players and virtual game
      for (vector<Player*>::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
  if (   ((modus >=  1000000) && (modus != INT_MAX))
      || ((modus <= -1000000) && (modus != INT_MIN))) {
    cerr << "bad modus : " << modus << endl;
    cerr << "rating = " << rating->type() << endl;
    SEGFAULT;
  }
#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 / 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<Ai const&>(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<Player*>::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);
    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 (10000 * (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 (10000 * game_summary.points());
      else
	return -(10000 * game_summary.points());
    } // if !(game_summary.winnerteam() == TEAM::NOTEAM)
  } // if (virt_game.tricks_remaining_no() == 0)

  // some tricks has been calculated, get the modus from them.

  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));
  }
  // copied from 'WVirtualGames'
  // add some points, if the own team is in the back,
  if (virt_game.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)

  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<GametreeData*>(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
