Package

Source Code of AIPlayer$historyComparator

import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JFrame;


/*
* Notes:
* alphabeta search does not account for repeated positions.
* Do something to do with arbitrary moves?
*
* @author Nicholas Gyd�
*/


public class AIPlayer implements Player{
 
  private final static short RESIGN_CODE = 5555;
 
  public int promotionFlag;
 
  private Chess chessGame;
  private boolean iAmWhite;
  private boolean myTurn;
  public boolean justBecameMyTurn; //an interrupt flag
  private int searchDepth = 6;
  private short numMoves;
  private byte[] historyHeuristicButterfly;
  //private MinimaxNode[] doIfButterfly = new MinimaxNode[4096]; //indexed by opponent's move
  private static final int HISTORY_SCALAR = 1;
  private static final int TRANSPOSITION_CAPACITY = 1000000;
  private static final int PIECE_SCALAR = 100;
  private static final int MOBILITY_SCALAR = 8;
  private static int debug = 0;
  public static Random random = new Random();
 
  private static final int[] pointValues = new int[] {PIECE_SCALAR*4,PIECE_SCALAR*20, PIECE_SCALAR*10,PIECE_SCALAR*11,
                            PIECE_SCALAR*36,PIECE_SCALAR*16,0,0};
  private static final short[][] moveHelper =
      new short[][]{ {8,16,7,9, -8,-16,-7,-9}
            ,{-1,1,-8,8}
            ,{6,10,15,17,-6,-10,-15,-17}
            ,{7,9,-7,-9}
            ,{7,9,-7,-9,-1,1,-8,8}
            ,{7,9,-7,-9,-1,1,-8,8,2,-2}};
 
  private static final String openingBook =
        "(e2e4"
      + "  (e7e5"
      + "     (g1f3"
      + "       (b8c6"
      + "         (f1c4 f1b5)))"
      + "   c7c5"
      + "     (b1c3 "
      + "       (b8c6 e7d6 d7d6)"
      + "      g1f3"
      + "       (b8c6"
      + "         (d2d4)"
      + "        d7d6 (d2d4 (c5d4 (f3d4 (g8f6 (b1c3)))))))" //sicilian
      + "   e7e6 d7d6 g7g6 g8f6 d7d5)"
      + " g1f3" //reti
      + "   (d7d5"
      + "     (c2c4"
      + "       (e8e6 d5c4 (e2e3 e2e4 b1a3 d1a4) d5d4)))"
      + " d2d4"
      + "   (g8f6"
      + "     (c2c4"
      + "       (e7e6 g7g6 c7c5))"
      + "    d7d5"
      + "     (c2c4"
      + "       (e7e6 c7c6 d5c4))"
      + "    e7e6"
      + "     (e2e4 c2c4 g1f3)"
      + "    d7d6"
      + "     (e2e4 g1f3 c2c4))"
      + " c2c4" //english
      + "    (g8f6 "
      + "       (d2d4 (e7e6 g7g6) g1f3 (e7e6 g7g6) b1c3 (e7e5 e7e6))"
      + "     e7e5 (b1c3 (b8c6 g8f6) g2g3 (g8f6 b8c6))"
      + "     e7e6 (d2d4 (g8f6 d7d5) g1f3 (g8f6 d7d5) b1c3 (g8f6 d7d5))"
      + "     c7c5 (g1f3 (g8f6 b8f6) b1c3 (g8f6 d7d5))"
      + "     g7g6 (d2d4 (g8f6 f8g7))"
      + "     c7c6 (e2e4 (d7d5 e7e5) d2d4 "
      + "                     (d7d5" //slav
      + "                      (b1c3 (e7e5 e7e6)"
      + "                       c4d5 (c6d5)"
      + "                       g1f3 (e7e6 g8f6 (b1c3 (a7a6 d5c4 (a2a4 (b8a6 e7e6 c8f5 (e2e3 f3e5 f3h4) c8g4))))))))))";
 
