package me.stieglmaier.sphereMiners.view; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.geometry.VPos; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Button; import javafx.scene.control.Slider; import javafx.scene.control.TableView; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.TextAlignment; import me.stieglmaier.sphereMiners.main.Constants; import me.stieglmaier.sphereMiners.model.ai.Player; import me.stieglmaier.sphereMiners.model.util.GameSimulation; import me.stieglmaier.sphereMiners.model.util.Sphere; import me.stieglmaier.sphereMiners.model.util.Tick; import me.stieglmaier.sphereMiners.model.util.Tick.WinningTick; /** * This class handles the drawing on the canvas, such that the simulation * can be viewed. * * @author stieglma * */ public class DisplayGameHandler { private volatile int currentTick = 0; private final Runnable playTick; private final Runnable showCurrentTick; private final Slider progressBar; private final Constants constants; private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture<?> future; private boolean isPaused = false; private final ChangeListener<Number> currentSliderTickListener; private final Map<Player, Integer> playerSizes = new HashMap<>(); /** * The constructor creates the handler and some listeners that are attached * e.g. to the progressBar. * * @param graphicsContext the graphics object that is used to draw * @param simulation the simulation that should be played * @param progressBar the progressBar displaying the current viewed state * @param playButton the button that is used to start/stop/pause the replay * @param playingAIs the tableview showing the current size of the players * @param constants the constants which are necessary for computing the view */ public DisplayGameHandler( GraphicsContext graphicsContext, GameSimulation simulation, Slider progressBar, Button playButton, TableView<Player> playingAIs, Constants constants) { this.constants = constants; this.progressBar = progressBar; // this will be filled and used during showCurrentTick for (Player p : playingAIs.getItems()) { playerSizes.put(p, 0); } playTick = () -> { progressBar.increment(); }; showCurrentTick = () -> { // check if current tick is available before retrieving it if (currentTick >= simulation.getSize()) { future.cancel(true); playButton.setText("play"); return; } // clear drawing area graphicsContext.clearRect(0, 0, constants.getFieldWidth(), constants.getFieldHeight()); // retrieve tick Tick tick = simulation.getTick(currentTick); // reset playersizes playerSizes.replaceAll((a, b) -> 0); //do drawing on graphics object for (Sphere s : tick.getSpheres()) { Player owner = s.getOwner(); graphicsContext.setFill(owner.getColor()); playerSizes.replace(owner, playerSizes.get(owner) + s.getSize()); double radius = s.getRadius(); graphicsContext.fillOval( s.getPosition().getX() - radius, s.getPosition().getY() - radius, radius * 2, radius * 2); } playerSizes.forEach((a, b) -> a.getSizeProperty().set(b)); playingAIs.sort(); for (Sphere s : tick.getDots()) { graphicsContext.setFill(s.getColor()); double radius = s.getRadius(); graphicsContext.fillOval( s.getPosition().getX() - radius, s.getPosition().getY() - radius, radius, radius); } if (tick instanceof WinningTick) { graphicsContext.clearRect(0, 0, constants.getFieldWidth(), constants.getFieldHeight()); graphicsContext.setTextAlign(TextAlignment.CENTER); graphicsContext.setTextBaseline(VPos.CENTER); graphicsContext.setFont(Font.font(null, FontWeight.BOLD, 20)); List<Player> winners = ((WinningTick) tick).getWinners(); if (winners.size() > 2) { graphicsContext.fillText( "Game Finished\n\n the winners are:\n\n" + winners .stream() .map(p -> p.getNameProperty().get()) .reduce((a, b) -> a + "\n" + b) .get(), constants.getFieldWidth() / 2, constants.getFieldHeight() / 2); } else { graphicsContext.fillText( "Game Finished\n\n the winner is:\n\n" + winners.get(0).getNameProperty().get(), constants.getFieldWidth() / 2, constants.getFieldHeight() / 2); } future.cancel(true); playButton.setText("play"); return; } }; currentSliderTickListener = (a, b, n) -> { currentTick = (int) (n.doubleValue() * constants.getFramesPerSecond()); showCurrentTick.run(); }; } /** * Returns the listener for the slider change event * @return the listener */ public ChangeListener<Number> getSliderChangedListener() { return currentSliderTickListener; } /** * Starts the animation. */ public void startAnimation() { future = scheduler.scheduleAtFixedRate( () -> Platform.runLater(playTick), 0, constants.getFramesPerSecond(), TimeUnit.MILLISECONDS); } /** * Pauses or resumes the animation. */ public void pauseResumeAnimation() { isPaused = !isPaused; if (isPaused) { future.cancel(true); } else { currentTick = (int) progressBar.getValue(); future = scheduler.scheduleAtFixedRate( () -> Platform.runLater(playTick), 0, constants.getFramesPerSecond(), TimeUnit.MILLISECONDS); } } /** * Stops the animation, cannot be undone, the DisplayHandler cannot be used * anymore afterwards. */ public void stopAnimation() { scheduler.shutdownNow(); } }