//
// @(#)OrderStatsWriter.java 5/2004
//
// Copyright 2004 Zachary DelProposto. All rights reserved.
// Use is subject to license terms.
//
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// Or from http://www.gnu.org/
//
package dip.gui.report;
import dip.gui.ClientFrame;
import dip.gui.dialog.TextViewer;
import dip.misc.Utils;
import dip.misc.Help;
import dip.world.World;
import dip.world.Power;
import dip.world.Phase;
import dip.world.Unit;
import dip.world.TurnState;
import dip.world.Position;
import dip.gui.dialog.TextViewer;
import dip.order.*;
import dip.order.result.OrderResult;
import dip.order.OrderFormatOptions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.text.DecimalFormat;
import javax.swing.JScrollPane;
/**
* Order Statistics
*
*/
public class OrderStatsWriter
{
// i18n constants
private static final String DIALOG_TITLE = "OrderStatsWriter.dialog.title";
private static final String HTML_TEMPLATE = "OrderStatsWriter.template"; // points to HTML file
private static final String AVERAGE_HEADER = "OrderStatsWriter.label.average";
private static final String NO_RESULTS_HTML_MSG = "OrderStatsWriter.noresults"; // points to HTML file
// hilite
private static final String TR_HIGHLIGHT = "<tr bgcolor=\"#E6EEF0\">";
// instance variables
private final World world;
private final Power[] allPowers;
private final OrderFormatOptions ofo;
private final DecimalFormat pctFmt = new DecimalFormat("###%");
/**
* Gets the order statistics as HTML
*/
public static String getOrderStatsAsHTML(World w, OrderFormatOptions orderFormatOptions)
{
OrderStatsWriter osw = new OrderStatsWriter(w, orderFormatOptions);
return osw.getResultsAsHTML();
}// getOrderStatsAsHTML()
/**
* Returns the HTML-encoded Order Statistics for
* an entire game, inside a dialog.
*/
public static void displayDialog(final ClientFrame clientFrame,
final World w, final OrderFormatOptions orderFormat)
{
TextViewer tv = new TextViewer(clientFrame);
tv.setEditable(false);
tv.addSingleButton( tv.makeOKButton() );
tv.setTitle(Utils.getLocalString(DIALOG_TITLE));
tv.setHeaderVisible(false);
tv.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
tv.lazyLoadDisplayDialog(new TextViewer.TVRunnable()
{
public void run()
{
setText(getOrderStatsAsHTML(w, orderFormat));
}
});
}// displayDialog()
/** OrderStatsWriter constructor. */
private OrderStatsWriter(World w, OrderFormatOptions ofo)
{
world = w;
allPowers = w.getMap().getPowers();
this.ofo = ofo;
pctFmt.setMaximumFractionDigits(0);
}// OrderStatsWriter()
/** Create HTML results */
private String getResultsAsHTML()
{
final MovePhaseTurnData[] mptd = collectData();
// if there are no movement-phase results, return a
// notice indicating we can't yet calculate statistics.
if(mptd.length == 0)
{
return Utils.getText(Utils.getLocalString(NO_RESULTS_HTML_MSG));
}
// get template
String templateText = Utils.getText(Utils.getLocalString(HTML_TEMPLATE));
// get template objects
Object[] templateData = new Object[]
{
makeTable(mptd, 0), // {0} :
makeTable(mptd, 3), // {1} :
makeTable(mptd, 1), // {2} :
makeTable(mptd, 2) // {3} :
};
// format into template
return Utils.format(templateText, templateData);
}// getResultsAsHTML()
private String makeTable(final MovePhaseTurnData[] mptds, int type)
{
StringBuffer sb = new StringBuffer(4096);
sb.append("<table cellspacing=\"3\" cellpadding=\"1\" border=\"0\">");
// header row.
// [EMPTY] [POWER-1] [POWER-2] ... [POWER-n] [average]
//
sb.append("<tr>");
sb.append("<td></td>"); // empty
for(int i=0; i<allPowers.length; i++)
{
sb.append("<td><b> ");
sb.append(allPowers[i].getName());
sb.append(" </b></td>");
}
sb.append("<td>");
sb.append( Utils.getLocalString(AVERAGE_HEADER) );
sb.append("<td>");
sb.append("</tr>");
for(int row=0; row<mptds.length; row++)
{
MovePhaseTurnData mptd = mptds[row];
// by-year row
// [year] [%] [%] ... [%} [avg-%]
//
final Phase phase = mptd.getPhase();
final Stats[] stats = mptd.getStats();
sb.append( (((row & 1) == 0) ? TR_HIGHLIGHT : "<tr>") );
sb.append("<td><b>");
sb.append(phase.getSeasonType());
sb.append(", ");
sb.append(phase.getYearType());
sb.append("</b></td>");
float sum = 0.0f;
int nPowers = 0; // for computing average....
float value = 0.0f;
for(int i=0; i<allPowers.length; i++)
{
switch(type) // it's just so easy...
{
case 0:
value = stats[i].getOverallSuccess();
break;
case 1:
value = stats[i].getPercentSupport();
break;
case 2:
value = stats[i].getPercentNonSelfSupport();
break;
case 3:
value = stats[i].getPercentMoveSuccess();
break;
default:
throw new IllegalStateException();
}
if(stats[i].isEliminated || value < 0.0f)
{
// don't add to average, don't print 0% (just empty),
// don't increment nPowers
sb.append("<td></td>");
}
else
{
sum += value;
nPowers++;
sb.append("<td>");
sb.append( pctFmt.format(value) );
sb.append("</td>");
}
}
sb.append("<td>");
sb.append( pctFmt.format(sum / (float) nPowers) );
sb.append("</td>");
sb.append("</tr>");
}
sb.append("</table>");
return sb.toString();
}// makeOrderSuccessRateTable()
private String makeSupportRateTable(final MovePhaseTurnData[] mptds)
{
StringBuffer sb = new StringBuffer(4096);
return sb.toString();
}// makeSupportRateTable()
private String makeNonSelfSupportRateTable(final MovePhaseTurnData[] mptds)
{
StringBuffer sb = new StringBuffer(4096);
return sb.toString();
}// makeNonSelfSupportRateTable()
/**
* Makes an array of tabular data, for easy calculation.
* ONLY Movement TURNS are used to create statistical data.
*/
public MovePhaseTurnData[] collectData()
{
List turns = world.getAllTurnStates();
ArrayList data = new ArrayList(turns.size());
Iterator iter = turns.iterator();
while(iter.hasNext())
{
TurnState ts = (TurnState) iter.next();
if( ts.isResolved() &&
Phase.PhaseType.MOVEMENT.equals(ts.getPhase().getPhaseType()) )
{
data.add( new MovePhaseTurnData(ts) );
}
}
return (MovePhaseTurnData[]) data.toArray(new MovePhaseTurnData[data.size()]);
}// collectData()
/** Class to hold and gather Movement-Phase turn statistics */
private class MovePhaseTurnData
{
private final Phase phase;
private final Stats[] stats;
public MovePhaseTurnData(TurnState ts)
{
if(!ts.getPhase().getPhaseType().equals(Phase.PhaseType.MOVEMENT))
{
throw new IllegalArgumentException();
}
if(!ts.isResolved())
{
throw new IllegalArgumentException();
}
this.phase = ts.getPhase();
this.stats = new Stats[allPowers.length];
collectStats(ts);
}// MovePhaseTurnData()
public Phase getPhase()
{
return phase;
}
public Stats[] getStats()
{
return stats;
}// getStats()
public Stats getStats(Power p)
{
if(p == null)
{
throw new IllegalArgumentException();
}
for(int i=0; i<stats.length; i++)
{
if(p.equals(stats[i].getPower()))
{
return stats[i];
}
}
throw new IllegalStateException();
}// getStats()
private void collectStats(TurnState ts)
{
// create order-result mapping
HashMap resultMap = new HashMap(53);
Iterator iter = ts.getResultList().iterator();
while(iter.hasNext())
{
Object obj = iter.next();
if(obj instanceof OrderResult)
{
OrderResult ordRes = (OrderResult) obj;
// we only map SUCCESSFULL orders.
if(ordRes.getResultType() == OrderResult.ResultType.SUCCESS)
{
resultMap.put(ordRes.getOrder(), Boolean.TRUE);
}
}
}
// create statistics
for(int i=0; i<allPowers.length; i++)
{
Stats s = new Stats(allPowers[i]);
s.isEliminated = ts.getPosition().isEliminated(allPowers[i]);
iter = ts.getOrders(allPowers[i]).iterator();
while(iter.hasNext())
{
s.nOrders++;
final Orderable order = (Orderable) iter.next();
final boolean success = (resultMap.get(order) == Boolean.TRUE);
if(order instanceof Move)
{
s.nMoves++;
if(success) { s.nMovesOK++; }
}
else if(order instanceof Hold)
{
s.nHolds++;
if(success) { s.nHoldsOK++; }
}
else if(order instanceof Convoy)
{
s.nConvoys++;
if(success) { s.nConvoysOK++; }
}
else if(order instanceof Support)
{
s.nSupports++;
if(success) { s.nSupportsOK++; }
// self support?
final Support sup = (Support) order;
final Unit supUnit = ts.getPosition().getUnit(sup.getSupportedSrc().getProvince());
if(supUnit != null)
{
if(sup.getPower().equals( supUnit.getPower() ))
{
s.nSupportsSelf++;
if(success) { s.nSupportsSelfOK++; }
}
}
}
}
stats[i] = s;
}
}// collectStats()
}// inner class MovePhaseTurnData
private class Stats
{
private final Power power; // power for which these stats apply
public boolean isEliminated = false;
public int nOrders = 0; // total # of orders
public int nMoves = 0;
public int nConvoys = 0;
public int nHolds = 0;
public int nSupports = 0; // supports to any unit
public int nSupportsSelf = 0; // supports of own units
public int nMovesOK = 0; // successful Move orders
public int nConvoysOK = 0; // successful Convoy orders
public int nHoldsOK = 0; // successful Hold orders
public int nSupportsOK = 0; // # successful total supports
public int nSupportsSelfOK = 0; // # successful self-supports
// other data to collect (per-power)
// # of units total
// # of units moving?
// # of units dislodged?
//
public Stats(Power p)
{
if(p == null)
{
throw new IllegalArgumentException();
}
this.power = p;
}// Stats()
/** Get the Power */
public Power getPower()
{
return power;
}// getPower()
/** Calculate percent successful orders (all orders) */
public float getOverallSuccess()
{
if(getTotal() == 0)
{
return 0.0f;
}
final int success = (nMovesOK + nConvoysOK + nHoldsOK + nSupportsOK);
return ((float) success / (float) getTotal());
}
/**
* Calculate percent support orders (successfull or failed),
* of all total orders.
*/
public float getPercentSupport()
{
if(getTotal() == 0)
{
return 0.0f;
}
return ((float) nSupports / (float) getTotal());
}
/**
* Calculate percent SELF support orders (successfull or failed),
* of all total orders.
*/
public float getPercentSelfSupport()
{
if(getTotal() == 0)
{
return 0.0f;
}
return ((float) nSupportsSelf / (float) getTotal());
}
/**
* Calculate percent NON-SELF support orders (successfull or failed),
* of all total orders.
*/
public float getPercentNonSelfSupport()
{
assert (nSupports >= nSupportsSelf);
if(getTotal() == 0)
{
return 0.0f;
}
return ((float) (nSupports-nSupportsSelf) / (float) getTotal());
}
/**
* Calculate percent successful Move orders.
* Returns negative # if no Move orders
*/
public float getPercentMoveSuccess()
{
if(nMoves == 0)
{
return -1.0f;
}
return ((float) nMovesOK / (float) nMoves);
}// getPercentMoveSuccess()
/** Get total orders */
private int getTotal()
{
return (nMoves + nConvoys + nHolds + nSupports);
}// getTotal()
}// inner class Stats
}// class OrderStatsWriter