  private static final short[] goodOpeningMovesW = new short[]{
    cnToMove("e2e4"), cnToMove("e2e3"), cnToMove("d2d4"), cnToMove("d2d3"),
    cnToMove("c2c3"), cnToMove("f2f3"), cnToMove("b1c3"), cnToMove("b1d2"),
    cnToMove("g1f3"), cnToMove("g1e2"), cnToMove("c1d2"), cnToMove("c1e3"),
    cnToMove("c1f4"), cnToMove("c1b2"), cnToMove("f1e2"), cnToMove("f1d3"),
    cnToMove("f1c4"), cnToMove("f1g2"), cnToMove("e1g1"), cnToMove("e1c1")
  };
  private static final short[] goodOpeningMovesB = new short[]{
    cnToMove("e7e5"), cnToMove("e7e6"), cnToMove("d7d5"), cnToMove("d7d6"),
    cnToMove("c7c5"), cnToMove("f7f6"), cnToMove("b8c6"), cnToMove("b8d6"),
    cnToMove("g8f6"), cnToMove("g8e7"), cnToMove("c8d7"), cnToMove("c1e6"),
    cnToMove("c8f5"), cnToMove("c8b7"), cnToMove("f8e7"), cnToMove("f8d6"),
    cnToMove("f8c5"), cnToMove("f8g7"), cnToMove("e8g8"), cnToMove("e8c8")
  };
 
  public AIPlayer(Chess chessGame, boolean playingWhite){
    this.chessGame = chessGame; iAmWhite = playingWhite; numMoves = 0;
    promotionFlag = GameState.QUEEN;
    //transTable =  new Hashtable<GameState, TTEntry>();
    transTable = new LinkedHashMap<GameState, TTEntry>(TRANSPOSITION_CAPACITY, 0.75f, true);
    historyHeuristicButterfly = new byte[4096];
  }

  private static short moveFromSchemeStyleBook(String book, List<String> cnIn) {
    ArrayList<String> cn = new ArrayList<String>();
    cn.addAll(cnIn);
    char[] chars = book.toCharArray();
    int i = 0; int j = chars.length;
    String mv = "";
    while (!cn.isEmpty()) {
      mv = cn.get(0);
      cn.remove(0);
      for (int k = i+1; k < j-1; ++k) {
        if (chars[k] == '(') {
          int depth = 1; ++k;
          while(depth > 0){
            if (chars[k] == '(') ++depth;
            if (chars[k] == ')') --depth;
            ++k;
          }
        }
        if (chars[k]   == mv.charAt(0) &&
          chars[k+1] == mv.charAt(1) &&
          chars[k+2] == mv.charAt(2) &&
          chars[k+3] == mv.charAt(3)) {
            i=k+4;
            while (chars[i] != '(' && k < book.length()){
              ++i;
              if (chars[i] != ' ' && chars[i] != '(') return RESIGN_CODE;
            }
            --j;
            while (chars[j] != ')') --j;
            break;
        }
        if (k >= j-2) return RESIGN_CODE;
      }
    }
    ArrayList<String> choices = new ArrayList<String>();
    for (int k = i+1; k < j-1; k+=4) {
      while (chars[k] == ' ') ++k;
      if (chars[k] == '(') {
        int depth = 1; ++k;
        while(depth > 0){
          if (chars[k] == '(') ++depth;
          if (chars[k] == ')') --depth;
          ++k;
        }
      }
      while (chars[k] == ' ') ++k;
      if(chars[k] == ')') break;
      choices.add(book.substring(k,k+4));
    }
    if (choices.isEmpty()) return RESIGN_CODE;
      return cnToMove(choices.get(random.nextInt(choices.size())));
  }
 
  private static short cnToMove(String cn) {
    return ((short)
        ((((short)(cn.charAt(0) - 'a') +
        (short)(cn.charAt(1)-'0'-1)*8)<<6) +
        (short)(cn.charAt(2)-'a') +
        (short)(cn.charAt(3)-'0'-1)*8));
  }
 
