Moving the map generator code for European starting units to a separate class. The code has been restructured to allow map defined starting positions in a later commit.

This commit is contained in:
Stian Grenborgen 2024-01-27 18:33:26 +01:00
parent b3baff9650
commit a35f586e61
2 changed files with 490 additions and 422 deletions

View File

@ -0,0 +1,456 @@
package net.sf.freecol.server.generator;
import static net.sf.freecol.common.model.Constants.INFINITY;
import static net.sf.freecol.common.util.CollectionUtils.find;
import static net.sf.freecol.common.util.CollectionUtils.minimize;
import static net.sf.freecol.common.util.CollectionUtils.transform;
import static net.sf.freecol.common.util.RandomUtils.getRandomMember;
import static net.sf.freecol.common.util.RandomUtils.randomShuffle;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.function.ToIntFunction;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.AbstractUnit;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.BuildingType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.EuropeanNationType;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Role;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TileType;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.option.GameOptions;
import net.sf.freecol.server.model.ServerBuilding;
import net.sf.freecol.server.model.ServerColony;
import net.sf.freecol.server.model.ServerPlayer;
import net.sf.freecol.server.model.ServerUnit;
/**
* Handles the starting placement of European units.
*/
class EuropeanStartingPositionsGenerator {
private static final Logger logger = Logger.getLogger(EuropeanStartingPositionsGenerator.class.getName());
private final Random random;
EuropeanStartingPositionsGenerator(Random random) {
this.random = random;
}
/**
* Creates and places the European units as determined by the map settings.
*
* The non-naval are placed on land if there are no starting naval unit able to
* carry them, and in Europe if there's a starting naval unit but no more room.
*
* Naval units are placed in Europe if non-naval units are placed on land.
*
* @param map The {@code Map} to place the european units on.
* @param players The players in the game. Starting units are still only created
* for non-REF European players.
*/
void createEuropeanUnits(Map map, List<Player> players) {
final List<Player> europeanPlayers = players.stream()
.filter(p -> p.isEuropean() && !p.isREF())
.collect(Collectors.toList());
if (europeanPlayers.isEmpty()) {
throw new RuntimeException("No players to generate units for!");
}
final java.util.Map<Player, StartingUnits> playerStartingUnits = determineStartingUnits(europeanPlayers);
final java.util.Map<Player, Tile> playerStartingTiles = determineStartingTiles(map, europeanPlayers, playerStartingUnits);
for (Player player : europeanPlayers) {
final Tile start = playerStartingTiles.get(player);
final StartingUnits startingUnits = playerStartingUnits.get(player);
placeUnitsAtStartingLocation(player, start, startingUnits);
}
}
private java.util.Map<Player, StartingUnits> determineStartingUnits(List<Player> europeanPlayers) {
final java.util.Map<Player, StartingUnits> playerStartingUnits = new HashMap<>();
for (Player player : europeanPlayers) {
playerStartingUnits.put(player, new StartingUnits(player, random));
}
return playerStartingUnits;
}
private java.util.Map<Player, Tile> determineStartingTiles(Map map, List<Player> europeanPlayers, java.util.Map<Player, StartingUnits> playerStartingUnits) {
return determineStartingTilesWithoutUsingPredeterminedPositions(map, europeanPlayers, playerStartingUnits);
}
private java.util.Map<Player, Tile> determineStartingTilesWithoutUsingPredeterminedPositions(Map map, List<Player> europeanPlayers, java.util.Map<Player, StartingUnits> playerStartingUnits) {
final Specification spec = map.getSpecification();
final int positionType = spec.getInteger(GameOptions.STARTING_POSITIONS);
final int number = europeanPlayers.size();
// Make lists of candidate starting tiles on the east and west
// of the map, then break them up by land and "sea" (revisit
// if we get a map with a lake on the edge)
List<Tile> eastTiles = new ArrayList<>();
List<Tile> westTiles = new ArrayList<>();
List<Tile> eastLandTiles = new ArrayList<>();
List<Tile> westLandTiles = new ArrayList<>();
List<Tile> eastSeaTiles = new ArrayList<>();
List<Tile> westSeaTiles = new ArrayList<>();
map.collectStartingTiles(eastTiles, westTiles);
for (Tile t : eastTiles) {
if (t.isLand()) eastLandTiles.add(t); else eastSeaTiles.add(t);
}
for (Tile t : westTiles) {
if (t.isLand()) westLandTiles.add(t); else westSeaTiles.add(t);
}
// Now consider what type of positions we are selecting from
switch (positionType) {
case GameOptions.STARTING_POSITIONS_CLASSIC:
// Break the lists up into at least <number> candidate
// positions and shuffle. Empty the relevant list on failure.
sampleTiles(eastLandTiles, number);
sampleTiles(eastSeaTiles, number);
sampleTiles(westLandTiles, number);
sampleTiles(westSeaTiles, number);
break;
case GameOptions.STARTING_POSITIONS_RANDOM:
// Random starts are the same as classic but do not
// distinguish between east and west
eastLandTiles.addAll(westLandTiles);
eastSeaTiles.addAll(westSeaTiles);
sampleTiles(eastLandTiles, number);
sampleTiles(eastSeaTiles, number);
break;
case GameOptions.STARTING_POSITIONS_HISTORICAL:
break; // Historic positions retain all the possible tiles
}
final java.util.Map<Player, Tile> playerStartingTiles = new HashMap<>();
for (Player player : europeanPlayers) {
boolean startEast = player.getNation().getStartsOnEastCoast();
boolean startAtSea = !playerStartingUnits.get(player).getCarriers().isEmpty();
// Select a starting position from the available tiles
Tile start = null;
switch (positionType) {
case GameOptions.STARTING_POSITIONS_CLASSIC:
// Classic mode respects coast preference, the lists
// are pre-sampled and shuffled
start = ((startAtSea)
? ((startEast) ? eastSeaTiles : westSeaTiles)
: ((startEast) ? eastLandTiles : westLandTiles)).remove(0);
break;
case GameOptions.STARTING_POSITIONS_RANDOM:
// Random mode is as classic but ignores coast
// preference, the east lists already contain the west
start = ((startAtSea) ? eastSeaTiles : eastLandTiles).remove(0);
break;
case GameOptions.STARTING_POSITIONS_HISTORICAL:
start = (startAtSea)
? findHistoricalStartingPosition(player, map,
eastSeaTiles, westSeaTiles)
: findHistoricalStartingPosition(player, map,
eastLandTiles, westLandTiles);
break;
}
if (start == null) {
throw new RuntimeException("Failed to find start tile "
+ ((startAtSea) ? "at sea" : "on land")
+ " for player " + player);
}
playerStartingTiles.put(player, start);
}
return playerStartingTiles;
}
private void placeUnitsAtStartingLocation(Player player, Tile start, StartingUnits startingUnits) {
final Map map = player.getGame().getMap();
final Specification spec = map.getSpecification();
player.setEntryTile(start);
final Europe europe = player.getEurope();
final ServerPlayer serverPlayer = (ServerPlayer) player;
if (!start.isLand()) { // All aboard!
for (Unit u : startingUnits.getCarriers()) {
u.setLocation(start);
serverPlayer.exploreForUnit(u);
}
passengers: for (Unit unit : startingUnits.getPassengers()) {
for (Unit carrier : startingUnits.getCarriers()) {
if (carrier.canAdd(unit)) {
unit.setLocation(carrier);
continue passengers;
}
}
// no space left on carriers
unit.setLocation(europe);
}
for (Unit u : startingUnits.getOtherNaval()) {
u.setLocation(start);
serverPlayer.exploreForUnit(u);
}
} else { // Land ho!
for (Unit u : startingUnits.getPassengers()) {
u.setLocation(start);
serverPlayer.exploreForUnit(u);
}
for (Unit u : startingUnits.getOtherNaval()) {
u.setLocation(europe);
}
}
if (FreeColDebugger.isInDebugMode(FreeColDebugger.DebugMode.INIT)) {
createDebugUnits(map, player, start);
spec.setInteger(GameOptions.STARTING_MONEY, 10000);
}
// Start our REF player entry tile somewhere near to our
// starting tile, but do not place their units.
// They are assumed to always be on naval transport.
final Player ourREF = player.getREFPlayer();
if (ourREF == null) {
return;
}
final int distance = 10;
final List<Tile> refTiles = StreamSupport.stream(map.getCircleTiles(start, true, distance).spliterator(), false)
.filter(t -> !t.isLand())
.collect(Collectors.toList());
final Tile startRef = getRandomMember(logger, ourREF + " start", refTiles, random);
ourREF.setEntryTile(startRef);
}
/**
* Find the best historical starting position for a player from lists
* of tiles.
*
* @param player The {@code Player} to find a tile for.
* @param map The {@code Map} to search.
* @param east A list of starting {@code Tile}s on the east of the map.
* @param west A list of starting {@code Tile}s on the west of the map.
* @return The best {@code Tile} found, or null if none suitable.
*/
private Tile findHistoricalStartingPosition(final Player player,
final Map map,
List<Tile> east,
List<Tile> west) {
final Nation nation = player.getNation();
final int latY = map.getRow(nation.getPreferredLatitude());
List<Tile> tiles = (nation.getStartsOnEastCoast()) ? east : west;
if (tiles.isEmpty()) return null;
final ToIntFunction<Tile> dist = t -> Math.abs(t.getY() - latY);
final Comparator<Tile> closest = Comparator.comparingInt(dist);
Tile ret = minimize(tiles, closest);
tiles.remove(ret); // chosen tile is no longer a candidate
return ret;
}
/**
* Sample a list of tiles to pick spread out starting positions.
* Shuffle the result or clear it if there were too few tiles.
*
* @param tiles The list of {@code Tile}s to sample.
* @param number The number of players, which determines the spacing.
* @return True if there were enough tiles in the list.
*/
private boolean sampleTiles(List<Tile> tiles, int number) {
final int n = tiles.size();
final int step = n / number;
if (step <= 1) {
tiles.clear();
return false;
}
List<Tile> samples = new ArrayList<>();
// The offset start prevents selecting right on the poles
for (int i = step/2; i < n; i += step) {
samples.add(tiles.get(i));
}
tiles.clear();
tiles.addAll(samples);
randomShuffle(logger, "Starting tiles", tiles, random);
return true;
}
private List<Unit> createDebugUnits(Map map, Player player, Tile startTile) {
final Game game = map.getGame();
final Specification spec = game.getSpecification();
List<Unit> ret = new ArrayList<>(20);
// In debug mode give each player a few more units and a colony.
UnitType unitType = spec.getUnitType("model.unit.galleon");
Unit galleon = new ServerUnit(game, startTile, player, unitType);
galleon.setName(player.getNameForUnit(unitType, random));
ret.add(galleon);
unitType = spec.getUnitType("model.unit.privateer");
Unit privateer = new ServerUnit(game, startTile, player, unitType);
ret.add(privateer);
privateer.setName(player.getNameForUnit(unitType, random));
((ServerPlayer)player).exploreForUnit(privateer);
unitType = spec.getUnitType("model.unit.freeColonist");
ret.add(new ServerUnit(game, galleon, player, unitType));
unitType = spec.getUnitType("model.unit.veteranSoldier");
ret.add(new ServerUnit(game, galleon, player, unitType));
unitType = spec.getUnitType("model.unit.jesuitMissionary");
ret.add(new ServerUnit(game, galleon, player, unitType));
Tile colonyTile = null;
for (Tile tempTile : map.getCircleTiles(startTile, true, INFINITY)) {
if (tempTile.isPolar()) continue; // No initial polar colonies
if (player.canClaimToFoundSettlement(tempTile)) {
colonyTile = tempTile;
break;
}
}
if (colonyTile == null) {
return ret;
}
colonyTile.setType(find(spec.getTileTypeList(), t -> !t.isWater()));
unitType = spec.getUnitType("model.unit.expertFarmer");
Unit buildColonyUnit = new ServerUnit(game, colonyTile,
player, unitType);
ret.add(buildColonyUnit);
String colonyName = Messages.message(player.getNationLabel())
+ " " + Messages.message("Colony");
Colony colony = new ServerColony(game, player, colonyName, colonyTile);
player.addSettlement(colony);
colony.placeSettlement(true);
for (Tile tile : transform(colonyTile.getSurroundingTiles(1,1), t ->
(!t.hasSettlement()
&& (t.getOwner() == null || !t.getOwner().isEuropean())))) {
tile.changeOwnership(player, colony);
if (tile.hasLostCityRumour()) tile.removeLostCityRumour();
}
buildColonyUnit.setLocation(colony);
Tile ct = buildColonyUnit.getWorkTile();
if (ct != null) {
for (TileType t : transform(spec.getTileTypeList(),
tt -> !tt.isWater())) {
ct.setType(t);
TileImprovementType plowType = map.getSpecification()
.getTileImprovementType("model.improvement.plow");
TileImprovement plow = new TileImprovement(game, ct, plowType, null);
plow.setTurnsToComplete(0);
ct.add(plow);
break;
}
}
BuildingType schoolType = spec.getBuildingType("model.building.schoolhouse");
Building schoolhouse = new ServerBuilding(game, colony, schoolType);
colony.addBuilding(schoolhouse);
unitType = spec.getUnitType("model.unit.elderStatesman");
Unit statesman = new ServerUnit(game, colonyTile, player, unitType);
ret.add(statesman);
statesman.setLocation(colony.getWorkLocationFor(statesman,
statesman.getType().getExpertProduction()));
unitType = spec.getUnitType("model.unit.expertLumberJack");
Unit lumberjack = new ServerUnit(game, colony, player, unitType);
ret.add(lumberjack);
Tile lt = lumberjack.getWorkTile();
if (lt != null) {
TileType tt = find(spec.getTileTypeList(), TileType::isForested);
if (tt != null) lt.setType(tt);
lumberjack.changeWorkType(lumberjack.getType()
.getExpertProduction());
}
unitType = spec.getUnitType("model.unit.masterCarpenter");
ret.add(new ServerUnit(game, colony, player, unitType));
unitType = spec.getUnitType("model.unit.seasonedScout");
Unit scout = new ServerUnit(game, colonyTile, player, unitType);
ret.add(scout);
((ServerPlayer)player).exploreForUnit(scout);
unitType = spec.getUnitType("model.unit.veteranSoldier");
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
unitType = spec.getUnitType("model.unit.artillery");
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
unitType = spec.getUnitType("model.unit.treasureTrain");
Unit train = new ServerUnit(game, colonyTile, player, unitType);
ret.add(train);
train.setTreasureAmount(10000);
unitType = spec.getUnitType("model.unit.wagonTrain");
Unit wagon = new ServerUnit(game, colonyTile, player, unitType);
ret.add(wagon);
GoodsType cigarsType = spec.getGoodsType("model.goods.cigars");
Goods cigards = new Goods(game, wagon, cigarsType, 5);
wagon.add(cigards);
unitType = spec.getUnitType("model.unit.jesuitMissionary");
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
((ServerPlayer)player).exploreForSettlement(colony);
return ret;
}
private static final class StartingUnits {
private final List<Unit> carriers = new ArrayList<>();
private final List<Unit> passengers = new ArrayList<>();
private final List<Unit> otherNaval = new ArrayList<>();
StartingUnits(Player player, Random random) {
final Game game = player.getGame();
final Specification spec = player.getSpecification();
List<AbstractUnit> unitList = ((EuropeanNationType) player.getNationType()).getStartingUnits();
for (AbstractUnit startingUnit : unitList) {
UnitType type = startingUnit.getType(spec);
Role role = startingUnit.getRole(spec);
Unit newUnit = new ServerUnit(game, null, player, type, role);
newUnit.setName(player.getNameForUnit(type, random));
if (newUnit.isNaval()) {
if (newUnit.canCarryUnits()) {
newUnit.setState(Unit.UnitState.ACTIVE);
carriers.add(newUnit);
} else {
otherNaval.add(newUnit);
}
} else {
newUnit.setState(Unit.UnitState.SENTRY);
passengers.add(newUnit);
}
}
}
List<Unit> getCarriers() {
return carriers;
}
List<Unit> getPassengers() {
return passengers;
}
List<Unit> getOtherNaval() {
return otherNaval;
}
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (C) 2002-2022 The FreeCol Team
* Copyright (C) 2002-2024 The FreeCol Team
*
* This file is part of FreeCol.
*
@ -19,7 +19,6 @@
package net.sf.freecol.server.generator;
import static net.sf.freecol.common.model.Constants.INFINITY;
import static net.sf.freecol.common.util.CollectionUtils.alwaysTrue;
import static net.sf.freecol.common.util.CollectionUtils.any;
import static net.sf.freecol.common.util.CollectionUtils.descendingListLengthComparator;
@ -29,7 +28,6 @@ import static net.sf.freecol.common.util.CollectionUtils.isNotNull;
import static net.sf.freecol.common.util.CollectionUtils.isNull;
import static net.sf.freecol.common.util.CollectionUtils.map;
import static net.sf.freecol.common.util.CollectionUtils.matchKeyEquals;
import static net.sf.freecol.common.util.CollectionUtils.minimize;
import static net.sf.freecol.common.util.CollectionUtils.toListNoNulls;
import static net.sf.freecol.common.util.CollectionUtils.transform;
import static net.sf.freecol.common.util.RandomUtils.getRandomMember;
@ -39,7 +37,6 @@ import static net.sf.freecol.common.util.RandomUtils.randomShuffle;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@ -48,21 +45,12 @@ import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import net.sf.freecol.common.debug.FreeColDebugger;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Ability;
import net.sf.freecol.common.model.AbstractUnit;
import net.sf.freecol.common.model.Area;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.BuildingType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Direction;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.EuropeanNationType;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsType;
@ -74,12 +62,8 @@ import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.NationType;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Role;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TileType;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.option.GameOptions;
@ -88,8 +72,6 @@ import net.sf.freecol.common.option.OptionGroup;
import net.sf.freecol.common.util.LogBuilder;
import net.sf.freecol.common.util.RandomChoice;
import net.sf.freecol.common.util.RandomUtils.RandomIntCache;
import net.sf.freecol.server.model.ServerBuilding;
import net.sf.freecol.server.model.ServerColony;
import net.sf.freecol.server.model.ServerIndianSettlement;
import net.sf.freecol.server.model.ServerPlayer;
import net.sf.freecol.server.model.ServerRegion;
@ -131,6 +113,36 @@ public class SimpleMapGenerator implements MapGenerator {
this.cache = new RandomIntCache(logger, "simpleMap", random,
1 << 16, 512);
}
/**
* {@inheritDoc}
*/
@Override
public Map generateEmptyMap(Game game, int width, int height, LogBuilder lb) {
final LandMap landMap = new LandMap(width, height, this.cache);
return new TerrainGenerator(this.random).generateMap(game, null, landMap, lb);
}
/**
* {@inheritDoc}
*/
@Override
public Map generateMap(Game game, Map importMap, boolean generateEuropeanPlayerUnits, LogBuilder lb) {
final LandMap landMap = (importMap != null)
? new LandMap(importMap, this.cache)
: new LandMap(game.getMapGeneratorOptions(), this.cache);
final Map map = new TerrainGenerator(this.random).generateMap(game, importMap, landMap, lb);
makeNativeSettlements(map, importMap, lb);
makeLostCityRumours(map, importMap, lb);
if (generateEuropeanPlayerUnits) {
createEuropeanUnits(map, game.getLiveEuropeanPlayerList());
}
lb.shrink("\n");
return map;
}
/**
@ -619,408 +631,8 @@ public class SimpleMapGenerator implements MapGenerator {
spec.getUnitTypesWithAbility(Ability.EXPERT_SCOUT), random);
}
/**
* Sample a list of tiles to pick spread out starting positions.
* Shuffle the result or clear it if there were too few tiles.
*
* @param tiles The list of {@code Tile}s to sample.
* @param number The number of players, which determines the spacing.
* @return True if there were enough tiles in the list.
*/
private boolean sampleTiles(List<Tile> tiles, int number) {
final int n = tiles.size();
final int step = n / number;
if (step <= 1) {
tiles.clear();
return false;
}
List<Tile> samples = new ArrayList<>();
// The offset start prevents selecting right on the poles
for (int i = step/2; i < n; i += step) {
samples.add(tiles.get(i));
}
tiles.clear();
tiles.addAll(samples);
randomShuffle(logger, "Starting tiles", tiles, random);
return true;
}
/**
* Find the best historical starting position for a player from lists
* of tiles.
*
* @param player The {@code Player} to find a tile for.
* @param map The {@code Map} to search.
* @param east A list of starting {@code Tile}s on the east of the map.
* @param west A list of starting {@code Tile}s on the west of the map.
* @return The best {@code Tile} found, or null if none suitable.
*/
private Tile findHistoricalStartingPosition(final Player player,
final Map map,
List<Tile> east,
List<Tile> west) {
final Nation nation = player.getNation();
final int latY = map.getRow(nation.getPreferredLatitude());
List<Tile> tiles = (nation.getStartsOnEastCoast()) ? east : west;
if (tiles.isEmpty()) return null;
final ToIntFunction<Tile> dist = t -> Math.abs(t.getY() - latY);
final Comparator<Tile> closest = Comparator.comparingInt(dist);
Tile ret = minimize(tiles, closest);
tiles.remove(ret); // chosen tile is no longer a candidate
return ret;
}
/**
* Create two ships, one with a colonist, for each player, and
* select suitable starting positions.
*
* @param map The {@code Map} to place the european units on.
* @param players The players to create {@code Settlement}s
* and starting locations for. That is; both indian and
* european players.
* @param lb A {@code LogBuilder} to log to.
*/
private void createEuropeanUnits(Map map, List<Player> players,
LogBuilder lb) {
final Game game = map.getGame();
final Specification spec = game.getSpecification();
final int positionType = spec.getInteger(GameOptions.STARTING_POSITIONS);
// Split out the non-REF players
final Predicate<Player> notREF = (Player p) -> !p.isREF();
List<Player> europeanPlayers = transform(players, notREF);
final int number = europeanPlayers.size();
if (europeanPlayers.isEmpty()) {
throw new RuntimeException("No players to generate units for!");
}
// Make lists of candidate starting tiles on the east and west
// of the map, then break them up by land and "sea" (revisit
// if we get a map with a lake on the edge)
List<Tile> eastTiles = new ArrayList<>();
List<Tile> westTiles = new ArrayList<>();
List<Tile> eastLandTiles = new ArrayList<>();
List<Tile> westLandTiles = new ArrayList<>();
List<Tile> eastSeaTiles = new ArrayList<>();
List<Tile> westSeaTiles = new ArrayList<>();
map.collectStartingTiles(eastTiles, westTiles);
for (Tile t : eastTiles) {
if (t.isLand()) eastLandTiles.add(t); else eastSeaTiles.add(t);
}
for (Tile t : westTiles) {
if (t.isLand()) westLandTiles.add(t); else westSeaTiles.add(t);
}
// Now consider what type of positions we are selecting from
switch (positionType) {
case GameOptions.STARTING_POSITIONS_CLASSIC:
// Break the lists up into at least <number> candidate
// positions and shuffle. Empty the relevant list on failure.
sampleTiles(eastLandTiles, number);
sampleTiles(eastSeaTiles, number);
sampleTiles(westLandTiles, number);
sampleTiles(westSeaTiles, number);
break;
case GameOptions.STARTING_POSITIONS_RANDOM:
// Random starts are the same as classic but do not
// distinguish between east and west
eastLandTiles.addAll(westLandTiles);
eastSeaTiles.addAll(westSeaTiles);
sampleTiles(eastLandTiles, number);
sampleTiles(eastSeaTiles, number);
break;
case GameOptions.STARTING_POSITIONS_HISTORICAL:
break; // Historic positions retain all the possible tiles
}
// For each player, find the units, which determines whether
// to start at sea.
List<Tile> startingTiles = new ArrayList<>();
List<Unit> carriers = new ArrayList<>();
List<Unit> passengers = new ArrayList<>();
List<Unit> otherNaval = new ArrayList<>();
for (Player player : europeanPlayers) {
lb.add("For player ", player, ", ");
carriers.clear();
passengers.clear();
otherNaval.clear();
List<AbstractUnit> unitList = ((EuropeanNationType)player.getNationType())
.getStartingUnits();
for (AbstractUnit startingUnit : unitList) {
UnitType type = startingUnit.getType(spec);
Role role = startingUnit.getRole(spec);
Unit newUnit = new ServerUnit(game, null, player, type, role);
newUnit.setName(player.getNameForUnit(type, random));
if (newUnit.isNaval()) {
if (newUnit.canCarryUnits()) {
newUnit.setState(Unit.UnitState.ACTIVE);
carriers.add(newUnit);
} else {
otherNaval.add(newUnit);
}
} else {
newUnit.setState(Unit.UnitState.SENTRY);
passengers.add(newUnit);
}
}
boolean startEast = player.getNation().getStartsOnEastCoast();
boolean startAtSea = !carriers.isEmpty();
lb.add("found ", carriers.size(), " carriers, ",
passengers.size(), " passengers, ",
otherNaval.size(), " other naval units, starting ",
(startAtSea) ? "at sea" : "on land", " to the ",
(startEast) ? "east" : "west");
// Select a starting position from the available tiles
Tile start = null;
switch (positionType) {
case GameOptions.STARTING_POSITIONS_CLASSIC:
// Classic mode respects coast preference, the lists
// are pre-sampled and shuffled
start = ((startAtSea)
? ((startEast) ? eastSeaTiles : westSeaTiles)
: ((startEast) ? eastLandTiles : westLandTiles)).remove(0);
break;
case GameOptions.STARTING_POSITIONS_RANDOM:
// Random mode is as classic but ignores coast
// preference, the east lists already contain the west
start = ((startAtSea) ? eastSeaTiles : eastLandTiles).remove(0);
break;
case GameOptions.STARTING_POSITIONS_HISTORICAL:
start = (startAtSea)
? findHistoricalStartingPosition(player, map,
eastSeaTiles, westSeaTiles)
: findHistoricalStartingPosition(player, map,
eastLandTiles, westLandTiles);
break;
}
if (start == null) {
throw new RuntimeException("Failed to find start tile "
+ ((startAtSea) ? "at sea" : "on land")
+ " for player " + player);
}
player.setEntryTile(start);
lb.add(" at starting tile ", start);
final Europe europe = player.getEurope();
if (startAtSea) { // All aboard!
for (Unit u : carriers) {
u.setLocation(start);
((ServerPlayer)player).exploreForUnit(u);
}
passengers: for (Unit unit : passengers) {
for (Unit carrier : carriers) {
if (carrier.canAdd(unit)) {
unit.setLocation(carrier);
continue passengers;
}
}
// no space left on carriers
unit.setLocation(europe);
}
} else { // Land ho!
for (Unit u : passengers) {
u.setLocation(start);
((ServerPlayer)player).exploreForUnit(u);
}
}
for (Unit u : otherNaval) {
u.setLocation(europe);
}
if (FreeColDebugger.isInDebugMode(FreeColDebugger.DebugMode.INIT)) {
createDebugUnits(map, player, start, lb);
spec.setInteger(GameOptions.STARTING_MONEY, 10000);
}
// Start our REF player entry tile somewhere near to our
// starting tile, but do not place their units.
// They are assumed to always be on naval transport.
final Player ourREF = player.getREFPlayer();
if (ourREF == null) continue; // Some tests do not generate REFs
final int threshold = 10;
final int startY = start.getY();
final Predicate<Tile> closeSeaTile
= t -> !t.isLand() && Math.abs(t.getY() - startY) < threshold;
List<Tile> refTiles = (startEast) ? eastTiles : westTiles;
start = getRandomMember(logger, ourREF + " start",
transform(refTiles, closeSeaTile), random);
ourREF.setEntryTile(start);
lb.add(" with REF at ", start, ".\n");
}
}
private Tile findTileFor(Map map, int row, int start, boolean startAtSea,
LogBuilder lb) {
Tile tile = null;
Tile ret = null;
int offset = (start == 0) ? 1 : -1;
for (int x = start; 0 <= x && x < map.getWidth(); x += offset) {
tile = map.getTile(x, row);
if (tile.isDirectlyHighSeasConnected()) {
if (startAtSea != tile.isLand()) ret = tile;
} else {
break; // Previous tile is a potential candidate
}
}
if (ret == null) lb.add("No suitable tile in row ", row, ".\n");
return ret;
}
private List<Unit> createDebugUnits(Map map, Player player, Tile startTile,
LogBuilder lb) {
final Game game = map.getGame();
final Specification spec = game.getSpecification();
List<Unit> ret = new ArrayList<>(20);
// In debug mode give each player a few more units and a colony.
UnitType unitType = spec.getUnitType("model.unit.galleon");
Unit galleon = new ServerUnit(game, startTile, player, unitType);
galleon.setName(player.getNameForUnit(unitType, random));
ret.add(galleon);
unitType = spec.getUnitType("model.unit.privateer");
Unit privateer = new ServerUnit(game, startTile, player, unitType);
ret.add(privateer);
privateer.setName(player.getNameForUnit(unitType, random));
((ServerPlayer)player).exploreForUnit(privateer);
unitType = spec.getUnitType("model.unit.freeColonist");
ret.add(new ServerUnit(game, galleon, player, unitType));
unitType = spec.getUnitType("model.unit.veteranSoldier");
ret.add(new ServerUnit(game, galleon, player, unitType));
unitType = spec.getUnitType("model.unit.jesuitMissionary");
ret.add(new ServerUnit(game, galleon, player, unitType));
Tile colonyTile = null;
for (Tile tempTile : map.getCircleTiles(startTile, true, INFINITY)) {
if (tempTile.isPolar()) continue; // No initial polar colonies
if (player.canClaimToFoundSettlement(tempTile)) {
colonyTile = tempTile;
break;
}
}
if (colonyTile == null) {
lb.add("Could not find a debug colony site.\n");
return ret;
}
colonyTile.setType(find(spec.getTileTypeList(), t -> !t.isWater()));
unitType = spec.getUnitType("model.unit.expertFarmer");
Unit buildColonyUnit = new ServerUnit(game, colonyTile,
player, unitType);
ret.add(buildColonyUnit);
String colonyName = Messages.message(player.getNationLabel())
+ " " + Messages.message("Colony");
Colony colony = new ServerColony(game, player, colonyName, colonyTile);
player.addSettlement(colony);
colony.placeSettlement(true);
for (Tile tile : transform(colonyTile.getSurroundingTiles(1,1), t ->
(!t.hasSettlement()
&& (t.getOwner() == null || !t.getOwner().isEuropean())))) {
tile.changeOwnership(player, colony);
if (tile.hasLostCityRumour()) tile.removeLostCityRumour();
}
buildColonyUnit.setLocation(colony);
Tile ct = buildColonyUnit.getWorkTile();
if (ct != null) {
for (TileType t : transform(spec.getTileTypeList(),
tt -> !tt.isWater())) {
ct.setType(t);
TileImprovementType plowType = map.getSpecification()
.getTileImprovementType("model.improvement.plow");
TileImprovement plow = new TileImprovement(game, ct, plowType, null);
plow.setTurnsToComplete(0);
ct.add(plow);
break;
}
}
BuildingType schoolType = spec.getBuildingType("model.building.schoolhouse");
Building schoolhouse = new ServerBuilding(game, colony, schoolType);
colony.addBuilding(schoolhouse);
unitType = spec.getUnitType("model.unit.elderStatesman");
Unit statesman = new ServerUnit(game, colonyTile, player, unitType);
ret.add(statesman);
statesman.setLocation(colony.getWorkLocationFor(statesman,
statesman.getType().getExpertProduction()));
unitType = spec.getUnitType("model.unit.expertLumberJack");
Unit lumberjack = new ServerUnit(game, colony, player, unitType);
ret.add(lumberjack);
Tile lt = lumberjack.getWorkTile();
if (lt != null) {
TileType tt = find(spec.getTileTypeList(), TileType::isForested);
if (tt != null) lt.setType(tt);
lumberjack.changeWorkType(lumberjack.getType()
.getExpertProduction());
}
unitType = spec.getUnitType("model.unit.masterCarpenter");
ret.add(new ServerUnit(game, colony, player, unitType));
unitType = spec.getUnitType("model.unit.seasonedScout");
Unit scout = new ServerUnit(game, colonyTile, player, unitType);
ret.add(scout);
((ServerPlayer)player).exploreForUnit(scout);
unitType = spec.getUnitType("model.unit.veteranSoldier");
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
unitType = spec.getUnitType("model.unit.artillery");
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
unitType = spec.getUnitType("model.unit.treasureTrain");
Unit train = new ServerUnit(game, colonyTile, player, unitType);
ret.add(train);
train.setTreasureAmount(10000);
unitType = spec.getUnitType("model.unit.wagonTrain");
Unit wagon = new ServerUnit(game, colonyTile, player, unitType);
ret.add(wagon);
GoodsType cigarsType = spec.getGoodsType("model.goods.cigars");
Goods cigards = new Goods(game, wagon, cigarsType, 5);
wagon.add(cigards);
unitType = spec.getUnitType("model.unit.jesuitMissionary");
ret.add(new ServerUnit(game, colonyTile, player, unitType));
ret.add(new ServerUnit(game, colonyTile, player, unitType));
((ServerPlayer)player).exploreForSettlement(colony);
return ret;
}
// Implement MapGenerator
/**
* {@inheritDoc}
*/
@Override
public Map generateEmptyMap(Game game, int width, int height,
LogBuilder lb) {
return new TerrainGenerator(this.random)
.generateMap(game, null,
new LandMap(width, height, this.cache), lb);
}
/**
* {@inheritDoc}
*/
@Override
public Map generateMap(Game game, Map importMap, boolean generateEuropeanPlayerUnits, LogBuilder lb) {
// Create land map.
LandMap landMap = (importMap != null)
? new LandMap(importMap, this.cache)
: new LandMap(game.getMapGeneratorOptions(), this.cache);
// Create terrain.
Map map = new TerrainGenerator(this.random)
.generateMap(game, importMap, landMap, lb);
// Decorate the map.
makeNativeSettlements(map, importMap, lb);
makeLostCityRumours(map, importMap, lb);
if (generateEuropeanPlayerUnits) {
createEuropeanUnits(map, game.getLiveEuropeanPlayerList(), lb);
}
lb.shrink("\n");
return map;
private void createEuropeanUnits(Map map, List<Player> liveEuropeanPlayerList) {
final EuropeanStartingPositionsGenerator startPosGenerator = new EuropeanStartingPositionsGenerator(random);
startPosGenerator.createEuropeanUnits(map, liveEuropeanPlayerList);
}
}