package koth.system;
import koth.game.*;
import koth.game.Action;
import koth.util.Vector;
import koth.user.Human;
import koth.util.Renderer;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferStrategy;
import java.util.Set;
public class Display implements Runnable {
private final Object lock = new Object();
private final Simulator simulator;
private final Animator animator;
private Renderer renderer;
private volatile boolean running, paused;
private float camx, camy, camr;
private float dcamx, dcamy, dcamr;
private volatile float speed;
private boolean waiting;
private Pawn pawn;
private Action action;
private JFrame frame;
private Canvas canvas;
private BufferStrategy strategy;
public Display(Simulator simulator) {
// Create simulation components
this.simulator = simulator;
animator = new Animator(simulator.getGame());
renderer = new Renderer.Isometric();
// Initialize control variables
running = true;
paused = false;
key('=');
camx = dcamx;
camy = dcamy;
camr = dcamr;
waiting = false;
pawn = null;
action = null;
// Create window
frame = new JFrame("Match");
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
key((char)27);
}
});
frame.setIgnoreRepaint(true);
frame.setBackground(Color.BLACK);
frame.setFocusable(false);
frame.setFocusTraversalKeysEnabled(false);
// Create canvas with double-buffering
canvas = new Canvas();
canvas.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
mouse(e.getX(), e.getY(), e.getButton());
}
});
canvas.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
key(e.getKeyChar());
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
key('4');
break;
case KeyEvent.VK_RIGHT:
key('6');
break;
case KeyEvent.VK_UP:
key('8');
break;
case KeyEvent.VK_DOWN:
key('2');
break;
}
}
});
canvas.setIgnoreRepaint(true);
canvas.setBackground(Color.BLACK);
canvas.setFocusable(true);
canvas.setFocusTraversalKeysEnabled(false);
frame.add(canvas);
frame.pack();
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
canvas.createBufferStrategy(2);
strategy = canvas.getBufferStrategy();
}
private void key(char c) {
switch (c) {
// Use ESCAPE to exit
case KeyEvent.VK_ESCAPE:
running = false;
// TODO interrupt?
break;
// Use SPACE to toggle pause
case ' ':
paused = !paused;
break;
// Use TAB to switch view mode
case '\t':
synchronized (lock) {
if (renderer instanceof Renderer.Isometric)
renderer = new Renderer.Orthogonal();
else
renderer = new Renderer.Isometric();
}
break;
// Use + to zoom
case '+':
synchronized (lock) {
dcamr /= 1.4f;
}
break;
// Use - to unzoom
case '-':
synchronized (lock) {
dcamr *= 1.4f;
}
break;
// Use '*' to increase speed
case '*':
speed *= 2;
break;
// Use '/' to decrease speed
case '/':
speed /= 2;
break;
// Use '=' to reset camera and time
case '=':
synchronized (lock) {
Board board = simulator.getGame().getBoard();
dcamx = (board.getMax().getX() - board.getMin().getX()) * 0.5f;
dcamy = (board.getMax().getY() - board.getMin().getY()) * 0.5f;
dcamr = 2 * (float)Math.sqrt(dcamx * dcamx + dcamy * dcamy);
dcamx += board.getMin().getX();
dcamy += board.getMin().getY();
speed = 2;
}
break;
// Use digits to move camera
case '2':
synchronized (lock) {
dcamy -= dcamr * 0.25f;
}
break;
case '8':
synchronized (lock) {
dcamy += dcamr * 0.25f;
}
break;
case '4':
synchronized (lock) {
dcamx += dcamr * 0.25f;
}
break;
case '6':
synchronized (lock) {
dcamx -= dcamr * 0.25f;
}
break;
}
}
private void mouse(int x, int y, int button) {
switch (button) {
// Left button to select nearest pawn
case MouseEvent.BUTTON1:
synchronized (lock) {
if (waiting) {
Point.Float loc = renderer.unproject(x, y, 0);
float dist = 0;
this.pawn = null;
for (Pawn pawn : simulator.getGame().getPawns(simulator.getTeam())) {
float dx = pawn.getLocation().getX() - loc.x;
float dy = pawn.getLocation().getY() - loc.y;
float d = (float) Math.sqrt(dx * dx + dy * dy);
if (this.pawn == null || d < dist) {
this.pawn = pawn;
dist = d;
}
}
}
}
break;
// Right button to move selected pawn
case MouseEvent.BUTTON3:
synchronized (lock) {
if (pawn != null) {
// There already is a selected pawn, check if we clicked on the HUD
Point.Float p = renderer.project(pawn.getLocation().getX(), pawn.getLocation().getY(), 0.35f);
float s = 0.8f * (float) p.distance(renderer.project(pawn.getLocation().getX() + 1, pawn.getLocation().getY() + 1, 0.35f));
float m = s * 0.5f;
float b = s * 0.1f;
// Check if a new stance was selected
Stance stance = null;
if (y >= p.y - s * 2 - b && y <= p.y - s - b) {
if (x >= p.x - s - b - s * 0.5f && x <= p.x - b - s * 0.5f)
stance = Stance.Rock;
else if (x >= p.x - s * 0.5f && x <= p.x + s * 0.5f)
stance = Stance.Paper;
else if (x >= p.x + s + b - s * 0.5f && x <= p.x + 2 * s + b - s * 0.5f)
stance = Stance.Scissors;
}
if (stance != null) {
if (stance != pawn.getStance())
action = new Action(pawn, stance);
waiting = false;
lock.notifyAll();
return;
}
// Check if turn was skipped
if (0 == 1) { // TODO button to skip turn
pawn = null;
waiting = false;
lock.notifyAll();
return;
}
// Check if a destination was chosen
Point.Float t = renderer.unproject(x, y, 0);
t.x -= pawn.getLocation().getX();
t.y -= pawn.getLocation().getY();
if (t.x * t.x + t.y * t.y < 0.2f)
break;
Move move;
if (Math.abs(t.x) > Math.abs(t.y))
move = t.x >= 0 ? Move.East : Move.West;
else
move = t.y >= 0 ? Move.North : Move.South;
action = new Action(pawn, move);
waiting = false;
lock.notifyAll();
pawn = null;
}
}
break;
// Middle button to move camera
case MouseEvent.BUTTON2:
synchronized (lock) {
Point.Float p = renderer.unproject(x, y, 0);
dcamx = p.x;
dcamy = p.y;
}
break;
}
}
private void drawString(Graphics2D g, String txt, float cx, float cy, int h, int v) {
Rectangle2D rect = g.getFontMetrics().getStringBounds(txt, g);
cx -= rect.getWidth() * (1 - h) * 0.5f;
cy += rect.getHeight() * (1 - v) * 0.5f;
g.drawString(txt, cx, cy);
}
private String name(int team) {
synchronized (lock) {
return simulator.getAiFactories().get(team).getName() + "[" + team + "]";
}
}
private void animate(float dt, Graphics2D g, int w, int h) {
// Update camera
final float halflife = 0.2f;
float c = (float)Math.pow(2, -dt / halflife);
synchronized (lock) {
camx = c * camx + (1 - c) * dcamx;
camy = c * camy + (1 - c) * dcamy;
camr = c * camr + (1 - c) * dcamr;
}
// Clear background
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
// Compute and paint new frame
animator.step(dt);
Set<Renderer.Cube> cubes = animator.getCubes();
synchronized (lock) {
renderer.setCubes(cubes);
renderer.setCamera(camx, camy, 0, camr);
renderer.setViewport(w, h);
renderer.paint(g);
}
// Check for human player and if a pawn is selected
synchronized (lock) {
if (pawn != null) {
Point.Float p = renderer.project(pawn.getLocation().getX(), pawn.getLocation().getY(), 0.35f);
float s = 0.8f * (float)p.distance(renderer.project(pawn.getLocation().getX() + 1, pawn.getLocation().getY() + 1, 0.35f));
float m = s * 0.5f;
float b = s * 0.1f;
// Draw crosshair to show selected pawn
g.setColor(Color.WHITE);
g.fill(new Rectangle2D.Float(p.x - s, p.y - s, b, m));
g.fill(new Rectangle2D.Float(p.x - s, p.y - s, m, b));
g.fill(new Rectangle2D.Float(p.x + s - b, p.y - s, b, m));
g.fill(new Rectangle2D.Float(p.x + s - m, p.y - s, m, b));
g.fill(new Rectangle2D.Float(p.x - s, p.y + s - m, b, m));
g.fill(new Rectangle2D.Float(p.x - s, p.y + s - b, m, b));
g.fill(new Rectangle2D.Float(p.x + s - b, p.y + s - m, b, m));
g.fill(new Rectangle2D.Float(p.x + s - m, p.y + s - b, m, b));
if (waiting) {
// Draw buttons to switch stance
g.setColor(animator.getStanceColor(pawn.getStance() == Stance.Rock ? null : Stance.Rock));
g.fill(new Rectangle.Float(p.x - s - b - s * 0.5f, p.y - s * 2 - b, s, s));
g.setColor(animator.getStanceColor(pawn.getStance() == Stance.Paper ? null : Stance.Paper));
g.fill(new Rectangle.Float(p.x - s * 0.5f, p.y - s * 2 - b, s, s));
g.setColor(animator.getStanceColor(pawn.getStance() == Stance.Scissors ? null : Stance.Scissors));
g.fill(new Rectangle.Float(p.x + s + b - s * 0.5f, p.y - s * 2 - b, s, s));
}
}
if (waiting) {
// Draw skip button
// TODO skip button
}
}
// Draw HUD
synchronized (lock) {
// Print info about current turn
g.setColor(animator.getTeamColor(simulator.getTeam()));
g.setFont(g.getFont().deriveFont(20.0f));
drawString(g, "Turn " + simulator.getTurn() + ", " + name(simulator.getTeam()) + " (" + simulator.getPoints() + " remaining)", 10, 10, 1, -1);
// Show if game is paused or finished
String text = null;
if (simulator.getGame().isDraw())
text = "DRAW";
else if (simulator.getGame().hasWinner())
text = "TEAM " + simulator.getGame().getWinner() + " WINS";
else if (paused)
text = "PAUSED";
if (text != null) {
g.setColor(Color.WHITE);
g.setFont(g.getFont().deriveFont(100.0f));
drawString(g, text, w / 2, h / 2, 0, 0);
}
}
}
private void animate(float dt) {
do {
Graphics2D g = null;
try {
g = (Graphics2D)strategy.getDrawGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
animate(dt, g, canvas.getWidth(), canvas.getHeight());
} finally {
if (g != null)
g.dispose();
}
strategy.show();
} while (strategy.contentsLost());
}
private void animate() {
long before = System.nanoTime();
while (running) {
long now = System.nanoTime();
float dt = (now - before) * 0.000000001f * speed;
if (dt < 0.001f)
dt = 0.001f;
animate(dt);
before = now;
try {
Thread.sleep(20);
} catch (InterruptedException e) {}
}
}
private void compute() {
while (true) {
// If enough animations in queue, sleep
synchronized (animator) {
try {
while (!animator.isExhausted() || paused) {
if (!running || simulator.getGame().isFinished())
return;
animator.wait(100);
}
} catch (InterruptedException e) {}
}
if (!running || simulator.getGame().isFinished())
return;
// If it is a human, wait until graphical interaction
if (simulator.getAi() instanceof Human) {
Human human = (Human)simulator.getAi();
synchronized (lock) {
waiting = true;
pawn = null;
action = null;
while (waiting && running) {
try {
lock.wait(100);
} catch (InterruptedException e) {}
}
human.set(action);
waiting = false;
pawn = null;
action = null;
}
}
// Play and register events
simulator.play(animator);
}
}
@Override
public void run() {
frame.setVisible(true);
Thread thread = new Thread() {
@Override
public void run() {
animate();
}
};
thread.start();
compute();
try {
thread.join();
} catch (InterruptedException e) {}
frame.dispose();
}
}