  public void makeMove(GameState currState){
    long start = System.nanoTime();
    short move;
    transTable.clear();
    int depth = searchDepth;   
    MinimaxNode[] children = childrenOf(new MinimaxNode((short)9999, currState), iAmWhite);
    MinimaxNode child;
    MinimaxNode bestNode = new MinimaxNode(RESIGN_CODE, currState);
    move = moveFromSchemeStyleBook(openingBook, chessGame.cn);
    if (move == RESIGN_CODE){ 
      //Check if I thought of anything during opponent's move.
      //short lastMove = cnToMove(chessGame.cn.get(chessGame.cn.size()-1));
      //if (doIfButterfly[lastMove] != null && doIfButterfly[lastMove].move != RESIGN_CODE) move = doIfButterfly[lastMove].move;
      //else {
        //short move = RESIGN_CODE;
      int color = iAmWhite?1:-1; int val;
      int alpha = Integer.MIN_VALUE+1; int beta = Integer.MAX_VALUE;
      int bestValue = alpha;
      children = OrderMoves(children);
      for (int i = 0; i < children.length; ++i) {
        child = children[i];
        //chessGame.lineBoards[5].setGameState(child.state); //DEBUG
        val = -negamax(child.state, depth-1, -beta, -alpha, -color);
        if (val > bestValue) {bestValue = val; bestNode = child;}
        alpha = Math.max(alpha, val);
        if (alpha >= beta) {
          if (color>0 && currState.pieceType(child.move%64) >= GameState.BLANK0) { //history heuristic
            historyHeuristicButterfly[child.move] += HISTORY_SCALAR*depth*depth;
          }
          break;
        }
      }
      move = bestNode.move;
    }
 
    if (move == RESIGN_CODE) resign();
    else {
      //If move is promotion, set promotion flag.
      if (currState.pieceType(move>>>6) == GameState.PAWN && move%8 == 7) {
        promotionFlag = bestNode.state.pieceType(move%64);
      }
     
      if (!chessGame.doIfLegal(move>>>6, move%64)) System.err.println("AI attempted illegal move: " + (move>>>6) + " TO " + move%64);
      else historyHeuristicButterfly[move] = 0;
      if (currState.afterMove(move>>>6, move%64, true, chessGame)[0].moverInCheck()) {
        if (currState.afterMove(move>>>6, move%64, true, chessGame)[0].isCheckmate()) say("\nCheckmate. Good game.");
        else say("Check.");
      }
      ++numMoves;
    }
  }

  private void think(){
    /*justBecameMyTurn = false; //maybe not needed
    while(!myTurn) {
    doIfButterfly = new MinimaxNode[4096];
    Arrays.fill(doIfButterfly, null);
    GameState currState = chessGame.currentState();
    short move;
    int depth = searchDepth;
    while(!myTurn){
      MinimaxNode[] opchildren = childrenOf(new MinimaxNode((short)9999, currState), !iAmWhite);
      for (MinimaxNode opChild : opchildren) {
        if (justBecameMyTurn || myTurn) break;
        MinimaxNode[] children = childrenOf(opChild, iAmWhite);
        MinimaxNode child;
        MinimaxNode bestNode = new MinimaxNode(RESIGN_CODE, currState);
          int color = iAmWhite?1:-1; int val;
          int alpha = Integer.MIN_VALUE+1; int beta = Integer.MAX_VALUE;
          int bestValue = alpha;
          children = OrderMoves(children);
          for (int i = 0; i < children.length; ++i) {
            child = children[i];
            //chessGame.lineBoards[5].setGameState(child.state); //DEBUG
            val = -negamax(child.state, depth-1, -beta, -alpha, -color);
            if (val > bestValue) {bestValue = val; bestNode = child;}
            alpha = Math.max(alpha, val);
            if (alpha >= beta) {
              if (color>0 && currState.pieceType(child.move%64) >= GameState.BLANK0) { //history heuristic
                historyHeuristicButterfly[child.move] += HISTORY_SCALAR*depth*depth;
              }
              break;
            }
          }
          doIfButterfly[opChild.move] = bestNode;
        }
        ++depth;
      }
    }*/
  }
 
  public int inquirePromotionType(){
    return promotionFlag;
  }
 
  private void resign(){
    say("I resign.");
    chessGame.resign(this);
  }
 
