package me.stieglmaier.sphereMiners.model;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import me.stieglmaier.sphereMiners.main.Constants;
import me.stieglmaier.sphereMiners.model.ai.AIManager;
import me.stieglmaier.sphereMiners.model.ai.AIManager.LoadingStatus;
import me.stieglmaier.sphereMiners.model.ai.Player;
import me.stieglmaier.sphereMiners.model.physics.Physics;
import me.stieglmaier.sphereMiners.model.util.GameSimulation;
import me.stieglmaier.sphereMiners.model.util.Tick;
import me.stieglmaier.sphereMiners.view.ErrorPopup;
/**
* The model class connects the whole backend of the framework. It is responsible
* for starting a simulation and setting up all necessary parts.
*
* @author stieglma
*
*/
public class Model extends Observable {
private final Physics physics;
private final AIManager ais;
private static GameSimulation simulationView;
private Simulation simulation;
private final Constants constants;
private final Consumer<List<Player>> badAICallback;
/**
* Creates a new {@link Model}.
*
* @param phys The {@link Physics} to use for the simulation.
* @param ai The {@link AIManager} to use for the simulation.
* @param constants The {@link Constants} to use for the simulation.
* @param badAICallback The callBack to remove bad AIs from the list of playing AIs
*/
public Model(
final Physics phys,
final AIManager ai,
final Constants constants,
final Consumer<List<Player>> badAICallback) {
this.physics = phys;
this.ais = ai;
this.constants = constants;
this.badAICallback = badAICallback;
ais.setPhysics(physics);
}
/**
* Simulates a game and returns the simulation object where the ticks
* are saved into.
*
* @param aisToPlay the list of players that should play a game
* @return the SimulationObject that can be viewed
*/
public GameSimulation simulateGame(final List<Player> aisToPlay) {
// create new Simulation
simulationView = new GameSimulation();
simulationView.addInstance(physics.createInitialTick(aisToPlay));
simulation = new Simulation(ais, physics, aisToPlay, constants, badAICallback);
simulation.start();
return simulationView;
}
/**
* Completely deletes the current simulation.
*/
public void deleteSimulation() {
if (simulationView != null) {
simulationView = null;
synchronized (simulation) {
simulation.stopSimulation();
}
}
}
/**
* Pauses or Resumes the current simulation.
*/
public void pauseSimulation() {
if (simulationView != null) {
synchronized (simulation) {
simulation.pauseResume();
}
}
}
/**
* Returns the list of AIs that can be used for playing
*
* @return the list of ais that can be used for playing
*/
public ObservableList<String> getAIList() {
return ais.getAIList();
}
private static class Simulation extends Thread {
private boolean isRunning = false;
private boolean stopSimulation = false;
private final Physics physMgr;
private final AIManager ais;
private final List<Player> aisToPlay;
private final Constants constants;
private final Consumer<List<Player>> badAICallback;
public Simulation(
AIManager ais,
Physics physics,
List<Player> aisToPlay,
Constants constants,
Consumer<List<Player>> badAICallback) {
this.ais = ais;
this.physMgr = physics;
this.aisToPlay = aisToPlay;
this.constants = constants;
this.badAICallback = badAICallback;
setName("[sphereMiners][simulationThread]");
}
public void pauseResume() {
isRunning = !isRunning;
if (isRunning) {
notify();
}
}
public void stopSimulation() {
stopSimulation = true;
}
@Override
public void run() {
isRunning = true;
Map<Player, LoadingStatus> loadingStatus = ais.initializeGameAIs(aisToPlay);
if (loadingStatus.values().contains(LoadingStatus.INITIALIZING_FAILED)) {
Platform.runLater(
()
-> ErrorPopup.create(
"Error while initializing AIs",
"Errors exist, either in the constructor or the init() method of the AIs:\n"
+ loadingStatus
.entrySet()
.stream()
.filter(e -> e.getValue() == LoadingStatus.INITIALIZING_FAILED)
.reduce(
"",
(a, b) -> a + "\n" + b.getKey().getNameProperty().get(),
(a, b) -> a + "\n" + b),
null));
} else if (loadingStatus.values().contains(LoadingStatus.INVALID_LOCATION)) {
Platform.runLater(
()
-> ErrorPopup.create(
"Error while loading AIs",
"The AI is not located at the given location, please refresh the List"
+ "of usable AIs:\n"
+ loadingStatus
.entrySet()
.stream()
.filter(e -> e.getValue() == LoadingStatus.INVALID_LOCATION)
.reduce(
"",
(a, b) -> a + "\n" + b.getKey().getNameProperty().get(),
(a, b) -> a + "\n" + b),
null));
}
badAICallback.accept(
loadingStatus
.entrySet()
.stream()
.filter(e -> e.getValue() != LoadingStatus.LOADED)
.map(e -> e.getKey())
.collect(Collectors.toList()));
// let the AIs apply their moves and
// calculate the tick based on them
// adds the finished tick to the simulation object
while (!stopSimulation) {
synchronized (this) {
while (!isRunning) {
try {
wait();
} catch (InterruptedException e) {
constants.getLogger().logException(Level.WARNING, e, "");
}
}
}
ais.applyMoves();
Tick nextTick = physMgr.applyPhysics();
// is the game over?
boolean isEnded = constants.getWinningCondition().hasGameEnded(simulationView, constants);
if (isEnded) {
nextTick = nextTick.toWinningTick(constants.getWinningCondition().getWinner());
}
simulationView.addInstance(nextTick);
// end this thread if game is finished
if (isEnded) {
return;
}
}
}
}
}