// // @(#)Path.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.world; import dip.order.Orderable; import dip.order.Convoy; import dip.order.Move; import dip.process.Adjudicator; import dip.process.OrderState; import dip.process.Tristate; import java.util.*; /** * Determines Convoy paths between points on a Map, and also minimum distances * between two map points. * <p> * This class is undergoing a transition, and most of the current method will probably * be replaced by the static methods based on findAllSeaPaths(). * */ public class Path extends Object { private final Position position; private final Adjudicator adjudicator; /** Create a new Path object */ public Path(Position position) { this.adjudicator = null; this.position = position; }// Path() /** * Create a new Path object. * <p> * Note: this constructor is required for SuperConvoyPathEvaluator / * LegalConvoyPathEvaluator which are used by ANY non-theoretical * convoy route evaluator. */ public Path(Adjudicator adjudicator) { this.adjudicator = adjudicator; this.position = adjudicator.getTurnState().getPosition(); }// Path() /** * Convenience Method. * <p> * This is analagous to other getConvoyRouteEvaluation() methods, however, * it will work for both Implicit (jDip finds a path) and Explicit (paths * were specified in the Move order, judge-style) paths in a Move order. * <p> * This decides whether to use implicit or explicit paths based on if * Move.getConvoyRoutes() returns null or not. * <p> * The actual path taken will be returned in the actualPath argument. * <p> * Note that the invalidLoc (2000 rule support) and actualPath arguments * may be null * <p>. * <h3>Returns:</h3> * <ul> * <li>Tristate.FAILURE<br> * no path exists from src-dest, or a convoy was dislodged / failed on this route.</li> * * <li>Tristate.UNCERTAIN<br> * a path exists, but, the status of the convoy units is not yet determined.</li> * * <li>Tristate.SUCCESS<br> * a path exists, and all Convoy orders along that path are SUCCESS. * note that if multiple paths exist, only one path has to be successful * for this to work.</li> * </ul> */ public Tristate getConvoyRouteEvaluation(Move move, Location invalidLoc, List actualPath) { if(move == null) { throw new IllegalArgumentException(); } final List explicitRoutes = move.getConvoyRoutes(); if(explicitRoutes == null) { // implicit paths. return getConvoyRouteEvaluation(move.getSource(), move.getDest(), invalidLoc, actualPath); } else { // explicit path(s). Evaluate them all. Return the successful path // if one is successful. // final Province invalidProvince = (invalidLoc == null) ? null : invalidLoc.getProvince(); final Location src = move.getSource(); final Location dest = move.getDest(); boolean hasUncertainRoute = false; // true if >= 1 route is uncertain, but not failed. Iterator iter = explicitRoutes.iterator(); while(iter.hasNext()) { final Province[] route = (Province[]) iter.next(); boolean isFailed = true; boolean isUncertain = false; for(int i=1; i<(route.length - 1); i++) { final Province province = route[i]; OrderState os = adjudicator.findOrderStateBySrc( province ); Orderable order = os.getOrder(); if(order instanceof Convoy) { Convoy convoy = (Convoy) order; if( convoy.getConvoySrc().isProvinceEqual(src) && convoy.getConvoyDest().isProvinceEqual(dest) ) { if(province.equals(invalidProvince)) { isFailed = true; break; } final Tristate evalState = os.getEvalState(); // if 'invalidLoc' (invalidProvince) is on the path, // it is not successfull. if(!province.equals(invalidLoc)) { if(evalState == Tristate.FAILURE || os.getDislodgedState() == Tristate.YES) { isFailed = true; break; } else if(evalState == Tristate.UNCERTAIN) { isUncertain = true; break; } else if(evalState == Tristate.SUCCESS) { isUncertain = false; isFailed = false; } else { throw new IllegalStateException(); } } } else { isFailed = true; break; } } else { isFailed = true; break; } } // if we found a successful route, we don't need to check other routes // return success. Return path, too. if(!isFailed && !isUncertain) { if(actualPath != null) { actualPath.addAll( Arrays.asList(route) ); } return Tristate.SUCCESS; } // if uncertain, and not yet set, set uncertain flag. hasUncertainRoute = (hasUncertainRoute || isUncertain); } // we would have returned SUCCESS by now, if successful. // so, if we have not uncertain routes, we fail. // return (hasUncertainRoute) ? Tristate.UNCERTAIN : Tristate.FAILURE; } }// getConvoyRouteEvaluation() /** * Checks a convoy route to see if it is 'theoretically' valid; this is true iff: * <ol> * <li>route is non-null and length of 3 or more * <li>first and last route provinces match src and dest Location provinces </li> * <li>first and last provinces are land</li> * <li>all other provinces are convoyable (sea or convoyable coast)</li> * <li>all convoyable locations have a fleet present</li> * <li>route is composed of adjacent spaces</li> * </ol> * */ public static boolean isRouteValid(final Position pos, final Location src, final Location dest, final Province[] route) { if(route == null || route.length < 3) { return false; } if( !src.isProvinceEqual(route[0]) || !dest.isProvinceEqual(route[route.length-1]) ) { return false; } if( !route[0].isLand() || !route[route.length-1].isLand() ) { return false; } Coast lastCoast = src.getCoast(); Province p = route[0]; for(int i=1; i<(route.length-1); i++) { p = route[i]; if(!p.isConvoyable()) { return false; } Unit unit = pos.getUnit(p); if(unit == null || unit.getType() != Unit.Type.FLEET) { return false; } // for the second provice, check only if we can reach the source province (not location!), // as we do not know the source coast! if(((i != 1) && (!p.isAdjacent(unit.getCoast(), new Location(route[i-1], lastCoast)))) || ((i == 1) && (!p.isAdjacent(unit.getCoast(), route[i-1])))) { return false; } lastCoast = unit.getCoast(); } // endpoints adjacency check // check only, if we can reach the destination _province_ from the last location, // as we do not know the destination coast! if(!p.isAdjacent(lastCoast, dest.getProvince())) { return false; } return true; }// isRouteValid() /** * Verifies a route during adjudication stage; * Given a valid route, all fleets must have: * <ol> * <li>a Convoy order, with convoy src/dest matching the src/dest given</li> * <li>the evaluation state of the Convoy order must not be Tristate.FAILURE</li> * </ol> */ public static boolean isRouteLegal(final Adjudicator adj, final Province[] route) { final Province src = route[0]; final Province dest = route[route.length-1]; for(int i=1; i<(route.length-1); i++) { OrderState os = adj.findOrderStateBySrc( route[i] ); final Orderable order = os.getOrder(); if(order instanceof Convoy) { final Convoy convoy = (Convoy) order; if( convoy.getConvoySrc().isProvinceEqual(src) && convoy.getConvoyDest().isProvinceEqual(dest) ) { if( os.getEvalState() == Tristate.FAILURE || os.getDislodgedState() == Tristate.YES) { return false; } } } } return true; }// isRouteLegal() /** * Determines the adjudication status of static convoy route(s), * that are both valid and legal (verified). * Analagous to getConvoyRouteEvaluation() * <p> * See getConvoyRouteEvaluation() for return values. */ public static Tristate evaluateRoutes(final Adjudicator adj, List routes, final Location invalid) { final Province invalidProvince = (invalid == null) ? null : invalid.getProvince(); Tristate overallResult = Tristate.FAILURE; for(int routeIdx=0; routeIdx<routes.size(); routeIdx++) { final Province[] route = (Province[]) routes.get(routeIdx); final Province src = route[0]; final Province dest = route[route.length-1]; Tristate result = null; for(int i=1; i<(route.length-1); i++) { Province province = route[i]; OrderState os = adj.findOrderStateBySrc( province ); Orderable order = os.getOrder(); if(order instanceof Convoy) { Convoy convoy = (Convoy) order; // only consider matching orders, // and only consider orders that don't involve the invalidProvince // if( convoy.getConvoySrc().isProvinceEqual(src) && convoy.getConvoyDest().isProvinceEqual(dest) && (invalidProvince != province) ) { Tristate evalState = os.getEvalState(); if(evalState == Tristate.FAILURE || os.getDislodgedState() == Tristate.YES) { result = Tristate.FAILURE; break; } else if(evalState == Tristate.UNCERTAIN) { result = Tristate.UNCERTAIN; break; } else if(evalState == Tristate.SUCCESS) { result = Tristate.SUCCESS; } else { // this is not strictly needed. throw new IllegalStateException("evaluateRoute(): internal error"); } } } }// for(route) assert(result != null); // if we have one successful route, return success; no others need be checked // if we have an uncertain result, return uncertain // if all have failed, return failure. if(result == Tristate.SUCCESS) { return Tristate.SUCCESS; } else { // result at this point cannot be SUCCESS; only UNCERTAIN or FAILURE // we cannot return with an 'uncertain' result, because we may have a // subsequent 'success' result later. assert(result != Tristate.SUCCESS); overallResult = (result == Tristate.UNCERTAIN) ? Tristate.UNCERTAIN : Tristate.FAILURE; } } return overallResult; }// evaluateRoute /** * Returns if there is a "theoretical" convoy * route between src and dest. * <p> * A theoretical convoy route is a string of adjacent fleets that * could convoy the desired army from src to dest, but may not * have convoy orders to do so. */ public boolean isPossibleConvoyRoute(Location src, Location dest) { if(src.getProvince().isCoastal() && dest.getProvince().isCoastal()) { List path = new ArrayList(12); PathEvaluator pe = new AnyConvoyPathEvaluator(); return findPathBreadthFirst(src, dest, src, path, pe); } return false; }// isPossibleConvoyRoute() /** * Returns a path (the first valid path) of a theoretical convoy * route between src and dest. * <p> * A theoretical convoy route is a string of adjacent fleets that * could convoy the desired army from src to dest, but may not * have convoy orders to do so. */ public List getConvoyRoute(Location src, Location dest) { List path = new ArrayList(); PathEvaluator pe = new AnyConvoyPathEvaluator(); findPathBreadthFirst(src, dest, src, path, pe); return path; }// getConvoyRoute() /** * Find if a true convoy route exists between src & dest; all fleets must * have: * <ol> * <li>a Convoy order, with convoy src/dest matching the src/dest given</li> * <li>the evaluation state of the Convoy order must not be Tristate.FAILURE</li> * </ol> * */ public boolean isLegalConvoyRoute(Location src, Location dest) { List path = new ArrayList(12); PathEvaluator pe = new LegalConvoyPathEvaluator(src, dest); return findPathBreadthFirst(src, dest, src, path, pe); }// isLegalConvoyRoute() /** * Returns the true convoy route between src & dest; all fleets must * have: * <ol> * <li>a Convoy order, with convoy src/dest matching the src/dest given</li> * <li>the evaluation state of the Convoy order must not be Tristate.FAILURE</li> * </ol> * */ public List getLegalConvoyRoute(Location src, Location dest) { List path = new ArrayList(12); PathEvaluator pe = new LegalConvoyPathEvaluator(src, dest); findPathBreadthFirst(src, dest, src, path, pe); return path; }// isLegalConvoyRoute() /** * Convenience version of getConvoyRouteEvaluation() where the * 'invalid' Location is set to null. * */ public Tristate getConvoyRouteEvaluation(Location src, Location dest, List validPath) { return getConvoyRouteEvaluation(src, dest, null, validPath); }// getConvoyRouteEvaluation() /** * The convoying fleet specified in 'invalid' is considered * to fail. (this is for implementing the 2000 rule / multi-route-convoys) * <p> * Thus, Say we have the following:<br> * <code>path: a-b-c</code><br> * if a, b, OR c is marked as 'invalid', we do not have a complete path, thus false results. * <p> * But, if we have 2 paths:<br> * <code>path 1: a-b-c</code><br> * <code>path 2: a-d-c</code><br> * if a or c is invalid, path will fail (false) returned. * if b OR d is invalid (but a & c are good), path will succeed, since an alternate path exists. * This will return true unless both b & d are invalid * <p> * * <h3>Returns:</h3> * <ul> * <li>Tristate.FAILURE<br> * a) no path exists from src-dest, or a convoy was dislodged / failed on this route.</li> * * <li>Tristate.UNCERTAIN<br> * a) a path exists, but, the status of the convoy units is not yet determined.</li> * * <li>Tristate.SUCCESS<br> * a) a path exists, and all Convoy orders along that path are SUCCESS. * note that if multiple paths exist, only one path has to be successful * for this to work.</li> * </ul> * <p> * There is an optional argument, validPath. If a List is supplied, and the convoy route is * successful, the path taken will be returned in this List. If this argument is null, * it will be ignored. The returned List will contain only Province objects. * <p> * <h3>Algorithm:</h3> * we must call SuperConvoyPath evaluator twice. The first time, we check for * successes, by not counting "uncertain" convoys. This is because we cannot * easily distinguish a successfull path from an uncertain path. * <p> * The second time through (if we were not succesful), we check to see if we * fail (no path), or if we are uncertain. * */ public Tristate getConvoyRouteEvaluation(Location src, Location dest, Location invalid, List validPath) { List path = new ArrayList(12); SuperConvoyPathEvaluator spe = null; boolean isPathFound = false; // 1st pass: look for a successful route only. spe = new SuperConvoyPathEvaluator(src, dest, invalid, false); isPathFound = findPathBreadthFirst(src, dest, src, path, spe); if(isPathFound) { // note: our path, if found, may be longer than required (due to // breadth-first search. So iterate until we find the dest. if(validPath != null) { for(int i=0; i<path.size(); i++) { Location loc = (Location) path.get(i); validPath.add(loc.getProvince()); if(dest.isProvinceEqual(loc)) { break; } } } return Tristate.SUCCESS; } // 2nd pass: determine unsuccessful vs. uncertain path.clear(); spe = new SuperConvoyPathEvaluator(src, dest, invalid, true); isPathFound = findPathBreadthFirst(src, dest, src, path, spe); if(isPathFound) { // TODO: assert that isUncertain() is true here. It should be; // otherwise, we would have returned above. return Tristate.UNCERTAIN; } return Tristate.FAILURE; }// getConvoyRouteEvaluation() /** * Generalized recursive Path-Finder, Breadth-First search. * <p> * The path evaluator decides WHICH locations to add to the list of * <p> * The first path found that meets criteria will be in 'path' * * * * */ protected boolean findPathBreadthFirst(Location src, Location dest, Location current, List path, PathEvaluator pathEvaluator) { // Step 1: add current location to path path.add(current); // Step 2: check if current location is adjacent to dest // if so, add it to the path, and return 'true', since we are done. if(pathEvaluator.isAdjacentToDest(current, dest)) { path.add(dest); return true; } // Step 3: find all adjacent locations to the current location. // note that we ONLY add a location if it is ok'd by the PathEvaluator. List adjLocs = new LinkedList(); for(int i=0; i<Coast.ALL_COASTS.length; i++) { Location[] locations = current.getProvince().getAdjacentLocations(Coast.ALL_COASTS[i]); for(int j=0; j<locations.length; j++) { Location testLoc = locations[j]; if(pathEvaluator.evaluate(testLoc)) { adjLocs.add(testLoc); } } } // Step 4: If there are no locations in our adjacency list, // then, we do not have a path if(adjLocs.isEmpty()) { return false; } // Step 5: We have one or more possible routes to check. // If we find that a route is invalid, we will remove it // from adjacency list. Iterator iter = adjLocs.iterator(); while(iter.hasNext()) { Location location = (Location) iter.next(); if(path.contains(location)) { // if adjacent province already in the path, we are going // in circles (or at least backwards)! remove it. iter.remove(); } else { // we haven't yet visited this Location. We will recusively // evaluate this position, and remove this location from the // list iff we return 'false'. if( !findPathBreadthFirst(src, dest, location, path, pathEvaluator) ) { iter.remove(); } } } // Step 6: If there are ANY paths left in the adjacency list, we have // at least one path that may be valid. return !(adjLocs.isEmpty()); }// findPathBreadthFirst() protected static interface PathEvaluator { // see if current location has nesc. requirments // to add it to the path. public boolean evaluate(Location location); // only called to check if we are at the end. public boolean isAdjacentToDest(Location current, Location dest); }// inner interface PathEvaluator protected class AnyConvoyPathEvaluator implements PathEvaluator { // if this is false, then we have not even found a single convoy // and we cannot possibly have a valid path. This is to prevent // us from checking if src.isAdjacent(dest) when src,dest are immediately // adjacent to each other (by land) but we may not even *have* convoy fleets // near enough in the water! private boolean foundConvoy = false; // must have a fleet in the desired area public boolean evaluate(Location location) { Province province = location.getProvince(); Unit unit = position.getUnit(province); if(unit != null && (province.isSea() || province.isConvoyableCoast())) { if(unit.getType() == Unit.Type.FLEET) { final boolean result = evalFleet(province, unit); if(result) { foundConvoy = true; } return result; } } return false; }// evaluate() // must be adjacent by PROVINCE (not coastal) to destination. public boolean isAdjacentToDest(Location current, Location dest) { Province province = current.getProvince(); if(province.isTouching(dest.getProvince()) && foundConvoy) { return true; } return false; }// isAdjacentToDest() // can subclass to do further fleet evaluation // we do no further evaluation with this class. // evaluates the fleet present in this province protected boolean evalFleet(Province province, Unit unit) { return true; }// evalFleet() }// inner class AnyConvoyPathEvaluator private class LegalConvoyPathEvaluator extends AnyConvoyPathEvaluator { private Location src = null; private Location dest = null; // set Src and Dest of path, so we can evaluate fleet orders public LegalConvoyPathEvaluator(Location src, Location dest) { if(adjudicator == null) { throw new IllegalStateException("null adjudicator in path"); } this.src = src; this.dest = dest; }// LegalConvoyPathEvaluator() // override: check fleet orders protected boolean evalFleet(Province province, Unit unit) { OrderState os = adjudicator.findOrderStateBySrc( province ); Orderable order = os.getOrder(); if(order instanceof Convoy) { Convoy convoy = (Convoy) order; if( convoy.getConvoySrc().isProvinceEqual(src) && convoy.getConvoyDest().isProvinceEqual(dest) ) { if(os.getEvalState() != Tristate.FAILURE && os.getDislodgedState() != Tristate.YES) { return true; } } } return false; }// evalFleet() }// inner class LegalConvoyPathEvaluator private class SuperConvoyPathEvaluator extends AnyConvoyPathEvaluator { private Location src = null; private Location dest = null; private Location invalid = null; private boolean isUncertain = false; // if we found one or more uncertains. private boolean isFailure = false; // if we found one or more failures/dislodged private boolean noteUncertains = false; // set Src and Dest of path, so we can evaluate fleet orders public SuperConvoyPathEvaluator(Location src, Location dest, Location invalid, boolean noteUncertains) { if(adjudicator == null) { throw new IllegalStateException("null adjudicator in path"); } this.src = src; this.dest = dest; this.invalid = invalid; this.noteUncertains = noteUncertains; }// SuperConvoyPathEvaluator() // success / failure depends upon if a route can be found. However, if a route // is found, it may be "uncertain". public boolean isUncertain() { return isUncertain; }// getPathStatus() public boolean isFailure() { return isFailure; }// isFailure() // override: check fleet orders protected boolean evalFleet(Province province, Unit unit) { OrderState os = adjudicator.findOrderStateBySrc( province ); Orderable order = os.getOrder(); if(order instanceof Convoy) { Convoy convoy = (Convoy) order; if( convoy.getConvoySrc().isProvinceEqual(src) && convoy.getConvoyDest().isProvinceEqual(dest) ) { // we found a correctly matching Convoy order. // // but, if this order should be ignored ('invalid'), we won't consider it. if(invalid != null) { if(invalid.getProvince() == province) { return false; } } Tristate evalState = os.getEvalState(); if(evalState == Tristate.FAILURE || os.getDislodgedState() == Tristate.YES) { isFailure = true; return false; // not valid for the path } else if(evalState == Tristate.UNCERTAIN) { if(noteUncertains) { isUncertain = true; return true; } else { return false; } } else if(evalState == Tristate.SUCCESS) { return true; // definately valid! } else { // this is not strictly needed. throw new IllegalStateException("path exception: else case reached"); } } } return false; }// evalFleet() }// inner class SuperConvoyPathEvaluator /** * Find shortest distance between src & dest. * Note that this uses 'touching' adjacency, and that * the cost of movement between any adjacent province * is the same. * <p> * This will return -1 in the event that src and dest are * not connected. * <p> * Null src/dest Provinces are not allowed * */ public int getMinDistance(Province src, Province dest) { // simple case: src adjacent to dest if(src == dest) { return 0; } int dist = 0; HashMap visited = new HashMap(119); visited.put(src, Boolean.TRUE); ArrayList toCheck = new ArrayList(32); ArrayList nextToCheck = new ArrayList(32); ArrayList swapTmp = null; toCheck.add(src); while(true) { // inc dist dist++; // iterate toCheck, create nextToCheck list for(int z=0; z<toCheck.size(); z++) { Province p = (Province) toCheck.get(z); if(p == dest) { return dist; } /* OLD CODE: before Coast.WING (TOUCHING) available for(int i=0; i<Coast.ALL_COASTS.length; i++) { Location[] locs = p.getAdjacentLocations(Coast.ALL_COASTS[i]); for(int j=0; j<locs.length; j++) { Province ckp = locs[j].getProvince(); if(visited.get(ckp) == null) { nextToCheck.add(ckp); visited.put(ckp, Boolean.TRUE); } } } */ // NEW CODE: using Coast.TOUCHING Location[] locs = p.getAdjacentLocations(Coast.TOUCHING); for(int i=0; i<locs.length; i++) { Province ckp = locs[i].getProvince(); if(visited.get(ckp) == null) { nextToCheck.add(ckp); visited.put(ckp, Boolean.TRUE); } } // END NEW CODE } // swap lists toCheck.clear(); swapTmp = toCheck; toCheck = nextToCheck; nextToCheck = swapTmp; // test for unconnectedness (remember, we are swapped) if(toCheck.isEmpty()) { return -1; } } }// getMinDistance() //// NEW path finding stuff below here.... /** * Finds all sea paths from src to dest.. * <p> * This will return a zero-length array iff src or dest is not adjacent to * a sea or a convoyable coastal ("sea equivalent") province. * <p> * Otherwise, all possible unique paths are returned, subject to * the evaluation constraints of the FAPEvaluator. * <p> * This is typically very fast. For standard map, gas->lvp takes about * 0.155 ms on a P4/3.0ghz; 10 unique paths are found. More specific * FAPEvaluator methods (e.g., that look for a Fleet) will be faster. */ public static Province[][] findAllSeaPaths(FAPEvaluator evaluator, Province src, Province dest) { // check: src/dest if(!src.isLand() || !dest.isLand()) { return new Province[0][]; } // quick check: dest: next to at least 1 sea/conv coastal province final Location[] dLocs = dest.getAdjacentLocations(Coast.TOUCHING); boolean isOk = false; for(int i=0; i<dLocs.length; i++) { final Province p = dLocs[i].getProvince(); if(p.isConvoyableCoast() || p.isSea()) { isOk = true; break; } } if(!isOk) { return new Province[0][]; } // create the tree. final TreeNode root = new TreeNode(null, src); // breadth-first add of Sea or Convoayble Coastal provinces to the tree, // that are adjacent to the current node. A path cannot use the same // TreeNode more than once, so we use addUniqueChild() to ensure this. // LinkedList queue = new LinkedList(); queue.addLast(root); while( queue.size() > 0 ) { TreeNode node = (TreeNode) queue.removeFirst(); Province prov = node.getProvince(); final Location[] locs = prov.getAdjacentLocations(Coast.TOUCHING); for(int i=0; i<locs.length; i++) { Province p = locs[i].getProvince(); if(p.equals(dest)) { // special case. dest is NOT nescessarily a convoyable coast, // and is not Sea, and should not be evaluated with TreeBuilder. TreeNode newNode = new TreeNode(node, p); node.addUniqueChild(newNode); } else if( (p.isConvoyableCoast() || p.isSea()) && evaluator.evaluate(p) ) { TreeNode newNode = new TreeNode(node, p); if(node.addUniqueChild(newNode)) { queue.addLast(newNode); } } } } // return all paths from root, now that tree is built. return root.getAllBranchesTo(dest); }// findAllSeaPaths() /** * FAPEvaluator class. Determines if a Province should be added * to the tree of possible moves. * <p> * By default, this just returns true. When used with * <code>findAllSeaPaths</code>, this will return <b>all</b> possible * unique convoy paths from the source to the destination. * <p> * For example, if we wanted to know if a unit was present in * the province, we could test for it here in the evaluate() * method. */ public static class FAPEvaluator // FAP == "Find All Paths" { /** Evaluate if a Province should be added. */ public boolean evaluate(Province province) { return true; }// evaluate() }// inner class FAPEvaluator /** * FAPEvaluator that checks to see if there is a Fleet present * in the given Province. This is useful to find theoretical, or * "possible" convoy paths between two locations. */ public static class FleetFAPEvaluator extends FAPEvaluator { private final Position fapPos; /** Create a FleetFAPEvaluator */ public FleetFAPEvaluator(Position pos) { fapPos = pos; }// FleetFAPEvaluator() /** Evaluate if a Province should be added. */ public boolean evaluate(Province province) { return fapPos.hasUnit(province, Unit.Type.FLEET); }// evaluate() }// inner class FleetFAPEvaluator /** * FAPEvaluator that checks to see if there is a Fleet present * in the given Province that has orders to Convoy, and that * convoy order has the given convoy-source and convoy-destination * provinces. * <p> * This additionally checks to make sure that the Convoying Fleet in the * province being evaluated is not dislodged and has not * failed. */ public static class ConvoyFAPEvaluator extends FAPEvaluator { private final Adjudicator adj; private final Province src; private final Province dest; /** Create a ConvoyFAPEvaluator */ public ConvoyFAPEvaluator(Adjudicator adj, Province src, Province dest) { this.adj = adj; this.src = src; this.dest = dest; }// ConvoyFAPEvaluator() /** Evaluate if a Province should be added. */ public boolean evaluate(Province province) { final Position pos = adj.getTurnState().getPosition(); if(pos.hasUnit(province, Unit.Type.FLEET)) { OrderState os = adj.findOrderStateBySrc( province ); final Orderable order = os.getOrder(); if(order instanceof Convoy) { Convoy convoy = (Convoy) order; if( convoy.getConvoySrc().isProvinceEqual(src) && convoy.getConvoyDest().isProvinceEqual(dest) ) { if( os.getEvalState() != Tristate.FAILURE && os.getDislodgedState() != Tristate.YES ) { return true; } } } } return false; }// evaluate() }// inner class ConvoyFAPEvaluator /** Node of a Tree that holds a Location */ private static class TreeNode { private final TreeNode parent; private final Province prov; private final int depth; private List kids; /** Create a TreeNode. Null parent is the root. Null Location not ok. */ public TreeNode(TreeNode parent, Province prov) { if(prov == null) { throw new IllegalArgumentException(); } this.parent = parent; this.prov = prov; this.kids = new ArrayList(4); // ?? vs. linkedlist this.depth = (parent == null) ? 0 : (parent.getDepth() + 1); }// TreeNode() /** Get Location */ public final Province getProvince() { return prov; } /** Get Parent */ public final TreeNode getParent() { return parent; } /** Get # of children */ public final int getChildCount() { return kids.size(); } /** Depth. 0 == root. */ public final int getDepth() { return depth; } /** Leaf Node? (no kids) */ public final boolean isLeaf() { return kids.isEmpty(); } /** Add a child */ public void addChild(TreeNode child) { if(child == null) { throw new IllegalArgumentException(); } kids.add(child); }// addChild() /** * Add a child, but only if it Location is unique, as compared * to its lineage (all parents).. */ public boolean addUniqueChild(TreeNode child) { if(child == null) { throw new IllegalArgumentException(); } TreeNode mommy = getParent(); while(mommy != null) { if(child.getProvince().equals(mommy.getProvince())) { return false; } mommy = mommy.getParent(); } kids.add(child); return true; }// addUniqueChild() /** * Get all branches of the Tree, starting with this node. * If used on the root node, this would get ALL nodes. * This is an IRREGULAR 2D list of Locations. * Location[a][b] where a == the branch, and b == the * location on the branch (0 to length). * */ public Province[][] getAllBranches() { // first: do a BFS to find all leaf nodes (no kids). // we'll store these in a list, and then iterate back // up using getParent(). LinkedList leafNodeList = new LinkedList(); LinkedList queue = new LinkedList(); queue.addLast(this); while(queue.size() > 0) { TreeNode n = (TreeNode) queue.removeFirst(); if(n.isLeaf()) { leafNodeList.addLast(n); } for(int i=0; i<n.kids.size(); i++) { queue.addLast(n.kids.get(i)); } } return createProvinceArray(leafNodeList); }// getAllBranches() /** * Get all branches of the Tree, starting with this node, * and ending with a TreeNode that contains the given Province. * <p> * Otherwise similar to getAllBranches() * */ public Province[][] getAllBranchesTo(Province end) { LinkedList leafNodeList = new LinkedList(); LinkedList queue = new LinkedList(); queue.addLast(this); while(queue.size() > 0) { TreeNode n = (TreeNode) queue.removeFirst(); if(n.isLeaf() && n.getProvince().equals(end)) { leafNodeList.addLast(n); } for(int i=0; i<n.kids.size(); i++) { queue.addLast(n.kids.get(i)); } } return createProvinceArray(leafNodeList); }// getAllBranchesTo() /** Creates a Province array from a List of endpoints. */ private Province[][] createProvinceArray(List list) { Province[][] pathArray = new Province[list.size()][]; int idx = 0; Iterator iter = list.iterator(); while(iter.hasNext()) { TreeNode n = (TreeNode) iter.next(); Province[] path = new Province[n.getDepth()+1]; // root is depth 0 for(int i=(path.length - 1); i>=0; i--) { path[i] = n.getProvince(); n = n.getParent(); } pathArray[idx] = path; idx++; } return pathArray; }// createProvinceArray() /** Print the Tree from this node to System.out */ public void print() { Province[][] px = getAllBranches(); for(int i=0; i<px.length; i++) { StringBuffer sb = new StringBuffer(128); sb.append(px[i][0].getShortName()); for(int j=1; j<px[i].length; j++) { sb.append("-"); sb.append(px[i][j].getShortName()); } System.out.println(sb); } }// print() }// inner class TreeNode }// class Path