From 0581c8593e8e1b8f9b3c505b0c0da51f82f93c0d Mon Sep 17 00:00:00 2001 From: emkael Date: Tue, 10 Apr 2018 02:19:58 +0200 Subject: Renaming files to suit Python convention --- Program.cs | 116 --------------- pybcdd.cs | 116 +++++++++++++++ pybcdd/BCalcWrapper.cs | 44 ++++++ pybcdd/DDTable.cs | 156 ++++++++++++++++++++ pybcdd/PBNBoard.cs | 390 +++++++++++++++++++++++++++++++++++++++++++++++++ pybcdd/PBNFile.cs | 75 ++++++++++ pybcdd/ParContract.cs | 227 ++++++++++++++++++++++++++++ pybcdd/ParScore.cs | 233 +++++++++++++++++++++++++++++ src/BCalcWrapper.cs | 44 ------ src/DDTable.cs | 156 -------------------- src/PBNBoard.cs | 390 ------------------------------------------------- src/PBNFile.cs | 75 ---------- src/ParContract.cs | 227 ---------------------------- src/ParScore.cs | 233 ----------------------------- 14 files changed, 1241 insertions(+), 1241 deletions(-) delete mode 100644 Program.cs create mode 100644 pybcdd.cs create mode 100644 pybcdd/BCalcWrapper.cs create mode 100644 pybcdd/DDTable.cs create mode 100644 pybcdd/PBNBoard.cs create mode 100644 pybcdd/PBNFile.cs create mode 100644 pybcdd/ParContract.cs create mode 100644 pybcdd/ParScore.cs delete mode 100644 src/BCalcWrapper.cs delete mode 100644 src/DDTable.cs delete mode 100644 src/PBNBoard.cs delete mode 100644 src/PBNFile.cs delete mode 100644 src/ParContract.cs delete mode 100644 src/ParScore.cs diff --git a/Program.cs b/Program.cs deleted file mode 100644 index ea1eab3..0000000 --- a/Program.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows.Forms; -using System.IO; - -namespace BCDD -{ - class Program - { - static List getFiles(string[] args) - { - List filenames = new List(); - foreach (String arg in args) - { - if (File.Exists(arg)) - { - filenames.Add(arg); - } - } - if (filenames.Count == 0) - { - OpenFileDialog fd = new OpenFileDialog(); - fd.Multiselect = true; - fd.Filter = "PBN files (*.pbn)|*.pbn|All files (*.*)|*.*"; - if (fd.ShowDialog() == DialogResult.OK) - { - filenames = new List(fd.FileNames); - } - } - return filenames; - } - - [STAThread] - static void Main(string[] args) - { - List files = Program.getFiles(args); - List errors = new List(); - if (files.Count > 0) - { - foreach (String filename in files) - { - try - { - Console.WriteLine("Analyzing " + filename); - PBNFile file = new PBNFile(filename); - foreach (PBNBoard board in file.Boards) - { - DDTable table = new DDTable(board); - String boardNo; - try - { - boardNo = board.GetNumber(); - } - catch (FieldNotFoundException) - { - boardNo = "?"; - } - try - { - int[,] ddTable = table.GetDDTable(); - if (ddTable != null) - { - Console.WriteLine("Board " + boardNo); - DDTable.PrintTable(ddTable); - ParScore par = new ParScore(board); - ParContract contract = par.GetParContract(ddTable); - Console.WriteLine(contract); - Console.WriteLine(); - board.SaveDDTable(ddTable); - board.SaveParContract(contract); - file.WriteBoard(board); - } - else - { - String error = "unable to determine DD table for board " + boardNo; - errors.Add(String.Format("[{0}] {1}", filename, error)); - Console.WriteLine("ERROR: " + error); - } - } - catch (DllNotFoundException) - { - throw; - } - catch (Exception e) - { - errors.Add(String.Format("[{0}:{1}] {2}", filename, boardNo, e.Message)); - Console.WriteLine("ERROR: " + e.Message); - } - } - file.Save(); - } - catch (DllNotFoundException e) - { - errors.Add("libbcalcdds.dll could not be loaded - make sure it's present in application directory!"); - Console.WriteLine("ERROR: " + e.Message); - break; - } - catch (Exception e) - { - errors.Add(e.Message); - Console.WriteLine("ERROR: " + e.Message); - } - } - if (errors.Count > 0) { - Console.WriteLine("Following ERRORs occured:"); - foreach (String error in errors) { - Console.WriteLine(error); - } - Console.WriteLine("Press any key to continue..."); - Console.ReadKey(); - } - } - } - } -} diff --git a/pybcdd.cs b/pybcdd.cs new file mode 100644 index 0000000..ea1eab3 --- /dev/null +++ b/pybcdd.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.IO; + +namespace BCDD +{ + class Program + { + static List getFiles(string[] args) + { + List filenames = new List(); + foreach (String arg in args) + { + if (File.Exists(arg)) + { + filenames.Add(arg); + } + } + if (filenames.Count == 0) + { + OpenFileDialog fd = new OpenFileDialog(); + fd.Multiselect = true; + fd.Filter = "PBN files (*.pbn)|*.pbn|All files (*.*)|*.*"; + if (fd.ShowDialog() == DialogResult.OK) + { + filenames = new List(fd.FileNames); + } + } + return filenames; + } + + [STAThread] + static void Main(string[] args) + { + List files = Program.getFiles(args); + List errors = new List(); + if (files.Count > 0) + { + foreach (String filename in files) + { + try + { + Console.WriteLine("Analyzing " + filename); + PBNFile file = new PBNFile(filename); + foreach (PBNBoard board in file.Boards) + { + DDTable table = new DDTable(board); + String boardNo; + try + { + boardNo = board.GetNumber(); + } + catch (FieldNotFoundException) + { + boardNo = "?"; + } + try + { + int[,] ddTable = table.GetDDTable(); + if (ddTable != null) + { + Console.WriteLine("Board " + boardNo); + DDTable.PrintTable(ddTable); + ParScore par = new ParScore(board); + ParContract contract = par.GetParContract(ddTable); + Console.WriteLine(contract); + Console.WriteLine(); + board.SaveDDTable(ddTable); + board.SaveParContract(contract); + file.WriteBoard(board); + } + else + { + String error = "unable to determine DD table for board " + boardNo; + errors.Add(String.Format("[{0}] {1}", filename, error)); + Console.WriteLine("ERROR: " + error); + } + } + catch (DllNotFoundException) + { + throw; + } + catch (Exception e) + { + errors.Add(String.Format("[{0}:{1}] {2}", filename, boardNo, e.Message)); + Console.WriteLine("ERROR: " + e.Message); + } + } + file.Save(); + } + catch (DllNotFoundException e) + { + errors.Add("libbcalcdds.dll could not be loaded - make sure it's present in application directory!"); + Console.WriteLine("ERROR: " + e.Message); + break; + } + catch (Exception e) + { + errors.Add(e.Message); + Console.WriteLine("ERROR: " + e.Message); + } + } + if (errors.Count > 0) { + Console.WriteLine("Following ERRORs occured:"); + foreach (String error in errors) { + Console.WriteLine(error); + } + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); + } + } + } + } +} diff --git a/pybcdd/BCalcWrapper.cs b/pybcdd/BCalcWrapper.cs new file mode 100644 index 0000000..d2d0aa2 --- /dev/null +++ b/pybcdd/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/pybcdd/DDTable.cs b/pybcdd/DDTable.cs new file mode 100644 index 0000000..c2ffcd1 --- /dev/null +++ b/pybcdd/DDTable.cs @@ -0,0 +1,156 @@ +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; + } + + private static bool bannerDisplayed = false; + + public int[,] GetBCalcTable() + { + if (!DDTable.bannerDisplayed) + { + Console.WriteLine("Double dummy analysis provided by BCalc."); + Console.WriteLine("BCalc is awesome, check it out: http://bcalc.w8.pl"); + DDTable.bannerDisplayed = true; + } + 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.PtrToStringAnsi(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/pybcdd/PBNBoard.cs b/pybcdd/PBNBoard.cs new file mode 100644 index 0000000..7578fbe --- /dev/null +++ b/pybcdd/PBNBoard.cs @@ -0,0 +1,390 @@ +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 GetEvent() + { + return this.GetField("Event"); + } + + public void WriteEvent(String name) + { + for (int i = 0; i < this.Fields.Count; i++) + { + if ("Board".Equals(this.Fields[i].Key)) + { + this.Fields.Insert(i, new PBNField("Event", name)); + break; + } + } + } + + 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) + { + String minimax; + if (contract.Score == 0) + { + minimax = "7NS0"; + } + else + { + minimax = String.Format("{0}{1}{2}{3}{4}", contract.Level, contract.Denomination, contract.Doubled ? "D" : "", contract.Declarer, contract.Score); + } + this.Fields.Add(new PBNField("Minimax", minimax)); + } + + 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/pybcdd/PBNFile.cs b/pybcdd/PBNFile.cs new file mode 100644 index 0000000..b26f84c --- /dev/null +++ b/pybcdd/PBNFile.cs @@ -0,0 +1,75 @@ +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)); + } + if (!this.Boards[0].HasField("Event")) + { + this.Boards[0].WriteEvent(""); + } + } + + 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/pybcdd/ParContract.cs b/pybcdd/ParContract.cs new file mode 100644 index 0000000..9255ca5 --- /dev/null +++ b/pybcdd/ParContract.cs @@ -0,0 +1,227 @@ +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) + { + int declarerIndex = Array.IndexOf(BCalcWrapper.PLAYERS, this.Declarer); + int denominationIndex = Array.IndexOf(BCalcWrapper.DENOMINATIONS, this.Denomination); + if (this.Level != 0 && this.Level + 6 <= ddTable[declarerIndex, denominationIndex]) + { + List defendersIndexes = new List(); + defendersIndexes.Add((declarerIndex + 1) & 3); + defendersIndexes.Add((declarerIndex + 3) & 3); + List possibleDefense = new List(); + int scoreSquared = this.Score * this.Score; + for (int i = 0; i < 5; i++) + { + int level = this.Level; + if (i <= denominationIndex) + { + level++; + } + if (level <= 7) + { + 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) + { + // Lowest from the most profitable sacrifices + if (optimumDefense.Higher(defense)) + { + optimumDefense = defense; + } + } + return optimumDefense; + } + } + return null; + } + } +} diff --git a/pybcdd/ParScore.cs b/pybcdd/ParScore.cs new file mode 100644 index 0000000..068be90 --- /dev/null +++ b/pybcdd/ParScore.cs @@ -0,0 +1,233 @@ +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 = 3; i >= 0; 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 + || (level == contract.Level && j > Array.IndexOf(BCalcWrapper.DENOMINATIONS, contract.Denomination))) + { + 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(); + } + ParContract highest = nsHighest.Higher(ewHighest) ? nsHighest : ewHighest; + ParContract otherSideHighest = nsHighest.Higher(ewHighest) ? ewHighest : nsHighest; + nsPlaying = ('N'.Equals(highest.Declarer) || 'S'.Equals(highest.Declarer)); + bool defenseVulnerability = this.determineVulnerability(vulnerability, nsPlaying ? 'E' : 'N'); + ParContract highestDefense = highest.GetDefense(ddTable, defenseVulnerability); + if (highestDefense != null) + { + // Highest contract has profitable defense + 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 (otherSideHighest.Higher(contract)) + { + // Contract is lower than other side's contract + break; + } + if (highest.Score * contract.Score > 0) + { + // Contract makes + if (Math.Abs(contract.Score) >= Math.Abs(highest.Score)) + { + // Contract is profitable + ParContract defense = contract.GetDefense(ddTable, defenseVulnerability); + if (defense != null && (contract.Score * contract.Score > contract.Score * defense.Score)) + { + // Contract has defense + possibleOptimums.Add(defense); + // So lower contracts will too. + break; + } + else + { + // Contract does not have defense + possibleOptimums.Add(contract); + } + } + else + { + // Contract is not profitable + break; + } + } + level--; + } + } + } + foreach (ParContract contract in possibleOptimums) + { + if ((Math.Abs(contract.Score) > Math.Abs(highest.Score))) + { + // Contract is more profitable + highest = contract; + } + else + { + if (contract.Score == highest.Score) + { + if (highest.Higher(contract)) + { + // Equally profitable, but lower + 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); + } + } + } + + } +} diff --git a/src/BCalcWrapper.cs b/src/BCalcWrapper.cs deleted file mode 100644 index d2d0aa2..0000000 --- a/src/BCalcWrapper.cs +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index c2ffcd1..0000000 --- a/src/DDTable.cs +++ /dev/null @@ -1,156 +0,0 @@ -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; - } - - private static bool bannerDisplayed = false; - - public int[,] GetBCalcTable() - { - if (!DDTable.bannerDisplayed) - { - Console.WriteLine("Double dummy analysis provided by BCalc."); - Console.WriteLine("BCalc is awesome, check it out: http://bcalc.w8.pl"); - DDTable.bannerDisplayed = true; - } - 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.PtrToStringAnsi(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 deleted file mode 100644 index 7578fbe..0000000 --- a/src/PBNBoard.cs +++ /dev/null @@ -1,390 +0,0 @@ -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 GetEvent() - { - return this.GetField("Event"); - } - - public void WriteEvent(String name) - { - for (int i = 0; i < this.Fields.Count; i++) - { - if ("Board".Equals(this.Fields[i].Key)) - { - this.Fields.Insert(i, new PBNField("Event", name)); - break; - } - } - } - - 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) - { - String minimax; - if (contract.Score == 0) - { - minimax = "7NS0"; - } - else - { - minimax = String.Format("{0}{1}{2}{3}{4}", contract.Level, contract.Denomination, contract.Doubled ? "D" : "", contract.Declarer, contract.Score); - } - this.Fields.Add(new PBNField("Minimax", minimax)); - } - - 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 deleted file mode 100644 index b26f84c..0000000 --- a/src/PBNFile.cs +++ /dev/null @@ -1,75 +0,0 @@ -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)); - } - if (!this.Boards[0].HasField("Event")) - { - this.Boards[0].WriteEvent(""); - } - } - - 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 deleted file mode 100644 index 9255ca5..0000000 --- a/src/ParContract.cs +++ /dev/null @@ -1,227 +0,0 @@ -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) - { - int declarerIndex = Array.IndexOf(BCalcWrapper.PLAYERS, this.Declarer); - int denominationIndex = Array.IndexOf(BCalcWrapper.DENOMINATIONS, this.Denomination); - if (this.Level != 0 && this.Level + 6 <= ddTable[declarerIndex, denominationIndex]) - { - List defendersIndexes = new List(); - defendersIndexes.Add((declarerIndex + 1) & 3); - defendersIndexes.Add((declarerIndex + 3) & 3); - List possibleDefense = new List(); - int scoreSquared = this.Score * this.Score; - for (int i = 0; i < 5; i++) - { - int level = this.Level; - if (i <= denominationIndex) - { - level++; - } - if (level <= 7) - { - 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) - { - // Lowest from the most profitable sacrifices - if (optimumDefense.Higher(defense)) - { - optimumDefense = defense; - } - } - return optimumDefense; - } - } - return null; - } - } -} diff --git a/src/ParScore.cs b/src/ParScore.cs deleted file mode 100644 index 068be90..0000000 --- a/src/ParScore.cs +++ /dev/null @@ -1,233 +0,0 @@ -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 = 3; i >= 0; 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 - || (level == contract.Level && j > Array.IndexOf(BCalcWrapper.DENOMINATIONS, contract.Denomination))) - { - 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(); - } - ParContract highest = nsHighest.Higher(ewHighest) ? nsHighest : ewHighest; - ParContract otherSideHighest = nsHighest.Higher(ewHighest) ? ewHighest : nsHighest; - nsPlaying = ('N'.Equals(highest.Declarer) || 'S'.Equals(highest.Declarer)); - bool defenseVulnerability = this.determineVulnerability(vulnerability, nsPlaying ? 'E' : 'N'); - ParContract highestDefense = highest.GetDefense(ddTable, defenseVulnerability); - if (highestDefense != null) - { - // Highest contract has profitable defense - 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 (otherSideHighest.Higher(contract)) - { - // Contract is lower than other side's contract - break; - } - if (highest.Score * contract.Score > 0) - { - // Contract makes - if (Math.Abs(contract.Score) >= Math.Abs(highest.Score)) - { - // Contract is profitable - ParContract defense = contract.GetDefense(ddTable, defenseVulnerability); - if (defense != null && (contract.Score * contract.Score > contract.Score * defense.Score)) - { - // Contract has defense - possibleOptimums.Add(defense); - // So lower contracts will too. - break; - } - else - { - // Contract does not have defense - possibleOptimums.Add(contract); - } - } - else - { - // Contract is not profitable - break; - } - } - level--; - } - } - } - foreach (ParContract contract in possibleOptimums) - { - if ((Math.Abs(contract.Score) > Math.Abs(highest.Score))) - { - // Contract is more profitable - highest = contract; - } - else - { - if (contract.Score == highest.Score) - { - if (highest.Higher(contract)) - { - // Equally profitable, but lower - 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