File indexing completed on 2023-09-24 08:17:45

0001 /*
0002     This file is part of the KDE games lskat program
0003     SPDX-FileCopyrightText: 2006 Martin Heni <kde@heni-online.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "aiinput.h"
0009 
0010 // Qt includes
0011 #include <QTimer>
0012 
0013 // KF includes
0014 
0015 // Local includes
0016 #include "lskatglobal.h"
0017 #include "player.h"
0018 
0019 // Constructor for the engine
0020 AiInput::AiInput(EngineTwo *engine, QObject *parent)
0021     : AbstractInput(parent)
0022 {
0023     // Store engine
0024     mEngine = engine;
0025 }
0026 
0027 // Allow or disallow input with this device
0028 void AiInput::setInputAllowed(bool allowed)
0029 {
0030     AbstractInput::setInputAllowed(allowed);
0031     if (allowed) QTimer::singleShot(1000, this, &AiInput::aiTurn);
0032 }
0033 
0034 // Calculate and send out AI turn
0035 void AiInput::aiTurn()
0036 {
0037     // Turn was stopped meanwhile
0038     if (!mInputAllowed) return;
0039 
0040     if (global_debug > 5) qCDebug(LSKAT_LOG) << "===================================================";
0041     if (global_debug > 5) qCDebug(LSKAT_LOG) << "AI TURN START " << mInputAllowed;
0042 
0043     // Check we are the right player
0044     if (mId != mEngine->currentPlayer())
0045     {
0046         qCCritical(LSKAT_LOG) << "AI plays for wrong player";
0047         return;
0048     }
0049 
0050     // Retrieve game board
0051     AiInput::Board board = getBoardFromEngine();
0052     AiInput::Move move;
0053 
0054     if (global_debug > 0) qCDebug(LSKAT_LOG) << QLatin1String("");
0055 
0056     // Initiate move
0057     if (mEngine->currentMovePhase() == EngineTwo::FirstPlayerTurn)
0058     {
0059         if (global_debug > 5) qCDebug(LSKAT_LOG) << "Performing initial move " << mId;
0060         move = initiateMove(mId, board);
0061     }
0062     // Respond to move
0063     else
0064     {
0065         if (global_debug > 5) qCDebug(LSKAT_LOG) << "Performing answer move " << mId;
0066         move = answerMove(mId, board);
0067     }
0068 
0069     // Send out move
0070     if (global_debug > 5) qCDebug(LSKAT_LOG) << "AI player " << mId << " moves to " << move.move;
0071     if (move.move >= 0) Q_EMIT signalPlayerInput(mId, mId, move.move);
0072     else qCCritical(LSKAT_LOG) << "Illegal AI Move???";
0073 }
0074 
0075 // Extract the current game board from the engine
0076 AiInput::Board AiInput::getBoardFromEngine()
0077 {
0078     Board b;
0079     int cnt = 0;
0080     // Reset array
0081     for (int i = 0; i < 32; i++)
0082     {
0083         b.playedCards[i] = -1;
0084     }
0085 
0086     for (int i = 0; i < 2; i++)
0087     {
0088         Player *p = mEngine->player(i);
0089         for (int c = 0; c < 16; c++)
0090         {
0091             int card      = p->getCard(c);
0092             b.cards[i][c] = card;
0093             b.points[i]   = p->points();
0094         }
0095         for (int c = 0; c < p->noOfMovesWon() * 2; c++)
0096         {
0097             int card = p->getWonCard(c);
0098             b.playedCards[cnt] = card;
0099             cnt++;
0100         }
0101     }
0102 
0103     b.whoseTurn  = mEngine->currentPlayer();
0104     b.firstPlay  = mEngine->currentMovePhase() == EngineTwo::FirstPlayerTurn;
0105     b.playedCard = mEngine->playedCard(0);
0106     b.trump      = mEngine->trump();
0107     b.analyze();
0108     return b;
0109 }
0110 
0111 // Game evaluation ratings
0112 // Point ratings
0113 const double RATING_SCHWARZ             = 100000.0;
0114 const double RATING_SCHNEIDER           =  70000.0;
0115 const double RATING_WON                 =  50000.0;
0116 const double RATING_REMIS               =  20000.0;
0117 const double RATING_ONE_POINT           =    500.0;
0118 // Tactical ratings
0119 const double RATING_AMOUNT_TRUMPCARD    = 3.0 * RATING_ONE_POINT;
0120 const double RATING_AMOUNT_OPENCARD     = 1.5 * RATING_ONE_POINT;
0121 const double RATING_AMOUNT_ACES         = 3.0 * RATING_ONE_POINT;
0122 const double RATING_AMOUNT_TENS         = 1.0 * RATING_ONE_POINT;
0123 const double RATING_AMOUNT_JACKS        = 8.5 * RATING_ONE_POINT;
0124 
0125 const double RATING_JACK_OF_CLUBS       = 0.8 * RATING_ONE_POINT;
0126 const double RATING_JACK_OF_SPADE       = 0.6 * RATING_ONE_POINT;
0127 const double RATING_JACK_OF_HEART       = 0.4 * RATING_ONE_POINT;
0128 const double RATING_JACK_OF_DIAMOND     = 0.2 * RATING_ONE_POINT;
0129 
0130 // Evaluate the current game board and return a rating
0131 double AiInput::evaluteGame(int p, const AiInput::Board &current)
0132 {
0133     double rating = 0.0;
0134 
0135     // ===== Points evaluation =====
0136     // Check for won games of our side
0137     if (current.points[p] == 120) rating += RATING_SCHWARZ;
0138     else if (current.points[p] >= 90) rating += RATING_SCHNEIDER;
0139     else if (current.points[p] > 60) rating += RATING_WON;
0140     else if (current.points[p] == 60) rating += RATING_REMIS;
0141 
0142     // Check for won games of other side
0143     if (current.points[1 - p] == 120) rating -= RATING_SCHWARZ;
0144     else if (current.points[1 - p] >= 90) rating -= RATING_SCHNEIDER;
0145     else if (current.points[1 - p] > 60) rating -= RATING_WON;
0146     else if (current.points[1 - p] == 60) rating -= RATING_REMIS;
0147 
0148     // Evaluate points
0149     rating += (current.points[p] - current.points[1 - p]) * RATING_ONE_POINT;
0150 
0151     // ===== Tactical evaluation =====
0152     // Number of trumps (Stored under index Grand)
0153     int trump1 = current.amountOfSuite[p][int(Grand)];
0154     int trump2 = current.amountOfSuite[1 - p][int(Grand)];
0155     rating += (trump1 - trump2) * RATING_AMOUNT_TRUMPCARD;
0156     // Increase value of trumps for Grand
0157     if (current.trump == Grand) rating += (trump1 - trump2) * RATING_AMOUNT_TRUMPCARD;
0158 
0159     // Missing suites
0160     //if (current.amountOfSuite[p][int(Club)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
0161     //if (current.amountOfSuite[p][int(Spade)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
0162     //if (current.amountOfSuite[p][int(Heart)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
0163     //if (current.amountOfSuite[p][int(Diamond)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
0164 
0165     //if (current.amountOfSuite[1 - p][int(Club)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
0166     //if (current.amountOfSuite[1 - p][int(Spade)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
0167     //if (current.amountOfSuite[1 - p][int(Heart)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
0168     //if (current.amountOfSuite[1 - p][int(Diamond)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
0169 
0170     // Number of open cards
0171     int amount1 = amountOfOpenCards(p, current);
0172     int amount2 = amountOfOpenCards(1 - p, current);
0173     rating += (amount1 - amount2) * RATING_AMOUNT_OPENCARD;
0174 
0175     // Card types
0176     // Aces
0177     int amountAce1 = current.amountOfCardType[p][int(Ace)];
0178     int amountAce2 = current.amountOfCardType[1 - p][int(Ace)];
0179     rating += (amountAce1 - amountAce2) * RATING_AMOUNT_ACES;
0180 
0181     // Tens
0182     int amountTen1 = current.amountOfCardType[p][int(Ten)];
0183     int amountTen2 = current.amountOfCardType[1 - p][int(Ten)];
0184     rating += (amountTen1 - amountTen2) * RATING_AMOUNT_TENS;
0185 
0186     // Jacks
0187     int amountJack1 = current.amountOfCardType[p][int(Jack)];
0188     int amountJack2 = current.amountOfCardType[1 - p][int(Jack)];
0189     rating += (amountJack1 - amountJack2) * RATING_AMOUNT_JACKS;
0190     //qCDebug(LSKAT_LOG) << "    Add rating(p=" << p << ") for jacks j1=" << amountJack1 <<" j2=" << amountJack2 << " has JC=" << findCard(current, Club, Jack);
0191 
0192     if (findCard(current, Club, Jack) == p)    rating += RATING_JACK_OF_CLUBS;
0193     if (findCard(current, Spade, Jack) == p)   rating += RATING_JACK_OF_SPADE;
0194     if (findCard(current, Heart, Jack) == p)   rating += RATING_JACK_OF_HEART;
0195     if (findCard(current, Diamond, Jack) == p) rating += RATING_JACK_OF_DIAMOND;
0196 
0197     if (findCard(current, Club, Jack) == 1 - p)    rating -= RATING_JACK_OF_CLUBS;
0198     if (findCard(current, Spade, Jack) == 1 - p)   rating -= RATING_JACK_OF_SPADE;
0199     if (findCard(current, Heart, Jack) == 1 - p)   rating -= RATING_JACK_OF_HEART;
0200     if (findCard(current, Diamond, Jack) == 1 - p) rating -= RATING_JACK_OF_DIAMOND;
0201     //qCDebug(LSKAT_LOG) << "    Rating after jacks " << rating;
0202 
0203     return rating;
0204 }
0205 
0206 // Initiate a new move as first player
0207 AiInput::Move AiInput::initiateMove(int p, const AiInput::Board &board)
0208 {
0209     AiInput::Move maxMove;
0210     maxMove.move  = -1;
0211     maxMove.value = -100.0 * RATING_SCHWARZ; // Absolute minimum score
0212 
0213     // Loop all moves
0214     for (int m = 0; m < 8; m++)
0215     {
0216         AiInput::Board current(board);
0217         int card = current.takeCard(p, m);
0218         if (card < 0) continue; // Illegal move
0219         // Store move
0220         current.playedCard = card;
0221         current.whoseTurn = 1 - p;
0222         if (global_debug > 5) qCDebug(LSKAT_LOG) << "***** First mover try move on " << m << " (" << Deck::name(card) << ")";
0223         AiInput::Move answer = answerMove(1 - p, current);
0224         // Negate answering moves value to get our rating
0225         double rating = -answer.value;
0226         if (global_debug > 5) qCDebug(LSKAT_LOG) << "First mover yields rating of " << rating;
0227 
0228         rating += rulebaseFirstMover(p, card, board);
0229         if (global_debug > 5) qCDebug(LSKAT_LOG) << "  rulesbase correction to " << rating;
0230 
0231         // New best move?
0232         if (rating > maxMove.value)
0233         {
0234             maxMove.value = rating;
0235             maxMove.move  = m;
0236         }
0237     }
0238 
0239     return maxMove;
0240 }
0241 
0242 // Answer a move as second player
0243 AiInput::Move AiInput::answerMove(int p, const AiInput::Board &board)
0244 {
0245     AiInput::Move maxMove;
0246     maxMove.move  = -1;
0247     maxMove.value = -100.0 * RATING_SCHWARZ; // Absolute minimum score
0248 
0249     // Loop all moves
0250     for (int m = 0; m < 8; m++)
0251     {
0252         AiInput::Board current(board);
0253     // qCDebug(LSKAT_LOG) << "CARD " << m << " is "
0254         //          << Deck::name(current.cards[p][m]) << " on top of "
0255         //          << Deck::name(current.cards[p][m + 8]);
0256 
0257         int card = current.takeCard(p, m);
0258         if (card < 0) continue; // Illegal card
0259 
0260         // Check validity of move
0261         if (!isLegalMove(current.playedCard, card, p, current)) continue;
0262 
0263         // Check move winner
0264         int winner = EngineTwo::whoWonMove(current.playedCard, card, current.trump);
0265         if (global_debug > 5)
0266         qCDebug(LSKAT_LOG) << "   Card" << m << " (" << Deck::name(card) << ") is valid "
0267                     << "countering" << Deck::name(current.playedCard) << " with "
0268                     << "winner (0:other, 1:we) " << winner;
0269         int deltaPoints = 0;
0270         deltaPoints += Deck::getCardValue(current.playedCard);
0271         deltaPoints += Deck::getCardValue(card);
0272         // The first mover won
0273         if (winner == 0)
0274         {
0275             current.points[1 - p] += deltaPoints;
0276         }
0277         // The second mover won (us)
0278         else
0279         {
0280             current.points[p] += deltaPoints;
0281         }
0282 
0283         double rating = evaluteGame(p, current);
0284         rating += rulebaseAnswerMover(p, card, board);
0285 
0286         if (global_debug > 5)
0287         qCDebug(LSKAT_LOG) << "   Points after 2nd move " << m << " would be we: "
0288                     << current.points[p] << " other: " << current.points[1 - p]
0289                     << " rating is thus " << rating;
0290         // New best move?
0291         if (rating > maxMove.value)
0292         {
0293             maxMove.value = rating;
0294             maxMove.move  = m;
0295         }
0296     }
0297     return maxMove;
0298 }
0299 
0300 // Board copy constructor
0301 AiInput::Board::Board(const AiInput::Board &board)
0302 {
0303     for (int i = 0; i < 2; i++)
0304     {
0305         points[i] = board.points[i];
0306         for (int j = 0; j < 16; j++)
0307         {
0308             cards[i][j] = board.cards[i][j];
0309         }
0310         for (int j = 0; j < 5; j++)
0311         {
0312             amountOfSuite[i][j] = board.amountOfSuite[i][j];
0313         }
0314         for (int j = 0; j < 8; j++)
0315         {
0316             amountOfCardType[i][j] = board.amountOfCardType[i][j];
0317         }
0318     }
0319     for (int i = 0; i < 32; i++)
0320     {
0321         playedCards[i] = board.playedCards[i];
0322     }
0323     playedCard = board.playedCard;
0324     whoseTurn  = board.whoseTurn;
0325     firstPlay  = board.firstPlay;
0326     trump      = board.trump;
0327 }
0328 
0329 // Retrieve card at given position for given player
0330 int AiInput::Board::card(int p, int pos) const
0331 {
0332     int card = cards[p][pos];  // 1st card
0333     if (card < 0)
0334     {
0335         card = cards[p][pos + 8];  // 2nd card
0336     }
0337     return card;
0338 }
0339 
0340 // Check amount of cards at position,
0341 // i.e. card on top (2), bottom(1), or none at all (0)
0342 int AiInput::Board::cardsAtPos(int p, int pos) const
0343 {
0344     int card = cards[p][pos];  // 1st card
0345     if (card >= 0) return 2;
0346     card = cards[p][pos + 8];  // 2nd card
0347     if (card >= 0) return 1;
0348     return 0;
0349 }
0350 
0351 // Retrieve card at given position for given player and
0352 // reset it (take it away)
0353 int AiInput::Board::takeCard(int p, int pos)
0354 {
0355     int card = cards[p][pos];  // 1st card
0356     cards[p][pos] = -1;        // Can do in any case
0357     if (card < 0)
0358     {
0359         card = cards[p][pos + 8];  // 2nd card
0360         cards[p][pos + 8] = -1;    // Can do in any case
0361     }
0362     else
0363     {
0364         cards[p][pos + 8] = -cards[p][pos + 8]; // Do not look underneath card
0365     }
0366     analyze();
0367     return card;
0368 }
0369 
0370 // Perform pre board analysis
0371 // + Count how many of each color
0372 void AiInput::Board::analyze()
0373 {
0374     // How many cards of given suite
0375     for (int pl = 0; pl < 2; pl++)
0376     {
0377         for (int i = 0; i < 5; i++) amountOfSuite[pl][i] = 0;
0378         for (int i = 0; i < 8; i++) amountOfCardType[pl][i] = 0;
0379         for (int i = 0; i < 8; i++)
0380         {
0381             int c         = card(pl, i);
0382             if (c < 0) continue;
0383             Suite suite   = Deck::getSuite(c);
0384             CardType type = Deck::getCardType(c);
0385             if (suite == trump || type == Jack) amountOfSuite[pl][Grand]++;
0386             else amountOfSuite[pl][int(suite)]++;
0387             amountOfCardType[pl][int(type)]++;
0388         }
0389     }
0390 }
0391 
0392 // Internal return values for find card
0393 const int CARD_CURRENTLY_PLAYED  = 2;
0394 const int CARD_PLAYED            = 3;
0395 
0396 // Check where a card is:
0397 // -1: unknown (still covered), 0: player 0, 1: player 1: 2: currently played, 2: already played
0398 int AiInput::findCard(const AiInput::Board &current, Suite sSuite, CardType sCardType) const
0399 {
0400     // Loop player's cards
0401     for (int p = 0; p < 2; p++)
0402     {
0403         for (int i = 0; i < 8; i++)
0404         {
0405             int card      = current.card(p, i);
0406             if (card < 0) continue;
0407             Suite suite   = Deck::getSuite(card);
0408             CardType type = Deck::getCardType(card);
0409             if (suite == sSuite && type == sCardType)
0410             {
0411                 //qCDebug(LSKAT_LOG) << "Found" << Deck::name(sSuite, sCardType) << " at " << p << "," << i;
0412                 return p;
0413             }
0414         }
0415     }
0416 
0417     // Currently played card if any
0418     int card = current.playedCard;
0419     if (card >= 0)
0420     {
0421         Suite suite   = Deck::getSuite(card);
0422         CardType type = Deck::getCardType(card);
0423         if (suite == sSuite && type == sCardType)
0424         {
0425             //qCDebug(LSKAT_LOG) << "Found" << Deck::name(sSuite, sCardType) << " as currently played card";
0426             return CARD_CURRENTLY_PLAYED;
0427         }
0428     }
0429 
0430     // Already played cards
0431     for (int i = 0; i < 32; i++)
0432     {
0433         int card      = current.playedCards[i];
0434         if (card < 0) continue;
0435         Suite suite   = Deck::getSuite(card);
0436         CardType type = Deck::getCardType(card);
0437         if (suite == sSuite && type == sCardType)
0438         {
0439             //qCDebug(LSKAT_LOG) << "Found " << Deck::name(sSuite, sCardType) << " as played card " << i;
0440             return CARD_PLAYED;
0441         }
0442     }
0443 
0444     return -1;
0445 }
0446 
0447 // How many open cards has the given player
0448 int AiInput::amountOfOpenCards(int p, const AiInput::Board &current) const
0449 {
0450     int cnt = 0;
0451     for (int i = 0; i < 8; i++)
0452     {
0453         if (current.cardsAtPos(p, i) > 0) cnt++;
0454     }
0455     return cnt;
0456 }
0457 
0458 int AiInput::amountOfWinningCards(int p, Suite sSuite, const AiInput::Board &current) const
0459 {
0460     int cnt = 0;
0461     for (int i = 0; i < 8; i++)
0462     {
0463         int card      = current.card(p, i);
0464         if (card < 0) continue;
0465         Suite suite   = Deck::getSuite(card);
0466         CardType type = Deck::getCardType(card);
0467         // Treat jacks as trump
0468         if (type == Jack) suite = Grand;
0469         // Check only right suite or trump cards
0470         if (sSuite != suite) continue;
0471 
0472         // Would we win this card?
0473         if (wouldWinMove(p, card, current)) cnt++;
0474     }
0475     return cnt;
0476 }
0477 
0478 // Check whether the given card would win a initial move
0479 bool AiInput::wouldWinMove(int p, int card, const AiInput::Board &current) const
0480 {
0481     Suite suite   = Deck::getSuite(card);
0482     CardType type = Deck::getCardType(card);
0483     if (type == Jack) suite = current.trump;
0484 
0485     // Check only right suite or trump cards
0486     if (suite != current.trump &&
0487         current.amountOfSuite[1 - p][suite] == 0 &&
0488         current.amountOfSuite[1 - p][Grand] > 0)
0489     {
0490         //qCDebug(LSKAT_LOG) << "Player" << (1 - p) << "can use trump against" << Deck::name(suite);
0491         return false;
0492     }
0493 
0494     // Other player has suite..check cards
0495     for (int j = 0; j < 8; j++)
0496     {
0497         int ocard      = current.card(1 - p, j);
0498         if (ocard < 0) continue;
0499         Suite osuite   = Deck::getSuite(ocard);
0500         CardType otype = Deck::getCardType(ocard);
0501         if (otype == Jack) osuite = current.trump;
0502 
0503         // Check only right suite or trump cards
0504         if (suite == osuite)
0505         {
0506             // 0 if card wins ocards, 1 otherwise
0507             int owinner = EngineTwo::whoWonMove(card, ocard, current.trump);
0508             if (owinner == 1)
0509             {
0510                 //qCDebug(LSKAT_LOG) << "Player " << p << " looses " << Deck::name(card);
0511                 return false;
0512             }
0513         }
0514     }
0515     //qCDebug(LSKAT_LOG) << "Player " << p << " wins " << Deck::name(card);
0516     return true;
0517 }
0518 
0519 // Game evaluation ratings
0520 
0521 // Try to save a free ten
0522 const double RULE_FREE_TEN        =  20.0 * RATING_ONE_POINT;
0523 // Catch a free ten
0524 const double RULE_CATCH_TEN       =  15.0 * RATING_ONE_POINT;
0525 // Use Ace to hunt free tens
0526 const double RULE_HUNTER_ACE      = -20.0 * RATING_ONE_POINT;
0527 // Do not free Ace to possibly hunt free tens
0528 const double RULE_SUPPORT_ACE     = -14.0 * RATING_ONE_POINT;
0529 // Protect possible free ten with minor card
0530 const double RULE_PROTECT_TEN     = -14.0 * RATING_ONE_POINT;
0531 // Protect possible free ten with removing hunter Ace
0532 const double RULE_KILL_HUNTER_ACE =  25.0 * RATING_ONE_POINT;
0533 // Pull trumps
0534 const double RULE_PULL_TRUMP      =  10.0 * RATING_ONE_POINT;
0535 // Save trumps
0536 const double RULE_SAVE_TRUMP      = -10.0 * RATING_ONE_POINT;
0537 // Stay first mover
0538 const double RULE_FIRST_MOVER     =   4.0 * RATING_ONE_POINT;
0539 
0540 // Rule based override over board evaluation to tackle special scenarios
0541 double AiInput::rulebaseFirstMover(int p, int card, const AiInput::Board &current) const
0542 {
0543     double result = 0.0;
0544     Suite suite   = Deck::getSuite(card);
0545     CardType type = Deck::getCardType(card);
0546     Suite altSuite = (suite == current.trump || type == Jack)?Grand:suite;
0547 
0548     // Check whether we win the move
0549     if (wouldWinMove(p, card, current))
0550     {
0551         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Staying first mover " << Deck::name(card);
0552         result += RULE_FIRST_MOVER;
0553     }
0554 
0555     // Protect free ten
0556     if (type == Ten &&
0557         findCard(current, suite, Ace) != CARD_PLAYED &&
0558         findCard(current, suite, Ace) != p &&
0559         wouldWinMove(p, card, current))
0560     {
0561         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Saving Ten " << Deck::name(card);
0562         result += RULE_FREE_TEN;
0563     }
0564 
0565     // Catch free ten
0566     if (type == Ace &&
0567         (1 - p) == findCard(current, suite, Ten) &&
0568         hasAmount(1 - p, altSuite, 1, 1, current) &&
0569         wouldWinMove(p, card, current))
0570     {
0571         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Catching Ten with " << Deck::name(card);
0572         result += RULE_CATCH_TEN;
0573     }
0574 
0575     // Saving Ace to try to catch free ten
0576     if (type == Ace &&
0577         suite != current.trump &&
0578         (1 - p) == findCard(current, suite, Ten) &&
0579         hasAmount(1 - p, suite, 2, 3, current))
0580     {
0581         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Hunting Ten with " << Deck::name(card);
0582         result += RULE_HUNTER_ACE;
0583     }
0584 
0585     // Saving additional cards for hunter Ace
0586     if (suite != current.trump &&
0587         type != Jack &&
0588         type != Ace &&
0589         (1 - p) == findCard(current, suite, Ten) &&
0590         (p) == findCard(current, suite, Ace) &&
0591         hasAmount(1 - p, suite, 2, 3, current))
0592     {
0593         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Supporting Hunter ACE with " << Deck::name(card);
0594         if (hasAmount(1 - p, suite, 2, 2, current)) result += RULE_SUPPORT_ACE;
0595         else result += 0.75 * RULE_SUPPORT_ACE;
0596     }
0597 
0598     // Saving additional cards for hunted Ten
0599     if (suite != current.trump &&
0600         type != Jack &&
0601         type != Ten &&
0602         (1 - p) == findCard(current, suite, Ace) &&
0603         (p) == findCard(current, suite, Ten) &&
0604         hasAmount(p, suite, 2, 3, current) &&
0605         hasAmount(1 - p, suite, 2, 11, current))
0606     {
0607         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Protecting hunted TEN with " << Deck::name(card);
0608         if (hasAmount(p, suite, 2, 2, current)) result += RULE_PROTECT_TEN;
0609         else result += 0.5 * RULE_PROTECT_TEN;
0610     }
0611 
0612     // Killing hunter Ace
0613     if (suite != current.trump &&
0614         type != Jack &&
0615         type != Ten &&
0616         (1 - p) == findCard(current, suite, Ace) &&
0617         (p) == findCard(current, suite, Ten) &&
0618         hasAmount(p, suite, 2, 11, current) &&
0619         hasAmount(1 - p, suite, 1, 1, current))
0620     {
0621         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Killing hunter ACE with " << Deck::name(card);
0622         result += RULE_KILL_HUNTER_ACE;
0623     }
0624 
0625     // Pull trumps
0626     if (altSuite == Grand)
0627     {
0628         int trumpWin1 = amountOfWinningCards(p, altSuite, current);
0629         //int trumpWin2 = amountOfWinningCards(1 - p, altSuite, current);
0630         int trump1    = current.amountOfSuite[p][int(altSuite)];
0631         int trump2    = current.amountOfSuite[1 - p][int(altSuite)];
0632         // Pull trump
0633         if (trumpWin1 >= trump2 &&
0634             trump1 > trump2 &&
0635             trump2 > 0 &&
0636             wouldWinMove(p, card, current))
0637         {
0638             if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Pull trump " << Deck::name(card);
0639             result += RULE_PULL_TRUMP;
0640         }
0641 
0642         // Do not play trump if other party has none
0643         if (trump2 == 0)
0644         {
0645             if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Save trump " << Deck::name(card);
0646             if (trump1 == 1) result += RULE_SAVE_TRUMP;
0647             else if (trump1 == 2) result += 0.75 * RULE_SAVE_TRUMP;
0648             else result += 0.5 * RULE_SAVE_TRUMP;
0649         }
0650     }
0651 
0652     return result;
0653 }
0654 
0655 // Rule based override over board evaluation to tackle special scenarios
0656 double AiInput::rulebaseAnswerMover(int p, int card, const AiInput::Board &current) const
0657 {
0658     double result = 0.0;
0659     Suite suite   = Deck::getSuite(card);
0660     CardType type = Deck::getCardType(card);
0661     Suite altSuite = (suite == current.trump || type == Jack)?Grand:suite;
0662 
0663     // Check whether we win the move
0664     if (wouldWinMove(p, card, current))
0665     {
0666         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER ANSWER RULE: Becoming first mover" << Deck::name(card);
0667         result += RULE_FIRST_MOVER;
0668     }
0669 
0670     // Saving Ace to try to catch free ten
0671     if (type == Ace &&
0672         suite != current.trump &&
0673         (1 - p) == findCard(current, suite, Ten) &&
0674         hasAmount(1 - p, suite, 1, 2, current) &&
0675         hasAmount(p, suite, 2, 3, current))
0676     {
0677         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER ANSWER RULE: Hunting Ten with" << Deck::name(card);
0678         result += RULE_HUNTER_ACE;
0679     }
0680 
0681     // Saving additional cards for hunter Ace
0682     if (suite != current.trump &&
0683         type != Jack &&
0684         type != Ace &&
0685         (1 - p) == findCard(current, suite, Ten) &&
0686         (p) == findCard(current, suite, Ace) &&
0687         hasAmount(1 - p, suite, 1, 2, current))
0688     {
0689         if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER ANSWER RULE: Supporting Hunter ACE with" << Deck::name(card);
0690         if (hasAmount(1 - p, suite, 1, 1, current)) result += RULE_SUPPORT_ACE;
0691         else result += 0.75 * RULE_SUPPORT_ACE;
0692     }
0693 
0694     return result;
0695 }
0696 
0697 // Check whether the given player has between [min,max] cards of the given suite
0698 bool AiInput::hasAmount(int player, Suite suite, int min, int max, const AiInput::Board &current) const
0699 {
0700     if (current.amountOfSuite[player][int(suite)] >= min &&
0701         current.amountOfSuite[player][int(suite)] <= max)
0702     {
0703         return true;
0704     }
0705     return false;
0706 }
0707 
0708 // Check whether the two cards played are legal, supposed the given player is
0709 // the second one (as the first player always plays a legal card)
0710 bool AiInput::isLegalMove(int card1, int card2, int pl, const AiInput::Board &current) const
0711 {
0712     Suite suite1   = Deck::getSuite(card1);
0713     Suite suite2   = Deck::getSuite(card2);
0714     CardType type1 = Deck::getCardType(card1);
0715     CardType type2 = Deck::getCardType(card2);
0716 
0717     // Force trump colour as Jacks count as Trump
0718     if (type1 == Jack) suite1 = current.trump;
0719     if (type2 == Jack) suite2 = current.trump;
0720 
0721     // Same suite is always OK
0722     if (suite1 == suite2) return true;
0723 
0724     // Search if current player has a card of the same colour but did not
0725     // play it (if it was played we checked already above)
0726     for (int i = 0; i < 8; i++)
0727     {
0728         int card = current.card(pl, i);
0729         // This card is not available anymore
0730         if (card < 0) continue;
0731     //   if (type1 == Jack) qCDebug(LSKAT_LOG) << "Check card 2 " <<Deck::name(card);
0732 
0733         // Ignore played card
0734         if (card == card2) continue;
0735 
0736         // Analyze card
0737         Suite suite   = Deck::getSuite(card);
0738         CardType type = Deck::getCardType(card);
0739 
0740         // Force trump colour as Jacks count as Trump
0741         if (type == Jack) suite = current.trump;
0742 
0743         // Check whether current card matches the first player card
0744         if (suite == suite1)
0745         {
0746             return false;
0747         }
0748     }
0749     // if (type1 == Jack) qCDebug(LSKAT_LOG) << "ALLOWED  ";
0750     return true;
0751 }
0752 
0753 #include "moc_aiinput.cpp"