//
// @(#)Convoy.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.order.result;
//
package dip.order;
import dip.order.result.OrderResult.ResultType;
import dip.world.*;
import dip.process.Adjudicator;
import dip.process.OrderState;
import dip.process.Tristate;
import dip.misc.Log;
import dip.misc.Utils;
import java.util.List;
import java.util.Iterator;
/**
*
* Implementation of the Convoy order.
*
*
*
*
*/
public class Convoy extends Order
{
/**
*
*/
private static final long serialVersionUID = -870980815801984342L;
// il8n constants
private static final String CONVOY_SEA_FLEETS = "CONVOY_SEA_FLEETS";
private static final String CONVOY_ONLY_ARMIES = "CONVOY_ONLY_ARMIES";
private static final String CONVOY_NO_ROUTE = "CONVOY_NO_ROUTE";
private static final String CONVOY_VER_NOMOVE = "CONVOY_VER_NOMOVE";
private static final String CONVOY_FORMAT = "CONVOY_FORMAT";
private static final String CONVOY_SELF_ILLEGAL = "CONVOY_SELF_ILLEGAL";
private static final String CONVOY_TO_SAME_PROVINCE = "CONVOY_TO_SAME_PROVINCE";
// constants: names
private static final String orderNameBrief = "C";
private static final String orderNameFull = "Convoy";
private static final transient String orderFormatString = Utils.getLocalString(CONVOY_FORMAT);
// instance variables
protected Location convoySrc = null;
protected Location convoyDest = null;
protected Unit.Type convoyUnitType = null;
protected Power convoyPower = null;
/** Creates a Convoy order */
protected Convoy(Power power, Location src, Unit.Type srcUnit,
Location convoySrc, Power convoyPower, Unit.Type convoyUnitType,
Location convoyDest)
{
super(power, src, srcUnit);
if(convoySrc == null || convoyUnitType == null || convoyDest == null)
{
throw new IllegalArgumentException("null argument(s)");
}
this.convoySrc = convoySrc;
this.convoyUnitType = convoyUnitType;
this.convoyPower = convoyPower;
this.convoyDest = convoyDest;
}// Convoy()
/** Creates a Convoy order */
protected Convoy()
{
super();
}// Convoy()
/** Returns the Location of the Unit to be Convoyed */
public Location getConvoySrc() { return convoySrc; }
/**
* Returns the Unit Type of the Unit to be Convoyed
* <b>Warning:</b> this can be null, if no unit type was set, and
* no strict validation was performed (via <code>validate()</code>).
*/
public Unit.Type getConvoyUnitType() { return convoyUnitType; }
/**
* Returns the Power of the Unit we are Convoying.
* <b>Warning:</b> this can be null, if no unit type was set, and
* no strict validation was performed (via <code>validate()</code>).
* <p>
* <b>Important Note:</b> This also may be null only when a saved game
* from 1.5.1 or prior versions are loaded into a recent version,
* since prior versions did not support this field.
*/
public Power getConvoyedPower() { return convoyPower; }
/** Returns the Location of the Convoy destination */
public Location getConvoyDest() { return convoyDest; }
public String getFullName()
{
return orderNameFull;
}// getName()
public String getBriefName()
{
return orderNameBrief;
}// getBriefName()
public String getDefaultFormat()
{
return orderFormatString;
}// getFormatBrief()
public String toBriefString()
{
StringBuffer sb = new StringBuffer(64);
super.appendBrief(sb);
sb.append(' ');
sb.append(orderNameBrief);
sb.append(' ');
sb.append(convoyUnitType.getShortName());
sb.append(' ');
convoySrc.appendBrief(sb);
sb.append('-');
convoyDest.appendBrief(sb);
return sb.toString();
}// toBriefString()
public String toFullString()
{
StringBuffer sb = new StringBuffer(128);
super.appendFull(sb);
sb.append(' ');
sb.append(orderNameFull);
sb.append(' ');
sb.append(convoyUnitType.getFullName());
sb.append(' ');
convoySrc.appendFull(sb);
sb.append(" -> ");
convoyDest.appendFull(sb);
return sb.toString();
}// toFullString()
public boolean equals(Object obj)
{
if(obj instanceof Convoy)
{
Convoy convoy = (Convoy) obj;
if( super.equals(convoy)
&& this.convoySrc.equals(convoy.convoySrc)
&& this.convoyUnitType.equals(convoy.convoyUnitType)
&& this.convoyDest.equals(convoy.convoyDest) )
{
return true;
}
}
return false;
}// equals()
public void validate(TurnState state, ValidationOptions valOpts, RuleOptions ruleOpts)
throws OrderException
{
// v.0: check phase, basic validation
checkSeasonMovement(state, orderNameFull);
checkPower(power, state, true);
super.validate(state, valOpts, ruleOpts);
if(valOpts.getOption(ValidationOptions.KEY_GLOBAL_PARSING).equals(ValidationOptions.VALUE_GLOBAL_PARSING_STRICT))
{
Position position = state.getPosition();
Province srcProvince = src.getProvince();
// v.1: src unit type must be a fleet, in a body of water
// OR in a convoyable coast.
if(!srcUnitType.equals(Unit.Type.FLEET) || (!srcProvince.isSea() && !srcProvince.isConvoyableCoast()))
{
throw new OrderException(Utils.getLocalString(CONVOY_SEA_FLEETS));
}
// validate Borders
Border border = src.getProvince().getTransit(src, srcUnitType, state.getPhase(), this.getClass());
if(border != null)
{
throw new OrderException( Utils.getLocalString(ORD_VAL_BORDER, src.getProvince(), border.getDescription()) );
}
// v.2: a) type-match unit type with current state, and unit must exist
// b) unit type must be ARMY
Unit convoyUnit = position.getUnit( convoySrc.getProvince() );
convoyUnitType = getValidatedUnitType(convoySrc.getProvince(), convoyUnitType, convoyUnit);
if( !convoyUnitType.equals(Unit.Type.ARMY) )
{
throw new OrderException(Utils.getLocalString(CONVOY_ONLY_ARMIES));
}
// v.3.a: validate locations: convoySrc & convoyDest
convoySrc = convoySrc.getValidatedAndDerived(convoyUnitType, convoyUnit);
convoyDest = convoyDest.getValidated(convoyUnitType);
// v.3.b: convoying to self (if we are in a convoyable coast) is illegal!
if(srcProvince.isConvoyableCoast() && src.isProvinceEqual(convoyDest))
{
throw new OrderException(Utils.getLocalString(CONVOY_SELF_ILLEGAL));
}
// v.3.c: origin/destination of convoy must not be same province.
if(convoySrc.isProvinceEqual(convoyDest))
{
throw new OrderException(Utils.getLocalString(CONVOY_TO_SAME_PROVINCE));
}
// v.4: a *theoretical* convoy route must exist between
// convoySrc and convoyDest
Path path = new Path(position);
if( !path.isPossibleConvoyRoute(convoySrc, convoyDest) )
{
throw new OrderException(Utils.getLocalString(CONVOY_NO_ROUTE,
convoySrc.toLongString(), convoyDest.toLongString() ));
}
// validate Borders
border = convoySrc.getProvince().getTransit(convoySrc, convoyUnitType, state.getPhase(), this.getClass());
if(border != null)
{
throw new OrderException( Utils.getLocalString(ORD_VAL_BORDER, src.getProvince(), border.getDescription()) );
}
border = convoyDest.getProvince().getTransit(convoyDest, convoyUnitType, state.getPhase(), this.getClass());
if(border != null)
{
throw new OrderException( Utils.getLocalString(ORD_VAL_BORDER, src.getProvince(), border.getDescription()) );
}
}
}// validate();
/**
* Checks for matching Move orders.
*
*/
public void verify(Adjudicator adjudicator)
{
OrderState thisOS = adjudicator.findOrderStateBySrc(getSource());
if(thisOS.getEvalState() == Tristate.UNCERTAIN)
{
// check for a matching move order.
//
// note that the move must have its isByConvoy() flag set, so we don't
// kidnap armies that prefer not to be convoyed.
boolean foundMatchingMove = false;
OrderState matchingOS = adjudicator.findOrderStateBySrc( getConvoySrc() );
if(matchingOS != null)
{
if(matchingOS.getOrder() instanceof Move)
{
Move convoyedMove = (Move) matchingOS.getOrder();
// check that Move has been verified; if it has not,
// we should just immediately verify it (though we could
// wait for the adjudicator to do so).
if(!matchingOS.isVerified())
{
convoyedMove.verify(adjudicator);
// but if it doesn't verify, then we have a
// dependency-error.
if(!matchingOS.isVerified())
{
throw new IllegalStateException("Verify dependency error.");
}
}
if( convoyedMove.isConvoying()
&& getConvoyDest().isProvinceEqual(convoyedMove.getDest()) )
{
foundMatchingMove = true;
}
}
}
if(!foundMatchingMove)
{
thisOS.setEvalState(Tristate.FAILURE);
adjudicator.addResult(thisOS, ResultType.FAILURE, Utils.getLocalString(CONVOY_VER_NOMOVE));
}
}
thisOS.setVerified(true);
}// verify()
/**
* Dependencies for a Convoy order are:
* <ol>
* <li>Moves to this space (to determine dislodgement)
* <li>Supports to this space (only considered if attacked, to prevent dislodgement)
* </ol>
*/
public void determineDependencies(Adjudicator adjudicator)
{
addSupportsOfAndMovesToSource(adjudicator);
}// determineDependencies()
/** Convoy order evaluation logic */
public void evaluate(Adjudicator adjudicator)
{
Log.println("--- evaluate() dip.order.Convoy ---");
OrderState thisOS = adjudicator.findOrderStateBySrc(getSource());
// calculate support
thisOS.setDefMax( thisOS.getSupport(false) );
thisOS.setDefCertain( thisOS.getSupport(true) );
if(Log.isLogging())
{
Log.println(" order: ",this);
Log.println(" initial evalstate: ",thisOS.getEvalState());
Log.println(" def-max: ",thisOS.getDefMax());
Log.println(" def-cert: ",thisOS.getDefCertain());
Log.println(" # supports: ",thisOS.getDependentSupports().length);
}
// determine evaluation state. This is important for Convoy orders, since
// moves depend upon them. If we cannot determine, we will remain uncertain.
if(thisOS.getEvalState() == Tristate.UNCERTAIN)
{
// if for some reason we were dislodged, but not marked as failure,
// mark as failure.
if(thisOS.getDislodgedState() == Tristate.YES)
{
thisOS.setEvalState(Tristate.FAILURE);
return;
}
// we will also succeed if there are *no* moves against us, or if all the
// moves against us have failed.
boolean isSuccess = true;
OrderState[] depMovesToSrc = thisOS.getDependentMovesToSource();
for(int i=0; i<depMovesToSrc.length; i++)
{
if(depMovesToSrc[i].getEvalState() != Tristate.FAILURE)
{
isSuccess = false;
break;
}
}
if(isSuccess)
{
thisOS.setEvalState(Tristate.SUCCESS);
thisOS.setDislodgedState(Tristate.NO);
}
}
Log.println(" final evalState: ", thisOS.getEvalState());
}// evaluate()
}// class Convoy