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] ++;
}
}
}