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 + "\"");
}
}