diff --git a/midp.js b/midp.js index 3e0af67a..25ad2f69 100644 --- a/midp.js +++ b/midp.js @@ -760,7 +760,7 @@ Native["com/sun/midp/rms/RecordStoreSharedDBHeader.getHeaderRefCount0.(I)I"] = f MIDP.nativeEventQueue = []; MIDP.copyEvent = function(obj) { - var e = MIDP.nativeEventQueue.pop(); + var e = MIDP.nativeEventQueue.shift(); obj.class.getField("type", "I").set(obj, e.type); obj.class.fields.forEach(function(field) { field.set(obj, e[field.name]); diff --git a/tests/asteroids/Asteroid.java b/tests/asteroids/Asteroid.java new file mode 100644 index 00000000..770ee518 --- /dev/null +++ b/tests/asteroids/Asteroid.java @@ -0,0 +1,337 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.lcdui.*; +import java.util.*; + +/** + * Class to implement an asteroid. Asteroids come in three sizes. + * @author Jean-Francois Doue + * @version 1.2, 2001/10/24 + */ +public class Asteroid extends Mobile { + /** + * A size constant for small asteroids. + */ + public static final byte SIZE_SMALL = 0; + + /** + * A size constant for medium asteroids. + */ + public static final byte SIZE_MEDIUM = 1; + + /** + * A size constant for large asteroids. + */ + public static final byte SIZE_LARGE = 2; + + /** + * The x coordinates of the asteroid's vertices + */ + public static byte[][] xcoords; + + /** + * The y coordinates of the asteroid's vertices + */ + public static byte[][] ycoords; + + /** + * The radius of the asteroid. + */ + public static byte[] radii; + + /** + * The diameter of the asteroid. + */ + public static int[] diameters; + + + /** + * Scan converted asteroids used for collision detection. + */ + public static boolean[][][] masks; + + /** + * The asteroids currently existing in the game. + */ + public static Pool asteroids; + + private byte _angle; // Orientation of the asteroid. + private byte _size; // Small, medium or large. + private static byte[] _value = {5, 2, 1}; // Points earned when shot. + private static final byte _FIELD_TOP = 0; + private static final byte _FIELD_BOTTOM = 1; + private static final byte _FIELD_LEFT = 2; + private static final byte _FIELD_RIGHT = 3; + + + static { + // Create and populate the asteroid pool. + Asteroid[] array = new Asteroid[20]; + for (int i = array.length - 1; i >= 0; i--) { + array[i] = new Asteroid(); + } + asteroids = new Pool(array); + + final int[] allRadii = {3, 5, 8}; + final byte[][] allAngles = { + {3, 11, 19, 25, 3}, // small + {2, 7, 17, 19, 26, 2}, // medium + {1, 4, 9, 15, 21, 23, 29, 1} // large + }; + radii = new byte[3]; + diameters = new int[3]; + xcoords = new byte[3][]; + ycoords = new byte[3][]; + masks = new boolean[3][][]; + for (int i = SIZE_SMALL; i <= SIZE_LARGE; i++) { + // Precompute the asteroid vertices. + radii[i] = (byte)(allRadii[i] * ratioNum / ratioDenom); + byte[] angles = allAngles[i]; + byte[] pointx = new byte[angles.length]; + xcoords[i] = pointx; + byte[] pointy = new byte[angles.length]; + ycoords[i] = pointy; + for (int j = angles.length - 1; j >= 0; j--) { + pointx[j] = (byte)((radii[i] * Mobile.cos[angles[j]]) >> 6); + pointy[j] = (byte)((radii[i] * Mobile.sin[angles[j]]) >> 6); + } + + // Scan convert the asteroid polygon. + diameters[i] = radii[i] << 1; + masks[i] = new boolean[diameters[i]][]; + for (int j = 0; j < diameters[i]; j++) { + masks[i][j] = new boolean[diameters[i]]; + } + Geometry.scanConvertPolygon(masks[i], pointx, pointy); + } + } + + /** + * Initializes an Asteroid instance to make it large, positionned + * on a screen boundary and going in the direction of opposite boundary. + */ + public static void randomInit(Asteroid asteroid) { + int x = 0, y = 0; + byte angle = 0; + switch(Math.abs(Game.random.nextInt()) % 4) { + case _FIELD_TOP: + y = 1; + x = Math.abs(Game.random.nextInt()) % width; + angle = (byte)(15 + Math.abs(Game.random.nextInt()) % 15); + break; + case _FIELD_LEFT: + x = 1; + y = Math.abs(Game.random.nextInt()) % height; + angle = (byte)((23 + Math.abs(Game.random.nextInt()) % 15) % 32); + break; + case _FIELD_RIGHT: + x = width - 1; + y = Math.abs(Game.random.nextInt()) % height; + angle = (byte)(7 + Math.abs(Game.random.nextInt()) % 15); + break; + case _FIELD_BOTTOM: + y = height - 1; + x = Math.abs(Game.random.nextInt()) % width; + angle = (byte)(Math.abs(Game.random.nextInt()) % 15); + break; + } + asteroid.init(x, y, angle, SIZE_LARGE); + } + + /** + * Initializes a Asteroid instance by setting its position, angle + * and size. + */ + public final void init(int x, int y, byte angle, byte size) { + _angle = angle; + _size = size; + moveTo(x, y); + setVelocity(cos[angle] << 2, sin[angle] << 2); + } + + public Asteroid() { + } + + /** + * Move the asteroid to its next position. + */ + public static final void move() { + for (int i = 0; i < asteroids.count; i++) { + Asteroid a = (Asteroid)asteroids.pool[i]; + a._x += a.vx; + a._y += a.vy; + a.x = a._x >> 8; + a.y = a._y >> 8; + + // If a border has been hit, wrap the trajectory around + // the screen. The new origin is the projection of the + // intersection point on the opposite border. + if (a.x <= 0) { + a.moveTo(width - 2, a.y); + } else if (a.x >= width - 1) { + a.moveTo(1, a.y); + } else if (a.y <= 0) { + a.moveTo(a.x, height - 2); + } else if (a.y >= height - 1) { + a.moveTo(a.x, 1); + } + } + } + + /** + * Compute collisions between the asteroids and + * the ship and the rockets. + */ + public static final void collisionDetection() { + Ship ship = Ship.ship; + Field field = Game.field; + + // Collision detection makes sense only while the game is + // being played and the ship is alive. + if ((field != null) && (field.getState() == Field.GAME_STATE) && (ship.isAlive)) { + + int score = field.getScore(); + int newScore = score; + Pool rockets = Rocket.rockets; + Pool explosions = Explosion.explosions; + + for (asteroids.current = asteroids.count - 1; asteroids.current >= 0;) { + Asteroid a = (Asteroid)asteroids.pool[asteroids.current--]; + + // Detect ship - asteroid collisions. + int offset = ship.angle << 2; + for (int i = 0; i < 4; i++) { + int sx = ship.x + Ship.xcoords[offset + i] - a.x + radii[a._size]; + if ((sx >= 0) && (sx < diameters[a._size])) { + int sy = ship.y + Ship.ycoords[offset + i] - a.y + radii[a._size]; + if ((sy >= 0) && (sy < diameters[a._size])) { + + // Use the mask for collision detection + if (masks[a._size][sx][sy]) { + int lives = field.getLives(); + if ((lives > 0) && (ship.isAlive)) { + ship.explode(); + field.setLives(lives - 1); + } + } + } + } + } + + + // Detect asteroid - rocket collisions. + for (rockets.current = rockets.count - 1; rockets.current >= 0;) { + Rocket r = (Rocket)rockets.pool[rockets.current--]; + + // Transform the r into asteroid coordinates. + int rx = r.x - a.x + radii[a._size]; + if ((rx >= 0) && (rx < diameters[a._size])) { + int ry = r.y - a.y + radii[a._size]; + if ((ry >= 0) && (ry < diameters[a._size])) { + + // Use the mask for collision detection + if (masks[a._size][rx][ry]) { + asteroids.removeCurrent(); + rockets.removeCurrent(); + newScore += _value[a._size]; + a.split(r); + Explosion explosion = (Explosion)explosions.addNewObject(); + if (explosion != null) { + explosion.init(r.x, r.y); + } + break; + } + } + } + } + } + if (newScore != score) { + field.setScore(newScore); + } + } + } + + /** + * Draws an asteroid of the specified size and location + * in the specified graphic context. + */ + public static final void draw(byte size, int xpos, int ypos, Graphics g) { + byte[] xcoord = xcoords[size]; + byte[] ycoord = ycoords[size]; + for (int i = 0; i < xcoord.length - 1; i++) { + g.drawLine(xcoord[i] + xpos, ycoord[i] + ypos, xcoord[i + 1] + xpos, ycoord[i + 1] + ypos); + } + } + + + /** + * Draws all the asteroids of the supplied object pool using the + * specified graphic context. + */ + static public final void draw(Graphics g) { + for (int i = 0; i < asteroids.count; i++) { + Asteroid a = (Asteroid)asteroids.pool[i]; + byte[] xcoord = xcoords[a._size]; + byte[] ycoord = ycoords[a._size]; + for (int j = 0; j < xcoord.length - 1; j++) { + g.drawLine( + xcoord[j] + a.x, + ycoord[j] + a.y, + xcoord[j + 1] + a.x, + ycoord[j + 1] + a.y); + } + } + } + + + /** + * Splits the asteroid in two smaller pieces and set their + * direction depending on the asteroid direction and the hitting + * rocket direction. The pieces are added to the supplied collection. + */ + public final void split(Rocket rocket) { + if (_size >= SIZE_MEDIUM) { + byte newSize = (byte)(_size - 1); + int angle = (cos[_angle] * sin[rocket.angle] - cos[rocket.angle] * sin[_angle]) >> 10; + byte angle1 = (byte)(_angle + angle + Math.abs(Game.random.nextInt()) % 5); + if (angle1 < 0) { + angle1 += 32; + } + if (angle1 >= 32) { + angle1 -= 32; + } + byte angle2 = (byte)(_angle + angle - Math.abs(Game.random.nextInt()) % 5); + if (angle2 < 0) { + angle2 += 32; + } + if (angle2 >= 32) { + angle2 -= 32; + } + Asteroid a = (Asteroid)asteroids.addNewObject(); + if (a != null) { + a.init(x, y, angle1, newSize); + } + a = (Asteroid)asteroids.addNewObject(); + if (a != null) { + a.init(x, y, angle2, newSize); + } + } + } +} diff --git a/tests/asteroids/Explosion.java b/tests/asteroids/Explosion.java new file mode 100644 index 00000000..e41d017d --- /dev/null +++ b/tests/asteroids/Explosion.java @@ -0,0 +1,101 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.lcdui.*; +import java.util.*; + +/** + * Class to represent an explosion. + * @author Jean-Francois Doue + * @version 1.0, 2001/07/26 + */ +public class Explosion extends Object { + public static byte[] xcoords; + public static byte[] ycoords; + private int _frame, _x, _y; + private static final int MAX_FRAME = 6; + public static Pool explosions; + + static { + // Create and populate the explosion pool. + Explosion[] array = new Explosion[10]; + for (int i = array.length - 1; i >= 0; i--) { + array[i] = new Explosion(); + } + explosions = new Pool(array); + + // Pre-compute the explosion images + int[] Xcoords = new int[8]; + int[] Ycoords = new int[8]; + for (int i = 0; i < 8; i++) { + Xcoords[i] = Mobile.cos[i << 2] * (Math.abs(Game.random.nextInt()) % 12); + Ycoords[i] = Mobile.sin[i << 2] * (Math.abs(Game.random.nextInt()) % 12); + } + xcoords = new byte[48]; + ycoords = new byte[48]; + for (int i = 0; i < Explosion.MAX_FRAME; i++) { + int offset = i << 3; + for (int j = 0; j < 8; j++) { + xcoords[offset + j] = (byte)(Xcoords[j] >> 8); + ycoords[offset + j] = (byte)(Ycoords[j] >> 8); + Xcoords[j] += Mobile.cos[j << 2] << 2; + Ycoords[j] += Mobile.sin[j << 2] << 2; + } + } + } + public Explosion() { + } + + public final void init(int x, int y) { + _x = x; + _y = y; + _frame = 0; + } + + /** + * Animate the explosion and remove those which are done. + */ + public static final void explode() { + for (explosions.current = explosions.count - 1; explosions.current >= 0;) { + Explosion e = (Explosion)explosions.pool[explosions.current--]; + e._frame++; + if (e._frame >= Explosion.MAX_FRAME) { + explosions.removeCurrent(); + } + } + } + + /** + * Draw all the explosions using the supplied context. + */ + public static final void draw(Graphics g) { + for (int i = 0; i < explosions.count; i++) { + Explosion e = (Explosion)explosions.pool[i]; + int offset = e._frame << 3; + for (int j = 0; j < 8; j++) { + g.drawLine( + e._x + xcoords[j + offset], + e._y + ycoords[j + offset], + e._x + xcoords[j + offset], + e._y + ycoords[j + offset]); + } + } + } + +} diff --git a/tests/asteroids/Field.java b/tests/asteroids/Field.java new file mode 100644 index 00000000..945b3a60 --- /dev/null +++ b/tests/asteroids/Field.java @@ -0,0 +1,469 @@ +/** + * Copyright 2001-2002 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.midlet.*; +import javax.microedition.lcdui.*; +import java.util.*; + +/** + * The game main screen. The class also implements the game's main loop. + * @author Jean-Francois Doue + * @author Pavel Machek + * @version 1.4, 2002/10/14 + */ +public class Field extends Canvas implements Runnable { + /** + * The 'title' game state. Displays the game name and author. + */ + public static final byte TITLE_STATE = 0; + + /** + * The 'control' game state. Displays the mapping between keys and game commands. + */ + public static final byte CONTROL_STATE = 1; + + /** + * The 'points' game state. Displays the value of each screen items. + */ + public static final byte POINTS_STATE = 2; + + /** + * The 'high score' game state. Displays the high score table. + */ + public static final byte HISCORE_STATE = 3; + + /** + * The 'game over' game state. Displays the 'game over' message. + */ + public static final byte GAMEOVER_STATE = 4; + + /** + * The 'new high score' game state. Lets players enter their name. + */ + public static final byte NEWHIGHSCORE_STATE = 5; + + /** + * The 'game' game state. The player is actively playing the game. + */ + public static final byte GAME_STATE = 6; + + /** + * When not in game mode, the game switches its state every TIMEOUT ms + */ + public static final long TIMEOUT = 8000L; + + public static Font smallFont = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL); + public static Font bigFont = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE); + + /** + * Time spent moving objects and computing collisions. + */ + //public static long computeavg; + + /** + * Time spent painting screens. + */ + //public static long paintavg; + + /** + * Number of game frames since beginning. + */ + //public static long frames; + + private byte _state; // The current game state + private Slideshow _nextSlide; // A TimeTask used to trigger the transition to the next state. + private int _lives; // Number of lives left. + private int _nextBonusLife; // Score above which next extra life will be awarded. + private int _score; // Current score. + private byte _level; // Current level. + private boolean _paused; // Set the true by the Game if it wants to enter its paused state + // This causes the main game thread to exit. + private boolean _frozen; // Set when another Displayable is being displayed by the game + // (nothing happens in the game in between, but the game + // must be able to resume). + + private Image _buffer; // To implement double-buffering on platforms which do not have it. + private char[] _scoreString; + private char _liveString; + private char[] _levelString // The current level indicator + = {'L', 'e', 'v', 'e', 'l', ' ', '0'}; + private byte _levelStringTimer; // The number of frames during which the level indicator is displayed + private int _liveStringWidth; // In pixels. + private int _lastKeyPressed; // The last key pressed by the player. + private boolean _isRepeatedKey; // True if _lastKeyPressed was obtained through keyRepeated(). + + public Field() { + //computeavg = 0; + //paintavg = 0; + //frames = 0; + + // Determine the dimensions of the Canvas. + // The game has been developed using the j2mewtk's + // DefaultColorPhone, using a resolution of 100x96. + // Object are sized proportionaly on other platforms + // ratioDenom. + Mobile.width = getWidth(); + Mobile.height = getHeight(); + Mobile.ratioNum = Math.min(Mobile.width, Mobile.height); + + // The score is stored as a char array for greater + // efficiency. + _scoreString = new char[4]; + + // For device which do not support double buffering, + // create an offscreen image for double buffering. + if (!isDoubleBuffered()) { + _buffer = Image.createImage(Mobile.width, Mobile.height); + } + } + + /** + * Starts a new game. + */ + public final void newGame() { + _level = 0; + setLives(4); + _nextBonusLife = 500; + setScore(0); + Asteroid.asteroids.removeAll(); + _lastKeyPressed = 0; + _isRepeatedKey = false; + Ship.ship.reset(); + } + + /** + * Returns the number of remaining ships + */ + public final int getLives() { + return _lives; + } + + /** + * Sets the number of remaining ships + */ + public final void setLives(int lives) { + if (lives < 0) { + lives = 0; + } + _lives = lives; + _liveString = (char)('0' + _lives); + _liveStringWidth = smallFont.charWidth(_liveString); + } + + /** + * Returns the current score. + */ + public final int getScore() { + return _score; + } + + /** + * Updates the score. + */ + public final void setScore(int score) { + _score = score; + + // Convert the score to an array of chars + // of the form: 0000 + Scores.toCharArray(_score, _scoreString); + + // Extra lives are awarded every 500 points. + if (_score >= _nextBonusLife) { + _nextBonusLife += 500; + setLives(_lives + 1); + } + } + + /** + * Initializes a new level. + */ + private final void _nextLevel() { + // At most 7 large asteroids per screen. + if (_level < 5) { + _level++; + } + + // Populate the game level with large asteroids. + for (byte i = 0; i < 3 + _level; i++) { + Asteroid asteroid = (Asteroid)Asteroid.asteroids.addNewObject(); + if (asteroid != null) { + Asteroid.randomInit(asteroid); + } + } + + // Clear all the existing rockets or explosions and reset + // the ship. + Rocket.rockets.removeAll(); + Explosion.explosions.removeAll(); + + if (_level >= 1) { + _levelString[6] = (char)('0' + _level); + _levelStringTimer = 20; + } + } + + /** + * Overriden from Canvas. + */ + protected void paint(Graphics g) { + Graphics gr = (_buffer != null) ? _buffer.getGraphics() : g; + gr.setColor(0x00FFFFFF);; + gr.fillRect(0, 0, getWidth() - 1, getHeight() - 1); + gr.setColor(0x00000000); + gr.drawRect(0, 0, getWidth() - 1, getHeight() - 1); + + // Draw the asteroids + gr.setColor(0x000000FF); + Asteroid.draw(gr); + + // Draw the explosions + gr.setColor(0x00FF00FF); + Explosion.draw(gr); + + // Draw the rockets + gr.setColor(0x00FF0000); + Rocket.draw(gr); + + switch(_state) { + case TITLE_STATE: + Slideshow.drawTitleScreen(gr); + break; + + case CONTROL_STATE: + Slideshow.drawControlScreen(gr); + break; + + case POINTS_STATE: + Slideshow.drawPointsScreen(gr); + break; + + case HISCORE_STATE: + Slideshow.drawHighScoresScreen(gr); + break; + + case GAME_STATE: + gr.setColor(0x00000000); + //gr.drawRect(10, 10, 16, 16); + + gr.setFont(smallFont); + gr.drawChars(_scoreString, 0, 4, 2, getHeight() - 1, Graphics.BOTTOM|Graphics.LEFT); + gr.drawChar(_liveString, Mobile.width - _liveStringWidth - 1, Mobile.height, Graphics.BOTTOM|Graphics.LEFT); + Ship.draw(0, Mobile.width - Ship.radius - 2 - _liveStringWidth, Mobile.height - Ship.radius - 1, gr); + // Display the level indicator (only during the first few frames of the level) + if (_levelStringTimer > 0) { + _levelStringTimer--; + int x = (Mobile.width - Field.smallFont.charsWidth(_levelString, 0, _levelString.length)) >> 1; + int y = ((Mobile.height) >> 1) + Field.smallFont.getHeight(); + gr.drawChars(_levelString, 0, _levelString.length, x, y, Graphics.BOTTOM|Graphics.LEFT); + } + Ship.ship.draw(gr); + break; + + case GAMEOVER_STATE: + Slideshow.drawGameOverScreen(gr); + break; + } + if (_buffer != null) { + g.drawImage(_buffer, 0, 0, Graphics.TOP|Graphics.LEFT); + } + } + + /** + * Overriden from Canvas. + */ + protected void keyPressed(int keyCode) { + _lastKeyPressed = keyCode; + } + + protected void keyRepeated(int keyCode) { + _lastKeyPressed = keyCode; + _isRepeatedKey = true; + } + + /** + * Returns the current state. + */ + public final byte getState() { + return _state; + } + + /** + * Triggers a transition from one state of the game automaton + * to the other. + */ + public final void setState(byte newState) { + switch (newState) { + case TITLE_STATE: + _nextSlide = new Slideshow(CONTROL_STATE); + Game.timer.schedule(_nextSlide, TIMEOUT); + break; + case CONTROL_STATE: + _nextSlide = new Slideshow(POINTS_STATE); + Game.timer.schedule(_nextSlide, TIMEOUT); + break; + case POINTS_STATE: + _nextSlide = new Slideshow(HISCORE_STATE); + Game.timer.schedule(_nextSlide, TIMEOUT); + break; + case HISCORE_STATE: + _nextSlide = new Slideshow(TITLE_STATE); + Game.timer.schedule(_nextSlide, TIMEOUT); + break; + case GAMEOVER_STATE: + if (Game.scores.isHighScore(_score)) { + _nextSlide = new Slideshow(NEWHIGHSCORE_STATE); + Game.timer.schedule(_nextSlide, TIMEOUT / 2); + } else { + _nextSlide = new Slideshow(HISCORE_STATE); + Game.timer.schedule(_nextSlide, TIMEOUT); + } + break; + case NEWHIGHSCORE_STATE: + setFrozen(true); + if (Game.display.getCurrent() != Game.scoreForm) { + Game.enterHighScore(); + } + break; + case GAME_STATE: + if (_nextSlide != null) { + _nextSlide.cancel(); + _nextSlide = null; + } + break; + } + _state = newState; + } + + /** + * Asks the game's main thread to exit. + */ + public final void pause() { + _paused = true; + } + + /** + * + */ + public final void setFrozen(boolean frozen) { + _frozen = frozen; + if (_frozen) { + if (_nextSlide != null) { + _nextSlide.cancel(); + _nextSlide = null; + } + } + } + + /** + * Executes the game's main loop. + */ + public void run() { + try { + while (!_paused) { + long time1 = System.currentTimeMillis(); + //long time2; + long time3; + long ellapsed = 0; + if (!_frozen) { + + // If the field has been cleared, change the level. + if (Asteroid.asteroids.size() == 0) { + _nextLevel(); + } + + // Animate the explosion and remove those which are done. + Explosion.explode(); + + // Move the asteroids. + Asteroid.move(); + + // Move the rockets and remove those which have expired. + Rocket.move(); + + + // Handle user events + // Move the ship (only while the game is actually proceeding) + if (_state == GAME_STATE) { + if (_lastKeyPressed != 0) { + int gameAction = getGameAction(_lastKeyPressed); + if (gameAction != 0) { + switch(gameAction) { + case LEFT: + Ship.ship.rotate(-2); + break; + case RIGHT: + Ship.ship.rotate(2); + break; + case FIRE: + Ship.ship.shoot(Rocket.rockets); + break; + case GAME_A: + case UP: + Ship.ship.burn(); + break; + case GAME_B: + if (!_isRepeatedKey) { + Ship.ship.teleport(); + } + break; + } + } + _lastKeyPressed = 0; + _isRepeatedKey = false; + } + Ship.ship.move(); + } + + // Compute collisions between the asteroids and + // the ship and the rockets. + Asteroid.collisionDetection(); + + // Detect game over + if ((_state == GAME_STATE) && (_lives <= 0) && (Ship.ship.isAlive)) { + setState(GAMEOVER_STATE); + } + + // Determine the time spent to compute the frame. + //time2 = System.currentTimeMillis(); + //computeavg += (time2 - time1); + + // Force a screen refresh. + repaint(); + serviceRepaints(); + + // Determine the time spent to draw the frame. + time3 = System.currentTimeMillis(); + //paintavg += (time3 - time2); + //frames++; + + // Determine the total time for the frame. + ellapsed = time3 - time1; + } + // Sleep for a while (at least 20ms) + try { + Thread.currentThread().sleep(Math.max(50 - ellapsed, 20)); + } catch(java.lang.InterruptedException e) { + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + } +} diff --git a/tests/asteroids/Game.java b/tests/asteroids/Game.java new file mode 100644 index 00000000..d8563e4f --- /dev/null +++ b/tests/asteroids/Game.java @@ -0,0 +1,224 @@ +/** + * Copyright 2001-2002 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.midlet.*; +import javax.microedition.lcdui.*; +import java.util.*; + +/** + * The MIDlet's main class. Handles the game lifecycle and owns + * the various game screens and commands. + * @author Jean-Francois Doue + * @version 1.4, 2002/10/14 + */ +public class Game extends MIDlet implements CommandListener { + /** + * The random number generator used throughout the + * game + */ + public static Random random = new Random(); + + /** + * A timer used to schedule the transitions between + * the various game screens. + */ + public static Timer timer; + + /** + * The game's main screen, where all the drawing occurs. + */ + public static Field field; + + /** + * A form used to get the player's initials for high scores. + */ + public static Form scoreForm; + + /** + * A form used to display license info. + */ + public static Form licenseForm; + + /** + * The high score database. + */ + public static Scores scores; + + /** + * The MIDlet display. + */ + public static Display display; + private static Command _exitCommand; + private static Command _startCommand; + private static Command _licenseCommand; + private static Command _scoreOkCommand; + private static Command _licenseOkCommand; + private static TextField _scoreField; + private static Displayable _currentDisplayable; + + /** + * Constructor invoked by the application management software. + * Do not obfuscate. + */ + public Game() { + + // Retrieve the display. + display = Display.getDisplay(this); + + // Create a form which will be used to display + // licensing info. + licenseForm = new Form("License"); + licenseForm.append("This game is free software licensed under the GNU General Public License. For details, see http://jfdoue.free.fr"); + _licenseOkCommand = new Command("Ok", Command.SCREEN, 1); + licenseForm.addCommand(_licenseOkCommand); + licenseForm.setCommandListener(this); + + // Instantiate the main game canvas. + // Add two commands to the canvas, one to start the game + // and one to exit the MIDlet + field = new Field(); + _exitCommand = new Command("Exit", Command.EXIT, 1); + _startCommand = new Command("Start", Command.SCREEN, 1); + _licenseCommand = new Command("License", Command.SCREEN, 3); + field.addCommand(_exitCommand); + field.addCommand(_startCommand); + field.addCommand(_licenseCommand); + field.setCommandListener(this); + _currentDisplayable = field; + + // Create a form which will be used to enter the + // user name for high scores. + scoreForm = new Form("Congratulations"); + _scoreField = new TextField("Your score is one of the 3 best. Enter your initials", "", 3, TextField.ANY); + scoreForm.append(_scoreField); + _scoreOkCommand = new Command("Done", Command.SCREEN, 1); + scoreForm.addCommand(_scoreOkCommand); + scoreForm.setCommandListener(this); + + // Read the high scores from persistent storage. + scores = new Scores(); + + // Initialize the random number generator. + random = new Random(); + } + + /** + * Method used to switch screens. + */ + public static void setDisplayable(Displayable displayable) { + _currentDisplayable = displayable; + display.setCurrent(displayable); + } + + /** + * Invoked by Field to ask the game to switch to + * the high score form. + */ + public static void enterHighScore() { + _scoreField.setString(""); + setDisplayable(scoreForm); + } + + // Methods overwritten from MIDlet. These methods are + // invoked by the application management software + // to ask to MIDlet to enter the desired state. + + /** + * Overriden from MIDlet. + */ + protected void startApp() { + //System.out.println("startApp"); + + // The game has two application threads + // + One timer thread to trigger a switch between + // the different game screens while not in game mode. + // + A thread to run the main game loop. + timer = new Timer(); + setDisplayable(_currentDisplayable); + Thread thread = new Thread(field); + thread.start(); + field.setState(field.getState()); + } + + /** + * Overriden from MIDlet. + */ + protected void pauseApp() { + //System.out.println("pauseApp"); + + // Kill the two game threads. + field.pause(); + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + /** + * Overriden from MIDlet. + */ + protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { + + field.pause(); + timer.cancel(); + timer = null; + random = null; + field = null; + scores = null; + display = null; + _exitCommand = null; + _startCommand = null; + _scoreOkCommand = null; + scoreForm = null; + licenseForm = null; + _scoreField = null; + _currentDisplayable = null; + //System.out.println("computeavg = " + (Field.computeavg / Field.frames)); + //System.out.println("paintavg = " + (Field.paintavg / Field.frames)); + } + + // Implementation of the CommandListener interface + /** + * CommandListener interface implementation. + */ + public void commandAction(Command c, Displayable d) { + if (c == _exitCommand) { + try { + destroyApp(false); + } catch(MIDletStateChangeException e) { + } + notifyDestroyed(); + } else if (c == _startCommand) { + field.setState(Field.GAME_STATE); + field.newGame(); + } else if (c == _scoreOkCommand) { + scores.addHighScore(field.getScore(), _scoreField.getString()); + field.setState(Field.HISCORE_STATE); + setDisplayable(field); + field.setFrozen(false); + } else if (c == _licenseCommand) { + field.setFrozen(true); + setDisplayable(licenseForm); + } else if (c == _licenseOkCommand) { + setDisplayable(field); + field.setState(field.getState()); + field.setFrozen(false); + } + } +} diff --git a/tests/asteroids/Geometry.java b/tests/asteroids/Geometry.java new file mode 100644 index 00000000..f34a8eb8 --- /dev/null +++ b/tests/asteroids/Geometry.java @@ -0,0 +1,190 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +/** + * Class to group various geometric functions. + * @author Jean-Francois Doue + * @version 1.4, 2002/10/14 + */ + +public class Geometry extends Object { + + /** + * Scan converts a polygon. + * @param pixels + * The resulting scan-converted polygon as a 2D boolean array + * @param xcoords + * The X-coordinates of the polygon vertices. The vertex are ordered clockwise, + * and the last vertex equals the first vertex. + * @param ycoords + * The Y-coordinates of the polygon vertices. The vertex are ordered clockwise, + * and the last vertex equals the first vertex. + */ + + public static void scanConvertPolygon(boolean[][] pixels, byte[] xcoords, byte[] ycoords) { + // Degenerate cases + if (pixels.length == 0) { + return; + } + if (pixels.length == 1) { + pixels[0][0] = true; + return; + } + + // Index of the polygon segment being scan converted. + int top = 0, bottom = xcoords.length - 1; + + // Parameters used to adapt the Bresenham algorithm. + // to the 8 possible octants. (top and bottom lines) + boolean isSteepT = false, isSteepB = false; + int incrYT = 0, incrYB = 0; + + // Decision variable of the Bresenham algorithm. + // If d <= 0, then the next point ought to be E + // If d > 0, then the next point ought to be NE + int dT = 0, dB = 0; + + // Incremental d of the Bresenham algorithm when E is chosen. + int incrET = 0, incrEB = 0; + + // Incremental d of the Bresenham algorithm when NE is chosen. + int incrNET = 0, incrNEB = 0; + + // The x coordinate of the current line point. + int x = xcoords[top]; + + // The y coordinate of the current line point (top and bottom lines). + int yT = 0, yB = 0; + + // Translation + int tx = pixels.length >> 1; + + + // Scan convert the top and bottom border of the polygon. + // The scan conversion is done once the lines cross. + while (top <= bottom) { + + // Another segment of the top border has been reached. + if (x == xcoords[top]) { + while (xcoords[top + 1] == xcoords[top] && top < xcoords.length) { + top++; + } + int dxT = xcoords[top + 1] - xcoords[top]; + int dyT = ycoords[top + 1] - ycoords[top]; + yT = ycoords[top]; + top++; + incrYT = (dyT >= 0) ? 1 : -1; + int absDxT = Math.abs(dxT); + int absDyT = Math.abs(dyT); + isSteepT = (absDxT <= absDyT); + if (isSteepT) { + dT = (absDxT << 1) - absDyT; + incrET = absDxT << 1; + incrNET = (absDxT - absDyT ) << 1; + } else { + dT = (absDyT << 1) - absDxT; + incrET = absDyT << 1; + incrNET = (absDyT - absDxT ) << 1; + } + } else { + // Compute the Y coordinate of the next top border point. + if (isSteepT) { + if (dT <= 0) { + while (dT <= 0) { + yT += incrYT; + dT += incrET; + } + } + yT += incrYT; + dT += incrNET; + } else { + if (dT <= 0) { + dT += incrET; + } else { + yT += incrYT; + dT += incrNET; + } + } + } + + // Another segment of the bottom border has been reached. + if (x == xcoords[bottom]) { + while (xcoords[bottom - 1] == xcoords[bottom] && bottom >= 0) { + bottom--; + } + int dxB = xcoords[bottom - 1] - xcoords[bottom]; + int dyB = ycoords[bottom - 1] - ycoords[bottom]; + yB = ycoords[bottom]; + bottom--; + incrYB = (dyB >= 0) ? 1 : -1; + int absDxB = Math.abs(dxB); + int absDyB = Math.abs(dyB); + isSteepB = (absDxB <= absDyB); + if (isSteepB) { + dB = (absDxB << 1) - absDyB; + incrEB = absDxB << 1; + incrNEB = (absDxB - absDyB ) << 1; + } else { + dB = (absDyB << 1) - absDxB; + incrEB = absDyB << 1; + incrNEB = (absDyB - absDxB ) << 1; + } + } else { + // Compute the Y coordinate of the next bottom border point. + if (isSteepB) { + if (dB <= 0) { + while (dB <= 0) { + yB += incrYB; + dB += incrEB; + } + } + yB += incrYB; + dB += incrNEB; + } else { + if (dB <= 0) { + dB += incrEB; + } else { + yB += incrYB; + dB += incrNEB; + } + } + } + + // Fill the line between the top and the bottom border points. + int min = (yB <= yT) ? yB : yT; + int max = (yB > yT) ? yB : yT; + for (int i = min; i <= max; i++) { + pixels[pixels.length - 1 - i - tx][x + tx] = true; + } + x--; + } + /* + for (int i = 0; i < pixels.length; i++) { + for (int j = pixels[i].length - 1; j >= 0; j--) { + if (pixels[i][j]) { + System.out.print("#"); + } else { + System.out.print("."); + } + } + System.out.println(); + } + */ + } +} diff --git a/tests/asteroids/Mobile.java b/tests/asteroids/Mobile.java new file mode 100644 index 00000000..45743d1f --- /dev/null +++ b/tests/asteroids/Mobile.java @@ -0,0 +1,108 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +/** + * The base class for all moving objects on screen. + * Provides pseudo-floating point capabilities. + * @author Jean-Francois Doue + * @version 1.3, 2001/10/26 + */ +public abstract class Mobile extends Object { + /** + * A cosine table. + */ + public static final int[] cos = { 64, + 62, 59, 53, 45, 35, 24, 12, 0, -12, + -24, -35, -45, -53, -59, -62, -64, + -62, -59, -53, -45, -35, -24, -12, + 0, 12, 24, 35, 45, 53, 59, 62 }; + + /** + * A sine table. + */ + public static final int[] sin = { 0, + 12, 24, 35, 45, 53, 59, 62, 64, 62, + 59, 53, 45, 35, 24, 12, 0, -12, + -24, -35, -45, -53, -59, -62, -64, + -62, -59, -53, -45, -35, -24, -12 + }; + /** + * Graphics are scaled to keep the same + * aspect as on the original development platform. + */ + + public static int ratioNum; + /** + * The screen width of the original development platform. + */ + public static final int ratioDenom = 96; + + /** + * Screen width + */ + public static int width; + + /** + * Screen height + */ + public static int height; + + /** + * The screen coordinates (in pixels) of the mobile. + */ + public int x, y; + + /** + * The velocity of the mobile (in pseudo floating point units.) + */ + public int vx, vy; + + /** + * The screen coordinates (in pseudo floating point units) of the mobile. + */ + protected int _x, _y; + + /** + * The previous screen coordinates (in pixels) of the mobile. + */ + public int xold, yold; + + public Mobile() { + } + + /** + * Move the mobile to a specific screen location. + */ + public void moveTo(int x, int y) { + this.x = x; + this.y = y; + this._x = x << 8; + this._y = y << 8; + this.xold = x; + this.yold = y; + } + + /** + * Alters the velocity of the mobile. + */ + public void setVelocity(int vx, int vy) { + this.vx = vx; + this.vy = vy; + } +} diff --git a/tests/asteroids/Pool.java b/tests/asteroids/Pool.java new file mode 100644 index 00000000..969a7b82 --- /dev/null +++ b/tests/asteroids/Pool.java @@ -0,0 +1,105 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +/** + * Class to implement efficient, pool based collections. + * The class supports an iterator method for collection traversal, + * and methods to add and remove elements. The collection size + * cannot grow beyond the size of the initial pool of objects + * provided for the collection. + * @author Jean-Francois Doue + * @version 1.1, 2001/10/02 + */ +public class Pool extends Object { + /** + * An object array the pool where objects are actually stored. + */ + public Object[] pool; + /** + * The number of objects currently in the pool. + */ + public int count; + /** + * The index of the current object. + */ + public int current; + + /** + * Initializes the collection with a pool of instances. + */ + public Pool(Object[] pool) { + this.pool = pool; + } + + /** + * Resets the collection iterator. + */ + public final void reset() { + current = count - 1; + } + + /** + * Returns the next object in the collection. + */ + public final Object next() { + if (current >= 0) { + return pool[current--]; + } + return null; + } + + /** + * Removes the current object from the collection. + */ + public final void removeCurrent() { + if (current + 1 < count - 1) { + Object tmp = pool[current + 1]; + pool[current + 1] = pool[count - 1]; + pool[count - 1] = tmp; + } + count--; + } + + /** + * Adds a new object to the collection. The + * returned object must be initialized by the callee. + * Returns null if the collection capacity has been + * exceeded. + */ + public final Object addNewObject() { + if (count >= pool.length) { + return null; + } + return pool[count++]; + } + + /** + * Removes all the objects from the collection. + */ + public final void removeAll() { + count = 0; + } + + /** + * Returns the size of the collection. + */ + public final int size() { + return count; + } +} diff --git a/tests/asteroids/Rocket.java b/tests/asteroids/Rocket.java new file mode 100644 index 00000000..ea8dc5f4 --- /dev/null +++ b/tests/asteroids/Rocket.java @@ -0,0 +1,120 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.lcdui.*; + +/** + * Class to implement a rocket shot by the player's spaceship. + * @author Jean-Francois Doue + * @version 1.4, 2002/10/14 + */ +public class Rocket extends Mobile { + /** + * The orientation of the rocket. + */ + public byte angle; + + /** + * The rockets currently existing in the game + */ + public static Pool rockets; + + private int _rangex, _rangey; // The distance travelled by the rocket. + + static { + // Create and populate the rocket pool. + Rocket[] array = new Rocket[10]; + for (int i = array.length - 1; i >= 0; i--) { + array[i] = new Rocket(); + } + rockets = new Pool(array); + } + + public Rocket() { + } + + /** + * Initializes a Rocket instance by setting its position + * and angle. + */ + public final void init(int x, int y, byte angle, int vx, int vy) { + moveTo(x, y); + setVelocity((cos[angle] << 4) + vx, (sin[angle] << 4) + vy); + this.angle = angle; + _rangex = 0; + _rangey = 0; + } + + /** + * Move the rockets and remove those which have expired. + */ + public static final void move() { + for (rockets.current = rockets.count - 1; rockets.current >= 0;) { + Rocket r = (Rocket)rockets.pool[rockets.current--]; + + int rangex = r._rangex >> 8; + int rangey = r._rangey >> 8; + int maxRange = (((width <= height) ? width : height) * 8) / 10; + + // Determines if the rocket has travelled its maximum distance + // (80% of the screens smallest dimension). + if ((rangex * rangex + rangey * rangey) > maxRange * maxRange) { + rockets.removeCurrent(); + } else { + + r.xold = r.x; + r.yold = r.y; + r._x += r.vx; + r._y += r.vy; + r.x = r._x >> 8; + r.y = r._y >> 8; + + // If a border has been hit, wrap the trajectory around + // the screen. The new origin is the projection of the + // intersection point on the opposite border. + if (r.x <= 0) { + r.moveTo(width - 2, r.y); + } else if (r.x >= width - 1) { + r.moveTo(1, r.y); + } else if (r.y <= 0) { + r.moveTo(r.x, height - 2); + } else if (r.y >= height - 1) { + r.moveTo(r.x, 1); + } + + // Upgrade the range travelled. + r._rangex += r.vx; + r._rangey += r.vy; + + } + } + } + + + /** + * Draws all the rockets of the supplied object pool using the + * specified graphic context. + */ + public static final void draw(Graphics g) { + for (int i = 0; i < rockets.count; i++) { + Rocket r = (Rocket)rockets.pool[i]; + g.drawLine(r.x, r.y, r.x, r.y); + } + } +} diff --git a/tests/asteroids/Scores.java b/tests/asteroids/Scores.java new file mode 100644 index 00000000..972c1700 --- /dev/null +++ b/tests/asteroids/Scores.java @@ -0,0 +1,153 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.rms.*; +import java.io.*; + +/** + * Class used for storing game scores. + * @author Jean-Francois Doue + * @version 1.0, 2001/07/26 + */ +public class Scores { + /** + * An array of player names. + */ + + public String[] names = {"LAU", "RAG", "NES"}; + /** + * An array of player high scores. + */ + public int[] values = {980, 675, 172}; + //public int[] values = {30, 20, 10}; + + /** + * Utility method to convert a integer score to a character + * array. + */ + public static void toCharArray(int score, char[] charArray) { + // Convert the score to an array of chars + // of the form: 0000 + charArray[0] = (char)('0' + (score / 1000)); + score = score % 1000; + charArray[1] = (char)('0' + (score / 100)); + score = score % 100; + charArray[2] = (char)('0' + (score / 10)); + score = score % 10; + charArray[3] = (char)('0' + score); + } + + public Scores() { + + // Initialize / read scores from persistent storage. + RecordStore recordStore = null; + try { + recordStore = RecordStore.openRecordStore("scores", true); + + // If the record store exists and contains records, + // read the high scores. + if (recordStore.getNumRecords() > 0) { + for (int i = 0; i < names.length; i++) { + byte[] record = recordStore.getRecord(i + 1); + DataInputStream istream = new DataInputStream(new ByteArrayInputStream(record, 0, record.length)); + values[i] = istream.readInt(); + names[i] = istream.readUTF(); + } + } else { + // Otherwise, create the records and initialize them + // with the default values. They will have record IDs + // 1, 2, 3 + for (int i = 0; i < names.length; i++) { + ByteArrayOutputStream bstream = new ByteArrayOutputStream(12); + DataOutputStream ostream = new DataOutputStream(bstream); + ostream.writeInt(values[i]); + ostream.writeUTF(names[i]); + ostream.flush(); + ostream.close(); + byte[] record = bstream.toByteArray(); + recordStore.addRecord(record, 0, record.length); + } + } + } catch(Exception e) { + } finally { + if (recordStore != null) { + try { + recordStore.closeRecordStore(); + } catch(Exception e) { + } + } + } + } + + /** + * Returns true if the score is among the high scores. + */ + public boolean isHighScore(int score) { + for (int i = 0; i < names.length; i++) { + if (score >= values[i]) { + return true; + } + } + return false; + } + + /** + * Updates the high score database with the supplied name and score. + */ + public void addHighScore(int score, String name) { + for (int i = 0; i < names.length; i++) { + if (score >= values[i]) { + // Shift the score table. + for (int j = names.length - 1; j > i; j--) { + values[j] = values[j - 1]; + names[j] = names[j - 1]; + } + + // Insert the new score. + values[i] = score; + names[i] = name; + + // Overwrite the scores in persistent storage. + RecordStore recordStore = null; + try { + recordStore = RecordStore.openRecordStore("scores", true); + for (int j = 0; j < names.length; j++) { + ByteArrayOutputStream bstream = new ByteArrayOutputStream(12); + DataOutputStream ostream = new DataOutputStream(bstream); + ostream.writeInt(values[j]); + ostream.writeUTF(names[j]); + ostream.flush(); + ostream.close(); + byte[] record = bstream.toByteArray(); + recordStore.setRecord(j + 1, record, 0, record.length); + } + } catch(Exception e) { + } finally { + if (recordStore != null) { + try { + recordStore.closeRecordStore(); + } catch(Exception e) { + } + } + } + break; + } + } + } +} diff --git a/tests/asteroids/Ship.java b/tests/asteroids/Ship.java new file mode 100644 index 00000000..1d687675 --- /dev/null +++ b/tests/asteroids/Ship.java @@ -0,0 +1,235 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import javax.microedition.lcdui.*; +import java.util.*; + +/** + * Class to implement the player's spaceship. The spaceship + * can rotate, move, shoot and teleport itself. + * @author Jean-Francois Doue + * @version 1.4, 2002/10/14 + */ +public class Ship extends Mobile { + /** + * False if the ship has been shot. + */ + public boolean isAlive; + + /** + * The orientation of the ship. + */ + public byte angle; + + /** + * The X coordinates of an array of points representing the ship + * at various orientations. + */ + public static byte[] xcoords; + + /** + * The Y coordinates of an array of points representing the ship + * at various orientations. + */ + public static byte[] ycoords; + + /** + * The size of the ship (in pixels). + */ + public static byte radius; + + /** + * The ship singleton + */ + public static Ship ship; + + + private byte _explosionFrame; + private byte _latency; // To limit the ship's firing power + + static { + // Instantiate the ship. + ship = new Ship(); + + final byte[] pointx = {5,-3, 0,-3}; + final byte[] pointy = {0, 4, 0,-4}; + + radius = (byte)(5 * ratioNum / ratioDenom); + xcoords = new byte[128]; + ycoords = new byte[128]; + for (int i = 0; i < 32; i++) { + int offset = i << 2; + for (int j = 0; j < 4; j++) { + xcoords[offset + j] = (byte)(((pointx[j] * cos[i] - pointy[j] * sin[i]) * ratioNum / ratioDenom) >> 6); + ycoords[offset + j] = (byte)(((pointx[j] * sin[i] + pointy[j] * cos[i]) * ratioNum / ratioDenom) >> 6); + } + } + } + + public Ship() { + } + + /** + * Positions the ship at its original location, orientation + * and velocity. + */ + public final void reset() { + angle = 0; + moveTo(Mobile.width >> 1, Mobile.height >> 1); + vx = 0; + vy = 0; + isAlive = true; + rotate(0); + } + + /** + * Called when the ship collides with an asteroid. + */ + public final void explode() { + isAlive = false; + _explosionFrame = 0; + } + + /** + * Move the ship to its next position. + */ + public final void move() { + if (isAlive) { + xold = x; + yold = y; + _x += vx; + _y += vy; + x = _x >> 8; + y = _y >> 8; + + // If a border has been hit, wrap the trajectory around + // the screen. The new origin is the projection of the + // intersection point on the opposite border. + if (x <= 0) { + moveTo(width - 2, y); + } else if (x >= width - 1) { + moveTo(1, y); + } else if (y <= 0) { + moveTo(x, height - 2); + } else if (y >= height - 1) { + moveTo(x, 1); + } + + if (_latency > 0) { + _latency--; + } + } else { + _explosionFrame++; + if (_explosionFrame > 20) { + reset(); + } + } + } + + /** + * Changes the orientation of the ship. + */ + public final void rotate(int direction) { + if (isAlive) { + angle += direction; + if (angle > 31) { + angle -= 32; + } else if (angle < 0) { + angle += 32; + } + } + } + + /** + * Adds velocity to the ship. + */ + public final void burn() { + if (isAlive) { + int newvx = vx + (cos[angle] << 1); + int newvy = vy + (sin[angle] << 1); + // clamp the ship velocity. + if (newvx * newvx + newvy * newvy < 262144) { + vx = newvx; + vy = newvy; + } + } + } + + /** + * Shoots a rocket. + */ + public final Rocket shoot(Pool rockets) { + if (isAlive) { + if (_latency == 0) { + Rocket rocket = (Rocket)rockets.addNewObject(); + if (rocket != null) { + int offset = angle << 2; + rocket.init(xcoords[offset] + x, ycoords[offset] + y, angle, vx, vy); + + // Prevent the ship for shooting again for 3 frames + _latency = 3; + } + } + } + return null; + } + + /** + * Teleports the ship to another lcoation. + */ + public final void teleport() { + if (isAlive) { + moveTo(Math.abs(Game.random.nextInt()) % width, Math.abs(Game.random.nextInt()) % height); + } + } + + /** + * Draws the ship at the specified location and orientation. + */ + public static void draw(int orientation, int xpos, int ypos, Graphics g) { + int offset = orientation << 2; + g.drawLine(xcoords[offset] + xpos, ycoords[offset] + ypos, + xcoords[offset + 1] + xpos, ycoords[offset + 1] + ypos); + g.drawLine(xcoords[offset + 1] + xpos, ycoords[offset + 1] + ypos, + xcoords[offset + 2] + xpos, ycoords[offset + 2] + ypos); + g.drawLine(xcoords[offset + 2] + xpos, ycoords[offset + 2] + ypos, + xcoords[offset + 3] + xpos, ycoords[offset + 3] + ypos); + g.drawLine(xcoords[offset + 3] + xpos, ycoords[offset + 3] + ypos, + xcoords[offset + 0] + xpos, ycoords[offset + 0] + ypos); + } + + /** + * Draws the ship in the specified graphic context. + */ + public final void draw(Graphics g) { + if (isAlive) { + draw(angle, x, y, g); + } else { + if (_explosionFrame < 6) { + g.setColor(255, 0, 0); + int radius = _explosionFrame; + g.drawArc(x - radius, y - radius, radius << 1, radius << 1, 0, 360); + radius = _explosionFrame + 2; + g.drawArc(x - radius, y - radius, radius << 1, radius << 1, 0, 360); + radius = _explosionFrame + 4; + g.drawArc(x - radius, y - radius, radius << 1, radius << 1, 0, 360); + } + } + } +} diff --git a/tests/asteroids/Slideshow.java b/tests/asteroids/Slideshow.java new file mode 100644 index 00000000..13dd8fae --- /dev/null +++ b/tests/asteroids/Slideshow.java @@ -0,0 +1,187 @@ +/** + * Copyright 2001 Jean-Francois Doue + * + * This file is part of Asteroid Zone. Asteroid Zone 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 2 of the License, or (at your option) any later version. + * Asteroid Zone 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 Asteroid Zone; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA + */ + +package asteroids; + +import java.util.*; +import javax.microedition.lcdui.*; + +/** + * Class to manage the game presentation screens. + * @author Jean-Francois Doue + * @version 1.2, 2001/10/24 + */ +public class Slideshow extends TimerTask { + private byte _nextState; + private static int _smallFontHeight; + private static int _bigFontHeight; + private static final String _asteroid = "ASTEROID"; + private static final String _field = "ZONE"; + private static final String _byjfd = "by J.F. Doue"; + private static final String _version = "v1.4 - 13 Oct 2002"; + private static final String _gameControls = "Game Controls"; + private static final String _points = "Points"; + private static final String _hiScores = "High Scores"; + private static final String _gameOver = "Game Over"; + private static final String[] _commands = {"Rotate:", "Shoot:", "Thrust:", "Teleport:"}; + private static final String[] _keys = {"Left/Right", "Fire", "Up or A", "B"}; + static { + _smallFontHeight = Field.smallFont.getHeight();; + _bigFontHeight = Field.bigFont.getHeight(); + } + + public Slideshow(byte nextState) { + _nextState = nextState; + } + + /** + * Overriden from TimerTask. Triggers the transition to the next + * game state. + */ + public void run() { + Game.field.setState(_nextState); + } + + + /** + * Draws the title screen. + */ + public static void drawTitleScreen(Graphics g) { + g.setColor(0x00000000); + g.setFont(Field.bigFont); + int x = (Mobile.width - Field.bigFont.stringWidth(_asteroid)) >> 1; + int y = ((Mobile.height - ((_bigFontHeight + _smallFontHeight) << 1)) >> 1) + _bigFontHeight; + g.drawString(_asteroid, x, y, Graphics.BOTTOM|Graphics.LEFT); + x = (Mobile.width - Field.bigFont.stringWidth(_field)) >> 1; + g.drawString(_field, x, y + _bigFontHeight, Graphics.BOTTOM|Graphics.LEFT); + + g.setColor(0x008080FF); + x = ((Mobile.width - Field.bigFont.stringWidth(_asteroid)) >> 1) - 1; + y += 1; + g.drawString(_asteroid, x, y, Graphics.BOTTOM|Graphics.LEFT); + x = ((Mobile.width - Field.bigFont.stringWidth(_field)) >> 1) - 1; + y += _bigFontHeight; + g.drawString(_field, x, y, Graphics.BOTTOM|Graphics.LEFT); + + y += _smallFontHeight; + g.setColor(0x00000000); + g.setFont(Field.smallFont); + x = (Mobile.width - Field.smallFont.stringWidth(_byjfd)) >> 1; + g.drawString(_byjfd, x, y, Graphics.BOTTOM|Graphics.LEFT); + y += _smallFontHeight; + x = (Mobile.width - Field.smallFont.stringWidth(_version)) >> 1; + g.drawString(_version, x, y, Graphics.BOTTOM|Graphics.LEFT); + } + + /** + * Draws the control screen. + */ + public static void drawControlScreen(Graphics g) { + // Draw the "Game control" string + g.setFont(Field.smallFont); + g.setColor(0x00000000); + int x = (Mobile.width - Field.smallFont.stringWidth(_gameControls)) >> 1; + int y0 = ((Mobile.height - _smallFontHeight * 5) >> 1) + _smallFontHeight; + int y = y0; + g.drawString(_gameControls, x, y, Graphics.BOTTOM|Graphics.LEFT); + + // Draw the command names + x = (Mobile.width >> 1) - 2; + for (int i = 0; i < 4; i++) { + y += _smallFontHeight; + g.drawString(_commands[i], x, y, Graphics.BOTTOM|Graphics.RIGHT); + } + + // Draw the command keys + g.setColor(0x00FF0000); + y = y0; + x = (Mobile.width >> 1) + 2; + for (int i = 0; i < 4; i++) { + y += _smallFontHeight; + g.drawString(_keys[i], x, y, Graphics.BOTTOM|Graphics.LEFT); + } + } + + /** + * Draws the points screen. + */ + public static void drawPointsScreen(Graphics g) { + g.setFont(Field.smallFont); + g.setColor(0x00000000); + int x = (Mobile.width - Field.smallFont.stringWidth(_points)) >> 1; + int y = ((Mobile.height - 6 * Asteroid.radii[Asteroid.SIZE_LARGE] - _smallFontHeight) >> 1) + _smallFontHeight; + g.drawString(_points, x, y, Graphics.BOTTOM|Graphics.LEFT); + x = Mobile.width >> 2; + y += Asteroid.radii[Asteroid.SIZE_LARGE]; + Asteroid.draw(Asteroid.SIZE_SMALL, x, y, g); + int textx = Mobile.width >> 1; + int texty = y + (_smallFontHeight >> 1); + g.drawString("5 pts", textx, texty, Graphics.BOTTOM|Graphics.LEFT); + y += (Asteroid.radii[Asteroid.SIZE_LARGE] << 1); + Asteroid.draw(Asteroid.SIZE_MEDIUM, x, y, g); + texty = y + (_smallFontHeight >> 1); + g.drawString("2 pts", textx, texty, Graphics.BOTTOM|Graphics.LEFT); + y += (Asteroid.radii[Asteroid.SIZE_LARGE] << 1); + Asteroid.draw(Asteroid.SIZE_LARGE, x, y, g); + texty = y + (_smallFontHeight >> 1); + g.drawString("1 pt", textx, texty, Graphics.BOTTOM|Graphics.LEFT); + + } + + /** + * Draws the game over screen. + */ + public static void drawGameOverScreen(Graphics g) { + // Draw the "Game over" string + g.setFont(Field.smallFont); + g.setColor(0x00000000); + int x = (Mobile.width - Field.smallFont.stringWidth(_gameOver)) >> 1; + int y = (Mobile.height + _smallFontHeight) >> 1; + g.drawString(_gameOver, x, y, Graphics.BOTTOM|Graphics.LEFT); + } + + /** + * Draws the high scores screen. + */ + public static void drawHighScoresScreen(Graphics g) { + // Draw the "High score" string + g.setFont(Field.smallFont); + g.setColor(0x00000000); + int x = (Mobile.width - Field.smallFont.stringWidth(_hiScores)) >> 1; + int y0 = ((Mobile.height - (_smallFontHeight << 2)) >> 1) + _smallFontHeight; + int y = y0; + g.drawString(_hiScores, x, y, Graphics.BOTTOM|Graphics.LEFT); + + // Draw the best player names + x = (Mobile.width >> 1) - 2; + for (int i = 0; i < 3; i++) { + y += _smallFontHeight; + g.drawString(Game.scores.names[i], x, y, Graphics.BOTTOM|Graphics.RIGHT); + } + + // Draw the best scores. + g.setColor(0x00FF0000); + y = y0; + x = (Mobile.width >> 1) + 2; + char[] scoreString = new char[4]; + for (int i = 0; i < 3; i++) { + y += _smallFontHeight; + Scores.toCharArray(Game.scores.values[i], scoreString); + g.drawChars(scoreString, 0, 4, x, y, Graphics.BOTTOM|Graphics.LEFT); + } + } +}