From f19c09b573857cb2fc07221ad2a2c8b4610a30c5 Mon Sep 17 00:00:00 2001 From: Marshall Date: Thu, 19 Apr 2012 21:05:59 -0400 Subject: [PATCH] -PlayerModel classes, including a function to get the target score for a player's game and a neural network to predict a player's moves. --- src/model/playerModel/Node.java | 9 ++ src/model/playerModel/PlayerModel.java | 134 ++++++++++++++++++++ src/model/playerModel/node/InputNode.java | 22 ++++ src/model/playerModel/node/SigmoidNode.java | 58 +++++++++ 4 files changed, 223 insertions(+) create mode 100644 src/model/playerModel/Node.java create mode 100644 src/model/playerModel/PlayerModel.java create mode 100644 src/model/playerModel/node/InputNode.java create mode 100644 src/model/playerModel/node/SigmoidNode.java diff --git a/src/model/playerModel/Node.java b/src/model/playerModel/Node.java new file mode 100644 index 0000000..7184e94 --- /dev/null +++ b/src/model/playerModel/Node.java @@ -0,0 +1,9 @@ +package model.playerModel; + +public interface Node { + + boolean axon(); + + void learn(boolean correct); + +} diff --git a/src/model/playerModel/PlayerModel.java b/src/model/playerModel/PlayerModel.java new file mode 100644 index 0000000..e23b5ee --- /dev/null +++ b/src/model/playerModel/PlayerModel.java @@ -0,0 +1,134 @@ +package model.playerModel; + +import java.util.Random; + +import model.Board; +import model.playerModel.node.InputNode; +import model.playerModel.node.SigmoidNode; + +public class PlayerModel { + + public static final int FIRST_SCORE = 50; + public static final Random rand = new Random(); + + private final SigmoidNode[] hiddenLayer; + private final int[] highScore = new int[10]; + + private int highScoresAchieved = 0; + // One node for each tile-color combination, plus one for each upcoming + // tile-color combination. + private final InputNode[] inputNode = new InputNode[(Board.NUM_COLS + * Board.NUM_ROWS * (Board.TileColor.values().length - 1)) + + (4 * 2)]; + + private int nextHighInGames = 0; + + // One node for each tile. + private final SigmoidNode[] outputNode = new SigmoidNode[Board.NUM_COLS + * Board.NUM_ROWS]; + + public PlayerModel() { + hiddenLayer = new SigmoidNode[inputNode.length + + ((inputNode.length * 2) / 3)]; + + for (int i = 0; i < inputNode.length; i++) { + inputNode[i] = new InputNode(); + } + + for (int i = 0; i < hiddenLayer.length; i++) { + hiddenLayer[i] = new SigmoidNode(); + + for (int j = 0; j < inputNode.length; j++) { + hiddenLayer[i].addNode(inputNode[j], 1); + } + } + + for (int i = 0; i < outputNode.length; i++) { + outputNode[i] = new SigmoidNode(); + + for (int j = 0; j < hiddenLayer.length; j++) { + outputNode[i].addNode(hiddenLayer[j], 1); + } + } + } + + public boolean[] getPrediction(boolean[] input) { + if (input.length == inputNode.length) { + boolean[] prediction = new boolean[outputNode.length]; + + for (int i = 0; i < input.length; i++) { + inputNode[i].setStimulation(input[i]); + } + + for (int i = 0; i < prediction.length; i++) { + prediction[i] = outputNode[i].axon(); + } + + return prediction; + } + + return null; + } + + public int getTargetScore() { + int targetScore; + int highScore = getHighScore(); + + if (highScoresAchieved == 0) { + targetScore = FIRST_SCORE; + nextHighInGames = 1; + } + + else if (nextHighInGames == 0) { + // Set next game for high score. + nextHighInGames = (int) Math.pow(highScoresAchieved, 2); + + // Return high score times increase percentage. + targetScore = highScore / (2 * (highScoresAchieved + 1)); + + if (targetScore <= highScore) { + targetScore = highScore + 1; + } + } + + else { + double scoreReduction = .1 * rand.nextDouble(); + targetScore = (int) (highScore - (highScore * scoreReduction)); + + if (targetScore >= highScore) { + targetScore = highScore - 1; + } + } + + return targetScore; + } + + public void logGame(int score) { + loop: for (int i = 0; i < highScore.length; i++) { + if (score > highScore[i]) { + for (int j = i; j < highScore.length - 1; j++) { + highScore[j + 1] = highScore[j]; + } + + highScore[i] = score; + + break loop; + } + } + + highScoresAchieved += (score == highScore[0]) ? 1 : 0; + nextHighInGames--; + } + + public void train(boolean[] example) { + if (example.length == outputNode.length) { + for (int i = 0; i < outputNode.length; i++) { + outputNode[i].learn(example[i]); + } + } + } + + private int getHighScore() { + return highScore[0]; + } +} diff --git a/src/model/playerModel/node/InputNode.java b/src/model/playerModel/node/InputNode.java new file mode 100644 index 0000000..87b6751 --- /dev/null +++ b/src/model/playerModel/node/InputNode.java @@ -0,0 +1,22 @@ +package model.playerModel.node; + +import model.playerModel.Node; + +public class InputNode implements Node { + + private boolean dendrite = false; + + @Override + public boolean axon() { + return dendrite; + } + + @Override + public void learn(boolean correct) { + // No learning for input nodes. + } + + public void setStimulation(boolean active) { + dendrite = active; + } +} diff --git a/src/model/playerModel/node/SigmoidNode.java b/src/model/playerModel/node/SigmoidNode.java new file mode 100644 index 0000000..386a695 --- /dev/null +++ b/src/model/playerModel/node/SigmoidNode.java @@ -0,0 +1,58 @@ +package model.playerModel.node; + +import java.util.Hashtable; + +import model.playerModel.Node; + + +public class SigmoidNode implements Node { + + // Training rate. + private final double A = .15; + + private final Hashtable dendrites = new Hashtable(); + + private final double s = 1; // Slope paramater for the activation function. + // It's doing nothing right now, but I figured + // I'd implement it as a tweaking option. + private final double THRESHOLD = .5; + + public void addNode(Node n, double weight) { + dendrites.put(n, weight); + } + + @Override + public boolean axon() { + double sum = 0; + + for (Node n : dendrites.keySet()) { + sum += n.axon() ? dendrites.get(n) : 0; + } + + return (activation(sum) > THRESHOLD); + + } + + @Override + public void learn(boolean correct) { + for (Node n : dendrites.keySet()) { + if (correct && n.axon()) { + dendrites.put(n, dendrites.get(n) + A); + + n.learn(correct); + } + } + } + + private double activation(double sum) { + // Sigmoid function: + // 1/(1+(e^(-bt))) + + double a = -1 * s * sum; + a = Math.exp(a); + a = 1 + a; + a = 1 / a; + + return a; + } +}