<commit_before>package nl.tudelft.jpacman.level;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import nl.tudelft.jpacman.board.Board;
import nl.tudelft.jpacman.board.Direction;
import nl.tudelft.jpacman.board.Square;
import nl.tudelft.jpacman.board.Unit;
import nl.tudelft.jpacman.fruit.Fruit;
import nl.tudelft.jpacman.fruit.FruitFactory;
import nl.tudelft.jpacman.level.Bridge;
import nl.tudelft.jpacman.npc.Bullet;
import nl.tudelft.jpacman.npc.DirectionCharacter;
import nl.tudelft.jpacman.npc.NPC;
import nl.tudelft.jpacman.npc.ghost.Blinky;
import nl.tudelft.jpacman.npc.ghost.Ghost;
import nl.tudelft.jpacman.npc.ghost.GhostColor;
import nl.tudelft.jpacman.npc.ghost.GhostFactory;
import nl.tudelft.jpacman.sprite.PacManSprites;

/**
 * A level of Pac-Man. A level consists of the board with the players and the
 * AIs on it.
 * 
 * @author <NAME> 
 */
public class Level {

	/**
	 * The board of this level.
	 */
	private final Board board;

	/**
	 * The lock that ensures moves are executed sequential.
	 */
	private final Object moveLock = new Object();

	/**
	 * The lock that ensures starting and stopping can't interfere with each
	 * other.
	 */
	private final Object startStopLock = new Object();


	/**
	 * The NPCs of this level and, if they are running, their schedules.
	 */
	private final Map<NPC, ScheduledExecutorService> npcs;

	/**
	 * <code>true</code> iff this level is currently in progress, i.e. players
	 * and NPCs can move.
	 */
	private boolean inProgress;

	public boolean infiniteMode;

	/**
	 * The squares from which players can start this game.
	 */
	private final List<Square> startSquares;

	/**
	 * The start current selected starting square.
	 */
	private int startSquareIndex;
	
	/**
	 * The Fruit factory for this level.
	 */
	private FruitFactory fruitFactory;

	/**
	 * The players on this level.
	 */
	private final List<Player> players;

	/**
	 * The table of possible collisions between units.
	 */
	private final CollisionMap collisions;

	/**
	 * The objects observing this level.
	 */
	private final List<LevelObserver> observers;

	private static final PacManSprites SPRITE_STORE = new PacManSprites();

	private Timer timerHunterMode = new Timer();

	private Timer timerRespawn = new Timer();

	private Timer timerWarning = new Timer();

	private Timer addGhostTask = new Timer();

	private Timer addFruitTask = new Timer();

	public static int ghostLeft;

	public static int ghostAte = 0;

	public static int superPelletLeft;

	private static Level level = null;

	/**
	 * Creates a new level for the board.
	 * 
	 * @param b
	 *            The board for the level.
	 * @param ghosts
	 *            The ghosts on the board.
	 * @param startPositions
	 *            The squares on which players start on this board.
	 * @param collisionMap
	 *            The collection of collisions that should be handled.
	 */
	public Level(Board b, List<NPC> ghosts, List<Square> startPositions,
			CollisionMap collisionMap) {
		assert b != null;
		assert ghosts != null;
		assert startPositions != null;

		this.board = b;
		this.inProgress = false;
		this.npcs = new HashMap<>();
		for (NPC g : ghosts) {
			npcs.put(g, null);
			ghostLeft++;
		}
		this.startSquares = startPositions;
		this.startSquareIndex = 0;
		this.players = new ArrayList<>();
		this.collisions = collisionMap;
		this.observers = new ArrayList<>();
		if(level == null) {
			level = this;
		}
	}
	
	/**
	 * Setup the fruit for this level if the board specified that some square may contain a fruit.
	 * @param fruitpositions the list of the squares where a fruit may appear
	 * @param npcs the list of the NPC registered on this level at the time of setting-up the fruits. 
	 */
	public void setupFruits(List<Square> fruitpositions, List<NPC> npcs) {
		fruitFactory = new FruitFactory(SPRITE_STORE, fruitpositions, npcs);
	}
	