  /*
   * negamax with alpha-beta pruning and history heuristic move ordering.
   */
  private int negamax(GameState node, int depth, int alpha, int beta, int color){
    //if (!myTurn && chessGame.currentState().whiteToMove()== iAmWhite) {myTurn = true; justBecameMyTurn = true;}
    if (depth == 0) return color*heuristicVal(node);
    int bestValue = Integer.MIN_VALUE+1; int val;
    MinimaxNode child; MinimaxNode[] children = childrenOf(new MinimaxNode((short)9999, node), color>0);
    children = OrderMoves(children);
    for (int i = 0; i < children.length; ++i) {
      child = children[i];
      //if (justBecameMyTurn && depth > searchDepth) val = heuristicVal(node);
      val = -negamax(child.state, depth-1, -beta, -alpha, -color);
      /*if (chessGame.gameStates.size()-depth < 17 && val>Integer.MIN_VALUE+1 && val<Integer.MAX_VALUE) { //If this is the opening, check for good opening move bonuses.
        for (short good : goodOpeningMovesW) if (child.move == good) val+=color;
        for (short good : goodOpeningMovesB) if (child.move == good) val-=color;
      }*/
      bestValue = Math.max(bestValue, val);
      alpha = Math.max(alpha, val);
      if (alpha >= beta) {
        short mv = child.move;
        if (color>0 && node.pieceType(mv%64) >= GameState.BLANK0) { //history heuristic
          historyHeuristicButterfly[mv] += HISTORY_SCALAR*depth*depth;
        }
        break;
      }
    }
    return bestValue;
  }
 
  private LinkedHashMap<GameState, TTEntry> transTable;
 
  /*
   * negamax with alpha-beta pruning, transposition table, and history heuristic move ordering.
   */
  private int negamaxTH(GameState node, int depth, int alpha, int beta, int color){
    ///
    int alphaOrig = alpha;
    //TTEntry ttEntry = transTable.get(hashKey(node)); TODO
    TTEntry ttEntry = null;
    if (ttEntry != null && ttEntry.depth >= depth) {
      if (ttEntry.flag == TTEntry.EXACT) {
        System.out.println("EXACT hit.");
        return ttEntry.value;
      }
      if (ttEntry.flag == TTEntry.LOWERBOUND){
        System.out.println("LOW hit.");
        alpha = Math.max(alpha, ttEntry.value);
      }else if
         (ttEntry.flag == TTEntry.UPPERBOUND){
        System.out.println("HIGH hit.");
        beta = Math.min(beta, ttEntry.value);
      }
      if (alpha >= beta) return ttEntry.value;
    }
    ///
    if (depth == 0) return color*heuristicVal(node);
    int bestValue = Integer.MIN_VALUE+1; int val;
    MinimaxNode child; MinimaxNode[] children = childrenOf(new MinimaxNode((short)9999, node), color>0);
    children = OrderMoves(children);
    for (int i = 0; i < children.length; ++i) {
      child = children[i];
      /*if (depth == 5) {
        chessGame.lineBoards[4].setGameState(child.state); //DEBUG
      }
      if (depth == 4) {
        chessGame.lineBoards[3].setGameState(child.state); //DEBUG
      }*/
      val = -negamaxTH(child.state, depth-1, -beta, -alpha, -color);
      bestValue = Math.max(bestValue, val);
      alpha = Math.max(alpha, val);
      if (alpha >= beta) {
        if (color>0 && node.pieceType(child.move%64) >= GameState.BLANK0) { //history heuristic
          historyHeuristicButterfly[child.move] += HISTORY_SCALAR*depth*depth;
        }
        break;
      }
    }
    ///
    byte flag;
    if (bestValue <= alphaOrig) flag = TTEntry.UPPERBOUND;
    else if (bestValue >= beta) flag = TTEntry.LOWERBOUND;
    else flag = TTEntry.EXACT;
    if (ttEntry == null) {
      ttEntry = new TTEntry(flag, bestValue, depth);
      //transTable.put(hashKey(node), ttEntry); TODO
    } else {
      ttEntry.flag = flag; ttEntry.value = bestValue; ttEntry.depth = depth;
    }
    return bestValue;
  }
 
  /*
   * Order the children using the history heuristic.
   */
  private MinimaxNode[] OrderMoves(MinimaxNode[] children) {
    if (children.length > 0) Arrays.sort(children, new historyComparator());
    return children;
  }
 
  private class historyComparator implements Comparator<MinimaxNode>{
    @Override
    public int compare(MinimaxNode o1, MinimaxNode o2) {
      int color = o1.state.whiteToMove()?1:-1;
      int o1VAL = color*historyHeuristicButterfly[o1.move];
      int o2VAL = color*historyHeuristicButterfly[o2.move];
      return color*Integer.signum(o1VAL - o2VAL);
    }
  }
 
