package games.strategy.engine.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Logger;
import games.strategy.triplea.delegate.Matches;
import games.strategy.util.CompositeMatchOr;
import games.strategy.util.Match;
public class CompositeRouteFinder {
private static final Logger s_logger = Logger.getLogger(CompositeRouteFinder.class.getName());
private final GameMap m_map;
private final HashMap<Match<Territory>, Integer> m_matches;
/**
* This class can find composite routes between two territories.
* Example set of matches: [Friendly Land, score: 1] [Enemy Land, score: 2] [Neutral Land, score = 4]
* With this example set, an 8 length friendly route is considered equal in score to a 4 length enemy route and a 2
* length neutral route.
* This is because the friendly route score is 1/2 of the enemy route score and 1/4 of the neutral route score.
* Note that you can choose whatever scores you want, and that the matches can mix and match with each other in any
* way.
*
* @param map
* - Game map found through <gamedata>.getMap()
* @param matches
* - Set of matches and scores. The lower a match is scored, the more favorable it is.
*/
public CompositeRouteFinder(final GameMap map, final HashMap<Match<Territory>, Integer> matches) {
m_map = map;
m_matches = matches;
s_logger.finer("Initializing CompositeRouteFinderClass...");
}
private HashSet<Territory> ToHashSet(final Collection<Territory> ters) {
final HashSet<Territory> result = new HashSet<>();
for (final Territory ter : ters) {
result.add(ter);
}
return result;
}
public Route findRoute(final Territory start, final Territory end) {
final HashSet<Territory> allMatchingTers =
ToHashSet(Match.getMatches(m_map.getTerritories(), new CompositeMatchOr<>(m_matches.keySet())));
final HashMap<Territory, Integer> terScoreMap = CreateScoreMap();
final HashMap<Territory, Integer> routeScoreMap = new HashMap<>();
int bestRouteToEndScore = Integer.MAX_VALUE;
final HashMap<Territory, Territory> previous = new HashMap<>();
List<Territory> routeLeadersToProcess = new ArrayList<>();
for (final Territory ter : m_map.getNeighbors(start, Matches.territoryIsInList(allMatchingTers))) {
final int routeScore = terScoreMap.get(start) + terScoreMap.get(ter);
routeScoreMap.put(ter, routeScore);
routeLeadersToProcess.add(ter);
previous.put(ter, start);
}
while (routeLeadersToProcess.size() > 0) {
final List<Territory> newLeaders = new ArrayList<>();
for (final Territory oldLeader : routeLeadersToProcess) {
for (final Territory ter : m_map.getNeighbors(oldLeader, Matches.territoryIsInList(allMatchingTers))) {
final int routeScore = routeScoreMap.get(oldLeader) + terScoreMap.get(ter);
if (routeLeadersToProcess.contains(ter) || ter.equals(start)) {
continue;
}
if (previous.containsKey(ter)) { // If we're bumping into an existing route
if (routeScore >= routeScoreMap.get(ter)) {
continue;
}
}
if (bestRouteToEndScore <= routeScore) {
// Ignore this route leader, as we know we already have a better route
continue;
}
routeScoreMap.put(ter, routeScore);
newLeaders.add(ter);
previous.put(ter, oldLeader);
if (ter.equals(end)) {
if (routeScore < bestRouteToEndScore) {
bestRouteToEndScore = routeScore;
}
}
}
}
routeLeadersToProcess = newLeaders;
}
if (bestRouteToEndScore == Integer.MAX_VALUE) {
return null;
}
return AssembleRoute(start, end, previous);
}
private Route AssembleRoute(final Territory start, final Territory end,
final HashMap<Territory, Territory> previous) {
final List<Territory> routeTers = new ArrayList<>();
Territory curTer = end;
while (previous.containsKey(curTer)) {
routeTers.add(curTer);
curTer = previous.get(curTer);
}
routeTers.add(start);
Collections.reverse(routeTers);
return new Route(routeTers);
}
private HashMap<Territory, Integer> CreateScoreMap() {
final HashMap<Territory, Integer> result = new HashMap<>();
for (final Territory ter : m_map.getTerritories()) {
result.put(ter, getTerScore(ter));
}
return result;
}
/*
* Returns the score of the best match that matches this territory
*/
private int getTerScore(final Territory ter) {
int bestMatchingScore = Integer.MAX_VALUE;
for (final Match<Territory> match : m_matches.keySet()) {
final int score = m_matches.get(match);
if (score < bestMatchingScore) { // If this is a 'better' match
if (match.match(ter)) {
bestMatchingScore = score;
}
}
}
return bestMatchingScore;
}
}