//
// @(#)RetreatChecker.java 1.00 4/1/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.process;
import dip.world.Location;
import dip.world.TurnState;
import dip.world.Position;
import dip.world.World;
import dip.order.Orderable;
import dip.order.Move;
import dip.order.result.Result;
import dip.order.result.OrderResult;
import dip.order.result.OrderResult.ResultType;
import dip.misc.Log;
import java.util.List;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.HashMap;
/**
* RetreatChecker analyzes the current TurnState and the results of the previous
* TurnState to determine which (if any) retreat locations are acceptable for a
* retreating unit. Thus it is only dependent upon the adjudication results, and
* the current Position.
* <p>
* Basic Retreat Algorithm:<br>
* <ul>
* <li> locations must be adjacent to dislodged unit
* <li> locations must be unoccupied
* <li> unoccupied location must not be that of the dislodging attacks origin,
* unless that attack occured was by a (successful) convoyed Move
* <li> unoccupied locations must not be involved in a standoff (2 or more
* unsuccesful Moves)
* </ul>
*
* Should be threadsafe.
*/
public class RetreatChecker
{
// instance variables
private transient final Position position;
private transient final ArrayList filteredMoveResults;
/**
* Create a RetreatChecker.
* <p>
* There must be at least one prior TurnState in the World object for
* this to work, however, if we a unit is Dislodged and it is the very
* first TurnState (this can happen if the game is edited), it is allowed.
*/
public RetreatChecker(TurnState current)
{
List results = null;
TurnState last = current.getWorld().getPreviousTurnState(current);
if(last == null)
{
// if we are the very first TurnState, last==null is permissable,
// but we must take special action to make it work
World w = current.getWorld();
if(w.getInitialTurnState() == current)
{
//Log.println(" no previous turnstate, and we are first; creating results");
results = new ArrayList();
}
else
{
throw new IllegalStateException("No Previous Turn State!!");
}
}
else
{
results = last.getResultList();
//Log.println(" last turnstate: ",last.getPhase());
}
this.position = current.getPosition();
this.filteredMoveResults = makeFMRList(results);
}// RetreatChecker()
/**
* Create a RetreatChecker.
* <p>
* Useful for when the previous TurnState has not yet been inserted
* into the World object.
*/
public RetreatChecker(TurnState current, List previousTurnStateResults)
{
if(current == null || previousTurnStateResults == null)
{
throw new IllegalStateException("null arguments!");
}
this.position = current.getPosition();
this.filteredMoveResults = makeFMRList(previousTurnStateResults);
}// RetreatChecker()
/**
* Determines if the unit located in <code>from</code> can retreat to
* the Location <code>to</code>
*
*/
public boolean isValid(Location from, Location to)
{
Location[] validLocs = getValidLocations(from);
// debugging
/*
if(Log.isLogging())
{
Log.print(" valid retreat locations from ");
Log.print(from);
Log.print(": ");
for(int i=0; i<validLocs.length; i++)
{
Log.print(validLocs[i]);
Log.print(" ");
}
Log.print("\n");
}
*/
for(int i=0; i<validLocs.length; i++)
{
if(validLocs[i].equals(to))
{
return true;
}
}
return false;
}// isValid()
/**
* Gets all valid locations to which this unit may retreat.
* <p>
* Returns a zero-length array if there are no acceptable retreat locations.
*/
public Location[] getValidLocations(Location from)
{
List retreatLocations = new ArrayList(8);
Location[] adjacent = from.getProvince().getAdjacentLocations(from.getCoast());
for(int i=0; i<adjacent.length; i++)
{
if( !position.hasUnit( adjacent[i].getProvince() )
&& !isDislodgersSpace(from, adjacent[i])
&& !isContestedSpace(adjacent[i]) )
{
retreatLocations.add(adjacent[i]);
}
}
return (Location[]) retreatLocations.toArray(new Location[retreatLocations.size()]);
}// getValidLocations()
/** Returns 'true' if at least one valid retreat exists for the dislodged unit in 'from' */
public boolean hasRetreats(Location from)
{
Location[] adjacent = from.getProvince().getAdjacentLocations(from.getCoast());
for(int i=0; i<adjacent.length; i++)
{
if( !position.hasUnit( adjacent[i].getProvince() )
&& !isDislodgersSpace(from, adjacent[i])
&& !isContestedSpace(adjacent[i]) )
{
return true;
}
}
return false;
}// hasNoRetreats()
/**
* Returns <code>true</code> if the space is unoccupied,
* and their exists a <b>successful</b> move order from
* that space which dislodged the unit.
* <b>
* @param dislodgedLoc The location with the dislodged unit
* @param loc The unoccupied location which we are checking
* @return <code>true</code> if Move from <code>loc</code>
* dislodged <code>dislodgedLoc</code>
*/
private boolean isDislodgersSpace(Location dislodgedLoc, Location loc)
{
Iterator iter = filteredMoveResults.iterator();
while(iter.hasNext())
{
RCMoveResult rcmr = (RCMoveResult) iter.next();
// note: dislodgedLoc is the potential move destination
if(rcmr.isDislodger(loc, dislodgedLoc))
{
return true;
}
}
return false;
}// isDislodgersSpace()
/**
* Returns true if a standoff has occured;
* A standoff exists if:
* <ol>
* <li>no unit in space (essential!)</li>
* <li>2 or more <b>legal ("valid")</b> failed move orders exist
* with dest of space</li>
* </ol>
*/
private boolean isContestedSpace(Location loc)
{
if( position.hasUnit(loc.getProvince()) )
{
return false;
}
int moveCount = 0;
Iterator iter = filteredMoveResults.iterator();
while(iter.hasNext())
{
RCMoveResult rcmr = (RCMoveResult) iter.next();
if(rcmr.isPossibleStandoff(loc))
{
moveCount++;
}
}
return (moveCount >= 2);
}// isContestedSpace()
/**
* Generate a List of (only) Move orders; when checking multiple
* retreats this is a performance gain.
* <p>
* The filtered Move results consist of going through all OrderResults
* looking for those involving Move orders. For each Move order, we
* generate one RCMoveResult object, which holds the pertinent information
* about that Move order.
*/
private ArrayList makeFMRList(List turnStateResults)
{
ArrayList mrList = new ArrayList(64);
HashMap map = new HashMap(119); // key: move source province; value: RCMoveResult
Iterator iter = turnStateResults.iterator();
while(iter.hasNext())
{
Object obj = iter.next();
if(obj instanceof OrderResult)
{
OrderResult or = (OrderResult) obj;
Orderable order = or.getOrder();
if(order instanceof Move)
{
// see if we have an entry for this Move; if so,
// set options; if not, create an entry.
// This avoids duplicate entries per Move.
//
RCMoveResult rcmr = (RCMoveResult) map.get(order.getSource().getProvince());
if(rcmr == null)
{
rcmr = new RCMoveResult(or);
map.put(order.getSource().getProvince(), rcmr);
mrList.add(rcmr);
}
else
{
rcmr.setOptions(or);
}
}
}
}
map.clear(); // no longer needed
return mrList;
}// makeFMRList()
/**
* RCMoveResult holds information about a Move order, as generated
* from an OrderResult.
*
*/
private class RCMoveResult
{
private final Move move;
private boolean isSuccess = false;
private boolean isByConvoy = false;
private boolean isValid = true;
/**
* Create an RCMoveResult. Assumes that the passed
* OrderResult refers to a Move order. Sets any options
* for the OrderResult.
*/
public RCMoveResult(OrderResult or)
{
move = (Move) or.getOrder();
setOptions(or);
}// RCMoveResult()
/**
* Given an OrderResult, checks to see if the success
* or convoy flags can be set. If the passed OrderResult
* does NOT refer to the same Move (via referential
* equality), an exception is thrown.
*/
public final void setOptions(OrderResult or)
{
if(!or.getOrder().equals(move))
{
throw new IllegalArgumentException();
}
if(or.getResultType() == OrderResult.ResultType.CONVOY_PATH_TAKEN)
{
isByConvoy = true;
}
else if(or.getResultType() == OrderResult.ResultType.SUCCESS)
{
isSuccess = true;
}
else if(or.getResultType() == OrderResult.ResultType.VALIDATION_FAILURE)
{
isValid = false;
}
}// setOptions()
/** Successful? */
public boolean isSuccess()
{
return isSuccess;
}
/**
* Is this move a potentially vying for a potential standoff?
* <p>
* This will return true iff:
* (1) Destination *province* of Move matchest given location
* (2) Move is NOT successful
* (3) Move is NOT invalid (i.e., no VALIDATION_FAILURE result)
*/
public boolean isPossibleStandoff(Location loc)
{
return
(
isValid
&& !isSuccess
&& move.getDest().isProvinceEqual(loc)
);
}// isPossibleStandoff()
/**
* Is this move a potentially dislodging move?
* This will return true iff:
* (1) src and dest match;
* (2) successful
* (3) NOT convoyed (DATC 16-dec-03 4.A.5)
*/
public boolean isDislodger(Location src, Location dest)
{
return
(
isSuccess
&& !isByConvoy
&& move.getSource().isProvinceEqual(src)
&& move.getDest().isProvinceEqual(dest)
);
}// isDislodger()
}// inner class RCMoveResult
}// class RetreatChecker