	/**
	 * Adds an observer that will be notified when the level is won or lost
	 * or change his state (Hunter mode).
	 *
	 * @param observer The observer that will be notified.
	 */
	public void addObserver(LevelObserver observer) {
		if (observers.contains(observer)) {
			return;
		}
		observers.add(observer);
	}

	/**
	 * Removes an observer if it was listed.
	 *
	 * @param observer The observer to be removed.
	 */
	public void removeObserver(LevelObserver observer) {
		observers.remove(observer);
	}

	/**
	 * Registers a player on this level, assigning him to a starting position. A
	 * player can only be registered once, registering a player again will have
	 * no effect.
	 * 
	 * @param p
	 *            The player to register.
	 */
	public void registerPlayer(Player p) {
		assert p != null;
		assert !startSquares.isEmpty();

		if (players.contains(p)) {
			return;
		}
		players.add(p);
		Square square = startSquares.get(startSquareIndex);
		p.occupy(square);
		startSquareIndex++;
		startSquareIndex %= startSquares.size();
	}

	/**
	 * Returns the board of this level.
	 * 
	 * @return The board of this level.
	 */
	public Board getBoard() {
		return board;
	}

	/**
	 * Moves the unit into the given direction if possible and handles all
	 * collisions.
	 * 
	 * @param unit
	 *            The unit to move.
	 * @param direction
	 *            The direction to move the unit in.
	 */
	public void move(Unit unit, Direction direction) {
		assert unit != null;
		assert direction != null;

		if (!isInProgress() || (unit instanceof DirectionCharacter && !((DirectionCharacter) unit).getMobility())) {
			return;
		}

		synchronized (moveLock) {
			unit.setDirection(direction);
			Square location = unit.getSquare();
			Square destination = location.getSquareAt(direction);
			
			if (destination.isAccessibleTo(unit) && !(Bridge.blockedBybridge(unit, direction))) {
				unit.setOnBridge(false);
				List<Unit> occupants = destination.getOccupants();
				unit.occupy(destination);
				for (Unit occupant : occupants) {
					collisions.collide(unit, occupant);
				}
			}
			updateObservers();
		}
	}

	/**
	 * Starts or resumes this level, allowing movement and (re)starting the
	 * NPCs.
	 */
	public void start() {
		synchronized (startStopLock) {
			if (isInProgress()) {
				return;
			}
			startNPCs();
			inProgress = true;
			updateObservers();
		}
		Random random = new Random();
		int nbr = random.nextInt(11);
		addGhostTask.schedule(new TimerAddGhostTask(), (nbr+10)*1000);
		addFruitTask.schedule(new TimerAddFruitTask(), (nbr+10)*1000);
	}

	/**
	 * Stops or pauses this level, no longer allowing any movement on the board
	 * and stopping all NPCs.
	 */
	public void stop() {
		synchronized (startStopLock) {
			if (!isInProgress()) {
				return;
			}
			stopNPCs();
			inProgress = false;
		}
	}

	/**
	 * Starts all NPC movement scheduling.
	 */
	private void startNPCs() {
		for (final NPC npc : npcs.keySet()) {
			ScheduledExecutorService service = Executors
					.newSingleThreadScheduledExecutor();
			service.schedule(new NpcMoveTask(service, npc),
					npc.getInterval() / 2, TimeUnit.MILLISECONDS);
			npcs.put(npc, service);
		}
	}

	/**
	 * Stops all NPC movement scheduling and interrupts any movements being
	 * executed.
	 */
	private void stopNPCs() {
		for (Entry<NPC, ScheduledExecutorService> e : npcs.entrySet()) {
			e.getValue().shutdownNow();
		}
	}

