From 18077719f32071e614d3b4d01134d501796f571d Mon Sep 17 00:00:00 2001 From: Nao Pross Date: Wed, 21 Nov 2018 12:16:51 +0100 Subject: Fix InvalidStateException at scene loading, add Scene.requestScene() The internal game state management has also been changed for the new scene management in the graphics --- src/subconscious/Game.java | 57 ++++++++++++---------- src/subconscious/graphics/GameWindow.java | 79 +++++++++++++++++-------------- src/subconscious/graphics/MapScene.java | 37 ++++++++------- src/subconscious/graphics/MenuScene.java | 3 +- src/subconscious/graphics/PauseScene.java | 6 +-- src/subconscious/graphics/Scene.java | 43 ++++++++++++++--- 6 files changed, 138 insertions(+), 87 deletions(-) diff --git a/src/subconscious/Game.java b/src/subconscious/Game.java index a6d42be..2361dd0 100644 --- a/src/subconscious/Game.java +++ b/src/subconscious/Game.java @@ -13,7 +13,7 @@ import java.util.concurrent.locks.Condition; public class Game { public enum State { // main menu - MENU, + MAIN_MENU, // where actors can move freely WORLD, // where actors are constrained in the tiles @@ -22,11 +22,12 @@ public class Game { PAUSE, CLOSING, } - private State state = State.MENU; + private State state = State.MAIN_MENU; private State lastState; - private final Lock stateLock = new ReentrantLock(); - private Condition stateChanged; + // private final Lock stateLock = new ReentrantLock(); + // private Condition stateChanged; + private boolean stateChanged; private boolean running = true; private boolean gameOver = false; @@ -40,7 +41,7 @@ public class Game { public Game() { // set up stateLock - stateChanged = stateLock.newCondition(); + // stateChanged = stateLock.newCondition(); // TODO: this will be replaced with a dynamic mechanism based // on the progress within the game @@ -61,38 +62,45 @@ public class Game { /* methods to manage the state */ public void setState(State state) { - stateLock.lock(); - this.stateChanged.signal(); - stateLock.unlock(); + // stateLock.lock(); + // this.stateChanged.signal(); + // stateLock.unlock(); + this.stateChanged = true; + this.lastState = this.state; this.state = state; } public State getState() { + this.stateChanged = false; return this.state; } - public State waitStateChange() { - stateLock.lock(); - try { - stateChanged.await(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } finally { - stateLock.unlock(); - } - - return this.state; + public State getLastState() { + return this.lastState; } - public void togglePause() { - if (this.isPaused()) - this.resume(); - else - this.pause(); + public boolean stateChanged() { + return this.stateChanged; } + // public State waitStateChange() { + // stateLock.lock(); + // try { + // stateChanged.await(); + // } catch (InterruptedException ex) { + // ex.printStackTrace(); + // } finally { + // stateLock.unlock(); + // } + + // return this.state; + // } + public void pause() { + if (this.state == State.PAUSE) + return; + this.lastState = this.state; this.setState(State.PAUSE); } @@ -116,6 +124,7 @@ public class Game { } public void quit() { + // TODO: change to MAIN_MENU? this.setState(State.CLOSING); this.running = false; } diff --git a/src/subconscious/graphics/GameWindow.java b/src/subconscious/graphics/GameWindow.java index da69d40..8f24dac 100644 --- a/src/subconscious/graphics/GameWindow.java +++ b/src/subconscious/graphics/GameWindow.java @@ -32,7 +32,7 @@ public class GameWindow extends JFrame implements WindowListener { // stack of loaded scenes in the background that are paused private Stack> loadedScenes = new Stack<>(); - private Game game; + private volatile Game game; // TODO: remove map editor, start directly on Battle mode public GameWindow(Game g) { @@ -66,33 +66,39 @@ public class GameWindow extends JFrame implements WindowListener { this.loadScene(new MenuScene(this.game, "mainmenu")); while (this.game.isRunning()) { - Game.State newState = this.game.waitStateChange(); - - switch (newState) { - case MENU: - // unload all scenes to get the first one => menu - this.unloadAllScenes(); - break; - - // TODO: rewrite the following part - case BATTLE: - this.loadScene(new BattleScene(this.game, "battle1")); - break; - - case PAUSE: - this.loadScene(new PauseScene(this.game, "pause")); - try { - this.sceneThread.join(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - this.unloadScene(); - break; + // check if the scene has requested a new scene + if (this.scene.isRequestingScene()) { + this.loadScene(this.scene.getRequestedScene()); } + + // check for game state change + if (this.game.stateChanged()) { + // Game.State newState = this.game.waitStateChange(); + Game.State newState = this.game.getState(); + Game.State lastState = this.game.getLastState(); + + if (lastState == newState) { + throw new IllegalStateException(); + } - } + // if unpaused unload PauseScene + if (lastState == Game.State.PAUSE) { + this.unloadScene(); + } - this.quit(); + // MAIN_MENU is always the first scene + if (newState == Game.State.MAIN_MENU) { + this.unloadAllScenes(); + } + // if paused load new PauseScene + else if (newState == Game.State.PAUSE) { + this.loadScene(new PauseScene(this.game, "pause")); + } + else if (newState == Game.State.CLOSING) { + this.quit(); + } + } + } } private void loadScene(Scene scene) { @@ -122,15 +128,16 @@ public class GameWindow extends JFrame implements WindowListener { } private void unloadAllScenes() { - unloadScenes(this.loadedScenes.size() -1); - } - - private void unloadScenes(int count) { - for (int i = 0; i < count; i++) { + while (!this.loadedScenes.empty()) this.unloadScene(); - } } + // private void unloadScenes(int count) { + // for (int i = 0; i < count; i++) { + // this.unloadScene(); + // } + // } + private void unloadScene() { // close old scene this.scene.stop(); @@ -144,11 +151,8 @@ public class GameWindow extends JFrame implements WindowListener { // remove card from UI this.root.remove(scene); - // TODO: this should probably be an error if (this.loadedScenes.empty()) { - this.scene = null; - this.sceneThread = null; - return; + throw new IllegalStateException(); } // load the last scene @@ -164,6 +168,8 @@ public class GameWindow extends JFrame implements WindowListener { this.unloadAllScenes(); this.setVisible(false); this.dispose(); + + System.exit(0); } /* window listener */ @@ -175,7 +181,8 @@ public class GameWindow extends JFrame implements WindowListener { @Override public void windowDeactivated(WindowEvent e) { - this.game.pause(); + if (this.game.getState() != Game.State.MAIN_MENU) + this.game.pause(); } @Override public void windowDeiconified(WindowEvent e) {} diff --git a/src/subconscious/graphics/MapScene.java b/src/subconscious/graphics/MapScene.java index 87a6012..ed58506 100644 --- a/src/subconscious/graphics/MapScene.java +++ b/src/subconscious/graphics/MapScene.java @@ -228,22 +228,27 @@ public abstract class MapScene extends Scene { int keyCode = e.getKeyCode(); switch (keyCode) { - case KeyEvent.VK_UP: - this.pan.y += 10; - break; - case KeyEvent.VK_DOWN: - this.pan.y -= 10; - break; - case KeyEvent.VK_LEFT: - this.pan.x += 10; - break; - case KeyEvent.VK_RIGHT: - this.pan.x -= 10; - break; - - case KeyEvent.VK_ESCAPE: - this.game.pause(); - break; + case KeyEvent.VK_UP: + this.pan.y += 10; + break; + case KeyEvent.VK_DOWN: + this.pan.y -= 10; + break; + case KeyEvent.VK_LEFT: + this.pan.x += 10; + break; + case KeyEvent.VK_RIGHT: + this.pan.x -= 10; + break; + + case KeyEvent.VK_ESCAPE: + this.game.pause(); + break; + + // TODO: remove + case KeyEvent.VK_Q: + this.game.setState(Game.State.MAIN_MENU); + break; } } diff --git a/src/subconscious/graphics/MenuScene.java b/src/subconscious/graphics/MenuScene.java index 9c57ca6..ac38d10 100644 --- a/src/subconscious/graphics/MenuScene.java +++ b/src/subconscious/graphics/MenuScene.java @@ -54,7 +54,6 @@ public class MenuScene extends Scene implements ActionListener { if (e.getActionCommand().startsWith("btn")) { if (e.getActionCommand().equals("btn-exit")) { this.game.quit(); - this.stop(); return; } @@ -63,6 +62,8 @@ public class MenuScene extends Scene implements ActionListener { // this.loadScene(new MapEditorScene()); } else if (e.getActionCommand().equals("btn-battle")) { this.game.start(); + // TODO: change + this.requestScene(new BattleScene(this.game, "demobattle")); } } diff --git a/src/subconscious/graphics/PauseScene.java b/src/subconscious/graphics/PauseScene.java index 6365514..2b5f411 100644 --- a/src/subconscious/graphics/PauseScene.java +++ b/src/subconscious/graphics/PauseScene.java @@ -27,16 +27,16 @@ public class PauseScene extends Scene { } protected void update(long deltaNanoTime) { + } @Override - public void keyReleased(KeyEvent e) { - super.keyReleased(e); + public void keyPressed(KeyEvent e) { + super.keyPressed(e); if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { this.game.resume(); - this.stop(); } } } \ No newline at end of file diff --git a/src/subconscious/graphics/Scene.java b/src/subconscious/graphics/Scene.java index 4132ded..7b54a1a 100644 --- a/src/subconscious/graphics/Scene.java +++ b/src/subconscious/graphics/Scene.java @@ -45,12 +45,15 @@ public abstract class Scene extends JPanel // Game is never be cached in the local thread protected volatile Game game; + // TODO: this could become a Queue of scenes to load + protected Scene requestedScene = null; + protected volatile boolean running; protected volatile boolean threadPaused; protected Object pauseLock; + protected volatile Dimension canvasSize; protected BufferStrategy buffer; - protected Dimension canvasSize; protected Canvas canvas; // TODO: make accessible from user settings @@ -93,6 +96,26 @@ public abstract class Scene extends JPanel protected abstract void update(long deltaNanoTime); protected abstract void render(Graphics2D g); + /* request scenes */ + protected void requestScene(Scene sc) { + if (this.requestedScene != null) + throw new UnsupportedOperationException("only one scene can be requested at once"); + + this.requestedScene = sc; + } + + public boolean isRequestingScene() { + return this.requestedScene != null; + } + + public Scene getRequestedScene() { + Scene requested = this.requestedScene; + this.requestedScene = null; + + return requested; + } + + /* runnable implementation */ public void run() { long beginLoopTime; long endLoopTime; @@ -109,9 +132,15 @@ public abstract class Scene extends JPanel while (!this.canvas.isDisplayable() && running); // initialize canvas buffer - this.canvas.createBufferStrategy(2); - this.buffer = this.canvas.getBufferStrategy(); - this.canvas.requestFocus(); + // the condition checks because the while above can be interrupted by + // changing this.running + // TODO: this is because of the MenuScene which hides the canvas + // when the scene will be fixed this if can be removed + if (this.canvas.isDisplayable()) { + this.canvas.createBufferStrategy(2); + this.buffer = this.canvas.getBufferStrategy(); + this.canvas.requestFocus(); + } while (running) { beginLoopTime = System.nanoTime(); @@ -122,9 +151,9 @@ public abstract class Scene extends JPanel this.render(g); g.dispose(); // repeat if the rendering buffer contents were restored - } while (this.buffer.contentsRestored()); + } while (this.buffer.contentsRestored() && running); // repeat if the drawing buffer contents were lost - } while (this.buffer.contentsLost()); + } while (this.buffer.contentsLost() && running); try { this.buffer.show(); @@ -192,7 +221,7 @@ public abstract class Scene extends JPanel // WARNING: does not always work // TODO: why doesn't this always work? public synchronized void updateCanvasSize() { - this.canvasSize = this.getParent().getSize(); + this.canvasSize = this.getParent().getParent().getSize(); } // this one always works -- cgit v1.2.1