package org.drooms.impl.util; import org.drooms.api.Action; import org.drooms.api.Node; import org.drooms.api.Player; import org.drooms.api.Playground; import org.drooms.impl.PlayerPosition; import java.util.*; import java.util.stream.Collectors; public class Detectors { /** * Detect players that have been inactive for so long, they have gone over the threshold. * * @param allowedInactiveTurns The maximum allowed number of turns for which the player is allowed to be inactive. * Any more than this and the player will be considered inactive. Negative number means * infinity. * @param players Players and their activities. The closer to the end of the list, the more recent the action. * @return Players that have been inactive for longer than allowed. */ public static Set<Player> detectInactivity(final int allowedInactiveTurns, Map<Player, List<Action>> players) { if (players == null) { throw new IllegalArgumentException("Players must not be null."); } else if (players.isEmpty()) { return Collections.emptySet(); } else if (allowedInactiveTurns < 0) { return Collections.emptySet(); } return Collections.unmodifiableSet(players.keySet().parallelStream().filter(p -> !Detectors.isActive(players.get(p), allowedInactiveTurns)).collect(Collectors.toSet())); } protected static boolean isActive(final List<Action> actions, final int allowedInactiveTurns) { final int turnCount = actions.size(); if (turnCount < allowedInactiveTurns) { return true; } final List<Action> relevantActions = actions.subList(Math.max(0, turnCount - allowedInactiveTurns - 1), turnCount); final Collection<Action> uniqueActions = new HashSet<>(relevantActions); // TODO is "NOTHING" the only inactivity? if (uniqueActions.size() == 1 && uniqueActions.contains(Action.NOTHING)) { return false; } return true; } protected static boolean didPlayerHitItself(final Collection<Node> player) { final Collection<Node> nodes = new HashSet<>(player); return nodes.size() < player.size(); } protected static boolean didPlayerHitWall(final Node playerHeadPosition, final Playground playground) { return !playground.isAvailable(playerHeadPosition.getX(), playerHeadPosition.getY()); } protected static boolean didPlayerCollideWithOther(final Node playerHeadNode, final Collection<Node> otherPlayer) { return otherPlayer.contains(playerHeadNode); } /** * Detect players who performed a move that will get them killed. This either means running into a wall, colliding * with themselves or colliding with another player. * * @param players Positions of the players to be evaluated. Assumed to have come from the same playground. * @return Unmodifiable set of players that have in some way collided. */ public static Set<PlayerPosition> detectCollision(final Set<PlayerPosition> players) { // find all players that collided by crashing into themselves or into a wall final Collection<PlayerPosition> playersNotFailed = players.stream().filter(player -> !Detectors.didPlayerHitItself(player.getNodes())).filter(player -> !Detectors.didPlayerHitWall(player.getHeadNode(), player.getPlayground())).collect(Collectors.toSet()); // for every other player, check for collisions with all known players final Collection<PlayerPosition> playersSurviving = playersNotFailed.stream().filter(position -> { for (PlayerPosition otherPosition: players) { if (otherPosition.equals(position)) { // no point in checking for collisions of the player with itself continue; } if (Detectors.didPlayerCollideWithOther(position.getHeadNode(), otherPosition.getNodes())) { return false; } } return true; }).collect(Collectors.toSet()); // and assemble the final collection of worms that have somehow crashed return Collections.unmodifiableSet(players.stream().filter(player -> !playersSurviving.contains(player)).collect(Collectors.toSet())); } }