package com.lyndir.omicron.api; import com.google.common.collect.*; import com.lyndir.lhunath.opal.math.*; import com.lyndir.lhunath.opal.system.error.AlreadyCheckedException; import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.util.*; import com.lyndir.omicron.api.error.NotAuthenticatedException; import com.lyndir.omicron.api.util.PathUtils; import java.util.*; import java.util.Optional; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nullable; /** * <i>10 07, 2012</i> * * @author lhunath */ @SuppressWarnings("ParameterHidesMemberVariable") // IDEA doesn't understand setters that return this. public class Game extends MetaObject implements IGame { @SuppressWarnings("UnusedDeclaration") private static final Logger logger = Logger.get( Game.class ); private static final Random RANDOM = new Random(); private final Deque<Turn> turns = new ConcurrentLinkedDeque<>(); @ObjectMeta(ignoreFor = ObjectMeta.For.all) private final GameController gameController; private final Size levelSize; private final ImmutableList<Level> levels; private final ImmutableList<Player> players; private final Set<Player> readyPlayers = Collections.synchronizedSet( new HashSet<>() ); private boolean running; public static Builder builder() { return new Builder(); } private Game(final Size levelSize, final Iterable<Player> players, final Stream<VictoryConditionType> victoryConditions, final Map<GameListener, Player> gameListeners, final GameResourceConfig resourceConfig, final GameUnitConfig unitConfig) throws NotAuthenticatedException { turns.add( new Turn() ); this.levelSize = levelSize; levels = ImmutableList.of( new Level( levelSize, LevelType.GROUND ), new Level( levelSize, LevelType.SKY ), new Level( levelSize, LevelType.SPACE ) ); this.players = ImmutableList.copyOf( players ); gameController = new GameController( this ); for (Iterator<VictoryConditionType> iterator = victoryConditions.iterator(); iterator.hasNext(); ) iterator.next().install( this ); gameController.addGameListeners( gameListeners ); Security.activateGame( this ); // Add resources to the tiles. // Figure out how many resources we're distributing for each of the resource types supported by our levels. Map<ResourceType, Integer> remainingResources = new EnumMap<>( ResourceType.class ); remainingResources.putAll( FluentIterable.from( levels ) .transformAndConcat( level -> level.getType().getSupportedResources() ) .toMap( resourceConfig::quantity ) ); while (true) { // Do we have remaining undistributed resources left? while (remainingResources.values().remove( 0 )) ; if (remainingResources.isEmpty()) break; // Go over our levels and distribute a puddle of remaining resources supported by them. for (final Level level : levels) { for (final ResourceType resourceType : level.getType().getSupportedResources()) { Integer remaining = remainingResources.get( resourceType ); if (remaining == null || remaining == 0) // No resources left to distribute for this type. continue; // Pick a spot to start a puddle, and determine the puddle tiles. Vec2 position = Vec2.create( RANDOM.nextInt( level.getSize().getWidth() ), RANDOM.nextInt( level.getSize().getHeight() ) ); Collection<? extends ITile> puddle = PathUtils.neighbours( level.getTile( position ).get(), resourceConfig.puddleSize( resourceType ), ITile::neighbours ); // Fill the puddle tiles with resource. for (final ITile tile : puddle) { int tileResources = Math.min( remaining, RANDOM.nextInt( resourceConfig.quantityPerTile( resourceType ) ) ); Tile.cast( tile ).addResourceQuantity( resourceType, tileResources ); remaining -= tileResources; logger.trc( "Deposited %d %s at %s (%d left to deposit)", tileResources, resourceType, tile.getPosition(), remaining ); } // Remember how much undistributed resource is left. remainingResources.put( resourceType, remaining ); } } } // Give each player some units. for (final Player player : players) unitConfig.addUnits( this, player ); } @Override public int hashCode() { return System.identityHashCode( this ); } @Override public boolean equals(@Nullable final Object obj) { return obj == this; } @Override public GameController getController() { return gameController; } @Override public Deque<Turn> getTurns() { return turns; } Turn newTurn() { readyPlayers.clear(); Turn newTurn = new Turn( turns.getLast() ); turns.add( newTurn ); return newTurn; } @Override public ImmutableList<Player> getPlayers() { return players; } @Override public ImmutableSet<Player> getReadyPlayers() { return ImmutableSet.copyOf( readyPlayers ); } boolean setReady(final Player player) { synchronized (readyPlayers) { return readyPlayers.add( player ) && readyPlayers.containsAll( getPlayers() ); } } void setRunning(final boolean running) { this.running = running; } @Override public boolean isRunning() { return running; } @Override public Size getLevelSize() { return levelSize; } @Override public ImmutableList<? extends ILevel> getLevels() { return levels; } public static class Builder implements IBuilder { private final Map<GameListener, Player> gameListeners = Maps.newLinkedHashMap(); private final List<Player> players = Lists.newLinkedList(); private final List<PublicVictoryConditionType> victoryConditions = Lists.newArrayList( PublicVictoryConditionType.values() ); private Size levelSize = new Size( 200, 200 ); private int nextPlayerID = 1; private int totalPlayers = 4; private GameResourceConfig resourceConfig = GameResourceConfigs.PLENTY; private PublicGameUnitConfig unitConfig = PublicGameUnitConfig.BASIC; private Builder() { } @Override public Game build() { return Security.godRun( () -> { // Add random players until totalPlayers count is satisfied. while (players.size() < totalPlayers) players.add( new Player( nextPlayerID(), null, Player.randomName(), // Color.Template.randomColor(), Color.Template.randomColor() ) ); return new Game( levelSize, players, VictoryConditionType.cast( victoryConditions ), gameListeners, resourceConfig, GameUnitConfig.cast( unitConfig ) ); } ); } @Override public Size getLevelSize() { return levelSize; } @Override public Builder setLevelSize(final Size levelSize) { this.levelSize = levelSize; return this; } @Override public Collection<? extends IPlayer> getPlayers() { return players; } @Override public Builder setPlayer(final PlayerKey playerKey, final String name, final Color primaryColor, final Color secondaryColor) { Player existingPlayer = Iterables.find( players, player -> player.hasKey( playerKey ), null ); if (existingPlayer != null) players.remove( existingPlayer ); players.add( new Player( nextPlayerID(), playerKey, name, primaryColor, secondaryColor ) ); return this; } @Override public Player addPlayer(final PlayerKey playerKey, final String name, final Color primaryColor, final Color secondaryColor) { Player player = new Player( nextPlayerID(), playerKey, name, primaryColor, secondaryColor ); players.add( player ); return player; } @Override public List<PublicVictoryConditionType> getVictoryConditions() { return victoryConditions; } @Override public Builder addVictoryCondition(final PublicVictoryConditionType victoryCondition) { victoryConditions.add( victoryCondition ); return this; } @Override public Integer getTotalPlayers() { return totalPlayers; } @Override public Builder setTotalPlayers(final Integer totalPlayers) { this.totalPlayers = totalPlayers; return this; } @Override public Builder addGameListener(final GameListener gameListener) { gameListeners.put( gameListener, Security.currentPlayer() ); return this; } @Override public GameResourceConfig getResourceConfig() { return resourceConfig; } @Override public Builder setResourceConfig(final GameResourceConfig resourceConfig) { this.resourceConfig = resourceConfig; return this; } @Override public PublicGameUnitConfig getUnitConfig() { return unitConfig; } @Override public Builder setUnitConfig(final PublicGameUnitConfig unitConfig) { this.unitConfig = unitConfig; return this; } @Override public int nextPlayerID() { return nextPlayerID++; } } enum GameUnitConfig { NONE { @Override void addUnits(final Game game, final Player player) { } }, BASIC { @Override void addUnits(final Game game, final Player player) { // Find tiles for the units. ILevel ground = game.getLevel( LevelType.GROUND ); ILevel sky = game.getLevel( LevelType.SKY ); Optional<? extends ITile> engineerTile, airshipTile, scoutTile; while (true) { Vec2 engineerPosition = Vec2.create( RANDOM.nextInt( ground.getSize().getWidth() ), RANDOM.nextInt( ground.getSize().getHeight() ) ); Side randomSide = Side.values()[RANDOM.nextInt( Side.values().length )]; Vec2 airshipPosition = engineerPosition.translate( randomSide.getDelta() ); randomSide = Side.values()[RANDOM.nextInt( Side.values().length )]; Vec2 scoutPosition = engineerPosition.translate( randomSide.getDelta() ); engineerTile = ground.getTile( engineerPosition ); if (!engineerTile.isPresent() || engineerTile.get().getContents().isPresent() || // !engineerTile.get().getResourceQuantity( ResourceType.METALS ).isPresent()) continue; airshipTile = sky.getTile( airshipPosition ); if (!airshipTile.isPresent() || airshipTile.get().getContents().isPresent()) continue; scoutTile = ground.getTile( scoutPosition ); if (!scoutTile.isPresent() || scoutTile.get().getContents().isPresent()) continue; break; } // Add the units. GameObject engineer = new GameObject( UnitTypes.ENGINEER, game, player, Tile.cast( engineerTile.get() ) ); engineer.onModule( ModuleType.CONTAINER, module -> module.getResourceType() == ResourceType.METALS, module -> module.addStock( Integer.MAX_VALUE ) ); engineer.register(); new GameObject( UnitTypes.AIRSHIP, game, player, Tile.cast( airshipTile.get() ) ).register(); new GameObject( UnitTypes.SCOUT, game, player, Tile.cast( scoutTile.get() ) ).register(); } }; abstract void addUnits(final Game game, final Player player); static GameUnitConfig cast(final PublicGameUnitConfig unitConfig) { switch (unitConfig) { case NONE: return NONE; case BASIC: return BASIC; } throw new AlreadyCheckedException(); } } }