Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 35 additions & 40 deletions BiddingLogic/BidManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@ namespace BiddingLogic
{
using RelayBidKindFunc = Func<Auction, string, SouthInformation, BidManager.RelayBidKind>;

public enum BidPosibilities
{
CannotBid,
CanInvestigate,
CannotInvestigate
}

public enum ConstructedSouthhandOutcome
{
NotSet,
Expand All @@ -31,6 +24,14 @@ public enum ConstructedSouthhandOutcome
NoMatchFoundNoQueens,
}

public enum ExpectedContract
{
Game,
SmallSlam,
GrandSlam,
}


public class BidManager
{
public enum RelayBidKind
Expand Down Expand Up @@ -104,6 +105,7 @@ public static void SetOptimizationParameters(string json)
private readonly ReverseDictionaries reverseDictionaries = null;
private readonly bool useSingleDummySolver = false;
private readonly bool useSingleDummySolverDuringRelaying = false;
public SuitSelection SuitSelection { get; set; } = SuitSelection.AllSuits;

private readonly RelayBidKindFunc GetRelayBidKindFunc = null;

Expand Down Expand Up @@ -352,39 +354,41 @@ Bid GetRelayBid()

public static Bid GetEndContract(Dictionary<Bid, int> possibleContracts, Bid currentBid)
{
Dictionary<Bid, (int occurrences, BidPosibilities posibility)> enrichedContracts = possibleContracts.ToDictionary(kvp => kvp.Key, kvp => (kvp.Value, GetBidPosibility(kvp.Key, currentBid)));
GroupGameContracts();
var reachableContracts = enrichedContracts.Where(y => y.Value.posibility != BidPosibilities.CannotBid).ToDictionary(x => x.Key, y => y.Value);
var reachableContracts = possibleContracts.Where(y => CanBeBid(y.Key, currentBid));
var reachableContractsGrouped = reachableContracts.GroupBy(x => x.Key.rank < 6 ? GetBestGame(x.Key.suit) : x.Key).ToDictionary(g => g.Key, g => g.Sum(v => v.Value));
var investigatableContracts = reachableContractsGrouped.Where(y => CanBeInvestigated(y.Key, currentBid));

var bid = reachableContracts.Count switch
var bid = reachableContractsGrouped.Count switch
{
0 => Bid.PassBid,
1 => reachableContracts.Single().Key,
_ => reachableContracts.Count(y => y.Value.posibility == BidPosibilities.CanInvestigate) <= 1
? reachableContracts.MaxBy(y => y.Value.occurrences).First().Key
1 => reachableContractsGrouped.Single().Key,
_ => investigatableContracts.GroupBy(x => GetContractType(x.Key)).Count() <= 1
? reachableContractsGrouped.MaxBy(y => y.Value).First().Key
: null,
};

var bidString = bid == null ? "Relay a bit more" : $"Bid: {bid}";
loggerBidding.Info($"{reachableContracts.Count} contracts are possible. " +
loggerBidding.Info($"{reachableContracts.Count()} contracts are possible. " +
$"Reachable contracts: {string.Join(';', reachableContracts.Select(y => y.Key))}. " +
$"Investigatable contracts: {string.Join(';', reachableContracts.Where(y => y.Value.posibility == BidPosibilities.CanInvestigate).Select(y => y.Key))} {bidString}");
$"Investigatable contracts: {string.Join(';', investigatableContracts.Select(y => y.Key))} " +
$"{(bid == null ? "Relay a bit more" : $"Bid: {bid}")}");
loggerBidding.Info("*************************");

return bid;

void GroupGameContracts()
Bid GetBestGame(Suit suit) => reachableContracts.Where(x => x.Key.suit == suit && x.Key.rank < 6).MinBy(x => x.Key).Single().Key;
static bool CanBeBid(Bid contract, Bid currentBid) => contract >= currentBid && contract != currentBid + 1;
static bool CanBeInvestigated(Bid contract, Bid currentBid) => contract != currentBid && contract != currentBid + 3;

static ExpectedContract GetContractType(Bid bid)
{
var bestGames = enrichedContracts.Where(x => x.Key.rank < 6 && x.Value.posibility != BidPosibilities.CannotBid).MinBy(x => x.Key);
if (bestGames.Any())
return bid.rank switch
{
var bestGame = bestGames.Single().Key;
enrichedContracts = enrichedContracts.GroupBy(x => x.Key.rank < 6 ? bestGame : x.Key)
.ToDictionary(g => g.Key, g => (g.Sum(v => v.Value.occurrences),
g.Key == bestGame ? g.Any(v => v.Value.posibility == BidPosibilities.CanInvestigate) ? BidPosibilities.CanInvestigate :
BidPosibilities.CannotInvestigate : g.Single().Value.posibility));
}
6 => ExpectedContract.SmallSlam,
7 => ExpectedContract.GrandSlam,
_ => ExpectedContract.Game,
};
}

}

public RelayBidKind GetRelayBidKindSolver(Auction auction, string northHand, SouthInformation southInformation)
Expand Down Expand Up @@ -415,24 +419,15 @@ private Dictionary<Bid, int> GetPossibleContractsFromAuction(Auction auction, st
var declarers = Enum.GetValues(typeof(Suit)).Cast<Suit>().ToDictionary(suit => suit, suit => auction.GetDeclarerOrNorth(suit));
bool canReuseSolverOutput = biddingState.Fase == Fase.ScanningControls && southInformation.ControlsScanningBidCount > 0;
if (!canReuseSolverOutput || occurrencesForBids == null)
occurrencesForBids = SingleDummySolver.SolveSingleDummy(northHand, southInformation, optimizationParameters.numberOfHandsForSolver, declarers);
occurrencesForBids = SingleDummySolver.SolveSingleDummy(northHand, southInformation, optimizationParameters.numberOfHandsForSolver, declarers, SuitSelection);
loggerBidding.Info($"Occurrences by bid in GetPossibleContractsFromAuction: {JsonConvert.SerializeObject(occurrencesForBids)}");

return occurrencesForBids;
}

private static BidPosibilities GetBidPosibility(Bid contract, Bid currentBid)
{
if (contract < currentBid || contract == currentBid + 1)
return BidPosibilities.CannotBid;
if (contract == currentBid || contract == currentBid + 3)
return BidPosibilities.CannotInvestigate;
return BidPosibilities.CanInvestigate;
}

private Dictionary<int, double> GetConfidenceTricks(string northHand, SouthInformation southInformation, Dictionary<Suit, Player> declarers)
{
occurrencesForBids = SingleDummySolver.SolveSingleDummy(northHand, southInformation, optimizationParameters.numberOfHandsForSolver, declarers);
occurrencesForBids = SingleDummySolver.SolveSingleDummy(northHand, southInformation, optimizationParameters.numberOfHandsForSolver, declarers, SuitSelection.LongestSuit);
var nrOfHands = occurrencesForBids.Sum(x => x.Value);
var groupedTricked = occurrencesForBids.GroupBy(x => x.Key.rank + 6);
var confidenceTricks = groupedTricked.ToDictionary(bid => bid.Key, bid => (double)100 * bid.Select(x => x.Value).Sum() / nrOfHands);
Expand All @@ -443,7 +438,7 @@ private Bid CalculateEndContract(Auction auction, string northHand, Bid currentB
{
var southInformation = biddingInformation.GetInformationFromAuction(auction, northHand);
var declarers = Enum.GetValues(typeof(Suit)).Cast<Suit>().ToDictionary(suit => suit, suit => auction.GetDeclarerOrNorth(suit));
var tricksForBid = SingleDummySolver.SolveSingleDummy(northHand, southInformation, optimizationParameters.numberOfHandsForSolver, declarers);
var tricksForBid = SingleDummySolver.SolveSingleDummy(northHand, southInformation, optimizationParameters.numberOfHandsForSolver, declarers, SuitSelection);
var possibleTricksForBid = tricksForBid.Where(bid => bid.Key >= currentBid);
if (!possibleTricksForBid.Any())
return Bid.PassBid;
Expand Down Expand Up @@ -497,7 +492,7 @@ public string ConstructSouthHandSafe(string[] hand, Auction auction)
{
constructedSouthhandOutcome = ConstructedSouthhandOutcome.MultipleMatchesFound;
return $"Multiple matches found. Matches: {string.Join('|', constructedSouthHand)}. NorthHand: {northHand}. SouthHand: {southHand}";
}
}

if (constructedSouthHand.First() == Util.HandWithx(southHand))
{
Expand All @@ -507,7 +502,7 @@ public string ConstructSouthHandSafe(string[] hand, Auction auction)
return $"Match is found but queens are wrong : Expected queens: {queens}. SouthHand: {southHand}";

return $"Match is found: {constructedSouthHand.First()}. NorthHand: {northHand}. SouthHand: {southHand}";
}
}
else
{
constructedSouthhandOutcome = ConstructedSouthhandOutcome.IncorrectSouthhand;
Expand Down
11 changes: 11 additions & 0 deletions Common/SuitSelection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Common
{
public enum SuitSelection
{
LongestSuit,
LongestSuitAndNT,
AllSuits,
}
}
6 changes: 6 additions & 0 deletions Common/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ public static (Suit, int) GetLongestSuitShape(string northHand, string southHand
return ((Suit)(3 - longestSuit), maxSuitLength);
}

public static IEnumerable<Suit> GetSuitsWithFitShape(string northHand, string southHandShape)
{
var southHand = string.Join(',', southHandShape.Select(x => new string('x', int.Parse(x.ToString()))));
return GetSuitsWithFit(northHand, southHand);
}

public static IEnumerable<Suit> GetSuitsWithFit(string northHand, string southHand)
{
var suitLengthNS = GetSuitLengthNS(northHand, southHand);
Expand Down
20 changes: 13 additions & 7 deletions Solver/SingleDummySolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private static IEnumerable<string> GetHandsForSolverExactHands(string northHandS
return shufflingDeal.Execute();
}

public static Dictionary<Bid, int> SolveSingleDummy(string northHand, SouthInformation southInformation, int numberOfHands, Dictionary<Suit, Player> declarers)
public static Dictionary<Bid, int> SolveSingleDummy(string northHand, SouthInformation southInformation, int numberOfHands, Dictionary<Suit, Player> declarers, SuitSelection suitSelection)
{
var tricksPerContract = new Dictionary<Bid, int>();
var shufflingDeal = new ShufflingDeal
Expand All @@ -51,22 +51,28 @@ public static Dictionary<Bid, int> SolveSingleDummy(string northHand, SouthInfor
shufflingDeal.South.Shape = shape;

if (southInformation.SpecificControls == null)
ShuffleAndUpdate(shufflingDeal, shape);
ShuffleAndUpdate(shape);
else
foreach (var specificControls in southInformation.SpecificControls)
{
shufflingDeal.South.SpecificControls = specificControls;
ShuffleAndUpdate(shufflingDeal, shape);
ShuffleAndUpdate(shape);
}
}
return tricksPerContract;

void ShuffleAndUpdate(ShufflingDeal shufflingDeal, string shape)
void ShuffleAndUpdate(string shape)
{
var handsForSolver = shufflingDeal.Execute();
// TODO extend for multiple trump suits and for NT
var (suit, length) = Util.GetLongestSuitShape(northHand, shape);
CalculateAndUpdateDictionary(handsForSolver, length >= 8 ? suit : Suit.NoTrump);
var suits = suitSelection switch
{
SuitSelection.LongestSuit => new [] { Util.GetLongestSuitShape(northHand, shape).Item1 },
SuitSelection.LongestSuitAndNT => Util.GetSuitsWithFitShape(northHand, shape),
SuitSelection.AllSuits => Util.GetSuitsWithFitShape(northHand, shape).Append(Suit.NoTrump),
_ => throw new ArgumentException(null, nameof(suitSelection))
};
foreach (var suit in suits)
CalculateAndUpdateDictionary(handsForSolver, suit);
}

void CalculateAndUpdateDictionary(IEnumerable<string> handsForSolver, Suit suit)
Expand Down
27 changes: 23 additions & 4 deletions TosrGui.Test/GetEndContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace TosrGui.Test
{
public class GetEndContractTests
{
public static IEnumerable<object[]> TestCases()
public static IEnumerable<object[]> TestCasesOneSuit()
{
// Two bids
yield return new object[] { null, new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 0 }, { new Bid(6, Suit.Spades), 0 } }, new Bid(3, Suit.NoTrump) };
Expand All @@ -31,7 +31,7 @@ public static IEnumerable<object[]> TestCases()
yield return new object[] { null, new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 1 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 3 } }, new Bid(3, Suit.NoTrump) };
yield return new object[] { null, new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 1 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 3 } }, new Bid(4, Suit.Diamonds) };
yield return new object[] { new Bid(6, Suit.Spades), new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 1 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 4 } }, new Bid(5, Suit.Clubs) };
yield return new object[] { new Bid(5, Suit.Spades), new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 2 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 3 } }, new Bid(5, Suit.Clubs) };
yield return new object[] { new Bid(6, Suit.Spades), new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 2 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 3 } }, new Bid(5, Suit.Clubs) };
yield return new object[] { null, new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 1 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 3 } }, new Bid(4, Suit.NoTrump) };
yield return new object[] { new Bid(6, Suit.Spades), new Dictionary<Bid, int> { { new Bid(4, Suit.Spades), 1 }, { new Bid(5, Suit.Spades), 2 }, { new Bid(6, Suit.Spades), 3 } }, new Bid(5, Suit.NoTrump) };
// NT
Expand All @@ -40,9 +40,28 @@ public static IEnumerable<object[]> TestCases()
yield return new object[] { new Bid(4, Suit.NoTrump), new Dictionary<Bid, int> { { new Bid(4, Suit.NoTrump), 2 }, { new Bid(6, Suit.NoTrump), 1 } }, new Bid(4, Suit.Diamonds) };
}

