//
// @(#)JudgeImport.java 1.00 6/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.judge.parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import dip.judge.parser.TurnParser.Turn;
import dip.misc.Log;
import dip.misc.Utils;
import dip.order.Build;
import dip.order.Disband;
import dip.order.Move;
import dip.order.NJudgeOrderParser;
import dip.order.NJudgeOrderParser.NJudgeOrder;
import dip.order.Order;
import dip.order.OrderException;
import dip.order.OrderFactory;
import dip.order.Orderable;
import dip.order.Remove;
import dip.order.ValidationOptions;
import dip.order.result.DislodgedResult;
import dip.order.result.SubstitutedResult;
import dip.order.result.OrderResult;
import dip.order.result.Result;
import dip.process.Adjustment;
import dip.world.Location;
import dip.world.Phase;
import dip.world.Position;
import dip.world.Power;
import dip.world.Province;
import dip.world.RuleOptions;
import dip.world.TurnState;
import dip.world.Unit;
import dip.world.VictoryConditions;
import dip.world.World;
/**
*
* Processes an entire game history to create a world.
* <p>
* TODO:
* <br>positioning units with orders that failed parsing (e.g., a move to Switzerland (swi))
*
*
*/
final class JudgeImportHistory
{
// constants
private static final String STDADJ_MV_UNIT_DESTROYED = "STDADJ_MV_UNIT_DESTROYED";
private static final String JIH_BAD_POSITION = "JP.import.badposition";
private static final String JIH_NO_MOVEMENT_PHASE = "JP.history.nomovement";
private static final String JIH_ORDER_PARSE_FAILURE = "JP.history.badorder";
private static final String JIH_UNKNOWN_RESULT = "JP.history.unknownresult";
private static final String JIH_NO_DISLODGED_MATCH = "JP.history.dislodgedmatchfail";
private static final String JIH_INVALID_RETREAT = "JP.history.badretreat";
private static final String JIH_BAD_LAST_PHASE = "JP.history.badlastphase";
// parsing parameters
/**
* Regular expression for parsing what the Next phase is. This is only used to create the last
* (final) phase.<p>
* Capture groups: (1)Phase (2)Season (3)Year
*/
public static final String PARSE_REGEX = "(?i)the\\snext\\sphase\\s.*will\\sbe\\s(\\p{Alpha}+)\\sfor\\s(\\p{Alpha}+)\\sof\\s((\\p{Digit}+))";
public static final String END_FOF_GAME = "(?i)the game is over";
public static final String START_POSITIONS = "(?i)Subject:\\s\\p{Alpha}+:\\p{Alnum}+\\s-\\s(\\p{Alpha})(\\p{Digit}+)(\\p{Alpha})";
// instance variables
private final dip.world.Map map;
private final OrderFactory orderFactory;
private final World world;
private JudgeParser jp = null;
private Position oldPosition = null;
private final ValidationOptions valOpts;
private HSCInfo[] homeSCInfo = null;
private boolean finalTurn = false;
/** Create a JudgeImportHistory */
protected JudgeImportHistory(OrderFactory orderFactory, World world, JudgeParser jp, Position oldPosition)
throws IOException, PatternSyntaxException
{
this.orderFactory = orderFactory;
this.world = world;
this.jp = jp;
this.oldPosition = oldPosition;
this.map = world.getMap();
// create a very strict validation object, loose seems to have some weird problems when importing.
valOpts = new ValidationOptions();
valOpts.setOption(ValidationOptions.KEY_GLOBAL_PARSING, ValidationOptions.VALUE_GLOBAL_PARSING_STRICT);
processTurns();
}// JudgeImportHistory()
/** Create a JudgeImportHistory and process a single turn */
protected JudgeImportHistory(OrderFactory orderFactory, World world, JudgeParser jp, Turn turn)
throws IOException, PatternSyntaxException
{
this.orderFactory = orderFactory;
this.world = world;
this.jp = jp;
this.oldPosition = world.getLastTurnState().getPosition();
this.map = world.getMap();
// create a very strict validation object, loose seems to have some weird problems when importing.
valOpts = new ValidationOptions();
valOpts.setOption(ValidationOptions.KEY_GLOBAL_PARSING, ValidationOptions.VALUE_GLOBAL_PARSING_STRICT);
processSingleTurn(turn);
}// JudgeImportHistory()
/** Returns the World, with TurnStates & Positions added as appropriate. */
protected World getWorld()
{
return world;
}// getWorld()
/**
* Processes the Turn data, starting with the first Movement phase. An exception is
* thrown if no Movement phase exists.
*
*/
private void processTurns()
throws IOException, PatternSyntaxException
{
// break data up into turns
Turn[] turns = new TurnParser(jp.getText()).getTurns();
//System.out.println("# of turns: "+turns.length);
// find first movement phase, if any
int firstMovePhase = -1;
for(int i=0; i<turns.length; i++)
{
if(turns[i].getPhase() != null)
{
if(turns[i].getPhase().getPhaseType() == Phase.PhaseType.MOVEMENT)
{
firstMovePhase = i;
break;
}
}
}
// If we couldn't find the first movement phase... perhaps the game is just starting
if(firstMovePhase == -1)
{
// Try to use the text info to create the game at its starting positions
try {
createStartingPositions();
// Don't do the rest of this method, it will all fail.
return;
} catch (IOException e){
throw new IOException(Utils.getLocalString(JIH_NO_MOVEMENT_PHASE));
}
}
//System.out.println("First move phase: "+firstMovePhase);
// get home supply center information from the oldPosition object
// and store it in HSCInfo object array, so that it can be set during each successive
// turn.
ArrayList hscList = new ArrayList(50);
Province[] provinces = map.getProvinces();
for(int i=0; i<provinces.length; i++)
{
Power power = oldPosition.getSupplyCenterHomePower(provinces[i]);
if(power != null)
{
hscList.add(new HSCInfo(provinces[i], power));
}
}
homeSCInfo = (HSCInfo[]) hscList.toArray(new HSCInfo[hscList.size()]);
// process all but the final phase
for(int i=firstMovePhase; i<turns.length-1; i++)
{
//System.out.println("processing turn: "+i+"; phase = "+turns[i].getPhase());
if(i==0) { procTurn(turns[i], null, null, false); }
else if(i==1) { procTurn(turns[i], turns[i-1], null, false); }
else { procTurn(turns[i], turns[i-1], turns[i-2], false); }
}
// process the last turn once more, but as the final turn, to allow proper positioning.
finalTurn = true;
if(turns.length == 1){
procTurn(turns[turns.length-1],null,null,true);
} else if (turns.length == 2){
procTurn(turns[turns.length-1],turns[turns.length-2],null,true);
} else if (turns.length >= 3){
procTurn(turns[turns.length-1], turns[turns.length-2], turns[turns.length-3], true);
}
Pattern endofgame = Pattern.compile(END_FOF_GAME);
Matcher e = endofgame.matcher(turns[turns.length-1].getText());
if(!e.find()){
// create last (un-resolved) turnstate
makeLastTurnState(turns[turns.length-1]);
// reprocess the last turn, again, not as final, so it looks right for viewing.
finalTurn = false;
if(turns.length == 1){
procTurn(turns[turns.length-1],null,null,false);
} else if (turns.length == 2){
procTurn(turns[turns.length-1],turns[turns.length-2],null,false);
} else if (turns.length >= 3){
procTurn(turns[turns.length-1], turns[turns.length-2], turns[turns.length-3], false);
}
} else {
// The imported game has ended
// Reprocess the last turn, again, not as final, so it looks right for viewing.
finalTurn = false;
procTurn(turns[turns.length-1], turns[turns.length-2], turns[turns.length-3], false);
// Set the game as ended.
TurnState ts = world.getTurnState(turns[turns.length-1].getPhase());
VictoryConditions vc = world.getVictoryConditions();
RuleOptions ruleOpts = world.getRuleOptions();
Adjustment.AdjustmentInfoMap adjMap = Adjustment.getAdjustmentInfo(ts, ruleOpts, world.getMap().getPowers());
vc.evaluate(ts,adjMap);
List evalResults = ts.getResultList();
evalResults.addAll(vc.getEvaluationResults());
ts.setResultList(evalResults);
ts.setEnded(true);
ts.setResolved(true);
world.setTurnState(ts);
}
// all phases have been processed; perform post-processing here.
}// processTurns()
/**
* Processes a single turn.
*
*/
private void processSingleTurn(Turn turn)
throws IOException, PatternSyntaxException
{
// get home supply center information from the oldPosition object
// and store it in HSCInfo object array, so that it can be set during each successive
// turn.
ArrayList hscList = new ArrayList(50);
Province[] provinces = map.getProvinces();
for(int i=0; i<provinces.length; i++)
{
Power power = oldPosition.getSupplyCenterHomePower(provinces[i]);
if(power != null)
{
hscList.add(new HSCInfo(provinces[i], power));
}
}
homeSCInfo = (HSCInfo[]) hscList.toArray(new HSCInfo[hscList.size()]);
// process the turn
procTurn(turn, null, null, false);
// save new turn state
TurnState savedTS = world.getLastTurnState();
// process the last turn once more, but as the final turn, to allow proper positioning.
finalTurn = true;
procTurn(turn, null, null, true);
// create last (un-resolved) turnstate
makeLastTurnState(turn);
// inject the saved turnstate.
world.setTurnState(savedTS);
// TODO: do we have to check for victory conditions ?
}// processSingleTurn()
/**
* Decides how to process the turn, based upon the phase information and past turns.
* This is not the best way to process the turns, especially the adjustment phase,
* but it works.
*/
private void procTurn(Turn turn, Turn prevTurn, Turn thirdTurn, boolean positionPlacement)
throws IOException
{
Phase phase = turn.getPhase();
if(phase != null)
{
Phase.PhaseType phaseType = phase.getPhaseType();
if(phaseType == Phase.PhaseType.MOVEMENT)
{
procMove(turn, positionPlacement);
}
else if(phaseType == Phase.PhaseType.RETREAT)
{
/*
* Set the proper positionPlacement value depending on if the turn being
* processed is the final turn. Set it back again when done.
*/
if(!finalTurn) { procMove(prevTurn, !positionPlacement); }
else { procMove(prevTurn, positionPlacement); }
procRetreat(turn, positionPlacement);
if(!finalTurn) { procMove(prevTurn, positionPlacement); }
else { procMove(prevTurn, !positionPlacement); }
}
else if(phaseType == Phase.PhaseType.ADJUSTMENT)
{
Phase.PhaseType prevPhaseType = Phase.PhaseType.MOVEMENT; // dummy
if (prevTurn != null) {
Phase phase_p = prevTurn.getPhase();
prevPhaseType = phase_p.getPhaseType();
}
/*
* Much the same as above, set the proper positionPlacement value depending
* on the PhaseType and if the turn being processed is the final turn.
* Set it back again when done.
*/
if(prevPhaseType == Phase.PhaseType.MOVEMENT) {
if(!finalTurn) { procMove(prevTurn, !positionPlacement); }
else { procMove(prevTurn, positionPlacement); }
procAdjust(turn, positionPlacement);
if(!finalTurn) { procMove(prevTurn, positionPlacement); }
else { procMove(prevTurn, !positionPlacement); }
} else {
if(!finalTurn) {
procMove(thirdTurn, !positionPlacement);
procRetreat(prevTurn, !positionPlacement);
}
else {
procMove(thirdTurn, positionPlacement);
procRetreat(prevTurn, positionPlacement);
}
procAdjust(turn, positionPlacement);
if(!finalTurn) {
procRetreat(prevTurn, positionPlacement);
procMove(thirdTurn, positionPlacement);
}
else {
procRetreat(prevTurn, !positionPlacement);
procMove(thirdTurn, !positionPlacement);
}
}
}
else
{
throw new IllegalStateException("unknown phase type");
}
}
}// procTurn()
/** Creates a TurnState object with the correct Phase, Position, and World information,
* including setting things such as home supply centers and what not.
* <p>
* This method ensures that TurnState objects are properly (and consistently) initialized.
*/
private TurnState makeTurnState(Turn turn)
{
// does the turnstate already exist?
// it could, if we are importing orders into an already-existing game.
//
Log.println("JIH::makeTurnState() ", turn.getPhase());
TurnState ts = world.getTurnState(turn.getPhase());
if(ts == null)
{
Log.println(" creating new turnstate.");
// make new TurnState
ts = new TurnState(turn.getPhase());
ts.setWorld(world);
ts.setPosition(new Position(world.getMap()));
// note: we don't add the turnstate to the World object at this point (although we could), because
// if a processing error occurs, we don't want a partial turnstate object in the World.
// set Home Supply centers in position
Position pos = ts.getPosition();
for(int i=0; i<homeSCInfo.length; i++)
{
pos.setSupplyCenterHomePower(homeSCInfo[i].getProvince(), homeSCInfo[i].getPower());
}
}
else
{
// likely importing orders to an existing game.
// no changes to turnstate.
Log.println(" using existing turnstate");
}
return ts;
}// makeTurnState()
/** Process a Movement phase turn */
private void procMove(Turn turn, boolean positionPlacement)
throws IOException
{
if (turn == null) return;
// create TurnState
TurnState ts = makeTurnState(turn);
List results = ts.getResultList();
Log.println("JIH::procMove(): ", ts.getPhase(), "; positionPlacement: ", String.valueOf(positionPlacement));
// copy previous lastOccupier information into current turnstate.
copyPreviousLastOccupierInfo(ts);
// parse orders, and create orders for each unit
final JudgeOrderParser jop = new JudgeOrderParser(map, orderFactory, turn.getText());
final NJudgeOrder[] nJudgeOrders = jop.getNJudgeOrders();
// get Position
Position position = ts.getPosition();
// create units from start position
for(int i=0; i<nJudgeOrders.length; i++)
{
final NJudgeOrder njo = nJudgeOrders[i];
final Orderable order = njo.getOrder();
if(order == null)
{
Log.println("JIH::procMove(): Null order; njo: ", njo);
throw new IOException("Internal error: null order in JudgeImportHistory::procMove()");
}
Location loc = order.getSource();
final Unit.Type unitType = order.getSourceUnitType();
final Power power = order.getPower();
// validate location
try
{
loc = loc.getValidated(unitType);
}
catch(OrderException e)
{
Log.println("ERROR: ", njo);
Log.println("TURN: \n", turn.getText());
throw new IOException(e.getMessage());
}
// create unit, and add to Position
Unit unit = new Unit(order.getPower(), unitType);
unit.setCoast(loc.getCoast());
position.setUnit(loc.getProvince(), unit);
position.setLastOccupier(loc.getProvince(), power);
// if we found a Wing unit, make sure Wing units are enabled.
checkAndEnableWings(unitType);
// debug
//System.out.println(" "+location+"; "+unit);
}
// now, validate all order objects from the parsed order
// also create result objects
// create positions from successful orders...
// note that we only need to set the last occupier for changing (moving)
// units, but we will do it for all units for consistency
//
{
// create orderMap, which maps powers to their respective order list
Power[] powers = map.getPowers();
Log.println("JIH::procMove():CREATING POWER->ORDER MAP");
HashMap orderMap = new HashMap(powers.length);
for(int i=0; i<powers.length; i++)
{
orderMap.put(powers[i], new LinkedList());
}
// process all orders
final RuleOptions ruleOpts = world.getRuleOptions();
for(int i=0; i<nJudgeOrders.length; i++)
{
final NJudgeOrder njo = nJudgeOrders[i];
final Orderable order = njo.getOrder();
// first try to validate under strict settings; if fail, try
// to validate under loose settings.
try
{
order.validate(ts, valOpts, ruleOpts);
List list = (LinkedList) orderMap.get(order.getPower());
list.add(order);
results.addAll(njo.getResults());
Log.println(" order ok: ", order);
}
catch(OrderException e)
{
Log.println("JIH::procMove():OrderException! using loose validation....");
//Try loosening the validation object
valOpts.setOption(ValidationOptions.KEY_GLOBAL_PARSING, ValidationOptions.VALUE_GLOBAL_PARSING_LOOSE);
/* Try the order once more.
* nJudge accepts illegal moves as valid as long as the syntax is valid.
* (Perhaps a few other things as well). jDip should accept these as well,
* even if the move is illegal.
*/
try
{
order.validate(ts, valOpts, ruleOpts);
List list = (LinkedList) orderMap.get(order.getPower());
list.add(order);
results.addAll(njo.getResults());
Log.println(" order ok: ", order);
}
catch(OrderException e1)
{
// create a general result indicating failure if an order could not be validated.
results.add( new Result(Utils.getLocalString(JIH_ORDER_PARSE_FAILURE, order, e.getMessage())));
Log.println("JIH::procMove():OrderException (during validation): ", order, "; ", e1.getMessage());
throw new IOException("Cannot validate order on second pass.\n"+e1.getMessage());
}
// Back to strict!
valOpts.setOption(ValidationOptions.KEY_GLOBAL_PARSING, ValidationOptions.VALUE_GLOBAL_PARSING_STRICT);
}
}
Log.println("JIH::procMove():ORDER PARSING COMPLETE");
// clear all units (dislodged or not) from the board
Province[] unitProv = position.getUnitProvinces();
Province[] dislProv = position.getDislodgedUnitProvinces();
for(int i=0;i<unitProv.length;i++) { position.setUnit(unitProv[i],null); }
for(int i=0;i<dislProv.length;i++) { position.setUnit(dislProv[i],null); }
// now that all orders are parsed, and all units are cleared, put
// unit in the proper place.
Iterator iter = results.iterator();
while(iter.hasNext())
{
Result result = (Result) iter.next();
if(result instanceof OrderResult)
{
OrderResult ordResult = (OrderResult) result;
Orderable order = ordResult.getOrder();
if(ordResult.getResultType() == OrderResult.ResultType.DISLODGED)
{
// dislodged orders create a unit in the source province, marked as dislodged,
// unless it was destroyed; if so, it will be destroyed later. Mark as dislodged for now.
Unit unit = new Unit(order.getPower(), order.getSourceUnitType());
/*
* Check for the positionPlacement flag, if not, we need to position the units
* in their source places for VIEWING. Otherwise the units need to be
* in their destination place for copying.
*/
if(!positionPlacement) {
unit.setCoast(order.getSource().getCoast());
position.setUnit(order.getSource().getProvince(), unit);
} else {
unit.setCoast(order.getSource().getCoast());
position.setDislodgedUnit(order.getSource().getProvince(), unit);
}
}
else if( ordResult.getResultType() == OrderResult.ResultType.SUCCESS
&& order instanceof Move )
{
// successful moves create a unit in the destination province
Move move = (Move) order;
Unit unit = new Unit(move.getPower(), move.getSourceUnitType());
/*
* Check for the positionPlacement flag, if not, we need to position the units
* in their source places for VIEWING. Otherwise the units need to be
* in their destination place for copying.
*/
if(!positionPlacement) {
unit.setCoast(move.getSource().getCoast());
position.setUnit(move.getSource().getProvince(), unit);
position.setLastOccupier(move.getSource().getProvince(), move.getPower());
} else {
unit.setCoast(move.getDest().getCoast());
position.setUnit(move.getDest().getProvince(), unit);
position.setLastOccupier(move.getDest().getProvince(), move.getPower());
}
}
else
{
// all other orders create a non-dislodged unit in the source province
Unit unit = new Unit(order.getPower(), order.getSourceUnitType());
/*
* Only add a unit if there is not a unit currently there, this stops
* powers further down in alpha. order from overriding powers before
* them. Eg. England dislodged Germany will be overriding if this isn't here.
*/
if(!position.hasUnit(order.getSource().getProvince()))
{
unit.setCoast(order.getSource().getCoast());
position.setUnit(order.getSource().getProvince(), unit);
position.setLastOccupier(order.getSource().getProvince(), order.getPower());
}
}
}
}
// set orders in turnstate
for(int i=0; i<powers.length; i++)
{
ts.setOrders(powers[i], (LinkedList) orderMap.get(powers[i]));
}
}
// process dislodged unit info, to determine retreat paths
// correct dislodged results are created here, and the old dislodged
// results are removed
if(!positionPlacement)
{
DislodgedParser dislodgedParser = new DislodgedParser(ts.getPhase(), turn.getText());
makeDislodgedResults(ts.getPhase(), results, position, dislodgedParser.getDislodgedInfo(), false);
}
else
{
DislodgedParser dislodgedParser = new DislodgedParser(ts.getPhase(), turn.getText());
makeDislodgedResults(ts.getPhase(), results, position, dislodgedParser.getDislodgedInfo(), true);
}
// process adjustment info ownership info (if any)
//
AdjustmentParser adjParser = new AdjustmentParser(map, turn.getText());
procAdjustmentBlock(adjParser.getOwnership(), ts, position);
// check for elimination
position.setEliminationStatus(map.getPowers());
// set adjudicated flag
ts.setResolved(true);
// add to world
world.setTurnState(ts);
}// procMove()
/** Process a Retreat phase turn */
private void procRetreat(Turn turn, boolean positionPlacement)
throws IOException
{
if (turn == null) return;
// create TurnState
final TurnState ts = makeTurnState(turn);
final Position position = ts.getPosition();
final List results = ts.getResultList();
final RuleOptions ruleOpts = world.getRuleOptions();
Log.println("JIH::procRetreat(): ", ts.getPhase(), "; positionPlacement: ", String.valueOf(positionPlacement));
// parse orders, and create orders for each unit
JudgeOrderParser jop = new JudgeOrderParser(map, orderFactory, turn.getText());
NJudgeOrder[] nJudgeOrders = jop.getNJudgeOrders();
// Copy previous phase positions
copyPreviousPositions(ts);
// process retreat orders (either moves or disbands)
// if order failed, it counts as a disband
// generate results
// create units for all successfull move (retreat) orders in destination province
{
// create orderMap, which maps powers to their respective order list
Power[] powers = map.getPowers();
HashMap orderMap = new HashMap(powers.length);
for(int i=0; i<powers.length; i++)
{
orderMap.put(powers[i], new LinkedList());
}
// validate all parsed orders
for(int i=0; i<nJudgeOrders.length; i++)
{
final NJudgeOrder njo = nJudgeOrders[i];
final Orderable order = njo.getOrder();
if(order == null)
{
Log.println("JIH::procRetreat(): Null order; njo: ", njo);
throw new IOException("Internal error: null order in JudgeImportHistory::procRetreat()");
}
// if we found a Wing unit, make sure Wing units are enabled.
checkAndEnableWings(order.getSourceUnitType());
try
{
order.validate(ts, valOpts, ruleOpts);
List list = (LinkedList) orderMap.get(order.getPower());
list.add(order);
results.addAll(njo.getResults());
Log.println(" order ok: ", order);
}
catch(OrderException e)
{
results.add( new Result(Utils.getLocalString(JIH_ORDER_PARSE_FAILURE, order, e.getMessage())));
Log.println("JIH::procMove():OrderException (during validation): ", order, "; ", e.getMessage());
throw new IOException("Cannot validate retreat order.\n"+e.getMessage());
}
}
// clear all dislodged units from board
if(positionPlacement){
Province[] dislProv = position.getDislodgedUnitProvinces();
for(int i=0;i<dislProv.length;i++) { position.setDislodgedUnit(dislProv[i],null); }
}
// now that all orders are parsed, and all units are cleared, put
// unit in the proper place.
//
Iterator iter = results.iterator();
while(iter.hasNext())
{
Result result = (Result) iter.next();
if(result instanceof OrderResult)
{
OrderResult ordResult = (OrderResult) result;
Orderable order = ordResult.getOrder();
// successful moves create a unit in the destination province
// unsuccessful moves OR disbands create no unit
if(order instanceof Move)// && ordResult.getResultType() == OrderResult.ResultType.SUCCESS)
{
// success: unit retreat to destination
Move move = (Move) order;
Unit unit = new Unit(move.getPower(), move.getSourceUnitType());
/*
* Check for the positionPlacement flag, if not, we need to position the units
* in their source places for VIEWING. Otherwise the units need to be
* in their destination place for copying.
*/
if(!positionPlacement) {
unit.setCoast(move.getSource().getCoast());
position.setDislodgedUnit(move.getSource().getProvince(), unit);
position.setLastOccupier(move.getSource().getProvince(), move.getPower());
} else {
unit.setCoast(move.getDest().getCoast());
position.setUnit(move.getDest().getProvince(), unit);
position.setLastOccupier(move.getSource().getProvince(), move.getPower());
}
} else if (order instanceof Disband){
/*
* Check for the positionPlacement flag, if not, we need to position the units
* in their source places for VIEWING. Otherwise the units should not be drawn.
*/
Unit unit = new Unit(order.getPower(), order.getSourceUnitType());
if(!positionPlacement) {
unit.setCoast(order.getSource().getCoast());
position.setDislodgedUnit(order.getSource().getProvince(), unit);
}
}
}
}
// set orders in turnstate
for(int i=0; i<powers.length; i++)
{
ts.setOrders(powers[i], (LinkedList) orderMap.get(powers[i]));
}
}
// process adjustment info ownership info (if any)
AdjustmentParser adjParser = new AdjustmentParser(map, turn.getText());
procAdjustmentBlock(adjParser.getOwnership(), ts, position);
// check for elimination
ts.getPosition().setEliminationStatus(map.getPowers());
// set adjudicated flag
ts.setResolved(true);
// add to world
world.setTurnState(ts);
}// procRetreat()
/** Process an Adjustment phase turn */
private void procAdjust(Turn turn, boolean positionPlacement)
throws IOException
{
if(turn == null)
{
return;
}
// create TurnState
final TurnState ts = makeTurnState(turn);
final List results = ts.getResultList();
final RuleOptions ruleOpts = world.getRuleOptions();
Log.println("JIH::procAdjust(): ", ts.getPhase());
// parse orders, and create orders for each unit
final JudgeOrderParser jop = new JudgeOrderParser(map, orderFactory, turn.getText());
final NJudgeOrder[] nJudgeOrders = jop.getNJudgeOrders();
// Copy previous phase positions
copyPreviousPositions(ts);
// Copy previous SC info (we need proper ownership info before parsing orders)
copyPreviousSCInfo(ts);
// DEBUG: use Adjustment to check out WTF is going on
/*
System.out.println("dip.process.Adjustment.getAdjustmentInfo()");
for(int i=0; i<map.getPowers().length; i++)
{
Power power = map.getPowers()[i];
System.out.println(" for power: "+power+"; "+dip.process.Adjustment.getAdjustmentInfo(ts, power));
}
*/
// process adjustment orders (either builds or removes)
// create a unit, unless order failed
{
// get Position
final Position position = ts.getPosition();
// create orderMap, which maps powers to their respective order list
Power[] powers = map.getPowers();
HashMap orderMap = new HashMap(powers.length);
for(int i=0; i<powers.length; i++)
{
orderMap.put(powers[i], new LinkedList());
}
// parse all orders
for(int i=0; i<nJudgeOrders.length; i++)
{
final NJudgeOrder njo = nJudgeOrders[i];
final Orderable order = njo.getOrder();
// all adjustment orders produced by NJudgeOrderParser should
// have only 1 result
//
if(njo.getResults().size() != 1)
{
throw new IOException("Internal error: JIH:procAdjustments(): "+
"getResults() != 1");
}
final Result result = (Result) njo.getResults().get(0);
// if result is a substituted result, the player defaulted,
// and the Judge inserted a Disband order
//
final boolean isDefaulted = (result instanceof SubstitutedResult);
if(order == null && !isDefaulted)
{
// orders may be null; if they are, that is because
// it's a waive or unusable/pending order. These have
// results, but no associated order.
results.addAll( njo.getResults() );
}
else
{
// NOTE: everything in this block should use newOrder,
// not order, from here on!!
Orderable newOrder = order;
if(isDefaulted)
{
newOrder = ((SubstitutedResult) result).getSubstitutedOrder();
assert (newOrder != null);
}
// if we found a Wing unit, make sure Wing units are enabled.
checkAndEnableWings(newOrder.getSourceUnitType());
try
{
newOrder.validate(ts, valOpts, ruleOpts);
if(!isDefaulted)
{
List list = (LinkedList) orderMap.get(newOrder.getPower());
list.add(newOrder);
}
results.addAll(njo.getResults());
Log.println(" order ok: ", newOrder);
// create or remove units
// as far as I know, orders are always successful.
//
if(newOrder instanceof Build)
{
if(positionPlacement)
{
final Unit unit = new Unit(newOrder.getPower(), newOrder.getSourceUnitType());
unit.setCoast(newOrder.getSource().getCoast());
position.setUnit(newOrder.getSource().getProvince(), unit);
position.setLastOccupier(newOrder.getSource().getProvince(), newOrder.getPower());
}
}
else if(newOrder instanceof Remove)
{
if(positionPlacement)
{
position.setUnit(newOrder.getSource().getProvince(), null);
}
}
else
{
throw new IllegalStateException("JIH::procAdjust(): type :"+newOrder+" not handled!");
}
}
catch(OrderException e)
{
results.add( new Result(Utils.getLocalString(JIH_ORDER_PARSE_FAILURE, newOrder, e.getMessage())));
Log.println("JIH::procAdjust():OrderException (during validation): ");
Log.println(" phase: ", ts.getPhase());
Log.println(" order: ", newOrder);
Log.println(" error: ", e.getMessage());
throw new IOException("Cannot validate adjustment order.\n"+e.getMessage());
}
}
}
// set orders in turnstate
for(int i=0; i<powers.length; i++)
{
ts.setOrders(powers[i], (LinkedList) orderMap.get(powers[i]));
}
}
// check for elimination
ts.getPosition().setEliminationStatus(map.getPowers());
// set adjudicated flag
ts.setResolved(true);
// Since this is the adjustment phase, check for supply center change. Required for VictoryConditions
// Otherwise, problems can arise and the game will end after importing due to no SC change.
if(!positionPlacement){
TurnState previousTS = world.getPreviousTurnState(ts);
while(previousTS.getPhase().getPhaseType() != Phase.PhaseType.MOVEMENT){
previousTS = world.getPreviousTurnState(previousTS);
}
//System.out.println(previousTS.getPhase());
Position oldPosition = previousTS.getPosition();
Position position = ts.getPosition();
Province[] provinces = position.getProvinces();
for(int i=0; i<provinces.length; i++)
{
Province province = provinces[i];
if(province != null && province.hasSupplyCenter())
{
Unit unit = position.getUnit(province);
if(unit != null)
{
// nextPosition still contains old ownership information
Power oldOwner = oldPosition.getSupplyCenterOwner(province);
Power newOwner = unit.getPower();
//System.out.println(oldOwner + " VS " + newOwner);
// change if ownership change, and not a wing unit
if(oldOwner != newOwner && unit.getType() != Unit.Type.WING)
{
// set owner-changed flag in TurnState [req'd for certain victory conditions]
ts.setSCOwnerChanged(true);
}
}
}
}
}
// add to world
world.setTurnState(ts);
}// procAdjust()
/**
* Clones all non-dislodged units from previous phase TurnState
* and inserts them into the current turnstate.
* <p>
* We also copy non-dislodged units, unless the CURRENT turnstate is
* an Adjustment phase
*/
private void copyPreviousPositions(TurnState current)
{
// get previous turnstate
TurnState previousTS = current.getWorld().getPreviousTurnState(current);
final boolean isCopyDislodged = (current.getPhase().getPhaseType() != Phase.PhaseType.ADJUSTMENT);
// get position info
Position newPos = current.getPosition();
Position oldPos = null;
if(previousTS == null)
{
oldPos = oldPosition;
}
else
{
oldPos = previousTS.getPosition();
}
Log.println("copyPreviousPositions() from: ", oldPos);
// clone!
final Province[] provinces = map.getProvinces();
for(int i=0; i<provinces.length; i++)
{
final Province p = provinces[i];
Unit unit = oldPos.getUnit(p);
if(unit != null)
{
Unit newUnit = (Unit) unit.clone();
newPos.setUnit(p, newUnit);
Log.println(" cloned unit from/into: ", p);
}
unit = oldPos.getDislodgedUnit(p);
if(isCopyDislodged && unit != null)
{
Unit newUnit = (Unit) unit.clone();
newPos.setDislodgedUnit(p, newUnit);
Log.println(" cloned dislodged unit from/into: ", p);
}
// clone any lastOccupied info as well.
newPos.setLastOccupier(p, oldPos.getLastOccupier(p));
}
}// copyPreviousPositions()
/**
* Copies the previous TurnState (Position, really) home SC and SC info.
* <p>
* If no previous home supply center information is available (e.g.,
* initial turn), the information from the initial board setup is
* used.
* <p>
* This method should only be used if no AdjustmentInfo block has
* been detected.
*/
private void copyPreviousSCInfo(TurnState current)
{
Log.println("copyPreviousSCInfo(): ", current.getPhase());
// get previous position information (or initial, if previous not available)
final TurnState previousTS = current.getWorld().getPreviousTurnState(current);
Position prevPos = (previousTS == null) ? oldPosition : previousTS.getPosition();
/*
if(previousTS != null)
{
Log.println(" Copying *previous* SC ownership info from: ", previousTS.getPhase());
}
else
{
Log.println(" !! Copying *previous* SC ownership info from: oldPosition");
Log.println(" world has the following Turnstates: ");
Log.println(" ", current.getWorld().getPhaseSet());
}
*/
// current position
Position currentPos = current.getPosition();
// copy!
Province[] provinces = map.getProvinces();
for(int i=0; i<provinces.length; i++)
{
Power power = prevPos.getSupplyCenterOwner(provinces[i]);
if(power != null)
{
//System.out.println(" SC @ "+provinces[i]+", owned by "+power);
currentPos.setSupplyCenterOwner(provinces[i], power);
Log.println(" set SC: ", provinces[i], " owned by ", power);
}
power = prevPos.getSupplyCenterHomePower(provinces[i]);
if(power != null)
{
currentPos.setSupplyCenterHomePower(provinces[i], power);
Log.println(" set HSC: ", provinces[i], " owned by ", power);
}
}
}// copyPreviousSCInfo()
/** Copies the Previous turnstate's lastOccupier information only */
private void copyPreviousLastOccupierInfo(TurnState current)
{
TurnState previousTS = current.getWorld().getPreviousTurnState(current);
Position newPos = current.getPosition();
Position oldPos = (previousTS == null) ? oldPosition : previousTS.getPosition();
final Province[] provinces = map.getProvinces();
for(int i=0; i<provinces.length; i++)
{
final Province p = provinces[i];
// clone any lastOccupied info as well.
newPos.setLastOccupier(p, oldPos.getLastOccupier(p));
}
}// copyPreviousLastOccupierInfo()
/**
* Processes a block of adjustment info; this can occur during a
* Move or Retreat phase. Only the Supply Center ownership is used;
* the adjustment values are ignored, since they can be computed
* based upon ownership information.
* <p>
* If no SC owner info exists, copyPreviousSCInfo() is used to
* supply the appropriate information.
*/
private void procAdjustmentBlock(AdjustmentParser.OwnerInfo[] ownerInfo, TurnState ts, Position position)
throws IOException
{
Log.println("procAdjustmentBlock(): ", ts.getPhase());
if(ownerInfo.length == 0)
{
Log.println(" No adjustment block. Copying previous SC ownership info.");
copyPreviousSCInfo(ts);
}
else
{
for(int i=0; i<ownerInfo.length; i++)
{
Power power = map.getPowerMatching(ownerInfo[i].getPowerName());
if(power != null)
{
Log.println(" SC Owned by Power: ", power);
String[] provNames = ownerInfo[i].getProvinces();
for(int pi=0; pi<provNames.length; pi++)
{
Province province = map.getProvinceMatching(provNames[pi]);
if(province == null)
{
throw new IOException("Unknown Province in SC Ownership block: "+provNames[pi]);
}
Log.println(" ", province);
position.setSupplyCenterOwner(province, power);
}
}
else
{
Log.println(" *** Unrecognized power: ", ownerInfo[i].getPowerName());
throw new IOException("Unregognized power \""+ownerInfo[i].getPowerName()+"\" in Ownership block.");
}
}
}
}// procAdjustmentBlock()
/**
* Creates correct dislodged results (with retreat information) by matching
* DislodgedInfo with the previously generated Dislodged result.
* <p>
* Units with no retreat results are destroyed, and a message generated indicating so.
* <p>
* old Dislodged results are discarded.
*/
private void makeDislodgedResults(Phase phase, List results, Position position,
DislodgedParser.DislodgedInfo[] dislodgedInfo, boolean positionPlacement)
throws IOException
{
ListIterator iter = results.listIterator();
while(iter.hasNext())
{
Result result = (Result) iter.next();
if(result instanceof OrderResult)
{
OrderResult orderResult = (OrderResult) result;
if(OrderResult.ResultType.DISLODGED.equals(orderResult.getResultType()))
{
String[] retreatLocNames = null;
for(int i=0; i<dislodgedInfo.length; i++)
{
// find the province for this dislodgedInfo source
// remember, we use map.parseLocation() to auto-normalize coasts (see Coast.normalize())
Location location = map.parseLocation(dislodgedInfo[i].getSourceName());
if(orderResult.getOrder().getSource().isProvinceEqual(location))
{
retreatLocNames = dislodgedInfo[i].getRetreatLocationNames();
break;
}
}
// we didn't find a match!! note that, and don't delete old dislodged order
if(retreatLocNames == null)
{
iter.add( new Result(Utils.getLocalString(JIH_NO_DISLODGED_MATCH, orderResult.getOrder())) );
String message = "Could not match dislodged order: "+orderResult.getOrder()+"; phase: "+phase;
Log.println(message);
// we are more strict with our errors
throw new IOException(message);
}
else
{
try
{
// create objects from retreat location names
Location[] retreatLocations = new Location[retreatLocNames.length];
for(int i=0; i<retreatLocNames.length; i++)
{
retreatLocations[i] = map.parseLocation(retreatLocNames[i]);
retreatLocations[i] = retreatLocations[i].getValidated(orderResult.getOrder().getSourceUnitType());
}
// remove old dislodged result, replacing with the new dislodged result
iter.set(new DislodgedResult(orderResult.getOrder(), retreatLocations));
// if no retreat results, destroy unit
if(retreatLocations.length == 0)
{
// destroy
Province province = orderResult.getOrder().getSource().getProvince();
Unit unit;
/*
* Check for the positionPlacement flag. If so, go ahead and set the unit to the
* dislodged one. If not, the unit that is dislodged is not SHOWN as dislodged
* therefore get that one.
*/
if(positionPlacement) { unit = position.getDislodgedUnit(province); }
else { unit = position.getUnit(province); }
position.setDislodgedUnit(province, null);
// System.out.println(" ** DESTROYING unit: "+unit+" at "+province);
// send result
iter.add(new Result(unit.getPower(), Utils.getLocalString(STDADJ_MV_UNIT_DESTROYED, unit.getType().getFullName(), province)));
}
}
catch(OrderException e)
{
// couldn't validate!!
iter.add( new Result(Utils.getLocalString(JIH_INVALID_RETREAT, orderResult.getOrder())) );
Log.println("JIH::makeDislodgedResults(): exception: ", orderResult.getOrder());
throw new IOException(e.getMessage());
}
}
}
}
}
}// makeDislodgedResults()
private void createStartingPositions() throws IOException{
Phase phase = null;
// determine the next phase by reading through the turn text.
Pattern pattern = Pattern.compile(START_POSITIONS);
Matcher m = pattern.matcher(jp.getText());
if(m.find())
{
StringBuffer sb = new StringBuffer(64);
sb.append(m.group(1));
sb.append(' ');
sb.append(m.group(2));
sb.append(' ');
sb.append(m.group(3));
phase = Phase.parse(sb.toString());
}
if(phase == null)
{
throw new IOException(Utils.getLocalString(JIH_BAD_LAST_PHASE));
}
// Create the new turnstate
TurnState ts = new TurnState(phase);
ts.setWorld(world);
ts.setPosition(new Position(world.getMap()));
// set Home Supply centers in position
Position pos = oldPosition;
for(int i=0; i<oldPosition.getHomeSupplyCenters().length; i++)
{
pos.setSupplyCenterHomePower(oldPosition.getHomeSupplyCenters()[i], oldPosition.getSupplyCenterHomePower(oldPosition.getHomeSupplyCenters()[i]));
}
// Copy previous phase positions
copyPreviousPositions(ts);
// Copy previous SC info (we need proper ownership info before parsing orders)
copyPreviousSCInfo(ts);
// check for elimination
ts.getPosition().setEliminationStatus(map.getPowers());
// add to World
world.setTurnState(ts);
}// createStartingPositions()
/**
* Creates the last TurnState, which is always ready for adjudication.
* <p>
* If parsing fails, no last turnstate will be created.
*/
private void makeLastTurnState(Turn lastTurn)
throws IOException
{
Phase phase = null;
// determine the next phase by reading through the turn text.
Pattern pattern = Pattern.compile(PARSE_REGEX);
Matcher m = pattern.matcher(lastTurn.getText());
if(m.find())
{
StringBuffer sb = new StringBuffer(64);
sb.append(m.group(1));
sb.append(' ');
sb.append(m.group(2));
sb.append(' ');
sb.append(m.group(3));
phase = Phase.parse(sb.toString());
}
if(phase == null)
{
throw new IOException(Utils.getLocalString(JIH_BAD_LAST_PHASE));
}
// Create the new turnstate
TurnState ts = new TurnState(phase);
ts.setWorld(world);
ts.setPosition(new Position(world.getMap()));
// set Home Supply centers in position
Position pos = ts.getPosition();
for(int i=0; i<homeSCInfo.length; i++)
{
pos.setSupplyCenterHomePower(homeSCInfo[i].getProvince(), homeSCInfo[i].getPower());
}
// Copy previous phase positions
copyPreviousPositions(ts);
// Copy previous SC info (we need proper ownership info before parsing orders)
copyPreviousSCInfo(ts);
// check for elimination
ts.getPosition().setEliminationStatus(map.getPowers());
// add to World
world.setTurnState(ts);
}// makeLastTurnState()
/**
* If a WING unit is detected, make sure we have the WING option
* enabled; if it already is, do nothing.
*/
private void checkAndEnableWings(Unit.Type unitType)
{
if(Unit.Type.WING.equals(unitType))
{
RuleOptions ruleOpts = world.getRuleOptions();
if(RuleOptions.VALUE_WINGS_DISABLED.equals(ruleOpts.getOptionValue(RuleOptions.OPTION_WINGS)))
{
ruleOpts.setOption(RuleOptions.OPTION_WINGS, RuleOptions.VALUE_WINGS_ENABLED);
world.setRuleOptions(ruleOpts);
}
}
}// enableWings()
/** Home Supply Center information */
private class HSCInfo
{
private Province province;
private Power power;
public HSCInfo(Province province, Power power)
{
this.province = province;
this.power = power;
}// HSCInfo()
public Province getProvince() { return province; }
public Power getPower() { return power; }
}// inner class HSCInfo
}// class JudgeImportHistory