  private class MinimaxNode {
    GameState state;
    short move;
    public MinimaxNode (short mv, GameState gs){move = mv; state = gs;}
  }

  /*
   * Returns the heuristic value of a position in quarter centipawns.
   */
  private int heuristicVal(GameState gs){

    int whiteScore = 0; int blackScore = 0;
    //Checkmates are infinitely good/bad
    if (gs.isCheckmate()) {
      if (gs.whiteToMove()) {return Integer.MIN_VALUE+1;}
      else           {return Integer.MAX_VALUE;}
    }
   
    //for each square
    for (byte i = 0; i < 64; ++i){
      //material points.
      if (gs.isWhite(i)){
        whiteScore += pointValues[gs.pieceType(i)];
      } else {
        blackScore += pointValues[gs.pieceType(i)];
      }
    }
   
    return whiteScore-blackScore;
  }
 
  private class TTEntry {
    public static final byte LOWERBOUND = 0;
    public static final byte UPPERBOUND = 1;
    public static final byte EXACT = 2;
    public byte flag;
    public int value;
    public int depth;
    public TTEntry(byte flag, int value, int depth) {this.flag = flag; this.value = value; this.depth = depth;}
  }
 
  private MinimaxNode[] childrenOf(MinimaxNode mn, boolean whitimizer) {
    Stack<Short> moves = candidateMoves(mn.state, whitimizer);
    Stack<MinimaxNode> nodes = new Stack(); short move;
    while (!moves.isEmpty()) {
      move = moves.pop();
      GameState[] hypGSs = mn.state.afterMove(move>>>6, move%64, true, chessGame);
      nodes.push(new MinimaxNode(move, hypGSs[0]));
      if (hypGSs.length > 1) {
      nodes.push(new MinimaxNode(move, hypGSs[1]));
      nodes.push(new MinimaxNode(move, hypGSs[2]));
      nodes.push(new MinimaxNode(move, hypGSs[3]));
      }
    }
    return nodes.toArray(new MinimaxNode[nodes.size()]);
  }
 
