PublishingVehicleTrackerDemo.java
This version just replaced the game engine DelegatingVehicleTracker with PublishingVehicleTracker, since both classes are thread safe, the program continues to work.
/*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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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 PublishingVehicleTrackerDemo extends JFrame{
public PublishingVehicleTrackerDemo() {
initGUI();
}
private void initGUI() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(10, 10, 400, 400);
setLayout(new BorderLayout());
Map<String, SafePoint> locations = new HashMap<>();
for(int i=0; i<5; i++) {
SafePoint p = new SafePoint(i* GameBoard.TANKWIDTH * 2, i* GameBoard.TANKHEIGHT * 2, 0);
locations.put("tank"+i, p);
}
PublishingVehicleTracker mt = new PublishingVehicleTracker(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 PublishingVehicleTrackerDemo();
}});
}
}
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 PublishingVehicleTracker mt; //thread safe
private static final JButton startButton = new JButton("Start");
public GameBoard(PublishingVehicleTracker 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()) {
SafePoint p = mt.getLocation(id);
if(p.get()[2] == 0) {
p.set(p.get()[0] + GameBoard.MOVESPEED, p.get()[1] + GameBoard.MOVESPEED * 2, 0);
}
}
}
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(SafePoint p: mt.getLocations().values()) {
if (p.get()[2] == 0)
drawTank(grphcs, p.get()[0], p.get()[1]);
}
repaint();
Toolkit.getDefaultToolkit().sync(); //smooth on Linux systems that buffer graphics events.
}
private void cleanDead() {
for(String id: mt.getLocations().keySet()) {
SafePoint p = mt.getLocation(id);
if (overlaps(p.get()[0], p.get()[1], TANKWIDTH, TANKHEIGHT, laserBeam)) {
mt.setLocation(id, -10, -10, 1);
}
if (overlaps(p.get()[0], p.get()[1], 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;
}
}
}
/*****Game Engine********/
class PublishingVehicleTracker {
private final Map<String, SafePoint> locations;
private final Map<String, SafePoint> unmodifiableMap;
public PublishingVehicleTracker(Map<String, SafePoint> locations) {
this.locations = new ConcurrentHashMap<String, SafePoint>(locations);
this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
}
public Map<String, SafePoint> getLocations() {
return unmodifiableMap;
}
public SafePoint getLocation(String id) {
return locations.get(id);
}
public void setLocation(String id, int x, int y, int alive) {
if (!locations.containsKey(id))
throw new IllegalArgumentException("invalid vehicle name: " + id);
locations.get(id).set(x, y, alive);
}
}
class SafePoint {
private int x, y, alive;
private SafePoint(int[] a) {
this(a[0], a[1], a[2]);
}
public SafePoint(SafePoint p) {
this(p.get());
}
public SafePoint(int x, int y, int alive) {
this.set(x, y, alive);
}
public synchronized int[] get() {
return new int[]{x, y, alive};
}
public synchronized void set(int x, int y, int alive) {
this.x = x;
this.y = y;
this.alive = alive;
}
}