import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * This panel implements a simple arcade game in which the user tries to blow * up a "submarine" (a black oval) by dropping "depth charges" (a red disk) from * a "boat" (a blue roundrect). The user moves the boat with the left- and * right-arrow keys and drops the depth charge with the down-arrow key. * The sub moves left and right erratically along the bottom of the panel. * This class contains a main() routine to allow it to be run as a program. */ public class SubKiller extends JPanel { public static void main(String[] args) { JFrame window = new JFrame("Sub Killer Game"); SubKiller content = new SubKiller(); window.setContentPane(content); window.setSize(600, 480); window.setLocation(100,100); window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); window.setResizable(false); // User can't change the window's size. window.setVisible(true); } //------------------------------------------------------------------------ private Timer timer; // Timer that drives the animation. private int width, height; // The size of the panel -- the values are set // the first time the paintComponent() method // is called. This class is not designed to // handle changes in size; once the width and // height have been set, they are not changed. // Note that width and height cannot be set // in the constructor because the width and // height of the panel have not been set at // the time that the constructor is called. private Boat boat; // The boat, bomb, and sub objects are defined private Bomb bomb; // by nested classes Boat, Bomb, and Submarine, private Submarine sub; // which are defined later in this class. // Note that the objects are created in the // paintComponent() method, after the width // and height of the panel are known. /** * The constructor sets the background color of the panel, creates the * timer, and adds a KeyListener, FocusListener, and MouseListener to the * panel. These listeners, as well as the ActionListener for the timer * are defined by anonymous inner classes. The timer will run only * when the panel has the input focus. */ public SubKiller() { setBackground( new Color(0,200,0) ); ActionListener action = new ActionListener() { // Defines the action taken each time the timer fires. public void actionPerformed(ActionEvent evt) { if (boat != null) { boat.updateForNewFrame(); bomb.updateForNewFrame(); sub.updateForNewFrame(); } repaint(); } }; timer = new Timer( 30, action ); // Fires every 30 milliseconds. addMouseListener( new MouseAdapter() { // The mouse listener simply requests focus when the user // clicks the panel. public void mousePressed(MouseEvent evt) { requestFocus(); } } ); addFocusListener( new FocusListener() { // The focus listener starts the timer when the panel gains // the input focus and stops the timer when the panel loses // the focus. It also calls repaint() when these events occur. public void focusGained(FocusEvent evt) { timer.start(); repaint(); } public void focusLost(FocusEvent evt) { timer.stop(); repaint(); } } ); addKeyListener( new KeyAdapter() { // The key listener responds to keyPressed events on the panel. Only // the left-, right-, and down-arrow keys have any effect. The left- and // right-arrow keys move the boat while down-arrow releases the bomb. public void keyPressed(KeyEvent evt) { int code = evt.getKeyCode(); // Which key was pressed? if (code == KeyEvent.VK_LEFT) { // Move the boat left. (If this moves the boat out of the frame, its // position will be adjusted in the boat.updateForNewFrame() method.) boat.centerX -= 15; } else if (code == KeyEvent.VK_RIGHT) { // Move the boat right. (If this moves boat out of the frame, its // position will be adjusted in the boat.updateForNewFrame() method.) boat.centerX += 15; } else if (code == KeyEvent.VK_DOWN) { // Start the bomb falling, if it is not already falling. if ( bomb.isFalling == false ) bomb.isFalling = true; } } } ); } // end constructor /** * The paintComponent() method draws the current state of the game. It * draws a gray or cyan border around the panel to indicate whether or not * the panel has the input focus. It draws the boat, sub, and bomb by * calling their respective draw() methods. */ public void paintComponent(Graphics g) { super.paintComponent(g); // Fill panel with background color, green. Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (boat == null) { // The first time that paintComponent is called, it assigns // values to the instance variables. width = getWidth(); height = getHeight(); boat = new Boat(); sub = new Submarine(); bomb = new Bomb(); } if (hasFocus()) g.setColor(Color.CYAN); else { g.setColor(Color.BLACK); g.drawString("CLICK TO ACTIVATE", 20, 30); g.setColor(Color.GRAY); } g.drawRect(0,0,width-1,height-1); // Draw a 3-pixel border. g.drawRect(1,1,width-3,height-3); g.drawRect(2,2,width-5,height-5); boat.draw(g); sub.draw(g); bomb.draw(g); } // end paintComponent() /** * This nested class defines the boat. Note that its constructor cannot * be called until the width of the panel is known! */ private class Boat { int centerX, centerY; // Current position of the center of the boat. Boat() { // Constructor centers the boat horizontally, 80 pixels from top. centerX = width/2; centerY = 80; } void updateForNewFrame() { // Makes sure boat has not moved off screen. if (centerX < 0) centerX = 0; else if (centerX > width) centerX = width; } void draw(Graphics g) { // Draws the boat at its current location. g.setColor(Color.BLUE); g.fillRoundRect(centerX - 40, centerY - 20, 80, 40, 20, 20); } } // end nested class Boat /** * This nested class defines the bomb. */ private class Bomb { int centerX, centerY; // Current position of the center of the bomb. boolean isFalling; // If true, the bomb is falling; if false, it // is attached to the boat. Bomb() { // Constructor creates a bomb that is initially attached to boat. isFalling = false; } void updateForNewFrame() { // If bomb is falling, take appropriate action. if (isFalling) { if (centerY > height) { // Bomb has missed the submarine. It is returned to its // initial state, with isFalling equal to false. isFalling = false; } else if (Math.abs(centerX - sub.centerX) <= 36 && Math.abs(centerY - sub.centerY) <= 21) { // Bomb has hit the submarine. The submarine // enters the "isExploding" state. sub.isExploding = true; sub.explosionFrameNumber = 1; isFalling = false; // Bomb reappears on the boat. } else { // If the bomb has not fallen off the panel or hit the // sub, then it is moved down 10 pixels. centerY += 10; } } } void draw(Graphics g) { // Draw the bomb. if ( ! isFalling ) { // If not falling, set centerX and centerY // to show the bomb on the bottom of the boat. centerX = boat.centerX; centerY = boat.centerY + 23; } g.setColor(Color.RED); g.fillOval(centerX - 8, centerY - 8, 16, 16); } } // end nested class Bomb /** * This nested class defines the sub. Note that its constructor cannot * be called until the width of the panel is known! */ private class Submarine { int centerX, centerY; // Current position of the center of the sub. boolean isMovingLeft; // Tells whether the sub is moving left or right boolean isExploding; // Set to true when the sub is hit by the bomb. int explosionFrameNumber; // If the sub is exploding, this is the number // of frames since the explosion started. Submarine() { // Create the sub at a random location 40 pixels from bottom. centerX = (int)(width*Math.random()); centerY = height - 40; isExploding = false; isMovingLeft = (Math.random() < 0.5); } void updateForNewFrame() { // Move sub or increase explosionFrameNumber. if (isExploding) { // If the sub is exploding, add 1 to explosionFrameNumber. // When the number reaches 15, the explosion ends and the // sub reappears in a random position. explosionFrameNumber++; if (explosionFrameNumber == 15) { centerX = (int)(width*Math.random()); centerY = height - 40; isExploding = false; isMovingLeft = (Math.random() < 0.5); } } else { // Move the sub. if (Math.random() < 0.04) { // In one frame out of every 25, on average, the sub // reverses its direction of motion. isMovingLeft = ! isMovingLeft; } if (isMovingLeft) { // Move the sub 5 pixels to the left. If it moves off // the left edge of the panel, move it back to the left // edge and start it moving to the right. centerX -= 5; if (centerX <= 0) { centerX = 0; isMovingLeft = false; } } else { // Move the sub 5 pixels to the right. If it moves off // the right edge of the panel, move it back to the right // edge and start it moving to the left. centerX += 5; if (centerX > width) { centerX = width; isMovingLeft = true; } } } } void draw(Graphics g) { // Draw sub and, if it is exploding, the explosion. g.setColor(Color.BLACK); g.fillOval(centerX - 30, centerY - 15, 60, 30); if (isExploding) { // Draw an "explosion" that grows in size as the number of // frames since the start of the explosion increases. g.setColor(Color.YELLOW); g.fillOval(centerX - 4*explosionFrameNumber, centerY - 2*explosionFrameNumber, 8*explosionFrameNumber, 4*explosionFrameNumber); g.setColor(Color.RED); g.fillOval(centerX - 2*explosionFrameNumber, centerY - explosionFrameNumber/2, 4*explosionFrameNumber, explosionFrameNumber); } } } // end nested class Submarine } // end class SubKiller