package search.game; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.UnsupportedEncodingException; /** * Chess game * * <p/> * Copyright 2002-2012 by Mark Watson. All rights reserved. * <p/> * This software is can be used under either of the following licenses: * <p/> * 1. LGPL v3<br/> * 2. Apache 2 * <p/> */ public class Chess extends GameSearch { // note: set the following to false if running in IntelliJ IDE: console does not display // unicode characters. Usually, you want to set to true (at least try it). static public boolean USE_UNICODE_CHARS = true; // set to true for graphics characters /** * Notes: PROGRAM false -1, HUMAN true 1 */ /** * PUBLIC API (mostly from GameSearch) */ public boolean drawnPosition(Position p) { /** * We want to keep searching: */ return false; } public boolean wonPosition(Position p, boolean player) { // eventually, we should check for checkmates here... return false; } final public float positionEvaluation(Position p, boolean player) { ChessPosition pos = (ChessPosition)p; int [] b = pos.board; float ret = 0.0f; // adjust for material: for (int i=22; i<100; i++) { if (b[i] != 0 && b[i] != 7) ret += 1.75f * b[i]; } // adjust for positional advantages: setControlData(pos); int control = 0; for (int i=22; i<100; i++) { control += humanControl[i]; control -= computerControl[i]; } // Count center squares extra: control += humanControl[55] - computerControl[55]; control += humanControl[56] - computerControl[56]; control += humanControl[65] - computerControl[65]; control += humanControl[66] - computerControl[66]; control /= 10.0f; ret += control; // credit for attacked pieces: for (int i=22; i<100; i++) { if (b[i] == 0 || b[i] == 7) continue; if (b[i] < 0) { if (humanControl[i] > computerControl[i]) { ret += 0.9f * value[-b[i]]; } if (b[i] == -ChessPosition.QUEEN && humanControl[i]>0) ret += 2; if (b[i] == -ChessPosition.KING && humanControl[i]>0) ret += 4; } if (b[i] > 0) { if (humanControl[i] < computerControl[i]) { ret -= 0.9f * value[b[i]]; } if (b[i] == ChessPosition.QUEEN && humanControl[i]>0) ret -= 2; if (b[i] == ChessPosition.KING && humanControl[i]>0) ret -= 4; } } // adjust if computer side to move: if (!player) ret = -ret; return ret; } public void printPosition(Position p) { System.out.println("Board position:"); ChessPosition pos = (ChessPosition)p; int [] b = pos.board; for (int col=92; col>=22; col-=10) { System.out.println(); for (int ii=0; ii<8; ii++) { int i = ii + col; if (b[i] != 0) { System.out.print(pp(b[i], i)); } else { boolean white_sq = true; for (int k=0; k<blackSquares.length; k++) { if (i == blackSquares[k]) { white_sq = false; break; } } if (white_sq) System.out.print(" "); else System.out.print(" . "); } } } System.out.println(); } private String pp(int piece, int square_index) { if (piece == 0) return " "; if (USE_UNICODE_CHARS) { switch (piece) { case 1: return " \u2659 "; case 2: return " \u2658 "; case 3: return " \u2657 "; case 4: return " \u2656 "; case 5: return " \u2655 "; case 9: return " \u2654 "; case -1: return " \u265F "; case -2: return " \u265E "; case -3: return " \u265D "; case -4: return " \u265C "; case -5: return " \u265B "; case -9: return " \u265A "; } return "error"; } else { String color; if (piece < 0) color = "B"; else color = "W"; int p = piece; if (p < 0) p = -p; switch (p) { case 1: return " " + color + "P"; case 2: return " " + color + "N"; case 3: return " " + color + "B"; case 4: return " " + color + "R"; case 5: return " " + color + "Q"; case 9: return " " + color + "K"; } return "error"; } } final public Position [] possibleMoves(Position p, boolean player) { if (GameSearch.DEBUG) System.out.println("posibleMoves("+p+","+player+")"); ChessPosition pos = (ChessPosition)p; //System.out.println("Chess.possibleMoves(): pos=" + pos); //for (int i=22; i<40; i++) System.out.println(pos.board[i]); int num = calcPossibleMoves(pos, player); if (num == 0) { System.out.println("Stalemate"); System.exit(0); } ChessPosition [] chessPos = new ChessPosition[num]; for (int i=0; i<num; i++) { chessPos[i] = new ChessPosition(); for (int j=22; j<100; j++) chessPos[i].board[j] = pos.board[j]; chessPos[i].board[possibleMoveList[i].to] = chessPos[i].board[possibleMoveList[i].from]; chessPos[i].board[possibleMoveList[i].from] = 0; } return chessPos; } public Position makeMove(Position p, boolean player, Move move) { if (GameSearch.DEBUG) System.out.println("Entered Chess.makeMove"); ChessMove m = (ChessMove)move; ChessPosition pos = (ChessPosition)p; ChessPosition pos2 = new ChessPosition(); for (int i=0; i<120; i++) pos2.board[i] = pos.board[i]; int pp; if (player) pp = 1; else pp = -1; if (GameSearch.DEBUG) System.out.println("makeMove: m.from = " + m.from + ", m.to = " + m.to); pos2.board[m.to] = pos2.board[m.from]; pos2.board[m.from] = 0; return pos2; } final public boolean reachedMaxDepth(Position p, int depth) { if (depth < 5) return false; return true; } private BufferedReader in = null; public Move createMove() { if (GameSearch.DEBUG) System.out.println("Enter blank square index [0,8]:"); ChessMove mm = new ChessMove(); System.out.println("enter a move like 'd2d4' or 'oo'"); try { if (in == null) { in = new BufferedReader(new InputStreamReader(System.in)); } String s = in.readLine().toLowerCase(); System.out.println("s="+s); // TBD: check for oo and ooo: char c0 = (char)(s.charAt(0) - 'a' + 2); char r0 = (char)(s.charAt(1) - '1' + 2); char c1 = (char)(s.charAt(2) - 'a' + 2); char r1 = (char)(s.charAt(3) - '1' + 2); mm.from = r0*10+c0; mm.to = r1*10+c1; System.out.println("From " + mm.from + ", to " + mm.to); } catch (Exception e) { System.out.println(e); } return mm; } static public void main(String [] args) throws UnsupportedEncodingException { System.out.println("\u2654"); String unicodeMessage = "\u2654 " + // white king "\u2655 " + // white queen "\u2656 " + // white rook "\u2657 " + // white bishop "\u2658 " + // white knight "\u2659 " + // white pawn "\n" + "\u265A " + // black queen "\u265B " + // black queen "\u265C " + // black rook "\u265D " + // black bishop "\u265E " + // black knight "\u265F " + // black pawn "\n" + "\u2610 " + "\u2612 " + "\u25A0 " + "\u25FC"; // trying for white and black squares System.out.println(unicodeMessage); PrintStream out = new PrintStream (System.out, true , "UTF8" ); out.println(unicodeMessage); ChessPosition p = new ChessPosition(); for (int i=0; i<120; i++) p.board[i] = initialBoard[i]; Chess ttt = new Chess(); /* DEBUG*/ // ttt.setControlData(p); ttt.playGame(p, true); } /** * PRIVATE API, mostly chess move and evaluation utilities */ // static data that can be re-used (assume single threading!) static private float [] computerControl = new float[120]; static private float [] humanControl = new float[120]; private void setControlData(ChessPosition pos) { for (int i=0; i<120; i++) { computerControl[i] = 0; humanControl[i] = 0; } int [] b = pos.board; float [] control; // set to computerControl or humanControl, as appropriate for (int square_index=22; square_index<100; square_index++) { int piece = b[square_index]; if (piece == 7 || piece == 0) continue; int piece_type = piece; if (piece_type < 0) { piece_type = -piece_type; control = computerControl; } else { control = humanControl; } int count = 0, side_index, move_offset, temp, next_square; int piece_index = index[piece_type]; int move_index = pieceMovementTable[piece_index]; if (piece < 0) side_index = -1; else side_index = 1; switch (piece_type) { case ChessPosition.PAWN: { // first check for possible pawn captures: for (int delta=-1; delta<= 1; delta += 2) { move_offset = square_index + side_index * 10 + delta; control[move_offset] += 1.1f; int target = b[move_offset]; if ((target <= -1 && target != 7 && piece > 0) || (target >= 1 && target != 7 && piece < 0)) { // kluge: count pawn control more: control[square_index + side_index * delta] += 1.25f; } } } // Note: no break here: we want pawns to use move table also: break; // ?? case ChessPosition.KNIGHT: case ChessPosition.BISHOP: case ChessPosition.ROOK: case ChessPosition.KING: case ChessPosition.QUEEN: { move_index = piece; if (move_index < 0) move_index = -move_index; move_index = index[move_index]; //System.out.println("move_index="+move_index); next_square = square_index + pieceMovementTable[move_index]; outer: while (true) { inner: while (true) { if (next_square > 99) break inner; if (next_square < 22) break inner; if (b[next_square] == 7) break inner; control[next_square] += 1; // the next statement should be augmented for x-ray analysis: if (side_index < 0 && b[next_square] < 0) break inner; if (side_index > 0 && b[next_square] > 0 && b[next_square] != 7) break inner; // NOTE: prevents calculating guarding: //if (b[next_square] != 0) break inner; if (piece_type == ChessPosition.PAWN && (square_index / 10 == 3)) break inner; if (piece_type == ChessPosition.KNIGHT) break inner; if (piece_type == ChessPosition.KING) break inner; next_square += pieceMovementTable[move_index]; } move_index += 1; if (pieceMovementTable[move_index] == 0) break outer; next_square = square_index + pieceMovementTable[move_index]; } } } } if (false) { printPosition(pos); System.out.println("Human control:"); for (int col = 92; col >= 22; col -= 10) { System.out.println(); for (int ii = 0; ii < 8; ii++) { int i = ii + col; //for (int i=99; i>=22; i--) { //if (b[i] == 7 && b[i + 1] == 7) { // System.out.println(); // } if (b[i] != 7) { System.out.print(humanControl[i] + " "); } } } System.out.println(); System.out.println("Computer control:"); for (int col = 92; col >= 22; col -= 10) { System.out.println(); for (int ii = 0; ii < 8; ii++) { int i = ii + col; //if (b[i] == 7 && b[i + 1] == 7) { // System.out.println(); //} if (b[i] != 7) { System.out.print(computerControl[i] + " "); } } } System.out.println(); System.exit(1); } } static class aMove { int from; int to; } private static aMove [] possibleMoveList = new aMove[255]; static { for (int i=0; i<255; i++) possibleMoveList[i] = new aMove(); } private int calcPossibleMoves(ChessPosition pos, boolean player) { //System.out.println("calcPossibleMoves()"); int [] b = pos.board; int count = 0; for (int i=22; i<100; i++) { int board_val = b[i]; //System.out.println(board_val); if (board_val == 7) continue; // computer pieces will be negative: if ((board_val < 0 && !player) || (board_val > 0 && player)) { int num = calcPieceMoves(pos, i); for (int j=0; j<num; j++) { if (b[piece_moves[j]] != 7) { //System.out.println("count="+count+", i="+i); possibleMoveList[count].from = i; possibleMoveList[count].to = piece_moves[j]; // System.out.println("possible move: player="+player+ // ", from="+i+", to="+piece_moves[j]); count++; } } // TBD: castle logic, etc. (page 159) } } return count; } private int calcPieceMoves(ChessPosition pos, int square_index) { int [] b = pos.board; int piece = b[square_index]; int piece_type = piece; if (piece_type < 0) piece_type = -piece_type; int count = 0, side_index, move_offset, temp, next_square; int piece_index = index[piece_type]; int move_index = pieceMovementTable[piece_index]; if (piece < 0) side_index = -1; else side_index = 1; switch (piece_type) { case ChessPosition.PAWN: { // first check for possible pawn captures: for (int delta=-1; delta<= 1; delta += 2) { move_offset = square_index + side_index * 10 + delta; int target = b[move_offset]; if ((target <= -1 && target != 7 && piece > 0) || (target >= 1 && target != 7 && piece < 0)) { piece_moves[count++] = square_index + side_index * 10 + delta; } } // check for initial pawn move of 2 squares forward: move_offset = square_index + side_index * 20; if (piece > 0) temp = 3; else temp = 8; if (b[move_offset] == 0 && (square_index / 10) == temp && ((piece < 0 && b[square_index - 10]==0) || (piece > 0 && b[square_index + 10]==0))) { piece_moves[count++] = square_index + side_index * 20; } // try to move forward 1 square: move_offset = square_index + side_index * 10; if (b[move_offset] == 0) { piece_moves[count++] = move_offset; } } break; case ChessPosition.KNIGHT: case ChessPosition.BISHOP: case ChessPosition.ROOK: case ChessPosition.KING: case ChessPosition.QUEEN: { move_index = piece; if (move_index < 0) move_index = -move_index; move_index = index[move_index]; //System.out.println("move_index="+move_index); next_square = square_index + pieceMovementTable[move_index]; outer: while (true) { inner: while (true) { if (next_square > 99) break inner; if (next_square < 22) break inner; if (b[next_square] == 7) break inner; // check for piece on the same side: if (side_index < 0 && b[next_square] < 0) break inner; if (side_index >0 && b[next_square] > 0) break inner; piece_moves[count++] = next_square; if (b[next_square] != 0) break inner; if (piece_type == ChessPosition.KNIGHT) break inner; if (piece_type == ChessPosition.KING) break inner; next_square += pieceMovementTable[move_index]; } move_index += 1; if (pieceMovementTable[move_index] == 0) break outer; next_square = square_index + pieceMovementTable[move_index]; } } } return count; } private static int [] piece_moves = new int[255]; private static int [] initialBoard = { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 4, 2, 3, 5, 9, 3, 2, 4, 7, 7, // white pieces 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, // white pawns 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, // 8 blank squares, 2 off board 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, // 8 blank squares, 2 off board 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, // 8 blank squares, 2 off board 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, // 8 blank squares, 2 off board -1,-1,-1,-1,-1,-1,-1,-1, 7, 7, // black pawns -4,-2,-3,-5,-9,-3,-2,-4, 7, 7, // black pieces 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }; private static int [] index = { 0, 12, 15, 10, 1, 6, 0, 0, 0, 6 }; private static int [] pieceMovementTable = { 0, -1, 1, 10, -10, 0, -1, 1, 10, -10, -9, -11, 9, 11, 0, 8, -8, 12, -12, 19, -19, 21, -21, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0 }; private static int [] value = { 0, 1, 3, 3, 5, 9, 0, 0, 0, 20 }; private static int [] blackSquares = { 22, 24, 26, 28, 33, 35, 37, 39, 42, 44, 46, 48, 53, 55, 57, 59, 62, 64, 66, 68, 73, 75, 77, 79, 82, 84, 86, 88, 93, 95, 97, 99 }; }