package net.demilich.metastone.gui.simulationmode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.demilich.nittygrittymvc.Notification;
import net.demilich.nittygrittymvc.SimpleCommand;
import net.demilich.nittygrittymvc.interfaces.INotification;
import net.demilich.metastone.GameNotification;
import net.demilich.metastone.game.GameContext;
import net.demilich.metastone.game.Player;
import net.demilich.metastone.game.decks.DeckFormat;
import net.demilich.metastone.game.logic.GameLogic;
import net.demilich.metastone.game.gameconfig.GameConfig;
import net.demilich.metastone.game.gameconfig.PlayerConfig;
import net.demilich.metastone.utils.Tuple;
public class SimulateGamesCommand extends SimpleCommand<GameNotification> {
private class PlayGameTask implements Callable<Void> {
private final GameConfig gameConfig;
public PlayGameTask(GameConfig gameConfig) {
this.gameConfig = gameConfig;
}
@Override
public Void call() throws Exception {
PlayerConfig playerConfig1 = gameConfig.getPlayerConfig1();
PlayerConfig playerConfig2 = gameConfig.getPlayerConfig2();
Player player1 = new Player(playerConfig1);
Player player2 = new Player(playerConfig2);
DeckFormat deckFormat = gameConfig.getDeckFormat();
GameContext newGame = new GameContext(player1, player2, new GameLogic(), deckFormat);
newGame.play();
onGameComplete(gameConfig, newGame);
newGame.dispose();
return null;
}
}
private static Logger logger = LoggerFactory.getLogger(SimulateGamesCommand.class);
private int gamesCompleted;
private long lastUpdate;
private SimulationResult result;
@Override
public void execute(INotification<GameNotification> notification) {
final GameConfig gameConfig = (GameConfig) notification.getBody();
result = new SimulationResult(gameConfig);
gamesCompleted = 0;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
int cores = Runtime.getRuntime().availableProcessors();
logger.info("Starting simulation on " + cores + " cores");
ExecutorService executor = Executors.newFixedThreadPool(cores);
// ExecutorService executor =
// Executors.newSingleThreadExecutor();
List<Future<Void>> futures = new ArrayList<Future<Void>>();
// send initial status update
Tuple<Integer, Integer> progress = new Tuple<>(0, gameConfig.getNumberOfGames());
getFacade().sendNotification(GameNotification.SIMULATION_PROGRESS_UPDATE, progress);
// queue up all games as tasks
lastUpdate = System.currentTimeMillis();
for (int i = 0; i < gameConfig.getNumberOfGames(); i++) {
PlayGameTask task = new PlayGameTask(gameConfig);
Future<Void> future = executor.submit(task);
futures.add(future);
}
executor.shutdown();
boolean completed = false;
while (!completed) {
completed = true;
for (Future<Void> future : futures) {
if (!future.isDone()) {
completed = false;
continue;
}
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
logger.error(ExceptionUtils.getStackTrace(e));
e.printStackTrace();
System.exit(-1);
}
}
futures.removeIf(future -> future.isDone());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
result.calculateMetaStatistics();
getFacade().sendNotification(GameNotification.SIMULATION_RESULT, result);
logger.info("Simulation finished");
}
});
t.setDaemon(true);
t.start();
}
private void onGameComplete(GameConfig gameConfig, GameContext context) {
long timeStamp = System.currentTimeMillis();
gamesCompleted++;
if (timeStamp - lastUpdate > 100) {
lastUpdate = timeStamp;
Tuple<Integer, Integer> progress = new Tuple<>(gamesCompleted, gameConfig.getNumberOfGames());
Notification<GameNotification> updateNotification = new Notification<>(GameNotification.SIMULATION_PROGRESS_UPDATE, progress);
getFacade().notifyObservers(updateNotification);
}
synchronized (result) {
result.getPlayer1Stats().merge(context.getPlayer1().getStatistics());
result.getPlayer2Stats().merge(context.getPlayer2().getStatistics());
}
}
}