package org.mafagafogigante.dungeon.game; import org.mafagafogigante.dungeon.commands.IssuedCommand; import org.mafagafogigante.dungeon.commands.IssuedCommandEvaluation; import org.mafagafogigante.dungeon.commands.IssuedCommandProcessor; import org.mafagafogigante.dungeon.gui.GameWindow; import org.mafagafogigante.dungeon.io.Loader; import org.mafagafogigante.dungeon.io.Writer; import org.mafagafogigante.dungeon.logging.DungeonLogger; import org.mafagafogigante.dungeon.util.StopWatch; import org.mafagafogigante.dungeon.util.Utils; import org.apache.commons.lang3.StringUtils; import java.awt.Color; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; public class Game { private static final InstanceInformation instanceInformation = new InstanceInformation(); private static GameWindow gameWindow; private static GameState gameState; /** * The main method. */ public static void main(String[] args) { final StopWatch stopWatch = new StopWatch(); invokeOnEventDispatchThreadAndWait(new Runnable() { @Override public void run() { gameWindow = new GameWindow(); } }); DungeonLogger.info("Finished making the window. Took " + stopWatch.toString() + "."); setGameState(getInitialGameState()); invokeOnEventDispatchThreadAndWait(new Runnable() { @Override public void run() { getGameWindow().startAcceptingCommands(); DungeonLogger.info("Signaled the window to start accepting commands."); } }); } /** * Invokes a runnable on the EDT and waits for it to finish. If an exception is thrown, this method logs it and * finishes the application. */ private static void invokeOnEventDispatchThreadAndWait(Runnable runnable) { try { SwingUtilities.invokeAndWait(runnable); } catch (InterruptedException | InvocationTargetException fatal) { DungeonLogger.logSevere(fatal); } } /** * Loads a saved GameState or creates a new one. Should be invoked to get the first GameState of the instance. * * <p>If a new GameState is created and the saves folder is empty, the tutorial is suggested. */ private static GameState getInitialGameState() { GameState gameState = Loader.loadGame(true); if (gameState == null) { gameState = Loader.newGame(); // Note that loadedGameState may be null even if a save exists (if the player declined to load it). // So check for any save in the folder. if (!Loader.checkForSave()) { // Suggest the tutorial only if no saved game exists. suggestTutorial(); } } return gameState; } private static void suggestTutorial() { Writer.write(new DungeonString("\nYou may want to issue 'tutorial' to learn the basics.\n")); } /** * Gets a GameState object. Should be invoked to get a GameState after the Hero dies. */ private static GameState getAfterDeathGameState() { GameState gameState = Loader.loadGame(false); if (gameState != null) { JOptionPane.showMessageDialog(getGameWindow(), "Loaded the most recent saved game."); } else { gameState = Loader.newGame(); JOptionPane.showMessageDialog(getGameWindow(), "Could not load a saved game. Created a new game."); } return gameState; } public static GameWindow getGameWindow() { return gameWindow; } public static GameState getGameState() { return gameState; } /** * Sets a new GameState to the static field. Can be used to nullify the GameState, something that should be done while * another GameState is being created. If the provided GameState is not null, this setter also invokes Hero.look(). * * @param state another GameState object, or null */ public static void setGameState(GameState state) { if (getGameState() != null) { DungeonLogger.warning("Called setGameState without unsetting the old game state."); } if (state == null) { throw new IllegalArgumentException("passed null to setGameState."); } gameState = state; DungeonLogger.info("Set the GameState field in Game to a GameState."); // This is a new GameState that must be refreshed in order to have spawned creatures at the beginning. Engine.refresh(); Writer.write(new DungeonString("\n")); // Improves readability. gameState.getHero().look(); } public static void unsetGameState() { DungeonLogger.info("Set the GameState field in Game to null."); gameState = null; } /** * Renders a turn based on the last IssuedCommand. * * @param issuedCommand the last IssuedCommand. */ public static void renderTurn(IssuedCommand issuedCommand, StopWatch stopWatch) { DungeonLogger.logCommandRenderingReport(issuedCommand.toString(), "started renderTurn", stopWatch); // Clears the text pane. getGameWindow().clearTextPane(); DungeonLogger.logCommandRenderingReport(issuedCommand.toString(), "started processInput", stopWatch); boolean wasSuccessful = processInput(issuedCommand); DungeonLogger.logCommandRenderingReport(issuedCommand.toString(), "finished processInput", stopWatch); if (wasSuccessful) { if (getGameState().getHero().getHealth().isDead()) { getGameWindow().clearTextPane(); Writer.write("You died."); unsetGameState(); setGameState(getAfterDeathGameState()); } else { Engine.endTurn(); } } DungeonLogger.logCommandRenderingReport(issuedCommand.toString(), "finished renderTurn", stopWatch); } /** * Processes the player's input. Adds the IssuedCommand to the CommandHistory and to the CommandStatistics. Finally, * this method finds and executes the corresponding Command object or prints a message if there is not such Command. * * @param issuedCommand the last IssuedCommand. * @return a boolean indicating whether or not the command executed successfully */ private static boolean processInput(IssuedCommand issuedCommand) { IssuedCommandEvaluation evaluation = IssuedCommandProcessor.evaluateIssuedCommand(issuedCommand); if (evaluation.isValid()) { instanceInformation.incrementAcceptedCommandCount(); getGameState().getCommandHistory().addCommand(issuedCommand); getGameState().getStatistics().addCommand(issuedCommand); IssuedCommandProcessor.prepareIssuedCommand(issuedCommand).execute(); return true; } else { DungeonString string = new DungeonString(); string.setColor(Color.RED); string.append("That is not a valid command.\n"); string.append("But it is similar to "); List<String> suggestionsBetweenCommas = new ArrayList<>(); for (String suggestion : evaluation.getSuggestions()) { suggestionsBetweenCommas.add(StringUtils.wrap(suggestion, '"')); } string.append(Utils.enumerate(suggestionsBetweenCommas)); string.append(".\n"); string.setColor(Color.ORANGE); string.append("See 'commands' for a complete list of commands."); Writer.write(string); return false; } } /** * Exits the game, prompting the user if the current state should be saved if it is not already saved. */ public static void exit() { if (getGameState() != null && !getGameState().isSaved()) { Loader.saveGame(getGameState()); } logInstanceClosing(); System.exit(0); } private static void logInstanceClosing() { StringBuilder builder = new StringBuilder(); builder.append("Closing instance. Ran for "); builder.append(instanceInformation.getDurationString()); builder.append(". "); if (instanceInformation.getAcceptedCommandCount() == 0) { builder.append("Parsed no commands."); } else if (instanceInformation.getAcceptedCommandCount() == 1) { builder.append("Parsed one command."); } else { builder.append("Parsed "); builder.append(instanceInformation.getAcceptedCommandCount()); builder.append(" commands."); } DungeonLogger.info(builder.toString()); } }