package visualizer; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.RenderingHints; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JFrame; import common.Log; import data.GameControlData; import data.Rules; import data.SPL; import data.Teams; /** * @author Michel Bartsch * * This class displays the game-state */ public class GUI extends JFrame { private static final long serialVersionUID = -3694754414830065322L; /** * Some constants defining this GUI`s appearance as their names say. * Feel free to change them and see what happens. */ private static final boolean IS_OSX = System.getProperty("os.name").contains("OS X"); private static final boolean IS_APPLE_JAVA = IS_OSX && System.getProperty("java.version").compareTo("1.7") < 0; private static final String WINDOW_TITLE = "Visualizer"; private static final int DISPLAY_UPDATE_DELAY = 500; private static final String STANDARD_FONT = Font.DIALOG; private static final double STANDARD_FONT_SIZE = 0.08; private static final double STANDARD_FONT_XXL_SIZE = 0.16; private static final double STANDARD_FONT_S_SIZE = 0.05; private static final String TEST_FONT = "Lucida Console"; private static final double TEST_FONT_SIZE = 0.01; private static final String CONFIG_PATH = "config/"; private static final String BACKGROUND = "background"; private static final String WAITING_FOR_PACKAGE = "waiting for package..."; /** Available screens. */ private static final GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); private static final GraphicsDevice device = devices[devices[0].equals( GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()) ? devices.length - 1 : 0]; BufferStrategy bufferStrategy; /** If testmode is on to just display whole GameControlData. */ private boolean testmode = false; /** The last data received to show. */ private GameControlData data = null; /** The background. */ private BufferedImage background; /** The fonts used. */ private Font testFont; private Font standardFont; private Font standardSmallFont; private Font scoreFont; private Font coachMessageFont; /** * Creates a new GUI. */ GUI() { super(WINDOW_TITLE, device.getDefaultConfiguration()); setUndecorated(true); if (IS_APPLE_JAVA && devices.length != 1) { setSize(device.getDefaultConfiguration().getBounds().getSize()); } else { device.setFullScreenWindow(this); } loadBackground(); testFont = new Font(TEST_FONT, Font.PLAIN, (int)(TEST_FONT_SIZE*getWidth())); standardFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(STANDARD_FONT_SIZE*getWidth())); standardSmallFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(STANDARD_FONT_S_SIZE*getWidth())); scoreFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(STANDARD_FONT_XXL_SIZE*getWidth())); coachMessageFont = new Font(Font.DIALOG, Font.PLAIN, (int)(0.033*getWidth())); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { GameStateVisualizer.exit(); } }); if (IS_OSX) { setVisible(false); // without this, keyboard input is missing on OS X } setVisible(true); createBufferStrategy(2); bufferStrategy = getBufferStrategy(); Thread displayUpdater = new Thread() { @Override public void run() { while (true) { update(data); try { Thread.sleep(DISPLAY_UPDATE_DELAY); } catch (InterruptedException e) {} } } }; displayUpdater.start(); } /** * This toggles the visualizerĀ“s testmode on and off. */ public void toggleTestmode() { testmode = !testmode; update(data); } /** * This is called by the Listener after receiving GameControlData to show * them on the gui. * * @param data The GameControlData to show. */ public synchronized void update(GameControlData data) { this.data = data; // Automatically switch between SPL regular soccer and drop-in competitions if (data != null && (data.gameType == GameControlData.GAME_DROPIN) != Rules.league.dropInPlayerMode) { for (Rules league : Rules.LEAGUES) { if (league != Rules.league && league instanceof SPL) { Rules.league = league; break; } } Teams.readTeams(); loadBackground(); } do { do { Graphics g = bufferStrategy.getDrawGraphics(); draw(g); g.dispose(); } while (bufferStrategy.contentsRestored()); bufferStrategy.show(); } while (bufferStrategy.contentsLost()); } /** * This draws the whole visualizer. * * @param g The graphics object to draw on. */ public final void draw(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); g.drawImage(background, 0, 0, null); if (data == null) { drawNoPackage(g); } else if (testmode) { drawTestmode(g); } else { drawTeams(g); drawScores(g); drawTime(g); drawSecState(g); drawState(g); drawSubTime(g); drawPenaltyInfo(g); drawCoachMessages(g); } } /** * This draws something to inform that there is no package to draw. * * @param g The graphics object to draw on. */ private void drawNoPackage(Graphics g) { g.setColor(Color.BLACK); g.setFont(testFont); g.drawString(WAITING_FOR_PACKAGE, (int)(0.2*getWidth()), (int)(0.3*getHeight())); } /** * This draws everything in the package in a simple way, just for testing. * * @param g The graphics object to draw on. */ private void drawTestmode(Graphics g) { g.setColor(Color.BLACK); g.setFont(testFont); int x = getSizeToWidth(0.08); int y = getSizeToHeight(0.3); String[] out = data.toString().split("\n"); for (int i=0; i<out.length; i++) { g.drawString(out[i], x, y); y += testFont.getSize()*1.2; } for (int j=0; j<2; j++) { out = data.team[j].toString().split("\n"); for (int i=0; i<out.length; i++) { g.drawString(out[i], x, y); y += testFont.getSize()*1.2; } } x = getSizeToWidth(0.35); for (int i=0; i<2; i++) { y = getSizeToHeight(0.2); for (int j=0; j<data.team[i].player.length; j++) { out = data.team[i].player[j].toString().split("\n"); for (int k=0; k<out.length; k++) { g.drawString(out[k], x, y); y += testFont.getSize()*1.2; } } x = getSizeToWidth(0.64); } } /** * This draws the teamsĀ“s icons. * * @param g The graphics object to draw on. */ private void drawTeams(Graphics g) { int x = getSizeToWidth(0.01); int y = getSizeToHeight(0.35); int size = getSizeToWidth(0.28); BufferedImage[] icons = new BufferedImage[] { Teams.getIcon(data.team[0].teamNumber), Teams.getIcon(data.team[1].teamNumber)}; ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); for (int i=0; i<2; i++) { g.setColor(Rules.league.teamColor[data.team[i].teamColor]); float scaleFactorX = 1f; float scaleFactorY = 1f; if (icons[i].getWidth() * 1.2f > icons[i].getHeight()) { scaleFactorY = icons[i].getHeight()/(float)icons[i].getWidth(); } else { scaleFactorX = icons[i].getWidth()/(float)icons[i].getHeight(); } int offsetX = (int)((size - size*scaleFactorX)/2); int offsetY = (int)((size - size*scaleFactorY)/2); g.drawImage(icons[i], (i==1 ? x : getWidth()-x-size) + offsetX, y+offsetY, (int)(scaleFactorX*size), (int)(scaleFactorY*size), null); } } /** * This draws the score. * * @param g The graphics object to draw on. */ private void drawScores(Graphics g) { g.setFont(scoreFont); int x = getSizeToWidth(0.34); int y = getSizeToHeight(0.61); int yDiv = getSizeToHeight(0.59); int size = getSizeToWidth(0.12); g.setColor(Color.BLACK); drawCenteredString(g, ":", getWidth()/2-size, yDiv, 2*size); for (int i=0; i<2; i++) { g.setColor(Rules.league.teamColor[data.team[i].teamColor]); drawCenteredString( g, data.team[i].score+"", i==1 ? x : getWidth()-x-size, y, size); } } /** * This draws the main time. * * @param g The graphics object to draw on. */ private void drawTime(Graphics g) { g.setColor(Color.BLACK); g.setFont(standardFont); int x = getSizeToWidth(0.4); int y = getSizeToHeight(0.37); int size = getSizeToWidth(0.2); drawCenteredString(g, formatTime(data.secsRemaining), x, y, size); } /** * This draws the secondary state, for example "First Half". * * @param g The graphics object to draw on. */ private void drawSecState(Graphics g) { g.setColor(Color.BLACK); g.setFont(standardSmallFont); int x = getSizeToWidth(0.4); int y = getSizeToHeight(0.72); int size = getSizeToWidth(0.2); String state; switch (data.secGameState) { case GameControlData.STATE2_NORMAL: if (Rules.league.dropInPlayerMode) { state = ""; } else if (data.firstHalf == GameControlData.C_TRUE) { if (data.gameState == GameControlData.STATE_FINISHED) { state = "Half Time"; } else { state = "First Half"; } } else { if (data.gameState == GameControlData.STATE_INITIAL) { state = "Half Time"; } else { state = "Second Half"; } } break; case GameControlData.STATE2_OVERTIME: state = "Overtime"; break; case GameControlData.STATE2_PENALTYSHOOT: state = "Penalty Shootout"; break; case GameControlData.STATE2_TIMEOUT: state = "Time Out"; break; default: state = ""; } drawCenteredString(g, state, x, y, size); } /** * This draws the state, for example "Initial". * * @param g The graphics object to draw on. */ private void drawState(Graphics g) { g.setColor(Color.BLACK); g.setFont(standardSmallFont); int x = getSizeToWidth(0.4); int y = getSizeToHeight(0.81); int size = getSizeToWidth(0.2); String state; switch (data.gameState) { case GameControlData.STATE_INITIAL: state = "Initial"; break; case GameControlData.STATE_READY: state = "Ready"; break; case GameControlData.STATE_SET: state = "Set"; break; case GameControlData.STATE_PLAYING: state = "Playing"; break; case GameControlData.STATE_FINISHED: state = "Finished"; break; default: state = ""; } drawCenteredString(g, state, x, y, size); } /** * This draws the sub time, for example the ready time. * * @param g The graphics object to draw on. */ private void drawSubTime(Graphics g) { if (data.secondaryTime == 0) { return; } g.setColor(Color.BLACK); g.setFont(standardSmallFont); int x = getSizeToWidth(0.4); int y = getSizeToHeight(0.9); int size = getSizeToWidth(0.2); drawCenteredString(g, formatTime(data.secondaryTime), x, y, size); } /** * This draws the penalty tries and if they scored. * * @param g The graphics object to draw on. */ private void drawPenaltyInfo(Graphics g) { g.setColor(Color.RED); int x = getSizeToWidth(0.05); int y = getSizeToHeight(0.86); int size = getSizeToWidth(0.02); for (int i=0; i<2; i++) { g.setColor(Rules.league.teamColor[data.team[i].teamColor]); for (int j=0; j<data.team[i].penaltyShot; j++) { if ((data.team[i].singleShots & (1<<j)) != 0) { g.fillOval(i==1 ? x+j*2*size : getWidth()-x-(5-j)*2*size-size, y, size, size); } else { g.drawOval(i==1 ? x+j*2*size : getWidth()-x-(5-j)*2*size-size, y, size, size); } } } } /** * This is used to scale sizes depending on the visuaizers width. * * @param size A size between 0.0 (nothing) and 1.0 (full viualizers width). * * @return A size in pixel. */ private int getSizeToWidth(double size) { return (int)(size*getWidth()); } /** * This is used to scale sizes depending on the visuaizers height. * * @param size A size between 0.0 (nothing) and 1.0 (full viualizers height). * * @return A size in pixel. */ private int getSizeToHeight(double size) { return (int)(size*getHeight()); } /** * This simply draws a string horizontal centered on a given position. * * @param g The graphics object to draw on. * @param s The string to draw. * @param x Left position of the area to draw the string in. * @param y Upper position of the area to draw the string in. * @param width The width of the area to draw the string centered in. */ private void drawCenteredString(Graphics g, String s, int x, int y, int width) { int offset = (width - g.getFontMetrics().stringWidth(s)) / 2; g.drawString(s, x+offset, y); } private void drawCoachMessages(Graphics g) { Graphics2D g2 = (Graphics2D) g; //need for setting the thickness of the line of the rectangles for (int i = 0; i < 2; i++) { String coachMessage; try { coachMessage = new String(data.team[i].coachMessage, "UTF-8"); } catch (UnsupportedEncodingException e) { coachMessage = new String(data.team[i].coachMessage); } int p = coachMessage.indexOf(0); if (p != -1) { coachMessage = coachMessage.substring(0, p); } g2.setFont(standardSmallFont); int maxWidth = (getSizeToWidth(0.99) - getSizeToWidth(0.01) - g2.getFontMetrics().stringWidth("Second Half")) / 2; g2.setFont(coachMessageFont); ArrayList<String> rows = new ArrayList<String>(); while (true) { int split = -1; int j; for (j = 0; j < coachMessage.length() && g2.getFontMetrics().stringWidth(coachMessage.substring(0, j + 1)) <= maxWidth; ++j) { if (!Character.isLetter(coachMessage.charAt(j)) || j < coachMessage.length() - 1 && Character.isLowerCase(coachMessage.charAt(j)) && Character.isUpperCase(coachMessage.charAt(j + 1))) { split = j; } } if (j < coachMessage.length()) { rows.add(coachMessage.substring(0, split + 1).trim()); coachMessage = coachMessage.substring(split + 1).trim(); } else { break; } } if (coachMessage.length() > 0) { rows.add(coachMessage); } //Draw the coach label and coach message box g2.setColor(Rules.league.teamColor[data.team[i].teamColor]); if (i == 1) { for (int j = rows.size() - 1; j >= 0; --j) { g2.drawString(rows.get(j), getSizeToWidth(0.01), getSizeToHeight(0.98 - (rows.size() - 1 - j) * 0.05)); } } else { for (int j = rows.size() - 1; j >= 0; --j) { g2.drawString(rows.get(j), getSizeToWidth(0.99) - g2.getFontMetrics().stringWidth(rows.get(j)), getSizeToHeight(0.98 - (rows.size() - 1 - j) * 0.05)); } } } } /** * Formats a time in seconds to a usual looking minutes and seconds time as string. * * @param seconds Time to format in seconds. * * @return Time formated. */ private String formatTime(int seconds) { int displaySeconds = Math.abs(seconds) % 60; int displayMinutes = Math.abs(seconds) / 60; return (seconds < 0 ? "-" : "") + String.format("%02d:%02d", displayMinutes, displaySeconds); } private void loadBackground() { this.background = null; for (String format : new String [] {".png", ".jpeg", ".jpg"}) { try { this.background = ImageIO.read(new File(CONFIG_PATH+Rules.league.leagueDirectory+"/"+BACKGROUND+format)); } catch (IOException e) { } } if (this.background == null) { Log.error("Unable to load background image"); } float scaleFactor = (float)getWidth()/background.getWidth(); Image tmp = (new ImageIcon(background).getImage()).getScaledInstance( (int)(background.getWidth()*scaleFactor), (int)(background.getHeight()*scaleFactor), Image.SCALE_SMOOTH); background = new BufferedImage((int) (background.getWidth() * scaleFactor), (int) (background.getWidth() * scaleFactor), BufferedImage.TYPE_INT_ARGB); background.getGraphics().drawImage(tmp, 0, 0, null); } }