From 15ed56134e7a5d98e6e8bb5ff7eef85aad120dec Mon Sep 17 00:00:00 2001 From: Marshall Date: Sun, 29 Apr 2012 03:22:19 -0400 Subject: [PATCH] - Implemented multiple users, including a selection dialog and automatic preference saving and loading. - Integrated the ANN with the game. The network now predicts a user move, completely ignores it, and trains itself on the players actual move. This integration also included implementing two new functions. The first translates a board state to a boolean array to correspond with input nodes. The second translates a move to a boolean array to correspond with output nodes. --- src/model/Referee.java | 67 ++++++++-- src/model/playerModel/PlayerModel.java | 75 ++++++----- src/view/HighScoreDialog.java | 22 ---- src/view/MainFrame.java | 9 +- src/view/UserChooserFrame.java | 164 +++++++++++++++++++++++++ test/puzzleTests/Tests.java | 2 +- 6 files changed, 274 insertions(+), 65 deletions(-) create mode 100644 src/view/UserChooserFrame.java diff --git a/src/model/Referee.java b/src/model/Referee.java index 07b8e96..3bdf546 100644 --- a/src/model/Referee.java +++ b/src/model/Referee.java @@ -30,19 +30,38 @@ public class Referee implements Runnable { private final MainFrame mf; private PlayerModel playerModel = null; - public Referee(MainFrame mnFrm) { - if (PlayerModel.TRY_LOAD && PlayerModel.exists()) { - playerModel = PlayerModel.load(); + public Referee(MainFrame mnFrm, String player) { + if (PlayerModel.exists(player)) { + PlayerModel.getPlayerPath(player); + playerModel = PlayerModel.load(PlayerModel.getPlayerPath(player)); } - if (playerModel == null) { - playerModel = new PlayerModel(); + if (getPlayerModel() == null) { + playerModel = new PlayerModel(player); } mf = mnFrm; initGame(); } + public boolean[] getBoardState() { + boolean[] boardState = new boolean[getPlayerModel().getNumInputNodes()]; + + int i = 0; + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + boardState[i] = (board.getTile(r, c) == TileColor.BLUE); + boardState[i + 1] = (board.getTile(r, c) == TileColor.GREEN); + boardState[i + 2] = (board.getTile(r, c) == TileColor.RED); + boardState[i + 3] = (board.getTile(r, c) == TileColor.YELLOW); + + i += 4; + } + } + + return boardState; + } + public Player getComputerPlayer() { return computerPlayer; } @@ -83,13 +102,13 @@ public class Referee implements Runnable { initGame(); mf.updateBoard(); play(); - playerModel.logGame(getPlayerScore()); + getPlayerModel().logGame(getPlayerScore()); - if (!playerModel.save()) { + if (!getPlayerModel().save()) { System.err.println("Saving PlayerModel failed."); } - new HighScoreDialog(mf, playerModel); + new HighScoreDialog(mf, getPlayerModel()); } } @@ -97,6 +116,28 @@ public class Referee implements Runnable { this.boardPanel = boardPanel; } + private boolean[] getMoveArray(Move mv) { + boolean[] move = new boolean[getPlayerModel().getNumOutputNodes()]; + + move[0] = (mv.getColor() == TileColor.BLUE); + move[1] = (mv.getColor() == TileColor.GREEN); + move[2] = (mv.getColor() == TileColor.RED); + move[3] = (mv.getColor() == TileColor.YELLOW); + + int tile = 0; + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + move[tile] = (mv.getCell().r == r && mv.getCell().c == c); + } + } + + return move; + } + + private PlayerModel getPlayerModel() { + return playerModel; + } + private ScorePanel getScorePanel() { return mf.getScorePanel(); } @@ -126,6 +167,9 @@ public class Referee implements Runnable { Move mv = humanPlayer.getMove(board); if (board.getTile(mv.getCell().r, mv.getCell().c) == TileColor.NONE) { playToken(humanPlayer.getMove(board)); + + getPlayerModel().train(getMoveArray(mv)); + } else { humanPlayer.denyMove(); } @@ -133,6 +177,13 @@ public class Referee implements Runnable { } else { Move mv = computerPlayer.getMove(board); playToken(mv); + + // TODO + // This is the call that gets a prediction of a user's move. + // Some changes will probably be necessary to put it in the + // right place and also to get the node weights. But... all in + // due time. + getPlayerModel().getPrediction(getBoardState()); } mf.updateMessage(getMessage()); diff --git a/src/model/playerModel/PlayerModel.java b/src/model/playerModel/PlayerModel.java index bebb524..77cfa9a 100644 --- a/src/model/playerModel/PlayerModel.java +++ b/src/model/playerModel/PlayerModel.java @@ -17,30 +17,27 @@ import model.playerModel.node.SigmoidNode; public class PlayerModel implements Serializable { - public static final String PLAYER_MODEL_PATH = "playerModel.dat"; // Path to - // the - // stored - // player - // model. + public static final String DATA_FOLDER = "data/"; + public static final String PLAYER_MODEL_PREFIX = "playerModel"; + public static final String PLAYER_MODEL_SUFFIX = ".dat"; public static final Random rand = new Random(); // Randomizer object. - public static final boolean TRY_LOAD = true; // Set to false if any existing - // player model should be - // discarded and the next - // game should begin a new - // sequence. - private static final long serialVersionUID = 1L; - public static boolean exists() { - return (new File(PLAYER_MODEL_PATH)).exists(); + public static boolean exists(String playerName) { + return (new File(getPlayerPath(playerName))).exists(); } - public static PlayerModel load() { + public static String getPlayerPath(String playerName) { + return DATA_FOLDER + PLAYER_MODEL_PREFIX + "_" + playerName + + PLAYER_MODEL_SUFFIX; + } + + public static PlayerModel load(String path) { FileInputStream fin = null; ObjectInputStream oin = null; try { - fin = new FileInputStream(PLAYER_MODEL_PATH); + fin = new FileInputStream(path); oin = new ObjectInputStream(fin); PlayerModel pm = (PlayerModel) oin.readObject(); oin.close(); @@ -54,29 +51,31 @@ public class PlayerModel implements Serializable { private final SigmoidNode[] hiddenLayer; private int highScoresAchieved = 0; - // One node for each tile-color combination, plus one for each upcoming - // tile-color combination. + // One node for each tile-color combination. private final InputNode[] inputNode = new InputNode[(Board.NUM_COLS * Board.NUM_ROWS * (Board.TileColor.values().length - 1))]; + private final String name; + private int nextHighInGames = 0; - // One node for each tile plus four for the colors to be selected. - // outputNode[0] is blue. - // outputNode[1] is green. - // outputNode[2] is red. - // outputNode[3] is yellow. - // outputNode[4] through outputNode[n] represent grid spaces. A true means - // that the player is predicted to place on that tile. - // They should be read from the top-left to bottom-right, across rows. - // Ideally, the network should return only one true between 0 and 3 and only - // one true between 4 and n, representing one color and the tile in which it - // should be placed. + /* + * One node for each tile plus four for the colors to be selected. + * outputNode[0] is blue. outputNode[1] is green. outputNode[2] is red. + * outputNode[3] is yellow. outputNode[4] through outputNode[n] represent + * grid spaces. A true means that the player is predicted to place on that + * tile. They should be read from the top-left to bottom-right, across rows. + * Ideally, the network should return only one true between 0 and 3 and only + * one true between 4 and n, representing one color and the tile in which it + * should be placed. See Referee.getMoveArray(). + */ private final SigmoidNode[] outputNode = new SigmoidNode[(Board.NUM_COLS * Board.NUM_ROWS) + 4]; private final ArrayList scores = new ArrayList(); - public PlayerModel() { + public PlayerModel(String nme) { + name = nme; + hiddenLayer = new SigmoidNode[inputNode.length + ((inputNode.length * 2) / 3)]; @@ -121,6 +120,18 @@ public class PlayerModel implements Serializable { return highScores; } + public String getName() { + return name; + } + + public int getNumInputNodes() { + return inputNode.length; + } + + public int getNumOutputNodes() { + return outputNode.length; + } + public boolean[] getPrediction(boolean[] input) { if (input.length == inputNode.length) { boolean[] prediction = new boolean[outputNode.length]; @@ -187,13 +198,17 @@ public class PlayerModel implements Serializable { public boolean save() { FileOutputStream fout = null; ObjectOutputStream oout = null; + String path = getPlayerPath(getName()); try { - fout = new FileOutputStream(PLAYER_MODEL_PATH); + (new File(DATA_FOLDER)).mkdirs(); + + fout = new FileOutputStream(path); oout = new ObjectOutputStream(fout); oout.writeObject(this); oout.close(); return true; } catch (IOException ex) { + ex.printStackTrace(); return false; } } diff --git a/src/view/HighScoreDialog.java b/src/view/HighScoreDialog.java index d0d2cc9..5dd76f1 100644 --- a/src/view/HighScoreDialog.java +++ b/src/view/HighScoreDialog.java @@ -20,28 +20,6 @@ public class HighScoreDialog extends JDialog { */ private static final long serialVersionUID = 1L; - public static void main(String[] args) { - JFrame x = new JFrame(); - x.setDefaultCloseOperation(EXIT_ON_CLOSE); - x.setVisible(true); - - PlayerModel pm = new PlayerModel(); - pm.logGame(100); - pm.logGame(90); - pm.logGame(80); - pm.logGame(70); - pm.logGame(60); - pm.logGame(50); - pm.logGame(40); - pm.logGame(30); - pm.logGame(20); - pm.logGame(10); - pm.logGame(98); - pm.logGame(105); - - new HighScoreDialog(x, pm); - } - public HighScoreDialog(JFrame owner, PlayerModel pm) { super(owner, "Game over!", true); diff --git a/src/view/MainFrame.java b/src/view/MainFrame.java index d905a2b..80c2344 100644 --- a/src/view/MainFrame.java +++ b/src/view/MainFrame.java @@ -14,8 +14,7 @@ public class MainFrame extends JFrame { private static final long serialVersionUID = 1L; public static void main(String[] args) { - MainFrame mainFrame = new MainFrame(); - mainFrame.playGame(); + new UserChooserFrame(); } private BoardPanel bp; @@ -24,9 +23,10 @@ public class MainFrame extends JFrame { private TileSelectionPanel tp; ScorePanel sp; - public MainFrame() { + public MainFrame(String name) { super("CS8803 Project 4"); - referee = new Referee(this); + + referee = new Referee(this, name); init(); @@ -35,6 +35,7 @@ public class MainFrame extends JFrame { setLocationRelativeTo(null); setVisible(true); + playGame(); } public ScorePanel getScorePanel() { diff --git a/src/view/UserChooserFrame.java b/src/view/UserChooserFrame.java new file mode 100644 index 0000000..5375080 --- /dev/null +++ b/src/view/UserChooserFrame.java @@ -0,0 +1,164 @@ +package view; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collections; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; + +import model.playerModel.PlayerModel; + +public class UserChooserFrame extends JFrame { + + public static final String PLAYER_LIST_FILE = "players.dat"; + public static final JLabel RULES_TEXT = new JLabel( + "Here should go some rules text. Lorem ipsum and blah blah blah."); + private static final long serialVersionUID = 1L; + + private final JButton playButton = new JButton("Play!"); + private final JComboBox userNameBox = new JComboBox(); + private ArrayList users; + + public UserChooserFrame() { + initLayout(); + initActions(); + + pack(); + setResizable(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + setVisible(true); + } + + private String getUserListPath() { + return PlayerModel.DATA_FOLDER + PLAYER_LIST_FILE; + } + + private ArrayList getUsers() { + FileInputStream fin = null; + ObjectInputStream oin = null; + + try { + fin = new FileInputStream(getUserListPath()); + oin = new ObjectInputStream(fin); + + @SuppressWarnings("unchecked") + ArrayList list = (ArrayList) oin.readObject(); + + oin.close(); + return list; + + } catch (Exception e) { + return new ArrayList(); + } + } + + private void initActions() { + userNameBox.setEditable(true); + + users = getUsers(); + Collections.sort(users); + + for (int i = 0; i < users.size(); i++) { + userNameBox.addItem(users.get(i)); + } + + playButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + String name = ((String) userNameBox.getSelectedItem()); + name = name == null ? name : name.trim().toLowerCase(); + + if (name != null && name.compareTo("") != 0) { + UserChooserFrame.this.setVisible(false); + + boolean found = false; + for (int i = 0; !found && i < users.size(); i++) { + if (name.compareTo(users.get(i)) == 0) { + found = true; + } + } + + if (!found) { + users.add(name); + } + + saveUserList(); + new MainFrame(name); + } + } + }); + } + + private void initLayout() { + GridBagLayout gbl = new GridBagLayout(); + + GridBagConstraints con = new GridBagConstraints(); + con.fill = GridBagConstraints.BOTH; + con.gridheight = 1; + con.gridwidth = 5; + con.gridx = 0; + con.gridy = 0; + con.insets = new Insets(20, 20, 5, 20); + con.weightx = 1; + con.weighty = 1; + gbl.setConstraints(RULES_TEXT, con); + + con = new GridBagConstraints(); + con.fill = GridBagConstraints.BOTH; + con.gridheight = 1; + con.gridwidth = 5; + con.gridx = 0; + con.gridy = 1; + con.insets = new Insets(5, 20, 5, 20); + con.weightx = 1; + con.weighty = 1; + gbl.setConstraints(userNameBox, con); + + con = new GridBagConstraints(); + con.fill = GridBagConstraints.BOTH; + con.gridheight = 1; + con.gridwidth = 1; + con.gridx = 2; + con.gridy = 2; + con.insets = new Insets(5, 20, 20, 20); + con.weightx = 1; + con.weighty = 1; + gbl.setConstraints(playButton, con); + + setLayout(gbl); + add(RULES_TEXT); + add(userNameBox); + add(playButton); + } + + private void saveUserList() { + FileOutputStream fout = null; + ObjectOutputStream oout = null; + String path = getUserListPath(); + try { + (new File(PlayerModel.DATA_FOLDER)).mkdirs(); + + fout = new FileOutputStream(path); + oout = new ObjectOutputStream(fout); + oout.writeObject(users); + oout.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } +} diff --git a/test/puzzleTests/Tests.java b/test/puzzleTests/Tests.java index 4af06f3..fa01dbe 100644 --- a/test/puzzleTests/Tests.java +++ b/test/puzzleTests/Tests.java @@ -14,7 +14,7 @@ import model.playerModel.PlayerModel; public class Tests extends TestCase { public static PlayerModel getFakePlayerModel() { - PlayerModel pm = new PlayerModel(); + PlayerModel pm = new PlayerModel("marshall"); pm.logGame(25); pm.logGame(30);