From d76442512fd4314fcedbdc28a611f676ab70ce2b Mon Sep 17 00:00:00 2001 From: emkael Date: Wed, 25 May 2016 13:28:25 +0200 Subject: * project directory structure --- src/BCalcWrapper.cs | 44 +++++++ src/DDTable.cs | 148 +++++++++++++++++++++ src/PBNBoard.cs | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/PBNFile.cs | 71 ++++++++++ src/ParContract.cs | 223 ++++++++++++++++++++++++++++++++ src/ParScore.cs | 194 ++++++++++++++++++++++++++++ 6 files changed, 1044 insertions(+) create mode 100644 src/BCalcWrapper.cs create mode 100644 src/DDTable.cs create mode 100644 src/PBNBoard.cs create mode 100644 src/PBNFile.cs create mode 100644 src/ParContract.cs create mode 100644 src/ParScore.cs (limited to 'src') diff --git a/src/BCalcWrapper.cs b/src/BCalcWrapper.cs new file mode 100644 index 0000000..d2d0aa2 --- /dev/null +++ b/src/BCalcWrapper.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.InteropServices; + +namespace BCDD +{ + /// + /// Wrapper class for libbcalcDDS.ddl. + /// + class BCalcWrapper + { + public static char[] DENOMINATIONS = { 'C', 'D', 'H', 'S', 'N' }; + public static char[] PLAYERS = { 'N', 'E', 'S', 'W' }; + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a8f522e85482fe383bebd963e873897f5 + [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr bcalcDDS_new(IntPtr format, IntPtr hands, Int32 trump, Int32 leader); + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a369ce661d027bef3f717967e42bf8b33 + [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern Int32 bcalcDDS_getTricksToTake(IntPtr solver); + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a8998a1eb1ca25de2e07448381ce63261 + [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr bcalcDDS_getLastError(IntPtr solver); + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a4a68da83bc7da4663e2257429539912d + [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void bcalcDDS_delete(IntPtr solver); + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a88fba3432e66efa5979bbc9e1f044164 + [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void bcalcDDS_setTrumpAndReset(IntPtr solver, Int32 trump); + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a616031c1e1d856c4aac14390693adb4c + [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void bcalcDDS_setPlayerOnLeadAndReset(IntPtr solver, Int32 player); + + /// http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a6977a3b789bdf64eb2da9cbdb8b8fc39 + public static Int32 bcalc_declarerToLeader(Int32 player) + { + return (player + 1) & 3; + } + } +} diff --git a/src/DDTable.cs b/src/DDTable.cs new file mode 100644 index 0000000..7ba8568 --- /dev/null +++ b/src/DDTable.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace BCDD +{ + class DDTableInvalidException : FieldNotFoundException + { + public DDTableInvalidException() : base() { } + public DDTableInvalidException(String msg) : base(msg) { } + } + + class DDTable + { + private PBNBoard board; + + private int[,] getEmptyTable() + { + int[,] result = new int[4, 5]; + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 5; j++) + { + result[i, j] = -1; + } + } + return result; + } + + private int[,] validateTable(int[,] table) + { + foreach (int t in table) + { + if (t > 13 || t < 0) + { + throw new DDTableInvalidException("Invalid number of tricks: " + t.ToString()); + } + } + return table; + } + + public DDTable(PBNBoard board) + { + this.board = board; + } + + public int[,] GetBCalcTable() + { + int[,] result = this.getEmptyTable(); + String deal = this.board.GetLayout(); + IntPtr solver = BCalcWrapper.bcalcDDS_new(Marshal.StringToHGlobalAnsi("PBN"), Marshal.StringToHGlobalAnsi(deal), 0, 0); + for (int denom = 0; denom < 5; denom++) + { + BCalcWrapper.bcalcDDS_setTrumpAndReset(solver, denom); + for (int player = 0; player < 4; player++) + { + BCalcWrapper.bcalcDDS_setPlayerOnLeadAndReset(solver, BCalcWrapper.bcalc_declarerToLeader(player)); + result[player, denom] = 13 - BCalcWrapper.bcalcDDS_getTricksToTake(solver); + String error = Marshal.PtrToStringAuto(BCalcWrapper.bcalcDDS_getLastError(solver)); + if (error != null) + { + throw new DDTableInvalidException("BCalc error: " + error); + } + } + } + BCalcWrapper.bcalcDDS_delete(solver); + return this.validateTable(result); + } + + public int[,] GetJFRTable() + { + int[,] result = this.getEmptyTable(); + String ability = this.board.GetAbility(); + MatchCollection abilities = this.board.ValidateAbility(ability); + foreach (Match playerAbility in abilities) + { + char player = playerAbility.Groups[1].Value[0]; + int playerID = Array.IndexOf(BCalcWrapper.PLAYERS, player); + int denomID = 4; + foreach (char tricks in playerAbility.Groups[2].Value.ToCharArray()) + { + result[playerID, denomID] = (tricks > '9') ? (tricks - 'A' + 10) : (tricks - '0'); + denomID--; + } + } + return this.validateTable(result); + } + + public int[,] GetPBNTable() + { + List table = this.board.GetOptimumResultTable(); + List parsedTable = this.board.ValidateOptimumResultTable(table); + int[,] result = this.getEmptyTable(); + foreach (Match lineMatch in parsedTable) + { + char player = lineMatch.Groups[1].Value[0]; + char denom = lineMatch.Groups[2].Value[0]; + int tricks = Int16.Parse(lineMatch.Groups[3].Value); + int playerID = Array.IndexOf(BCalcWrapper.PLAYERS, player); + int denomID = Array.IndexOf(BCalcWrapper.DENOMINATIONS, denom); + result[playerID, denomID] = tricks; + } + return this.validateTable(result); + } + + public int[,] GetDDTable() + { + try + { + return this.GetJFRTable(); + } + catch (FieldNotFoundException) + { + try + { + return this.GetPBNTable(); + } + catch (FieldNotFoundException) + { + return this.GetBCalcTable(); + } + } + } + + public static void PrintTable(int[,] ddTable) + { + foreach (char header in BCalcWrapper.DENOMINATIONS) + { + Console.Write('\t'); + Console.Write(header); + } + Console.WriteLine(); + for (int i = 0; i < 4; i++) + { + Console.Write(BCalcWrapper.PLAYERS[i]); + for (int j = 0; j < 5; j++) + { + Console.Write('\t'); + Console.Write(ddTable[i, j].ToString()); + } + Console.WriteLine(); + } + } + } +} diff --git a/src/PBNBoard.cs b/src/PBNBoard.cs new file mode 100644 index 0000000..72f87a4 --- /dev/null +++ b/src/PBNBoard.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace BCDD +{ + class PBNField + { + public String Key; + public String Value; + public String RawField; + + public PBNField() { } + + public PBNField(String key, String value) + { + this.Key = key; + this.Value = value; + this.RawField = String.Format("[{0} \"{1}\"]", this.Key, this.Value); + } + + public PBNField(String rawData) + { + this.RawField = rawData; + } + } + + class FieldNotFoundException : Exception + { + public FieldNotFoundException() : base() { } + public FieldNotFoundException(String msg) : base(msg) { } + } + + class PBNBoard + { + public List Fields; + + private bool? hasOptimumResultTable = null; + private bool? hasAbility = null; + + private static Regex linePattern = new Regex(@"\[(.*) ""(.*)""\]"); + private static Regex abilityPattern = new Regex(@"\b([NESW]):([0-9A-D]{5})\b"); + private static Regex optimumResultTablePattern = new Regex(@"^([NESW])\s+([CDHSN])T?\s+(\d+)$"); + + public PBNBoard(List lines) + { + this.Fields = new List(); + foreach (String line in lines) + { + PBNField field = new PBNField(); + field.RawField = line; + Match lineParse = PBNBoard.linePattern.Match(line); + if (lineParse.Success) + { + field.Key = lineParse.Groups[1].Value; + field.Value = lineParse.Groups[2].Value; + } + this.Fields.Add(field); + } + } + + public bool HasField(String key) + { + foreach (PBNField field in this.Fields) + { + if (key.Equals(field.Key)) + { + return true; + } + } + return false; + } + + public String GetField(String key) + { + foreach (PBNField field in this.Fields) + { + if (key.Equals(field.Key)) + { + return field.Value; + } + } + throw new FieldNotFoundException(key + " field not found"); + } + + public void DeleteField(String key) + { + List toRemove = new List(); + foreach (PBNField field in this.Fields) + { + if (key.Equals(field.Key)) + { + toRemove.Add(field); + } + } + foreach (PBNField remove in toRemove) + { + this.Fields.Remove(remove); + } + } + + public String GetLayout() + { + return this.GetField("Deal"); + } + + public String GetNumber() + { + return this.GetField("Board"); + } + + public String GetVulnerable() + { + return this.GetField("Vulnerable"); + } + + public String GetDealer() + { + return this.GetField("Dealer"); + } + + public MatchCollection ValidateAbility(String ability) + { + MatchCollection matches = PBNBoard.abilityPattern.Matches(ability); + if (matches.Count != 4) + { + this.hasAbility = false; + throw new DDTableInvalidException("Invalid Ability line: " + ability); + } + List players = new List(); + foreach (Match match in matches) + { + if (players.Contains(match.Groups[1].Value)) + { + this.hasAbility = false; + throw new DDTableInvalidException("Duplicate entry in Ability: " + match.Groups[0].Value); + } + else + { + players.Add(match.Groups[1].Value); + } + } + this.hasAbility = true; + return matches; + } + + public String GetAbility() + { + return this.GetField("Ability"); + } + + public void DeleteAbility() + { + this.DeleteField("Ability"); + } + + public void WriteAbility(int[,] ddTable) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 4; i++) + { + sb.Append(BCalcWrapper.PLAYERS[i]); + sb.Append(':'); + for (int j = 4; j >= 0; j--) + { + sb.Append((char)(ddTable[i, j] > 9 ? 'A' + ddTable[i, j] - 10 : ddTable[i, j] + '0')); + } + if (i < 3) + { + sb.Append(' '); + } + } + String abilityStr = sb.ToString(); + this.Fields.Add(new PBNField("Ability", abilityStr)); + } + + public String GetMinimax() + { + return this.GetField("Minimax"); + } + + public void DeleteMinimax() + { + this.DeleteField("Minimax"); + } + + public void WriteMinimax(ParContract contract) + { + this.Fields.Add(new PBNField("Minimax", String.Format("{0}{1}{2}{3}{4}", contract.Level, contract.Denomination, contract.Doubled ? "D" : "", contract.Declarer, contract.Score))); + } + + public String GetOptimumScore() + { + return this.GetField("OptimumScore"); + } + + public void DeleteOptimumScore() + { + this.DeleteField("OptimumScore"); + } + + public void WriteOptimumScore(ParContract contract) + { + this.Fields.Add(new PBNField("OptimumScore", String.Format("NS {0}", contract.Score))); + } + + public String GetOptimumResult() + { + return this.GetField("OptimumResult"); + } + + public List ValidateOptimumResultTable(List table) + { + List matches = new List(); + List duplicates = new List(); + foreach (String line in table) + { + Match match = PBNBoard.optimumResultTablePattern.Match(line); + if (!match.Success) + { + this.hasOptimumResultTable = false; + throw new DDTableInvalidException("Invalid OptimumResultTable line: " + line); + } + String position = match.Groups[1].Value + " - " + match.Groups[2].Value; + if (duplicates.Contains(position)) + { + this.hasOptimumResultTable = false; + throw new DDTableInvalidException("Duplicate OptimumResultTable line: " + line); + } + else + { + duplicates.Add(position); + } + matches.Add(match); + } + this.hasOptimumResultTable = true; + return matches; + } + + public List GetOptimumResultTable() + { + bool fieldFound = false; + List result = new List(); + foreach (PBNField field in this.Fields) + { + if ("OptimumResultTable".Equals(field.Key)) + { + fieldFound = true; + } + else + { + if (fieldFound) + { + if (field.Key == null) + { + result.Add(field.RawField); + } + else + { + break; + } + } + } + } + if (!fieldFound) + { + this.hasOptimumResultTable = false; + throw new FieldNotFoundException("OptimumResultTable field not found"); + } + return result; + } + + public void DeleteOptimumResultTable() + { + bool fieldFound = false; + List toRemove = new List(); + foreach (PBNField field in this.Fields) + { + if ("OptimumResultTable".Equals(field.Key)) + { + fieldFound = true; + toRemove.Add(field); + } + else + { + if (fieldFound) + { + if (field.Key == null) + { + toRemove.Add(field); + } + else + { + break; + } + } + } + } + foreach (PBNField remove in toRemove) + { + this.Fields.Remove(remove); + } + } + + public void WriteOptimumResultTable(int[,] ddTable) + { + this.Fields.Add(new PBNField("OptimumResultTable", @"Declarer;Denomination\2R;Result\2R")); + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 5; j++) + { + this.Fields.Add(new PBNField(String.Format("{0} {1}{2} {3}", BCalcWrapper.PLAYERS[i], BCalcWrapper.DENOMINATIONS[j], (BCalcWrapper.DENOMINATIONS[j] == 'N') ? "T" : "", ddTable[i, j]))); + } + } + } + + public void SaveParContract(ParContract contract) + { + this.DeleteOptimumScore(); + this.WriteOptimumScore(contract); // we're not writing DDS custom fields, just parse them + this.DeleteMinimax(); + this.WriteMinimax(contract); + } + + public void SaveDDTable(int[,] ddTable) + { + if (this.hasOptimumResultTable == null) + { + try + { + List optimumResultTable = this.ValidateOptimumResultTable(this.GetOptimumResultTable()); + this.hasOptimumResultTable = true; + } + catch (FieldNotFoundException) + { + this.hasOptimumResultTable = false; + } + } + if (this.hasOptimumResultTable == false) + { + this.DeleteOptimumResultTable(); + this.WriteOptimumResultTable(ddTable); + } + if (this.hasAbility == null) + { + try + { + MatchCollection ability = this.ValidateAbility(this.GetAbility()); + this.hasAbility = true; + } + catch (FieldNotFoundException) + { + this.hasAbility = false; + } + } + if (this.hasAbility == false) + { + this.DeleteAbility(); + this.WriteAbility(ddTable); + } + } + } +} diff --git a/src/PBNFile.cs b/src/PBNFile.cs new file mode 100644 index 0000000..5526623 --- /dev/null +++ b/src/PBNFile.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace BCDD +{ + class PBNFile + { + public List Boards; + + private String filename; + private String tmpFileName; + + StreamWriter outputFile; + + public PBNFile(String filename) + { + this.filename = filename; + this.Boards = new List(); + String[] contents = File.ReadAllLines(this.filename).Select(l => l.Trim()).ToArray(); + List lines = new List(); + foreach (String line in contents) + { + if (line.Length == 0) + { + if (lines.Count > 0) + { + this.Boards.Add(new PBNBoard(lines)); + lines = new List(); + } + } + else + { + lines.Add(line); + } + } + if (lines.Count > 0) + { + this.Boards.Add(new PBNBoard(lines)); + } + } + + public void WriteBoard(PBNBoard board) + { + if (this.outputFile == null) + { + this.tmpFileName = Path.GetTempFileName(); + this.outputFile = new StreamWriter(new FileStream(this.tmpFileName, FileMode.Create), Encoding.UTF8); + } + foreach (PBNField field in board.Fields) + { + this.outputFile.WriteLine(field.RawField); + } + this.outputFile.WriteLine(); + } + + public void Save() + { + if (this.outputFile == null) + { + throw new IOException("No boards written to PBN file, unable to save it."); + } + this.outputFile.Flush(); + this.outputFile.Close(); + File.Delete(this.filename); + File.Move(this.tmpFileName, this.filename); + } + } +} diff --git a/src/ParContract.cs b/src/ParContract.cs new file mode 100644 index 0000000..9a6c500 --- /dev/null +++ b/src/ParContract.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BCDD +{ + class ParContract + { + public int Level = 0; + public char Denomination; + public char Declarer; + public bool Doubled = false; + public int Score = 0; + + public ParContract() { } + + public ParContract(int level, char denom, char declarer, bool doubled, int score) + { + this.Level = level; + this.Denomination = denom; + this.Declarer = declarer; + this.Doubled = doubled; + this.Score = score; + } + + public ParContract Validate() + { + if (this.Score == 0) + { + return this; + } + if (this.Level < 1 || this.Level > 7) + { + throw new ParScoreInvalidException("Invalid par contract level: " + this.Level.ToString()); + } + if (!"CDHSN".Contains(this.Denomination)) + { + throw new ParScoreInvalidException("Invalid par contract denomination: " + this.Denomination); + } + if (!"NESW".Contains(this.Declarer)) + { + throw new ParScoreInvalidException("Invalid par contract declarer: " + this.Declarer); + } + return this; + } + + override public String ToString() + { + if (this.Score == 0) + { + return "PASS"; + } + String contract = this.Level.ToString() + this.Denomination; + String risk = this.Doubled ? "x" : ""; + String declarer = " " + this.Declarer; + String result = " " + this.Score.ToString("+#;-#;0"); + return contract + risk + declarer + result; + } + + public override bool Equals(object other) + { + ParContract obj = (ParContract)(other); + return this.Level == obj.Level && this.Denomination == obj.Denomination && this.Score == obj.Score; + } + + public override int GetHashCode() + { + return this.Score + this.Level + 10000 * this.Denomination; + } + + public int CalculateScore(int tricks, bool vulnerable = false) + { + if (this.Level == 0) + { + return 0; + } + int score = 0; + if (this.Level + 6 > tricks) + { + int undertricks = this.Level + 6 - tricks; + if (this.Doubled) + { + do + { + if (undertricks == 1) // first undertrick: 100 non-vul, 200 vul + { + score -= vulnerable ? 200 : 100; + } + else + { + if (undertricks <= 3 && !vulnerable) // second non-vul undertrick: 200 + { + score -= 200; + } + else // further undertricks: 300 + { + score -= 300; + } + } + undertricks--; + } + while (undertricks > 0); + } + else + { + score = vulnerable ? -100 : -50; + score *= undertricks; + } + } + else + { + int parTricks = this.Level; + do + { + if (this.Denomination == 'N' && parTricks == 1) // first non-trump trick: 40 + { + score += 40; + } + else // other tricks + { + switch (this.Denomination) + { + case 'N': + case 'S': + case 'H': + score += 30; + break; + case 'D': + case 'C': + score += 20; + break; + } + } + parTricks--; + } + while (parTricks > 0); + if (this.Doubled) + { + score *= 2; + } + score += (score >= 100) ? (vulnerable ? 500 : 300) : 50; // game premium + if (this.Level == 7) // grand slam premium + { + score += vulnerable ? 1500 : 1000; + } + else if (this.Level == 6) // small slam premium + { + score += vulnerable ? 750 : 500; + } + if (this.Doubled) + { + score += 50; + } + int overtricks = tricks - this.Level - 6; + score += this.Doubled + ? (vulnerable ? 200 : 100) * overtricks // (re-)double overtricks: 100/200/200/400 + : overtricks * ((this.Denomination == 'C' || this.Denomination == 'D') ? 20 : 30); // undoubled overtricks + } + if (this.Declarer == 'E' || this.Declarer == 'W') + { + score = -score; + } + return score; + } + + public bool Higher(ParContract obj) + { + return (this.Level > obj.Level + || (this.Level == obj.Level + && Array.IndexOf(BCalcWrapper.DENOMINATIONS, this.Denomination) > Array.IndexOf(BCalcWrapper.DENOMINATIONS, obj.Denomination))); + } + + public ParContract GetDefense(int[,] ddTable, bool vulnerable) + { + if (this.Level != 0) + { + int declarerIndex = Array.IndexOf(BCalcWrapper.PLAYERS, this.Declarer); + List defendersIndexes = new List(); + defendersIndexes.Add((declarerIndex + 1) & 3); + defendersIndexes.Add((declarerIndex + 3) & 3); + List possibleDefense = new List(); + int scoreSquared = this.Score * this.Score; + int denominationIndex = Array.IndexOf(BCalcWrapper.DENOMINATIONS, this.Denomination); + for (int i = 0; i < 5; i++) + { + int level = this.Level; + if (i <= denominationIndex) + { + level++; + } + foreach (int defender in defendersIndexes) + { + if (level + 6 > ddTable[defender, i]) + { + ParContract defense = new ParContract(level, BCalcWrapper.DENOMINATIONS[i], BCalcWrapper.PLAYERS[defender], true, 0); + defense.Score = defense.CalculateScore(ddTable[defender, i], vulnerable); + if (scoreSquared > this.Score * defense.Score) + { + possibleDefense.Add(defense); + } + } + } + } + if (possibleDefense.Count > 0) + { + possibleDefense.Sort((x, y) => Math.Abs(x.Score - this.Score).CompareTo(Math.Abs(y.Score - this.Score))); + ParContract optimumDefense = possibleDefense.Last(); + possibleDefense = possibleDefense.FindAll(x => x.Score == optimumDefense.Score); + foreach (ParContract defense in possibleDefense) + { + if (defense.Higher(optimumDefense)) + { + optimumDefense = defense; + } + } + return optimumDefense; + } + } + return null; + } + } +} diff --git a/src/ParScore.cs b/src/ParScore.cs new file mode 100644 index 0000000..b1561fc --- /dev/null +++ b/src/ParScore.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace BCDD +{ + class ParScoreInvalidException : FieldNotFoundException + { + public ParScoreInvalidException() : base() { } + public ParScoreInvalidException(String msg) : base(msg) { } + } + + class ParScore + { + private PBNBoard board; + private static Regex pbnContractPattern = new Regex(@"(\d)([CDHSN])(X?)\s+([NESW])"); + private static Regex pbnScorePattern = new Regex(@"(NS|EW)\s+(-?\d})"); + private static Regex jfrContractPattern = new Regex(@"^(\d)([CDHSN])(D?)([NESW])(-?\d+)$"); + + public ParScore(PBNBoard board) + { + this.board = board; + } + + public ParContract GetPBNParContract() + { + String contractField = this.board.GetOptimumResult(); + if ("Pass".Equals(contractField)) + { + return new ParContract(); + } + Match contractMatch = ParScore.pbnContractPattern.Match(contractField); + if (!contractMatch.Success) + { + throw new ParScoreInvalidException("Invalid format for OptimumResult field: " + contractField); + } + String scoreField = this.board.GetOptimumScore(); + Match scoreMatch = ParScore.pbnScorePattern.Match(scoreField); + if (!scoreMatch.Success) + { + throw new ParScoreInvalidException("Invalid format for OptimumScore field: " + scoreField); + } + int score = Int16.Parse(scoreMatch.Groups[2].Value); + if ("EW".Equals(scoreMatch.Groups[1].Value)) + { + score = -score; + } + ParContract contract = new ParContract(Int16.Parse(contractMatch.Groups[1].Value), + contractMatch.Groups[2].Value[0], + contractMatch.Groups[4].Value[0], + "X".Equals(contractMatch.Groups[3].Value), + score); + return contract.Validate(); + } + + public ParContract GetJFRParContract() + { + String parString = this.board.GetMinimax(); + Match parMatch = ParScore.jfrContractPattern.Match(parString); + if (!parMatch.Success) + { + throw new ParScoreInvalidException("Invalid format for Minimax field: " + parString); + } + if ("0".Equals(parMatch.Groups[4].Value)) + { + return new ParContract(); // pass-out + } + ParContract contract = new ParContract(Int16.Parse(parMatch.Groups[1].Value), + parMatch.Groups[2].Value[0], + parMatch.Groups[4].Value[0], + "D".Equals(parMatch.Groups[3].Value), + Int16.Parse(parMatch.Groups[5].Value)); + return contract.Validate(); + } + + private bool determineVulnerability(String vulnerability, char declarer) + { + vulnerability = vulnerability.ToUpper(); + return "ALL".Equals(vulnerability) || "BOTH".Equals(vulnerability) + || (!"LOVE".Equals(vulnerability) && !"NONE".Equals(vulnerability) && vulnerability.Contains(declarer)); + } + + private ParContract getHighestMakeableContract(int[,] ddTable, bool forNS = true, bool forEW = true) + { + ParContract contract = new ParContract(); + int tricks = 0; + for (int i = 0; i < 4; i++) + { + if ((i % 2 == 0 && forNS) + || (i % 2 == 1 && forEW)) + { + for (int j = 0; j < 5; j++) + { + int level = ddTable[i, j] - 6; + if (level >= contract.Level) + { + contract.Level = level; + contract.Denomination = BCalcWrapper.DENOMINATIONS[j]; + contract.Declarer = BCalcWrapper.PLAYERS[i]; + tricks = ddTable[i, j]; + } + } + } + } + String vulnerability = this.board.GetVulnerable().ToUpper(); + bool vulnerable = this.determineVulnerability(vulnerability, contract.Declarer); + contract.Score = contract.CalculateScore(tricks, vulnerable); + return contract; + } + + public ParContract GetDDTableParContract(int[,] ddTable) + { + String dealer = this.board.GetDealer(); + String vulnerability = this.board.GetVulnerable().ToUpper(); + ParContract nsHighest = this.getHighestMakeableContract(ddTable, true, false); + ParContract ewHighest = this.getHighestMakeableContract(ddTable, false, true); + bool nsPlaying = ("N".Equals(dealer) || "S".Equals(dealer)); + if (nsHighest == ewHighest) + { + return nsPlaying ? nsHighest.Validate() : ewHighest.Validate(); + } + bool defenseVulnerability = this.determineVulnerability(vulnerability, nsPlaying ? 'E' : 'N'); + ParContract highest = nsHighest.Higher(ewHighest) ? nsHighest : ewHighest; + ParContract highestDefense = highest.GetDefense(ddTable, defenseVulnerability); + if (highestDefense != null) + { + return highestDefense.Validate(); + } + int denominationIndex = Array.IndexOf(BCalcWrapper.DENOMINATIONS, highest.Denomination); + int declarerIndex = Array.IndexOf(BCalcWrapper.PLAYERS, highest.Declarer); + List playerIndexes = new List(); + playerIndexes.Add(declarerIndex); + playerIndexes.Add((declarerIndex + 2) & 3); + bool vulnerable = this.determineVulnerability(vulnerability, highest.Declarer); + int scoreSquared = highest.Score * highest.Score; + List possibleOptimums = new List(); + for (int i = 0; i < 5; i++) + { + foreach (int player in playerIndexes) + { + int level = highest.Level; + if (i > denominationIndex) + { + level--; + } + while (level > 0) + { + ParContract contract = new ParContract(level, BCalcWrapper.DENOMINATIONS[i], BCalcWrapper.PLAYERS[player], false, 0); + contract.Score = contract.CalculateScore(ddTable[player, i], vulnerable); + if (scoreSquared < contract.Score * highest.Score) + { + possibleOptimums.Add(contract.GetDefense(ddTable, defenseVulnerability) ?? contract); + } + else + { + break; + } + level--; + } + } + } + foreach (ParContract contract in possibleOptimums) + { + if (Math.Abs(contract.Score) > Math.Abs(highest.Score) || (contract.Score == highest.Score && contract.Higher(highest))) + { + highest = contract; + } + } + return highest.Validate(); + } + + public ParContract GetParContract(int[,] ddTable) + { + try + { + return this.GetJFRParContract(); + } + catch (FieldNotFoundException) + { + try + { + return this.GetPBNParContract(); + } + catch (FieldNotFoundException) + { + return this.GetDDTableParContract(ddTable); + } + } + } + + } +} -- cgit v1.2.3