Site Search:

MonitorVehicleTrackerDemo.java

<Back




MonitorVehicleTrackerDemo.java update the enemy positions in a SwingWorker thread other than swing event dispatch thread, so that the GUI can be more responsive, even though the vehicle collections are large and I/O operations such as retrieving data from GPS or network took long time.

All state variables are correctly published. Since only state variables shared between SwingWorker thread and swing event dispatch thread is an instance of the thread safe class MonitorVehicleTracker, the program still have thread safety.

The SwingWorker thread is started by pressing a Start button. Since the GameBoard now needs to response to actions initialized from Timer or JButton, the actionCommand is checked in ActionPerformed method to tell which is which.

/*press arrow key to move, press space key to shoot*/
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;

/******* game graphics ********/
public class MonitorVehicleTrackerDemo extends JFrame{
    
    public MonitorVehicleTrackerDemo() {
        initGUI();
    }
    
    private void initGUI() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(10, 10, 400, 400);
        setLayout(new BorderLayout());
        
        Map<String, MutablePoint> locations = new HashMap<>();
        for(int i=0; i<5; i++) {
            MutablePoint p = new MutablePoint();
            p.x = i* GameBoard.TANKWIDTH * 2;
            p.y = i* GameBoard.TANKHEIGHT * 2;
            locations.put("tank"+i, p);
        }
        
        MonitorVehicleTracker mt = new MonitorVehicleTracker(locations);
        
        final GameBoard paintPanel = new GameBoard(mt);
        add(paintPanel, BorderLayout.CENTER);
        paintPanel.startTimer();
        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new MonitorVehicleTrackerDemo();
            }});
    }
}

class GameBoard extends JPanel implements KeyListener, ActionListener {
    public static final int HEIGHT = 300;
    public static final int WIDTH = 400;
    public static final int TANKWIDTH = 30;
    public static final int TANKHEIGHT = 10;
    public static final int MOVESPEED = 2;
    public static final int TARGETX = 100;
    public static final int TARGETY = 200;
    public static final int DELAY = 70;
    private static int dx = WIDTH / 2;
    private static int dy = HEIGHT;
    private static final Rectangle2D yourtank = new Rectangle2D.Double(dx, dy, TANKWIDTH,
            TANKHEIGHT); //reference to Rectangle2D is final, but the object's states can change
    //{System.out.println(javax.swing.SwingUtilities.isEventDispatchThread());}
    private static final Rectangle2D laserBeam = new Rectangle2D.Double(-10, -10, 0,
            0);
    private volatile MonitorVehicleTracker mt//thread safe
    private static final JButton startButton = new JButton("Start");

    public GameBoard(MonitorVehicleTracker mt) {
        this.addKeyListener(this);
        this.setBackground(Color.white);
        this.setFocusable(true);
        this.mt = mt;
        setDoubleBuffered(true);
        startButton.setText("Start");
        startButton.setActionCommand("Start");
        startButton.addActionListener(this);
        this.add(startButton);
    }
    
    //avoid starting timer in constructor (partially constructed this object).
    public void startTimer() {
        new Timer(DELAY/2, this).start();
    }
    