	/**
	 * Permet d'ajouter des ghosts dans le jeu
     */
	public void addGhostTask()
	{
		System.out.println("ghost en vie début : " + this.npcs.size());
		ScheduledExecutorService service = Executors
				.newSingleThreadScheduledExecutor();
		GhostFactory ghostFact = new GhostFactory(SPRITE_STORE);
		Random random = new Random();
		int nombre = random.nextInt(6);
		int ghostIndex = random.nextInt(4);
		addGhostTask.cancel();
		addGhostTask = new Timer();
		addGhostTask.schedule(new TimerAddGhostTask(), ((nombre+4)+this.npcs.size())*1000);
		Ghost g;
		switch (ghostIndex) {
			case 0:
				g = ghostFact.createBlinky();
				break;
			case 1:
				g = ghostFact.createInky();
				break;
			case 2:
				g = ghostFact.createPinky();
				break;
			case 3:
				g = ghostFact.createClyde();
				break;
			default:
				g = ghostFact.createBlinky();
				break;
		}
		npcs.put(g, service);
		Square squareGhost = null;
		while(squareGhost == null){
			Square posPlayer = players.get(0).getSquare();
			int X = posPlayer.getCoordX();
			int Y = posPlayer.getCoordY();
			int i = (X-15) + random.nextInt(4);
			int j = (Y-11) + random.nextInt(22);
			squareGhost = board.squareAt(i, j);
			if(squareGhost.isAccessibleTo(g));
			g.occupy(squareGhost);
		}
		stopNPCs();
		startNPCs();
		for (NPC npc : npcs.keySet()) {
			g = (Ghost) (npc);
			g.setSpeed(g.getSpeed() + 0.1);
		}
		System.out.println("ghost en vie fin : " + this.npcs.size());
	}

	/**
	 * Permet d'ajouter des ghosts dans le jeu
	 */
	public void addFruitTask()
	{
		Random random = new Random();
		int nbr = random.nextInt(6);
		addFruitTask.cancel();
		addFruitTask = new Timer();
		addFruitTask.schedule(new TimerAddFruitTask(), (nbr+10)*1000);
		fruitFactory = new FruitFactory(SPRITE_STORE, null, null);
		Fruit fruit = fruitFactory.getRandomFruit();
		System.out.println("fruit : " + fruit);
		Square squareFruit = null;
		while(squareFruit == null) {
			Square posPlayer = players.get(0).getSquare();
			int X = posPlayer.getCoordX();
			int Y = posPlayer.getCoordY();
			int i = (X - 15) + random.nextInt(4);
			int j = (Y - 11) + random.nextInt(22);
			squareFruit = board.squareAt(i, j);
			if (squareFruit.isAccessibleTo(fruit)) {
				fruit.occupy(squareFruit);
			}
			else {
				squareFruit = null;
			}
		}
	}

	/**
	 * Returns whether this level is in progress, i.e. whether moves can be made
	 * on the board.
	 * 
	 * @return <code>true</code> iff this level is in progress.
	 */
	public boolean isInProgress() {
		return inProgress;
	}

	private boolean isInfiniteMode() { return infiniteMode; }

	/**
	 * Updates the observers about the state of this level.
	 */
	private void updateObservers() {
		if(!infiniteMode) {
			if (ghostLeft != 4) {
				for (LevelObserver o : observers) {
					o.respawnGhost();
				}
			}
			if (remainingPellets() == 0) {
				for (LevelObserver o : observers) {
					o.levelWon();
				}
			}
		}
		if (!isAnyPlayerAlive()) {
			for (LevelObserver o : observers) {
				o.levelLost();
			}
		}
		if (isAnyPlayerInHunterMode()) {
			for (LevelObserver o : observers) {
				o.startHunterMode();
			}
		}
		if (anyPlayerDesserveFruits()) {
			for (LevelObserver o : observers) {
					o.fruitEvent();
			}
		}
		if (isAnyPlayerShooting()) {
			for (LevelObserver o : observers) {
					o.ShootingEvent();
			}
		}
		List<NPC> deadNPCs = NPCToClean() ;
		if(deadNPCs.size() > 0) {
			for (LevelObserver o : observers) {
				o.NPCCleanEvent(deadNPCs, npcs);
			}
		}
	}