  private Stack<Short> candidateMoves(GameState gs, boolean whitimizer){
    //ArrayList<Short> candid = new ArrayList<Short>();
    Stack<Short> candid = new Stack();
    for (byte i = 0; i < 64; ++i){
      if (gs.isWhite(i) == whitimizer) {
        switch (gs.pieceType(i)){
       
        case GameState.PAWN:
          for (short mv : moveHelper[GameState.PAWN]){
            if (gs.legalMove(i, i+mv)) {
              candid.add((short)((i<<6)+(i+mv)));
            }
          }
        break;
        case GameState.ROOK:
          byte nsewOK = 15;
          for (short multiplier = 1; multiplier < 9; ++multiplier){
            if ((nsewOK&1) == 1) {if (gs.legalMove(i, i+(multiplier<<3))) candid.add((short)((i<<6)+i+(multiplier<<3)));
            else if (!gs.moverInCheck())nsewOK&=0xe;}
            if ((nsewOK&2) == 2) {if (gs.legalMove(i, i-(multiplier<<3))) candid.add((short)((i<<6)+i-(multiplier<<3)));
            else if (!gs.moverInCheck())nsewOK&=0xd;}
            if ((nsewOK&4) == 4) {if (gs.legalMove(i, i+multiplier))    candid.add((short)((i<<6)+i+multiplier));
            else if (!gs.moverInCheck())nsewOK&=0xb;}
            if ((nsewOK&8) == 8) {if (gs.legalMove(i, i-multiplier))    candid.add((short)((i<<6)+i-multiplier));
            else if (!gs.moverInCheck())nsewOK&=0x7;}
            if (nsewOK == 0) break;
          }
        break;
        case GameState.KNIGHT:
          for (short mv : moveHelper[GameState.KNIGHT]){
            if (gs.legalMove(i, i+mv)) {
              candid.add((short)((i<<6)+(i+mv)));
            }
          }
        break;
        case GameState.BISHOP:
          byte quadOK = 15;
          for (short multiplier = 1; multiplier < 9; ++multiplier){
            if ((quadOK&1) == 1) {if (gs.legalMove(i, i+(multiplier<<3)+multiplier)) candid.add((short)((i<<6)+i+(multiplier<<3)+multiplier));
            else if (!gs.moverInCheck())quadOK&=0xe;}
            if ((quadOK&2) == 2) {if (gs.legalMove(i, i-(multiplier<<3)-multiplier)) candid.add((short)((i<<6)+i-(multiplier<<3)-multiplier));
            else if (!gs.moverInCheck())quadOK&=0xd;}
            if ((quadOK&4) == 4) {if (gs.legalMove(i, i+multiplier-(multiplier<<3)))    candid.add((short)((i<<6)+i+multiplier-(multiplier<<3)));
            else if (!gs.moverInCheck())quadOK&=0xb;}
            if ((quadOK&8) == 8) {if (gs.legalMove(i, i-multiplier+(multiplier<<3)))    candid.add((short)((i<<6)+i-multiplier+(multiplier<<3)));
            else if (!gs.moverInCheck())quadOK&=0x7;}
            if (quadOK == 0) break;
          }
        break;
        case GameState.QUEEN:
          byte quadOKq = 15;
          byte nsewOKq = 15;
          for (short multiplier = 1; multiplier < 9; ++multiplier){
            if ((nsewOKq&1) == 1) {if (gs.legalMove(i, i+(multiplier<<3))) candid.add((short)((i<<6)+i+(multiplier<<3)));
            else if (!gs.moverInCheck())nsewOKq&=0xe;}
            if ((nsewOKq&2) == 2) {if (gs.legalMove(i, i-(multiplier<<3))) candid.add((short)((i<<6)+i-(multiplier<<3)));
            else if (!gs.moverInCheck())nsewOKq&=0xd;}
            if ((nsewOKq&4) == 4) {if (gs.legalMove(i, i+multiplier))    candid.add((short)((i<<6)+i+multiplier));
            else if (!gs.moverInCheck())nsewOKq&=0xb;}
            if ((nsewOKq&8) == 8) {if (gs.legalMove(i, i-multiplier))    candid.add((short)((i<<6)+i-multiplier));
            else if (!gs.moverInCheck())nsewOKq&=0x7;}
            if ((quadOKq&1) == 1) {if (gs.legalMove(i, i+(multiplier<<3)+multiplier)) candid.add((short)((i<<6)+i+(multiplier<<3)+multiplier));
            else if (!gs.moverInCheck())quadOKq&=0xe;}
            if ((quadOKq&2) == 2) {if (gs.legalMove(i, i-(multiplier<<3)-multiplier)) candid.add((short)((i<<6)+i-(multiplier<<3)-multiplier));
            else if (!gs.moverInCheck())quadOKq&=0xd;}
            if ((quadOKq&4) == 4) {if (gs.legalMove(i, i+multiplier-(multiplier<<3)))    candid.add((short)((i<<6)+i+multiplier-(multiplier<<3)));
            else if (!gs.moverInCheck())quadOKq&=0xb;}
            if ((quadOKq&8) == 8) {if (gs.legalMove(i, i-multiplier+(multiplier<<3)))    candid.add((short)((i<<6)+i-multiplier+(multiplier<<3)));
            else if (!gs.moverInCheck())quadOKq&=0x7;}
            if (nsewOKq == 0 && quadOKq == 0) break;
          }
          break;
          case GameState.KING:
          for (short mv : moveHelper[GameState.KING]){
            if (gs.legalMove(i, i+mv)) {
              candid.add((short)((i<<6)+(i+mv)));
            }
          }
        }
      }
    }
    return candid;
  }
 
  private void updateRepetition(GameState gs){
    //TODO
  }

  @Override
  public void run() {
    while(!chessGame.gameStopped){
      if (chessGame.currentState().whiteToMove() == iAmWhite) {if(!myTurn)justBecameMyTurn = true; myTurn = true;} else myTurn = false;
      if (myTurn) {
        if (chessGame.currentState().isCheckmate() || chessGame.currentState().isStalemate()) {
          say("Good game."); myTurn = false; return;
        }
        makeMove(chessGame.currentState());
        myTurn = false;
        }
      else think();
      try {
        Thread.sleep(5);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
 
  private void say(String s){
    chessGame.conveyMessage("\nAI says \"" + s + "\"");
  }
}
TOP

Related Classes of AIPlayer$historyComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.