package org.ggp.base.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.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.border.TitledBorder; import org.ggp.base.player.GamePlayer; import org.ggp.base.player.gamer.Gamer; import org.ggp.base.player.gamer.exception.AbortingException; import org.ggp.base.server.GameServer; import org.ggp.base.server.event.ServerConnectionErrorEvent; import org.ggp.base.server.event.ServerIllegalMoveEvent; import org.ggp.base.server.event.ServerTimeoutEvent; import org.ggp.base.util.game.CloudGameRepository; import org.ggp.base.util.game.Game; import org.ggp.base.util.game.GameRepository; import org.ggp.base.util.gdl.grammar.GdlPool; import org.ggp.base.util.logging.GamerLogger; import org.ggp.base.util.match.Match; import org.ggp.base.util.observer.Event; import org.ggp.base.util.observer.Observer; import org.ggp.base.util.reflection.ProjectSearcher; import org.ggp.base.util.symbol.grammar.SymbolPool; import org.ggp.base.util.ui.NativeUI; import org.ggp.base.util.ui.PublishButton; import com.google.common.collect.Lists; /** * 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() { @Override public void run() { createAndShowGUI(serverPanel); } }); } private final JPanel managerPanel; private final JTextField playClockTextField; private final JTextField startClockTextField; private final JButton runButton; private final JList<AvailableGame> selectedGame; private final JCheckBox flipRoles; private final PublishButton publishButton; private final JPanel theGUIPanel; private final JComboBox<String> playerComboBox; private List<Class<? extends Gamer>> gamers = null; private final JTextField computerAddress; private final GameRepository theRepository; public Kiosk() { super(new GridBagLayout()); setPreferredSize(new Dimension(1050, 900)); NativeUI.setNativeUI(); GamerLogger.setFileToDisplay("GamePlayer"); SortedSet<AvailableGame> theAvailableGames = new TreeSet<AvailableGame>(); Set<Class<? extends GameCanvas>> theAvailableCanvasList = ProjectSearcher.GAME_CANVASES.getConcreteClasses(); for(Class<? extends GameCanvas> availableCanvas : theAvailableCanvasList) { try { GameCanvas theCanvas = availableCanvas.newInstance(); theAvailableGames.add(new AvailableGame(theCanvas.getGameName(), theCanvas.getGameKey(), availableCanvas)); } catch(Exception e) { ; } } flipRoles = new JCheckBox("Flip roles?"); selectedGame = new JList<AvailableGame>(theAvailableGames.toArray(new AvailableGame[0])); selectedGame.setSelectedIndex(0); selectedGame.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane selectedGamePane = new JScrollPane(selectedGame); computerAddress = new JTextField("player.ggp.org:80"); playerComboBox = new JComboBox<String>(); playerComboBox.addItemListener(this); gamers = Lists.newArrayList(ProjectSearcher.GAMERS.getConcreteClasses()); List<Class<?>> gamersCopy = new ArrayList<Class<?>>(gamers); for(Class<?> gamer : gamersCopy) { try { Gamer g = (Gamer) gamer.newInstance(); if (!g.isComputerPlayer()) { throw new Exception("Kiosk only considers computer players"); } playerComboBox.addItem(g.getName()); } catch(Exception ex) { gamers.remove(gamer); } } playerComboBox.setSelectedItem("Random"); playerComboBox.addItem(remotePlayerString); 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/base/"); } 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; } @Override 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 hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + ((gameName == null) ? 0 : gameName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AvailableGame other = (AvailableGame) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (gameName == null) { if (other.gameName != null) return false; } else if (!gameName.equals(other.gameName)) return false; return true; } @Override public int compareTo(AvailableGame o) { return gameName.compareTo(o.gameName); } private Kiosk getOuterType() { return Kiosk.this; } } 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) { if (kioskServer != null && !kioskServer.getMatch().isCompleted()) { // When a match is started while another match is still running, // abort the ongoing match so that the new one can start. This // also directly tells the human gamer to abort, since otherwise // it will wait indefinitely for the human to submit their last // move before it processes the abort message from the server, // since messages are processed serially, and the human gamer // has no time limits for processing play messages. kioskServer.abort(); kioskServer = null; try { theHumanGamer.abort(); } catch (AbortingException ae) { ae.printStackTrace(); } System.gc(); } try { GdlPool.drainPool(); SymbolPool.drainPool(); AvailableGame theGame = selectedGame.getSelectedValue(); Game game = theRepository.getGame(theGame.kifFile); if (game == null) { JOptionPane.showMessageDialog(this, "Cannot load game data for " + theGame.kifFile, "Error", JOptionPane.ERROR_MESSAGE); return; } String matchId = "kiosk." + theGame.kifFile + "-" + System.currentTimeMillis(); int startClock = Integer.valueOf(startClockTextField.getText()); int playClock = Integer.valueOf(playClockTextField.getText()); Match match = new Match(matchId, -1, 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"); } match.setPlayerNamesFromHost(playerNames); GamerLogger.startFileLogging(match, "kiosk"); kioskServer = new GameServer(match, hosts, ports); 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() + "]."); } } }