package apps.kiosk; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.border.TitledBorder; import apps.common.*; import apps.kiosk.games.*; import player.GamePlayer; import player.gamer.Gamer; import server.GameServer; import server.event.ServerConnectionErrorEvent; import server.event.ServerIllegalMoveEvent; import server.event.ServerTimeoutEvent; import util.configuration.LocalResourceLoader; import util.game.CloudGameRepository; import util.game.Game; import util.game.GameRepository; import util.logging.GamerLogger; import util.match.Match; import util.observer.Event; import util.observer.Observer; import util.reflection.ProjectSearcher; /** * Kiosk is a program for running two-player human-vs-computer matches * with clean visualizations and intuitive human interfaces. Originally * designed for running matches against players implemented using the * standard Java stack, it can also connect to remote players as need be. * * @author Sam */ @SuppressWarnings("serial") public final class Kiosk extends JPanel implements ActionListener, ItemListener, Observer { public static final String remotePlayerString = "[REMOTE PLAYER]"; private static void createAndShowGUI(Kiosk serverPanel) { JFrame frame = new JFrame("Gaming Kiosk"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(serverPanel); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { NativeUI.setNativeUI(); final Kiosk serverPanel = new Kiosk(); javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(serverPanel); } }); } private final JPanel managerPanel; private final JTextField playClockTextField; private final JTextField startClockTextField; private final JButton runButton; private final JList selectedGame; private final JCheckBox flipRoles; private final PublishButton publishButton; private final JPanel theGUIPanel; private final JComboBox playerComboBox; private List<Class<?>> gamers = null; private final JTextField computerAddress; private final GameRepository theRepository; public Kiosk() { super(new GridBagLayout()); LocalResourceLoader.setLocalResourceLoader(this); setPreferredSize(new Dimension(1050, 900)); NativeUI.setNativeUI(); GamerLogger.setFileToDisplay("GamePlayer"); SortedSet<AvailableGame> theAvailableGames = new TreeSet<AvailableGame>(); List<Class<?>> theAvailableCanvasList = ProjectSearcher.getAllClassesThatAre(GameCanvas.class); for(Class<?> availableCanvas : theAvailableCanvasList) { try { GameCanvas theCanvas = (GameCanvas) availableCanvas.newInstance(); theAvailableGames.add(new AvailableGame(theCanvas.getGameName(), theCanvas.getGameKey(), availableCanvas)); } catch(Exception e) { ; } } if(theAvailableGames.size() == 0) { theAvailableGames = getFakeAvailableGames(); } flipRoles = new JCheckBox("Flip roles?"); selectedGame = new JList(theAvailableGames.toArray()); selectedGame.setSelectedIndex(0); selectedGame.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane selectedGamePane = new JScrollPane(selectedGame); computerAddress = new JTextField("player.ggp.org:80"); playerComboBox = new JComboBox(); playerComboBox.addItemListener(this); new FindGamersThread().start(); runButton = new JButton("Run!"); runButton.addActionListener(this); publishButton = new PublishButton("Publish online!"); startClockTextField = new JTextField("30"); playClockTextField = new JTextField("10"); managerPanel = new JPanel(new GridBagLayout()); startClockTextField.setColumns(15); playClockTextField.setColumns(15); int nRowCount = 1; managerPanel.setBorder(new TitledBorder("Kiosk Control")); managerPanel.add(new JLabel("Opponent:"), new GridBagConstraints(0, nRowCount, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(playerComboBox, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(computerAddress, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(new JLabel("Start Clock:"), new GridBagConstraints(0, nRowCount, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(startClockTextField, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(new JLabel("Play Clock:"), new GridBagConstraints(0, nRowCount, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(playClockTextField, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(flipRoles, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(new JLabel("Game:"), new GridBagConstraints(0, nRowCount, 1, 1, 0.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(selectedGamePane, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 5.0, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(new JLabel("Publishing:"), new GridBagConstraints(0, nRowCount, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(publishButton, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 5, 5)); //managerPanel.add(new ConsolePanel(), new GridBagConstraints(0, nRowCount++, 2, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 5, 5)); managerPanel.add(runButton, new GridBagConstraints(1, nRowCount++, 1, 1, 0.0, 1.0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); JPanel gamePanel = new JPanel(new GridBagLayout()); gamePanel.setBorder(new TitledBorder("Game Kiosk")); theGUIPanel = new JPanel(); gamePanel.add(theGUIPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 5, 5)); this.add(managerPanel, new GridBagConstraints(0, 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 5, 5)); this.add(gamePanel, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 5, 5)); // Start up the gamers! try { theHumanGamer = new KioskGamer(theGUIPanel); theHumanPlayer = new GamePlayer(DEFAULT_HUMAN_PORT, theHumanGamer); theHumanPlayer.start(); } catch(Exception e) { e.printStackTrace(); } // This is where we get the rulesheets from. Each game has a corresponding // game (with rulesheet) stored on this repository server. Changing this is // likely to break things unless you know what you're doing. theRepository = new CloudGameRepository("http://games.ggp.org/"); } // Load the gamers asynchronously, so that we don't stall when loading // gamers that require Python/Clojure runtimes to be activated before // we can look up their names. class FindGamersThread extends Thread { @Override public void run() { gamers = ProjectSearcher.getAllClassesThatAre(Gamer.class); List<Class<?>> gamersCopy = new ArrayList<Class<?>>(gamers); for(Class<?> gamer : gamersCopy) { try { Gamer g = (Gamer) gamer.newInstance(); // TODO: Come up with a more elegant way to exclude // the HumanPlayer, which doesn't fit the Kiosk model. if(g.getName().equals("Human")) throw new RuntimeException(); playerComboBox.addItem(g.getName()); } catch(Exception ex) { gamers.remove(gamer); } } playerComboBox.addItem(remotePlayerString); } } class AvailableGame implements Comparable<AvailableGame> { private String gameName, kifFile; private Class<?> theCanvasClass; public AvailableGame(String gameName, String kifFile, Class<?> theCanvasClass) { this.gameName = gameName; this.kifFile = kifFile; this.theCanvasClass = theCanvasClass; } public String toString() { return gameName; } public GameCanvas getCanvas() { try { return (GameCanvas)theCanvasClass.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } @Override public int compareTo(AvailableGame o) { return gameName.compareTo(((AvailableGame)o).gameName); } } private GamePlayer theComputerPlayer = null; private GamePlayer theHumanPlayer = null; private KioskGamer theHumanGamer; private GameServer kioskServer = null; private static final int DEFAULT_HUMAN_PORT = 3333; private static final int DEFAULT_COMPUTER_PORT = 3334; @Override public void actionPerformed(ActionEvent e) { if(e.getSource() == runButton) { try { AvailableGame theGame = (AvailableGame) (selectedGame.getSelectedValue()); Game game = theRepository.getGame(theGame.kifFile); String matchId = "kiosk." + theGame.kifFile + "-" + System.currentTimeMillis(); int startClock = Integer.valueOf(startClockTextField.getText()); int playClock = Integer.valueOf(playClockTextField.getText()); Match match = new Match(matchId, startClock, playClock, game); theHumanGamer.setCanvas(theGame.getCanvas()); // Stop old player if it's not the right type String computerPlayerName = (String) playerComboBox.getSelectedItem(); if(theComputerPlayer != null && !theComputerPlayer.getGamer().getName().equals(computerPlayerName)) { theComputerPlayer.interrupt(); Thread.sleep(100); theComputerPlayer = null; } // Start a new player if necessary if(theComputerPlayer == null) { Gamer gamer = null; if(!playerComboBox.getSelectedItem().equals(remotePlayerString)) { Class<?> gamerClass = gamers.get(playerComboBox.getSelectedIndex()); try { gamer = (Gamer) gamerClass.newInstance(); } catch(Exception ex) { throw new RuntimeException(ex); } theComputerPlayer = new GamePlayer(DEFAULT_COMPUTER_PORT, gamer); theComputerPlayer.start(); System.out.println("Kiosk has started a gamer named " + theComputerPlayer.getGamer().getName() + "."); } } List<String> hosts = new ArrayList<String>(); List<Integer> ports = new ArrayList<Integer>(); List<String> playerNames = new ArrayList<String>(); if(!flipRoles.isSelected()) { hosts.add("127.0.0.1"); ports.add(theHumanPlayer.getGamerPort()); playerNames.add("Human"); } if(playerComboBox.getSelectedItem().equals(remotePlayerString)) { try { String[] splitAddress = computerAddress.getText().split(":"); String hostname = splitAddress[0]; int port = Integer.parseInt(splitAddress[1]); hosts.add(hostname); ports.add(port); playerNames.add("Computer"); } catch(Exception ex) { ex.printStackTrace(); return; } } else { hosts.add("127.0.0.1"); ports.add(theComputerPlayer.getGamerPort()); playerNames.add("Computer"); } if(flipRoles.isSelected()) { hosts.add("127.0.0.1"); ports.add(theHumanPlayer.getGamerPort()); playerNames.add("Human"); } if (kioskServer != null && !kioskServer.getMatch().isCompleted()) { // TODO: Figure out what the right behavior should be when the user // interrupts a match by trying to start another match. } GamerLogger.startFileLogging(match, "kiosk"); kioskServer = new GameServer(match, hosts, ports, playerNames); kioskServer.givePlayerUnlimitedTime((flipRoles.isSelected()? 1 : 0)); kioskServer.addObserver(theHumanGamer); kioskServer.addObserver(this); kioskServer.start(); publishButton.setServer(kioskServer); } catch(Exception ex) { ex.printStackTrace(); } } } @Override public void itemStateChanged(ItemEvent e) { if(e.getSource() == playerComboBox) { if(playerComboBox.getSelectedItem().equals(remotePlayerString)) { computerAddress.setVisible(true); } else { computerAddress.setVisible(false); } validate(); } } @Override public void observe(Event event) { if(event instanceof ServerIllegalMoveEvent) { ServerIllegalMoveEvent x = (ServerIllegalMoveEvent)event; System.err.println("Got illegal move [" + x.getMove() + "] by role [" + x.getRole() + "]."); } else if (event instanceof ServerTimeoutEvent) { ServerTimeoutEvent x = (ServerTimeoutEvent)event; System.err.println("Timeout when communicating with role [" + x.getRole() + "]."); } else if (event instanceof ServerConnectionErrorEvent) { ServerConnectionErrorEvent x = (ServerConnectionErrorEvent)event; System.err.println("Connection error when communicating with role [" + x.getRole() + "]."); } } // TODO: Fix things so that this isn't necessary. // Right now this is here for applets/self-executing-JARs. private void addToSet(Set<AvailableGame> theSet, Class<?> availableCanvas) { try { GameCanvas theCanvas = (GameCanvas) availableCanvas.newInstance(); theSet.add(new AvailableGame(theCanvas.getGameName(), theCanvas.getGameKey(), availableCanvas)); } catch(Exception e) { e.printStackTrace(); } } private SortedSet<AvailableGame> getFakeAvailableGames() { SortedSet<AvailableGame> theAvailableGames = new TreeSet<AvailableGame>(); addToSet(theAvailableGames, BattleCanvas.class); addToSet(theAvailableGames, BiddingTicTacToeCanvas.class); addToSet(theAvailableGames, BlockerCanvas.class); addToSet(theAvailableGames, BreakthroughCanvas.class); addToSet(theAvailableGames, BreakthroughHolesCanvas.class); addToSet(theAvailableGames, BreakthroughSmallCanvas.class); addToSet(theAvailableGames, CephalopodCanvas.class); addToSet(theAvailableGames, CheckersCanvas.class); addToSet(theAvailableGames, CheckersSmallCanvas.class); addToSet(theAvailableGames, CheckersTinyCanvas.class); addToSet(theAvailableGames, ChessCanvas.class); addToSet(theAvailableGames, ChickenTicTacToeCanvas.class); addToSet(theAvailableGames, ConnectFiveCanvas.class); addToSet(theAvailableGames, ConnectFourCanvas.class); addToSet(theAvailableGames, FFACanvas.class); addToSet(theAvailableGames, GoldenRectangleCanvas.class); addToSet(theAvailableGames, KnightFightCanvas.class); addToSet(theAvailableGames, KnightthroughCanvas.class); addToSet(theAvailableGames, NumberTicTacToeCanvas.class); addToSet(theAvailableGames, PawnWhoppingCanvas.class); addToSet(theAvailableGames, PentagoCanvas.class); addToSet(theAvailableGames, QyshinsuCanvas.class); addToSet(theAvailableGames, TicTacToeCanvas.class); addToSet(theAvailableGames, TTCC4Canvas.class); addToSet(theAvailableGames, TTCC4SmallCanvas.class); addToSet(theAvailableGames, TTCCanvas.class); addToSet(theAvailableGames, TTTxNineCanvas.class); return theAvailableGames; } }