public static IEnumerable<object[]> TestCasesTwoSuits()
{
var possibleContracts = new Dictionary<Bid, int> {
{ new Bid(4, Suit.Spades), 4 }, { new Bid(6, Suit.Spades), 6 },
{ new Bid(5, Suit.Diamonds), 6 }, { new Bid(6, Suit.Diamonds), 4 },
{ new Bid(3, Suit.NoTrump), 3 }, { new Bid(4, Suit.NoTrump), 4 }, { new Bid(6, Suit.NoTrump), 3 } };
yield return new object[] { null, possibleContracts, new Bid(2, Suit.NoTrump) };
yield return new object[] { new Bid(BidType.pass), possibleContracts, new Bid(7, Suit.NoTrump) };
yield return new object[] { null, possibleContracts, new Bid(4, Suit.Diamonds) };
yield return new object[] { new Bid(6, Suit.Spades), possibleContracts, new Bid(4, Suit.Spades) };
}

[Theory]
[MemberData(nameof(TestCasesOneSuit))]
public void GetEndContractOneSuitTest(Bid expectedBid, Dictionary<Bid, int> possibleContracts, Bid currentBid)
{
Assert.Equal(expectedBid, BidManager.GetEndContract(possibleContracts, currentBid));
}

[Theory]
[MemberData(nameof(TestCases))]
public void GetEndContractTest(Bid expectedBid, Dictionary<Bid, int> possibleContracts, Bid currentBid)
[MemberData(nameof(TestCasesTwoSuits))]
public void GetEndContractTwoSuitsTest(Bid expectedBid, Dictionary<Bid, int> possibleContracts, Bid currentBid)
{
Assert.Equal(expectedBid, BidManager.GetEndContract(possibleContracts, currentBid));
}
Expand Down
2 changes: 1 addition & 1 deletion TosrGui.Test/SolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void ExecuteTest()
Queens = "NYNY"
};

var scores = SingleDummySolver.SolveSingleDummy(northHand, southInformation, 10, declarers);
var scores = SingleDummySolver.SolveSingleDummy(northHand, southInformation, 10, declarers, SuitSelection.LongestSuit);

foreach (var contracts in scores.Keys)
{
Expand Down
2 changes: 1 addition & 1 deletion TosrIntegration.Test/QueensTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void TestAuctionsQueens(string testName, string northHand, string southHa
logger.Info($"Executing testcase {testName}");

_ = Pinvoke.Setup("Tosr.db3");
var bidManager = new BidManager(new BidGenerator(), fasesWithOffset, reverseDictionaries, true, false);
var bidManager = new BidManager(new BidGenerator(), fasesWithOffset, reverseDictionaries, true, false) { SuitSelection = SuitSelection.LongestSuit};
var auction = bidManager.GetAuction(northHand, southHand);
var actualBidsSouth = auction.GetBidsAsString(Player.South);
Assert.Equal(expectedBidsSouth, actualBidsSouth);
Expand Down