using System; using System.Collections.Generic; using System.IO; namespace Analizator9000 { /// /// Provided with a set of deals and contracts to check, conducts the actual statistics by gathering the results of BCalc analysis. /// class Accumulator { /// /// Two-dimensional dictionary of accumulated analysis results (sample count, sample sum and sample square sum). /// private Dictionary> sums; /// /// Number of deals already analyzed. /// private long analyzed = 0; /// /// Total number of deals to analyze. /// private long toAnalyze = 0; /// /// List (well, a stack) of deals to process. /// protected Stack deals; /// /// A back-reference to calling Form, for progress presentation purposes. /// protected Form1 form; /// /// List of contracts to analyze in BCalc notation (integers for denomination and declarer). /// protected Dictionary> contracts; /// /// Results file handle (version of StreamWriter initialized as synchronized). /// protected TextWriter outputFile; /// /// Filename for analysis output. /// private String filename; /// /// Accumulator constructor. /// /// Array of deals to process, in BCalc's "NESW" format with prepended deal number for orientation. /// List of denomination-declarer pairs (structures). /// GUI instance. public Accumulator(String[] deals, List contracts, Form1 form) { this.deals = new Stack(deals); if (this.deals.Count == 0) { throw new Exception(Form1.GetResourceManager().GetString("Accumulator_errorNoDeals", Form1.GetCulture())); } this.toAnalyze = deals.LongLength; this.form = form; this.sums = new Dictionary>(); for (int den = 0; den < BCalcWrapper.denominations.Length; den++) { this.sums.Add(den, new Dictionary()); for (int hand = 0; hand < BCalcWrapper.table.Length; hand++) { if (contracts.Contains(new Contract(den, hand))) { this.sums[den].Add(hand, new long[] { 0, 0, 0 }); } else { this.sums[den].Add(hand, new long[] { -1, -1, 0 }); } } } this.contracts = new Dictionary>(); foreach (Contract contract in contracts) { if (!this.contracts.ContainsKey(contract.Denomination)) { this.contracts.Add(contract.Denomination, new List()); } this.contracts[contract.Denomination].Add(contract.Declarer); } this.filename = Utils.getFilename("result"); this.outputFile = TextWriter.Synchronized(File.AppendText(@"files\"+this.filename)); } /// /// Number of threads initially run (and, subsequently, a maximum number that *should* be retained throughout analysis). /// private int portionSize = 10; /// /// Number of threads currently running. /// private int threadsRunning = 0; /// /// Initiates the analysis of a portion of deals. /// /// Portion size. If set to 0, assumes the default/initial portion size. /// Number of deals left to analyze. public int run(int portionSize = 0) { if (portionSize == 0) { portionSize = this.portionSize; } if (portionSize > this.deals.Count) { portionSize = this.deals.Count; } int toRun = Math.Min(portionSize, this.deals.Count); for (int i = 0; i < toRun; i++) { Action worker = this.analyzeDeal; lock (this.deals) { worker.BeginInvoke(this.deals.Pop(), this.endAnalyze, worker); } this.threadsRunning++; } return this.deals.Count; } /// /// Worker method for a single deal. /// /// Deal in BCalc's "NESW" format, with deal number prepended. private void analyzeDeal(String deal) { try { this.form.addStatusLine(deal); BCalcWrapper solver = new BCalcWrapper(deal); foreach (KeyValuePair> row in this.contracts) { try { solver.setTrump(row.Key); } catch (Exception ex) { this.form.addStatusLine(Form1.GetResourceManager().GetString("Form1_error", Form1.GetCulture()) + ": " + ex.Message); } foreach (int entry in row.Value) { try { BCalcResult result = solver.run(entry); if (!this.abort) { String line = "#" + result.dealNo + ", " + result.declarer + " " + Form1.GetResourceManager().GetString("Accumulator_playsIn", Form1.GetCulture()) + " " + result.trumpSuit + ", " + Form1.GetResourceManager().GetString("Accumulator_tricks", Form1.GetCulture()) + ": " + result.tricks; this.form.addStatusLine(line); this.outputFile.WriteLine(line); this.update(result); this.form.setResult(this.getString()); } } catch (Exception ex) { this.form.addStatusLine(Form1.GetResourceManager().GetString("Form1_error", Form1.GetCulture()) + ": " + ex.Message); } } } solver.destroy(); } catch (Exception ex) { this.outputFile.WriteLine(ex.Message); this.form.addStatusLine(Form1.GetResourceManager().GetString("Form1_error", Form1.GetCulture()) + ": " + ex.Message); } } /// /// Flag stating whether the user aborted the analysis. /// private bool abort = false; /// /// Analysis abort method. /// public void abortAnalysis() { this.abort = true; } protected Object threadLock = new Object(); /// /// Callback method for worker threads, ends the single deal analysis, updates the total result and fires next analysis if necessary. /// /// Method invokation result from the worker method. private void endAnalyze(IAsyncResult methodResult) { ((Action)methodResult.AsyncState).EndInvoke(methodResult); lock (this.threadLock) { bool finished = false; if (this.abort) { this.form.setProgress(0); this.form.addStatusLine(Form1.GetResourceManager().GetString("Accumulator_analysisInterrupted", Form1.GetCulture()) + ": " + this.filename); finished = true; } else { this.threadsRunning--; this.analyzed++; this.form.setProgress((int)(100 * this.analyzed / this.toAnalyze)); if (threadsRunning == 0 && this.deals.Count == 0) { this.form.setProgress(100); this.form.addStatusLine(Form1.GetResourceManager().GetString("Accumulator_analysisFinished", Form1.GetCulture()) + ": " + this.filename); finished = true; } if (threadsRunning < this.portionSize) { // Increasing the parameter would cause exponential thread creation rate. Funny. this.run(1); } } if (finished) { try { this.outputFile.WriteLine(this.getString(true)); this.outputFile.Close(); } catch (Exception) { }; this.form.endAnalysis(); } } } /// /// Presents the current analysis results in textual form. /// /// Return full analysis for log file purpose (may be used in override methods) /// Text table containing means and std. deviations for distinct contracts. protected virtual String getString(bool full = false) { StringWriter sw = new StringWriter(); sw.WriteLine(); sw.WriteLine(" N E S W"); foreach (KeyValuePair> row in this.sums) { sw.Write(BCalcWrapper.denominations[row.Key]+" "); foreach (KeyValuePair entry in row.Value) { if (entry.Value[0] >= 0) { double mean = (entry.Value[2] != 0) ? (double)entry.Value[0] / entry.Value[2] : 0.0; double stdDev = (entry.Value[2] != 0) ? Math.Sqrt((double)entry.Value[1] / entry.Value[2] - mean * mean) : 0.0; sw.Write(" {0,5:0.00} ± {1,5:0.00} ", mean, stdDev); } else { sw.Write(" "); } } sw.WriteLine(); } sw.Close(); return sw.ToString(); } /// /// Feeds the overall results with chunks of data from single contract analysis. /// /// Result of BCalc analysis. protected virtual void update(BCalcResult result) { int tricks = result.tricks; int suit = BCalcWrapper.denominations.IndexOf(result.trumpSuit); int player = BCalcWrapper.table.IndexOf(result.declarer); this.sums[suit][player][0] += tricks; this.sums[suit][player][1] += tricks * tricks; this.sums[suit][player][2] ++; } } }