gt:         boolean whiteWin=false, blackWin=false;
context: /*
   * To change this license header, choose License Headers in Project Properties.
   * To change this template file, choose Tools | Templates
   * and open the template in the editor.
   */
  package tak;
  
  import java.sql.SQLException;
  import java.sql.Statement;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Random;
  import java.util.Set;
  import java.util.Stack;
  import java.util.Timer;
  import java.util.TimerTask;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  /**
   *
   * @author chaitu
   */
  public class Game {
  
      Player white;
      Player black;
      long time;
  
      int no;
      
      //time in milli seconds
      long originalTime;
      long whiteTime;
      long blackTime;
      long lastUpdateTime;
      boolean timerStarted;
      Timer timer;
      
      Player drawOfferedBy;
      Player undoRequestedBy;
      
      boolean abandoned;
      
      Set<Client> spectators;
      
      public enum gameS {WHITE_ROAD, BLACK_ROAD, WHITE_TILE, BLACK_TILE, DRAW,
                          WHITE, BLACK, NONE};
      List<String> moveList;
      gameS gameState;
      
      static Map<Integer, Game> games=Collections.synchronizedMap(new HashMap<Integer, Game>());
      static Set<Client> gameListeners = Collections.synchronizedSet(new HashSet<Client>());
  
      class Board {
          int boardSize;
          int moveCount;
          int whiteCapstones;
          int blackCapstones;
  
          int whiteTilesCount;
          int blackTilesCount;
  
          class Square {
              private int file, row;
              private ArrayList<Character> stack;
              int graphNo;
              boolean markedByPlayer = false;
              boolean markedByObserver = false;
  
              Square(int f, int r) {
                  stack = new ArrayList<>();
                  file = f;
                  row = r;
                  graphNo = -1;
              }
  
              @Override
              public Square clone() {
                  Square sq = new Square(file, row);
                  sq.graphNo = graphNo;
                  for(Character c: stack) {
                      sq.add(c);
                  }
                  return sq;
              }
              boolean isEmpty() {
                  return stack.isEmpty();
              }
              void add(char c) {
                  stack.add(c);
              }
              int size() {
                  return stack.size();
              }
              char get(int i) {
                  return stack.get(i);
              }
              char pop() {
                  if(stack.size() > 0) {
                      char ret = topOfStack();
                      stack.remove(stack.size()-1);
                      return ret;
                  }
                  return 0;
              }
              char topOfStack() {
                  if(stack.isEmpty())
                      return 0;
  
                  return stack.get(stack.size()-1);
              }
              @Override
              public String toString() {
                  return ((char)(file+'A'))+""+row+" "+graphNo;
              }
  
              public String stackString() {
                  return stack.toString();
              }
              public boolean isMarkedByPlayer()
              {
                  return this.markedByPlayer;
              }
              public void setMarkedByPlayer(boolean marked)
              {
                  this.markedByPlayer = marked;
              }
              public boolean isMarkedByObserver()
              {
                  return this.markedByObserver;
              }
              public void setMarkedByObserver(boolean marked)
              {
                  this.markedByObserver = marked;
              }
          }
          Square[][] squares;
          
          Board(int b) {
              if (b < 3 || b > 8) {
                  b = DEFAULT_SIZE;
              }
              
              boardSize = b;
              int capstonesCount=0;
              int tilesCount=0;
              switch(b) {
                  case 3: capstonesCount = 0; tilesCount = 10; break;
                  case 4: capstonesCount = 0; tilesCount = 15; break;
                  case 5: capstonesCount = 1; tilesCount = 21; break;
                  case 6: capstonesCount = 1; tilesCount = 30; break;
                  case 7: capstonesCount = 2; tilesCount = 40; break;
                  case 8: capstonesCount = 2; tilesCount = 50; break;
              }
              
              whiteCapstones = blackCapstones = capstonesCount;
              whiteTilesCount = blackTilesCount = tilesCount;
              
              moveCount = 0;
              
              squares = new Square[boardSize][boardSize];
              for (int i = 0; i < b; i++) {
                  for (int j = 0; j < b; j++) {
                      squares[i][j] = new Square(j, i+1);
                  }
              }
          }
          
          Board(int boardSize, int moveCount, int whiteCapstones,
                  int blackCapstones, int whiteTilesCount, int blackTilesCount,
                  Square[][] squares) {
              this.boardSize = boardSize;
              this.moveCount = moveCount;
              this.whiteCapstones = whiteCapstones;
              this.blackCapstones = blackCapstones;
              this.whiteTilesCount = whiteTilesCount;
              this.blackTilesCount = blackTilesCount;
              this.squares = squares;
          }
          
          @Override
          public Board clone() {
              Board clone = new Board(boardSize, moveCount,
                                  whiteCapstones, blackCapstones,
                                  whiteTilesCount, blackTilesCount,
                                  getClonedSquares());
              
              return clone;
          }
  
          Square[][] getClonedSquares() {
              Square[][] clone = new Square[boardSize][boardSize];
  
              for(int i=0;i<boardSize;i++){
                  for(int j=0;j<boardSize;j++) {
                      clone[i][j] = squares[i][j].clone();
                  }
              }
              return clone;
          }
          
          private Square getSquare(char file, int rank) {
              if(!boundsCheck(file, rank))
                  return null;
              return board.squares[rank-1][file-'A'];
          }
          
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder();
              sb.append("wc=").append(whiteCapstones).append(" bc=")
                      .append(blackCapstones).append(" wt=")
                      .append(whiteTilesCount).append(" bt=")
                      .append(blackTilesCount).append(" mc=")
                      .append(moveCount);
              sb.append("\n").append(getBoardString());
              return sb.toString();
          }
          
          public String getBoardString() {
              StringBuilder sb = new StringBuilder();
              for(int i=boardSize;i>0;i--){
                  for(char j='A';j<boardSize+'A';j++){
                      sb.append(getSquare(j, i).stackString()).append(" ");
                  }
                  sb.append("\n");
              }
              return sb.toString();
          }
      }
      
      Board board;
      Stack<Board> boardHistory;
  
      static int DEFAULT_SIZE = 5;
  
      static int gameNo = 0;
      
      static final char FLAT='f';
      static final char WALL='w';
      static final char CAPSTONE='c';
  
      Game(Player p1, Player p2, int b, int t) {
          int rand = new Random().nextInt(99);
          white = (rand>=50)?p1:p2;
          black = (rand>=50)?p2:p1;
          
          originalTime = whiteTime = blackTime = t*1000;
          
          timer = new Timer();
          timerStarted = false;
          
          time = System.currentTimeMillis();
  
          abandoned = false;
          
          board = new Board(b);
          boardHistory = new Stack<>();
          
          no = ++gameNo;
          gameState = gameS.NONE;
          drawOfferedBy = null;
          undoRequestedBy = null;
          
          moveList = Collections.synchronizedList(new ArrayList<String>());
          
          boardHistory.push(board.clone());//store empty position
          spectators = Collections.synchronizedSet(new HashSet<Client>());
      }
      
      static void addGame(Game g) {
          Game.games.put(g.no, g);
          Game.updateGameListListeners("Add "+g.shortDesc());
      }
      
      static void removeGame(Game g) {
          Game.updateGameListListeners("Remove "+g.shortDesc());
          Game.games.remove(g.no);
      }
  
      void newSpectator(Client c) {
          c.send("Observe "+shortDesc());
          sendMoveListTo(c);
          sendMarkedListTo(c);
          spectators.add(c);
          updateTime(c);
      }
      
      void resign(Player p) {
          if(p == white)
              gameState = gameS.BLACK;
          else
              gameState = gameS.WHITE;
          whenGameEnd();
      }
  
      boolean setMarked(boolean marked, String field, Player player) {
          int file = field.charAt(0) - 'a';
          int rank = field.charAt(1) - 1;
          if (file < 0 || file >= this.board.boardSize
              || rank < 0 || rank >= this.board.boardSize)
              return false;
          
          String msg;
          if (player == this.white || player == this.black) {
              msg = "Game#" + no
                      + (marked ? " Mark_Player " : " Unmark_Player ") + field;
              this.board.squares[rank][file].setMarkedByPlayer(marked);
              sendToOtherPlayer(white, msg);
              sendToOtherPlayer(black, msg);
          }
          else {
              msg = "Game#" + no
                      + (marked ? " Mark_Observer " : " Unmark_Observer ") + field;
              this.board.squares[rank][file].setMarkedByObserver(marked);
          }
          sendToSpectators(msg);
          return true;
      }
  
      void saveBoardPosition() {
          boardHistory.push(board.clone());
      }
      
      void undoPosition() {
          boardHistory.pop();//discard cur pos        
          board = boardHistory.peek().clone();//replace cur pos with top of stack
          
          moveList.remove(moveList.size()-1);
      }
      
      void undo(Player p) {
          if(board.moveCount <= 0) {
              p.getClient().sendNOK();
              return;
          }
          
          if(undoRequestedBy == null) {
              undoRequestedBy = p;
              Player otherPlayer = (p==white)?black:white;
              otherPlayer.getClient().sendWithoutLogging("Game#"+no+" RequestUndo");
          } else if(undoRequestedBy != p) {
              undoRequestedBy = null;
              undoPosition();
              white.getClient().send("Game#"+no+" Undo");
              black.getClient().send("Game#"+no+" Undo");
              sendToSpectators("Game#"+no+" Undo");
              updateTimeTurnChange();
          }
      }
      
      void removeUndo(Player p) {
          if(undoRequestedBy == p) {
              undoRequestedBy = null;
              Player otherPlayer = (p==white)?black:white;
              otherPlayer.getClient().sendWithoutLogging("Game#"+no+" RemoveUndo");
          }
      }
      
      void draw(Player p) {
          if(drawOfferedBy == null) {
              drawOfferedBy = p;
              Player otherPlayer = (p==white)?black:white;
              otherPlayer.getClient().sendWithoutLogging("Game#"+no+" OfferDraw");
          } else if(drawOfferedBy!=p) {
              gameState = gameS.DRAW;
              whenGameEnd();
          }
      }
      
      void removeDraw(Player p) {
          if(drawOfferedBy == p) {
              drawOfferedBy = null;
              Player otherPlayer = (p==white)?black:white;
              otherPlayer.getClient().sendWithoutLogging("Game#"+no+" RemoveDraw");
          }
      }
      
      void unSpectate(Client c) {
          spectators.remove(c);
      }
      
      static void sendGameListTo(Client c) {
          for (Integer no : Game.games.keySet()) {
              c.sendWithoutLogging("GameList Add "+Game.games.get(no).shortDesc());
          }
      }
      
      void sendMoveListTo(Client c) {
          for(String move:moveList)
              c.sendWithoutLogging("Game#"+no+" "+move);
      }
      
      void sendMarkedListTo(Client c) {
        for(int i = 0; i < this.board.boardSize; ++i)
          for (int j = 0; j < this.board.boardSize; ++j)
          {
            if (this.board.squares[i][j].isMarkedByPlayer())
              c.sendWithoutLogging("Game#" + this.no + " Mark_Player "
                  + ((char) (j + 'a')) + (i + 1));
            if (this.board.squares[i][j].isMarkedByObserver())
              c.sendWithoutLogging("Game#" + this.no + " Mark_Observer "
                  + ((char) (j + 'a')) + (i + 1));
          }
      }
      
      String shortDesc(){
          StringBuilder sb=new StringBuilder("Game#"+no+" ");
          sb.append(white.getName()).append(" vs ").append(black.getName());
          sb.append(", ").append(board.boardSize).append("x").append(board.boardSize).append(", ");
          sb.append(originalTime/1000).append(", ");
          sb.append(board.moveCount).append(" half-moves played, ").append(isWhitesTurn()?white.getName():black.getName()).append(" to move");
          return sb.toString();
      }
      
      static void registerGameListListener(Client c) {
          gameListeners.add(c);
          sendGameListTo(c);
      }
      
      static void unregisterGameListListener(Client c) {
          gameListeners.remove(c);
      }
      
      static void updateGameListListeners(final String st) {
          new Thread() {
              @Override
              public void run() {
                  for (Client cc : gameListeners) {
                      cc.sendWithoutLogging("GameList " + st);
                  }
              }
          }.start();
      }
      
      @Override
      public String toString() {
          StringBuilder sb=new StringBuilder(shortDesc());
          sb.append("\n").append(board.getBoardString());
          return sb.toString();
      }
      
      private boolean boundsCheck(char file, int rank) {
          int fl = file - 'A';
          int rk = rank - 1;
          return fl<board.boardSize && fl>=0 && rk<board.boardSize && rk>=0;
      }
      
      private boolean isWhitesTurn() {
          return board.moveCount%2 == 0;
      }
      
      private boolean turnOf(Player p) {
          boolean whiteTurn = isWhitesTurn();
          return ((p==white)==whiteTurn)||((p==black)==!whiteTurn);
      }
      
      String sqState(char file, int rank) {
          Board.Square sq = board.getSquare(file, rank);
          if(sq==null)
              return "[]";
          return sq.stackString();
      }
      
      void checkOutOfPieces() {
          if(gameState!=gameS.NONE)
              return;
          if((board.whiteTilesCount==0 && board.whiteCapstones==0) ||
                  (board.blackTilesCount==0 && board.blackCapstones==0)){
              System.out.println("out of pieces.");
              findWhoWon();
          }
      }
      
      void timeCleanup() {
          white.getClient().removeGame(this);
          black.getClient().removeGame(this);
          Game.removeGame(this);
      }
      
      void updateTime(Client c) {
          if(whiteTime == -1 || board.moveCount == 0)
              return;
          
          long curTime = System.nanoTime();
          long elapsedMS = (curTime - lastUpdateTime)/1000000;
          long timeToCount = 0;
          
          if(!isWhitesTurn()) {
              blackTime -= elapsedMS;
          } else {
              whiteTime -= elapsedMS;
          }
          
          lastUpdateTime = curTime;
  
          String msg="Game#"+no+" Time "+whiteTime/1000+" "+blackTime/1000;
          c.sendWithoutLogging(msg);
      }
      
      void updateTimeTurnChange() {
          if(whiteTime == -1 || gameState!=gameS.NONE)
              return;
          //start time after 1st move is played
          if(!timerStarted) {
              timerStarted = true;
              this.lastUpdateTime = System.nanoTime();
          }
          
          long curTime = System.nanoTime();
          long elapsedMS = (curTime - lastUpdateTime)/1000000;
          long timeToCount=0;
          
          if(isWhitesTurn()) {
              blackTime -= elapsedMS;
              timeToCount = whiteTime;
          } else {
              whiteTime -= elapsedMS;
              timeToCount = blackTime;
          }
          
          lastUpdateTime = curTime;
          if(timeToCount<0)
              timeToCount = 0;
          
          timer.cancel();
          timer = new Timer();
          timer.schedule(new TimerTask() {
              @Override
              public void run() {
                  if(isWhitesTurn()) {
                      gameState = gameS.BLACK;
                  } else {
                      gameState = gameS.WHITE;
                  }
                  whenGameEnd();
                  timeCleanup();
              }
          }, timeToCount);
          String msg="Game#"+no+" Time "+whiteTime/1000+" "+blackTime/1000;
          white.getClient().sendWithoutLogging(msg);
          black.getClient().sendWithoutLogging(msg);
          sendToSpectators(msg);
      }
      
      Status placeMove(Player p, char file, int rank, boolean capstone,
              boolean wall) {
          //System.out.println("file = "+file+" rank="+rank+" capstone="
            //      +capstone+" wall="+wall);
          Board.Square sq = board.getSquare(file, rank);
          if(!turnOf(p))
              return new Status("Not your turn", false);
          
          if(sq!=null && sq.isEmpty()) {
              char ch;
              if(capstone)
                  ch = CAPSTONE;
              else if(wall)
                  ch = WALL;
              else
                  ch = FLAT;
              
              //First move should always be a flat
              if(board.moveCount/2 == 0 && !isFlat(ch))
                  return new Status("First move should be flat", false);
              
              //White places with capitals
              if(isWhitesTurn())
                  ch = (char)(ch - 'a'+'A');
              
              //first moves should be played with opponent pieces
              if(board.moveCount/2 == 0)
                  ch = Character.isUpperCase(ch)?Character.toLowerCase(ch):
                          Character.toUpperCase(ch);
              
              //check if enough capstones, and decrement if there are
              if(isCapstone(ch)) {
                  int caps = isWhitesTurn()?board.whiteCapstones:board.blackCapstones;
                  if(caps==0)
                      return new Status("You're out of capstones", false);
                  if(isWhitesTurn())
                      board.whiteCapstones--;
                  else
                      board.blackCapstones--;
              } else {
                  if(isWhitesTurn())
                      board.whiteTilesCount--;
                  else
                      board.blackTilesCount--;
              }
              
              sq.add(ch);
              board.moveCount++;
              String move="P "+file+rank+" "+(capstone?"C":"")+(wall?"W":"");
              moveList.add(move.trim());
              saveBoardPosition();
              undoRequestedBy = null;//remove any requests on move
              sendMove(p, move.trim());
              
              checkRoadWin();
              checkOutOfPieces();
              checkOutOfSquares();
              whenGameEnd();
              
              updateTimeTurnChange();
              return new Status(true);
          } else {
              return new Status("Square not empty", false);
          }
      }
      
      Player stackController(Board.Square sq) {
          return Character.isUpperCase(sq.topOfStack())?white:black;
      }
      
      boolean isCapstone(char c) {
          return Character.toLowerCase(c) == CAPSTONE;
      }
      
      boolean isWall(char c) {
          return Character.toLowerCase(c) == WALL;
      }
      
      boolean isFlat(char c) {
          return Character.toLowerCase(c) == FLAT;
      }
      
      boolean isBlack(char c) {
          return (c==FLAT)||(c==WALL)||(c==CAPSTONE);
      }
      
      boolean isWhite(char c) {
          return c!=0 && !isBlack(c);
      }
      
      static int abs(int x) {
          return x>0?x:-x;
      }
      
      String getPTN() {
          String ret="";
          for(int i=0;i<board.boardSize;i++){
              int xcnt=0;
              for(int j=0;j<board.boardSize;j++){
                  Board.Square sq = board.squares[i][j];
                  if(sq.isEmpty())
                      xcnt=0;
                  else {
                      ret+="x"+xcnt;
                      String cell="";
                      for(int k=0;k<sq.size();k++){
                          char ch = sq.get(k);
                          cell+=isWhite(ch)?"1":"2";
                          if(isWall(ch))
                              cell+="S";
                          else if(isCapstone(ch))
                              cell+="C";
                      }
                      if(j!=board.boardSize-1)
                          cell+=",";
                  }
              }
              ret+="/";
          }
          ret+=" "+board.moveCount+" "+(isWhitesTurn()?"1":"2");
          return ret;
      }
      
      Status moveMove(Player p, char f1, int r1, char f2, int r2, int[] vals) {
  //        System.out.print("moveMove "+f1+""+r1+"->"+f2+""+r2+" ");
  //        for(int i:vals)
  //            System.out.print(i);
  //        System.out.println("");
          //alternate turns
          if(!turnOf(p))
              return new Status("Not your turn", false);
          
          //first moves should be place moves
          if(board.moveCount/2==0)
              return new Status("First move should be place", false);
          
          //moves should be horizontal or vertical
          if(f1!=f2 && r1!=r2)
              return new Status("Move should be in straight line", false);
          
          Board.Square startSq = board.getSquare(f1, r1);
          Board.Square endSq = board.getSquare(f2, r2);
          //bounds checking of squares
          if(startSq == null || endSq == null)
              return new Status("Out of bounds", false);
          
          //stack should be controlled by current player
          if(p != stackController(startSq))
              return new Status("You don't control stack", false);
          
          //carry size should be less than or equal to boardSize and stack size
          int carrySize=0;
          for(int v:vals)
              carrySize+=v;
          if(carrySize>board.boardSize || carrySize>startSq.size())
              return new Status("Invalid move", false);
          
          //length of vals should be one less than no. of squares involved
          int num = (f1==f2)?abs(r1-r2):abs(f1-f2);
          if(vals.length != num)
              return new Status("Invalid move.", false);
          
          //first square should not be negative
          /*if(vals[0]<0)
              return new Status("Invalid input", false);*/
          //all other squares should be greater than 0
          for(int i=0;i<vals.length;i++)
              if(vals[i]<1)
                  return new Status("Should place atleast one tile in rest of"
                          + " the squares", false);
          
          char top = startSq.topOfStack();
          boolean capstone = isCapstone(top);
          
          SquareIterator sqIt = new SquareIterator(this, f1, r1, f2, r2);
          //check if none of the intermediate places are walls or capstones
          for(Board.Square sqr=sqIt.next();sqr!=null;sqr=sqIt.next()){
              if(sqr==startSq)
                  continue;
              char ttop = sqr.topOfStack();
              
              //can't stack over capstones
              if(isCapstone(ttop))
                  return new Status("Can't stack over capstones", false);
              
              //can't stack over walls.. but wall in last square can be flattened
              if(isWall(ttop)) {
                  if(sqr!=endSq)
                      return new Status("Can't stack over walls", false);
                  else {
                      if(vals[vals.length-1]!=1 || !capstone)
                          return new Status("Capstone should be on top to flatten"
                                  + " walls", false);
                  }
              }
          }
          
          //do the actual moving of stack
          sqIt.reset();
          int count=-1;
          Stack<Character> moveStack = new Stack<>();
          
          for(Board.Square sqr=sqIt.next();sqr!=null;
                  sqr=sqIt.next(),count++){
              assert(count<vals.length);
              
              //move to temporary stack
              if(sqr==startSq){
                  for(int i=0;i<carrySize;i++)
                      moveStack.push(sqr.pop());
              }
              //flatten
              else if(sqr == endSq && moveStack.size()==1 &&
                      isCapstone(moveStack.peek()) && isWall(sqr.topOfStack())){
                  char ch = sqr.pop();
                  sqr.add(isWhite(ch)?Character.toUpperCase(FLAT):FLAT);
                  sqr.add(moveStack.pop());
              }
              //move elements
              else {
                  for(int i=0;i<vals[count];i++){
                      sqr.add(moveStack.pop());
                  }
              }
          }
          board.moveCount++;
          
          String move = "M "+f1+r1+" "+f2+r2+" ";
          for(int val: vals)
              move+=val+" ";
          moveList.add(move.trim());
          saveBoardPosition();
          undoRequestedBy = null;//remove any requests on move
          sendMove(p, move.trim());
          
          checkRoadWin();
          checkOutOfSquares();
          whenGameEnd();
          
          updateTimeTurnChange();
          
          return new Status(true);
      }
      
      void whenGameEnd() {
          if(gameState==gameS.NONE)
              return;
          String msg="";
          msg += gameStateString();
          timer.cancel();
          
          if(!abandoned)
              msg = "Game#"+no+" Over "+msg;
          else {
              Player abandoningPlayer;
              if(gameState==gameS.WHITE)
                  abandoningPlayer = black;
              else
                  abandoningPlayer = white;
              
              msg = "Game#"+no+" Abandoned. "+abandoningPlayer.getName()+" quit";
          }
          
          if(!(abandoned && gameState==gameS.BLACK))
              white.getClient().send(msg);
          if(!(abandoned && gameState==gameS.WHITE))
              black.getClient().send(msg);
          sendToSpectators(msg);
          
          saveToDB();
      }
      
      private String moveListString() {
          StringBuilder sb = new StringBuilder();
          String prefix = "";
          for(String move: moveList) {
              sb.append(prefix);
              prefix = ",";
              sb.append(move);
          }
          return sb.toString();
      }
      
      private String gameStateString() {
          switch(gameState) {
              case DRAW: return "1/2-1/2";
              case WHITE_ROAD: return "R-0";
              case BLACK_ROAD: return "0-R";
              case WHITE_TILE: return "F-0";
              case BLACK_TILE: return "0-F";
              case WHITE: return "1-0";
              case BLACK: return "0-1";
              default: return "---";
          }
      }
      
      void saveToDB() {
          try {
              Statement stmt = Database.connection.createStatement();
              String sql = "INSERT INTO games "+
                      "VALUES (NULL,"+time+","+board.boardSize+",'"+white.getName()+"','"+black.getName()+"','"+moveListString()+"','"+
                      gameStateString()+"');";
              //System.out.println("SQL:: "+sql);
              stmt.executeUpdate(sql);
              stmt.close();
          } catch (SQLException ex) {
              Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
          }
      }
      
      void sendMove(Player p, String move) {
          String msg="Game#"+no+" "+move;
          sendToOtherPlayer(p, msg);
          sendToSpectators(msg);
      }
      
      void sendToOtherPlayer(Player p, String move) {
          if(white==p)
              black.getClient().sendWithoutLogging(move);
          else
              white.getClient().sendWithoutLogging(move);
      }
      
      void sendToSpectators(final String msg) {
          new Thread() {
              @Override
              public void run() {
                  for(Client c:spectators)
                      c.sendWithoutLogging(msg);
              }
          }.start();
      }
      void findWhoWon() {
          int blackCount=0, whiteCount=0;
          for(int i=0;i<board.boardSize;i++){
              for(int j=0;j<board.boardSize;j++){
                  char ch = board.squares[i][j].topOfStack();
                  if(ch!=0 && !isWall(ch) && !isCapstone(ch)){
                      if(isWhite(ch))
                          whiteCount++;
                      else
                          blackCount++;
                  }
              }
          }
          if(whiteCount==blackCount)
              gameState = gameS.DRAW;
          else if(whiteCount>blackCount)
              gameState = gameS.WHITE_TILE;
          else
              gameState = gameS.BLACK_TILE;
      }
      void checkOutOfSquares() {
          if(gameState!=gameS.NONE)
              return;
          for(int i=0;i<board.boardSize;i++){
              for(int j=0;j<board.boardSize;j++){
                  if(board.squares[i][j].isEmpty())
                      return;
              }
          }
          System.out.println("Out of squares");
          findWhoWon();
      }
      
      void checkRoadWin() {
  