public static ulong OutsMaskDiscounted(ulong player, ulong board, params ulong[] opponents)
{
ulong retval = 0UL, dead = 0UL;
int ncards = Hand.BitCount(player | board);
#if DEBUG
Debug.Assert(Hand.BitCount(player) == 2); // Must have two cards for a legit set of pocket cards
if (ncards != 5 && ncards != 6)
throw new ArgumentException("Outs only make sense after the flop and before the river");
#endif
if (opponents.Length > 0)
{
// Check opportunities to improve against one or more opponents
foreach (ulong opp in opponents)
{
Debug.Assert(Hand.BitCount(opp) == 2); // Must have two cards for a legit set of pocket cards
dead |= opp;
}
uint playerOrigHandVal = Hand.Evaluate(player | board, ncards);
uint playerOrigHandType = Hand.HandType(playerOrigHandVal);
uint playerOrigTopCard = Hand.TopCard(playerOrigHandVal);
foreach (ulong card in Hand.Hands(0UL, dead | board | player, 1))
{
bool bWinFlag = true;
uint playerHandVal = Hand.Evaluate(player | board | card, ncards + 1);
uint playerNewHandType = Hand.HandType(playerHandVal);
uint playerNewTopCard = Hand.TopCard(playerHandVal);
foreach (ulong oppmask in opponents)
{
uint oppHandVal = Hand.Evaluate(oppmask | board | card, ncards + 1);
bWinFlag = oppHandVal < playerHandVal && (playerNewHandType > playerOrigHandType || (playerNewHandType == playerOrigHandType && playerNewTopCard > playerOrigTopCard));
if (!bWinFlag)
break;
}
if (bWinFlag)
retval |= card;
}
}
else
{
// Look at the cards that improve the hand.
uint playerOrigHandVal = Hand.Evaluate(player | board, ncards);
uint playerOrigHandType = Hand.HandType(playerOrigHandVal);
uint playerOrigTopCard = Hand.TopCard(playerOrigHandVal);
uint boardOrigHandVal = Hand.Evaluate(board);
uint boardOrigHandType = Hand.HandType(boardOrigHandVal);
uint boardOrigTopCard = Hand.TopCard(boardOrigHandVal);
// Look at players pocket cards for special cases.
uint playerPocketHandVal = Hand.Evaluate(player);
uint playerPocketHandType = Hand.HandType(playerPocketHandVal);
// Seperate out by suit
uint sc = (uint)((board >> (CLUB_OFFSET)) & 0x1fffUL);
uint sd = (uint)((board >> (DIAMOND_OFFSET)) & 0x1fffUL);
uint sh = (uint)((board >> (HEART_OFFSET)) & 0x1fffUL);
uint ss = (uint)((board >> (SPADE_OFFSET)) & 0x1fffUL);
// Check if board is 3 suited.
bool discountSuitedBoard = ((nBitsTable[sc] > 2) || (nBitsTable[sd] > 2) || (nBitsTable[sh] > 2) || (nBitsTable[ss] > 2));
// Check if board is 3 Conected on the flop. A dangerous board:
// 3 possible straights using 2 pocket cards and a higher chance
// of 2 pair; players often play 2 connected cards which can hit.
int CountContiguous = 0;
int boardCardCount = BitCount(board);
if (boardCardCount == 3)
{
uint bf = CardMask(board, Clubs) | CardMask(board, Diamonds) | CardMask(board, Hearts) | CardMask(board, Spades);
if (BitCount(0x1800 & bf) == 2) CountContiguous++;
if (BitCount(0xc00 & bf) == 2) CountContiguous++;
if (BitCount(0x600 & bf) == 2) CountContiguous++;
if (BitCount(0x300 & bf) == 2) CountContiguous++;
if (BitCount(0x180 & bf) == 2) CountContiguous++;
if (BitCount(0xc0 & bf) == 2) CountContiguous++;
if (BitCount(0x60 & bf) == 2) CountContiguous++;
if (BitCount(0x30 & bf) == 2) CountContiguous++;
if (BitCount(0x18 & bf) == 2) CountContiguous++;
if (BitCount(0xc & bf) == 2) CountContiguous++;
if (BitCount(0x6 & bf) == 2) CountContiguous++;
if (BitCount(0x3 & bf) == 2) CountContiguous++;
if (BitCount(0x1001 & bf) == 2) CountContiguous++;
}
bool discountStraight = (CountContiguous > 1);
// Look ahead one card
foreach (ulong card in Hand.Hands(0UL, dead | board | player, 1))
{
uint boardNewHandval = Hand.Evaluate(board | card);
uint boardNewHandType = Hand.HandType(boardNewHandval);
uint boardNewTopCard = Hand.TopCard(boardNewHandval);
uint playerNewHandVal = Hand.Evaluate(player | board | card, ncards + 1);
uint playerNewHandType = Hand.HandType(playerNewHandVal);
uint playerNewTopCard = Hand.TopCard(playerNewHandVal);
bool playerImproved = (playerNewHandType > playerOrigHandType || (playerNewHandType == playerOrigHandType && playerNewTopCard > playerOrigTopCard) || (playerNewHandType == playerOrigHandType && playerNewHandType == (uint)HandTypes.TwoPair && playerNewHandVal > playerOrigHandVal));
bool playerStrongerThanBoard = (playerNewHandType > boardNewHandType || (playerNewHandType == boardNewHandType && playerNewTopCard > boardNewTopCard));
if (playerImproved && playerStrongerThanBoard)
{
bool isOut = false;
bool discountSuitedOut = false;
if (!discountSuitedBoard)
{
// Get suit of card.
uint cc = (uint)((card >> (CLUB_OFFSET)) & 0x1fffUL);
uint cd = (uint)((card >> (DIAMOND_OFFSET)) & 0x1fffUL);
uint ch = (uint)((card >> (HEART_OFFSET)) & 0x1fffUL);
uint cs = (uint)((card >> (SPADE_OFFSET)) & 0x1fffUL);
// Check if card will make a 3 suited board.
discountSuitedOut = ((nBitsTable[sc] > 1 && nBitsTable[cc] == 1) || (nBitsTable[sd] > 1 && nBitsTable[cd] == 1) || (nBitsTable[sh] > 1 && nBitsTable[ch] == 1) || (nBitsTable[ss] > 1 && nBitsTable[cs] == 1));
}
// Check if board is 4 connected or card + board is 4 connected.
// Dangerous board: straight using 1 pocket card only.
if (boardCardCount == 4)
{
// We need to check for the following:
// 9x,8x,7x,6x (4 in a row)
// 9x,8x,7x,5x (3 in a row with a 1 gap connected card)
// 9x,8x,6x,5x (2 connected with a 1 gap connected in the middle)
CountContiguous = 0;
uint bf = CardMask(board | card, Clubs) | CardMask(board | card, Diamonds) | CardMask(board | card, Hearts) | CardMask(board | card, Spades);
// AxKx
if (BitCount(0x1800 & bf) == 2)
{
CountContiguous++;
}
// KxQx
if (BitCount(0xc00 & bf) == 2)
{
CountContiguous++;
}
else
{
if (CountContiguous == 1 && BitCount(0x300 & bf) == 2)
// 2 connected with a 1 gap connected in the middle
discountStraight = true;
CountContiguous = 0;
}
// QxJx
if (BitCount(0x600 & bf) == 2)
{
CountContiguous++;
}
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x180 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for a T
if (BitCount(0x100 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
}
CountContiguous = 0;
}
// JxTx
if (BitCount(0x300 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0xc0 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 9x
if (BitCount(0x80 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// Tx9x
if (BitCount(0x180 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x60 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 8x or Ax
if (BitCount(0x1040 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 9x8x
if (BitCount(0xc0 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x30 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 7x or Kx
if (BitCount(0x820 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 8x7x
if (BitCount(0x60 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x18 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 6x or Qx
if (BitCount(0x410 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 7x6x
if (BitCount(0x30 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0xc & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 5x or Jx
if (BitCount(0x208 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 6x5x
if (BitCount(0x18 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x6 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 4x or Tx
if (BitCount(0x104 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 5x4x
if (BitCount(0xc & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x3 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 3x or 9x
if (BitCount(0x82 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 4x3x
if (BitCount(0x6 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 1:
if (BitCount(0x1001 & bf) == 2)
// 2 connected with a 1 gap in the middle
discountStraight = true;
break;
case 2:
// test for 2x or 8x
if (BitCount(0x41 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 3x2x
if (BitCount(0x3 & bf) == 2)
CountContiguous++;
else
{
switch (CountContiguous)
{
case 2:
// test for Ax or 7x
if (BitCount(0x1020 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
CountContiguous = 0;
}
// 2xAx
if (BitCount(0x1001 & bf) == 2)
{
CountContiguous++;
// Check one last time.
switch (CountContiguous)
{
case 2:
// test for 5x
if (BitCount(0x8 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
}
else
{
switch (CountContiguous)
{
case 2:
// test for 6x
if (BitCount(0x10 & bf) == 1)
// 3 in a row with a 1 gap connected
discountStraight = true;
break;
case 3: // 4 in a row
discountStraight = true;
break;
}
}
}
// Hand imporving to a pair, must use overcards and not make a 3 suited board.
if (playerNewHandType == (uint)HandTypes.Pair)
{
uint newCardVal = Hand.Evaluate(card);
uint newTopCard = Hand.TopCard(newCardVal);
if (boardOrigTopCard < newTopCard && !(discountSuitedBoard || discountSuitedOut) && !(discountStraight))
isOut = true;
}
// Hand imporving to two pair, must use one of the players pocket cards and
// the player already has a pair, either a pocket pair or a pair using the board.
// ie: not drawing to two pair when trips is out - drawing dead.
// And not make a 3 suited board and not discounting for a straight.
else if (playerNewHandType == (uint)HandTypes.TwoPair)
{
// Special case pair improving to two pair must use one of the players cards.
uint playerPocketHandNewCardVal = Hand.Evaluate(player | card);
uint playerPocketHandNewCardType = Hand.HandType(playerPocketHandNewCardVal);
if ((playerPocketHandNewCardType == (uint)HandTypes.Pair && playerPocketHandType != (uint)HandTypes.Pair) && (boardOrigHandType != (uint)HandTypes.Pair || playerOrigHandType == (uint)HandTypes.TwoPair))
{
if (!(discountSuitedBoard || discountSuitedOut) && !(discountStraight))
isOut = true;
}
}
// New hand better than two pair.
else if (playerNewHandType > (uint)HandTypes.TwoPair)
{
// Hand imporving trips, must not make a 3 suited board and not discounting for a straight.
if (playerNewHandType == (uint)HandTypes.Trips)
{
if (!(discountSuitedBoard || discountSuitedOut) && !(discountStraight))
isOut = true;
}
// Hand imporving to a straight, must not make a 3 suited board.
else if (playerNewHandType == (uint)HandTypes.Straight)
{
if (!(discountSuitedBoard || discountSuitedOut))
isOut = true;
}
else
// No discounting for a Flush (should we consider a straight Flush?),
// Full House, Four of a Kind and Straight Flush.
isOut = true;
}
if (isOut)
retval |= card;
}
}
}
return retval;
}