diff options
Diffstat (limited to 'docs/examples/spacewar')
23 files changed, 2366 insertions, 0 deletions
diff --git a/docs/examples/spacewar/Bullet.java b/docs/examples/spacewar/Bullet.java new file mode 100644 index 000000000..6581dbbf7 --- /dev/null +++ b/docs/examples/spacewar/Bullet.java @@ -0,0 +1,48 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Bullet.java +Part of the Spacewar game. + +*/ + +package spacewar; + +class Bullet extends SpaceObject { + + static private final int SIZE = 3; //Can't be changed for now!!! + static private int LIFETIME = 50; + + private int lifeLeft; + + Bullet (Game theGame, double xP, double yP, double xV, double yV) { + super(theGame, xP, yP, xV, yV); + lifeLeft = LIFETIME; + } + + int getSize() { return SIZE; } + + void handleCollision(SpaceObject obj) { + die(); + } + + void clockTick() { + if (--lifeLeft == 0) + die(); + super.clockTick(); + } +} diff --git a/docs/examples/spacewar/Debug.java b/docs/examples/spacewar/Debug.java new file mode 100644 index 000000000..a3afb1dda --- /dev/null +++ b/docs/examples/spacewar/Debug.java @@ -0,0 +1,219 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + +Debug.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import java.awt.Menu; +import java.awt.CheckboxMenuItem; +import java.awt.Frame; +import java.awt.TextArea; +import java.awt.Dimension; + +/** + * This aspect specifies debugging information to be output to the + * information window. + * + * When the debug aspect is compiled in the Frame menu has several checkbox + * items that can be used to control the amount of tracing information + * displayed. (By default the first three are off, because they generate + * so much information.) + * + * There are two reasons to gather all this debugging code into an aspect + * like this: + * + * (1) It makes it easier to understand when it is all in one place. + * + * (2) It means that we can "plug and debug". We can enable/disable + * the debugging code simply by weaving or not weaving this + * aspect in. + * + * All in all, this is a lot better than the usual practice of writing + * complex debugging code and then deleting it when the bug is found, + * only to regret it a month later when a related bug surfaces. (Or even + * the same bug!) + * + * This file also defines a class InfoWin, which it uses to display all the + * debugging information. + */ +aspect Debug { + + private static InfoWin infoWin = new InfoWin(); + + private static Menu menu = new Menu("Debug"); + + private static CheckboxMenuItem traceConstructors = + new CheckboxMenuItem("trace constructors", false); + private static CheckboxMenuItem traceInitializations = + new CheckboxMenuItem("trace initializations", false); + private static CheckboxMenuItem traceMethods = + new CheckboxMenuItem("trace methods", false); + private static CheckboxMenuItem traceClockTick = + new CheckboxMenuItem("trace clock tick", false); + private static CheckboxMenuItem traceRegistry = + new CheckboxMenuItem("trace registry", true); + private static CheckboxMenuItem traceFireCollideDamage = + new CheckboxMenuItem("trace fire, collide, damage", true); + + after() returning (SWFrame frame): call(SWFrame+.new(..)) { + menu.add(traceConstructors); + menu.add(traceInitializations); + menu.add(traceMethods); + menu.add(traceClockTick); + menu.add(traceRegistry); + menu.add(traceFireCollideDamage); + frame.getMenuBar().add(menu); + } + + /* + * all constructors + */ + pointcut allConstructorsCut(): + call((spacewar.* && !(Debug+ || InfoWin+)).new(..)); + + before(): allConstructorsCut() { + if (traceConstructors.getState()) { + infoWin.println("begin constructing " + thisJoinPoint.getSignature()); + } + } + + after() returning: allConstructorsCut() { + if (traceConstructors.getState()) { + infoWin.println("done constructing " + thisJoinPoint.getSignature()); + } + } + + /* + * All dynamic initializations + */ + pointcut allInitializationsCut(): + initialization((spacewar.* && !(Debug+ || InfoWin+)).new(..)); + + before(): allInitializationsCut() { + if (traceConstructors.getState()) { + infoWin.println("begin initializing " + thisJoinPoint.getSignature()); + } + } + after() returning : allInitializationsCut() { + if (traceConstructors.getState()) { + infoWin.println("done initializing " + thisJoinPoint.getSignature()); + } + } + + /* + * all methods + */ + pointcut allMethodsCut(): + execution(* (spacewar.* && !(Debug+ || InfoWin+)).*(..)); + + before(): allMethodsCut() { + if (traceMethods.getState()) { + infoWin.println("entering " + thisJoinPoint.getSignature()); + } + } + after() returning : allMethodsCut() { + if (traceMethods.getState()) { + infoWin.println("exiting " + thisJoinPoint.getSignature()); + } + } + + /* + * clock ticks + */ + after(Object obj) returning : + (target(obj) && (target(Game) || + target(Registry) || + target(SpaceObject))) + && call(void clockTick()) { + if (traceClockTick.getState()) + infoWin.println("ticking " + obj); + } + + /* + * registry contents + */ + after(Registry registry) returning : + target(registry) && (call(void register(..)) || + call(void unregister(..))) { + if (traceRegistry.getState()) + infoWin.println(registry.getTable().size() + + " space objects in the registry."); + } + + /* + * fire, collide, damage + */ + after() returning : call(void Ship.fire()) { + if (traceFireCollideDamage.getState()) + infoWin.println("firing"); + } + + after(Ship ship, SpaceObject obj) returning : + call(void handleCollision(SpaceObject)) && target(ship) && args(obj) { + if (traceFireCollideDamage.getState()) + infoWin.println(ship + " collides with " + obj); + } + + after(Ship shipA, Ship shipB) returning : + execution(void Ship.bounce(Ship, Ship)) && args(shipA, shipB) { + if (traceFireCollideDamage.getState()) + infoWin.println(shipA + " bounces with " + shipB); + } + + before(Ship ship, double amount): + call(void Ship.inflictDamage(double)) && target(ship) && args(amount) { + if (traceFireCollideDamage.getState()) + if (amount > 0) + infoWin.println(ship + "gets " + + amount + " damage (" + + ship.getDamage() + ")"); + } + +} + +class InfoWin { + private Frame frame; + private TextArea info; + + InfoWin() { + frame = new Frame("debugging info for spacewar game"); + info = new TextArea(); + info.setEditable(false); + + Dimension screenSize = frame.getToolkit().getScreenSize(); + frame.setSize(250, 600); + frame.setLocation(screenSize.width - 250, 0); + frame.add(info); + frame.show(); + frame.toFront(); + } + + void clear() { + info.setText(""); + } + + void println(String line) { + info.append(line + "\n"); + } + + void print(String line) { + info.append(line); + } +} diff --git a/docs/examples/spacewar/Display.java b/docs/examples/spacewar/Display.java new file mode 100644 index 000000000..4236407ee --- /dev/null +++ b/docs/examples/spacewar/Display.java @@ -0,0 +1,165 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Display.java +Part of the Spacewar system. +*/ + +package spacewar; + +import java.util.Vector; +import java.util.Enumeration; +import java.awt.Graphics; +import java.awt.Canvas; +import java.awt.Image; + +/** + * The display aspects capture the look and feel of the Game in modular + * pluggable units. + * + * The model is that constructing a concrete subclass of Display attaches that + * kind of display to the game. It will Display the game as it goes along. + * A game can have any number of displays. Any of the displays will accept + * keyboard input. + * + */ + +class Display extends Canvas { + + private static Vector DISPLAYS = new Vector(2); + private static Vector PLAYERS = new Vector(2); + private static Pilot pilot1, pilot2; + + Game game; + SWFrame frame; + Image offImage; + Graphics offGraphics; + + Game getGame() { return game; } + static Pilot getPilot1() { return pilot1; } + static Pilot getPilot2() { return pilot2; } + + Display(Game g) { + super(); + game = g; + + frame = new SWFrame(game, this); + DISPLAYS.addElement(this); + } + + + void noticeSizeChange() { + initializeOffImage(); + } + + private void initializeOffImage () { + int w = getSize().width; + int h = getSize().height; + if ( w > 0 & h > 0) { + offImage = createImage(w, h); + offGraphics = offImage.getGraphics(); + } + } + + /* + * In our double buffering scheme, painting just means copying the buffer + * to the screen. The Display aspect draws into the buffer. + */ + public void paint(Graphics g) { + if (offImage != null) + g.drawImage(offImage, 0, 0, null); + } + + public void update(Graphics g) { + /* + * There are 4 steps to this: + * - clear the double buffer + * - paint the objects into the double buffer + * - paint the status into the double buffer + * - paint the doublebuffer into the buffer + */ + offGraphics.setColor(getBackground()); + offGraphics.fillRect(0, 0, getBounds().width, getBounds().height); + paintObjects(offGraphics); + paintStatus(offGraphics); + g.drawImage(offImage, 0, 0, null); + } + + void paintObjects(Graphics g) { } + void paintStatus(Graphics g) {} + + static aspect DisplayAspect { + + after (String mode) returning (Game game): call(Game+.new(String)) && args(mode) { + new Display1(game); + new Display2(game); + + if ( mode.equals("1") ) { + pilot1 = game.newPlayer(1); + } + else if ( mode.equals("2") ) { + pilot1 = game.newPlayer(1); + pilot2 = game.newPlayer(2); + } + else if (mode. equals("demo")) { + pilot1 = game.newRobot(1); + pilot2 = game.newRobot(2); + } else { + game.error("Invalid mode: " + mode); + game.quit(); + } + } + + + /* + * I'm not really sure this belongs here. + * + * Being here what it does is makes the Display aspect + * responsible for having the Players couple up to it. That's + * kind of nice, but its a bit incomplete, since Player is + * really part of the GUI, not part of the core Game. + * + * In a future re-factoring this will get worked out better. + * What will happen is that GUI will be an aspect that has the + * core GUI. Each of the different kinds of displays will be + * aspects that tie themselves in. + */ + after () returning (Player player): call(Player+.new(..)) { + Enumeration elements = DISPLAYS.elements(); + while ( elements.hasMoreElements() ) { + Display display = (Display)elements.nextElement(); + display.addKeyListener(player); + } + } + + after() returning (Display display): call(Display+.new(..)) { + display.noticeSizeChange(); + } + + after(Display display) returning (): call(void setSize(..)) && target(display) { + display.noticeSizeChange(); + } + + after() returning : call(void Game.clockTick()) { + Enumeration elements = DISPLAYS.elements(); + while ( elements.hasMoreElements() ) { + Display display = (Display)elements.nextElement(); + display.repaint(); + } + } + } +} diff --git a/docs/examples/spacewar/Display1.java b/docs/examples/spacewar/Display1.java new file mode 100644 index 000000000..622f7d051 --- /dev/null +++ b/docs/examples/spacewar/Display1.java @@ -0,0 +1,201 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Display1.java +Part of the Spacewar system. +*/ + +package spacewar; + + +import java.awt.Graphics; +import java.awt.Color; +import java.util.Random; + +/** + * This is the standard display aspect. + */ +class Display1 extends Display { + /* + * Here's the color scheme for the game. No other places in this file + * should say Color.xxx. Instead, that color should be given a symbolic + * name here. + */ + private static Color backgroundColor = Color.black; + private static Color player1ShipColor = Color.white; + private static Color player2ShipColor = Color.gray; + private static Color robotShipColor = new Color(0xa00000); + private static Color flameColor = Color.red; + private static Color shipExplosionColor = Color.red; + private static Color bulletColor = Color.green; + private static Color energyPacketOuterColor = Color.blue; + private static Color energyPacketInnerColor = new Color(0x7070FF); + private static Color statusLabelsColor = Color.white; + private static Color statusMeterBorderColor = Color.white; + private static Color energyStatusMeterColor = Color.blue; + private static Color damageStatusMeterColor = Color.red; + + + Display1(Game game) { + super(game); + frame.setLocation(20, 20); + } + + void noticeSizeChange() { + super.noticeSizeChange(); + setBackground(backgroundColor); + } + + void paintObjects(Graphics g) { + SpaceObject[] objects = game.getRegistry().getObjects(); + final int len = objects.length; + for (int i = 0; i < len; i++) { + objects[i].paint(g); + } + } + + static aspect SpaceObjectPainting { + + abstract private void SpaceObject.paint(Graphics g); + + /* + * Ships are by far and away the most complex of the space Objects + * to paint. First off, we need to set the color when the ship + * is made. + */ + private Color Ship.color; + + after(Pilot pilot) returning (Ship ship): call(Ship Game.newShip(Pilot)) && args(pilot) { + if (pilot.getNumber() == 1) + ship.color = player1ShipColor; + else if (pilot.getNumber() == 2) + ship.color = player2ShipColor; + else + ship.color = robotShipColor; + } + + private void Ship.paint(Graphics g) { + final double PI = Math.PI; + int[] radius = {15, 12, -4, 12, -9, -15, -9}; + double[] angle = {0, PI * 3/4, 0, -PI * 3/4, PI/8, 0, -PI/8}; + int[] x; + int[] y; + + Random random = new Random(); + + if (this.getDamage() >= this.MAX_DAMAGE) { + int lines = 20; + x = new int[lines]; + y = new int[lines]; + g.setColor(shipExplosionColor); + for (int i = 0; i < lines; i++) { + x[i] = (int)(this.getXPos()) + random.nextInt() % 20; + y[i] = (int)(this.getYPos()) + random.nextInt() % 20; + } + for (int i = 0; i < lines; i++) + g.drawLine(x[i], y[i], x[(i + 1) % lines], y[(i + 1) % lines]); + } else { + x = new int[7]; + y = new int[7]; + + g.setColor(this.color); + + radius[5] += random.nextInt() % 3; + // convert coordinates from polar to cartesian + for (int i = 0; i < 7; i++) { + x[i] = (int) + (this.getXPos() + + Math.cos(this.getOrientation() + angle[i]) * radius[i]); + y[i] = (int) + (this.getYPos() + + Math.sin(this.getOrientation() + angle[i]) * radius[i]); + } + + // draw the body as a polygon + g.drawPolygon(x, y, 4); + + // if the ship is accelerating, draw in a flame + if (this.getRAcc() != 0) { + g.setColor(flameColor); + g.drawLine(x[4], y[4], x[5], y[5]); + g.drawLine(x[5], y[5], x[6], y[6]); + } + } + } + + /* + * Bullets + */ + private void Bullet.paint(Graphics g) { + g.setColor(bulletColor); + g.fillOval((int)this.getXPos() - 1, + (int)this.getYPos() - 1, + 3, + 3); + } + + /* + * energy packets + */ + private void EnergyPacket.paint(Graphics g) { + g.setColor(energyPacketOuterColor); + g.fillOval((int)this.getXPos() - 5, + (int)this.getYPos() - 5, + 10, 10); + g.setColor(energyPacketInnerColor); + g.fillOval((int)this.getXPos() - 2, + (int)this.getYPos() - 2, + 3, 3); + } + } + + + void paintStatus(Graphics g) { + int left1 = 60; + int top1 = 0; + + int left2 = 200; + int top2 = 0; + + g.setColor(statusLabelsColor); + g.drawString("energy:", 5, top1 + 15); + g.drawString("damage:", 5, top1 + 30); + + if (getPilot1() != null) + paintLevels(g, getPilot1().getShip(), top1, left1); + if (getPilot2() != null) + paintLevels(g, getPilot2().getShip(), top2, left2); + } + + static void paintLevels(Graphics g, Ship ship, int top, int left) { + if (ship == null) + return; + else if (ship.isAlive()) { + g.setColor(statusMeterBorderColor); + g.drawRect(left, top + 6, 101, 10); + g.drawRect(left, top + 21, 101, 10); + g.setColor(energyStatusMeterColor); + g.fillRect(left + 1, top + 7, (int)(ship.getEnergyLevel()*100), 9); + g.setColor(damageStatusMeterColor); + g.fillRect(left + 1, top + 22, (int)(ship.getDamageLevel()*100), 9); + } + else { + g.setColor(damageStatusMeterColor); + g.drawString("Ship is destroyed", left+1, top+15); + } + } +} diff --git a/docs/examples/spacewar/Display2.java b/docs/examples/spacewar/Display2.java new file mode 100644 index 000000000..0430a1cc5 --- /dev/null +++ b/docs/examples/spacewar/Display2.java @@ -0,0 +1,136 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Display2.java +Part of the Spacewar system. +*/ + +package spacewar; + + +import java.awt.Graphics; +import java.awt.Color; + + +/** + * This is the cheap Display aspect. + */ +class Display2 extends Display { + + Display2(Game game) { + super(game); + frame.setLocation(540, 20); + } + + void noticeSizeChange() { + super.noticeSizeChange(); + setBackground(Color.darkGray); + } + + void paintObjects(Graphics g) { + SpaceObject[] objects = game.getRegistry().getObjects(); + final int len = objects.length; + for (int i = 0; i < len; i++) { + objects[i].paint(g); + } + } + + static aspect SpaceObjectPainting { + + abstract private void SpaceObject.paint(Graphics g); + + /* + * Ships are by far and away the most complex of the space Objects + * to paint. + */ + private Color Ship.color; + + after(Pilot pilot) returning (Ship ship): call(Ship Game.newShip(Pilot)) && args(pilot) { + if (pilot.getNumber() == 1) + ship.color = Color.white; + else if (pilot.getNumber() == 2) + ship.color = Color.gray; + else + ship.color = new Color(0xa00000); + } + + private void Ship.paint(Graphics g) { + if (this.getDamage() < this.MAX_DAMAGE) { + double x = this.getXPos(); + double y = this.getYPos(); + double sinTheta = Math.sin(this.getOrientation()); + double cosTheta = Math.cos(this.getOrientation()); + + g.setColor(color); + g.drawLine((int)(x + 8*cosTheta), (int)(y + 8*sinTheta), + (int)(x - 8*cosTheta), (int)(y - 8*sinTheta)); + + // if the ship is accelerating, draw thruster + if (this.getRAcc() != 0) { + g.setColor(Color.red); + g.fillOval((int)(x - 8*cosTheta), (int)(y - 8*sinTheta), 6, 6); + } + } + } + + private void Bullet.paint(Graphics g) { + g.setColor(Color.green); + g.fillOval((int)this.getXPos() - 1, + (int)this.getYPos() - 1, + 3, + 3); + } + + private void EnergyPacket.paint(Graphics g) { + g.setColor(Color.white); + g.fillOval((int)this.getXPos() - 5, + (int)this.getYPos() - 5, + 10, + 10); + } + } + + void paintStatus(Graphics g) { + int left1 = 60; + int top1 = 0; + + int left2 = 200; + int top2 = 0; + + g.setColor(Color.white); + g.drawString("energy:", 5, top1 + 15); + g.drawString("damage:", 5, top1 + 30); + + if (getPilot1() != null) + paintLevels(g, getPilot1().getShip(), top1, left1); + if (getPilot2() != null) + paintLevels(g, getPilot2().getShip(), top2, left2); + } + + void paintLevels(Graphics g, Ship ship, int top, int left) { + if (ship == null) + return; + else if (ship.isAlive()) { + g.drawString(Float.toString(ship.getEnergyLevel()*100), left+1, top+15); + g.drawString(Float.toString(ship.getDamageLevel()*100), left+1, top+30); + } + else { + g.setColor(Color.red); + g.drawString("Ship is destroyed", left+1, top+15); + } + } +} diff --git a/docs/examples/spacewar/EnergyPacket.java b/docs/examples/spacewar/EnergyPacket.java new file mode 100644 index 000000000..d7bc4f9ec --- /dev/null +++ b/docs/examples/spacewar/EnergyPacket.java @@ -0,0 +1,44 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +EnergyPacket.java +Part of the Spacewar system. + +*/ + +package spacewar; + + +class EnergyPacket extends SpaceObject { + + static private final int SIZE = 5; //Can't be changed for now!!! + int getSize() { return SIZE; } + + private double energy; + + double getEnergy() { return energy; } + + EnergyPacket(Game theGame, + double xP, double yP, double xV, double yV, double e) { + super(theGame, xP, yP, xV, yV); + energy = e; + } + + void handleCollision(SpaceObject obj) { + die(); + } +} diff --git a/docs/examples/spacewar/EnergyPacketProducer.java b/docs/examples/spacewar/EnergyPacketProducer.java new file mode 100644 index 000000000..efd276042 --- /dev/null +++ b/docs/examples/spacewar/EnergyPacketProducer.java @@ -0,0 +1,63 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +EnergyPacketProducer.java +Part of the Spacewar system. + + This implementation creates booby-trapped packets 20% of the time. + +*/ + +package spacewar; + + +class EnergyPacketProducer extends Thread { + private final static int MIN = -20; + private final static int MAX = 80; + private final static int EXPECTEDINTERVAL = 15; + + private Game game; + + Game getGame() { return game; } + + EnergyPacketProducer(Game theGame) { + super("EnergyPacketProducer"); + game = theGame; + } + + public void run() { + while(true) { + produceAPacket(); + waitForABit(); + } + } + + void waitForABit() { + try { Thread.sleep((int)(Math.random() * EXPECTEDINTERVAL * 2000)); } + catch (InterruptedException e) {} + } + + void produceAPacket() { + EnergyPacket pkt = + new EnergyPacket(game, + Math.random() * getGame().getWidth(), + Math.random() * getGame().getHeight(), + Math.random() * 2 - 1, + Math.random() * 2 - 1, + Math.random() * (MAX - MIN) + MIN); + } +} diff --git a/docs/examples/spacewar/EnsureShipIsAlive.java b/docs/examples/spacewar/EnsureShipIsAlive.java new file mode 100644 index 000000000..f7b949a92 --- /dev/null +++ b/docs/examples/spacewar/EnsureShipIsAlive.java @@ -0,0 +1,35 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + +Ship.java +Part of the Spacewar system. + +*/ + +package spacewar; + +/** + * This aspect makes sure that the ship is alive before performing any console + * commands. + * + */ +aspect EnsureShipIsAlive { + void around (Ship ship): Ship.helmCommandsCut(ship) { + if ( ship.isAlive() ) { + proceed(ship); + } + } +} diff --git a/docs/examples/spacewar/Game.java b/docs/examples/spacewar/Game.java new file mode 100644 index 000000000..54b1bf2ac --- /dev/null +++ b/docs/examples/spacewar/Game.java @@ -0,0 +1,215 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Game.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import java.awt.Dimension; + +/** + * The Game class is the root of the spacewar game. To start a spacewar + * game, you can either call the main method, or instantiate this class + * directly. + * + * Synchronization is done by the GameSynchronization aspect. + */ +public class Game extends Thread { + + /** + * To run the game from top level, simply say Java Game, as usual. Passing + * an argument makes the game run in demo mode. Without an argument it runs + * in the normal player mode. + */ + public static void main(String[] args) { + if ( args.length == 0 ) + new Game("1").run(); + new Game(args[0]).run(); + } + + + private Timer timer; + private EnergyPacketProducer ePP; + + private Registry registry; + private Pilot pilot1, pilot2; + + private Dimension screenSize = new Dimension(500, 500); + + Registry getRegistry() { return registry; } + Pilot getPilot1() { return pilot1; } + Pilot getPilot2() { return pilot2; } + + /** returns the width of the screen, delegating to screenSize */ + int getWidth() { return screenSize.width; } + + /** returns the height of the screen, delegating to screenSize */ + int getHeight() { return screenSize.height; } + + /** + * To run the game, simply instantiate this class. It runs in its own + * thread. You can instantiate multiple games at once. For the time being + * the only way to end the game is to exit from the Java VM. + * + * @param mode Controls whether the game runs in demo mode or not. True + * means it is a demo, false means it runs in normal 2 player mode. + */ + public Game(String mode) { + timer = new Timer(this); + ePP = new EnergyPacketProducer(this); + registry = new Registry(this); + } + + public void run() { + timer.start(); + ePP.start(); + + while(true) { + try { + newRobot(3); + Thread.sleep(15000); + } + catch (InterruptedException e) {} + } + } + + + /** + * add a robot to the game. This is a menu command. + */ + void addRobot() { + newRobot(3); + } + + /** + * resurrect the ships in the game. This is a menu command. + */ + void resetShips() { + Ship[] ships = registry.getShips(); + + for (int i = 0; i < ships.length; i++) { + Ship ship = ships[i]; + Pilot pilot = ship.getPilot(); + newShip(pilot); + } + } + + /** + * leave the game. This is a menu command. + */ + void quit() { + System.exit(0); + } + + void error(Object o) { + System.err.println(o); + } + + + /** + * returns a new player. With {@link #newRobot} and {@link + * #newShip}, the only ways to make a Player, a Robot, or a Ship. + * The structural invariant is that there should be no calls to + * new of one of these three classes outside these three methods. + */ + Player newPlayer(int number) { + Player player = new Player(this, number); + newShip(player); + return player; + } + + /** + * returns a new robot. With {@link #newPlayer} and {@link + * #newShip}, the only ways to make a Player, a Robot, or a Ship. + * The structural invariant is that there should be no calls to + * new of one of these three classes outside these three methods. + */ + Robot newRobot(int number) { + Robot robot = new Robot(this, number); + newShip(robot); + robot.start(); + return robot; + } + + /** + * returns a new ship. With {@link #newRobot} and {@link + * #newPlayer}, the only ways to make a Player, a Robot, or a + * Ship. The structural invariant is that there should be no + * calls to new of one of these three classes outside these three + * methods. + */ + Ship newShip(Pilot pilot) { + // + // If there is an old ship (we're doing a reset), then remove it from + // the registry. + // + Ship oldShip = pilot.getShip(); + if (! (oldShip == null)) + oldShip.die(); + + Ship newShip = new Ship(this, + Math.random() * getWidth(), + Math.random() * getHeight(), + Math.random() * Math.PI * 2); + pilot.setShip(newShip); + newShip.setPilot(pilot); + + return newShip; + } + + void clockTick() { + registry.clockTick(); + handleCollisions(); + } + + // collision detection + + void handleCollisions() { + SpaceObject[] objects = registry.getObjects(); + + SpaceObject objI, objJ; + for (int i = 0; i < objects.length; i++) { + objI = objects[i]; + for (int j = i + 1; j < objects.length; j++) { + objJ = objects[j]; + if (objI instanceof Bullet && objJ instanceof Bullet) + continue; + if (isCollision(objI, objJ)) { + if (objI instanceof Ship && objJ instanceof Ship) + Ship.bounce((Ship)(objI), (Ship)(objJ)); + else { + objI.handleCollision(objJ); + objJ.handleCollision(objI); + } + } + } + } + } + + /* + * Is the distance between the two centers less than the sum of the two + * radii. This is a cheap and dirty (i.e. wrong) implementation of this. + */ + static boolean isCollision(SpaceObject a, SpaceObject b) { + return (Math.abs(a.getXPos() - b.getXPos()) + + Math.abs(a.getYPos() - b.getYPos())) < + (a.getSize()/2 + b.getSize()/2); + } +} diff --git a/docs/examples/spacewar/GameSynchronization.java b/docs/examples/spacewar/GameSynchronization.java new file mode 100644 index 000000000..dcf42e904 --- /dev/null +++ b/docs/examples/spacewar/GameSynchronization.java @@ -0,0 +1,54 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +RegistrySynchronization.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import coordination.Coordinator; + +/** + * This aspect ensures synchronized access to methods of the Game in the + * presence of several threads. + * + * It uses the Coordinator class, from the AspectJ coordination library. + * (This case is right on the borderline of being too simple to use the + * coordination library, but we use it anyways to keep the similarity + * with the RegistrySynchronizer.) + * + * It uses a per-Game coordination scheme, so there is one instance of + * this class for each instance of the Game class. When this class is + * constructed, it registers appropriate mutexes and selfexes using + * the behavior inherited from Coordinator. + * + * The coordination constraints for the Game are simple. We just need to + * make sure that newShip and handleCollisions are mutually exclusive. That + * ensures that they we can't destroy a ship that has just been replaced. + */ +aspect GameSynchronization extends Coordinator perthis(this(Game)) { + + protected pointcut synchronizationPoint(): + call(void Game.handleCollisions(..)) || call(Ship Game.newShip(..)); + + public GameSynchronization() { + addMutex(new String[] {"handleCollisions", "newShip"}); + } + +} diff --git a/docs/examples/spacewar/Makefile b/docs/examples/spacewar/Makefile new file mode 100644 index 000000000..e425dfd55 --- /dev/null +++ b/docs/examples/spacewar/Makefile @@ -0,0 +1,12 @@ +SHELL=bash +ACJOPTS=-verbose -nosymbols +AJC=ajc + +.PHONY: demo debug + +demo: + $(AJC) $(ACJOPTS) @demo.lst + +debug: + $(AJC) $(ACJOPTS) @debug.lst + diff --git a/docs/examples/spacewar/Pilot.java b/docs/examples/spacewar/Pilot.java new file mode 100644 index 000000000..330d860bf --- /dev/null +++ b/docs/examples/spacewar/Pilot.java @@ -0,0 +1,44 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +*/ + +package spacewar; + + +/** + * Pilot is the abstract superclass of Player and Robot. + * + */ + +abstract class Pilot { + private Game game; + private int number; + protected Ship ship = null; + + Game getGame() { return game; } + int getNumber() { return number; } + Ship getShip() { return ship; } + + void setShip(Ship s) { ship = s; } + + Pilot (Game g, int n) { + super(); + game = g; + number = n; + } +} diff --git a/docs/examples/spacewar/Player.java b/docs/examples/spacewar/Player.java new file mode 100644 index 000000000..3899c6d52 --- /dev/null +++ b/docs/examples/spacewar/Player.java @@ -0,0 +1,121 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +*/ + +package spacewar; + +import java.awt.event.KeyListener; +import java.awt.event.KeyEvent; + +class Player extends Pilot implements KeyListener { + + private KeyMapping keyMapping; + + /** current rotation key */ + private int rotation_direction = Ship.STOP; // current rotation key + + /** current thrust */ + private boolean thrust_on = false; + + Player(Game theGame, int number) { + super(theGame,number); + + if (getNumber() == 1) + keyMapping = KeyMapping.keyMapping1; + else if (getNumber() == 2) + keyMapping = KeyMapping.keyMapping2; + + } + + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + boolean consumed = true; + + if (keyCode == keyMapping.fire) { + ship.fire(); + } + else if (keyCode == keyMapping.thrust && !thrust_on) { + ship.thrust(true); + thrust_on = true; + } + else if (keyCode == keyMapping.right && + rotation_direction != Ship.COUNTERCLOCKWISE) { + //start rotating clockwise unless already rotating in the + //opposite direction + rotation_direction = Ship.CLOCKWISE; + ship.rotate(Ship.CLOCKWISE); + } + else if (keyCode == keyMapping.left && + rotation_direction != Ship.CLOCKWISE) { + //start rotating counterclockwise unless already rotating in the + //opposite direction + rotation_direction = Ship.COUNTERCLOCKWISE; + ship.rotate(Ship.COUNTERCLOCKWISE); + } + else { + consumed = false; + } + + if (consumed) e.consume(); + } + + public void keyReleased(KeyEvent e) { + int keyCode = e.getKeyCode(); + + if (keyCode == keyMapping.thrust) { + ship.thrust(false); //engine off + thrust_on = false; + } + else if (keyCode == keyMapping.right && + rotation_direction == Ship.CLOCKWISE + || + keyCode == keyMapping.left && + rotation_direction == Ship.COUNTERCLOCKWISE) { + ship.rotate(Ship.STOP); //stop rotation + rotation_direction = Ship.STOP; + } + } + + public void keyTyped(KeyEvent e) { + // have to implement this because it's in KeyListener + } +} + +class KeyMapping { + + static final KeyMapping keyMapping1 = + new KeyMapping(KeyEvent.VK_LEFT, + KeyEvent.VK_RIGHT, + KeyEvent.VK_UP, + KeyEvent.VK_SPACE); + + static final KeyMapping keyMapping2 = + new KeyMapping(KeyEvent.VK_X, + KeyEvent.VK_V, + KeyEvent.VK_D, + KeyEvent.VK_ALT); + + int left, right, thrust, fire; + + KeyMapping(int k_left, int k_right, int k_thrust, int k_fire) { + left = k_left; + right = k_right; + thrust = k_thrust; + fire = k_fire; + } +} diff --git a/docs/examples/spacewar/README.adoc b/docs/examples/spacewar/README.adoc new file mode 100644 index 000000000..9991867a3 --- /dev/null +++ b/docs/examples/spacewar/README.adoc @@ -0,0 +1,56 @@ +[[_5]] +== Exploring the Spacewar Example + +_© Copyright 1997-2001 Xerox Corporation. All rights reserved._ + +_Last updated: January 10, 2001_ + +The code in this directory is an implementation of the classic video +game Spacewar. + +The Spacewar game is intended to provide a modest-sized example of a +program that uses aspects. The code for this example is evolving, as we +add new features to AspectJ and come up with a better understanding of +how to use the features. + +In order to compile and run this example, make sure to have the latest +version of AspectJ correctly installed. If you're not sure you do, try +the helloworld example first by following the instructions in +xref:../doc/primer/default.html[Primer] section Getting Started. + +[[_5_1]] +=== Compiling Spacewar + +* Change to the `examples` directory. +* Type `ajc -argfile spacewar/demo.lst` to compile the system. + +[[_5_2]] +=== Running Spacewar + +* In the examples directory, type `java spacewar.Game` + +When the game starts up you will see two different displays. These are +the two built-in display aspects of the game. In each you will see a +single white ship and two red ships. The white ship is yours to control; +the red ships are an enemy robots. Your ship is controlled with the four +arrow keys to turn, thrust and stop; the spacebar fires. As you play, +the game will be displayed in both windows. + +When running on a 1.4 or later VM, click in the main panel to give it +focus so that your keystrokes are recognized. + +You can quit the game with ctl-Q. + +[[_5_3]] +=== Exploring the Code + +There is one other built-in configurations for the Spacewar game. Try it +by typing `ajc @spacewar\debug.lst`. This compiles in an elaborate +debugging aspect for the game. + +We recommend you explore the Spacewar source code and look at the +aspects that it uses. You will find several of them, of different scales +and different degrees of cross-cutting. Remember that these represent +our evolving understanding of how to use AspectJ to implement Spacewar. +If you believe we should be doing something differently, then please let +us know. diff --git a/docs/examples/spacewar/Registry.java b/docs/examples/spacewar/Registry.java new file mode 100644 index 000000000..a9cec0418 --- /dev/null +++ b/docs/examples/spacewar/Registry.java @@ -0,0 +1,126 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Registry.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + * The Registry keeps track of all the space objects that are floating around. + * It basically supports register, unregister and contents type operations. + * + * The synchronization is done by the RegistrySynchronization aspect. + */ + +class Registry { + + private Hashtable table; + private Game game; + + Game getGame() { return game; } + + Registry (Game theGame) { + game = theGame; + table = new Hashtable(); + } + + + void register(SpaceObject object) { + table.put(object, object); + } + + void unregister(SpaceObject object) { + table.remove(object); + } + + /* + * It is an invariant of the design that only two points in SpaceObject + * should call register and unregister. This aspect enforces that. + * + * Unfortunately, in the current compiler, we get a static warning when + * there are no illegal calls that this advice has no targets. That will + * be fixed in a future release. For the time being the dummy method + * just below this fixes that. + */ + static aspect RegistrationProtection { + after() returning(): + (call(void Registry.register(SpaceObject)) || + call(void Registry.unregister(SpaceObject))) && + !(within(SpaceObject) && (withincode(new(..)) || + withincode(void die()))) { + throw new IllegalAccessError( + "This is an illegal call to " + thisJoinPoint + "\n" + + "Only the constructor and the die() on SpaceObject\n" + + "should call the primitive registry operations."); + } + } + + void dummy() { // see comment above + register(getObjects()[0]); + unregister(getObjects()[0]); + } + + + SpaceObject[] getObjects() { + SpaceObject[] allObjects = new SpaceObject[table.size()]; + Enumeration elements = table.elements(); + for(int i = 0; elements.hasMoreElements(); i++) { + allObjects[i] = (SpaceObject)(elements.nextElement()); + } + return allObjects; + } + + Ship[] getShips() { + // + // First we have to put just the Ships into a vector, then we can put + // them into an array of exactly the right length. + // + Ship[] arrayOfShips; + Vector vectorOfShips = new Vector(); + Enumeration elements = table.elements(); + while (elements.hasMoreElements()) { + Object object = elements.nextElement(); + if (object instanceof Ship) { + vectorOfShips.addElement(object); + } + } + + arrayOfShips = new Ship[(vectorOfShips.size())]; + vectorOfShips.copyInto(arrayOfShips); + return arrayOfShips; + } + + Hashtable getTable() { return table; } + + // + // The protocol for clockTick is that it automatically cascades. + // + void clockTick() { + Enumeration elements = table.elements(); + while (elements.hasMoreElements()) { + ((SpaceObject)elements.nextElement()).clockTick(); + } + } +} + diff --git a/docs/examples/spacewar/RegistrySynchronization.java b/docs/examples/spacewar/RegistrySynchronization.java new file mode 100644 index 000000000..986e4bd30 --- /dev/null +++ b/docs/examples/spacewar/RegistrySynchronization.java @@ -0,0 +1,58 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +RegistrySynchronization.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import coordination.Coordinator; + + +/** + * This aspect ensures synchronized access to methods of the Registry in + * the presence of several threads. + * + * It uses the Coordinator class, from the AspectJ coordination library. + * + * It uses a per-Registry coordination scheme, so there is one instance of + * this class for each instance of the Registry class. When this class is + * constructed, it registers appropriate mutexes and selfexes using the + * behavior inherited from Coordinator. + * + * The mutating methods (register and unregister) should be self-exclusive. + * Each reader method should be mutually exclusive with the mutating + * methods. But the readers can run concurrently. */ +aspect RegistrySynchronization extends Coordinator perthis(this(Registry)) { + + protected pointcut synchronizationPoint(): + call(void Registry.register(..)) || + call(void Registry.unregister(..)) || + call(SpaceObject[] Registry.getObjects(..)) || + call(Ship[] Registry.getShips(..)); + + public RegistrySynchronization() { + addSelfex("register"); + addSelfex("unregister"); + + addMutex(new String[] {"register", "unregister", "getObjects"}); + addMutex(new String[] {"register", "unregister", "getShips"}); + } + +} diff --git a/docs/examples/spacewar/Robot.java b/docs/examples/spacewar/Robot.java new file mode 100644 index 000000000..05f276108 --- /dev/null +++ b/docs/examples/spacewar/Robot.java @@ -0,0 +1,201 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Robot.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import java.util.Random; + +/** + * Robot is an automatic pilot that now has quite a bit of intelligence. + * So, beware ! + */ +class Robot extends Pilot implements Runnable { + + private static final int FIRE_INTERVAL = 60; + private static final int REBIRTH_DELAY = 900; + + private final Random random = new Random(); + + private Thread runner; + private boolean runnable = true; + + Robot(Game theGame, int number) { + super(theGame, number); + } + + void start() { + if (runner == null) { + runner = new Thread(this); + runner.start(); + } + } + + void destroy() { + if (runner != null) { + runnable = false; + runner = null; + } + } + + + // A Robot tracks User-controlled ships and fires at them + public void run() { + Ship target = null; + + while(runnable) { + // find target ship + do { + Ship[] potentials = getGame().getRegistry().getShips(); + if(potentials.length != 0) + target = potentials[Math.abs(random.nextInt() % potentials.length)]; + sleepForABit(25); + } while (target == ship); + // main loop + int currentRotation = Ship.STOP; + int time; + boolean currentlyAccelerating = false; + double dx, dy, angleA, angleB, theta, dtheta, d, + targetVel, a, b, c, targetXVel, targetYVel; + + while(true) { + sleepForABit(FIRE_INTERVAL); + + // if my ship is destroyed, give me a new one + if (!ship.isAlive()) { + sleepForABit(REBIRTH_DELAY); + getGame().newShip(this); + } + + // find direction and distance from target to me + dx = ship.getXPos() - target.getXPos(); + if (dx < - getGame().getWidth() / 2) + dx += getGame().getWidth(); + if (dx > getGame().getWidth() / 2) + dx -= getGame().getWidth(); + dy = ship.getYPos() - target.getYPos(); + if (dy < - getGame().getHeight() / 2) + dy += getGame().getHeight(); + if (dy > getGame().getHeight() / 2) + dy -= getGame().getHeight(); + d = Math.sqrt(dx * dx + dy * dy); + angleA = Math.atan(dy / dx); + if (dx < 0) + angleA += Math.PI; + + // find relative velocity and trajectory of target + targetXVel = target.getXVel() - ship.getXVel(); + targetYVel = target.getYVel() - ship.getYVel(); + targetVel = Math.sqrt(targetXVel * targetXVel + + targetYVel * targetYVel); + angleB = Math.atan(targetYVel / targetXVel); + if (targetXVel < 0) + angleB+=Math.PI; + + // find angle between line to target and taget's direction of travel + theta = (angleA - angleB) % (2 * Math.PI); + if (theta < -Math.PI) + theta += 2 * Math.PI; + if (theta > Math.PI) + theta -= 2 * Math.PI; + + // calculate time to bullet impact using law of cosines + a = targetVel * targetVel + Ship.BULLET_SPEED * Ship.BULLET_SPEED; + b = d * targetVel * Math.cos(theta); + c = - d * d; + time = (int)((-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a); + + // calculate angle and distance to bullet impact location + dx = targetXVel * time - dx; + dy = targetYVel * time - dy; + theta = Math.atan(dy / dx); + if(dx < 0) + theta += Math.PI; + + // find desired change in rotation + dtheta = (theta - ship.getOrientation()) % (2 * Math.PI); + // find the shortest path to the desired orientation; + if(dtheta < - Math.PI) + dtheta += 2 * Math.PI; + if(dtheta > Math.PI) + dtheta -= 2 * Math.PI; + + // turn if nessecary + if (dtheta > Ship.DEFAULT_ANGULAR_VELOCITY / 2) { + if (currentRotation != Ship.CLOCKWISE) + ship.rotate(currentRotation = Ship.CLOCKWISE); + } + else if (dtheta < -Ship.DEFAULT_ANGULAR_VELOCITY / 2) { + if (currentRotation != Ship.COUNTERCLOCKWISE) + ship.rotate(currentRotation = Ship.COUNTERCLOCKWISE); + } // otherwise, fire, maybe even a burst + else { + if(currentRotation != Ship.STOP) + ship.rotate(currentRotation = Ship.STOP); + if (random.nextInt() % 40 == 0) { + ship.fire(); + } + } + + // randomly accelerate + if (currentlyAccelerating && random.nextInt() % 2 == 0) + ship.thrust(currentlyAccelerating = false); + else { + if (ship.getXVel() == 0) + angleA = 0; + else + angleA = Math.atan(ship.getYVel() / ship.getXVel()); + + if (ship.getXVel() < 0) + angleA+=Math.PI; + angleB = (angleA - ship.getOrientation()) % (2 * Math.PI); + if (angleB < -Math.PI) + angleB += 2 * Math.PI; + if (angleB > Math.PI) + angleB -= 2 * Math.PI; + angleB = Math.abs(angleB); + + // angleB now represents the angle between the ship's + // orientation and velocity vector. This will be used to + // determine the probably that the ship will thrust to + // prevent ships from accelerating too much in one direction + if (random.nextInt() % (int)(12 * (Math.PI - angleB) + 1) == 0) + ship.thrust(currentlyAccelerating = true); + } + + // switch targets if current one has been destroyed + if (target.getDamage() == 100) + break; + + // randomly switch targets + if (random.nextInt() % 4000 == 0) + break; + } + } + } + + void sleepForABit (int time) { + try { + runner.sleep(time); + } + catch (InterruptedException e) {} + } +} diff --git a/docs/examples/spacewar/SWFrame.java b/docs/examples/spacewar/SWFrame.java new file mode 100644 index 000000000..6dfb9f6f6 --- /dev/null +++ b/docs/examples/spacewar/SWFrame.java @@ -0,0 +1,92 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +SWFrame.java +Part of the Spacewar system. + +*/ + +package spacewar; + +import java.awt.Frame; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.MenuShortcut; +import java.awt.Dimension; +import java.awt.Insets; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +class SWFrame extends Frame implements ActionListener { + private Game game; + private Display display; + private Menu menu; + + Game getGame() { return game; } + Display getDisplay() { return display; } + Menu getMenu() { return menu; } + + SWFrame(Game theGame, Display d) { + super("Space War!"); + + game = theGame; + + display = d; + add(display); + + // create menu + menu = new Menu("Game"); + MenuItem item1 = new MenuItem("Add Robot", new MenuShortcut('a')); + MenuItem item2 = new MenuItem("Reset Ships", new MenuShortcut('r')); + MenuItem item3 = new MenuItem("Quit", new MenuShortcut('q')); + item1.setActionCommand("Add Robot"); + item2.setActionCommand("Reset Ships"); + item3.setActionCommand("Quit"); + menu.add(item1); + menu.add(item2); + menu.add(item3); + menu.addActionListener(this); + + setMenuBar(new MenuBar()); + getMenuBar().add(menu); + + Dimension screenSize = new Dimension(500, 500); + setSize(screenSize); + setVisible(true); + toFront(); + + Insets inset = getInsets(); + int displayWidth = screenSize.width - inset.left - inset.right; + int displayHeight = screenSize.height - inset.top - inset.bottom; + display.setSize(displayWidth, displayHeight); + } + + public void actionPerformed(ActionEvent e) { + String s = e.getActionCommand(); + if (s.equals("Add Robot")) { + getGame().addRobot(); + } + else if (s.equals("Reset Ships")) { + getGame().resetShips(); + } + else if (s.equals("Quit")) { + getGame().quit(); + } + } +} diff --git a/docs/examples/spacewar/Ship.java b/docs/examples/spacewar/Ship.java new file mode 100644 index 000000000..9978fe694 --- /dev/null +++ b/docs/examples/spacewar/Ship.java @@ -0,0 +1,296 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + +Ship.java +Part of the Spacewar system. + +*/ + +package spacewar; + +class Ship extends SpaceObject { + + pointcut helmCommandsCut(Ship ship): + target(ship) && ( call(void rotate(int)) || + call(void thrust(boolean)) || + call(void fire()) ); + + + /** + * Energy and Damage are key values in the state of a ship. Energy is + * basically about fuel, and damage is about how bad a shape we are in. + * + * The energy related values are: + * <ul> + * <li>MAX_ENERGY</li> + * <li>BULLET_ENERGY</li> + * <li>ACCELERATION_ENERGY_FACTOR</li> + * <li>energy</li> + * </ul> + * The damage related values are: + * <ul> + * <li>MAX_DAMAGE</li> + * <li>BULLET_DAMAGE</li> + * <li>COLLISION_DAMAGE_FACTOR</li> + * <li>damage</li> + * </ul> + * Finally, REPAIR_RATE is the rate at which energy is consumed to fix + * damage. + * + */ + private static final int MAX_ENERGY = 100; + private static final int BULLET_ENERGY= 2; + private static final double ACCELERATION_COST_FACTOR = 0.05; + + //XXX was private + static final int MAX_DAMAGE = 100; + private static final int BULLET_DAMAGE = 15; + private static final double COLLISION_DAMAGE_FACTOR = 0.1; + + private static final double REPAIR_RATE = 0.08; + + + private static final int EXPLOSION_LENGTH = 10; + + static final int BULLET_SPEED = 10; + + static final int CLOCKWISE = 1; + static final int STOP = 0; + static final int COUNTERCLOCKWISE = (-1); + + static final double DEFAULT_ANGULAR_VELOCITY = 0.2; + static final double DEFAULT_ACCELERATION = .4; + + static private final int SIZE = 30; //Can't be changed for now!!! + + private double energy; // range: 0 to MAX_ENERGY + private double damage; // range: 0 to MAX_DAMAGE + private double orientation; // in degrees + private double angularVel; // in ??? + private double xAcc, yAcc, rAcc; // + private int countdown; // remaining explosion time + + private Pilot pilot; + + Ship(Game theGame, double xPos, double yPos, double orientation) { + super(theGame, xPos, yPos, 0, 0); + xAcc = 0; + yAcc = 0; + this.orientation = orientation; + angularVel = 0; + + energy = MAX_ENERGY; + damage = 0; + countdown = EXPLOSION_LENGTH; + } + + + int getSize() { return SIZE; } + + double getEnergy() { return energy; } + double getDamage() { return damage; } + double getOrientation() { return orientation; } + double getRAcc() { return rAcc; } + + Pilot getPilot() { return pilot; } + void setPilot (Pilot p) { pilot = p; } + + float getEnergyLevel() { + return (float)energy / (float)MAX_ENERGY; + } + float getDamageLevel() { + return (float)damage / (float)MAX_DAMAGE; + } + + /** returns false if energy is out, otherwise decrements energy by amount + * and returns true + */ + boolean expendEnergy(double amount) { + if (amount <= energy) { + energy -= amount; + return true; + } + else + return false; + } + + /** increments damage by amount and handles the destruction of a ship if + * damage reaches MAX_DAMAGE. + */ + void inflictDamage(double amount) { + if (amount < 0) // shouldn't happen + return; + damage = Math.min(MAX_DAMAGE, damage + amount); + if (damage == MAX_DAMAGE) + setIsAlive(false); + } + + /** repairs some damage + */ + void repairDamage(double amount) { + if (amount < 0) // shouldn't happen + return; + if (damage == 0) + return; + damage = Math.max(0, damage - amount); + } + + public void clockTick() { + if (! isAlive()) { + // + // If we aren't alive, but we are still in the registry, it means + // we are exploding. countdown counts the length of the explosion. + // + if (--countdown == 0) + die(); + } + else { + if (angularVel != 0) { + orientation += angularVel; + xAcc = rAcc * Math.cos(orientation); + yAcc = rAcc * Math.sin(orientation); + } + setXVel(getXVel() + xAcc); + setYVel(getYVel() + yAcc); + + //expend energy + if (!expendEnergy(rAcc * ACCELERATION_COST_FACTOR)) + rAcc = xAcc = yAcc = 0; + + // fix damage + if (energy > 10 && damage > REPAIR_RATE) { + expendEnergy(REPAIR_RATE); + repairDamage(REPAIR_RATE); + } + } + super.clockTick(); + } + + /** + * First check to make sure we have enough energy to accelerate. If + * we do, then go ahead and do so. Acceleration is in the direction + * we are already facing (i.e. orientation). + */ + void setAcceleration(double acc) { + if (acc * ACCELERATION_COST_FACTOR <= energy) { + rAcc = acc; + xAcc = rAcc * Math.cos(orientation); + yAcc = rAcc * Math.sin(orientation); + } + } + + /** + * First check to make sure we have enough energy to rotate. If + * we do, then go ahead and do so. + */ + void setAngularVelocity(double omega) { + // changing direction of rotation takes energy + if (!expendEnergy(Math.abs(omega - angularVel) / 2)) + return; + //sets amount of degree rotation per clock tick, in radians; + //clockwise is positive + angularVel = omega; + } + + /** affect rotation thrusters. Direction can be one of {@link + * #CLOCKWISE}, {@link #COUNTERCLOCKWISE}, or zero for turning off + * the thrusters. + */ + void rotate(int direction) { + setAngularVelocity( + direction == CLOCKWISE ? DEFAULT_ANGULAR_VELOCITY : + direction == COUNTERCLOCKWISE ? -DEFAULT_ANGULAR_VELOCITY : + 0); + } + + /** turn on acceleration */ + void thrust(boolean onOff) { + setAcceleration(onOff ? DEFAULT_ACCELERATION : 0); + } + + /** create a bullet and fire it */ + void fire() { + // firing a shot takes energy + if (!expendEnergy(BULLET_ENERGY)) + return; + + //create a bullet object so it doesn't hit the ship that's firing it + double xV = getXVel() + BULLET_SPEED * (Math.cos(orientation)); + double yV = getYVel() + BULLET_SPEED * (Math.sin(orientation)); + + // create the actual bullet + new Bullet( + getGame(), + (getXPos() + ((getSize()/2 + 2) * (Math.cos(orientation))) + xV), + (getYPos() + ((getSize()/2 + 2) * (Math.sin(orientation))) + yV), + xV, + yV); + } + + + void handleCollision(SpaceObject obj) { + if (obj instanceof Ship) { + // should never be called. ship - ship collisions are handled in + // Ship.bounce(Ship shipA, Ship shipB) + } + else if (obj instanceof Bullet) { + inflictDamage(BULLET_DAMAGE); + } + else if (obj instanceof EnergyPacket) { + double packetEnergy = ((EnergyPacket)obj).getEnergy(); + energy = Math.max(0, Math.min(energy + packetEnergy, MAX_ENERGY)); + } + else { + System.err.println("collision with UFO!"); + } + } + + static void bounce(Ship shipA, Ship shipB) { + double dx, dy, denominator, + xAccA, yAccA, xAccB, yAccB, damage, + xComp, yComp, dvx, dvy; + + dx = Math.abs(shipA.getXPos() - shipB.getXPos()); + dy = Math.abs(shipA.getYPos() - shipB.getYPos()); + denominator = Math.sqrt(dx * dx + dy * dy); + xComp = dx / denominator; + yComp = dy / denominator; + xAccA = shipB.getXVel() * xComp + shipA.getXVel() * (1 - xComp) - + shipA.getXVel(); + yAccA = shipB.getYVel() * yComp + shipA.getYVel() * (1 - yComp) - + shipA.getYVel(); + xAccB = shipA.getXVel() * xComp + shipB.getXVel() * (1 - xComp) - + shipB.getXVel(); + yAccB = shipA.getYVel() * yComp + shipB.getYVel() * (1 - yComp) - + shipB.getYVel(); + shipA.accelerate(xAccA, yAccA); + shipB.accelerate(xAccB, yAccB); + dvx = shipA.getXVel() - shipB.getXVel(); + dvy = shipA.getYVel() - shipA.getYVel(); + damage = COLLISION_DAMAGE_FACTOR * (dvx * dvx + dvy * dvy); + shipA.inflictDamage(damage); + shipB.inflictDamage(damage); + + // !!! + // !!! poopers! this does a local time warp. this has to be a + // !!! violation of the clockTick protocol + // !!! + while (Game.isCollision(shipA, shipB)) { + shipA.clockTick(); + shipB.clockTick(); + } + } +} diff --git a/docs/examples/spacewar/SpaceObject.java b/docs/examples/spacewar/SpaceObject.java new file mode 100644 index 000000000..ee3afabcc --- /dev/null +++ b/docs/examples/spacewar/SpaceObject.java @@ -0,0 +1,106 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +SpaceObject.java +Part of the Spacewar system. + +*/ + +package spacewar; + + +/** + * SpaceObjects are objects that float around in space. They support the + * minimal SpaceObject protocol, having to do with position, velocity, + * size and liveness. They are constructed with game, position, velocity + * and size. When constructed, a spaceobject adds itself to the registry. + * + * When it dies, a spaceobject removes itself from the registry. But note + * that it doesn't decide when to die, subclasses do that. + * + * The display aspects actually draw the space object on the screen and say + * how much space it takes up there. + */ +abstract class SpaceObject { + + private Game game; + private double xPos, yPos, oldXPos, oldYPos, xVel, yVel; + private boolean alive; + + SpaceObject (Game theGame, double xP, double yP, double xV, double yV) { + game = theGame; + xPos = xP; + yPos = yP; + oldXPos = xP; + oldYPos = yP; + xVel = xV; + yVel = yV; + + alive = true; + getGame().getRegistry().register(this); + } + + Game getGame() { return game; } + + double getXPos() { return xPos; } + double getYPos() { return yPos; } + + double getOldXPos() { return oldXPos; } + double getOldYPos() { return oldYPos; } + + double getXVel() { return xVel; } + double getYVel() { return yVel; } + + void setXVel (double n) { xVel = n; } + void setYVel (double n) { yVel = n; } + + boolean isAlive() { return alive; } + void setIsAlive(boolean n) { alive = n; } + + + /** + * Move 1 unit of time's worth of distance. I.e. increment xPos by xVel + * and yPos by yVel. If we move off an edge of the screen move us back + * in the opposite edge. + */ + void clockTick() { + oldXPos = xPos; + oldYPos = yPos; + xPos = (xPos + xVel) % getGame().getWidth(); + if(xPos < 0) + xPos += getGame().getWidth(); + yPos = (yPos + yVel) % getGame().getHeight(); + if(yPos < 0) + yPos += getGame().getHeight(); + } + + void accelerate(double dXVel, double dYVel) { + xVel += dXVel; + yVel += dYVel; + } + + void die() { + getGame().getRegistry().unregister(this); + } + + abstract int getSize(); + + /** resolve the effects of colliding with a space object. + * @param obj the space object that this object is colliding with. + */ + abstract void handleCollision(SpaceObject obj); +} diff --git a/docs/examples/spacewar/Timer.java b/docs/examples/spacewar/Timer.java new file mode 100644 index 000000000..1f4a992a7 --- /dev/null +++ b/docs/examples/spacewar/Timer.java @@ -0,0 +1,53 @@ +/* + +Copyright (c) Xerox Corporation 1998-2002. All rights reserved. + +Use and copying of this software and preparation of derivative works based +upon this software are permitted. Any distribution of this software or +derivative works must comply with all applicable United States export control +laws. + +This software is made available AS IS, and Xerox Corporation makes no warranty +about the software, its performance or its conformity to any specification. + +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| +|<--- this code is formatted to fit into 80 columns --->| + + +Timer.java +Part of the Spacewar system. + +*/ + +package spacewar; + + +class Timer extends Thread { + + private final static int TICK_PERIOD = 40; // time between ticks in millis + + private Game game; + + Game getGame() { return game; } + + Timer (Game theGame) { + super("Timer"); + game = theGame; + } + + public void run() { + long t1, tdiff; + while (true) { + t1 = System.currentTimeMillis(); + getGame().clockTick(); + tdiff = System.currentTimeMillis() - t1; + if (tdiff < TICK_PERIOD) { + try { + sleep (Math.max(0 , TICK_PERIOD - tdiff)); + } + catch (InterruptedException e) { } + } + } + } +} diff --git a/docs/examples/spacewar/debug.lst b/docs/examples/spacewar/debug.lst new file mode 100644 index 000000000..3de8ddafe --- /dev/null +++ b/docs/examples/spacewar/debug.lst @@ -0,0 +1,2 @@ +@demo.lst +Debug.java diff --git a/docs/examples/spacewar/demo.lst b/docs/examples/spacewar/demo.lst new file mode 100644 index 000000000..dd188af83 --- /dev/null +++ b/docs/examples/spacewar/demo.lst @@ -0,0 +1,19 @@ +@../coordination/lib.lst +Bullet.java +EnergyPacket.java +EnergyPacketProducer.java +Game.java +GameSynchronization.java +Pilot.java +Registry.java +RegistrySynchronization.java +Robot.java +Ship.java +EnsureShipIsAlive.java +SpaceObject.java +Timer.java +SWFrame.java +Display.java +Display1.java +Display2.java +Player.java |