	/**
	 * Returns <code>true</code> iff at least one of the players in this level
	 * is alive.
	 * 
	 * @return <code>true</code> if at least one of the registered players is
	 *         alive.
	 */
	public boolean isAnyPlayerAlive() {
		for (Player p : players) {
			if (p.isAlive()) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if at lest one of the player meet the criteria for making a fruit appear.
	 * 
	 * @return <code>true</code> if at lest one of the player has 500 or 1500 points.
	 */
	public boolean anyPlayerDesserveFruits() {
		for (Player p : players) {
			if (fruitFactory != null && (p.getScore() == 500 || p.getScore() == 1500)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns <code>true</code> if at lest one of the player meet the criteria for making a fruit appear.
	 *
	 * @return <code>true</code> if at lest one of the player has 500 or 1500 points.
	 */
	public boolean anyPlayerDesserveFruits2() {
		for (Player p : players) {
			if (fruitFactory != null && (p.getScore() == 500 || p.getScore() == 1500)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if at lest one of the player can shoot bullets.
	 * 
	 * @return <code>true</code> if at lest one of the player can shoot bullets.
	 */
	public boolean isAnyPlayerShooting() {
		for (Player p : players) {
			if (p.isShooting()) {
				return true;
			}
		}
		return false;
	}
	
	/**<commit_msg>Optimisation et correction de bugs dans Level 2<commit_after>package nl.tudelft.jpacman.level;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import nl.tudelft.jpacman.board.Board;
import nl.tudelft.jpacman.board.Direction;
import nl.tudelft.jpacman.board.Square;
import nl.tudelft.jpacman.board.Unit;
import nl.tudelft.jpacman.fruit.Fruit;
import nl.tudelft.jpacman.fruit.FruitFactory;
import nl.tudelft.jpacman.level.Bridge;
import nl.tudelft.jpacman.npc.Bullet;
import nl.tudelft.jpacman.npc.DirectionCharacter;
import nl.tudelft.jpacman.npc.NPC;
import nl.tudelft.jpacman.npc.ghost.Blinky;
import nl.tudelft.jpacman.npc.ghost.Ghost;
import nl.tudelft.jpacman.npc.ghost.GhostColor;
import nl.tudelft.jpacman.npc.ghost.GhostFactory;
import nl.tudelft.jpacman.sprite.PacManSprites;

/**
 * A level of Pac-Man. A level consists of the board with the players and the
 * AIs on it.
 * 
 * @author <NAME> 
 */
public class Level {

	/**
	 * The board of this level.
	 */
	private final Board board;

	/**
	 * The lock that ensures moves are executed sequential.
	 */
	private final Object moveLock = new Object();

	/**
	 * The lock that ensures starting and stopping can't interfere with each
	 * other.
	 */
	private final Object startStopLock = new Object();


	/**
	 * The NPCs of this level and, if they are running, their schedules.
	 */
	private final Map<NPC, ScheduledExecutorService> npcs;

	/**
	 * <code>true</code> iff this level is currently in progress, i.e. players
	 * and NPCs can move.
	 */
	private boolean inProgress;

	public boolean infiniteMode;

	/**
	 * The squares from which players can start this game.
	 */
	private final List<Square> startSquares;

	/**
	 * The start current selected starting square.
	 */
	private int startSquareIndex;
	
	/**
	 * The Fruit factory for this level.
	 */
	private FruitFactory fruitFactory;

	/**
	 * The players on this level.
	 */
	private final List<Player> players;

	/**
	 * The table of possible collisions between units.
	 */
	private final CollisionMap collisions;

	/**
	 * The objects observing this level.
	 */
	private final List<LevelObserver> observers;

	private static final PacManSprites SPRITE_STORE = new PacManSprites();

	private Timer timerHunterMode = new Timer();

	private Timer timerRespawn = new Timer();

	private Timer timerWarning = new Timer();

	private Timer addGhostTask = new Timer();

	private Timer addFruitTask = new Timer();

	public static int ghostLeft;

	public static int ghostAte = 0;

	public static int superPelletLeft;

	private static Level level = null;

	/**
	 * Creates a new level for the board.
	 * 
	 * @param b
	 *            The board for the level.
	 * @param ghosts
	 *            The ghosts on the board.
	 * @param startPositions
	 *            The squares on which players start on this board.
	 * @param collisionMap
	 *            The collection of collisions that should be handled.
	 */
	public Level(Board b, List<NPC> ghosts, List<Square> startPositions,
			CollisionMap collisionMap) {
		assert b != null;
		assert ghosts != null;
		assert startPositions != null;

		this.board = b;
		this.inProgress = false;
		this.npcs = new HashMap<>();
		for (NPC g : ghosts) {
			npcs.put(g, null);
			ghostLeft++;
		}
		this.startSquares = startPositions;
		this.startSquareIndex = 0;
		this.players = new ArrayList<>();
		this.collisions = collisionMap;
		this.observers = new ArrayList<>();
		if(level == null) {
			level = this;
		}
	}
	
	/**
	 * Setup the fruit for this level if the board specified that some square may contain a fruit.
	 * @param fruitpositions the list of the squares where a fruit may appear
	 * @param npcs the list of the NPC registered on this level at the time of setting-up the fruits. 
	 */
	public void setupFruits(List<Square> fruitpositions, List<NPC> npcs) {
		fruitFactory = new FruitFactory(SPRITE_STORE, fruitpositions, npcs);
	}
	

	/**
	 * Adds an observer that will be notified when the level is won or lost
	 * or change his state (Hunter mode).
	 *
	 * @param observer The observer that will be notified.
	 */
	public void addObserver(LevelObserver observer) {
		if (observers.contains(observer)) {
			return;
		}
		observers.add(observer);
	}

	/**
	 * Removes an observer if it was listed.
	 *
	 * @param observer The observer to be removed.
	 */
	public void removeObserver(LevelObserver observer) {
		observers.remove(observer);
	}

	/**
	 * Registers a player on this level, assigning him to a starting position. A
	 * player can only be registered once, registering a player again will have
	 * no effect.
	 * 
	 * @param p
	 *            The player to register.
	 */
	public void registerPlayer(Player p) {
		assert p != null;
		assert !startSquares.isEmpty();

		if (players.contains(p)) {
			return;
		}
		players.add(p);
		Square square = startSquares.get(startSquareIndex);
		p.occupy(square);
		startSquareIndex++;
		startSquareIndex %= startSquares.size();
	}

	/**
	 * Returns the board of this level.
	 * 
	 * @return The board of this level.
	 */
	public Board getBoard() {
		return board;
	}

	/**
	 * Moves the unit into the given direction if possible and handles all
	 * collisions.
	 * 
	 * @param unit
	 *            The unit to move.
	 * @param direction
	 *            The direction to move the unit in.
	 */
	public void move(Unit unit, Direction direction) {
		assert unit != null;
		assert direction != null;

		if (!isInProgress() || (unit instanceof DirectionCharacter && !((DirectionCharacter) unit).getMobility())) {
			return;
		}

		synchronized (moveLock) {
			unit.setDirection(direction);
			Square location = unit.getSquare();
			Square destination = location.getSquareAt(direction);
			
			if (destination.isAccessibleTo(unit) && !(Bridge.blockedBybridge(unit, direction))) {
				unit.setOnBridge(false);
				List<Unit> occupants = destination.getOccupants();
				unit.occupy(destination);
				for (Unit occupant : occupants) {
					collisions.collide(unit, occupant);
				}
			}
			updateObservers();
		}
	}

	/**
	 * Starts or resumes this level, allowing movement and (re)starting the
	 * NPCs.
	 */
	public void start() {
		synchronized (startStopLock) {
			if (isInProgress()) {
				return;
			}
			startNPCs();
			inProgress = true;
			updateObservers();
		}
		Random random = new Random();
		int nbr = random.nextInt(11);
		if(infiniteMode) {
			addGhostTask.schedule(new TimerAddGhostTask(), 1000);//(nbr+10)*1000);
		}
		addFruitTask.schedule(new TimerAddFruitTask(), (nbr+10)*1000);
	}

	/**
	 * Stops or pauses this level, no longer allowing any movement on the board
	 * and stopping all NPCs.
	 */
	public void stop() {
		synchronized (startStopLock) {
			if (!isInProgress()) {
				return;
			}
			stopNPCs();
			addGhostTask.cancel();
			addFruitTask.cancel();
			inProgress = false;
		}
	}

	/**
	 * Starts all NPC movement scheduling.
	 */
	private void startNPCs() {
		for (final NPC npc : npcs.keySet()) {
			ScheduledExecutorService service = Executors
					.newSingleThreadScheduledExecutor();
			service.schedule(new NpcMoveTask(service, npc),
					npc.getInterval() / 2, TimeUnit.MILLISECONDS);
			npcs.put(npc, service);
		}
	}

	/**
	 * Stops all NPC movement scheduling and interrupts any movements being
	 * executed.
	 */
	private void stopNPCs() {
		for (Entry<NPC, ScheduledExecutorService> e : npcs.entrySet()) {
			e.getValue().shutdownNow();
		}
	}

	/**
	 * Permet d'ajouter des ghosts dans le jeu
     */
	public void addGhostTask()
	{
		if(this.npcs.size() < 10) {
			System.out.println("ghost en vie début : " + this.npcs.size());
			ScheduledExecutorService service = Executors
					.newSingleThreadScheduledExecutor();
			GhostFactory ghostFact = new GhostFactory(SPRITE_STORE);
			Random random = new Random();
			int nombre = random.nextInt(6);
			int ghostIndex = random.nextInt(4);
			addGhostTask.cancel();
			addGhostTask = new Timer();
			addGhostTask.schedule(new TimerAddGhostTask(), 1000);//((nombre + 4) + this.npcs.size()) * 1000);
			Ghost g;
			switch (ghostIndex) {
				case 0:
					g = ghostFact.createBlinky();
					break;
				case 1:
					g = ghostFact.createInky();
					break;
				case 2:
					g = ghostFact.createPinky();
					break;
				case 3:
					g = ghostFact.createClyde();
					break;
				default:
					g = ghostFact.createBlinky();
					break;
			}
			npcs.put(g, service);
			Square squareGhost = null;
			while (squareGhost == null) {
				Square posPlayer = players.get(0).getSquare();
				int X = posPlayer.getCoordX();
				int Y = posPlayer.getCoordY();
				int i, j;
				if (X - 10 < 0) {
					i = random.nextInt(23);
				} else {
					i = (X - 10) + random.nextInt(23);
				}
				if (Y - 14 < 0) {
					j = random.nextInt(4);
				} else {
					j = (Y - 14) + random.nextInt(4);
				}
				squareGhost = board.squareAt(i, j);
				if (squareGhost.isAccessibleTo(g)) {
					g.occupy(squareGhost);
				}
				else{
					squareGhost = null;
				}
			}
			stopNPCs();
			startNPCs();
			for (NPC npc : npcs.keySet()) {
				g = (Ghost) (npc);
				g.setSpeed(g.getSpeed() + 0.1);
			}
			System.out.println("ghost en vie fin : " + this.npcs.size());
		}
	}

	/**
	 * Permet d'ajouter des fruits dans le jeu
	 */
	public void addFruitTask()
	{
		Random random = new Random();
		int nbr = random.nextInt(6);
		addFruitTask.cancel();
		addFruitTask = new Timer();
		addFruitTask.schedule(new TimerAddFruitTask(), (nbr+10)*1000);
		fruitFactory = new FruitFactory(SPRITE_STORE, null, null);
		Fruit fruit = fruitFactory.getRandomFruit();
		Square squareFruit = null;
		while(squareFruit == null) {
			Square posPlayer = players.get(0).getSquare();
			int X = posPlayer.getCoordX();
			int Y = posPlayer.getCoordY();
			int i, j;
			if(X-10 < 0){
				i =  random.nextInt(23);
			}
			else{
				i = (X-10) + random.nextInt(23);
			}
			if(Y-14 < 0){
				j = random.nextInt(4);
			}
			else{
				j = (Y-14) + random.nextInt(4);
			}
			squareFruit = board.squareAt(i, j);
			if (squareFruit.isAccessibleTo(fruit)) {
				fruit.occupy(squareFruit);
			}
			else {
				squareFruit = null;
			}
		}
	}

	/**
	 * Returns whether this level is in progress, i.e. whether moves can be made
	 * on the board.
	 * 
	 * @return <code>true</code> iff this level is in progress.
	 */
	public boolean isInProgress() {
		return inProgress;
	}

	private boolean isInfiniteMode() { return infiniteMode; }

	/**
	 * Updates the observers about the state of this level.
	 */
	private void updateObservers() {
		if(!infiniteMode) {
			if (ghostLeft != 4) {
				for (LevelObserver o : observers) {
					o.respawnGhost();
				}
			}
			if (remainingPellets() == 0) {
				for (LevelObserver o : observers) {
					o.levelWon();
				}
			}
		}
		if (!isAnyPlayerAlive()) {
			for (LevelObserver o : observers) {
				o.levelLost();
			}
		}
		if (isAnyPlayerInHunterMode()) {
			for (LevelObserver o : observers) {
				o.startHunterMode();
			}
		}
		if (anyPlayerDeserveFruits()) {
			for (LevelObserver o : observers) {
					o.fruitEvent();
			}
		}
		if (isAnyPlayerShooting()) {
			for (LevelObserver o : observers) {
					o.ShootingEvent();
			}
		}
		List<NPC> deadNPCs = NPCToClean() ;
		if(deadNPCs.size() > 0) {
			for (LevelObserver o : observers) {
				o.NPCCleanEvent(deadNPCs, npcs);
			}
		}
	}

	/**
	 * Returns <code>true</code> iff at least one of the players in this level
	 * is alive.
	 * 
	 * @return <code>true</code> if at least one of the registered players is
	 *         alive.
	 */
	public boolean isAnyPlayerAlive() {
		for (Player p : players) {
			if (p.isAlive()) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if at lest one of the player meet the criteria for making a fruit appear.
	 * 
	 * @return <code>true</code> if at lest one of the player has 500 or 1500 points.
	 */
	public boolean anyPlayerDeserveFruits() {
		for (Player p : players) {
			if (fruitFactory != null && (p.getScore() == 500 || p.getScore() == 1500)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if at lest one of the player can shoot bullets.
	 * 
	 * @return <code>true</code> if at lest one of the player can shoot bullets.
	 */
	public boolean isAnyPlayerShooting() {
		for (Player p : players) {
			if (p.isShooting()) {
				return true;
			}
		}
		return false;
	}
	
	/**
{'fraction_non_alphanumeric': 0.12231291417488283, 'fraction_numerical': 0.007434944237918215, 'mean_word_length': 3.115915516381174, 'pattern_counts': {'":': 0, '<': 74, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3864557', 'n_tokens_mistral': 9693, 'n_tokens_neox': 8162, 'n_words': 2323}