//
// @(#)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 dip.world.*;
import dip.world.metadata.*;
import dip.order.OrderException;
import dip.order.OrderFactory;
import dip.world.variant.VariantManager;
import dip.world.variant.data.Variant;
import dip.world.variant.data.MapGraphic;
import dip.misc.Utils;
import dip.misc.Log;
import java.io.*;
import java.net.*;
import java.util.regex.PatternSyntaxException;
import java.util.*;
/**
*
* Imports text or reads a file, that is a Judge history file or listing.
* A World object is created from that file, if successful.
* <p>
* Todo:<p>
* <br>
* <br>
*/
public class JudgeImport
{
// resource constants
private static final String JI_VARIANT_NOTFOUND = "JP.import.novariant";
private static final String JI_NO_SUPPLY_INFO = "JP.import.nosupplyinfo";
private static final String JI_NO_UNIT_INFO = "JP.import.nounitinfo";
private static final String JI_UNKNOWN_POWER = "JP.import.powernotfound";
private static final String JI_UNKNOWN_PROVINCE = "JP.import.provincenotfound";
private static final String JI_BAD_POSITION = "JP.import.badposition";
private static final String JI_UNKNOWN_TYPE = "JP.import.unknowntype";
// import result constants
public static final String JI_RESULT_NEWWORLD = "JP.import.newworld";
public static final String JI_RESULT_TRYREWIND = "JP.import.tryrewind";
public static final String JI_RESULT_LOADOTHER = "JP.import.loadother";
public static final String JI_RESULT_THISWORLD = "JP.import.thisworld";
// Instance variables
private OrderFactory orderFactory = null;
private JudgeParser jp = null;
private World world = null;
private World currentWorld = null;
private String importResult = JI_RESULT_NEWWORLD;
private String gameInfo; // e.g. "Game: test Judge: USCA Variant: Standard S1901M"
/** Creates a JudgeImport object from a File */
public JudgeImport(OrderFactory orderFactory, File file, World currentWorld)
throws IOException, PatternSyntaxException
{
this(orderFactory, new FileReader(file), currentWorld);
}// JudgeImport()
/** Creates a JudgeImport object from a String */
public JudgeImport(OrderFactory orderFactory, String input)
throws IOException, PatternSyntaxException
{
this(orderFactory, new StringReader(input), null); // TODO: submit a currentWorld
}// JudgeImport()
/** Creates a JudgeImport object from a generic Reader */
public JudgeImport(OrderFactory orderFactory, Reader reader, World currentWorld)
throws IOException, PatternSyntaxException
{
this.orderFactory = orderFactory;
this.currentWorld = currentWorld;
jp = new JudgeParser(orderFactory, reader);
procJudgeInput();
}// JudgeImport()
/** Returns the World object after successful parsing, or null if unsuccessfull. */
public World getWorld()
{
return world;
}// getWorld()
/** Returns if the creator of this JudgeImport object needs to create a new world,
* or if the current world is modified.
*/
public String getResult()
{
return importResult;
}
public String getGameInfo()
{
return gameInfo;
}
/** Processing that is common to both History and Listing files */
private void procJudgeInput()
throws IOException, PatternSyntaxException
{
// determine if we can load the variant
Variant variant = VariantManager.getVariant(jp.getVariantName(), VariantManager.VERSION_NEWEST);
if(variant == null)
{
throw new IOException(Utils.getLocalString(JI_VARIANT_NOTFOUND, jp.getVariantName()));
}
// create the world
try
{
world = WorldFactory.getInstance().createWorld(variant);
// essential! create the default rules
world.setRuleOptions(RuleOptions.createFromVariant(variant));
}
catch(InvalidWorldException e)
{
throw new IOException(e.getMessage());
}
// set the 'explicit convoy' rule option (all nJudge games require this)
Log.println("JudgeImport: RuleOptions.VALUE_PATHS_EXPLICIT set");
RuleOptions ruleOpts = world.getRuleOptions();
ruleOpts.setOption(RuleOptions.OPTION_CONVOYED_MOVES, RuleOptions.VALUE_PATHS_EXPLICIT);
world.setRuleOptions(ruleOpts);
// eliminate all existing TurnStates; we will create our own from parsed values
// we need the Position, though, since it has home-supply-center information
Position position = null;
Iterator iter = world.getPhaseSet().iterator();
while(iter.hasNext())
{
TurnState ts = world.getTurnState( (Phase) iter.next());
if(position == null)
{
position = ts.getPosition();
}
world.removeTurnState( ts );
}
// set essential world data (variant name, map graphic to use)
World.VariantInfo variantInfo = world.getVariantInfo();
variantInfo.setVariantName( variant.getName() );
variantInfo.setVariantVersion( variant.getVersion() );
variantInfo.setMapName( variant.getDefaultMapGraphic().getName() );
// set general metadata
GameMetadata gmd = world.getGameMetadata();
gmd.setJudgeName(jp.getJudgeName());
gmd.setGameName(jp.getGameName());
// set player metadata (email address)
String[] pPowerNames = jp.getPlayerPowerNames();
String[] pPowerEmail = jp.getPlayerEmails();
dip.world.Map map = world.getMap();
for(int i=0; i<pPowerNames.length; i++)
{
Power power = map.getPowerMatching(pPowerNames[i]);
if(power != null)
{
PlayerMetadata pmd = world.getPlayerMetadata(power);
pmd.setEmailAddresses(new String[]{ pPowerEmail[i] });
}
else if(pPowerNames[i].equalsIgnoreCase("master"))
{
gmd.setModeratorEmail(pPowerEmail[i]);
}
}
// activate listing or history parsing
if(jp.getType() == JudgeParser.JP_TYPE_LISTING)
{
procListing(position);
}
else if (jp.getType() == JudgeParser.JP_TYPE_HISTORY)
{
JudgeImportHistory jih = new JudgeImportHistory(orderFactory, world, jp, position);
world = jih.getWorld();
}
else if (jp.getType() == JudgeParser.JP_TYPE_RESULTS)
{
procResults(jp, variant.getName());
}
else if (jp.getType() == JudgeParser.JP_TYPE_GAMESTART)
{
jp.prependText("Subject: "+jp.getJudgeName()+":"+jp.getGameName()+" - "+
jp.getPhase().getBriefName()+" Game Starting\n");
JudgeImportHistory jih = new JudgeImportHistory(orderFactory, world, jp, position);
world = jih.getWorld();
}
else {
// unknown judge input
throw new IOException(Utils.getLocalString(JI_UNKNOWN_TYPE));
}
}// procJudgeInput()
/** Process a Game Result */
private void procResults(JudgeParser jp, String variantName)
throws IOException, PatternSyntaxException
{
// set game info
gameInfo = "Judge: "+jp.getJudgeName()+" Game: "+jp.getGameName()+" Variant: "+variantName+
", "+jp.getPhase().toString();
// check, if currentWorld matches judge, game, variant and phase of these results
if (currentWorld == null)
{
importResult = JI_RESULT_LOADOTHER;
return;
}
GameMetadata gmd = currentWorld.getGameMetadata();
if ((gmd.getJudgeName() == null) || (!gmd.getJudgeName().equalsIgnoreCase(jp.getJudgeName())) ||
(gmd.getGameName() == null) || (!gmd.getGameName().equalsIgnoreCase(jp.getGameName())) ||
(!currentWorld.getVariantInfo().getVariantName().equalsIgnoreCase(variantName)))
{
// wrong game
importResult = JI_RESULT_LOADOTHER;
return;
}
else
{
// right game, check phase
if (currentWorld.getLastTurnState().getPhase().compareTo(jp.getPhase()) != 0)
{
if (currentWorld.getLastTurnState().getPhase().compareTo(jp.getPhase()) > 0)
{
importResult = JI_RESULT_TRYREWIND;
}
else
{
importResult = JI_RESULT_LOADOTHER;
}
return;
}
}
TurnParser.Turn turn = new TurnParser.Turn();
turn.setPhase(jp.getPhase());
turn.setText(jp.getText());
JudgeImportHistory jih = new JudgeImportHistory(orderFactory, currentWorld, jp, turn);
importResult = JI_RESULT_THISWORLD;
}
/** Process a Game Listing */
private void procListing(Position oldPosition)
throws IOException, PatternSyntaxException
{
// Remember, for listings, we use jp.getText(), not jp.getInitialText()
//
//
// parse position information
PositionParser pp = new PositionParser(jp.getText());
Phase phase = pp.getPhase();
PositionParser.PositionInfo[] posInfo = pp.getPositionInfo();
// parse ownership / adjustment information
AdjustmentParser ap = new AdjustmentParser(world.getMap(), jp.getText());
AdjustmentParser.OwnerInfo[] ownerInfo = ap.getOwnership();
// ERROR if no positions, or no owner information.
if(posInfo.length == 0)
{
throw new IOException(Utils.getLocalString(JI_NO_UNIT_INFO));
}
if(ownerInfo.length == 0)
{
throw new IOException(Utils.getLocalString(JI_NO_SUPPLY_INFO));
}
// Create a TurnState
TurnState ts = new TurnState(phase);
ts.setWorld(world);
// Create position information, and add to TurnState
Position position = new Position(world.getMap());
ts.setPosition(position);
// get world map information
dip.world.Map map = world.getMap();
// reset home supply centers
Province[] provinces = map.getProvinces();
for(int i=0; i<provinces.length; i++)
{
Power power = oldPosition.getSupplyCenterHomePower(provinces[i]);
if(power != null)
{
position.setSupplyCenterHomePower(provinces[i], power);
}
}
// set SC ownership information
for(int i=0; i<ownerInfo.length; i++)
{
Power power = map.getPowerMatching(ownerInfo[i].getPowerName());
if(power == null)
{
throw new IOException(Utils.getLocalString(JI_UNKNOWN_POWER, ownerInfo[i].getPowerName()));
}
String[] ownedProvNames = ownerInfo[i].getProvinces();
for(int j=0; j<ownedProvNames.length; j++)
{
Province province = map.getProvinceMatching( ownedProvNames[j] );
if(province == null)
{
throw new IOException(Utils.getLocalString(JI_UNKNOWN_PROVINCE, ownerInfo[i].getPowerName()));
}
position.setSupplyCenterOwner(province, power);
}
}
// create units & positions on the map
for(int i=0; i<posInfo.length; i++)
{
Power power = map.getPowerMatching( posInfo[i].getPowerName() );
Unit.Type unitType = Unit.Type.parse( posInfo[i].getUnitName() );
Location location = map.parseLocation( posInfo[i].getLocationName() );
// check
if(power == null || location == null || unitType.equals(Unit.Type.UNDEFINED))
{
throw new IOException(
Utils.getLocalString(JI_BAD_POSITION,
posInfo[i].getPowerName(),
posInfo[i].getUnitName(),
posInfo[i].getLocationName()) );
}
// validate location
try
{
location = location.getValidated(unitType);
}
catch(OrderException e)
{
throw new IOException(e.getMessage());
}
// create unit, and add to Position
Unit unit = new Unit(power, unitType);
unit.setCoast(location.getCoast());
position.setUnit(location.getProvince(), unit);
}
// although we parse adjustment info, we should not require it. We can just
// detect supply-center differences.
// add TurnState to World.
world.setTurnState(ts);
}// procListing()
}// class JudgeImport