/** * Version 1.00 * Copyright (C) 2009 Ivo Wetzel * <http://github.com/BonsaiDen/Bonsai-Game-Library/> * * * This file is part of the Bonsai Game Library. * * The Bonsai Game Library is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Bonsai Game Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * the Bonsai Game Library. If not, see <http://www.gnu.org/licenses/>. * */ package org.bonsai.dev; import java.applet.Applet; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.VolatileImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URISyntaxException; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.WindowConstants; import org.bonsai.ext.Base64; import netscape.javascript.JSObject; public class Game extends Applet { // Applet private static final long serialVersionUID = -7860545086276629929L; // Graphics private GraphicsConfiguration config = GraphicsEnvironment.getLocalGraphicsEnvironment() .getDefaultScreenDevice() .getDefaultConfiguration(); protected Container focusElement; private JPanel canvasPanel; private VolatileImage background; protected Color backgroundColor = Color.BLACK; private int width; private int height; private int scale; // Game Stuff private boolean gameLoaded = false; private boolean gameSound = true; private boolean isRunning = true; protected boolean paused = false; private boolean focused = false; private boolean pausedOnFocus = false; private boolean animationPaused = false; private int currentFPS = 0; private long fpsWait; private long gameTime = 0; private boolean limitFPS = true; private int maxFPS = 0; private boolean stopped = false; private transient Thread gameLoader = null; // GUI private JFrame frame = null; protected Applet applet = null; // Classes protected GameAnimation animation = null; protected GameSound sound = null; protected GameImage image = null; protected GameInput input = null; protected GameFont font = null; protected GameTimer timer = null; protected GameMenu menu = null; // Console protected GameConsole console = null; protected boolean consoleOpen = false; // Builder private boolean buildScaled = false; private boolean buildSound = true; private int buildSizex = 320; private int buildSizey = 240; private String buildTitle = "Bonsai"; private boolean buildInitMenu = false; private boolean buildGameMenu = false; /* * Path -------------------------------------------------------------------- */ public final boolean isJar() { boolean isJar = true; if (!isApplet()) { isJar = currentPath().toLowerCase().endsWith(".jar"); } return isJar; } public final String getPath() { String path = ""; if (!isApplet()) { path = currentPath(); if (isJar()) { path = path.substring(0, path.lastIndexOf("/") + 1); } } return path; } private String currentPath() { String path = ""; if (!isApplet()) { try { path = this.getClass() .getProtectionDomain() .getCodeSource() .getLocation() .toURI() .getPath(); } catch (URISyntaxException e) { path = ""; } } return path; } /* * Builder Methods --------------------------------------------------------- */ public final Game title(final String title) { buildTitle = title; return this; } public final Game size(final int width, final int height) { buildSizex = width; buildSizey = height; return this; } public final Game scaled(final boolean scaled) { buildScaled = scaled; return this; } public final Game sound(final boolean sound) { buildSound = sound; return this; } public final Game menu(final boolean menu, final boolean def) { buildInitMenu = menu; buildGameMenu = def; return this; } public final Game background(final Color color) { backgroundColor = color; return this; } public final void create() { // Size scale = buildScaled ? 2 : 1; width = buildSizex; height = buildSizey; // Create frame frame = new JFrame(config); frame.setLayout(new BorderLayout(0, 0)); // Init Engine gameSound = buildSound; initEngine(frame); // Setup frame frame.setResizable(false); frame.setTitle(buildTitle); menu = new GameMenu(this, buildInitMenu, buildGameMenu); frame.addWindowListener(new FrameClose()); frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); frame.setVisible(true); resizeFrame(); // Start threads initThreads(); } /* * General Methods --------------------------------------------------------- */ public final void setSize(final int width, final int height) { this.width = width; this.height = height; background = image.createVolatile(width, height, false); setScale(1); } private final void resizeFrame() { frame.setSize((width * scale) + frame.getInsets().left + frame.getInsets().right, (height * scale) + frame.getInsets().top + frame.getInsets().bottom + menu.getSize()); } public void onMenu(final String menuID) { } public void onConsole(final String consoleInput) { } private class FrameClose extends WindowAdapter { @Override public void windowClosing(final WindowEvent e) { isRunning = false; } } public final JFrame getFrame() { return frame; } public final boolean hasMenu() { return menu != null; } public static void main(final String args[]) { new Game().title("Bonsai Game Library 1.00").size(320, 240).menu(true, true).create(); } /* * Applet ------------------------------------------------------------------ */ @Override public final void init() { if (stopped) { isRunning = true; } else { this.setFocusable(true); scale = (getParameter("scaled") != null && getParameter("scaled").equals( "true")) ? 2 : 1; gameSound = getParameter("sound") != null ? getParameter("sound").equals("true") : true; width = getWidth() / scale; height = getHeight() / scale; initApplet(this); setLayout(new BorderLayout(0, 0)); applet = this; initEngine(this); menu = new GameMenu(this, false, false); initThreads(); stopped = false; } } @Override public final void paint(Graphics g) { if (!gameLoaded) { g.setColor(backgroundColor); g.fillRect(0, 0, width() * scale, height() * scale); g.dispose(); } } @Override public final void stop() { stopped = true; } @Override public final void destroy() { exitGame(); } public final boolean isApplet() { return applet != null; } public final Applet getApplet() { return applet; } public final int height() { return height; } public final int width() { return width; } public final int scale() { return scale; } public final synchronized void setScale(final int scale) { canvasPanel.setPreferredSize(new Dimension(width * scale, height * scale)); this.scale = scale; resizeFrame(); } /* * Gameloader -------------------------------------------------------------- */ private void initEngine(final Container parent) { // We don't need double buffering here since we're already blitting all // stuff to our own buffer for scaling before actually updating the // screen. canvasPanel = new JPanel(false); canvasPanel.setPreferredSize(new Dimension(width * scale, height * scale)); canvasPanel.setFocusable(applet != null); canvasPanel.setOpaque(true); canvasPanel.setIgnoreRepaint(true); parent.add(canvasPanel, 0); // Components animation = new GameAnimation(this); sound = new GameSound(this); image = new GameImage(this); input = new GameInput(this); font = new GameFont(this); timer = new GameTimer(this); console = new GameConsole(this); // Add input listeners focusElement = applet != null ? canvasPanel : parent; focusElement.addMouseListener(input); focusElement.addMouseMotionListener(input); focusElement.addKeyListener(input); focusElement.addFocusListener(input); focusElement.requestFocus(); // Our background for scaling which also acts as a replacement for // double buffering background = image.createVolatile(width, height, false); setFPS(30); } private void initThreads() { new GameLoop().start(); gameLoader = new GameLoader(); gameLoader.start(); } private class GameLoader extends Thread { public GameLoader() { setDaemon(true); setName("Bonsai-GameLoader"); } @Override public void run() { // Init Loading initGame(true); if (gameSound) { gameSound = sound.init(); // This actually takes time! } finishGame(false); menu.enable(true); gameLoaded = true; // Fix some of the graphical lag // This hack lowers the systems interrupt rate so that Thread.sleep // becomes more precise try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { isRunning = false; Thread.interrupted(); } } } /* * Methods implemented by the game ----------------------------------------- */ public void initGame(final boolean loaded) { } public void initApplet(final Applet applet) { } public void updateGame(final boolean loaded) { } public void renderGame(final boolean loaded, final Graphics2D g) { g.setColor(Color.BLACK); g.fillRect(0, 0, width(), height()); } public void finishGame(final boolean loaded) { } /* * Gameloop ---------------------------------------------------------------- */ private class GameLoop extends Thread { @Override public void run() { setName("Bonsai-GameLoop"); initGame(false); // FPS long renderStart = System.nanoTime(); final long[] renderStats = new long[10]; final long[] renderStatsMax = new long[10]; // Graphics while (isRunning) { // Pausing if (!consoleOpen && input.keyPressed(java.awt.event.KeyEvent.VK_P, true)) { pause(!paused); } // Console if (console != null && consoleKey()) { consoleOpen = !consoleOpen; } if (consoleOpen) { console.control(); } // Update Game if (!paused) { updateGame(gameLoaded); if (!animationPaused) { animation.update(); } } input.clearKeys(); input.clearMouse(); // Render Graphics2D bg = (Graphics2D) background.getGraphics(); Graphics2D cbg = (Graphics2D) canvasPanel.getGraphics(); renderGame(gameLoaded, bg); if (consoleOpen) { console.draw(bg, 0, 0); } // Fix the backbuffer if it breaks if (background.contentsLost()) { background.validate(config); } // Scale the buffer? if (scale != 1) { cbg.drawImage(background, 0, 0, width * scale, height * scale, 0, 0, width, height, null); } else { cbg.drawImage(background, 0, 0, null); } cbg.dispose(); bg.dispose(); // Limit FPS if (!paused) { // Use Nanoseconds instead of currentTimeMillis which // has a much lower resolution(based on the OS interrupt // rate) and would result in too high FPS. // Note: There is a way to set the interrupt rate lower // which is done by many programs, mostly media players. // That means if you use currentTimeMillis and play a // track, your FPS is okay, but without the music it's // too high. // More on this: // <http://blogs.sun.com/dholmes/entry/inside_the_hotspot_vm_clocks> long renderTime = (System.nanoTime() - renderStart) / 10000; if (limitFPS) { try { Thread.sleep(Math.max(0, fpsWait - (renderTime / 100))); } catch (InterruptedException e) { Thread.interrupted(); break; } } long allRenderTime = (System.nanoTime() - renderStart) / 10000; if (gameLoaded) { gameTime += allRenderTime / 100; } // Average FPS over 10 frames final int frame = (int) (System.nanoTime() % 10); renderStats[frame] = allRenderTime; renderStatsMax[frame] = renderTime; if (frame == 9) { int time = 1; int max = 1; for (int i = 0; i < 10; i++) { time += renderStats[i]; max += renderStatsMax[i]; } currentFPS = (int) 1000000 / time; maxFPS = (int) 1000000 / max; } renderStart = System.nanoTime(); } else { try { Thread.sleep(25); } catch (InterruptedException e) { Thread.interrupted(); break; } } } // Clean up gameLoader.interrupt(); finishGame(true); sound.stopAll(); if (!isApplet()) { frame.dispose(); } else { applet = null; } } } public final GraphicsConfiguration getConfig() { return config; } public final VolatileImage getBackbuffer() { return background; } public final JPanel getCanvas() { return canvasPanel; } /* * Game methods ------------------------------------------------------------ */ public final void exitGame() { isRunning = false; } // Setters & Getters public final boolean isRunning() { return isRunning; } public final boolean hasSound() { return gameSound; } public final long getTime() { return gameTime; } public final void setFPS(final int fps) { fpsWait = (long) (1.0 / fps * 1000); } public final int getFPS() { return currentFPS; } public final int getMaxFPS() { return maxFPS; } public final void pause(final boolean mode) { paused = mode; menu.select("pause", paused); sound.pauseAll(paused); } public final boolean isPaused() { return paused; } public final void animationTime(final boolean mode) { animationPaused = mode; } public final boolean isAnimationPaused() { return animationPaused; } public final void pauseOnFocus(final boolean mode) { pausedOnFocus = mode; } public final boolean isPausedOnFocus() { return pausedOnFocus; } public final boolean isFocused() { return focused; } public final void setFocused(final boolean focus) { focused = focus; } public final boolean isConsoleOpen() { return consoleOpen; } public boolean consoleKey() { return input.keyDown(java.awt.event.KeyEvent.VK_SHIFT, true) && input.keyPressed(java.awt.event.KeyEvent.VK_F1, true); } public final void setLimitFPS(final boolean limit) { limitFPS = limit; menu.select("limit", limit); } public final boolean isLimitFPS() { return limitFPS; } /* * Saving ------------------------------------------------------------------ */ public final boolean saveGame(final String filename, final String cookiename) { try { if (!isApplet()) { OutputStream stream = new FileOutputStream(new File(filename)); writeSave(stream); stream.close(); } else { ByteArrayOutputStream stream = new ByteArrayOutputStream(); writeSave(stream); JSObject win = JSObject.getWindow(this); JSObject doc = (JSObject) win.getMember("document"); String data = cookiename + "=" + Base64.encodeBytes(stream.toByteArray()) // nobody will use this library in 10 years I guess // so this is ok ;) + "; path=/; expires=Thu, 31-Dec-2019 12:00:00 GMT"; doc.setMember("cookie", data); stream.close(); } } catch (Exception e) { return false; } return true; } public void writeSave(final OutputStream stream) throws IOException { } /* * Loading ----------------------------------------------------------------- */ public final boolean loadGame(final String filename, final String cookiename) { try { InputStream stream = null; if (!isApplet()) { stream = new FileInputStream(filename); } else { String data = null; JSObject myBrowser = JSObject.getWindow(this); JSObject myDocument = (JSObject) myBrowser.getMember("document"); String myCookie = (String) myDocument.getMember("cookie"); if (myCookie.length() > 0) { String[] cookies = myCookie.split(";"); for (String cookie : cookies) { int pos = cookie.indexOf("="); if (cookie.substring(0, pos).trim().equals(cookiename)) { data = cookie.substring(pos + 1); break; } } } // Decode if (data != null) { byte[] buffer = Base64.decode(data); stream = new ByteArrayInputStream(buffer); } } // No Stream if (stream == null) { return false; } // Empty Stream if (stream.available() <= 0) { stream.close(); return false; } // Read Save readSave(stream); stream.close(); } catch (Exception e) { // e.printStackTrace(); return false; } return true; } public void readSave(final InputStream stream) throws IOException { } }