// // @(#)TestSuite.java 4/2002 // // Copyright 2002 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.misc; import dip.order.*; import dip.order.result.*; import dip.world.Unit; import dip.world.Province; import dip.world.Power; import dip.world.Phase; import dip.world.Location; import dip.world.RuleOptions; import dip.world.World; import dip.world.WorldFactory; import dip.world.variant.VariantManager; import dip.world.variant.data.*; import dip.world.TurnState; import dip.world.Position; import dip.process.*; import java.util.*; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Serializable; /** * A very hastily-programmed Test harness.. * <p> * This will read in a file of cases (1 or more). All cases must use the same * variant. The variant is then loaded, orders are parsed, and adjudication then * occurs. After adjudication, the positions of units are checked with that of * the case file for discrepancies. If no discrepancies exist, the case passes. * <p> * Note that when in performance-testing mode, all logging is disabled and * comparison-checking is not performed; the goal is testing adjudicator code * only. * <p> * All output is printed to stdout * <p> * <b>Case File Format Notes:</b> * <ul> * <li> * Any line prefixed by a # is a comment line. A # may be placed after a line, * to comment out part of a line or make a comment about a particular line. * </li> * <li> * Empty lines / whitespace-only lines are ignored. Whitespace before keywords * and lines are also ignored by the parser. * </li> * <li> * Single Line keywords are a keyword, followed by whitespace, followed by * text; that text is parsed and associated with that keyword. Some keywords * (such as END) do not have any text that follows them. * </li> * <li> * Block keywords begin a block; DO NOT put text on the same line as a block * keyword; start text on the next line. A block ends when another keyword * (block or single line) is detected. * </li> * </ul> * * <b>Case File Keywords:</b> * <ul> * <li><b>VARIANT_ALL: </b><i>Required</i>. * This must occur at the beginning of the case file. <i>All cases are * required to use the same variant</i>. Single line. * </li> * <li><b>CASE: (String)</b><i>Required</i>. * Begins a Case. The text following the case is the case name, and may * contain any printable character, including spaces, but must fit on * a single line. * </li> * <li><b>PRESTATE_SETPHASE: (phase)</b><i>Recommended</i>. * Set the phase (e.g., "Fall 1901, Movement" or "F1901M"). Single line. * </li> * <li><b>PRESTATE: </b><i>Recommended</i>. * Begins the non-dislodged unit setup block. Unit setups must consist of power, unit type, * and province, on the next line(s). e.g.: "England: F lon". Any orders to * non-dislodged units require a unit in the PRESTATE block. * </li> * <li><b>PRESTATE_DISLODGED: </b><i>Optional</i>. * If any dislodged units are to be positioned, set them in this block. * e.g.: "England: F lon" would create a dislodged Fleet in London. * </li> * <li><b>PRESTATE_RESULTS: </b><i>Optional</i>. * If a retreat phase is to be adjudicated, this sets up the "prior" phase. * Begins a block, where each order must be preceded by the keyword "SUCCESS:" * or "FAILURE:", followed by an order (i.e., Move, Hold, etc.). * </li> * <li><b>PRESTATE_SUPPLYCENTER_OWNERS: </b><i>Optional</i>. * Set owned, but not occupied, supply center owners in this block. If this is omitted, * the ownership is used from the initial variant settings. If it is supplied, * the variant information is erased and replaced with the given information. * <b>Note:</b> Currently you must use a unit too; e.g., "France: F lon" would set * the supply center in London to be owned by France. The unit type is required by * the parser but is ignored. * </li> * <li><b>ORDERS: </b><i>Recommended</i>. * One line, one order, in this block. e.g., "England: F lon-bel". * The orders are what will be adjudicated. * </li> * <li><b>POSTSTATE: </b><i>Recommended</i>. * A block of post-adjudication non-dislodged unit positions. The TestSuite tests * and make sure these match the post-adjudication state. Same format as PRESTATE. * </li> * <li><b>POSTSTATE_DISLODGED: </b><i>Recommended</i>. * A block of post-adjudication dislodged unit positions. The TestSuite tests * and make sure these match the post-adjudication state. Same format as PRESTATE * (or PRESTATE_DISLODGED for that matter). * </li> * <li><b>POSTSTATE_SAME: </b><i>Optional</i>. * If non-dislodged units do not change position, this may be used instead * of a POSTSTATE block and a list of non-dislodged unit positions. * </li> * <li><b>END: </b><i>Required</i>. * Ends a case. Must be the last line in a case. * </li> * </ul> * <p> * <b>An Example Case File:</b> <pre> VARIANT_ALL Standard CASE Example Case 1 (illustrative example) PRESTATE_SETPHASE Fall 1901, Movement PRESTATE Russia: F con Russia: F bla Turkey: F ank ORDERS Russia: F con S F bla-ank Russia: F bla-ank Turkey: F ank-con POSTSTATE Russia: F con Russia: F ank POSTSTATE_DISLODGED Turkey: F ank END </pre> */ public final class TestSuite { // constants private static final String VARIANT_ALL = "variant_all"; private static final String CASE = "case"; private static final String PRESTATE = "prestate"; private static final String ORDERS = "orders"; private static final String POSTSTATE = "poststate"; private static final String POSTSTATE_SAME = "poststate_same"; private static final String END = "end"; private static final String PRESTATE_SETPHASE = "prestate_setphase"; private static final String PRESTATE_SUPPLYCENTER_OWNERS = "prestate_supplycenter_owners"; private static final String PRESTATE_DISLODGED = "prestate_dislodged"; private static final String POSTSTATE_DISLODGED = "poststate_dislodged"; private static final String PRESTATE_RESULTS = "prestate_results"; // warning: POSTSTATE_SAME MUST come before POSTSTATE (since we use startsWith()) // "other" == not CASE (begin) or END private static final String[] KEY_TYPES_OTHER = { ORDERS, POSTSTATE_SAME, PRESTATE_SETPHASE, PRESTATE_RESULTS, PRESTATE_SUPPLYCENTER_OWNERS, PRESTATE_DISLODGED, POSTSTATE_DISLODGED, PRESTATE, POSTSTATE, VARIANT_ALL }; private static final String[] KEY_TYPES_WITH_LIST = { ORDERS, PRESTATE_SUPPLYCENTER_OWNERS, PRESTATE_RESULTS, PRESTATE_DISLODGED, POSTSTATE_DISLODGED, POSTSTATE, PRESTATE}; private static float parseTime = -1; private static final String VARIANT_DIR = "variants"; private Map keyMap = null; private static boolean isAdjudicatorLogged = true; private static boolean isLogging = true; private static boolean isPerfTest = false; private static boolean isRegression = false; private static String inFileName = null; private List cases = new ArrayList(10); private World world = null; private TurnState templateTurnState; private StdAdjudicator stdJudge = null; private List failedCaseNames = new ArrayList(10); private static int benchTimes = 1; // VARIANT_ALL name private static String variantName = null; /** Start the TestSuite */ public static void main(String args[]) { if(args.length < 1 || args.length > 2) { printUsageAndExit(); } if(args.length == 2) { inFileName = args[1]; String firstArg = args[0].trim().toLowerCase(); if(firstArg.startsWith("-perftest")) { isLogging = false; isAdjudicatorLogged = false; isPerfTest = true; if(firstArg.indexOf(":") != -1) { benchTimes = getTimes(firstArg); } else { printUsageAndExit(); } } else if(firstArg.equals("-brief")) { isAdjudicatorLogged = false; } else if(firstArg.equals("-statsonly")) { isAdjudicatorLogged = false; isPerfTest = false; isLogging = false; } else if(firstArg.equals("-regress")) { isAdjudicatorLogged = false; isPerfTest = false; isLogging = false; isRegression = true; } else { printUsageAndExit(); } } else { inFileName = args[0]; } Log.setLogging(isAdjudicatorLogged); TestSuite ts = new TestSuite(); println("TestSuite Results: (", new Date(), ")"); println("======================================================================="); println(" test case file: ", inFileName); File file = new File(inFileName); long startTime = System.currentTimeMillis(); ts.parseCases(file); parseTime = (System.currentTimeMillis() - startTime) / 1000f; println(" initialization complete."); println(" variant: ", variantName); ts.evaluate(); }// main() private static void printUsageAndExit() { System.out.println("USAGE: TestSuite [-statsonly | -perftest | -brief] <test-input-file>"); System.out.println(" All log output to stdout"); System.out.println(" -statsonly disable all logging; only show statistics"); System.out.println(" -perftest:n no logging or statistics; repeat all cases n times"); System.out.println(" -brief disable internal adjudicator logging"); System.out.println(" -regress run test cases in infinite loop; no logging or stats."); System.out.println(""); System.out.println(" Examples:"); System.out.println(" java dip.misc.TestSuite datc.txt >out"); System.out.println(" java dip.misc.TestSuite -brief datc.txt >out"); System.out.println(" java dip.misc.TestSuite -perftest:1000 case.txt >out"); System.exit(1); } private static int getTimes(String in) { String s = in.substring(in.indexOf(':')+1); int n = -1; try { n = Integer.parseInt(s); } catch(NumberFormatException e) { System.err.println("ERROR: invalid argument: "+in); printUsageAndExit(); } if(n <=0) { System.err.println("Benchmark repitition out of range; must be greater than 0"); printUsageAndExit(); } return n; }// getTimes() private TestSuite() { }// TestSuite() private void initVariant() { try { // get default variant directory. File defaultVariantSearchDir = null; if(System.getProperty("user.dir") == null) { defaultVariantSearchDir = new File(".", VARIANT_DIR); } else { defaultVariantSearchDir = new File(System.getProperty("user.dir"), VARIANT_DIR ); } // parse variants VariantManager.init(new File[]{defaultVariantSearchDir}, false); // load the default variant (Standard) // error if it cannot be found!! Variant variant = VariantManager.getVariant(variantName, VariantManager.VERSION_NEWEST); if(variant == null) { throw new Exception("Cannot find variant "+variantName); } // create the world world = WorldFactory.getInstance().createWorld(variant); templateTurnState = world.getLastTurnState(); world.removeTurnState(templateTurnState); // set the RuleOptions in the World (this is normally done // by the GUI) world.setRuleOptions(RuleOptions.createFromVariant(variant)); } catch(Exception e) { println("Init error: ", e); e.printStackTrace(); System.exit(1); } }// init() private void evaluate() { int nOrders = 0; int nPass = 0; int nFail = 0; int nCases = 0; List unRezParadoxes = new LinkedList(); long startMillis = System.currentTimeMillis(); // start timing! // all cases in an array final Case[] allCases = (Case[]) cases.toArray(new Case[cases.size()]); if(isRegression) { // no stats are kept in regression mode, because we're in an // infinite loop. // int rCount = 0; System.out.print("Running cases in an infinite loop"); while(true) { for(int ccn=0; ccn<allCases.length; ccn++) { Case currentCase = allCases[ccn]; // world: setup world.setTurnState(currentCase.getCurrentTurnState()); world.setTurnState(currentCase.getPreviousTurnState()); stdJudge = new StdAdjudicator(OrderFactory.getDefault(), currentCase.getCurrentTurnState()); stdJudge.process(); // cleanup: remove turnstates from world world.removeAllTurnStates(); // cleanup: clear results in currentTurnSTate // this is absolutely essential!! currentCase.getCurrentTurnState().getResultList().clear(); // print a '.' every 1000 iterations rCount++; if(rCount == 1000) { System.out.print('.'); rCount = 0; } } } } else if(isPerfTest) { // performance mode. We need to track stats here, // but there is no logging or output except stats. // for(int i=0; i<benchTimes; i++) { for(int ccn=0; ccn<allCases.length; ccn++) { Case currentCase = allCases[ccn]; // world: setup world.setTurnState(currentCase.getCurrentTurnState()); world.setTurnState(currentCase.getPreviousTurnState()); nOrders += currentCase.getOrders().length; // adjudicate // we don't check results when in performance mode. // stdJudge = new StdAdjudicator(OrderFactory.getDefault(), currentCase.getCurrentTurnState()); stdJudge.process(); nCases++; // cleanup: remove turnstates from world world.removeAllTurnStates(); // cleanup: clear results in currentTurnSTate // this is absolutely essential!! currentCase.getCurrentTurnState().getResultList().clear(); } } } else { // 'typical' mode (testing). // we keep stats and may or may not have logging // for(int ccn=0; ccn<allCases.length; ccn++) { Case currentCase = allCases[ccn]; // world: setup world.setTurnState(currentCase.getCurrentTurnState()); world.setTurnState(currentCase.getPreviousTurnState()); // print case name println("\n\n"); println("=CASE=================================================================="); println(" ", currentCase.getName()); // print pre-state printState(currentCase); // print orders println("=ORDERS================================================================"); printOrders(currentCase); nOrders += currentCase.getOrders().length; // adjudicate println("=ADJUDICATION=========================================================="); stdJudge = new StdAdjudicator(OrderFactory.getDefault(), currentCase.getCurrentTurnState()); stdJudge.process(); // print adjudication results, if not performance testing // also print & check post conditions, if not performance testing if(!isAdjudicatorLogged) { println(" [adjudicator logging disabled]"); } // add unresolved paradoxes to list, so we know which cases they are if(stdJudge.isUnresolvedParadox()) { unRezParadoxes.add(currentCase.getName()); } println("=ADJUDICATION RESULTS=================================================="); if(stdJudge.getNextTurnState() == null) { println("=NEXT PHASE: NONE. Game has been won."); } else { println("=NEXT PHASE: ", stdJudge.getNextTurnState().getPhase()); } List resultList = stdJudge.getTurnState().getResultList(); Iterator resultIter = resultList.iterator(); while(resultIter.hasNext() && isLogging) { Result r = (Result) resultIter.next(); println(" ", r); } // check post conditions println("=POST-STATE============================================================"); if(compareState(currentCase, stdJudge.getNextTurnState())) { nPass++; } else { nFail++; failedCaseNames.add(currentCase.getName()); } println("======================================================================="); nCases++; // cleanup: remove turnstates from world world.removeAllTurnStates(); // cleanup: clear results in currentTurnSTate // this is absolutely essential!! currentCase.getCurrentTurnState().getResultList().clear(); } } // print stats // long time = System.currentTimeMillis() - startMillis; // end timing! println("End: ",new Date()); // total time: includes setup/adjudication/comparison float orderTime = (float) time / (float) nOrders; float thruPut = 1000.0f / orderTime; float score = (float) nPass / (float) nCases * 100.0f; println("\nFailed Cases:"); println("============="); Iterator iter = failedCaseNames.iterator(); while(iter.hasNext()) { println(" ", iter.next()); } println(" [total: "+failedCaseNames.size()+"]"); println("\nUnresolved Paradoxes:"); println("====================="); iter = unRezParadoxes.iterator(); while(iter.hasNext()) { println(" " + ((String) iter.next()) ); } println(" [total: ", unRezParadoxes.size(), "]"); // print to log println("\nStatistics:"); println("==========="); println(" Case parse time: "+parseTime+" seconds."); if(isPerfTest) { println(" "+nCases+" cases evaluated. Pass/Fail rate not available with -perftest option."); println(" Adjudication Performance for "+benchTimes+" iterations:"); } else { println(" "+nCases+" cases evaluated. "+nPass+" passed, "+nFail+" failed; "+score+"% pass rate."); println(" Times [includes setup, adjudication, and post-adjudication comparision]"); } println(" "+nOrders+" orders processed in "+time+" ms; "+orderTime+" ms/order average"); println(" Throughput: "+thruPut+" orders/second"); // if in 'brief' mode, only print out summary statistics if(!isLogging) { System.out.println("\nStatistics for \""+inFileName+"\":"); System.out.println(" Case parse time: "+parseTime+" seconds."); if(isPerfTest) { System.out.println(" "+nCases+" cases evaluated. Pass/Fail rate not available with -perftest option."); System.out.println(" Adjudication Performance for "+benchTimes+" iterations:"); } else { System.out.println(" "+nCases+" cases evaluated. "+nPass+" passed, "+nFail+" failed; "+score+"% pass rate."); System.out.println(" Times [includes setup, adjudication, and post-adjudication comparision]"); } System.out.println(" "+nOrders+" orders processed in "+time+" ms; "+orderTime+" ms/order average"); System.out.println(" Throughput: "+thruPut+" orders/second"); if(isPerfTest) { printPerfStatsBrief(benchTimes, nOrders, time, thruPut); } } // exit System.exit(nFail); }// evaluate() /** Briefly print performance stats for cut/paste */ private void printPerfStatsBrief(int nIter, int nOrder, float timeTotal, float thruput) { StringBuffer sb = new StringBuffer(); sb.append("**\t"); // start line; asterisks // file name only [no path] File file = new File(inFileName); sb.append(file.getName()); sb.append("\t"); // # of iterations sb.append(nIter); sb.append("\t"); // # of orders sb.append(nOrder); sb.append("\t"); // total time (ms) sb.append(timeTotal); sb.append("\t"); // thruput (orders / second) sb.append(thruput); sb.append("\t"); System.out.println(sb); } // prints state settings... private void printState(Case c) { if(!isLogging) { return; } TurnState turnState = c.getCurrentTurnState(); //Position position = turnState.getPosition(); println("=PHASE================================================================="); println(" ", turnState.getPhase()); // if we have some results to display, for prior state, do that now. if(isLogging && c.getResults().length > 0) { // print println("=PRESTATE_RESULTS======================================================"); println(" From ", c.getPreviousTurnState().getPhase()); OrderResult[] or = c.getResults(); for(int i=0; i<or.length; i++) { println(" ", or[i]); } } // print non-dislodged units if(c.getPreState().length > 0) { println("=PRE-STATE============================================================="); DefineState[] dsOrds = c.getPreState(); for(int i=0; i<dsOrds.length; i++) { println(" ", dsOrds[i]); } } // print dislodged units if(c.getPreDislodged().length > 0) { println("=PRE-STATE DISLODGED==================================================="); DefineState[] dsOrds = c.getPreDislodged(); for(int i=0; i<dsOrds.length; i++) { println(" ", dsOrds[i]); } } }// printState() /** Prints the orders in a case */ private void printOrders(Case currentCase) { if(isLogging) { Order[] orders = currentCase.getOrders(); for(int i=0; i<orders.length; i++) { println(" ", orders[i].toString()); } if(orders.length == 0) { println(" [none]"); } } }// printOrders() /** * compareState: checks to see if resolved state matches, * unit for unit, the Case POSTSTATEs. Units that match * are prepended with an '='. Units that are not found in the * case POSTSTATE/POSTSTATE_DISLODGED are prepended with a '+', * and units in POSTSTATE/POSTSTATE_DISLODGED not found in * the resolved turnstate are prepended with a '-'. * <p> * This is a more strict comparison than the old compareState, * w.r.t. dislodged units and coast checking. The implementation * is fairly simple and is not optimized for performance. * <p> * If no POSTSTATE or POSTSTATE_DISLODGED results are given, * it is assumed that there are no units for the omitted section. * <p> * Returns true if the states match (or game has been won); * otherwise, returns false. */ private boolean compareState(Case c, TurnState resolvedTS) { // special case: check for a win. if(resolvedTS == null) { println("The game has been won. No new TurnState object is created."); return true; } final Position pos = resolvedTS.getPosition(); // create set of resolvedUnits // Set resolvedUnits = new HashSet(); Province[] provs = pos.getUnitProvinces(); for(int i=0; i<provs.length; i++) { if(!resolvedUnits.add(new UnitPos(pos, provs[i], false))) { throw new IllegalStateException("CompareState: Internal error (non dislodged)"); } } provs = pos.getDislodgedUnitProvinces(); for(int i=0; i<provs.length; i++) { if(!resolvedUnits.add(new UnitPos(pos, provs[i], true))) { throw new IllegalStateException("CompareState: Internal error (dislodged)"); } } resolvedUnits = Collections.unmodifiableSet(resolvedUnits); // for safety // create set of caseUnits // Set caseUnits = new HashSet(); DefineState[] dsOrds = c.getPostState(); for(int i=0; i<dsOrds.length; i++) { if(!caseUnits.add(new UnitPos(dsOrds[i], false))) { println("ERROR: duplicate POSTSTATE position: "+dsOrds[i]); return false; } } dsOrds = c.getPostDislodged(); for(int i=0; i<dsOrds.length; i++) { if(!caseUnits.add(new UnitPos(dsOrds[i], true))) { println("ERROR: duplicate POSTSTATE_DISLODGED position: "+dsOrds[i]); return false; } } caseUnits = Collections.unmodifiableSet(caseUnits); // for safety // compare sets. // // first, we must make a duplicate of one set. // these are the units that are in the correct position (intersection) // Set intersection = new HashSet(caseUnits); intersection.retainAll(resolvedUnits); // now, create subtraction sets Set added = new HashSet(resolvedUnits); added.removeAll(caseUnits); Set missing = new HashSet(caseUnits); missing.removeAll(resolvedUnits); // if subtraction sets have no units, we are done. Otherwise, we must print // the differences. // if(!missing.isEmpty() || !added.isEmpty()) { println(" CompareState: FAILED: unit positions follow."); // print adds printSet(added, "+"); // print subtracts printSet(missing, "-"); // print units in correct position printSet(intersection, "="); return false; } else { println(" CompareState: PASSED"); } return true; }// compareState() /** Print all the UnitPos objects from a Set; prefixing with the given prefix */ private void printSet(Set set, String prefix) { Iterator iter = set.iterator(); while(iter.hasNext()) { UnitPos up = (UnitPos) iter.next(); StringBuffer sb = new StringBuffer(64); sb.append(" "); // spacer sb.append(prefix); sb.append(" "); sb.append(up); println(sb.toString()); } }// printSet() /** * Private inner class, usually contained in Sets, that * is comparable, for determining if the end-state is * in fact correct. */ private class UnitPos { private final Unit unit; // owner/type/coast private final Province province; // position private final boolean isDislodged; // dislodged? /** Create a UnitPos */ public UnitPos(DefineState ds, boolean isDislodged) { this.unit = new Unit(ds.getPower(), ds.getSourceUnitType()); unit.setCoast(ds.getSource().getCoast()); this.province = ds.getSource().getProvince(); this.isDislodged = isDislodged; }// UnitPos() /** Create a UnitPos */ public UnitPos(Position pos, Province prov, boolean isDislodged) { this.province = prov; this.isDislodged = isDislodged; this.unit = (isDislodged) ? pos.getDislodgedUnit(prov): pos.getUnit(prov); if(this.unit == null) { throw new IllegalArgumentException(); } }// UnitPos() /** Print */ public String toString() { StringBuffer sb = new StringBuffer(32); sb.append(unit.getPower().getName()); sb.append(' '); sb.append(unit.getType().getShortName()); sb.append(' '); sb.append(province.getShortName()); sb.append('/'); sb.append(unit.getCoast().getAbbreviation()); if(isDislodged) { sb.append(" [DISLODGED]"); } return sb.toString(); }// toString() /** Compare */ public boolean equals(Object obj) { if(obj instanceof UnitPos) { UnitPos up = (UnitPos) obj; if( isDislodged == up.isDislodged && province == up.province && unit.equals(up.unit) ) { return true; } } return false; }// equals() /** Force all hashes to be the same, so equals() is used */ public int hashCode() { return 0; // very very bad! just an easy shortcut } }// inner class UnitPos /** Holds a Case */ private final class Case { private DefineState[] preState = null; private DefineState[] postState = null; private DefineState[] preDislodged = null; private DefineState[] postDislodged = null; private DefineState[] supplySCOwners = null; // all types are 'army' private OrderResult[] results = null; private Order[] orders = null; private String name; private Phase phase = null; private OrderParser of = null; private TurnState currentTS = null; private TurnState previousTS = null; // tsTemplate: template turnstate to create the current, and (if needed) previous // turnstates. public Case(String name, String phaseName, List pre, List ord, List post, List supplySCOwnersList, List preDislodgedList, List postDislodgedList, List orderResultList) { this.name = name; List<Serializable> temp = new ArrayList<Serializable>(50); Iterator iter = null; of = OrderParser.getInstance(); // phase if(phaseName != null) { phase = Phase.parse(phaseName); if(phase == null) { System.out.println("ERROR: case "+name); System.out.println("ERROR: cannot parse phase "+phaseName); System.exit(1); } } // set phase to template phase, if no phase was assigned. phase = (phaseName == null) ? templateTurnState.getPhase() : phase; // setup current turnstate from template // use phase, if appropriate. currentTS = new TurnState(phase); currentTS.setPosition(templateTurnState.getPosition().cloneExceptUnits()); currentTS.setWorld(world); // setup previous phase, in case we need it. previousTS = new TurnState(phase.getPrevious()); previousTS.setPosition(templateTurnState.getPosition().cloneExceptUnits()); previousTS.setWorld(world); // pre temp.clear(); iter = pre.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); Order order = parseOrder(line, currentTS, true); temp.add(order); } preState = (DefineState[]) temp.toArray(new DefineState[temp.size()]); // ord temp.clear(); iter = ord.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); Order order = parseOrder(line, currentTS, false); temp.add(order); } orders = (Order[]) temp.toArray(new Order[temp.size()]); // post temp.clear(); iter = post.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); Order order = parseOrder(line, currentTS, true); temp.add(order); } postState = (DefineState[]) temp.toArray(new DefineState[temp.size()]); // prestate dislodged if(preDislodgedList != null) { temp.clear(); iter = preDislodgedList.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); Order order = parseOrder(line, currentTS, true); temp.add(order); } this.preDislodged = (DefineState[]) temp.toArray(new DefineState[temp.size()]); } // poststate dislodged if(postDislodgedList != null) { temp.clear(); iter = postDislodgedList.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); Order order = parseOrder(line, currentTS, true); temp.add(order); } this.postDislodged = (DefineState[]) temp.toArray(new DefineState[temp.size()]); } // supply-center owners if(supplySCOwnersList != null) { temp.clear(); iter = supplySCOwnersList.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); Order order = parseOrder(line, currentTS, true); temp.add(order); } this.supplySCOwners = (DefineState[]) temp.toArray(new DefineState[temp.size()]); } // OrderResults // // THE BEST way to do this would be to setup the case, and then run // the adjudicator to get the results, checking the ajudicator results // against the 'prestate' positions. This way we would have all the same // results that the adjudicator would normally generate. // if(orderResultList != null) { temp.clear(); iter = orderResultList.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); OrderResult.ResultType ordResultType = null; // success or failure?? if(line.startsWith("success")) { ordResultType = OrderResult.ResultType.SUCCESS; } else if(line.startsWith("failure")) { ordResultType = OrderResult.ResultType.FAILURE; } else { System.out.println("ERROR"); System.out.println("case: "+name); System.out.println("line: "+line); System.out.println("PRESTATE_RESULTS: must prepend orders with \"SUCCESS:\" or \"FAILURE:\"."); System.exit(1); } // remove after first colon, and parse the order line = line.substring( line.indexOf(':')+1 ); Order order = parseOrder(line, previousTS, false); // was order a convoyed move? because then we have to add a // convoyed move result. // if(order instanceof Move) { Move mv = (Move) order; if( mv.isConvoying() ) { // NOTE: we cheat; path src/dest ok, middle is == src Province[] path = new Province[3]; path[0] = mv.getSource().getProvince(); path[1] = path[0]; path[2] = mv.getDest().getProvince(); temp.add(new ConvoyPathResult(order, path)); } } // create/add order result temp.add(new OrderResult(order, ordResultType, " (prestate)")); } this.results = (OrderResult[]) temp.toArray(new OrderResult[temp.size()]); // add results to previous turnstate previousTS.setResultList(new ArrayList<Serializable>(temp)); // add positions/ownership/orders to current turnstate // // add orders, first clearing any existing orders in the turnstate currentTS.clearAllOrders(); for(int i=0; i<orders.length; i++) { ArrayList orderList = currentTS.getOrders(orders[i].getPower()); orderList.add(orders[i]); currentTS.setOrders(orders[i].getPower(), orderList); } // get position Position position = currentTS.getPosition(); // ensure all powers are active Power[] powers = world.getMap().getPowers(); for(int i=0; i<powers.length; i++) { position.setEliminated(powers[i], false); } // Add non-dislodged units for(int i=0; i<preState.length; i++) { Unit unit = new Unit(preState[i].getPower(), preState[i].getSourceUnitType()); unit.setCoast(preState[i].getSource().getCoast()); position.setUnit(preState[i].getSource().getProvince(), unit); } // Add dislodged units for(int i=0; i<preDislodged.length; i++) { Unit unit = new Unit(preDislodged[i].getPower(), preDislodged[i].getSourceUnitType()); unit.setCoast(preDislodged[i].getSource().getCoast()); position.setDislodgedUnit(preDislodged[i].getSource().getProvince(), unit); } // Set supply center owners // if we have ANY supply center owners, we erase the template // if we do not have any, we assume the template is correct // no need to validate units if(supplySCOwners.length > 0) { // first erase old info final Province[] provinces = position.getProvinces(); for(int i=0; i<provinces.length; i++) { Province province = provinces[i]; if(position.hasSupplyCenterOwner(province)) { position.setSupplyCenterOwner(province, null); } } // add new info for(int i=0; i<supplySCOwners.length; i++) { position.setSupplyCenterOwner(supplySCOwners[i].getSource().getProvince(), supplySCOwners[i].getPower()); } } } }// Case() public String getName() { return name; } public DefineState[] getPreState() { return preState; } public DefineState[] getPostState() { return postState; } public DefineState[] getPreDislodged() { return preDislodged; } public DefineState[] getPostDislodged() { return postDislodged; } public DefineState[] getSCOwners() { return supplySCOwners; } public Phase getPhase() { return phase; } public Order[] getOrders() { return orders; } public OrderResult[] getResults() { return results; } public TurnState getCurrentTurnState() { return currentTS; } public TurnState getPreviousTurnState() { return previousTS; } private Order parseOrder(String s, TurnState ts, boolean isDefineState) { try { // no guessing (but not locked); we must ALWAYS specify the power. Order o = of.parse(OrderFactory.getDefault(), s, null, ts, false, false); if(isDefineState) { if(o instanceof DefineState) { // we just want to check if the DefineState order does not have // an undefined coast for a fleet unit. Location newLoc = o.getSource().getValidatedSetup(o.getSourceUnitType()); // create a new DefineState with a validated loc o = OrderFactory.getDefault().createDefineState(o.getPower(), newLoc, o.getSourceUnitType()); } else { throw new OrderException("A DefineState order is required here."); } } return o; } catch(OrderException e) { System.out.println("ERROR"); System.out.println("parseOrder() OrderException: "+e); System.out.println("Case: "+name); System.out.println("failure line: "+s); System.exit(1); } return null; }// parseOrder() }// class Case // NEW case parser private void parseCases(File caseFile) { BufferedReader br = null; // per case data that is NOT in List format String caseName = null; String phaseName = null; boolean inCase = false; // we are in a CASE // setup reader try { br = new BufferedReader(new FileReader(caseFile)); } catch(IOException e) { System.out.println("ERROR: I/O error opening case file \""+caseFile+"\""); System.out.println("EXCEPTION: "+e); System.exit(1); } try { String rawLine = br.readLine(); String currentKey = null; int lineCount = 1; while(rawLine != null) { String line = filterLine(rawLine); String key = getKeyType(line); if(key != null) { currentKey = key; } // only process non-null (after filtering) if(line != null) { if(currentKey == null) { // this can occur if a key is missing. System.out.println("ERROR: missing a required key"); System.out.println("Line "+lineCount+": "+rawLine); System.exit(1); } else if(currentKey.equals(VARIANT_ALL)) { // make sure nothing is defined yet if(variantName == null) { variantName = getAfterKeyword(line); } else { System.out.println("ERROR: before cases are defined, the variant must"); System.out.println(" be set with the VARIANT_ALL flag."); System.exit(1); } // make sure we are not in a case! if(inCase) { System.out.println("ERROR: VARIANT_ALL cannot be used within a CASE."); System.exit(1); } // attempt to initialize the variant initVariant(); } else if(currentKey.equals(CASE)) { // begin a case; case name appears after keyword // // clear data inCase = true; clearAndSetupKeyMap(); caseName = null; phaseName = null; currentKey = null; // set case name caseName = getAfterKeyword(line); // make sure we have defined a variant! if(variantName == null) { System.out.println("ERROR: before cases are defined, the variant must"); System.out.println(" be set with the VARIANT_ALL flag."); System.exit(1); } } else if(currentKey.equals(END)) { // end a case inCase = false; // create the case Case aCase = new Case(caseName, phaseName, getListForKeyType(PRESTATE), // prestate getListForKeyType(ORDERS), // orders getListForKeyType(POSTSTATE), // poststate getListForKeyType(PRESTATE_SUPPLYCENTER_OWNERS), // pre-state: sc owners getListForKeyType(PRESTATE_DISLODGED), // pre-dislodged getListForKeyType(POSTSTATE_DISLODGED), // post-dislodged getListForKeyType(PRESTATE_RESULTS) // results (of prior phase) ); cases.add(aCase); } else { if(inCase) { if(currentKey.equals(POSTSTATE_SAME)) { // just copy prestate data List list = getListForKeyType(POSTSTATE); list.addAll( getListForKeyType(PRESTATE) ); } else if(currentKey.equals(PRESTATE_SETPHASE)) { // phase appears after keyword phaseName = getAfterKeyword(line); } else if(key == null) // important: we don't want to add key lines to the lists { // we need to get a list. List list = getListForKeyType(currentKey); list.add(line); } } else { System.out.println("ERROR: line not enclosed within a CASE."); System.out.println("Line "+lineCount+": "+rawLine); System.exit(1); } } } rawLine = br.readLine(); lineCount++; }// while() } catch(IOException e) { println("ERROR: I/O error reading case file \"", caseFile, "\""); println("EXCEPTION: ", e); System.exit(1); } finally { if(br != null) { try { br.close(); } catch(IOException e2) {} } } println(" parsed "+cases.size()+" cases."); }// parseCases() // returns null if string is a comment line. private String filterLine(String in) { // remove whitespace String out = in.trim(); // find comment-character index, if it exists int ccIdx = out.indexOf('#'); // if entire line is a comment, or empty, return COMMENT_LINE now if(ccIdx == 0 || out.length() < 1) { return null; } // remove 'trailing' comments, if any // otherwise, it could interfere with order processing. if(ccIdx > 0) { out = out.substring(0, ccIdx); } // convert to lower case(); out = out.toLowerCase(); return out; }// filterLine // find first space this works, because the // preceding whitespace before a keyword has already been trimmed private String getAfterKeyword(String in) { int idxSpace = in.indexOf(' '); int idxTab = in.indexOf('\t'); if(idxSpace == -1 && idxTab == -1) { return null; } int idx = 0; if(idxSpace == -1 || idxTab == -1) { idx = (idxSpace > idxTab) ? idxSpace : idxTab; // return greater } else { idx = (idxSpace < idxTab) ? idxSpace : idxTab; // return lesser } return in.substring(idx+1); }// getAfterKeyword() private void clearAndSetupKeyMap() { if(keyMap == null) { keyMap = new HashMap(23); } keyMap.clear(); for(int i=0; i<KEY_TYPES_WITH_LIST.length; i++) { keyMap.put(KEY_TYPES_WITH_LIST[i], new LinkedList()); } }// setupKeyMap() /* returns: true key type type */ private String getKeyType(String line) { if(line == null) { return null; } if(line.startsWith(CASE)) { return CASE; } else if(line.startsWith(END)) { return END; } else { for(int i=0; i<KEY_TYPES_OTHER.length; i++) { if(line.startsWith(KEY_TYPES_OTHER[i])) { return KEY_TYPES_OTHER[i]; } } } return null; }// getKeyType() private List getListForKeyType(String keyType) { return (List) keyMap.get(keyType); }// getListForKeyType() // fast internal logging // this allows just references to be passed. Although references are copied, this is a // fast operation in java. If logging is turned off, no string concatenation need be // performed. /* -perftest speed increased from 550 orders/second to about 700 orders/second. That's a HUGE speedup (~33%); this is just on the DATC.txt test case set. */ private static final void println(String s1) { if(isLogging) { System.out.println(s1); } } private static final void println(String s1, int i1) { if(isLogging) { StringBuffer sb = new StringBuffer(256); sb.append(s1); sb.append(i1); System.out.println(sb.toString()); } } private static final void println(String s1, int i1, String s2) { if(isLogging) { StringBuffer sb = new StringBuffer(256); sb.append(s1); sb.append(i1); sb.append(s2); System.out.println(sb.toString()); } } private static final void println(String s1, Object o2) { if(isLogging) { StringBuffer sb = new StringBuffer(256); sb.append(s1); sb.append(o2); System.out.println(sb.toString()); } } private static final void println(String s1, Object o2, Object o3) { if(isLogging) { StringBuffer sb = new StringBuffer(256); sb.append(s1); sb.append(o2); sb.append(o3); System.out.println(sb.toString()); } } private static final void println(String s1, Object o2, Object o3, Object o4) { if(isLogging) { StringBuffer sb = new StringBuffer(256); sb.append(s1); sb.append(o2); sb.append(o3); sb.append(o4); System.out.println(sb.toString()); } } }// class TestSuite