diff --git a/src/flowerwarspp/GameLoop.java b/src/flowerwarspp/GameLoop.java index 03bcb7c7b54c0597f657cf0198e8849519a4d0e6..dff7cd997a11b1f2954dc4200e8cd43e0169b519 100644 --- a/src/flowerwarspp/GameLoop.java +++ b/src/flowerwarspp/GameLoop.java @@ -106,8 +106,14 @@ public class GameLoop extends Thread { Debug.print("Main loop: request move from player " + viewer.getTurn()); move = currentPlayer.request(); - board.make(move); - status = viewer.getStatus(); + if (board.getPossibleMoves().contains(move) ) { + board.make(move); + status = viewer.getStatus(); + } else { + status = Status.Illegal; + Debug.print("Wrong move! Idiot! RTFM Player " + + viewer.getTurn()); + } } while (status == Status.Illegal); currentPlayer.confirm(status); @@ -116,7 +122,7 @@ public class GameLoop extends Thread { System.out.println("Connection lost: " + e.getMessage()); System.exit(1); } catch (Exception e) { - System.out.println("Error: " + e.getMessage()); + System.out.println("Error Main Loop: " + e.getMessage()); } display.update(move, status); diff --git a/src/flowerwarspp/player/BasicPlayer.java b/src/flowerwarspp/player/BasicPlayer.java index 6d2a01aa22e2781d91fdbe57388e2d43f1878d9f..65f1905c60c62d6e0e17fb06041bf898f641e55f 100644 --- a/src/flowerwarspp/player/BasicPlayer.java +++ b/src/flowerwarspp/player/BasicPlayer.java @@ -6,6 +6,7 @@ import java.rmi.server.UnicastRemoteObject; import flowerwarspp.Debug; import flowerwarspp.boardmechanics.FGWBoard; +import flowerwarspp.boardmechanics.FGWType; import flowerwarspp.gui.BoardDisplay; import flowerwarspp.preset.Board; import flowerwarspp.preset.Move; @@ -40,20 +41,32 @@ public abstract class BasicPlayer extends UnicastRemoteObject implements flowerw */ protected PlayerColor color; - /** - * Needed next move, given by the Viewer. - * It is an auxiliary variable, thus no getter and setter needed. - */ - protected Move nextMove; + /** + * Needed next move, given by the Viewer. + * It is an auxiliary variable, thus no getter and setter needed. + */ + protected Move nextMove; - /** - * Each player has a copy of the board that controls the game. - */ - protected Board myBoard; + /** + * Each player has a copy of the board that controls the game. + */ + protected FGWBoard myBoard; + /** + * Each player needs a viewer to watch the game. + */ protected Viewer viewer; + /** + * Player needs a display for the network game. + */ protected BoardDisplay display; + + /** + * Needed for KI. It needs to know in which color the player's color is set. + */ + protected FGWType myFlowerColor; + //*********************** Get & Set Functions ************************** /** @@ -81,12 +94,33 @@ public abstract class BasicPlayer extends UnicastRemoteObject implements flowerw Debug.print("init player " + color); this.color = color; myBoard = new FGWBoard(boardSize); - status = PlayerStatus.INITIALIZED; + + if (this.color == PlayerColor.Red ) { + // Red Player calls first request() + status = PlayerStatus.UPDATE; + } + else if(this.color == PlayerColor.Blue) { + // Blue Player calls first update() + status = PlayerStatus.CONFIRM; + } + else + { + System.out.println("Invalid Color for player!"); + } + if (display != null) { Debug.print("Resize local board."); display.setViewer(myBoard.viewer()); display.reset(); } + if(color == PlayerColor.Red) { + myFlowerColor = FGWType.RED; + } else if (color == PlayerColor.Blue) { + myFlowerColor = FGWType.BLUE; + } else { + myFlowerColor = null; + System.out.println("Something went wrong with identifiy in which color my flowers are!"); + } } /** @@ -102,22 +136,20 @@ public abstract class BasicPlayer extends UnicastRemoteObject implements flowerw */ @Override public Move request() throws Exception { - if (color == PlayerColor.Red) { //check the right order, red starts with request - if (status == PlayerStatus.NOTEXISTING) { - throw new Exception("Call init() before using request()"); - } else if (status != PlayerStatus.UPDATE && status != PlayerStatus.INITIALIZED) { - throw new Exception("Update() cannot be called before request()"); - } - } else if (status != PlayerStatus.UPDATE) { //Blue starts with update BEFORE request - throw new Exception("call update() before request()"); + Debug.print("Request Move! " + status +" "+ color ); + if (status == PlayerStatus.NOTEXISTING) { + throw new Exception("Call init() before using request()"); + } + if (status==PlayerStatus.CONFIRM) { // Request might be called multiple times! + throw new Exception("In the request() method the player must not be in CONFIRM status!" + color + " "+type); } try { nextMove = chooseMove(); } catch (Exception e){ // TODO How should we handle this error?? System.out.println("Error while trying to choose draw: " + e); - } - + } + status = PlayerStatus.REQUEST; return nextMove; } @@ -135,9 +167,9 @@ public abstract class BasicPlayer extends UnicastRemoteObject implements flowerw } myBoard.make(nextMove); updateDisplay(nextMove, boardStatus); -// if (boardStatus != myBoard.getStatus()) { -// throw new Exception("Confusion! Board of Player not in the same state as Control Board!"); -// } + if (boardStatus != myBoard.getCurrentStatus()) { + throw new Exception("Confusion! Board of Player not in the same state as Control Board!"); + } status = PlayerStatus.CONFIRM; } @@ -150,21 +182,16 @@ public abstract class BasicPlayer extends UnicastRemoteObject implements flowerw */ @Override public void update(final Move opponentMove, final Status boardStatus) throws Exception, RemoteException { - if (this.color == PlayerColor.Blue) { //check the correct order, blue player starts with update() - if (status == PlayerStatus.NOTEXISTING) { - throw new Exception("Call init() before using request()"); - } else if (status != PlayerStatus.CONFIRM && status != PlayerStatus.INITIALIZED) { - throw new Exception("call confirm() before update()"); - } - } else if (status != PlayerStatus.CONFIRM) { - System.out.println("Wrong order of execution!" + color); + if (status == PlayerStatus.NOTEXISTING) { + throw new Exception("Call init() before using request()" + color); + } else if (status == PlayerStatus.REQUEST) { throw new Exception("call confirm() before update()" + color); - } + } myBoard.make(opponentMove); updateDisplay(opponentMove, boardStatus); -// if (boardStatus != myBoard.getStatus()) { -// throw new Exception("confusion at boardstatus"); -// } + if (boardStatus != myBoard.getCurrentStatus()) { + throw new Exception("confusion at boardstatus" + color ) ; + } status = PlayerStatus.UPDATE; } diff --git a/src/flowerwarspp/player/HumanPlayer.java b/src/flowerwarspp/player/HumanPlayer.java index bc50eaba7027c4195d315bf935e3735b3a4af560..9daabd194ab66549e53551858aeb4fe62d8ceda1 100644 --- a/src/flowerwarspp/player/HumanPlayer.java +++ b/src/flowerwarspp/player/HumanPlayer.java @@ -44,32 +44,12 @@ public class HumanPlayer extends BasicPlayer { //*********************** Function implementation ****************** - /** - * Initializes the Player and tells him/her the size of the board. - * This function is called first by the game. Overrides abstract method since viewer is needed. - * @param boardSize int - * @param Color PlayerColor - */ - @Override - public void init(final int boardSize, final PlayerColor color) { - myBoard = new FGWBoard(boardSize); - this.color = color; - if (viewer == null) { - viewer = new FGWBoardViewer((FGWBoard) myBoard) ; - } - status = PlayerStatus.INITIALIZED; - System.out.println("Init done!" ); - - } - - /** * This function chooses the next move of a human player. * This function was implemented as an abstract function in BasicPlayer. */ @Override public Move chooseMove() throws Exception { - System.out.println("Choose Move! " + status +" "+ color ); try { nextMove = input.request(); } catch (Exception e) { diff --git a/src/flowerwarspp/player/IntelligentPlayer.java b/src/flowerwarspp/player/IntelligentPlayer.java new file mode 100644 index 0000000000000000000000000000000000000000..1f2384235932f234861f65edecc14bfd7d9b548a --- /dev/null +++ b/src/flowerwarspp/player/IntelligentPlayer.java @@ -0,0 +1,109 @@ +package flowerwarspp.player; + +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +import flowerwarspp.boardmechanics.ColoredFlower; +import flowerwarspp.player.mcts.Node; +import flowerwarspp.player.mcts.State; +import flowerwarspp.preset.Flower; +import flowerwarspp.preset.Move; +import flowerwarspp.preset.MoveType; +import flowerwarspp.preset.PlayerType; +import flowerwarspp.preset.Position; +import flowerwarspp.preset.Viewer; + +public class IntelligentPlayer extends BasicPlayer{ + + private int oponent; + long start, end; + + public IntelligentPlayer(Viewer viewer ) throws RemoteException { + this.viewer = viewer; + type = PlayerType.ADVANCED_AI_2; + status = PlayerStatus.NOTEXISTING; + } + + + /** + * Chooses the next move and deliver it to request() function of the BasicPlayer. + * @return Move next move + * @throws Exception + */ + @Override + public Move chooseMove() throws Exception { + + Move intelligentMove = null; + start = System.currentTimeMillis(); + end = start + 1000; + + + while (System.currentTimeMillis() < end) { + + } + if (intelligentMove == null ) { + System.out.println("Error in the StupidPlayer inserted move that is null!"); + throw new Exception("The draw of the Stupid Player was null-pointer!"); + } + return intelligentMove; + } + + /** + * This function evaluates the nodes according to Upper Confidence Bound (UCT) + * (see also https://en.wikipedia.org/wiki/Monte_Carlo_tree_search#Exploration_and_exploitation) + * @param totalVisit + * @param nodeWinScore + * @param nodeVisit + * @return uctValue double + */ + private static double uctValue(int totalVisit, double nodeWinScore, int nodeVisit) { + if (nodeVisit == 0) { + return Integer.MAX_VALUE; + } + double sqrt2 = 1.41; + return (nodeWinScore/(double) nodeVisit) + sqrt2*Math.sqrt(Math.log(totalVisit)/(double) nodeVisit); + } + + /** + * This function tries to find the best node. To do so it uses the uctValue as defined above. + * @param node Node + * @return node Node + */ + private Node findBestNode (Node node) { + int parentVisit = node.getState().getVisitCount(); + return Collections.max( node.getChildArray(), + Comparator.comparing(c -> + uctValue(parentVisit, c.getState().getWinScore(), c.getState().getVisitCount() )) ); + } + + /** + * This function selects a certain node that is supposed to be the best node. + * @param rootNode + * @return Node node + */ + private Node selectNode(Node rootNode) { + Node node = rootNode; + while (node.getChildArray().size() != 0) { + node = findBestNode(node); + } + return node; + } + + private void expandNode(Node node) { + List<State> possibleStates = node.getState().getAllPossibleStates(); + for (State state : possibleStates){ + Node newNode = new Node(state); + newNode.setParent(node); + newNode.getState().setPlayer(node.getState().getOpponent()); + node.getChildArray().add(newNode); + } + } + + + + +} diff --git a/src/flowerwarspp/player/RandomPlayer.java b/src/flowerwarspp/player/RandomPlayer.java index d5dc3791eec81e969d8f7f2463014e80b5d1c059..d6ffe32b26c6cbb2756a53760875e5fe49b1d6fe 100644 --- a/src/flowerwarspp/player/RandomPlayer.java +++ b/src/flowerwarspp/player/RandomPlayer.java @@ -1,15 +1,19 @@ package flowerwarspp.player; +import flowerwarspp.Debug; import flowerwarspp.boardmechanics.ColoredFlower; import flowerwarspp.boardmechanics.FGWBoard; import flowerwarspp.boardmechanics.FGWBoardViewer; +import flowerwarspp.preset.Flower; import flowerwarspp.preset.Move; +import flowerwarspp.preset.MoveType; import flowerwarspp.preset.PlayerColor; import flowerwarspp.preset.PlayerType; import flowerwarspp.preset.Position; import flowerwarspp.preset.Viewer; import java.io.Serializable; import java.rmi.RemoteException; +import java.util.Collection; import java.util.Random; /** @@ -33,32 +37,34 @@ public class RandomPlayer extends BasicPlayer { */ @Override public Move chooseMove() throws Exception { - System.out.println("Choose Random Move! " + status +" "+ color ); - int numPossiblities= 10; //TODO needs still to be implemented! - //int numPossiblities = ((FGWBoard) myBoard).getPossibleMoves().size(); - - ColoredFlower blume1 = new ColoredFlower(new Position(1,1), new Position(1,2 ), new Position(2,1)); - ColoredFlower blume2 = new ColoredFlower(new Position(2,2), new Position(2,1 ), new Position(2,3)); - Move dummyMove= new Move(blume1, blume2); - - Random randomDraw=new Random(); - System.out.println("Possiblities: " + numPossiblities); - + Debug.print("Choose Random Move! " + status +" "+ color ); /* * getPossibleMoves delivers Vector of possible moves. * Using the Random class of java we can choose randomly one possible move for the random player. */ - //Move randomMove = (Move) ((FGWBoard) myBoard).getPossibleMoves().get(randomDraw.nextInt(numPossiblities)); - - Move randomMove = dummyMove; - System.out.println(randomMove); - - if (randomMove != null ) { - status = PlayerStatus.REQUEST; - } else { - System.out.println("Error Random Player inserted move that is null!"); - throw new Exception("The draw of the Random Player was null-pointer!"); + Random randomDraw=new Random(); + Collection <Move> possibleMovesCollection = viewer.getPossibleMoves(); + Move [] possibleMoves=possibleMovesCollection.toArray(new Move [0]); + int sizePossibleDraws = possibleMoves.length; + Move randomMove = null; + + Flower flower1 = null; + Flower flower2 = null; + while (flower1 == null || flower2 == null) { + do { + randomMove = possibleMoves[randomDraw.nextInt(sizePossibleDraws)]; + if (randomMove == null ) { + System.out.println("Error Random Player inserted move that is null!"); + throw new Exception("The draw of the Random Player was null-pointer!"); + } + } while(randomMove.getType() != MoveType.Flower ) ; + flower1= randomMove.getFirstFlower(); + flower2 = randomMove.getSecondFlower(); } + + + System.out.println(randomMove); + return randomMove; } diff --git a/src/flowerwarspp/player/StupidPlayer.java b/src/flowerwarspp/player/StupidPlayer.java index 435d2eb9c36f3616d8f2d4fa54a1427c2f2bdbf0..02aa6e5dbf952dbfca3ec1294eeb2fd8975284d2 100644 --- a/src/flowerwarspp/player/StupidPlayer.java +++ b/src/flowerwarspp/player/StupidPlayer.java @@ -2,11 +2,21 @@ package flowerwarspp.player; import flowerwarspp.boardmechanics.ColoredFlower; import flowerwarspp.player.BasicPlayer; +import flowerwarspp.preset.Flower; import flowerwarspp.preset.Move; +import flowerwarspp.preset.MoveType; +import flowerwarspp.preset.PlayerColor; import flowerwarspp.preset.PlayerType; import flowerwarspp.preset.Position; +import flowerwarspp.boardmechanics.FGWType; import flowerwarspp.preset.Viewer; import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Random; /** @@ -29,6 +39,46 @@ public class StupidPlayer extends BasicPlayer{ status = PlayerStatus.NOTEXISTING; } + /** + * This function counts the neighbors of a Flower. + * @param neighborList1 + * @return int numNeighours + */ + private int countNeighbours(Flower flower) { + int numNeighours = 0; + List<ColoredFlower> neighbourFlowersList=new ArrayList <ColoredFlower> (myBoard.getNeighborsOfAnyFlower(flower.hashCode())); + + for ( ColoredFlower coloredFlower : neighbourFlowersList) { + if(coloredFlower.getFlowerType() == myFlowerColor) { + numNeighours ++; + } + } + return numNeighours; + } + + /** + * This function computes the score of one move. + * It needs both flowers that are set in the possible move. + * @param flower1 + * @param flower2 + * @return score integer-value + */ + private int computeScore(Flower flower1, Flower flower2) { + int score; + int n1 = countNeighbours(flower1); + int n2 = countNeighbours(flower2); + score = (n1 + 1) * (n2 + 1); + HashMap<Integer, ColoredFlower>FlowersMap=new HashMap <Integer, ColoredFlower> (myBoard.getAllFlowers() ); + ColoredFlower cFlower1=FlowersMap.get(flower1.hashCode()); + ColoredFlower cFlower2=FlowersMap.get(flower2.hashCode()); + + + if (cFlower1.isNeighbor(cFlower2)) { + score*=2; + } + return score; + } + /** * Chooses the next move and deliver it to request() function of the BasicPlayer. * @return Move next move @@ -37,20 +87,41 @@ public class StupidPlayer extends BasicPlayer{ @Override public Move chooseMove() throws Exception { - ColoredFlower blume1 = new ColoredFlower(new Position(1,1), new Position(1,2 ), new Position(2,1)); - ColoredFlower blume2 = new ColoredFlower(new Position(2,2), new Position(2,1 ), new Position(2,3)); - Move dummyMove= new Move(blume1, blume2); +// ColoredFlower blume1 = new ColoredFlower(new Position(1,1), new Position(1,2 ), new Position(2,1)); +// ColoredFlower blume2 = new ColoredFlower(new Position(2,2), new Position(2,1 ), new Position(2,3)); +// Move dummyMove= new Move(blume1, blume2); + + Move stupidMove = null; + int maxScore=0; + int indexMove=0; + /* + * getPossibleMoves delivers Vector of possible moves. + * Using the Random class of java we can choose randomly one possible move for the random player. + */ + Random randomDraw=new Random(); + List<Move> possibleMovesCollection = new ArrayList<Move>( viewer.getPossibleMoves()); + Collections.shuffle(possibleMovesCollection); - Move stupidMove = dummyMove; + for (int i = 0; i < possibleMovesCollection.size(); i++ ) { + Move thisMove=possibleMovesCollection.get(i); + int thisMoveScore=0; + if (thisMove.getType() == MoveType.Flower) { + Flower flower1= thisMove.getFirstFlower(); + Flower flower2 = thisMove.getSecondFlower(); + thisMoveScore = computeScore(flower1, flower2); + } + if (maxScore < thisMoveScore) { + maxScore = thisMoveScore; + indexMove = i; + } + } - if (stupidMove != null ) { - status = PlayerStatus.REQUEST; - } else { + stupidMove = possibleMovesCollection.get(indexMove); + if (stupidMove == null ) { System.out.println("Error in the StupidPlayer inserted move that is null!"); throw new Exception("The draw of the Stupid Player was null-pointer!"); } return stupidMove; } - } diff --git a/src/flowerwarspp/player/mcts/Node.java b/src/flowerwarspp/player/mcts/Node.java new file mode 100644 index 0000000000000000000000000000000000000000..0766de319afc1aac18ff3016fd5c863399223c3a --- /dev/null +++ b/src/flowerwarspp/player/mcts/Node.java @@ -0,0 +1,76 @@ +package flowerwarspp.player.mcts; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class Node { + State state; + Node parent; + List<Node> childArray; + + public Node() { + this.state = new State(); + childArray = new ArrayList<>(); + } + + public Node(State state) { + this.state = state; + childArray = new ArrayList<>(); + } + + public Node(State state, Node parent, List<Node> childArray) { + this.state = state; + this.parent = parent; + this.childArray = childArray; + } + + public Node(Node node) { + childArray = new ArrayList<>(); + state = new State(node.getState()); + if (node.getParent() != null) + parent = node.getParent(); + List<Node> childArray = node.getChildArray(); + for (Node child : childArray) { + childArray.add(new Node(child)); + } + } + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public Node getParent() { + return parent; + } + + public void setParent(Node parent) { + this.parent = parent; + } + + public List<Node> getChildArray() { + return childArray; + } + + public void setChildArray(List<Node> childArray) { + this.childArray = childArray; + } + + public Node getRandomChildNode() { + int noOfPossibleMoves = this.childArray.size(); + int selectRandom = (int) (Math.random() * ((noOfPossibleMoves - 1) + 1)); + return this.childArray.get(selectRandom); + } + + public Node getChildWithMaxScore() { + return Collections.max(this.childArray, Comparator.comparing(c -> { + return c.getState().getVisitCount(); + })); + } + +} diff --git a/src/flowerwarspp/player/mcts/State.java b/src/flowerwarspp/player/mcts/State.java new file mode 100644 index 0000000000000000000000000000000000000000..a7f7b4024c8904a0e1c66f5aeeba803e73bf8b64 --- /dev/null +++ b/src/flowerwarspp/player/mcts/State.java @@ -0,0 +1,115 @@ +package flowerwarspp.player.mcts; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import flowerwarspp.boardmechanics.ColoredFlower; +import flowerwarspp.boardmechanics.FGWBoard; +import flowerwarspp.preset.Move; +import flowerwarspp.preset.PlayerColor; + +/** + * Each node will have its particular state of the problem. This is implemented in this State class. + * @author Felix Strnad + */ +public class State { + private FGWBoard board; + private PlayerColor color; + private int size; + private int visitCount; + private double winScore; + + public State() { + board = new FGWBoard(size); + } + + public State(State state) { + this.board = state.getBoard(); + this.color = state.getPlayerColor(); + this.visitCount = state.getVisitCount(); + this.winScore = state.getWinScore(); + } + + public State(FGWBoard board) { + this.board = new FGWBoard(size); + } + + private FGWBoard getBoard() { + return board; + } + + public void setBoard(FGWBoard board) { + this.board = board; + } + + public PlayerColor getPlayerColor() { + return color; + } + + public void setPlayer(PlayerColor color) { + this.color = color; + } + + public PlayerColor getOpponent() { + if (color==PlayerColor.Red) { + return PlayerColor.Blue; + } else if (color==PlayerColor.Blue) { + return PlayerColor.Red; + } + else { + System.out.println("Someting went wrong in the State with the colors!"); + return null; + } + } + + public int getVisitCount() { + return visitCount; + } + + public void setVisitCount(int visitCount) { + this.visitCount = visitCount; + } + + public double getWinScore() { + return winScore; + } + + void setWinScore(double winScore) { + this.winScore = winScore; + } + + public List<State> getAllPossibleStates() { + List<State> possibleStates = new ArrayList<>(); + List<Move> possibleMoves = board.getPossibleMoves(); /// TODO Check + + for ( Move possibleMove : possibleMoves) { + State newState = new State(board); + newState.setPlayer(color); + newState.getBoard().make(possibleMove); + possibleStates.add(newState); + } + return possibleStates; + } + + void incrementVisit() { + this.visitCount++; + } + + void addScore(double score) { + if (this.winScore != Integer.MIN_VALUE) + this.winScore += score; + } + + void randomPlay() { + List<Move> availablePositions = this.board.getPossibleMoves(); + int totalPossibilities = availablePositions.size(); + Random randomDraw=new Random(); + int chooseRandom = randomDraw.nextInt(totalPossibilities); + this.board.make(availablePositions.get(chooseRandom)); + } + + void interchangePlayer() { + color=getOpponent(); + } +} diff --git a/src/flowerwarspp/player/mcts/Tree.java b/src/flowerwarspp/player/mcts/Tree.java new file mode 100644 index 0000000000000000000000000000000000000000..83937701fc7ceb909358e6d0fdfd1a59d90d4587 --- /dev/null +++ b/src/flowerwarspp/player/mcts/Tree.java @@ -0,0 +1,25 @@ +package flowerwarspp.player.mcts; + +public class Tree { + Node root; + + public Tree() { + root = new Node(); + } + + public Tree(Node root) { + this.root = root; + } + + public Node getRoot() { + return root; + } + + public void setRoot(Node root) { + this.root = root; + } + + public void addChild(Node parent, Node child) { + parent.getChildArray().add(child); + } +}