    private class Animator extends SwingWorker<Void, Void> {
        @Override
        protected Void doInBackground() throws Exception {
            while(!isCancelled()) {
                Thread.sleep(DELAY);
                //System.out.println(javax.swing.SwingUtilities.isEventDispatchThread());
                for(String id: mt.getLocations().keySet()) {
                    MutablePoint p = mt.getLocation(id);
                    if(p.alive
                        mt.setLocation(id, p.x + GameBoard.MOVESPEED, p.y + GameBoard.MOVESPEED * 2, true);
                }
            }
            return null;
        }
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getActionCommand() == "Start") {
            new Animator().execute();
            startButton.setEnabled(false);
            startButton.setVisible(false);
        }
        //System.out.println(javax.swing.SwingUtilities.isEventDispatchThread());
        repaint();
    }
    
    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        cleanDead();
        Graphics2D gr = (Graphics2D) grphcs;
        gr.draw(yourtank);
        gr.draw(laserBeam);
        for(MutablePoint p: mt.getLocations().values()) {
            if (p.alive)
                drawTank(grphcs, p.x, p.y);
        }
        repaint();
        Toolkit.getDefaultToolkit().sync(); //smooth on Linux systems that buffer graphics events.
    }

    private void cleanDead() {
        for(String id: mt.getLocations().keySet()) {
            MutablePoint p = mt.getLocation(id);
            if (overlaps(p.x, p.y, TANKWIDTH, TANKHEIGHT, laserBeam)) {
                mt.setLocation(id, -10, -10, false);
            }
            if (overlaps(p.x, p.y, TANKWIDTH, TANKHEIGHT, yourtank)) {
                this.removeKeyListener(this);
            }
        }
    }

    private boolean overlaps(int x, int y, int width, int height, Rectangle2D r) {
        return x < r.getX() + r.getWidth() && x + width > r.getX()
                && y < r.getY() + r.getHeight() && y + height > r.getY();
    }
    
    private void drawTank(Graphics g, int x, int y) {
        g.setColor(Color.yellow);
        g.draw3DRect(x, y, TANKWIDTH, TANKHEIGHT, true);
    }

    @Override
    public void keyTyped(KeyEvent e) {
        System.out.println(e.getKeyCode());
        shoot();
        repaint();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        moveRec(e);
        repaint();
    }

    @Override
    public void keyReleased(KeyEvent e) {
        laserBeam.setRect(dx + TANKWIDTH/2, -10, 0, 0);  //hide it
        repaint();
    }

    public void shoot() {
        laserBeam.setRect(dx + TANKWIDTH/2, 0, 2, dy);
    }

    public void moveRec(KeyEvent evt) {
        switch (evt.getKeyCode()) {
        case KeyEvent.VK_LEFT:
            dx -= MOVESPEED;
            yourtank.setRect(dx, dy, TANKWIDTH, TANKHEIGHT);
            break;
        case KeyEvent.VK_RIGHT:
            dx += MOVESPEED;
            yourtank.setRect(dx, dy, TANKWIDTH, TANKHEIGHT);
            break;
        case KeyEvent.VK_UP:
            dy -= MOVESPEED;
            yourtank.setRect(dx, dy, TANKWIDTH, TANKHEIGHT);
            break;
        case KeyEvent.VK_DOWN:
            if (dy < HEIGHT)
                dy += MOVESPEED;
            yourtank.setRect(dx, dy, TANKWIDTH, TANKHEIGHT);
            break;
        }
    }
}

class MonitorVehicleTracker {
    private final Map<String, MutablePoint> locations;

    public MonitorVehicleTracker(Map<String, MutablePoint> locations) {
        this.locations = deepCopy(locations);
    }

    public synchronized Map<String, MutablePoint> getLocations() {
        return deepCopy(locations);
    }

    public synchronized MutablePoint getLocation(String id) {
        MutablePoint loc = locations.get(id);
        return loc == null ? null : new MutablePoint(loc);
    }

    public synchronized void setLocation(String id, int x, int y, boolean alive) {
        MutablePoint loc = locations.get(id);
        if (loc == null)
            throw new IllegalArgumentException("No such ID: " + id);
        loc.x = x;
        loc.y = y;
        loc.alive = alive;
    }

    private static Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> m) {
        Map<String, MutablePoint> result = new HashMap<String, MutablePoint>();

        for (String id : m.keySet())
            result.put(id, new MutablePoint(m.get(id)));

        return Collections.unmodifiableMap(result);
    }
}

class MutablePoint {
    public int x, y;
    public boolean alive;

    public MutablePoint() {
        x = 0;
        y = 0;
        alive = true;
    }

    public MutablePoint(MutablePoint p) {
        this.x = p.x;
        this.y = p.y;
        this.alive = p.alive;
    }

}