package me.stieglmaier.sphereMiners.model.ai; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; import javafx.scene.paint.Color; import me.stieglmaier.sphereMiners.main.Constants; import me.stieglmaier.sphereMiners.model.physics.Physics; import me.stieglmaier.sphereMiners.model.util.Position; import me.stieglmaier.sphereMiners.model.util.Sphere; public abstract class SphereMiners2015 { /** All owned spheres */ protected Set<Sphere> ownSpheres; /** All dots on the playground*/ protected Set<Sphere> dots; private Physics physics; private Player ownAI; private Set<Sphere> allSpheres; private Turn currentMine; private Turn currentChangeDest; private Turn currentSplit; private Turn currentMerge; private Constants constants; private ExecutorService threadExecutor = Executors.newSingleThreadExecutor(); /** * Set up your AI, initial values for attributes, color of your spheres, ... */ protected abstract void init(); /** * In this method your AI has to specify what it wants to do, it can either * change the moving direction of any number of spheres controlled by you, or * split a sphere into two (half) parts, or merge two spheres to a bigger one. * * Note: the or is exclusive in this case, only one of the above operations * is allowed at the same time */ protected abstract void playTurn(); /** * Sets the color of your spheres. * * @param color The color your spheres should have */ protected final void setColor(Color color) { ownAI.setColor(color); } /** * Sets your displayed name. * * @param name The name you want to have */ protected final void setName(String name) { ownAI.setName(name); } /** * Returns the Constants that are used throughout the framework. * * @return the Constants object used in the whole framework */ protected final Constants getConstants() { return constants; } /** * Changes the moving direction of a (own) Sphere instantly, there is no kind * of friction or acceleration in between. However the speed cannot be changed * with this method, as it is dependant on the size of the Sphere and cannot * be set by a player. * * Execution order of merging, splitting and changing direction is at first * mine, then merge, then split and then change directions. All steps are * independant and need not to be set in every turn. Also splitting can not * be done on just merged spheres, as well as new spheres (by splitting) * cannot change direction in this turn. * * @param spheres The map of spheres to their new (relative) moving directions * (does not need to include all spheres you own) */ protected final void changeMoveDirection(final Map<Sphere, Position> spheres) { Stream<Entry<Sphere, Position>> tmp = spheres.entrySet().stream().filter(e -> ownSpheres.contains(e.getKey())); currentChangeDest = () -> tmp.forEach(e -> physics.changeDirection(e.getKey(), e.getValue())); } /** * Splits the given (own) sphere to two equally (half) sized ones. This works * only if the maximal amount of spheres at the same time is not already reached * and will not be overflown with the given map of spheres to be splitted. * Otherwise this will simply do nothing. * * Execution order of merging, splitting and changing direction is at first * mine, then merge, then split and then change directions. All steps are * independant and need not to be set in every turn. Also splitting can not * be done on just merged spheres, as well as new spheres (by splitting) * cannot change direction in this turn. * * @param spheres The spheres you want to split into two parts */ protected final void split(Collection<Sphere> spheres) { Stream<Sphere> tmp = spheres.stream().filter(s -> ownSpheres.contains(s)); if (ownSpheres.size() + spheres.size() <= constants.getMaxSphereAmount()) { // lists cannot be changed directly therefore we need the phyiscsmanager here currentSplit = () -> tmp.forEach(s -> physics.split(s)); } } /** * Merges the given (own) spheres to one that has the accumulated size of both. * * Execution order of merging, splitting and changing direction is at first * mine, then merge, then split and then change directions. All steps are * independant and need not to be set in every turn. Also splitting can not * be done on just merged spheres, as well as new spheres (by splitting) * cannot change direction in this turn. * * @param spheres The map of spheres that should grow to spheres that should vanish */ protected final void merge(Map<Sphere, Sphere> spheres) { Stream<Entry<Sphere, Sphere>> tmp = spheres .entrySet() .stream() .filter(e -> ownSpheres.contains(e.getKey()) && ownSpheres.contains(e.getValue())); // lists cannot be changed directly therefore we need the phyiscsmanager here currentMerge = () -> tmp.forEach(e -> physics.merge(e.getKey(), e.getValue())); } /** * Mines the given sphere (value) with the other given sphere (key). A sphere * can be mined if it is an enemy and only if it is smaller than oneself. If * these constraints are not hold this method has no effect for the certain * sphere. * * Execution order of merging, splitting and changing direction is at first * mine, then merge, then split and then change directions. All steps are * independant and need not to be set in every turn. Also splitting can not * be done on just merged spheres, as well as new spheres (by splitting) * cannot change direction in this turn. * * @param spheres The map of (own) spheres to (enemy) spheres that should be mined */ protected final void mine(Map<Sphere, Sphere> spheres) { Stream<Entry<Sphere, Sphere>> tmp = spheres .entrySet() .stream() .filter(e -> ownSpheres.contains(e.getKey()) && !ownSpheres.contains(e.getValue())); // tmp.forEach(e -> System.out.println(e)); currentMine = () -> tmp.forEach(e -> physics.mine(e.getKey(), e.getValue())); } /** * Returns the enemies surrounding the given (owned!) sphere in a certain distance. * * @param sphere The sphere you want to find the surrounding enemies for * @return the sourrounding enemies of the given sphere */ protected final Set<Sphere> getSurroundingEnemies(Sphere sphere) { if (!ownSpheres.contains(sphere)) { return Collections.emptySet(); } return allSpheres .stream() .filter( s -> s.getOwner() != ownAI && s.getPosition().dist(sphere.getPosition()) <= constants.getSightDistance() + sphere.getRadius()) .collect(Collectors.toSet()); } /** * Package private, this should only be called by AIManager! * @return indicates wether the turn could be evaluated within the timelimit * or not */ boolean evaluateTurn() { setUpTurn(); Future<?> future = threadExecutor.submit(() -> playTurn()); try { future.get(constants.getAIComputationTime(), TimeUnit.MILLISECONDS); } catch (ExecutionException | InterruptedException e) { future.cancel(true); constants .getLogger() .logException( Level.SEVERE, e, "Unexpected exception during turn of AI " + ownAI.getInternalName()); return false; } catch (TimeoutException e) { future.cancel(true); constants .getLogger() .log(Level.INFO, "Computation took too long for AI " + ownAI.getInternalName()); return false; } currentMine.apply(); currentMerge.apply(); currentSplit.apply(); currentChangeDest.apply(); return true; } private void setUpTurn() { // reset turns to evaluate currentChangeDest = () -> {}; currentMerge = () -> {}; currentSplit = () -> {}; currentMine = () -> {}; // reset all sphere related variables dots = physics.getDots(); allSpheres = physics.getAISpheres(); ownSpheres = allSpheres.stream().filter(p -> p.getOwner() == ownAI).collect(Collectors.toSet()); } /** * Sets the constants object for this ai. * * @param constants The constants that should be used for this ai. */ void setConstants(Constants constants) { this.constants = constants; } /** * Package private, this should only be called and set by AImanager! * * @param physics the physics instance needed for some ai interactions */ void setPhysics(Physics physics) { this.physics = physics; } /** * Set the player used as identifier in maps throughout the framework * * @param player The internal representation of the player in the framework */ void setPlayer(Player player) { this.ownAI = player; } @FunctionalInterface private interface Turn { void apply(); } }