Moved DroidFish project to trunk/

This commit is contained in:
Peter Osterlund
2011-11-12 19:44:06 +00:00
parent 7a793cd44e
commit 252789e186
134 changed files with 29968 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
/** Settings controlling opening book usage */
public class BookOptions {
public String filename = "";
public int maxLength = 1000000;
public boolean preferMainLines = false;
public boolean tournamentMode = false;
public double random = 0; // Scale probabilities according to p^(exp(-random))
public BookOptions() { }
public BookOptions(BookOptions other) {
filename = other.filename;
maxLength = other.maxLength;
preferMainLines = other.preferMainLines;
tournamentMode = other.tournamentMode;
random = other.random;
}
@Override
public boolean equals(Object o) {
if ((o == null) || (o.getClass() != this.getClass()))
return false;
BookOptions other = (BookOptions)o;
return ((filename.equals(other.filename)) &&
(maxLength == other.maxLength) &&
(preferMainLines == other.preferMainLines) &&
(tournamentMode == other.tournamentMode) &&
(random == other.random));
}
@Override
public int hashCode() {
return 0;
}
}

View File

@@ -0,0 +1,665 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.UndoInfo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class ChessBoard extends View {
public Position pos;
public int selectedSquare;
public float cursorX, cursorY;
public boolean cursorVisible;
protected int x0, y0, sqSize;
int pieceXDelta, pieceYDelta; // top/left pixel draw position relative to square
public boolean flipped;
public boolean drawSquareLabels;
boolean oneTouchMoves;
List<Move> moveHints;
protected Paint darkPaint;
protected Paint brightPaint;
private Paint selectedSquarePaint;
private Paint cursorSquarePaint;
private Paint whitePiecePaint;
private Paint blackPiecePaint;
private Paint labelPaint;
private ArrayList<Paint> moveMarkPaint;
public ChessBoard(Context context, AttributeSet attrs) {
super(context, attrs);
pos = new Position();
selectedSquare = -1;
cursorX = cursorY = 0;
cursorVisible = false;
x0 = y0 = sqSize = 0;
pieceXDelta = pieceYDelta = -1;
flipped = false;
drawSquareLabels = false;
oneTouchMoves = false;
darkPaint = new Paint();
brightPaint = new Paint();
selectedSquarePaint = new Paint();
selectedSquarePaint.setStyle(Paint.Style.STROKE);
selectedSquarePaint.setAntiAlias(true);
cursorSquarePaint = new Paint();
cursorSquarePaint.setStyle(Paint.Style.STROKE);
cursorSquarePaint.setAntiAlias(true);
whitePiecePaint = new Paint();
whitePiecePaint.setAntiAlias(true);
blackPiecePaint = new Paint();
blackPiecePaint.setAntiAlias(true);
labelPaint = new Paint();
labelPaint.setAntiAlias(true);
moveMarkPaint = new ArrayList<Paint>();
for (int i = 0; i < 6; i++) {
Paint p = new Paint();
p.setStyle(Paint.Style.FILL);
p.setAntiAlias(true);
moveMarkPaint.add(p);
}
Typeface chessFont = Typeface.createFromAsset(getContext().getAssets(), "ChessCases.ttf");
whitePiecePaint.setTypeface(chessFont);
blackPiecePaint.setTypeface(chessFont);
setColors();
}
/** Must be called for new color theme to take effect. */
final void setColors() {
ColorTheme ct = ColorTheme.instance();
darkPaint.setColor(ct.getColor(ColorTheme.DARK_SQUARE));
brightPaint.setColor(ct.getColor(ColorTheme.BRIGHT_SQUARE));
selectedSquarePaint.setColor(ct.getColor(ColorTheme.SELECTED_SQUARE));
cursorSquarePaint.setColor(ct.getColor(ColorTheme.CURSOR_SQUARE));
whitePiecePaint.setColor(ct.getColor(ColorTheme.BRIGHT_PIECE));
blackPiecePaint.setColor(ct.getColor(ColorTheme.DARK_PIECE));
labelPaint.setColor(ct.getColor(ColorTheme.SQUARE_LABEL));
for (int i = 0; i < 6; i++)
moveMarkPaint.get(i).setColor(ct.getColor(ColorTheme.ARROW_0 + i));
invalidate();
}
private Handler handlerTimer = new Handler();
final class AnimInfo {
AnimInfo() { startTime = -1; }
boolean paused;
long posHash; // Position the animation is valid for
long startTime; // Time in milliseconds when animation was started
long stopTime; // Time in milliseconds when animation should stop
long now; // Current time in milliseconds
int piece1, from1, to1, hide1;
int piece2, from2, to2, hide2;
public final boolean updateState() {
now = System.currentTimeMillis();
return animActive();
}
private final boolean animActive() {
if (paused || (startTime < 0) || (now >= stopTime) || (posHash != pos.zobristHash()))
return false;
return true;
}
public final boolean squareHidden(int sq) {
if (!animActive())
return false;
return (sq == hide1) || (sq == hide2);
}
public final void draw(Canvas canvas) {
if (!animActive())
return;
double animState = (now - startTime) / (double)(stopTime - startTime);
drawAnimPiece(canvas, piece2, from2, to2, animState);
drawAnimPiece(canvas, piece1, from1, to1, animState);
long now2 = System.currentTimeMillis();
long delay = 20 - (now2 - now);
// System.out.printf("delay:%d\n", delay);
if (delay < 1) delay = 1;
handlerTimer.postDelayed(new Runnable() {
@Override
public void run() {
invalidate();
}
}, delay);
}
private void drawAnimPiece(Canvas canvas, int piece, int from, int to, double animState) {
if (piece == Piece.EMPTY)
return;
final int xCrd1 = getXCrd(Position.getX(from));
final int yCrd1 = getYCrd(Position.getY(from));
final int xCrd2 = getXCrd(Position.getX(to));
final int yCrd2 = getYCrd(Position.getY(to));
final int xCrd = xCrd1 + (int)Math.round((xCrd2 - xCrd1) * animState);
final int yCrd = yCrd1 + (int)Math.round((yCrd2 - yCrd1) * animState);
drawPiece(canvas, xCrd, yCrd, piece);
}
}
AnimInfo anim = new AnimInfo();
/**
* Set up move animation. The animation will start the next time setPosition is called.
* @param sourcePos The source position for the animation.
* @param move The move leading to the target position.
* @param forward True if forward direction, false for undo move.
*/
public void setAnimMove(Position sourcePos, Move move, boolean forward) {
anim.startTime = -1;
anim.paused = true; // Animation starts at next position update
if (forward) {
// The animation will be played when pos == targetPos
Position targetPos = new Position(sourcePos);
UndoInfo ui = new UndoInfo();
targetPos.makeMove(move, ui);
anim.posHash = targetPos.zobristHash();
} else {
anim.posHash = sourcePos.zobristHash();
}
int animTime; // Animation duration in milliseconds.
{
int dx = Position.getX(move.to) - Position.getX(move.from);
int dy = Position.getY(move.to) - Position.getY(move.from);
double dist = Math.sqrt(dx * dx + dy * dy);
double t = Math.sqrt(dist) * 100;
animTime = (int)Math.round(t);
}
if (animTime > 0) {
anim.startTime = System.currentTimeMillis();
anim.stopTime = anim.startTime + animTime;
anim.piece2 = Piece.EMPTY;
anim.from2 = -1;
anim.to2 = -1;
anim.hide2 = -1;
if (forward) {
int p = sourcePos.getPiece(move.from);
anim.piece1 = p;
anim.from1 = move.from;
anim.to1 = move.to;
anim.hide1 = anim.to1;
int p2 = sourcePos.getPiece(move.to);
if (p2 != Piece.EMPTY) { // capture
anim.piece2 = p2;
anim.from2 = move.to;
anim.to2 = move.to;
} else if ((p == Piece.WKING) || (p == Piece.BKING)) {
boolean wtm = Piece.isWhite(p);
if (move.to == move.from + 2) { // O-O
anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK;
anim.from2 = move.to + 1;
anim.to2 = move.to - 1;
anim.hide2 = anim.to2;
} else if (move.to == move.from - 2) { // O-O-O
anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK;
anim.from2 = move.to - 2;
anim.to2 = move.to + 1;
anim.hide2 = anim.to2;
}
}
} else {
int p = sourcePos.getPiece(move.from);
anim.piece1 = p;
if (move.promoteTo != Piece.EMPTY)
anim.piece1 = Piece.isWhite(anim.piece1) ? Piece.WPAWN : Piece.BPAWN;
anim.from1 = move.to;
anim.to1 = move.from;
anim.hide1 = anim.to1;
if ((p == Piece.WKING) || (p == Piece.BKING)) {
boolean wtm = Piece.isWhite(p);
if (move.to == move.from + 2) { // O-O
anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK;
anim.from2 = move.to - 1;
anim.to2 = move.to + 1;
anim.hide2 = anim.to2;
} else if (move.to == move.from - 2) { // O-O-O
anim.piece2 = wtm ? Piece.WROOK : Piece.BROOK;
anim.from2 = move.to + 1;
anim.to2 = move.to - 2;
anim.hide2 = anim.to2;
}
}
}
}
}
/**
* Set the board to a given state.
* @param pos
*/
final public void setPosition(Position pos) {
boolean doInvalidate = false;
if (anim.paused = true) {
anim.paused = false;
doInvalidate = true;
}
if (!this.pos.equals(pos)) {
this.pos = new Position(pos);
doInvalidate = true;
}
if (doInvalidate)
invalidate();
}
/**
* Set/clear the board flipped status.
* @param flipped
*/
final public void setFlipped(boolean flipped) {
if (this.flipped != flipped) {
this.flipped = flipped;
invalidate();
}
}
/**
* Set/clear the board flipped status.
* @param flipped
*/
final public void setDrawSquareLabels(boolean drawSquareLabels) {
if (this.drawSquareLabels != drawSquareLabels) {
this.drawSquareLabels = drawSquareLabels;
invalidate();
}
}
/**
* Set/clear the selected square.
* @param square The square to select, or -1 to clear selection.
*/
final public void setSelection(int square) {
if (square != selectedSquare) {
selectedSquare = square;
invalidate();
}
}
protected int getWidth(int sqSize) { return sqSize * 8 + 4; }
protected int getHeight(int sqSize) { return sqSize * 8 + 4; }
protected int getSqSizeW(int width) { return (width - 4) / 8; }
protected int getSqSizeH(int height) { return (height - 4) / 8; }
protected int getMaxHeightPercentage() { return 75; }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int sqSizeW = getSqSizeW(width);
int sqSizeH = getSqSizeH(height);
int sqSize = Math.min(sqSizeW, sqSizeH);
pieceXDelta = pieceYDelta = -1;
labelBounds = null;
if (height > width) {
int p = getMaxHeightPercentage();
height = Math.min(getHeight(sqSize), height * p / 100);
} else {
width = Math.min(getWidth(sqSize), width * 65 / 100);
}
setMeasuredDimension(width, height);
}
protected void computeOrigin(int width, int height) {
x0 = (width - sqSize * 8) / 2;
y0 = (height - sqSize * 8) / 2;
}
protected int getXFromSq(int sq) { return Position.getX(sq); }
protected int getYFromSq(int sq) { return Position.getY(sq); }
@Override
protected void onDraw(Canvas canvas) {
// long t0 = System.currentTimeMillis();
boolean animActive = anim.updateState();
final int width = getWidth();
final int height = getHeight();
sqSize = Math.min(getSqSizeW(width), getSqSizeH(height));
blackPiecePaint.setTextSize(sqSize);
whitePiecePaint.setTextSize(sqSize);
labelPaint.setTextSize(sqSize/4.0f);
computeOrigin(width, height);
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
final int xCrd = getXCrd(x);
final int yCrd = getYCrd(y);
Paint paint = Position.darkSquare(x, y) ? darkPaint : brightPaint;
canvas.drawRect(xCrd, yCrd, xCrd+sqSize, yCrd+sqSize, paint);
int sq = Position.getSquare(x, y);
if (!animActive || !anim.squareHidden(sq)) {
int p = pos.getPiece(sq);
drawPiece(canvas, xCrd, yCrd, p);
}
if (drawSquareLabels) {
if (x == (flipped ? 7 : 0)) {
drawLabel(canvas, xCrd, yCrd, false, false, "12345678".charAt(y));
}
if (y == (flipped ? 7 : 0)) {
drawLabel(canvas, xCrd, yCrd, true, true, "abcdefgh".charAt(x));
}
}
}
}
drawExtraSquares(canvas);
if (!animActive && (selectedSquare != -1)) {
int selX = getXFromSq(selectedSquare);
int selY = getYFromSq(selectedSquare);
selectedSquarePaint.setStrokeWidth(sqSize/(float)16);
int x0 = getXCrd(selX);
int y0 = getYCrd(selY);
canvas.drawRect(x0, y0, x0 + sqSize, y0 + sqSize, selectedSquarePaint);
}
if (cursorVisible) {
int x = Math.round(cursorX);
int y = Math.round(cursorY);
int x0 = getXCrd(x);
int y0 = getYCrd(y);
cursorSquarePaint.setStrokeWidth(sqSize/(float)16);
canvas.drawRect(x0, y0, x0 + sqSize, y0 + sqSize, cursorSquarePaint);
}
if (!animActive)
drawMoveHints(canvas);
anim.draw(canvas);
// long t1 = System.currentTimeMillis();
// System.out.printf("draw: %d\n", t1-t0);
}
private final void drawMoveHints(Canvas canvas) {
if (moveHints == null)
return;
float h = (float)(sqSize / 2.0);
float d = (float)(sqSize / 8.0);
double v = 35 * Math.PI / 180;
double cosv = Math.cos(v);
double sinv = Math.sin(v);
double tanv = Math.tan(v);
int n = Math.min(moveMarkPaint.size(), moveHints.size());
for (int i = 0; i < n; i++) {
Move m = moveHints.get(i);
if (m.from == m.to)
continue;
float x0 = getXCrd(Position.getX(m.from)) + h;
float y0 = getYCrd(Position.getY(m.from)) + h;
float x1 = getXCrd(Position.getX(m.to)) + h;
float y1 = getYCrd(Position.getY(m.to)) + h;
float x2 = (float)(Math.hypot(x1 - x0, y1 - y0) + d);
float y2 = 0;
float x3 = (float)(x2 - h * cosv);
float y3 = (float)(y2 - h * sinv);
float x4 = (float)(x3 - d * sinv);
float y4 = (float)(y3 + d * cosv);
float x5 = (float)(x4 + (-d/2 - y4) / tanv);
float y5 = (float)(-d / 2);
float x6 = 0;
float y6 = y5 / 2;
Path path = new Path();
path.moveTo(x2, y2);
path.lineTo(x3, y3);
// path.lineTo(x4, y4);
path.lineTo(x5, y5);
path.lineTo(x6, y6);
path.lineTo(x6, -y6);
path.lineTo(x5, -y5);
// path.lineTo(x4, -y4);
path.lineTo(x3, -y3);
path.close();
Matrix mtx = new Matrix();
mtx.postRotate((float)(Math.atan2(y1 - y0, x1 - x0) * 180 / Math.PI));
mtx.postTranslate(x0, y0);
path.transform(mtx);
Paint p = moveMarkPaint.get(i);
canvas.drawPath(path, p);
}
}
protected void drawExtraSquares(Canvas canvas) {
}
protected final void drawPiece(Canvas canvas, int xCrd, int yCrd, int p) {
String psb, psw;
boolean rotate = false;
switch (p) {
default:
case Piece.EMPTY: psb = null; psw = null; break;
case Piece.WKING: psb = "H"; psw = "k"; break;
case Piece.WQUEEN: psb = "I"; psw = "l"; break;
case Piece.WROOK: psb = "J"; psw = "m"; break;
case Piece.WBISHOP: psb = "K"; psw = "n"; break;
case Piece.WKNIGHT: psb = "L"; psw = "o"; break;
case Piece.WPAWN: psb = "M"; psw = "p"; break;
case Piece.BKING: psb = "N"; psw = "q"; rotate = true; break;
case Piece.BQUEEN: psb = "O"; psw = "r"; rotate = true; break;
case Piece.BROOK: psb = "P"; psw = "s"; rotate = true; break;
case Piece.BBISHOP: psb = "Q"; psw = "t"; rotate = true; break;
case Piece.BKNIGHT: psb = "R"; psw = "u"; rotate = true; break;
case Piece.BPAWN: psb = "S"; psw = "v"; rotate = true; break;
}
if (psb != null) {
if (pieceXDelta < 0) {
Rect bounds = new Rect();
blackPiecePaint.getTextBounds("H", 0, 1, bounds);
pieceXDelta = (sqSize - (bounds.left + bounds.right)) / 2;
pieceYDelta = (sqSize - (bounds.top + bounds.bottom)) / 2;
}
rotate ^= flipped;
rotate = false; // Disabled for now
if (rotate) {
canvas.save();
canvas.rotate(180, xCrd + sqSize * 0.5f, yCrd + sqSize * 0.5f);
}
xCrd += pieceXDelta;
yCrd += pieceYDelta;
canvas.drawText(psw, xCrd, yCrd, whitePiecePaint);
canvas.drawText(psb, xCrd, yCrd, blackPiecePaint);
if (rotate)
canvas.restore();
}
}
private Rect labelBounds = null;
private void drawLabel(Canvas canvas, int xCrd, int yCrd, boolean right,
boolean bottom, char c) {
String s = "";
s += c;
if (labelBounds == null) {
labelBounds = new Rect();
labelPaint.getTextBounds("f", 0, 1, labelBounds);
}
int margin = sqSize / 16;
if (right) {
xCrd += sqSize - labelBounds.right - margin;
} else {
xCrd += -labelBounds.left + margin;
}
if (bottom) {
yCrd += sqSize - labelBounds.bottom - margin;
} else {
yCrd += -labelBounds.top + margin;
}
canvas.drawText(s, xCrd, yCrd, labelPaint);
}
protected int getXCrd(int x) { return x0 + sqSize * (flipped ? 7 - x : x); }
protected int getYCrd(int y) { return y0 + sqSize * (flipped ? y : 7 - y); }
protected int getXSq(int xCrd) { int t = (xCrd - x0) / sqSize; return flipped ? 7 - t : t; }
protected int getYSq(int yCrd) { int t = (yCrd - y0) / sqSize; return flipped ? t : 7 - t; }
/**
* Compute the square corresponding to the coordinates of a mouse event.
* @param evt Details about the mouse event.
* @return The square corresponding to the mouse event, or -1 if outside board.
*/
public int eventToSquare(MotionEvent evt) {
int xCrd = (int)(evt.getX());
int yCrd = (int)(evt.getY());
int sq = -1;
if (sqSize > 0) {
int x = getXSq(xCrd);
int y = getYSq(yCrd);
if ((x >= 0) && (x < 8) && (y >= 0) && (y < 8)) {
sq = Position.getSquare(x, y);
}
}
return sq;
}
final private boolean myColor(int piece) {
return (piece != Piece.EMPTY) && (Piece.isWhite(piece) == pos.whiteMove);
}
public Move mousePressed(int sq) {
if (sq < 0)
return null;
cursorVisible = false;
if (selectedSquare != -1) {
int p = pos.getPiece(selectedSquare);
if (!myColor(p)) {
setSelection(-1); // Remove selection of opponents last moving piece
}
}
int p = pos.getPiece(sq);
if (selectedSquare != -1) {
if (sq != selectedSquare) {
if (!myColor(p)) {
Move m = new Move(selectedSquare, sq, Piece.EMPTY);
setSelection(sq);
return m;
}
}
setSelection(-1);
} else {
if (oneTouchMoves) {
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(pos);
moves = MoveGen.removeIllegal(pos, moves);
Move matchingMove = null;
int toSq = -1;
for (Move m : moves) {
if ((m.from == sq) || (m.to == sq)) {
if (matchingMove == null) {
matchingMove = m;
toSq = m.to;
} else {
matchingMove = null;
break;
}
}
}
if (matchingMove != null) {
setSelection(toSq);
return matchingMove;
}
}
if (myColor(p)) {
setSelection(sq);
}
}
return null;
}
public static class OnTrackballListener {
public void onTrackballEvent(MotionEvent event) { }
}
private OnTrackballListener otbl = null;
public final void setOnTrackballListener(OnTrackballListener onTrackballListener) {
otbl = onTrackballListener;
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (otbl != null) {
otbl.onTrackballEvent(event);
return true;
}
return false;
}
protected int minValidY() { return 0; }
protected int getSquare(int x, int y) { return Position.getSquare(x, y); }
public final Move handleTrackballEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
invalidate();
if (cursorVisible) {
int x = Math.round(cursorX);
int y = Math.round(cursorY);
cursorX = x;
cursorY = y;
int sq = getSquare(x, y);
return mousePressed(sq);
}
return null;
}
cursorVisible = true;
int c = flipped ? -1 : 1;
cursorX += c * event.getX();
cursorY -= c * event.getY();
if (cursorX < 0) cursorX = 0;
if (cursorX > 7) cursorX = 7;
if (cursorY < minValidY()) cursorY = minValidY();
if (cursorY > 7) cursorY = 7;
invalidate();
return null;
}
public final void setMoveHints(List<Move> moveHints) {
boolean equal = false;
if ((this.moveHints == null) || (moveHints == null)) {
equal = this.moveHints == moveHints;
} else {
equal = this.moveHints.equals(moveHints);
}
if (!equal) {
this.moveHints = moveHints;
invalidate();
}
}
}

View File

@@ -0,0 +1,105 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
public class ColorTheme {
private static ColorTheme inst = null;
/** Get singleton instance. */
static final ColorTheme instance() {
if (inst == null)
inst = new ColorTheme();
return inst;
}
final static int DARK_SQUARE = 0;
final static int BRIGHT_SQUARE = 1;
final static int SELECTED_SQUARE = 2;
final static int CURSOR_SQUARE = 3;
final static int DARK_PIECE = 4;
final static int BRIGHT_PIECE = 5;
final static int CURRENT_MOVE = 6;
final static int ARROW_0 = 7;
final static int ARROW_1 = 8;
final static int ARROW_2 = 9;
final static int ARROW_3 = 10;
final static int ARROW_4 = 11;
final static int ARROW_5 = 12;
final static int SQUARE_LABEL = 13;
private final static int numColors = 14;
private int colorTable[] = new int[numColors];
private static final String[] prefNames = {
"darkSquare", "brightSquare", "selectedSquare", "cursorSquare", "darkPiece", "brightPiece", "currentMove",
"arrow0", "arrow1", "arrow2", "arrow3", "arrow4", "arrow5", "squareLabel"
};
private static final String prefPrefix = "color_";
private final static int defaultTheme = 2;
final static String[] themeNames = { "Original", "XBoard", "Blue", "Grey" };
private final static String themeColors[][] = {
{
"#FF808080", "#FFBEBE5A", "#FFFF0000", "#FF00FF00", "#FF000000", "#FFFFFFFF", "#FF888888",
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000"
},
{
"#FF77A26D", "#FFC8C365", "#FFFFFF00", "#FF00FF00", "#FF202020", "#FFFFFFCC", "#FF6B9262",
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000"
},
{
"#FF83A5D2", "#FFFFFFFA", "#FF3232D1", "#FF5F5FFD", "#FF282828", "#FFF0F0F0", "#FF3333FF",
"#A01F1FFF", "#A01FFF1F", "#501F1FFF", "#501FFF1F", "#1E1F1FFF", "#281FFF1F", "#FFFF0000"
},
{
"#FF666666", "#FFDDDDDD", "#FFFF0000", "#FF0000FF", "#FF000000", "#FFFFFFFF", "#FF888888",
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000"
}
};
final void readColors(SharedPreferences settings) {
for (int i = 0; i < numColors; i++) {
String prefName = prefPrefix + prefNames[i];
String defaultColor = themeColors[defaultTheme][i];
String colorString = settings.getString(prefName, defaultColor);
colorTable[i] = 0;
try {
colorTable[i] = Color.parseColor(colorString);
} catch (IllegalArgumentException e) {
} catch (StringIndexOutOfBoundsException e) {
}
}
}
final void setTheme(SharedPreferences settings, int themeType) {
Editor editor = settings.edit();
for (int i = 0; i < numColors; i++)
editor.putString(prefPrefix + prefNames[i], themeColors[themeType][i]);
editor.commit();
readColors(settings);
}
final int getColor(int colorType) {
return colorTable[colorType];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Position;
/** Interface between the GUI and the ChessController. */
public interface GUIInterface {
/** Update the displayed board position. */
public void setPosition(Position pos, String variantInfo, List<Move> variantMoves);
/** Mark square i as selected. Set to -1 to clear selection. */
public void setSelection(int sq);
/** Set the status text. */
public void setStatusString(String str);
/** Update the list of moves. */
public void moveListUpdated();
/** Update the computer thinking information. */
public void setThinkingInfo(String pvStr, String bookInfo, ArrayList<ArrayList<Move>> pvMoves, List<Move> bookMoves);
/** Ask what to promote a pawn to. Should call reportPromotePiece() when done. */
public void requestPromotePiece();
/** Run code on the GUI thread. */
public void runOnUIThread(Runnable runnable);
/** Report that user attempted to make an invalid move. */
public void reportInvalidMove(Move m);
/** Called when computer made a move. GUI can notify user, for example by playing a sound. */
public void computerMoveMade();
/** Report remaining thinking time to GUI. */
public void setRemainingTime(long wTime, long bTime, long nextUpdate);
/** Report a move made that is a candidate for GUI animation. */
public void setAnimMove(Position sourcePos, Move move, boolean forward);
/** Return true if positive analysis scores means good for white. */
public boolean whiteBasedScores();
/** Return true if pondering (permanent brain) is enabled. */
public boolean ponderMode();
/** Return the number of engine threads to use. */
int engineThreads();
}

View File

@@ -0,0 +1,111 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
public class GameMode {
private final boolean playerWhite;
private final boolean playerBlack;
private final boolean analysisMode;
private final boolean clocksActive;
public static final int PLAYER_WHITE = 1;
public static final int PLAYER_BLACK = 2;
public static final int TWO_PLAYERS = 3;
public static final int ANALYSIS = 4;
public static final int TWO_COMPUTERS = 5;
public static final int EDIT_GAME = 6;
public GameMode(int modeNr) {
switch (modeNr) {
case PLAYER_WHITE: default:
playerWhite = true;
playerBlack = false;
analysisMode = false;
clocksActive = true;
break;
case PLAYER_BLACK:
playerWhite = false;
playerBlack = true;
analysisMode = false;
clocksActive = true;
break;
case TWO_PLAYERS:
playerWhite = true;
playerBlack = true;
analysisMode = false;
clocksActive = true;
break;
case ANALYSIS:
playerWhite = true;
playerBlack = true;
analysisMode = true;
clocksActive = false;
break;
case TWO_COMPUTERS:
playerWhite = false;
playerBlack = false;
analysisMode = false;
clocksActive = true;
break;
case EDIT_GAME:
playerWhite = true;
playerBlack = true;
analysisMode = false;
clocksActive = false;
break;
}
}
public final boolean playerWhite() {
return playerWhite;
}
public final boolean playerBlack() {
return playerBlack;
}
public final boolean analysisMode() {
return analysisMode;
}
public final boolean humansTurn(boolean whiteMove) {
return (whiteMove ? playerWhite : playerBlack) || analysisMode;
}
public final boolean clocksActive() {
return clocksActive;
}
@Override
public boolean equals(Object o) {
if ((o == null) || (o.getClass() != this.getClass()))
return false;
GameMode other = (GameMode)o;
if (playerWhite != other.playerWhite)
return false;
if (playerBlack != other.playerBlack)
return false;
if (analysisMode != other.analysisMode)
return false;
if (clocksActive != other.clocksActive)
return false;
return true;
}
@Override
public int hashCode() {
return 0;
}
}

View File

@@ -0,0 +1,27 @@
package org.petero.droidfish;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/** A ScrollView that uses at most 75% of the parent height. */
public class MyScrollView extends ScrollView {
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (getParent() instanceof View) {
int parentHeight = ((View)getParent()).getHeight();
if (parentHeight > 0)
height = Math.min(height, parentHeight * 3 / 4);
}
setMeasuredDimension(width, height);
}
}

View File

@@ -0,0 +1,54 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish;
/** Settings controlling PGN import/export */
public class PGNOptions {
public static class Viewer {
public boolean variations;
public boolean comments;
public boolean nag;
public boolean headers;
}
public static class Import {
public boolean variations;
public boolean comments;
public boolean nag;
}
public static class Export {
public boolean variations;
public boolean comments;
public boolean nag;
public boolean playerAction;
public boolean clockInfo;
public boolean pgnPromotions;
public boolean moveNrAfterNag;
}
public Viewer view;
public Import imp;
public Export exp;
public PGNOptions() {
view = new Viewer();
imp = new Import();
exp = new Export();
exp.moveNrAfterNag = true;
}
}

View File

@@ -0,0 +1,183 @@
package org.petero.droidfish;
import android.app.Dialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.SeekBar.OnSeekBarChangeListener;
/** Lets user enter a percentage value using a seek bar. */
public class SeekBarPreference extends Preference
implements OnSeekBarChangeListener {
private final static int maxValue = 1000;
private final static int DEFAULT_VALUE = 1000;
private int currVal = DEFAULT_VALUE;
private TextView currValBox;
public SeekBarPreference(Context context) {
super(context);
}
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected View onCreateView(ViewGroup parent) {
TextView name = new TextView(getContext());
name.setText(getTitle());
name.setTextSize(20);
name.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);
name.setGravity(Gravity.LEFT);
LinearLayout.LayoutParams lp =
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.LEFT;
lp.weight = 1.0f;
name.setLayoutParams(lp);
currValBox = new TextView(getContext());
currValBox.setTextSize(12);
currValBox.setTypeface(Typeface.MONOSPACE, Typeface.ITALIC);
currValBox.setPadding(2, 5, 0, 0);
currValBox.setText(valToString());
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER;
currValBox.setLayoutParams(lp);
LinearLayout row1 = new LinearLayout(getContext());
row1.setOrientation(LinearLayout.HORIZONTAL);
row1.addView(name);
row1.addView(currValBox);
final SeekBar bar = new SeekBar(getContext());
bar.setMax(maxValue);
bar.setProgress(currVal);
bar.setOnSeekBarChangeListener(this);
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.RIGHT;
bar.setLayoutParams(lp);
LinearLayout layout = new LinearLayout(getContext());
layout.setPadding(25, 5, 25, 5);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(row1);
layout.addView(bar);
layout.setId(android.R.id.widget_frame);
currValBox.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final Dialog dialog = new Dialog(getContext());
dialog.setContentView(R.layout.select_percentage);
String title = "";
String key = getKey();
if (key.equals("strength")) {
title = getContext().getString(R.string.edit_strength);
} else if (key.equals("bookRandom")) {
title = getContext().getString(R.string.edit_randomization);
}
dialog.setTitle(title);
final EditText valueView = (EditText)dialog.findViewById(R.id.selpercentage_number);
Button ok = (Button)dialog.findViewById(R.id.selpercentage_ok);
Button cancel = (Button)dialog.findViewById(R.id.selpercentage_cancel);
valueView.setText(currValBox.getText().toString().replaceAll("%", ""));
final Runnable selectValue = new Runnable() {
public void run() {
try {
String txt = valueView.getText().toString();
int value = (int)(Double.parseDouble(txt) * 10 + 0.5);
if (value < 0) value = 0;
if (value > maxValue) value = maxValue;
dialog.cancel();
onProgressChanged(bar, value, false);
} catch (NumberFormatException nfe) {
}
}
};
valueView.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
selectValue.run();
return true;
}
return false;
}
});
ok.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
selectValue.run();
}
});
cancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
dialog.cancel();
}
});
dialog.show();
}
});
return layout;
}
private final String valToString() {
return String.format("%.1f%%", currVal*0.1);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
if (!callChangeListener(progress)) {
seekBar.setProgress(currVal);
return;
}
seekBar.setProgress(progress);
currVal = progress;
currValBox.setText(valToString());
SharedPreferences.Editor editor = getEditor();
editor.putInt(getKey(), progress);
editor.commit();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
notifyChanged();
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
int defVal = a.getInt(index, DEFAULT_VALUE);
if (defVal > maxValue) defVal = maxValue;
if (defVal < 0) defVal = 0;
return defVal;
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
int val = restorePersistedValue ? getPersistedInt(DEFAULT_VALUE) : (Integer)defaultValue;
if (!restorePersistedValue)
persistInt(val);
currVal = val;
}
}

View File

@@ -0,0 +1,56 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import org.petero.droidfish.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
public class CPUWarning extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.cpu_warning);
showDialog(CPU_WARNING_DIALOG);
}
static final int CPU_WARNING_DIALOG = 1;
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case CPU_WARNING_DIALOG:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.app_name).setMessage(R.string.cpu_warning);
AlertDialog alert = builder.create();
alert.setOnDismissListener(new OnDismissListener() {
public void onDismiss(DialogInterface dialog) {
finish();
}
});
return alert;
}
return null;
}
}

View File

@@ -0,0 +1,210 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import org.petero.droidfish.ChessBoard;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* Chess board widget suitable for edit mode.
* @author petero
*/
public class ChessBoardEdit extends ChessBoard {
public ChessBoardEdit(Context context, AttributeSet attrs) {
super(context, attrs);
drawSquareLabels = true;
}
private final static int gap = 2;
@Override
protected int getWidth(int sqSize) { return sqSize * 8 + 4; }
@Override
protected int getHeight(int sqSize) { return sqSize * 10 + 4 + gap; }
@Override
protected int getSqSizeW(int width) { return (width - 4) / 8; }
@Override
protected int getSqSizeH(int height) { return (height - 4 - gap) / 10; }
@Override
protected int getMaxHeightPercentage() { return 85; }
@Override
protected void computeOrigin(int width, int height) {
x0 = (width - sqSize * 8) / 2;
y0 = (height - (sqSize * 10 + gap)) / 2;
}
int extraPieces(int x, int y) {
if (y == -1) { // White pieces
switch (x) {
case 0: return Piece.WKING;
case 1: return Piece.WQUEEN;
case 2: return Piece.WROOK;
case 3: return Piece.WBISHOP;
case 4: return Piece.WKNIGHT;
case 5: return Piece.WPAWN;
}
} else if (y == -2) {
switch (x) {
case 0: return Piece.BKING;
case 1: return Piece.BQUEEN;
case 2: return Piece.BROOK;
case 3: return Piece.BBISHOP;
case 4: return Piece.BKNIGHT;
case 5: return Piece.BPAWN;
}
}
return Piece.EMPTY;
}
@Override
protected int getXFromSq(int sq) {
if (sq >= 0) {
return Position.getX(sq);
} else {
int p = -2 - sq;
switch (p) {
case Piece.WKING: case Piece.BKING: return 0;
case Piece.WQUEEN: case Piece.BQUEEN: return 1;
case Piece.WROOK: case Piece.BROOK: return 2;
case Piece.WBISHOP: case Piece.BBISHOP: return 3;
case Piece.WKNIGHT: case Piece.BKNIGHT: return 4;
case Piece.WPAWN: case Piece.BPAWN: return 5;
default: return 6;
}
}
}
@Override
protected int getYFromSq(int sq) {
if (sq >= 0) {
return Position.getY(sq);
} else {
int p = -2 - sq;
return Piece.isWhite(p) ? -1 : -2;
}
}
@Override
protected int getSquare(int x, int y) {
if (y >= 0) {
return Position.getSquare(x, y);
} else {
int p = extraPieces(x, y);
return -p - 2;
}
}
@Override
protected void drawExtraSquares(Canvas canvas) {
for (int x = 0; x < 8; x++) {
for (int y = -2; y < 0; y++) {
final int xCrd = getXCrd(x);
final int yCrd = getYCrd(y);
Paint paint = Position.darkSquare(x, y) ? darkPaint : brightPaint;
canvas.drawRect(xCrd, yCrd, xCrd+sqSize, yCrd+sqSize, paint);
int p = extraPieces(x, y);
drawPiece(canvas, xCrd, yCrd, p);
}
}
}
@Override
public
Move mousePressed(int sq) {
if (sq == -1)
return null;
cursorVisible = false;
if (selectedSquare != -1) {
if (sq != selectedSquare) {
Move m = new Move(selectedSquare, sq, Piece.EMPTY);
setSelection(sq);
return m;
}
setSelection(-1);
} else {
setSelection(sq);
}
return null;
}
@Override
protected int minValidY() { return -2; }
@Override
protected int getXCrd(int x) {
return x0 + sqSize * (flipped ? 7 - x : x);
}
@Override
protected int getYCrd(int y) {
if (y >= 0) {
return y0 + sqSize * (flipped ? y : 7 - y);
} else {
return y0 + gap + sqSize * (7 - y);
}
}
@Override
protected int getXSq(int xCrd) {
int t = (xCrd - x0) / sqSize; return flipped ? 7 - t : t;
}
@Override
protected int getYSq(int yCrd) {
int t = (yCrd - y0) / sqSize;
t = flipped ? t : 7 - t;
if ((t >= 0) && (t < 8))
return t;
return 7 - (yCrd - y0 - gap) / sqSize;
}
/**
* Compute the square corresponding to the coordinates of a mouse event.
* @param evt Details about the mouse event.
* @return The square corresponding to the mouse event, or -1 if outside board.
*/
@Override
public int eventToSquare(MotionEvent evt) {
int sq = super.eventToSquare(evt);
if (sq != -1)
return sq;
int xCrd = (int)(evt.getX());
int yCrd = (int)(evt.getY());
if (sqSize > 0) {
int x = getXSq(xCrd);
int y = getYSq(yCrd);
if ((x >= 0) && (x < 8) && (y >= -2) && (y < 0)) {
int p = extraPieces(x, y);
sq = -p - 2;
}
}
return sq;
}
}

View File

@@ -0,0 +1,427 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import org.petero.droidfish.ChessBoard;
import org.petero.droidfish.R;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.text.ClipboardManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class EditBoard extends Activity {
private ChessBoardEdit cb;
private TextView status;
private Button okButton;
private Button cancelButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initUI();
Intent i = getIntent();
Position pos;
try {
pos = TextIO.readFEN(i.getAction());
cb.setPosition(pos);
} catch (ChessParseError e) {
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ChessBoardEdit oldCB = cb;
String statusStr = status.getText().toString();
initUI();
cb.cursorX = oldCB.cursorX;
cb.cursorY = oldCB.cursorY;
cb.cursorVisible = oldCB.cursorVisible;
cb.setPosition(oldCB.pos);
cb.setSelection(oldCB.selectedSquare);
status.setText(statusStr);
}
private final void initUI() {
setContentView(R.layout.editboard);
cb = (ChessBoardEdit)findViewById(R.id.eb_chessboard);
status = (TextView)findViewById(R.id.eb_status);
okButton = (Button)findViewById(R.id.eb_ok);
cancelButton = (Button)findViewById(R.id.eb_cancel);
okButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
sendBackResult();
}
});
cancelButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
status.setFocusable(false);
cb.setFocusable(true);
cb.requestFocus();
cb.setClickable(true);
cb.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int sq = cb.eventToSquare(event);
Move m = cb.mousePressed(sq);
if (m != null) {
doMove(m);
}
return false;
}
return false;
}
});
cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() {
public void onTrackballEvent(MotionEvent event) {
Move m = cb.handleTrackballEvent(event);
if (m != null) {
doMove(m);
}
}
});
cb.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showDialog(EDIT_DIALOG);
return true;
}
});
}
private void doMove(Move m) {
if (m.to < 0) {
if ((m.from < 0) || (cb.pos.getPiece(m.from) == Piece.EMPTY)) {
cb.setSelection(m.to);
return;
}
}
Position pos = new Position(cb.pos);
int piece = Piece.EMPTY;
if (m.from >= 0) {
piece = pos.getPiece(m.from);
} else {
piece = -(m.from + 2);
}
if (m.to >= 0)
pos.setPiece(m.to, piece);
if (m.from >= 0)
pos.setPiece(m.from, Piece.EMPTY);
cb.setPosition(pos);
cb.setSelection(-1);
checkValid();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
sendBackResult();
return true;
}
return super.onKeyDown(keyCode, event);
}
private final void sendBackResult() {
if (checkValid()) {
setPosFields();
String fen = TextIO.toFEN(cb.pos);
setResult(RESULT_OK, (new Intent()).setAction(fen));
} else {
setResult(RESULT_CANCELED);
}
finish();
}
private final void setPosFields() {
setEPFile(getEPFile()); // To handle sideToMove change
TextIO.fixupEPSquare(cb.pos);
TextIO.removeBogusCastleFlags(cb.pos);
}
private final int getEPFile() {
int epSquare = cb.pos.getEpSquare();
if (epSquare < 0) return 8;
return Position.getX(epSquare);
}
private final void setEPFile(int epFile) {
int epSquare = -1;
if ((epFile >= 0) && (epFile < 8)) {
int epRank = cb.pos.whiteMove ? 5 : 2;
epSquare = Position.getSquare(epFile, epRank);
}
cb.pos.setEpSquare(epSquare);
}
/** Test if a position is valid. */
private final boolean checkValid() {
try {
String fen = TextIO.toFEN(cb.pos);
TextIO.readFEN(fen);
status.setText("");
return true;
} catch (ChessParseError e) {
status.setText(e.getMessage());
}
return false;
}
static final int EDIT_DIALOG = 0;
static final int SIDE_DIALOG = 1;
static final int CASTLE_DIALOG = 2;
static final int EP_DIALOG = 3;
static final int MOVCNT_DIALOG = 4;
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case EDIT_DIALOG: {
final CharSequence[] items = {
getString(R.string.side_to_move),
getString(R.string.clear_board), getString(R.string.initial_position),
getString(R.string.castling_flags), getString(R.string.en_passant_file),
getString(R.string.move_counters),
getString(R.string.copy_position), getString(R.string.paste_position)
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.edit_board);
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
switch (item) {
case 0: // Edit side to move
showDialog(SIDE_DIALOG);
cb.setSelection(-1);
checkValid();
break;
case 1: { // Clear board
Position pos = new Position();
cb.setPosition(pos);
cb.setSelection(-1);
checkValid();
break;
}
case 2: { // Set initial position
try {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
cb.setPosition(pos);
cb.setSelection(-1);
checkValid();
} catch (ChessParseError e) {
}
break;
}
case 3: // Edit castling flags
removeDialog(CASTLE_DIALOG);
showDialog(CASTLE_DIALOG);
cb.setSelection(-1);
checkValid();
break;
case 4: // Edit en passant file
removeDialog(EP_DIALOG);
showDialog(EP_DIALOG);
cb.setSelection(-1);
checkValid();
break;
case 5: // Edit move counters
removeDialog(MOVCNT_DIALOG);
showDialog(MOVCNT_DIALOG);
cb.setSelection(-1);
checkValid();
break;
case 6: { // Copy position
setPosFields();
String fen = TextIO.toFEN(cb.pos) + "\n";
ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(fen);
cb.setSelection(-1);
break;
}
case 7: { // Paste position
ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
if (clipboard.hasText()) {
String fen = clipboard.getText().toString();
try {
Position pos = TextIO.readFEN(fen);
cb.setPosition(pos);
} catch (ChessParseError e) {
if (e.pos != null)
cb.setPosition(e.pos);
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
}
cb.setSelection(-1);
checkValid();
}
break;
}
}
}
});
AlertDialog alert = builder.create();
return alert;
}
case SIDE_DIALOG: {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.select_side_to_move_first)
.setPositiveButton(R.string.white, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
cb.pos.setWhiteMove(true);
checkValid();
dialog.cancel();
}
})
.setNegativeButton(R.string.black, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
cb.pos.setWhiteMove(false);
checkValid();
dialog.cancel();
}
});
AlertDialog alert = builder.create();
return alert;
}
case CASTLE_DIALOG: {
final CharSequence[] items = {
getString(R.string.white_king_castle), getString(R.string.white_queen_castle),
getString(R.string.black_king_castle), getString(R.string.black_queen_castle)
};
boolean[] checkedItems = {
cb.pos.h1Castle(), cb.pos.a1Castle(),
cb.pos.h8Castle(), cb.pos.a8Castle()
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.castling_flags);
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
Position pos = new Position(cb.pos);
boolean a1Castle = pos.a1Castle();
boolean h1Castle = pos.h1Castle();
boolean a8Castle = pos.a8Castle();
boolean h8Castle = pos.h8Castle();
switch (which) {
case 0: h1Castle = isChecked; break;
case 1: a1Castle = isChecked; break;
case 2: h8Castle = isChecked; break;
case 3: a8Castle = isChecked; break;
}
int castleMask = 0;
if (a1Castle) castleMask |= 1 << Position.A1_CASTLE;
if (h1Castle) castleMask |= 1 << Position.H1_CASTLE;
if (a8Castle) castleMask |= 1 << Position.A8_CASTLE;
if (h8Castle) castleMask |= 1 << Position.H8_CASTLE;
pos.setCastleMask(castleMask);
cb.setPosition(pos);
checkValid();
}
});
AlertDialog alert = builder.create();
return alert;
}
case EP_DIALOG: {
final CharSequence[] items = {
"A", "B", "C", "D", "E", "F", "G", "H", getString(R.string.none)
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.select_en_passant_file);
builder.setSingleChoiceItems(items, getEPFile(), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
setEPFile(item);
}
});
AlertDialog alert = builder.create();
return alert;
}
case MOVCNT_DIALOG: {
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.edit_move_counters);
dialog.setTitle(R.string.edit_move_counters);
final EditText halfMoveClock = (EditText)dialog.findViewById(R.id.ed_cnt_halfmove);
final EditText fullMoveCounter = (EditText)dialog.findViewById(R.id.ed_cnt_fullmove);
Button ok = (Button)dialog.findViewById(R.id.ed_cnt_ok);
Button cancel = (Button)dialog.findViewById(R.id.ed_cnt_cancel);
halfMoveClock.setText(String.format("%d", cb.pos.halfMoveClock));
fullMoveCounter.setText(String.format("%d", cb.pos.fullMoveCounter));
final Runnable setCounters = new Runnable() {
public void run() {
try {
int halfClock = Integer.parseInt(halfMoveClock.getText().toString());
int fullCount = Integer.parseInt(fullMoveCounter.getText().toString());
cb.pos.halfMoveClock = halfClock;
cb.pos.fullMoveCounter = fullCount;
dialog.cancel();
} catch (NumberFormatException nfe) {
Toast.makeText(getApplicationContext(), R.string.invalid_number_format, Toast.LENGTH_SHORT).show();
}
}
};
fullMoveCounter.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
setCounters.run();
return true;
}
return false;
}
});
ok.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setCounters.run();
}
});
cancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
dialog.cancel();
}
});
return dialog;
}
}
return null;
}
}

View File

@@ -0,0 +1,93 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import org.petero.droidfish.R;
import org.petero.droidfish.gamelogic.GameTree.Node;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
/** Activity to edit PGN comments. */
public class EditComments extends Activity {
TextView preComment, postComment, nag;
private Button okButton;
private Button cancelButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_comments);
preComment = (TextView)findViewById(R.id.ed_comments_pre);
TextView moveView = (TextView)findViewById(R.id.ed_comments_move);
nag = (TextView)findViewById(R.id.ed_comments_nag);
postComment = (TextView)findViewById(R.id.ed_comments_post);
okButton = (Button)findViewById(R.id.ed_comments_ok);
cancelButton = (Button)findViewById(R.id.ed_comments_cancel);
postComment.requestFocus();
Intent data = getIntent();
Bundle bundle = data.getBundleExtra("org.petero.droidfish.comments");
String pre = bundle.getString("preComment");
String post = bundle.getString("postComment");
String move = bundle.getString("move");
int nagVal = bundle.getInt("nag");
preComment.setText(pre);
postComment.setText(post);
moveView.setText(move);
String nagStr = Node.nagStr(nagVal).trim();
if ((nagStr.length() == 0) && (nagVal > 0))
nagStr = String.format("%d", nagVal);
nag.setText(nagStr);
okButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
sendBackResult();
}
});
cancelButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
}
private final void sendBackResult() {
String pre = preComment.getText().toString().trim();
String post = postComment.getText().toString().trim();
int nagVal = Node.strToNag(nag.getText().toString());
Bundle bundle = new Bundle();
bundle.putString("preComment", pre);
bundle.putString("postComment", post);
bundle.putInt("nag", nagVal);
Intent data = new Intent();
data.putExtra("org.petero.droidfish.comments", bundle);
setResult(RESULT_OK, data);
finish();
}
}

View File

@@ -0,0 +1,121 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import java.util.ArrayList;
import java.util.TreeMap;
import org.petero.droidfish.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
/** Activity to edit PGN headers. */
public class EditHeaders extends Activity {
private TextView event;
private TextView site;
private TextView date;
private TextView round;
private TextView white;
private TextView black;
private Button okButton;
private Button cancelButton;
private TreeMap<String,String> headers = new TreeMap<String,String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_headers);
event = (TextView)findViewById(R.id.ed_header_event);
site = (TextView)findViewById(R.id.ed_header_site);
date = (TextView)findViewById(R.id.ed_header_date);
round = (TextView)findViewById(R.id.ed_header_round);
white = (TextView)findViewById(R.id.ed_header_white);
black = (TextView)findViewById(R.id.ed_header_black);
okButton = (Button)findViewById(R.id.ed_header_ok);
cancelButton = (Button)findViewById(R.id.ed_header_cancel);
Intent data = getIntent();
Bundle bundle = data.getBundleExtra("org.petero.droidfish.headers");
ArrayList<String> tags = bundle.getStringArrayList("tags");
ArrayList<String> tagValues = bundle.getStringArrayList("tagValues");
for (int i = 0; i < tags.size(); i++)
headers.put(tags.get(i), tagValues.get(i));
event.setText(headers.get("Event"));
site .setText(headers.get("Site"));
date .setText(headers.get("Date"));
round.setText(headers.get("Round"));
white.setText(headers.get("White"));
black.setText(headers.get("Black"));
okButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
sendBackResult();
}
});
cancelButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
sendBackResult();
return true;
}
return super.onKeyDown(keyCode, event);
}
private final void sendBackResult() {
headers.put("Event", event.getText().toString().trim());
headers.put("Site", site .getText().toString().trim());
headers.put("Date", date .getText().toString().trim());
headers.put("Round", round.getText().toString().trim());
headers.put("White", white.getText().toString().trim());
headers.put("Black", black.getText().toString().trim());
Bundle bundle = new Bundle();
ArrayList<String> tags = new ArrayList<String>();
ArrayList<String> values = new ArrayList<String>();
for (String k : headers.keySet()) {
tags.add(k);
values.add(headers.get(k));
}
bundle.putStringArrayList("tags", tags);
bundle.putStringArrayList("tagValues", values);
Intent data = new Intent();
data.putExtra("org.petero.droidfish.headers", bundle);
setResult(RESULT_OK, data);
finish();
}
}

View File

@@ -0,0 +1,433 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import java.io.File;
import java.util.ArrayList;
import org.petero.droidfish.R;
import org.petero.droidfish.activities.PGNFile.GameInfo;
import org.petero.droidfish.activities.PGNFile.GameInfoResult;
import org.petero.droidfish.gamelogic.Pair;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnCancelListener;
import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
public class EditPGN extends ListActivity {
static ArrayList<GameInfo> gamesInFile = new ArrayList<GameInfo>();
static boolean cacheValid = false;
PGNFile pgnFile;
ProgressDialog progress;
GameInfo selectedGi = null;
ArrayAdapter<GameInfo> aa = null;
EditText filterText = null;
SharedPreferences settings;
int defaultItem = 0;
String lastSearchString = "";
String lastFileName = "";
long lastModTime = -1;
Thread workThread = null;
boolean loadGame; // True when loading game, false when saving
String pgnToSave;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
settings = PreferenceManager.getDefaultSharedPreferences(this);
if (savedInstanceState != null) {
defaultItem = savedInstanceState.getInt("defaultItem");
lastSearchString = savedInstanceState.getString("lastSearchString");
if (lastSearchString == null) lastSearchString = "";
lastFileName = savedInstanceState.getString("lastFileName");
if (lastFileName == null) lastFileName = "";
lastModTime = savedInstanceState.getLong("lastModTime");
} else {
defaultItem = settings.getInt("defaultItem", 0);
lastSearchString = settings.getString("lastSearchString", "");
lastFileName = settings.getString("lastFileName", "");
lastModTime = settings.getLong("lastModTime", 0);
}
Intent i = getIntent();
String action = i.getAction();
String fileName = i.getStringExtra("org.petero.droidfish.pathname");
if (action.equals("org.petero.droidfish.loadFile")) {
pgnFile = new PGNFile(fileName);
loadGame = true;
showDialog(PROGRESS_DIALOG);
final EditPGN lpgn = this;
workThread = new Thread(new Runnable() {
public void run() {
if (!readFile())
return;
runOnUiThread(new Runnable() {
public void run() {
lpgn.showList();
}
});
}
});
workThread.start();
} else if (action.equals("org.petero.droidfish.loadFileNextGame") ||
action.equals("org.petero.droidfish.loadFilePrevGame")) {
pgnFile = new PGNFile(fileName);
loadGame = true;
boolean next = action.equals("org.petero.droidfish.loadFileNextGame");
final int loadItem = defaultItem + (next ? 1 : -1);
if (loadItem < 0) {
Toast.makeText(getApplicationContext(), R.string.no_prev_game,
Toast.LENGTH_SHORT).show();
setResult(RESULT_CANCELED);
finish();
} else {
workThread = new Thread(new Runnable() {
public void run() {
if (!readFile())
return;
runOnUiThread(new Runnable() {
public void run() {
if (loadItem >= gamesInFile.size()) {
Toast.makeText(getApplicationContext(), R.string.no_next_game,
Toast.LENGTH_SHORT).show();
setResult(RESULT_CANCELED);
finish();
} else {
defaultItem = loadItem;
sendBackResult(gamesInFile.get(loadItem));
}
}
});
}
});
workThread.start();
}
} else if (action.equals("org.petero.droidfish.saveFile")) {
loadGame = false;
pgnToSave = i.getStringExtra("org.petero.droidfish.pgn");
boolean silent = i.getBooleanExtra("org.petero.droidfish.silent", false);
if (silent) { // Silently append to file
PGNFile pgnFile2 = new PGNFile(fileName);
pgnFile2.appendPGN(pgnToSave, null);
} else {
pgnFile = new PGNFile(fileName);
showDialog(PROGRESS_DIALOG);
final EditPGN lpgn = this;
workThread = new Thread(new Runnable() {
public void run() {
if (!readFile())
return;
runOnUiThread(new Runnable() {
public void run() {
if (gamesInFile.size() == 0) {
pgnFile.appendPGN(pgnToSave, getApplicationContext());
finish();
} else {
lpgn.showList();
}
}
});
}
});
workThread.start();
}
} else { // Unsupported action
setResult(RESULT_CANCELED);
finish();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("defaultItem", defaultItem);
outState.putString("lastSearchString", lastSearchString);
outState.putString("lastFileName", lastFileName);
outState.putLong("lastModTime", lastModTime);
}
@Override
protected void onPause() {
Editor editor = settings.edit();
editor.putInt("defaultItem", defaultItem);
editor.putString("lastSearchString", lastSearchString);
editor.putString("lastFileName", lastFileName);
editor.putLong("lastModTime", lastModTime);
editor.commit();
super.onPause();
}
@Override
protected void onDestroy() {
if (workThread != null) {
workThread.interrupt();
try {
workThread.join();
} catch (InterruptedException e) {
}
workThread = null;
}
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.edit_file_options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.item_delete_file:
removeDialog(DELETE_PGN_FILE_DIALOG);
showDialog(DELETE_PGN_FILE_DIALOG);
break;
}
return false;
}
private final void showList() {
progress.dismiss();
setContentView(R.layout.select_game);
aa = new ArrayAdapter<GameInfo>(this, R.layout.select_game_list_item, gamesInFile);
setListAdapter(aa);
ListView lv = getListView();
lv.setSelectionFromTop(defaultItem, 0);
lv.setFastScrollEnabled(true);
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
selectedGi = aa.getItem(pos);
if (loadGame) {
defaultItem = pos;
sendBackResult(selectedGi);
} else {
removeDialog(SAVE_GAME_DIALOG);
showDialog(SAVE_GAME_DIALOG);
}
}
});
lv.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int pos, long id) {
selectedGi = aa.getItem(pos);
if (!selectedGi.isNull()) {
removeDialog(DELETE_GAME_DIALOG);
showDialog(DELETE_GAME_DIALOG);
}
return true;
}
});
// lv.setTextFilterEnabled(true);
filterText = (EditText)findViewById(R.id.select_game_filter);
filterText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) { }
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
aa.getFilter().filter(s);
lastSearchString = s.toString();
}
});
filterText.setText(lastSearchString);
lv.requestFocus();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
final static int PROGRESS_DIALOG = 0;
final static int DELETE_GAME_DIALOG = 1;
final static int SAVE_GAME_DIALOG = 2;
final static int DELETE_PGN_FILE_DIALOG = 3;
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progress = new ProgressDialog(this);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setTitle(R.string.reading_pgn_file);
progress.setMessage(getString(R.string.please_wait));
progress.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
Thread thr = workThread;
if (thr != null)
thr.interrupt();
}
});
return progress;
case DELETE_GAME_DIALOG: {
final GameInfo gi = selectedGi;
selectedGi = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Delete game?");
String msg = gi.toString();
builder.setMessage(msg);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
deleteGame(gi);
dialog.cancel();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
return alert;
}
case SAVE_GAME_DIALOG: {
final GameInfo gi = selectedGi;
selectedGi = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Save game?");
final CharSequence[] items = { "Before Selected", "After Selected", "Replace Selected" };
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
GameInfo giToReplace;
switch (item) {
case 0: giToReplace = new GameInfo().setNull(gi.startPos); break;
case 1: giToReplace = new GameInfo().setNull(gi.endPos); break;
case 2: giToReplace = gi; break;
default:
finish(); return;
}
pgnFile.replacePGN(pgnToSave, giToReplace, getApplicationContext());
finish();
}
});
AlertDialog alert = builder.create();
return alert;
}
case DELETE_PGN_FILE_DIALOG: {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Delete file?");
String name = new File(pgnFile.getName()).getName();
String msg = String.format("Delete file %s?", name);
builder.setMessage(msg);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
pgnFile.delete();
finish();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
return alert;
}
default:
return null;
}
}
private final boolean readFile() {
String fileName = pgnFile.getName();
if (!fileName.equals(lastFileName))
defaultItem = 0;
long modTime = new File(fileName).lastModified();
if (cacheValid && (modTime == lastModTime) && fileName.equals(lastFileName))
return true;
pgnFile = new PGNFile(fileName);
Pair<GameInfoResult, ArrayList<GameInfo>> p = pgnFile.getGameInfo(this, progress);
if (p.first != GameInfoResult.OK) {
gamesInFile = new ArrayList<GameInfo>();
if (p.first == GameInfoResult.OUT_OF_MEMORY) {
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getApplicationContext(), "File too large", Toast.LENGTH_SHORT).show();
}
});
}
setResult(RESULT_CANCELED);
finish();
return false;
}
gamesInFile = p.second;
cacheValid = true;
lastModTime = modTime;
lastFileName = fileName;
return true;
}
private final void sendBackResult(GameInfo gi) {
String pgn = pgnFile.readOneGame(gi);
if (pgn != null) {
setResult(RESULT_OK, (new Intent()).setAction(pgn));
finish();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
private final void deleteGame(GameInfo gi) {
if (pgnFile.deleteGame(gi, getApplicationContext(), gamesInFile)) {
ListView lv = getListView();
int pos = lv.pointToPosition(0,0);
aa = new ArrayAdapter<GameInfo>(this, R.layout.select_game_list_item, gamesInFile);
setListAdapter(aa);
String s = filterText.getText().toString();
aa.getFilter().filter(s);
lv.setSelection(pos);
// Update lastModTime, since current change has already been handled
String fileName = pgnFile.getName();
long modTime = new File(fileName).lastModified();
lastModTime = modTime;
}
}
}

View File

@@ -0,0 +1,24 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
/** Load version of EditPGN class. */
public class EditPGNLoad extends EditPGN {
}

View File

@@ -0,0 +1,24 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
/** Save version of EditPGN class. */
public class EditPGNSave extends EditPGN {
}

View File

@@ -0,0 +1,262 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import java.io.File;
import java.util.Vector;
import org.petero.droidfish.R;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
public class LoadScid extends ListActivity {
private static final class GameInfo {
String summary = "";
int gameId = -1;
public String toString() {
return summary;
}
}
private static Vector<GameInfo> gamesInFile = new Vector<GameInfo>();
private static boolean cacheValid = false;
private String fileName;
private ProgressDialog progress;
private SharedPreferences settings;
private int defaultItem = 0;
private String lastFileName = "";
private long lastModTime = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
settings = PreferenceManager.getDefaultSharedPreferences(this);
if (savedInstanceState != null) {
defaultItem = savedInstanceState.getInt("defaultScidItem");
lastFileName = savedInstanceState.getString("lastScidFileName");
if (lastFileName == null) lastFileName = "";
lastModTime = savedInstanceState.getLong("lastScidModTime");
} else {
defaultItem = settings.getInt("defaultScidItem", 0);
lastFileName = settings.getString("lastScidFileName", "");
lastModTime = settings.getLong("lastScidModTime", 0);
}
Intent i = getIntent();
String action = i.getAction();
fileName = i.getStringExtra("org.petero.droidfish.pathname");
if (action.equals("org.petero.droidfish.loadScid")) {
showDialog(PROGRESS_DIALOG);
final LoadScid lpgn = this;
new Thread(new Runnable() {
public void run() {
readFile();
runOnUiThread(new Runnable() {
public void run() {
lpgn.showList();
}
});
}
}).start();
} else if (action.equals("org.petero.droidfish.loadScidNextGame") ||
action.equals("org.petero.droidfish.loadScidPrevGame")) {
boolean next = action.equals("org.petero.droidfish.loadScidNextGame");
final int loadItem = defaultItem + (next ? 1 : -1);
if (loadItem < 0) {
Toast.makeText(getApplicationContext(), R.string.no_prev_game,
Toast.LENGTH_SHORT).show();
setResult(RESULT_CANCELED);
finish();
} else {
new Thread(new Runnable() {
public void run() {
readFile();
runOnUiThread(new Runnable() {
public void run() {
if (loadItem >= gamesInFile.size()) {
Toast.makeText(getApplicationContext(), R.string.no_next_game,
Toast.LENGTH_SHORT).show();
setResult(RESULT_CANCELED);
finish();
} else {
defaultItem = loadItem;
sendBackResult(gamesInFile.get(loadItem));
}
}
});
}
}).start();
}
} else { // Unsupported action
setResult(RESULT_CANCELED);
finish();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("defaultScidItem", defaultItem);
outState.putString("lastScidFileName", lastFileName);
outState.putLong("lastScidModTime", lastModTime);
}
@Override
protected void onPause() {
Editor editor = settings.edit();
editor.putInt("defaultScidItem", defaultItem);
editor.putString("lastScidFileName", lastFileName);
editor.putLong("lastScidModTime", lastModTime);
editor.commit();
super.onPause();
}
private final void showList() {
progress.dismiss();
final ArrayAdapter<GameInfo> aa = new ArrayAdapter<GameInfo>(this, R.layout.select_game_list_item, gamesInFile);
setListAdapter(aa);
ListView lv = getListView();
lv.setSelectionFromTop(defaultItem, 0);
lv.setFastScrollEnabled(true);
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
defaultItem = pos;
sendBackResult(aa.getItem(pos));
}
});
}
final static int PROGRESS_DIALOG = 0;
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progress = new ProgressDialog(this);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setTitle(R.string.reading_scid_file);
progress.setMessage(getString(R.string.please_wait));
progress.setCancelable(false);
return progress;
default:
return null;
}
}
private final void readFile() {
if (!fileName.equals(lastFileName))
defaultItem = 0;
long modTime = new File(fileName).lastModified();
if (cacheValid && (modTime == lastModTime) && fileName.equals(lastFileName))
return;
lastModTime = modTime;
lastFileName = fileName;
gamesInFile.clear();
Cursor cursor = getListCursor();
if (cursor != null) {
int noGames = cursor.getCount();
gamesInFile.ensureCapacity(noGames);
int percent = -1;
if (cursor.moveToFirst()) {
addGameInfo(cursor);
int gameNo = 1;
while (cursor.moveToNext()) {
addGameInfo(cursor);
gameNo++;
final int newPercent = (int)(gameNo * 100 / noGames);
if (newPercent > percent) {
percent = newPercent;
if (progress != null) {
runOnUiThread(new Runnable() {
public void run() {
progress.setProgress(newPercent);
}
});
}
}
}
}
}
cacheValid = true;
}
private int idIdx;
private int summaryIdx;
private Cursor getListCursor() {
String scidFileName = fileName.substring(0, fileName.indexOf("."));
String[] proj = new String[]{"_id", "summary"};
Cursor cursor = managedQuery(Uri.parse("content://org.scid.database.scidprovider/games"),
proj, scidFileName, null, null);
idIdx = cursor.getColumnIndex("_id");
summaryIdx = cursor.getColumnIndex("summary");
return cursor;
}
private Cursor getOneGameCursor(int gameId) {
String scidFileName = fileName.substring(0, fileName.indexOf("."));
String[] proj = new String[]{"pgn"};
String uri = String.format("content://org.scid.database.scidprovider/games/%d", gameId);
Cursor cursor = managedQuery(Uri.parse(uri),
proj, scidFileName, null, null);
return cursor;
}
private void addGameInfo(Cursor cursor) {
GameInfo gi = new GameInfo();
gi.gameId = cursor.getInt(idIdx);
gi.summary = cursor.getString(summaryIdx);
gamesInFile.add(gi);
}
private final void sendBackResult(GameInfo gi) {
if (gi.gameId >= 0) {
Cursor cursor = getOneGameCursor(gi.gameId);
if (cursor != null && cursor.moveToFirst()) {
String pgn = cursor.getString(cursor.getColumnIndex("pgn"));
if (pgn != null && pgn.length() > 0) {
setResult(RESULT_OK, (new Intent()).setAction(pgn));
finish();
return;
}
}
}
setResult(RESULT_CANCELED);
finish();
}
}

View File

@@ -0,0 +1,392 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import org.petero.droidfish.gamelogic.Pair;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.widget.Toast;
public class PGNFile {
private final File fileName;
public PGNFile(String fileName) {
this.fileName = new File(fileName);
}
public final String getName() {
return fileName.getAbsolutePath();
}
static final class GameInfo {
String info = "";
long startPos;
long endPos;
final GameInfo setNull(long currPos) {
info = null;
startPos = currPos;
endPos = currPos;
return this;
}
final boolean isNull() { return info == null; }
public String toString() {
if (info == null)
return "--";
return info;
}
}
static private final class BufferedRandomAccessFileReader {
RandomAccessFile f;
byte[] buffer = new byte[8192];
long bufStartFilePos = 0;
int bufLen = 0;
int bufPos = 0;
BufferedRandomAccessFileReader(String fileName) throws FileNotFoundException {
f = new RandomAccessFile(fileName, "r");
}
final long length() throws IOException {
return f.length();
}
final long getFilePointer() throws IOException {
return bufStartFilePos + bufPos;
}
final void close() throws IOException {
f.close();
}
private final static int EOF = -1024;
final String readLine() throws IOException {
// First handle the common case where the next line is entirely
// contained in the buffer
for (int i = bufPos; i < bufLen; i++) {
byte b = buffer[i];
if ((b == '\n') || (b == '\r')) {
String line = new String(buffer, bufPos, i - bufPos);
for ( ; i < bufLen; i++) {
b = buffer[i];
if ((b != '\n') && (b != '\r')) {
bufPos = i;
return line;
}
}
break;
}
}
// Generic case
byte[] lineBuf = new byte[8192];
int lineLen = 0;
int b;
while (true) {
b = getByte();
if (b == '\n' || b == '\r' || b == EOF)
break;
lineBuf[lineLen++] = (byte)b;
if (lineLen >= lineBuf.length)
break;
}
while (true) {
b = getByte();
if ((b != '\n') && (b != '\r')) {
if (b != EOF)
bufPos--;
break;
}
}
if ((b == EOF) && (lineLen == 0))
return null;
else
return new String(lineBuf, 0, lineLen);
}
private final int getByte() throws IOException {
if (bufPos >= bufLen) {
bufStartFilePos = f.getFilePointer();
bufLen = f.read(buffer);
bufPos = 0;
if (bufLen <= 0)
return EOF;
}
return buffer[bufPos++];
}
}
private final static class HeaderInfo {
String event = "";
String site = "";
String date = "";
String round = "";
String white = "";
String black = "";
String result = "";
public String toString() {
StringBuilder info = new StringBuilder(128);
info.append(white);
info.append(" - ");
info.append(black);
if (date.length() > 0) {
info.append(' ');
info.append(date);
}
if (round.length() > 0) {
info.append(' ');
info.append(round);
}
if (event.length() > 0) {
info.append(' ');
info.append(event);
}
if (site.length() > 0) {
info.append(' ');
info.append(site);
}
info.append(' ');
info.append(result);
return info.toString();
}
}
public static enum GameInfoResult {
OK,
CANCEL,
OUT_OF_MEMORY;
}
/** Return info about all PGN games in a file. */
public final Pair<GameInfoResult,ArrayList<GameInfo>> getGameInfo(Activity activity,
final ProgressDialog progress) {
ArrayList<GameInfo> gamesInFile = new ArrayList<GameInfo>();
try {
int percent = -1;
gamesInFile.clear();
BufferedRandomAccessFileReader f = new BufferedRandomAccessFileReader(fileName.getAbsolutePath());
long fileLen = f.length();
GameInfo gi = null;
HeaderInfo hi = null;
boolean inHeader = false;
long filePos = 0;
while (true) {
filePos = f.getFilePointer();
String line = f.readLine();
if (line == null)
break; // EOF
int len = line.length();
if (len == 0)
continue;
boolean isHeader = line.charAt(0) == '[';
if (isHeader) {
if (!line.contains("\"")) // Try to avoid some false positives
isHeader = false;
}
if (isHeader) {
if (!inHeader) { // Start of game
inHeader = true;
if (gi != null) {
gi.endPos = filePos;
gi.info = hi.toString();
gamesInFile.add(gi);
final int newPercent = (int)(filePos * 100 / fileLen);
if (newPercent > percent) {
percent = newPercent;
if (progress != null) {
activity.runOnUiThread(new Runnable() {
public void run() {
progress.setProgress(newPercent);
}
});
}
}
if (Thread.currentThread().isInterrupted())
return new Pair<GameInfoResult,ArrayList<GameInfo>>(GameInfoResult.CANCEL, null);
}
gi = new GameInfo();
gi.startPos = filePos;
gi.endPos = -1;
hi = new HeaderInfo();
}
if (line.startsWith("[Event ")) {
hi.event = line.substring(8, len - 2);
if (hi.event.equals("?")) hi.event = "";
} else if (line.startsWith("[Site ")) {
hi.site = line.substring(7, len - 2);
if (hi.site.equals("?")) hi.site = "";
} else if (line.startsWith("[Date ")) {
hi.date = line.substring(7, len - 2);
if (hi.date.equals("?")) hi.date = "";
} else if (line.startsWith("[Round ")) {
hi.round = line.substring(8, len - 2);
if (hi.round.equals("?")) hi.round = "";
} else if (line.startsWith("[White ")) {
hi.white = line.substring(8, len - 2);
} else if (line.startsWith("[Black ")) {
hi.black = line.substring(8, len - 2);
} else if (line.startsWith("[Result ")) {
hi.result = line.substring(9, len - 2);
if (hi.result.equals("1-0")) hi.result = "1-0";
else if (hi.result.equals("0-1")) hi.result = "0-1";
else if ((hi.result.equals("1/2-1/2")) || (hi.result.equals("1/2"))) hi.result = "1/2-1/2";
else hi.result = "*";
}
} else {
inHeader = false;
}
}
if (gi != null) {
gi.endPos = filePos;
gi.info = hi.toString();
gamesInFile.add(gi);
}
f.close();
} catch (IOException e) {
} catch (OutOfMemoryError e) {
gamesInFile.clear();
gamesInFile = null;
return new Pair<GameInfoResult,ArrayList<GameInfo>>(GameInfoResult.OUT_OF_MEMORY, null);
}
return new Pair<GameInfoResult,ArrayList<GameInfo>>(GameInfoResult.OK, gamesInFile);
}
private final void mkDirs() {
File dirFile = fileName.getParentFile();
dirFile.mkdirs();
}
/** Read one game defined by gi. Return null on failure. */
final String readOneGame(GameInfo gi) {
try {
RandomAccessFile f = new RandomAccessFile(fileName, "r");
byte[] pgnData = new byte[(int) (gi.endPos - gi.startPos)];
f.seek(gi.startPos);
f.readFully(pgnData);
f.close();
return new String(pgnData);
} catch (IOException e) {
}
return null;
}
/** Append PGN to the end of this PGN file. */
public final void appendPGN(String pgn, Context context) {
try {
mkDirs();
FileWriter fw = new FileWriter(fileName, true);
fw.write(pgn);
fw.close();
Toast.makeText(context, "Game saved", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
if (context != null) {
String msg = "Failed to save game";
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
}
final boolean deleteGame(GameInfo gi, Context context, ArrayList<GameInfo> gamesInFile) {
try {
File tmpFile = new File(fileName + ".tmp_delete");
RandomAccessFile fileReader = new RandomAccessFile(fileName, "r");
RandomAccessFile fileWriter = new RandomAccessFile(tmpFile, "rw");
copyData(fileReader, fileWriter, gi.startPos);
fileReader.seek(gi.endPos);
copyData(fileReader, fileWriter, fileReader.length() - gi.endPos);
fileReader.close();
fileWriter.close();
tmpFile.renameTo(fileName);
// Update gamesInFile
if (gamesInFile != null) {
gamesInFile.remove(gi);
final int nGames = gamesInFile.size();
final long delta = gi.endPos - gi.startPos;
for (int i = 0; i < nGames; i++) {
GameInfo tmpGi = gamesInFile.get(i);
if (tmpGi.startPos > gi.startPos) {
tmpGi.startPos -= delta;
tmpGi.endPos -= delta;
}
}
}
return true;
} catch (IOException e) {
if (context != null) {
String msg = "Failed to delete game";
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
return false;
}
final boolean replacePGN(String pgnToSave, GameInfo gi, Context context) {
try {
File tmpFile = new File(fileName + ".tmp_delete");
RandomAccessFile fileReader = new RandomAccessFile(fileName, "r");
RandomAccessFile fileWriter = new RandomAccessFile(tmpFile, "rw");
copyData(fileReader, fileWriter, gi.startPos);
fileWriter.write(pgnToSave.getBytes());
fileReader.seek(gi.endPos);
copyData(fileReader, fileWriter, fileReader.length() - gi.endPos);
fileReader.close();
fileWriter.close();
tmpFile.renameTo(fileName);
Toast.makeText(context, "Game saved", Toast.LENGTH_SHORT).show();
return true;
} catch (IOException e) {
if (context != null) {
String msg = "Failed to save game";
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
return false;
}
private final static void copyData(RandomAccessFile fileReader,
RandomAccessFile fileWriter,
long nBytes) throws IOException {
byte[] buffer = new byte[8192];
while (nBytes > 0) {
int nRead = fileReader.read(buffer, 0, Math.min(buffer.length, (int)nBytes));
if (nRead > 0) {
fileWriter.write(buffer, 0, nRead);
nBytes -= nRead;
}
}
}
final boolean delete() {
return fileName.delete();
}
}

View File

@@ -0,0 +1,33 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.activities;
import org.petero.droidfish.R;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class Preferences extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}

View File

@@ -0,0 +1,740 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.engine.DroidBook.BookEntry;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.UndoInfo;
public class CtgBook implements IOpeningBook {
private BookOptions options = new BookOptions();
private File ctgFile;
private File ctbFile;
private File ctoFile;
static boolean canHandle(BookOptions options) {
String filename = options.filename;
return (filename.endsWith(".ctg") ||
filename.endsWith(".ctb") ||
filename.endsWith(".cto"));
}
@Override
public boolean enabled() {
return ctgFile.canRead() &&
ctbFile.canRead() &&
ctoFile.canRead();
}
@Override
public void setOptions(BookOptions options) {
this.options = new BookOptions(options);
String fileName = options.filename;
int len = fileName.length();
ctgFile = new File(fileName.substring(0, len-1) + "g");
ctbFile = new File(fileName.substring(0, len-1) + "b");
ctoFile = new File(fileName.substring(0, len-1) + "o");
}
@Override
public List<BookEntry> getBookEntries(Position pos) {
RandomAccessFile ctgF = null;
RandomAccessFile ctbF = null;
RandomAccessFile ctoF = null;
try {
ctgF = new RandomAccessFile(ctgFile, "r");
ctbF = new RandomAccessFile(ctbFile, "r");
ctoF = new RandomAccessFile(ctoFile, "r");
CtbFile ctb = new CtbFile(ctbF);
CtoFile cto = new CtoFile(ctoF);
CtgFile ctg = new CtgFile(ctgF, ctb, cto);
List<BookEntry> ret = null;
PositionData pd = ctg.getPositionData(pos);
if (pd != null) {
boolean mirrorColor = pd.mirrorColor;
boolean mirrorLeftRight = pd.mirrorLeftRight;
ret = pd.getBookMoves();
UndoInfo ui = new UndoInfo();
for (BookEntry be : ret) {
pd.pos.makeMove(be.move, ui);
PositionData movePd = ctg.getPositionData(pd.pos);
pd.pos.unMakeMove(be.move, ui);
double weight = be.weight;
if (movePd == null) {
// System.out.printf("%s : no pos\n", TextIO.moveToUCIString(be.move));
weight = 0;
} else {
int recom = movePd.getRecommendation();
if ((recom >= 64) && (recom < 128)) {
if (options.tournamentMode)
weight = 0;
} else if (recom >= 128) {
if (options.preferMainLines)
weight *= 10;
}
int score = movePd.getOpponentScore();
// double w0 = weight;
weight = weight * score;
// System.out.printf("%s : w0:%.3f rec:%d score:%d %.3f\n", TextIO.moveToUCIString(be.move),
// w0, recom, score, weight);
}
be.weight = weight;
}
if (mirrorLeftRight) {
for (int i = 0; i < ret.size(); i++)
ret.get(i).move = mirrorMoveLeftRight(ret.get(i).move);
}
if (mirrorColor) {
for (int i = 0; i < ret.size(); i++)
ret.get(i).move = mirrorMoveColor(ret.get(i).move);
}
}
return ret;
} catch (IOException e) {
return null;
} finally {
if (ctgF != null) try { ctgF.close(); } catch (IOException e) { }
if (ctbF != null) try { ctbF.close(); } catch (IOException e) { }
if (ctoF != null) try { ctoF.close(); } catch (IOException e) { }
}
}
/** Read len bytes from offs in file f. */
private final static byte[] readBytes(RandomAccessFile f, int offs, int len) throws IOException {
byte[] ret = new byte[len];
f.seek(offs);
f.readFully(ret);
return ret;
}
/** Convert len bytes starting at offs in buf to an integer. */
private final static int extractInt(byte[] buf, int offs, int len) {
int ret = 0;
for (int i = 0; i < len; i++) {
int b = buf[offs + i];
if (b < 0) b += 256;
ret = (ret << 8) + b;
}
return ret;
}
private final static class CtbFile {
int lowerPageBound;
int upperPageBound;
CtbFile(RandomAccessFile f) throws IOException {
byte[] buf = readBytes(f, 4, 8);
lowerPageBound = extractInt(buf, 0, 4);
upperPageBound = extractInt(buf, 4, 4);
}
}
private final static class BitVector {
private List<Byte> buf = new ArrayList<Byte>();
private int length = 0;
void addBit(boolean value) {
int byteIdx = length / 8;
int bitIdx = 7 - (length & 7);
while (buf.size() <= byteIdx)
buf.add(Byte.valueOf((byte)0));
if (value)
buf.set(byteIdx, (byte)(buf.get(byteIdx) | (1 << bitIdx)));
length++;
}
void addBits(int mask, int numBits) {
for (int i = 0; i < numBits; i++) {
int b = numBits - 1 - i;
addBit((mask & (1 << b)) != 0);
}
}
/** Number of bits left in current byte. */
int padBits() {
int bitIdx = length & 7;
return (bitIdx == 0) ? 0 : 8 - bitIdx;
}
final byte[] toByteArray() {
byte[] ret = new byte[buf.size()];
for (int i = 0; i < buf.size(); i++)
ret[i] = buf.get(i);
return ret;
}
}
/** Converts a position to a byte array. */
private final static byte[] positionToByteArray(Position pos) {
BitVector bits = new BitVector();
bits.addBits(0, 8); // Header byte
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
int p = pos.getPiece(Position.getSquare(x, y));
switch (p) {
case Piece.EMPTY: bits.addBits(0x00, 1); break;
case Piece.WKING: bits.addBits(0x20, 6); break;
case Piece.WQUEEN: bits.addBits(0x22, 6); break;
case Piece.WROOK: bits.addBits(0x16, 5); break;
case Piece.WBISHOP: bits.addBits(0x14, 5); break;
case Piece.WKNIGHT: bits.addBits(0x12, 5); break;
case Piece.WPAWN: bits.addBits(0x06, 3); break;
case Piece.BKING: bits.addBits(0x21, 6); break;
case Piece.BQUEEN: bits.addBits(0x23, 6); break;
case Piece.BROOK: bits.addBits(0x17, 5); break;
case Piece.BBISHOP: bits.addBits(0x15, 5); break;
case Piece.BKNIGHT: bits.addBits(0x13, 5); break;
case Piece.BPAWN: bits.addBits(0x07, 3); break;
}
}
}
TextIO.fixupEPSquare(pos);
boolean ep = pos.getEpSquare() != -1;
boolean cs = pos.getCastleMask() != 0;
if (!ep && !cs)
bits.addBit(false); // At least one pad bit
int specialBits = (ep ? 3 : 0) + (cs ? 4 : 0);
while (bits.padBits() != specialBits)
bits.addBit(false);
if (ep)
bits.addBits(Position.getX(pos.getEpSquare()), 3);
if (cs) {
bits.addBit(pos.h8Castle());
bits.addBit(pos.a8Castle());
bits.addBit(pos.h1Castle());
bits.addBit(pos.a1Castle());
}
if ((bits.length & 7) != 0) throw new RuntimeException();
int header = bits.length / 8;
if (ep) header |= 0x20;
if (cs) header |= 0x40;
byte[] buf = bits.toByteArray();
buf[0] = (byte)header;
return buf;
}
private final static class CtoFile {
RandomAccessFile f;
CtoFile(RandomAccessFile f) {
this.f = f;
}
final static ArrayList<Integer> getHashIndices(byte[] encodedPos, CtbFile ctb) throws IOException {
ArrayList<Integer> ret = new ArrayList<Integer>();
int hash = getHashValue(encodedPos);
for (int n = 0; n < 0x7fffffff; n = 2*n + 1) {
int c = (hash & n) + n;
if (c < ctb.lowerPageBound)
continue;
ret.add(c);
if (c >= ctb.upperPageBound)
break;
}
return ret;
}
final int getPage(int hashIndex) throws IOException {
byte[] buf = readBytes(f, 16 + 4 * hashIndex, 4);
int page = extractInt(buf, 0, 4);
return page;
}
final private static int tbl[] = {
0x3100d2bf, 0x3118e3de, 0x34ab1372, 0x2807a847,
0x1633f566, 0x2143b359, 0x26d56488, 0x3b9e6f59,
0x37755656, 0x3089ca7b, 0x18e92d85, 0x0cd0e9d8,
0x1a9e3b54, 0x3eaa902f, 0x0d9bfaae, 0x2f32b45b,
0x31ed6102, 0x3d3c8398, 0x146660e3, 0x0f8d4b76,
0x02c77a5f, 0x146c8799, 0x1c47f51f, 0x249f8f36,
0x24772043, 0x1fbc1e4d, 0x1e86b3fa, 0x37df36a6,
0x16ed30e4, 0x02c3148e, 0x216e5929, 0x0636b34e,
0x317f9f56, 0x15f09d70, 0x131026fb, 0x38c784b1,
0x29ac3305, 0x2b485dc5, 0x3c049ddc, 0x35a9fbcd,
0x31d5373b, 0x2b246799, 0x0a2923d3, 0x08a96e9d,
0x30031a9f, 0x08f525b5, 0x33611c06, 0x2409db98,
0x0ca4feb2, 0x1000b71e, 0x30566e32, 0x39447d31,
0x194e3752, 0x08233a95, 0x0f38fe36, 0x29c7cd57,
0x0f7b3a39, 0x328e8a16, 0x1e7d1388, 0x0fba78f5,
0x274c7e7c, 0x1e8be65c, 0x2fa0b0bb, 0x1eb6c371
};
final private static int getHashValue(byte[] encodedPos) {
int hash = 0;
int tmp = 0;
for (int i = 0; i < encodedPos.length; i++) {
int ch = encodedPos[i];
tmp += ((0x0f - (ch & 0x0f)) << 2) + 1;
hash += tbl[tmp & 0x3f];
tmp += ((0xf0 - (ch & 0xf0)) >> 2) + 1;
hash += tbl[tmp & 0x3f];
}
return hash;
}
}
private final static class CtgFile {
private RandomAccessFile f;
private CtbFile ctb;
private CtoFile cto;
CtgFile(RandomAccessFile f, CtbFile ctb, CtoFile cto) {
this.f = f;
this.ctb = ctb;
this.cto = cto;
}
final PositionData getPositionData(Position pos) throws IOException {
boolean mirrorColor = !pos.whiteMove;
boolean needCopy = true;
if (mirrorColor) {
pos = mirrorPosColor(pos);
needCopy = false;
}
boolean mirrorLeftRight = false;
if ((pos.getCastleMask() == 0) && (Position.getX(pos.getKingSq(true)) < 4)) {
pos = mirrorPosLeftRight(pos);
mirrorLeftRight = true;
needCopy = false;
}
if (needCopy)
pos = new Position(pos);
byte[] encodedPos = positionToByteArray(pos);
ArrayList<Integer> hashIdxList = CtoFile.getHashIndices(encodedPos, ctb);
PositionData pd = null;
for (int i = 0; i < hashIdxList.size(); i++) {
int page = cto.getPage(hashIdxList.get(i));
if (page < 0)
continue;
pd = findInPage(page, encodedPos);
if (pd != null) {
pd.pos = pos;
pd.mirrorColor = mirrorColor;
pd.mirrorLeftRight = mirrorLeftRight;
break;
}
}
return pd;
}
private final PositionData findInPage(int page, byte[] encodedPos) throws IOException {
byte[] pageBuf = readBytes(f, (page+1)*4096, 4096);
try {
int nPos = extractInt(pageBuf, 0, 2);
int nBytes = extractInt(pageBuf, 2, 2);
for (int i = nBytes; i < 4096; i++)
pageBuf[i] = 0; // Don't depend on trailing garbage
int offs = 4;
for (int p = 0; p < nPos; p++) {
boolean match = true;
for (int i = 0; i < encodedPos.length; i++)
if (encodedPos[i] != pageBuf[offs+i]) {
match = false;
break;
}
if (match)
return new PositionData(pageBuf, offs);
int posLen = pageBuf[offs] & 0x1f;
offs += posLen;
int moveBytes = extractInt(pageBuf, offs, 1);
offs += moveBytes;
offs += PositionData.posInfoBytes;
}
return null;
} catch (ArrayIndexOutOfBoundsException ex) {
return null; // Ignore corrupt book file entries
}
}
}
private final static class PositionData {
private byte[] buf;
private int posLen;
private int moveBytes;
final static int posInfoBytes = 3*4 + 4 + (3+4)*2 + 1 + 1 + 1;
Position pos;
boolean mirrorColor = false;
boolean mirrorLeftRight = false;
PositionData(byte[] pageBuf, int offs) {
posLen = pageBuf[offs] & 0x1f;
moveBytes = extractInt(pageBuf, offs + posLen, 1);
int bufLen = posLen + moveBytes + posInfoBytes;
buf = new byte[bufLen];
for (int i = 0; i < bufLen; i++)
buf[i] = pageBuf[offs + i];
}
final ArrayList<BookEntry> getBookMoves() {
ArrayList<BookEntry> entries = new ArrayList<BookEntry>();
int nMoves = (moveBytes - 1) / 2;
for (int mi = 0; mi < nMoves; mi++) {
int move = extractInt(buf, posLen + 1 + mi * 2, 1);
int flags = extractInt(buf, posLen + 1 + mi * 2 + 1, 1);
Move m = decodeMove(pos, move);
if (m == null)
continue;
// System.out.printf("mi:%d m:%s flags:%d\n", mi, TextIO.moveToUCIString(m), flags);
BookEntry ent = new BookEntry(m);
switch (flags) {
default:
case 0x00: ent.weight = 1; break; // No annotation
case 0x01: ent.weight = 8; break; // !
case 0x02: ent.weight = 0; break; // ?
case 0x03: ent.weight = 32; break; // !!
case 0x04: ent.weight = 0; break; // ??
case 0x05: ent.weight = 0.5; break; // !?
case 0x06: ent.weight = 0.125; break; // ?!
case 0x08: ent.weight = 1000000; break; // Only move
}
entries.add(ent);
}
return entries;
}
/** Return (wins + draws/2) / games. */
final int getOpponentScore() {
int statStart = posLen + moveBytes;
// int wins = extractInt(buf, statStart + 3, 3);
int loss = extractInt(buf, statStart + 6, 3);
int draws = extractInt(buf, statStart + 9, 3);
return loss * 2 + draws;
}
final int getRecommendation() {
int statStart = posLen + moveBytes;
int recom = extractInt(buf, statStart + 30, 1);
return recom;
}
private static final class MoveInfo {
int piece;
int pieceNo;
int dx;
int dy;
}
private final static MoveInfo MI(int piece, int pieceNo, int dx, int dy) {
MoveInfo mi = new MoveInfo();
mi.piece = piece;
mi.pieceNo = pieceNo;
mi.dx = dx;
mi.dy = dy;
return mi;
}
private final static MoveInfo[] moveInfo = new MoveInfo[256];
static {
moveInfo[0x00] = MI(Piece.WPAWN , 4, +1, +1);
moveInfo[0x01] = MI(Piece.WKNIGHT, 1, -2, -1);
moveInfo[0x03] = MI(Piece.WQUEEN , 1, +2, +0);
moveInfo[0x04] = MI(Piece.WPAWN , 1, +0, +1);
moveInfo[0x05] = MI(Piece.WQUEEN , 0, +0, +1);
moveInfo[0x06] = MI(Piece.WPAWN , 3, -1, +1);
moveInfo[0x08] = MI(Piece.WQUEEN , 1, +4, +0);
moveInfo[0x09] = MI(Piece.WBISHOP, 1, +6, +6);
moveInfo[0x0a] = MI(Piece.WKING , 0, +0, -1);
moveInfo[0x0c] = MI(Piece.WPAWN , 0, -1, +1);
moveInfo[0x0d] = MI(Piece.WBISHOP, 0, +3, +3);
moveInfo[0x0e] = MI(Piece.WROOK , 1, +3, +0);
moveInfo[0x0f] = MI(Piece.WKNIGHT, 0, -2, -1);
moveInfo[0x12] = MI(Piece.WBISHOP, 0, +7, +7);
moveInfo[0x13] = MI(Piece.WKING , 0, +0, +1);
moveInfo[0x14] = MI(Piece.WPAWN , 7, +1, +1);
moveInfo[0x15] = MI(Piece.WBISHOP, 0, +5, +5);
moveInfo[0x18] = MI(Piece.WPAWN , 6, +0, +1);
moveInfo[0x1a] = MI(Piece.WQUEEN , 1, +0, +6);
moveInfo[0x1b] = MI(Piece.WBISHOP, 0, -1, +1);
moveInfo[0x1d] = MI(Piece.WBISHOP, 1, +7, +7);
moveInfo[0x21] = MI(Piece.WROOK , 1, +7, +0);
moveInfo[0x22] = MI(Piece.WBISHOP, 1, -2, +2);
moveInfo[0x23] = MI(Piece.WQUEEN , 1, +6, +6);
moveInfo[0x24] = MI(Piece.WPAWN , 7, -1, +1);
moveInfo[0x26] = MI(Piece.WBISHOP, 0, -7, +7);
moveInfo[0x27] = MI(Piece.WPAWN , 2, -1, +1);
moveInfo[0x28] = MI(Piece.WQUEEN , 0, +5, +5);
moveInfo[0x29] = MI(Piece.WQUEEN , 0, +6, +0);
moveInfo[0x2a] = MI(Piece.WKNIGHT, 1, +1, -2);
moveInfo[0x2d] = MI(Piece.WPAWN , 5, +1, +1);
moveInfo[0x2e] = MI(Piece.WBISHOP, 0, +1, +1);
moveInfo[0x2f] = MI(Piece.WQUEEN , 0, +1, +0);
moveInfo[0x30] = MI(Piece.WKNIGHT, 1, -1, -2);
moveInfo[0x31] = MI(Piece.WQUEEN , 0, +3, +0);
moveInfo[0x32] = MI(Piece.WBISHOP, 1, +5, +5);
moveInfo[0x34] = MI(Piece.WKNIGHT, 0, +1, +2);
moveInfo[0x36] = MI(Piece.WKNIGHT, 0, +2, +1);
moveInfo[0x37] = MI(Piece.WQUEEN , 0, +0, +4);
moveInfo[0x38] = MI(Piece.WQUEEN , 1, -4, +4);
moveInfo[0x39] = MI(Piece.WQUEEN , 0, +5, +0);
moveInfo[0x3a] = MI(Piece.WBISHOP, 0, +6, +6);
moveInfo[0x3b] = MI(Piece.WQUEEN , 1, -5, +5);
moveInfo[0x3c] = MI(Piece.WBISHOP, 0, -5, +5);
moveInfo[0x41] = MI(Piece.WQUEEN , 1, +5, +5);
moveInfo[0x42] = MI(Piece.WQUEEN , 0, -7, +7);
moveInfo[0x44] = MI(Piece.WKING , 0, +1, -1);
moveInfo[0x45] = MI(Piece.WQUEEN , 0, +3, +3);
moveInfo[0x4a] = MI(Piece.WPAWN , 7, +0, +2);
moveInfo[0x4b] = MI(Piece.WQUEEN , 0, -5, +5);
moveInfo[0x4c] = MI(Piece.WKNIGHT, 1, +1, +2);
moveInfo[0x4d] = MI(Piece.WQUEEN , 1, +0, +1);
moveInfo[0x50] = MI(Piece.WROOK , 0, +0, +6);
moveInfo[0x52] = MI(Piece.WROOK , 0, +6, +0);
moveInfo[0x54] = MI(Piece.WBISHOP, 1, -1, +1);
moveInfo[0x55] = MI(Piece.WPAWN , 2, +0, +1);
moveInfo[0x5c] = MI(Piece.WPAWN , 6, +1, +1);
moveInfo[0x5f] = MI(Piece.WPAWN , 4, +0, +2);
moveInfo[0x61] = MI(Piece.WQUEEN , 0, +6, +6);
moveInfo[0x62] = MI(Piece.WPAWN , 1, +0, +2);
moveInfo[0x63] = MI(Piece.WQUEEN , 1, -7, +7);
moveInfo[0x66] = MI(Piece.WBISHOP, 0, -3, +3);
moveInfo[0x67] = MI(Piece.WKING , 0, +1, +1);
moveInfo[0x69] = MI(Piece.WROOK , 1, +0, +7);
moveInfo[0x6a] = MI(Piece.WBISHOP, 0, +4, +4);
moveInfo[0x6b] = MI(Piece.WKING , 0, +2, +0);
moveInfo[0x6e] = MI(Piece.WROOK , 0, +5, +0);
moveInfo[0x6f] = MI(Piece.WQUEEN , 1, +7, +7);
moveInfo[0x72] = MI(Piece.WBISHOP, 1, -7, +7);
moveInfo[0x74] = MI(Piece.WQUEEN , 0, +2, +0);
moveInfo[0x79] = MI(Piece.WBISHOP, 1, -6, +6);
moveInfo[0x7a] = MI(Piece.WROOK , 0, +0, +3);
moveInfo[0x7b] = MI(Piece.WROOK , 1, +0, +6);
moveInfo[0x7c] = MI(Piece.WPAWN , 2, +1, +1);
moveInfo[0x7d] = MI(Piece.WROOK , 1, +0, +1);
moveInfo[0x7e] = MI(Piece.WQUEEN , 0, -3, +3);
moveInfo[0x7f] = MI(Piece.WROOK , 0, +1, +0);
moveInfo[0x80] = MI(Piece.WQUEEN , 0, -6, +6);
moveInfo[0x81] = MI(Piece.WROOK , 0, +0, +1);
moveInfo[0x82] = MI(Piece.WPAWN , 5, -1, +1);
moveInfo[0x85] = MI(Piece.WKNIGHT, 0, -1, +2);
moveInfo[0x86] = MI(Piece.WROOK , 0, +7, +0);
moveInfo[0x87] = MI(Piece.WROOK , 0, +0, +5);
moveInfo[0x8a] = MI(Piece.WKNIGHT, 0, +1, -2);
moveInfo[0x8b] = MI(Piece.WPAWN , 0, +1, +1);
moveInfo[0x8c] = MI(Piece.WKING , 0, -1, -1);
moveInfo[0x8e] = MI(Piece.WQUEEN , 1, -2, +2);
moveInfo[0x8f] = MI(Piece.WQUEEN , 0, +7, +0);
moveInfo[0x92] = MI(Piece.WQUEEN , 1, +1, +1);
moveInfo[0x94] = MI(Piece.WQUEEN , 0, +0, +3);
moveInfo[0x96] = MI(Piece.WPAWN , 1, +1, +1);
moveInfo[0x97] = MI(Piece.WKING , 0, -1, +0);
moveInfo[0x98] = MI(Piece.WROOK , 0, +3, +0);
moveInfo[0x99] = MI(Piece.WROOK , 0, +0, +4);
moveInfo[0x9a] = MI(Piece.WQUEEN , 0, +0, +6);
moveInfo[0x9b] = MI(Piece.WPAWN , 2, +0, +2);
moveInfo[0x9d] = MI(Piece.WQUEEN , 0, +0, +2);
moveInfo[0x9f] = MI(Piece.WBISHOP, 1, -4, +4);
moveInfo[0xa0] = MI(Piece.WQUEEN , 1, +0, +3);
moveInfo[0xa2] = MI(Piece.WQUEEN , 0, +2, +2);
moveInfo[0xa3] = MI(Piece.WPAWN , 7, +0, +1);
moveInfo[0xa5] = MI(Piece.WROOK , 1, +0, +5);
moveInfo[0xa9] = MI(Piece.WROOK , 1, +2, +0);
moveInfo[0xab] = MI(Piece.WQUEEN , 1, -6, +6);
moveInfo[0xad] = MI(Piece.WROOK , 1, +4, +0);
moveInfo[0xae] = MI(Piece.WQUEEN , 1, +3, +3);
moveInfo[0xb0] = MI(Piece.WQUEEN , 1, +0, +4);
moveInfo[0xb1] = MI(Piece.WPAWN , 5, +0, +2);
moveInfo[0xb2] = MI(Piece.WBISHOP, 0, -6, +6);
moveInfo[0xb5] = MI(Piece.WROOK , 1, +5, +0);
moveInfo[0xb7] = MI(Piece.WQUEEN , 0, +0, +5);
moveInfo[0xb9] = MI(Piece.WBISHOP, 1, +3, +3);
moveInfo[0xbb] = MI(Piece.WPAWN , 4, +0, +1);
moveInfo[0xbc] = MI(Piece.WQUEEN , 1, +5, +0);
moveInfo[0xbd] = MI(Piece.WQUEEN , 1, +0, +2);
moveInfo[0xbe] = MI(Piece.WKING , 0, +1, +0);
moveInfo[0xc1] = MI(Piece.WBISHOP, 0, +2, +2);
moveInfo[0xc2] = MI(Piece.WBISHOP, 1, +2, +2);
moveInfo[0xc3] = MI(Piece.WBISHOP, 0, -2, +2);
moveInfo[0xc4] = MI(Piece.WROOK , 1, +1, +0);
moveInfo[0xc5] = MI(Piece.WROOK , 1, +0, +4);
moveInfo[0xc6] = MI(Piece.WQUEEN , 1, +0, +5);
moveInfo[0xc7] = MI(Piece.WPAWN , 6, -1, +1);
moveInfo[0xc8] = MI(Piece.WPAWN , 6, +0, +2);
moveInfo[0xc9] = MI(Piece.WQUEEN , 1, +0, +7);
moveInfo[0xca] = MI(Piece.WBISHOP, 1, -3, +3);
moveInfo[0xcb] = MI(Piece.WPAWN , 5, +0, +1);
moveInfo[0xcc] = MI(Piece.WBISHOP, 1, -5, +5);
moveInfo[0xcd] = MI(Piece.WROOK , 0, +2, +0);
moveInfo[0xcf] = MI(Piece.WPAWN , 3, +0, +1);
moveInfo[0xd1] = MI(Piece.WPAWN , 1, -1, +1);
moveInfo[0xd2] = MI(Piece.WKNIGHT, 1, +2, +1);
moveInfo[0xd3] = MI(Piece.WKNIGHT, 1, -2, +1);
moveInfo[0xd7] = MI(Piece.WQUEEN , 0, -1, +1);
moveInfo[0xd8] = MI(Piece.WROOK , 1, +6, +0);
moveInfo[0xd9] = MI(Piece.WQUEEN , 0, -2, +2);
moveInfo[0xda] = MI(Piece.WKNIGHT, 0, -1, -2);
moveInfo[0xdb] = MI(Piece.WPAWN , 0, +0, +2);
moveInfo[0xde] = MI(Piece.WPAWN , 4, -1, +1);
moveInfo[0xdf] = MI(Piece.WKING , 0, -1, +1);
moveInfo[0xe0] = MI(Piece.WKNIGHT, 1, +2, -1);
moveInfo[0xe1] = MI(Piece.WROOK , 0, +0, +7);
moveInfo[0xe3] = MI(Piece.WROOK , 1, +0, +3);
moveInfo[0xe5] = MI(Piece.WQUEEN , 0, +4, +0);
moveInfo[0xe6] = MI(Piece.WPAWN , 3, +0, +2);
moveInfo[0xe7] = MI(Piece.WQUEEN , 0, +4, +4);
moveInfo[0xe8] = MI(Piece.WROOK , 0, +0, +2);
moveInfo[0xe9] = MI(Piece.WKNIGHT, 0, +2, -1);
moveInfo[0xeb] = MI(Piece.WPAWN , 3, +1, +1);
moveInfo[0xec] = MI(Piece.WPAWN , 0, +0, +1);
moveInfo[0xed] = MI(Piece.WQUEEN , 0, +7, +7);
moveInfo[0xee] = MI(Piece.WQUEEN , 1, -1, +1);
moveInfo[0xef] = MI(Piece.WROOK , 0, +4, +0);
moveInfo[0xf0] = MI(Piece.WQUEEN , 1, +7, +0);
moveInfo[0xf1] = MI(Piece.WQUEEN , 0, +1, +1);
moveInfo[0xf3] = MI(Piece.WKNIGHT, 1, -1, +2);
moveInfo[0xf4] = MI(Piece.WROOK , 1, +0, +2);
moveInfo[0xf5] = MI(Piece.WBISHOP, 1, +1, +1);
moveInfo[0xf6] = MI(Piece.WKING , 0, -2, +0);
moveInfo[0xf7] = MI(Piece.WKNIGHT, 0, -2, +1);
moveInfo[0xf8] = MI(Piece.WQUEEN , 1, +1, +0);
moveInfo[0xf9] = MI(Piece.WQUEEN , 1, +0, +6);
moveInfo[0xfa] = MI(Piece.WQUEEN , 1, +3, +0);
moveInfo[0xfb] = MI(Piece.WQUEEN , 1, +2, +2);
moveInfo[0xfd] = MI(Piece.WQUEEN , 0, +0, +7);
moveInfo[0xfe] = MI(Piece.WQUEEN , 1, -3, +3);
}
private final static int findPiece(Position pos, int piece, int pieceNo) {
for (int x = 0; x < 8; x++)
for (int y = 0; y < 8; y++) {
int sq = Position.getSquare(x, y);
if (pos.getPiece(sq) == piece)
if (pieceNo-- == 0)
return sq;
}
return -1;
}
private final Move decodeMove(Position pos, int moveCode) {
MoveInfo mi = moveInfo[moveCode];
if (mi == null)
return null;
int from = findPiece(pos, mi.piece, mi.pieceNo);
if (from < 0)
return null;
int toX = (Position.getX(from) + mi.dx) & 7;
int toY = (Position.getY(from) + mi.dy) & 7;
int to = Position.getSquare(toX, toY);
int promoteTo = Piece.EMPTY;
if ((pos.getPiece(from) == Piece.WPAWN) && (toY == 7))
promoteTo = Piece.WQUEEN;
Move m = new Move(from, to, promoteTo);
return m;
}
}
private final static int mirrorSquareColor(int sq) {
int x = Position.getX(sq);
int y = 7 - Position.getY(sq);
return Position.getSquare(x, y);
}
private final static int mirrorPieceColor(int piece) {
if (Piece.isWhite(piece)) {
piece = Piece.makeBlack(piece);
} else {
piece = Piece.makeWhite(piece);
}
return piece;
}
private final static Position mirrorPosColor(Position pos) {
Position ret = new Position(pos);
for (int sq = 0; sq < 64; sq++) {
int mSq = mirrorSquareColor(sq);
int piece = pos.getPiece(sq);
int mPiece = mirrorPieceColor(piece);
ret.setPiece(mSq, mPiece);
}
ret.setWhiteMove(!pos.whiteMove);
int castleMask = 0;
if (pos.a1Castle()) castleMask |= (1 << Position.A8_CASTLE);
if (pos.h1Castle()) castleMask |= (1 << Position.H8_CASTLE);
if (pos.a8Castle()) castleMask |= (1 << Position.A1_CASTLE);
if (pos.h8Castle()) castleMask |= (1 << Position.H1_CASTLE);
ret.setCastleMask(castleMask);
int epSquare = pos.getEpSquare();
if (epSquare >= 0) {
int mEpSquare = mirrorSquareColor(epSquare);
ret.setEpSquare(mEpSquare);
}
ret.halfMoveClock = pos.halfMoveClock;
ret.fullMoveCounter = pos.fullMoveCounter;
return ret;
}
private final static Move mirrorMoveColor(Move m) {
if (m == null) return null;
Move ret = new Move(m);
ret.from = mirrorSquareColor(m.from);
ret.to = mirrorSquareColor(m.to);
ret.promoteTo = mirrorPieceColor(m.promoteTo);
return ret;
}
private final static int mirrorSquareLeftRight(int sq) {
int x = 7 - Position.getX(sq);
int y = Position.getY(sq);
return Position.getSquare(x, y);
}
private final static Position mirrorPosLeftRight(Position pos) {
Position ret = new Position(pos);
for (int sq = 0; sq < 64; sq++) {
int mSq = mirrorSquareLeftRight(sq);
int piece = pos.getPiece(sq);
ret.setPiece(mSq, piece);
}
int epSquare = pos.getEpSquare();
if (epSquare >= 0) {
int mEpSquare = mirrorSquareLeftRight(epSquare);
ret.setEpSquare(mEpSquare);
}
ret.halfMoveClock = pos.halfMoveClock;
ret.fullMoveCounter = pos.fullMoveCounter;
return ret;
}
private final static Move mirrorMoveLeftRight(Move m) {
if (m == null) return null;
Move ret = new Move(m);
ret.from = mirrorSquareLeftRight(m.from);
ret.to = mirrorSquareLeftRight(m.to);
ret.promoteTo = m.promoteTo;
return ret;
}
}

View File

@@ -0,0 +1,267 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.Pair;
import org.petero.droidfish.gamelogic.UndoInfo;
import chess.Piece;
/**
* Implements an opening book.
* @author petero
*/
public final class DroidBook {
static class BookEntry {
Move move;
double weight;
BookEntry(Move move) {
this.move = move;
weight = 1;
}
@Override
public String toString() {
return TextIO.moveToUCIString(move) + " (" + weight + ")";
}
}
private Random rndGen = new SecureRandom();
private IOpeningBook externalBook = new NullBook();
private IOpeningBook internalBook = new InternalBook();
private BookOptions options = null;
private static final DroidBook INSTANCE = new DroidBook();
public static DroidBook getInstance() {
return INSTANCE;
}
private DroidBook() {
rndGen.setSeed(System.currentTimeMillis());
}
public final void setOptions(BookOptions options) {
this.options = options;
if (CtgBook.canHandle(options))
externalBook = new CtgBook();
else if (PolyglotBook.canHandle(options))
externalBook = new PolyglotBook();
else
externalBook = new NullBook();
externalBook.setOptions(options);
internalBook.setOptions(options);
}
/** Return a random book move for a position, or null if out of book. */
public final Move getBookMove(Position pos) {
if ((options != null) && (pos.fullMoveCounter > options.maxLength))
return null;
List<BookEntry> bookMoves = getBook().getBookEntries(pos);
if (bookMoves == null)
return null;
ArrayList<Move> legalMoves = new MoveGen().pseudoLegalMoves(pos);
legalMoves = MoveGen.removeIllegal(pos, legalMoves);
double sum = 0;
final int nMoves = bookMoves.size();
for (int i = 0; i < nMoves; i++) {
BookEntry be = bookMoves.get(i);
if (!legalMoves.contains(be.move)) {
// If an illegal move was found, it means there was a hash collision,
// or a corrupt external book file.
return null;
}
sum += scaleWeight(bookMoves.get(i).weight);
}
if (sum <= 0) {
return null;
}
double rnd = rndGen.nextDouble() * sum;
sum = 0;
for (int i = 0; i < nMoves; i++) {
sum += scaleWeight(bookMoves.get(i).weight);
if (rnd < sum)
return bookMoves.get(i).move;
}
return bookMoves.get(nMoves-1).move;
}
private final double scaleWeight(double w) {
if (w <= 0)
return 0;
if (options == null)
return w;
return Math.pow(w, Math.exp(-options.random));
}
final private IOpeningBook getBook() {
if (externalBook.enabled()) {
return externalBook;
} else {
return internalBook;
}
}
/** Return a string describing all book moves. */
public final Pair<String,ArrayList<Move>> getAllBookMoves(Position pos) {
StringBuilder ret = new StringBuilder();
ArrayList<Move> bookMoveList = new ArrayList<Move>();
List<BookEntry> bookMoves = getBook().getBookEntries(pos);
// Check legality
if (bookMoves != null) {
ArrayList<Move> legalMoves = new MoveGen().pseudoLegalMoves(pos);
legalMoves = MoveGen.removeIllegal(pos, legalMoves);
for (int i = 0; i < bookMoves.size(); i++) {
BookEntry be = bookMoves.get(i);
if (!legalMoves.contains(be.move)) {
bookMoves = null;
break;
}
}
}
if (bookMoves != null) {
Collections.sort(bookMoves, new Comparator<BookEntry>() {
public int compare(BookEntry arg0, BookEntry arg1) {
double wd = arg1.weight - arg0.weight;
if (wd != 0)
return (wd > 0) ? 1 : -1;
String str0 = TextIO.moveToUCIString(arg0.move);
String str1 = TextIO.moveToUCIString(arg1.move);
return str0.compareTo(str1);
}});
double totalWeight = 0;
for (BookEntry be : bookMoves)
totalWeight += scaleWeight(be.weight);
if (totalWeight <= 0) totalWeight = 1;
for (BookEntry be : bookMoves) {
Move m = be.move;
bookMoveList.add(m);
String moveStr = TextIO.moveToString(pos, m, false);
ret.append(moveStr);
ret.append(':');
int percent = (int)Math.round(scaleWeight(be.weight) * 100 / totalWeight);
ret.append(percent);
ret.append(' ');
}
}
return new Pair<String, ArrayList<Move>>(ret.toString(), bookMoveList);
}
/** Creates the book.bin file. */
public static void main(String[] args) throws IOException {
List<Byte> binBook = createBinBook();
FileOutputStream out = new FileOutputStream("../src/book.bin");
int bookLen = binBook.size();
byte[] binBookA = new byte[bookLen];
for (int i = 0; i < bookLen; i++)
binBookA[i] = binBook.get(i);
out.write(binBookA);
out.close();
}
public static List<Byte> createBinBook() {
List<Byte> binBook = new ArrayList<Byte>(0);
try {
InputStream inStream = new Object().getClass().getResourceAsStream("/book.txt");
InputStreamReader inFile = new InputStreamReader(inStream);
BufferedReader inBuf = new BufferedReader(inFile, 8192);
LineNumberReader lnr = new LineNumberReader(inBuf);
String line;
while ((line = lnr.readLine()) != null) {
if (line.startsWith("#") || (line.length() == 0)) {
continue;
}
if (!addBookLine(line, binBook)) {
System.out.printf("Book parse error, line:%d\n", lnr.getLineNumber());
throw new RuntimeException();
}
// System.out.printf("no:%d line:%s%n", lnr.getLineNumber(), line);
}
lnr.close();
} catch (ChessParseError ex) {
throw new RuntimeException();
} catch (IOException ex) {
System.out.println("Can't read opening book resource");
throw new RuntimeException();
}
return binBook;
}
/** Add a sequence of moves, starting from the initial position, to the binary opening book. */
private static boolean addBookLine(String line, List<Byte> binBook) throws ChessParseError {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
UndoInfo ui = new UndoInfo();
String[] strMoves = line.split(" ");
for (String strMove : strMoves) {
// System.out.printf("Adding move:%s\n", strMove);
int bad = 0;
if (strMove.endsWith("?")) {
strMove = strMove.substring(0, strMove.length() - 1);
bad = 1;
}
Move m = TextIO.stringToMove(pos, strMove);
if (m == null) {
return false;
}
int prom = pieceToProm(m.promoteTo);
int val = m.from + (m.to << 6) + (prom << 12) + (bad << 15);
binBook.add((byte)(val >> 8));
binBook.add((byte)(val & 255));
pos.makeMove(m, ui);
}
binBook.add((byte)0);
binBook.add((byte)0);
return true;
}
private static int pieceToProm(int p) {
switch (p) {
case Piece.WQUEEN: case Piece.BQUEEN:
return 1;
case Piece.WROOK: case Piece.BROOK:
return 2;
case Piece.WBISHOP: case Piece.BBISHOP:
return 3;
case Piece.WKNIGHT: case Piece.BKNIGHT:
return 4;
default:
return 0;
}
}
}

View File

@@ -0,0 +1,597 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Pair;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.SearchListener;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.UndoInfo;
import org.petero.droidfish.gamelogic.SearchListener.PvInfo;
/**
* A computer algorithm player.
* @author petero
*/
public class DroidComputerPlayer {
private static String engineName = "";
private static UCIEngine uciEngine = null;
private SearchListener listener;
private DroidBook book;
private boolean newGame = false;
private String engine = "";
private int maxPV = 1; // >1 if multiPV mode is supported
private static int numCPUs = 1;
private boolean havePonderHit = false;
public DroidComputerPlayer(String engine) {
this.engine = engine;
if (uciEngine != null) {
stopSearch();
} else {
startEngine();
}
listener = null;
book = DroidBook.getInstance();
}
private final synchronized void startEngine() {
boolean useCuckoo = engine.equals("cuckoochess");
if (uciEngine == null) {
if (useCuckoo) {
uciEngine = new CuckooChessEngine();
} else {
uciEngine = new NativePipedProcess();
}
uciEngine.initialize();
uciEngine.writeLineToEngine("uci");
readUCIOptions();
int nThreads = getNumCPUs();
if (nThreads > 8) nThreads = 8;
numCPUs = nThreads;
if (!useCuckoo)
uciEngine.setOption("Hash", 16);
uciEngine.setOption("Ponder", false);
uciEngine.writeLineToEngine("ucinewgame");
syncReady();
}
}
public final synchronized void setEngineStrength(String engine, int strength) {
if (!engine.equals(this.engine)) {
shutdownEngine();
this.engine = engine;
startEngine();
}
if (uciEngine != null)
uciEngine.setStrength(strength);
}
public final synchronized int getMaxPV() {
return maxPV;
}
public final synchronized void setNumPV(int numPV) {
if ((uciEngine != null) && (maxPV > 1)) {
int num = Math.min(maxPV, numPV);
uciEngine.setOption("MultiPV", num);
}
}
private static int getNumCPUs() {
int nCPUsFromProc = 1;
try {
FileReader fr = new FileReader("/proc/stat");
BufferedReader inBuf = new BufferedReader(fr, 8192);
String line;
int nCPUs = 0;
while ((line = inBuf.readLine()) != null) {
if ((line.length() >= 4) && line.startsWith("cpu") && Character.isDigit(line.charAt(3)))
nCPUs++;
}
inBuf.close();
if (nCPUs < 1) nCPUs = 1;
nCPUsFromProc = nCPUs;
} catch (IOException e) {
}
int nCPUsFromOS = NativePipedProcess.getNPhysicalProcessors();
return Math.max(nCPUsFromProc, nCPUsFromOS);
}
public final void setListener(SearchListener listener) {
this.listener = listener;
}
public final void setBookOptions(BookOptions options) {
book.setOptions(options);
}
private void readUCIOptions() {
int timeout = 1000;
maxPV = 1;
while (true) {
String s = uciEngine.readLineFromEngine(timeout);
String[] tokens = tokenize(s);
if (tokens[0].equals("uciok"))
break;
else if (tokens[0].equals("id")) {
if (tokens[1].equals("name")) {
engineName = "";
for (int i = 2; i < tokens.length; i++) {
if (engineName.length() > 0)
engineName += " ";
engineName += tokens[i];
}
}
} else if ((tokens.length > 2) && tokens[2].toLowerCase().equals("multipv")) {
try {
for (int i = 3; i < tokens.length; i++) {
if (tokens[i].equals("max") && (i+1 < tokens.length)) {
maxPV = Math.max(maxPV, Integer.parseInt(tokens[i+1]));
break;
}
}
} catch (NumberFormatException nfe) { }
}
}
}
public synchronized String getEngineName() {
if (uciEngine != null)
return engineName + uciEngine.addStrengthToName();
else
return engineName;
}
/** Convert a string to tokens by splitting at whitespace characters. */
private final String[] tokenize(String cmdLine) {
cmdLine = cmdLine.trim();
return cmdLine.split("\\s+");
}
private final void syncReady() {
uciEngine.writeLineToEngine("isready");
while (true) {
String s = uciEngine.readLineFromEngine(1000);
if (s.equals("readyok"))
break;
}
}
/** Clear transposition table. */
public final void clearTT() {
newGame = true;
}
public final void maybeNewGame() {
if (newGame) {
newGame = false;
if (uciEngine != null) {
uciEngine.writeLineToEngine("ucinewgame");
syncReady();
}
}
}
/** Stop the engine process. */
public final synchronized void shutdownEngine() {
if (uciEngine != null) {
uciEngine.shutDown();
uciEngine = null;
}
}
public final synchronized void ponderHit(Position pos, Move ponderMove) {
havePonderHit = true;
uciEngine.writeLineToEngine("ponderhit");
pvModified = true;
notifyGUI(pos, ponderMove);
}
/**
* Do a search and return a command from the computer player.
* The command can be a valid move string, in which case the move is played
* and the turn goes over to the other player. The command can also be a special
* command, such as "draw" and "resign".
* @param pos An earlier position from the game
* @param mList List of moves to go from the earlier position to the current position.
* This list makes it possible for the computer to correctly handle draw
* by repetition/50 moves.
* @param ponderEnabled True if pondering is enabled in the GUI. Can affect time management.
* @param ponderMove Move to ponder, or null for non-ponder search.
* @param engineThreads Number of engine threads to use, if supported by engine.
* @return The computer player command, and the next ponder move.
*/
public final Pair<String,Move> doSearch(Position prevPos, ArrayList<Move> mList,
Position currPos, boolean drawOffer,
int wTime, int bTime, int inc, int movesToGo,
boolean ponderEnabled, Move ponderMove,
int engineThreads) {
if (listener != null)
listener.notifyBookInfo("", null);
if (ponderMove != null)
mList.add(ponderMove);
havePonderHit = false;
// Set up for draw detection
long[] posHashList = new long[mList.size()+1];
int posHashListSize = 0;
Position p = new Position(prevPos);
UndoInfo ui = new UndoInfo();
for (int i = 0; i < mList.size(); i++) {
posHashList[posHashListSize++] = p.zobristHash();
p.makeMove(mList.get(i), ui);
}
if (ponderMove == null) {
// If we have a book move, play it.
Move bookMove = book.getBookMove(currPos);
if (bookMove != null) {
if (canClaimDraw(currPos, posHashList, posHashListSize, bookMove) == "") {
return new Pair<String,Move>(TextIO.moveToString(currPos, bookMove, false), null);
}
}
// If only one legal move, play it without searching
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
moves = MoveGen.removeIllegal(currPos, moves);
if (moves.size() == 0) {
return new Pair<String,Move>("", null); // User set up a position where computer has no valid moves.
}
if (moves.size() == 1) {
Move bestMove = moves.get(0);
if (canClaimDraw(currPos, posHashList, posHashListSize, bestMove) == "") {
return new Pair<String,Move>(TextIO.moveToUCIString(bestMove), null);
}
}
}
StringBuilder posStr = new StringBuilder();
posStr.append("position fen ");
posStr.append(TextIO.toFEN(prevPos));
int nMoves = mList.size();
if (nMoves > 0) {
posStr.append(" moves");
for (int i = 0; i < nMoves; i++) {
posStr.append(" ");
posStr.append(TextIO.moveToUCIString(mList.get(i)));
}
}
maybeNewGame();
uciEngine.setOption("Ponder", ponderEnabled);
uciEngine.setOption("UCI_AnalyseMode", false);
uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs);
uciEngine.writeLineToEngine(posStr.toString());
if (wTime < 1) wTime = 1;
if (bTime < 1) bTime = 1;
StringBuilder goStr = new StringBuilder(96);
goStr.append(String.format("go wtime %d btime %d", wTime, bTime));
if (inc > 0)
goStr.append(String.format(" winc %d binc %d", inc, inc));
if (movesToGo > 0)
goStr.append(String.format(" movestogo %d", movesToGo));
if (ponderMove != null)
goStr.append(" ponder");
uciEngine.writeLineToEngine(goStr.toString());
Pair<String,String> pair = monitorEngine(currPos, ponderMove);
String bestMove = pair.first;
Move nextPonderMove = TextIO.UCIstringToMove(pair.second);
// Claim draw if appropriate
if (statScore <= 0) {
String drawClaim = canClaimDraw(currPos, posHashList, posHashListSize, TextIO.UCIstringToMove(bestMove));
if (drawClaim != "")
bestMove = drawClaim;
}
// Accept draw offer if engine is losing
if (drawOffer && !statIsMate && (statScore <= -300)) {
bestMove = "draw accept";
}
return new Pair<String,Move>(bestMove, nextPonderMove);
}
/** Wait for engine to respond with bestMove and ponderMove.
* While waiting, monitor and report search info. */
private final Pair<String,String> monitorEngine(Position pos, Move ponderMove) {
// Monitor engine response
clearInfo();
boolean stopSent = false;
while (true) {
int timeout = 2000;
while (true) {
UCIEngine uci = uciEngine;
if (uci == null)
break;
if (shouldStop && !stopSent) {
uci.writeLineToEngine("stop");
stopSent = true;
}
String s = uci.readLineFromEngine(timeout);
if (s.length() == 0)
break;
String[] tokens = tokenize(s);
if (tokens[0].equals("info")) {
parseInfoCmd(tokens, ponderMove);
} else if (tokens[0].equals("bestmove")) {
String bestMove = tokens[1];
String nextPonderMove = "";
if ((tokens.length >= 4) && (tokens[2].equals("ponder")))
nextPonderMove = tokens[3];
return new Pair<String,String>(bestMove, nextPonderMove);
}
timeout = 0;
}
notifyGUI(pos, ponderMove);
try {
Thread.sleep(100); // 10 GUI updates per second is enough
} catch (InterruptedException e) {
}
}
}
public final Pair<String, ArrayList<Move>> getBookHints(Position pos) {
Pair<String, ArrayList<Move>> bi = book.getAllBookMoves(pos);
return new Pair<String, ArrayList<Move>>(bi.first, bi.second);
}
public boolean shouldStop = false;
public final void analyze(Position prevPos, ArrayList<Move> mList, Position currPos,
boolean drawOffer, int engineThreads) {
if (shouldStop)
return;
if (listener != null) {
Pair<String, ArrayList<Move>> bi = getBookHints(currPos);
listener.notifyBookInfo(bi.first, bi.second);
}
// If no legal moves, there is nothing to analyze
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
moves = MoveGen.removeIllegal(currPos, moves);
if (moves.size() == 0)
return;
StringBuilder posStr = new StringBuilder();
posStr.append("position fen ");
posStr.append(TextIO.toFEN(prevPos));
int nMoves = mList.size();
if (nMoves > 0) {
posStr.append(" moves");
for (int i = 0; i < nMoves; i++) {
posStr.append(" ");
posStr.append(TextIO.moveToUCIString(mList.get(i)));
}
}
maybeNewGame();
uciEngine.writeLineToEngine(posStr.toString());
uciEngine.setOption("UCI_AnalyseMode", true);
uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs);
String goStr = String.format("go infinite");
uciEngine.writeLineToEngine(goStr);
monitorEngine(currPos, null);
}
/** Check if a draw claim is allowed, possibly after playing "move".
* @param move The move that may have to be made before claiming draw.
* @return The draw string that claims the draw, or empty string if draw claim not valid.
*/
private String canClaimDraw(Position pos, long[] posHashList, int posHashListSize, Move move) {
String drawStr = "";
if (canClaimDraw50(pos)) {
drawStr = "draw 50";
} else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
drawStr = "draw rep";
} else if (move != null) {
String strMove = TextIO.moveToString(pos, move, false);
posHashList[posHashListSize++] = pos.zobristHash();
UndoInfo ui = new UndoInfo();
pos.makeMove(move, ui);
if (canClaimDraw50(pos)) {
drawStr = "draw 50 " + strMove;
} else if (canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
drawStr = "draw rep " + strMove;
}
pos.unMakeMove(move, ui);
}
return drawStr;
}
private final static boolean canClaimDraw50(Position pos) {
return (pos.halfMoveClock >= 100);
}
private final static boolean canClaimDrawRep(Position pos, long[] posHashList, int posHashListSize, int posHashFirstNew) {
int reps = 0;
for (int i = posHashListSize - 4; i >= 0; i -= 2) {
if (pos.zobristHash() == posHashList[i]) {
reps++;
if (i >= posHashFirstNew) {
reps++;
break;
}
}
}
return (reps >= 2);
}
private int statCurrDepth = 0;
private int statPVDepth = 0;
private int statScore = 0;
private boolean statIsMate = false;
private boolean statUpperBound = false;
private boolean statLowerBound = false;
private int statTime = 0;
private int statNodes = 0;
private int statNps = 0;
private int pvNum = 0;
private ArrayList<String> statPV = new ArrayList<String>();
private String statCurrMove = "";
private int statCurrMoveNr = 0;
private ArrayList<PvInfo> statPvInfo = new ArrayList<PvInfo>();
private boolean depthModified = false;
private boolean currMoveModified = false;
private boolean pvModified = false;
private boolean statsModified = false;
private final void clearInfo() {
depthModified = false;
currMoveModified = false;
pvModified = false;
statsModified = false;
statPvInfo.clear();
}
private final void parseInfoCmd(String[] tokens, Move ponderMove) {
try {
boolean havePvData = false;
int nTokens = tokens.length;
int i = 1;
while (i < nTokens - 1) {
String is = tokens[i++];
if (is.equals("depth")) {
statCurrDepth = Integer.parseInt(tokens[i++]);
depthModified = true;
} else if (is.equals("currmove")) {
statCurrMove = tokens[i++];
currMoveModified = true;
} else if (is.equals("currmovenumber")) {
statCurrMoveNr = Integer.parseInt(tokens[i++]);
currMoveModified = true;
} else if (is.equals("time")) {
statTime = Integer.parseInt(tokens[i++]);
statsModified = true;
} else if (is.equals("nodes")) {
statNodes = Integer.parseInt(tokens[i++]);
statsModified = true;
} else if (is.equals("nps")) {
statNps = Integer.parseInt(tokens[i++]);
statsModified = true;
} else if (is.equals("multipv")) {
pvNum = Integer.parseInt(tokens[i++]) - 1;
if (pvNum < 0) pvNum = 0;
if (pvNum > 255) pvNum = 255;
pvModified = true;
} else if (is.equals("pv")) {
statPV.clear();
while (i < nTokens)
statPV.add(tokens[i++]);
pvModified = true;
havePvData = true;
statPVDepth = statCurrDepth;
} else if (is.equals("score")) {
statIsMate = tokens[i++].equals("mate");
statScore = Integer.parseInt(tokens[i++]);
statUpperBound = false;
statLowerBound = false;
if (tokens[i].equals("upperbound")) {
statUpperBound = true;
i++;
} else if (tokens[i].equals("lowerbound")) {
statLowerBound = true;
i++;
}
pvModified = true;
}
}
if (havePvData) {
while (statPvInfo.size() < pvNum)
statPvInfo.add(new PvInfo(0, 0, 0, 0, 0, false, false, false, new ArrayList<Move>()));
while (statPvInfo.size() <= pvNum)
statPvInfo.add(null);
ArrayList<Move> moves = new ArrayList<Move>();
if (ponderMove != null)
moves.add(ponderMove);
int nMoves = statPV.size();
for (i = 0; i < nMoves; i++)
moves.add(TextIO.UCIstringToMove(statPV.get(i)));
statPvInfo.set(pvNum, new PvInfo(statPVDepth, statScore, statTime, statNodes, statNps, statIsMate,
statUpperBound, statLowerBound, moves));
}
} catch (NumberFormatException nfe) {
// Ignore
} catch (ArrayIndexOutOfBoundsException aioob) {
// Ignore
}
}
/** Notify GUI about search statistics. */
private final synchronized void notifyGUI(Position pos, Move ponderMove) {
if (listener == null)
return;
if (depthModified) {
listener.notifyDepth(statCurrDepth);
depthModified = false;
}
if (currMoveModified) {
Move m = TextIO.UCIstringToMove(statCurrMove);
listener.notifyCurrMove(pos, m, statCurrMoveNr);
currMoveModified = false;
}
if (pvModified) {
Position notifyPos = pos;
ArrayList<PvInfo> pvInfo = statPvInfo;
boolean isPonder = ponderMove != null;
if (isPonder && havePonderHit) {
isPonder = false;
UndoInfo ui = new UndoInfo();
notifyPos = new Position(pos);
notifyPos.makeMove(ponderMove, ui);
pvInfo = new ArrayList<PvInfo>(statPvInfo.size());
for (int i = 0; i < statPvInfo.size(); i++) {
PvInfo pvi = new PvInfo(statPvInfo.get(i));
pvi.removeFirstMove();
pvInfo.add(pvi);
}
}
listener.notifyPV(notifyPos, pvInfo, isPonder);
pvModified = false;
}
if (statsModified) {
listener.notifyStats(statNodes, statNps, statTime);
statsModified = false;
}
}
public final synchronized void stopSearch() {
shouldStop = true;
if (uciEngine != null)
uciEngine.writeLineToEngine("stop");
havePonderHit = false;
}
}

View File

@@ -0,0 +1,36 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.util.List;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.engine.DroidBook.BookEntry;
import org.petero.droidfish.gamelogic.Position;
interface IOpeningBook {
/** Return true if book is currently enabled. */
boolean enabled();
/** Set book options, including filename. */
void setOptions(BookOptions options);
/** Get all book entries for a position. */
List<BookEntry> getBookEntries(Position pos);
}

View File

@@ -0,0 +1,159 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.engine.DroidBook.BookEntry;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.UndoInfo;
public final class InternalBook implements IOpeningBook {
private static Map<Long, List<BookEntry>> bookMap;
private static int numBookMoves = -1;
InternalBook() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
initInternalBook();
}
});
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
static boolean canHandle(String filename) {
return filename.length() == 0;
}
@Override
public boolean enabled() {
return true;
}
@Override
public List<BookEntry> getBookEntries(Position pos) {
initInternalBook();
List<BookEntry> ents = bookMap.get(pos.zobristHash());
if (ents == null)
return null;
List<BookEntry> ret = new ArrayList<BookEntry>();
for (BookEntry be : ents) {
BookEntry be2 = new BookEntry(be.move);
be2.weight = Math.sqrt(be.weight) * 100 + 1;
ret.add(be2);
}
return ret;
}
@Override
public void setOptions(BookOptions options) {
}
private synchronized final void initInternalBook() {
if (numBookMoves >= 0)
return;
// long t0 = System.currentTimeMillis();
bookMap = new HashMap<Long, List<BookEntry>>();
numBookMoves = 0;
try {
InputStream inStream = getClass().getResourceAsStream("/book.bin");
List<Byte> buf = new ArrayList<Byte>(8192);
byte[] tmpBuf = new byte[1024];
while (true) {
int len = inStream.read(tmpBuf);
if (len <= 0) break;
for (int i = 0; i < len; i++)
buf.add(tmpBuf[i]);
}
inStream.close();
Position startPos = TextIO.readFEN(TextIO.startPosFEN);
Position pos = new Position(startPos);
UndoInfo ui = new UndoInfo();
int len = buf.size();
for (int i = 0; i < len; i += 2) {
int b0 = buf.get(i); if (b0 < 0) b0 += 256;
int b1 = buf.get(i+1); if (b1 < 0) b1 += 256;
int move = (b0 << 8) + b1;
if (move == 0) {
pos = new Position(startPos);
} else {
boolean bad = ((move >> 15) & 1) != 0;
int prom = (move >> 12) & 7;
Move m = new Move(move & 63, (move >> 6) & 63,
promToPiece(prom, pos.whiteMove));
if (!bad)
addToBook(pos, m);
pos.makeMove(m, ui);
}
}
} catch (ChessParseError ex) {
throw new RuntimeException();
} catch (IOException ex) {
System.out.println("Can't read opening book resource");
throw new RuntimeException();
}
/* {
long t1 = System.currentTimeMillis();
System.out.printf("Book moves:%d (parse time:%.3f)%n", numBookMoves,
(t1 - t0) / 1000.0);
} */
}
/** Add a move to a position in the opening book. */
private final void addToBook(Position pos, Move moveToAdd) {
List<BookEntry> ent = bookMap.get(pos.zobristHash());
if (ent == null) {
ent = new ArrayList<BookEntry>();
bookMap.put(pos.zobristHash(), ent);
}
for (int i = 0; i < ent.size(); i++) {
BookEntry be = ent.get(i);
if (be.move.equals(moveToAdd)) {
be.weight++;
return;
}
}
BookEntry be = new BookEntry(moveToAdd);
ent.add(be);
numBookMoves++;
}
private static int promToPiece(int prom, boolean whiteMove) {
switch (prom) {
case 1: return whiteMove ? Piece.WQUEEN : Piece.BQUEEN;
case 2: return whiteMove ? Piece.WROOK : Piece.BROOK;
case 3: return whiteMove ? Piece.WBISHOP : Piece.BBISHOP;
case 4: return whiteMove ? Piece.WKNIGHT : Piece.BKNIGHT;
default: return Piece.EMPTY;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
public class NativePipedProcess extends UCIEngineBase {
static {
System.loadLibrary("jni");
}
@Override
public void setStrength(int strength) {
this.strength = strength;
setOption("Skill Level", strength/50);
}
/**
* Read a line from the process.
* @param timeoutMillis Maximum time to wait for data
* @return The line, without terminating newline characters,
* or empty string if no data available,
* or null if I/O error.
*/
@Override
public final String readLineFromEngine(int timeoutMillis) {
String ret = readFromProcess(timeoutMillis);
if (ret == null)
return null;
if (ret.length() > 0) {
// System.out.printf("Engine -> GUI: %s\n", ret);
}
return ret;
}
/** Write a line to the process. \n will be added automatically. */
@Override
public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data);
writeToProcess(data + "\n");
}
/** Start the child process. */
protected final native void startProcess();
/**
* Read a line of data from the process.
* Return as soon as there is a full line of data to return,
* or when timeoutMillis milliseconds have passed.
*/
private final native String readFromProcess(int timeoutMillis);
/** Write data to the process. */
private final native void writeToProcess(String data);
/** Return number of physical processors, i.e. hyper-threading ignored. */
final static native int getNPhysicalProcessors();
}

View File

@@ -0,0 +1,42 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.util.List;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.engine.DroidBook.BookEntry;
import org.petero.droidfish.gamelogic.Position;
public class NullBook implements IOpeningBook {
@Override
public boolean enabled() {
return false;
}
@Override
public List<BookEntry> getBookEntries(Position pos) {
return null;
}
@Override
public void setOptions(BookOptions options) {
}
}

View File

@@ -0,0 +1,420 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.engine.DroidBook.BookEntry;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
public class PolyglotBook implements IOpeningBook {
private File bookFile;
PolyglotBook() {
bookFile = new File("");
}
@Override
public final void setOptions(BookOptions options) {
bookFile = new File(options.filename);
}
/** Compute a polyglot hash key corresponding to a position. */
static long getHashKey(Position pos) {
// Pieces
long key = 0;
for (int sq = 0; sq < 64; sq++) {
int pVal = -1;
switch (pos.getPiece(sq)) {
case Piece.BPAWN: pVal = 0; break;
case Piece.WPAWN: pVal = 1; break;
case Piece.BKNIGHT: pVal = 2; break;
case Piece.WKNIGHT: pVal = 3; break;
case Piece.BBISHOP: pVal = 4; break;
case Piece.WBISHOP: pVal = 5; break;
case Piece.BROOK: pVal = 6; break;
case Piece.WROOK: pVal = 7; break;
case Piece.BQUEEN: pVal = 8; break;
case Piece.WQUEEN: pVal = 9; break;
case Piece.BKING: pVal = 10; break;
case Piece.WKING: pVal = 11; break;
}
if (pVal >= 0)
key ^= hashRandoms[64 * pVal + sq];
}
// Castle flags
if (pos.h1Castle()) key ^= hashRandoms[768 + 0];
if (pos.a1Castle()) key ^= hashRandoms[768 + 1];
if (pos.h8Castle()) key ^= hashRandoms[768 + 2];
if (pos.a8Castle()) key ^= hashRandoms[768 + 3];
// EP file
if (pos.getEpSquare() >= 0) {
int epFile = Position.getX(pos.getEpSquare());
key ^= hashRandoms[772 + epFile];
}
// Side to move
if (pos.whiteMove)
key ^= hashRandoms[780];
return key;
}
static private long hashRandoms[] = {
0x9D39247E33776D41L, 0x2AF7398005AAA5C7L, 0x44DB015024623547L, 0x9C15F73E62A76AE2L,
0x75834465489C0C89L, 0x3290AC3A203001BFL, 0x0FBBAD1F61042279L, 0xE83A908FF2FB60CAL,
0x0D7E765D58755C10L, 0x1A083822CEAFE02DL, 0x9605D5F0E25EC3B0L, 0xD021FF5CD13A2ED5L,
0x40BDF15D4A672E32L, 0x011355146FD56395L, 0x5DB4832046F3D9E5L, 0x239F8B2D7FF719CCL,
0x05D1A1AE85B49AA1L, 0x679F848F6E8FC971L, 0x7449BBFF801FED0BL, 0x7D11CDB1C3B7ADF0L,
0x82C7709E781EB7CCL, 0xF3218F1C9510786CL, 0x331478F3AF51BBE6L, 0x4BB38DE5E7219443L,
0xAA649C6EBCFD50FCL, 0x8DBD98A352AFD40BL, 0x87D2074B81D79217L, 0x19F3C751D3E92AE1L,
0xB4AB30F062B19ABFL, 0x7B0500AC42047AC4L, 0xC9452CA81A09D85DL, 0x24AA6C514DA27500L,
0x4C9F34427501B447L, 0x14A68FD73C910841L, 0xA71B9B83461CBD93L, 0x03488B95B0F1850FL,
0x637B2B34FF93C040L, 0x09D1BC9A3DD90A94L, 0x3575668334A1DD3BL, 0x735E2B97A4C45A23L,
0x18727070F1BD400BL, 0x1FCBACD259BF02E7L, 0xD310A7C2CE9B6555L, 0xBF983FE0FE5D8244L,
0x9F74D14F7454A824L, 0x51EBDC4AB9BA3035L, 0x5C82C505DB9AB0FAL, 0xFCF7FE8A3430B241L,
0x3253A729B9BA3DDEL, 0x8C74C368081B3075L, 0xB9BC6C87167C33E7L, 0x7EF48F2B83024E20L,
0x11D505D4C351BD7FL, 0x6568FCA92C76A243L, 0x4DE0B0F40F32A7B8L, 0x96D693460CC37E5DL,
0x42E240CB63689F2FL, 0x6D2BDCDAE2919661L, 0x42880B0236E4D951L, 0x5F0F4A5898171BB6L,
0x39F890F579F92F88L, 0x93C5B5F47356388BL, 0x63DC359D8D231B78L, 0xEC16CA8AEA98AD76L,
0x5355F900C2A82DC7L, 0x07FB9F855A997142L, 0x5093417AA8A7ED5EL, 0x7BCBC38DA25A7F3CL,
0x19FC8A768CF4B6D4L, 0x637A7780DECFC0D9L, 0x8249A47AEE0E41F7L, 0x79AD695501E7D1E8L,
0x14ACBAF4777D5776L, 0xF145B6BECCDEA195L, 0xDABF2AC8201752FCL, 0x24C3C94DF9C8D3F6L,
0xBB6E2924F03912EAL, 0x0CE26C0B95C980D9L, 0xA49CD132BFBF7CC4L, 0xE99D662AF4243939L,
0x27E6AD7891165C3FL, 0x8535F040B9744FF1L, 0x54B3F4FA5F40D873L, 0x72B12C32127FED2BL,
0xEE954D3C7B411F47L, 0x9A85AC909A24EAA1L, 0x70AC4CD9F04F21F5L, 0xF9B89D3E99A075C2L,
0x87B3E2B2B5C907B1L, 0xA366E5B8C54F48B8L, 0xAE4A9346CC3F7CF2L, 0x1920C04D47267BBDL,
0x87BF02C6B49E2AE9L, 0x092237AC237F3859L, 0xFF07F64EF8ED14D0L, 0x8DE8DCA9F03CC54EL,
0x9C1633264DB49C89L, 0xB3F22C3D0B0B38EDL, 0x390E5FB44D01144BL, 0x5BFEA5B4712768E9L,
0x1E1032911FA78984L, 0x9A74ACB964E78CB3L, 0x4F80F7A035DAFB04L, 0x6304D09A0B3738C4L,
0x2171E64683023A08L, 0x5B9B63EB9CEFF80CL, 0x506AACF489889342L, 0x1881AFC9A3A701D6L,
0x6503080440750644L, 0xDFD395339CDBF4A7L, 0xEF927DBCF00C20F2L, 0x7B32F7D1E03680ECL,
0xB9FD7620E7316243L, 0x05A7E8A57DB91B77L, 0xB5889C6E15630A75L, 0x4A750A09CE9573F7L,
0xCF464CEC899A2F8AL, 0xF538639CE705B824L, 0x3C79A0FF5580EF7FL, 0xEDE6C87F8477609DL,
0x799E81F05BC93F31L, 0x86536B8CF3428A8CL, 0x97D7374C60087B73L, 0xA246637CFF328532L,
0x043FCAE60CC0EBA0L, 0x920E449535DD359EL, 0x70EB093B15B290CCL, 0x73A1921916591CBDL,
0x56436C9FE1A1AA8DL, 0xEFAC4B70633B8F81L, 0xBB215798D45DF7AFL, 0x45F20042F24F1768L,
0x930F80F4E8EB7462L, 0xFF6712FFCFD75EA1L, 0xAE623FD67468AA70L, 0xDD2C5BC84BC8D8FCL,
0x7EED120D54CF2DD9L, 0x22FE545401165F1CL, 0xC91800E98FB99929L, 0x808BD68E6AC10365L,
0xDEC468145B7605F6L, 0x1BEDE3A3AEF53302L, 0x43539603D6C55602L, 0xAA969B5C691CCB7AL,
0xA87832D392EFEE56L, 0x65942C7B3C7E11AEL, 0xDED2D633CAD004F6L, 0x21F08570F420E565L,
0xB415938D7DA94E3CL, 0x91B859E59ECB6350L, 0x10CFF333E0ED804AL, 0x28AED140BE0BB7DDL,
0xC5CC1D89724FA456L, 0x5648F680F11A2741L, 0x2D255069F0B7DAB3L, 0x9BC5A38EF729ABD4L,
0xEF2F054308F6A2BCL, 0xAF2042F5CC5C2858L, 0x480412BAB7F5BE2AL, 0xAEF3AF4A563DFE43L,
0x19AFE59AE451497FL, 0x52593803DFF1E840L, 0xF4F076E65F2CE6F0L, 0x11379625747D5AF3L,
0xBCE5D2248682C115L, 0x9DA4243DE836994FL, 0x066F70B33FE09017L, 0x4DC4DE189B671A1CL,
0x51039AB7712457C3L, 0xC07A3F80C31FB4B4L, 0xB46EE9C5E64A6E7CL, 0xB3819A42ABE61C87L,
0x21A007933A522A20L, 0x2DF16F761598AA4FL, 0x763C4A1371B368FDL, 0xF793C46702E086A0L,
0xD7288E012AEB8D31L, 0xDE336A2A4BC1C44BL, 0x0BF692B38D079F23L, 0x2C604A7A177326B3L,
0x4850E73E03EB6064L, 0xCFC447F1E53C8E1BL, 0xB05CA3F564268D99L, 0x9AE182C8BC9474E8L,
0xA4FC4BD4FC5558CAL, 0xE755178D58FC4E76L, 0x69B97DB1A4C03DFEL, 0xF9B5B7C4ACC67C96L,
0xFC6A82D64B8655FBL, 0x9C684CB6C4D24417L, 0x8EC97D2917456ED0L, 0x6703DF9D2924E97EL,
0xC547F57E42A7444EL, 0x78E37644E7CAD29EL, 0xFE9A44E9362F05FAL, 0x08BD35CC38336615L,
0x9315E5EB3A129ACEL, 0x94061B871E04DF75L, 0xDF1D9F9D784BA010L, 0x3BBA57B68871B59DL,
0xD2B7ADEEDED1F73FL, 0xF7A255D83BC373F8L, 0xD7F4F2448C0CEB81L, 0xD95BE88CD210FFA7L,
0x336F52F8FF4728E7L, 0xA74049DAC312AC71L, 0xA2F61BB6E437FDB5L, 0x4F2A5CB07F6A35B3L,
0x87D380BDA5BF7859L, 0x16B9F7E06C453A21L, 0x7BA2484C8A0FD54EL, 0xF3A678CAD9A2E38CL,
0x39B0BF7DDE437BA2L, 0xFCAF55C1BF8A4424L, 0x18FCF680573FA594L, 0x4C0563B89F495AC3L,
0x40E087931A00930DL, 0x8CFFA9412EB642C1L, 0x68CA39053261169FL, 0x7A1EE967D27579E2L,
0x9D1D60E5076F5B6FL, 0x3810E399B6F65BA2L, 0x32095B6D4AB5F9B1L, 0x35CAB62109DD038AL,
0xA90B24499FCFAFB1L, 0x77A225A07CC2C6BDL, 0x513E5E634C70E331L, 0x4361C0CA3F692F12L,
0xD941ACA44B20A45BL, 0x528F7C8602C5807BL, 0x52AB92BEB9613989L, 0x9D1DFA2EFC557F73L,
0x722FF175F572C348L, 0x1D1260A51107FE97L, 0x7A249A57EC0C9BA2L, 0x04208FE9E8F7F2D6L,
0x5A110C6058B920A0L, 0x0CD9A497658A5698L, 0x56FD23C8F9715A4CL, 0x284C847B9D887AAEL,
0x04FEABFBBDB619CBL, 0x742E1E651C60BA83L, 0x9A9632E65904AD3CL, 0x881B82A13B51B9E2L,
0x506E6744CD974924L, 0xB0183DB56FFC6A79L, 0x0ED9B915C66ED37EL, 0x5E11E86D5873D484L,
0xF678647E3519AC6EL, 0x1B85D488D0F20CC5L, 0xDAB9FE6525D89021L, 0x0D151D86ADB73615L,
0xA865A54EDCC0F019L, 0x93C42566AEF98FFBL, 0x99E7AFEABE000731L, 0x48CBFF086DDF285AL,
0x7F9B6AF1EBF78BAFL, 0x58627E1A149BBA21L, 0x2CD16E2ABD791E33L, 0xD363EFF5F0977996L,
0x0CE2A38C344A6EEDL, 0x1A804AADB9CFA741L, 0x907F30421D78C5DEL, 0x501F65EDB3034D07L,
0x37624AE5A48FA6E9L, 0x957BAF61700CFF4EL, 0x3A6C27934E31188AL, 0xD49503536ABCA345L,
0x088E049589C432E0L, 0xF943AEE7FEBF21B8L, 0x6C3B8E3E336139D3L, 0x364F6FFA464EE52EL,
0xD60F6DCEDC314222L, 0x56963B0DCA418FC0L, 0x16F50EDF91E513AFL, 0xEF1955914B609F93L,
0x565601C0364E3228L, 0xECB53939887E8175L, 0xBAC7A9A18531294BL, 0xB344C470397BBA52L,
0x65D34954DAF3CEBDL, 0xB4B81B3FA97511E2L, 0xB422061193D6F6A7L, 0x071582401C38434DL,
0x7A13F18BBEDC4FF5L, 0xBC4097B116C524D2L, 0x59B97885E2F2EA28L, 0x99170A5DC3115544L,
0x6F423357E7C6A9F9L, 0x325928EE6E6F8794L, 0xD0E4366228B03343L, 0x565C31F7DE89EA27L,
0x30F5611484119414L, 0xD873DB391292ED4FL, 0x7BD94E1D8E17DEBCL, 0xC7D9F16864A76E94L,
0x947AE053EE56E63CL, 0xC8C93882F9475F5FL, 0x3A9BF55BA91F81CAL, 0xD9A11FBB3D9808E4L,
0x0FD22063EDC29FCAL, 0xB3F256D8ACA0B0B9L, 0xB03031A8B4516E84L, 0x35DD37D5871448AFL,
0xE9F6082B05542E4EL, 0xEBFAFA33D7254B59L, 0x9255ABB50D532280L, 0xB9AB4CE57F2D34F3L,
0x693501D628297551L, 0xC62C58F97DD949BFL, 0xCD454F8F19C5126AL, 0xBBE83F4ECC2BDECBL,
0xDC842B7E2819E230L, 0xBA89142E007503B8L, 0xA3BC941D0A5061CBL, 0xE9F6760E32CD8021L,
0x09C7E552BC76492FL, 0x852F54934DA55CC9L, 0x8107FCCF064FCF56L, 0x098954D51FFF6580L,
0x23B70EDB1955C4BFL, 0xC330DE426430F69DL, 0x4715ED43E8A45C0AL, 0xA8D7E4DAB780A08DL,
0x0572B974F03CE0BBL, 0xB57D2E985E1419C7L, 0xE8D9ECBE2CF3D73FL, 0x2FE4B17170E59750L,
0x11317BA87905E790L, 0x7FBF21EC8A1F45ECL, 0x1725CABFCB045B00L, 0x964E915CD5E2B207L,
0x3E2B8BCBF016D66DL, 0xBE7444E39328A0ACL, 0xF85B2B4FBCDE44B7L, 0x49353FEA39BA63B1L,
0x1DD01AAFCD53486AL, 0x1FCA8A92FD719F85L, 0xFC7C95D827357AFAL, 0x18A6A990C8B35EBDL,
0xCCCB7005C6B9C28DL, 0x3BDBB92C43B17F26L, 0xAA70B5B4F89695A2L, 0xE94C39A54A98307FL,
0xB7A0B174CFF6F36EL, 0xD4DBA84729AF48ADL, 0x2E18BC1AD9704A68L, 0x2DE0966DAF2F8B1CL,
0xB9C11D5B1E43A07EL, 0x64972D68DEE33360L, 0x94628D38D0C20584L, 0xDBC0D2B6AB90A559L,
0xD2733C4335C6A72FL, 0x7E75D99D94A70F4DL, 0x6CED1983376FA72BL, 0x97FCAACBF030BC24L,
0x7B77497B32503B12L, 0x8547EDDFB81CCB94L, 0x79999CDFF70902CBL, 0xCFFE1939438E9B24L,
0x829626E3892D95D7L, 0x92FAE24291F2B3F1L, 0x63E22C147B9C3403L, 0xC678B6D860284A1CL,
0x5873888850659AE7L, 0x0981DCD296A8736DL, 0x9F65789A6509A440L, 0x9FF38FED72E9052FL,
0xE479EE5B9930578CL, 0xE7F28ECD2D49EECDL, 0x56C074A581EA17FEL, 0x5544F7D774B14AEFL,
0x7B3F0195FC6F290FL, 0x12153635B2C0CF57L, 0x7F5126DBBA5E0CA7L, 0x7A76956C3EAFB413L,
0x3D5774A11D31AB39L, 0x8A1B083821F40CB4L, 0x7B4A38E32537DF62L, 0x950113646D1D6E03L,
0x4DA8979A0041E8A9L, 0x3BC36E078F7515D7L, 0x5D0A12F27AD310D1L, 0x7F9D1A2E1EBE1327L,
0xDA3A361B1C5157B1L, 0xDCDD7D20903D0C25L, 0x36833336D068F707L, 0xCE68341F79893389L,
0xAB9090168DD05F34L, 0x43954B3252DC25E5L, 0xB438C2B67F98E5E9L, 0x10DCD78E3851A492L,
0xDBC27AB5447822BFL, 0x9B3CDB65F82CA382L, 0xB67B7896167B4C84L, 0xBFCED1B0048EAC50L,
0xA9119B60369FFEBDL, 0x1FFF7AC80904BF45L, 0xAC12FB171817EEE7L, 0xAF08DA9177DDA93DL,
0x1B0CAB936E65C744L, 0xB559EB1D04E5E932L, 0xC37B45B3F8D6F2BAL, 0xC3A9DC228CAAC9E9L,
0xF3B8B6675A6507FFL, 0x9FC477DE4ED681DAL, 0x67378D8ECCEF96CBL, 0x6DD856D94D259236L,
0xA319CE15B0B4DB31L, 0x073973751F12DD5EL, 0x8A8E849EB32781A5L, 0xE1925C71285279F5L,
0x74C04BF1790C0EFEL, 0x4DDA48153C94938AL, 0x9D266D6A1CC0542CL, 0x7440FB816508C4FEL,
0x13328503DF48229FL, 0xD6BF7BAEE43CAC40L, 0x4838D65F6EF6748FL, 0x1E152328F3318DEAL,
0x8F8419A348F296BFL, 0x72C8834A5957B511L, 0xD7A023A73260B45CL, 0x94EBC8ABCFB56DAEL,
0x9FC10D0F989993E0L, 0xDE68A2355B93CAE6L, 0xA44CFE79AE538BBEL, 0x9D1D84FCCE371425L,
0x51D2B1AB2DDFB636L, 0x2FD7E4B9E72CD38CL, 0x65CA5B96B7552210L, 0xDD69A0D8AB3B546DL,
0x604D51B25FBF70E2L, 0x73AA8A564FB7AC9EL, 0x1A8C1E992B941148L, 0xAAC40A2703D9BEA0L,
0x764DBEAE7FA4F3A6L, 0x1E99B96E70A9BE8BL, 0x2C5E9DEB57EF4743L, 0x3A938FEE32D29981L,
0x26E6DB8FFDF5ADFEL, 0x469356C504EC9F9DL, 0xC8763C5B08D1908CL, 0x3F6C6AF859D80055L,
0x7F7CC39420A3A545L, 0x9BFB227EBDF4C5CEL, 0x89039D79D6FC5C5CL, 0x8FE88B57305E2AB6L,
0xA09E8C8C35AB96DEL, 0xFA7E393983325753L, 0xD6B6D0ECC617C699L, 0xDFEA21EA9E7557E3L,
0xB67C1FA481680AF8L, 0xCA1E3785A9E724E5L, 0x1CFC8BED0D681639L, 0xD18D8549D140CAEAL,
0x4ED0FE7E9DC91335L, 0xE4DBF0634473F5D2L, 0x1761F93A44D5AEFEL, 0x53898E4C3910DA55L,
0x734DE8181F6EC39AL, 0x2680B122BAA28D97L, 0x298AF231C85BAFABL, 0x7983EED3740847D5L,
0x66C1A2A1A60CD889L, 0x9E17E49642A3E4C1L, 0xEDB454E7BADC0805L, 0x50B704CAB602C329L,
0x4CC317FB9CDDD023L, 0x66B4835D9EAFEA22L, 0x219B97E26FFC81BDL, 0x261E4E4C0A333A9DL,
0x1FE2CCA76517DB90L, 0xD7504DFA8816EDBBL, 0xB9571FA04DC089C8L, 0x1DDC0325259B27DEL,
0xCF3F4688801EB9AAL, 0xF4F5D05C10CAB243L, 0x38B6525C21A42B0EL, 0x36F60E2BA4FA6800L,
0xEB3593803173E0CEL, 0x9C4CD6257C5A3603L, 0xAF0C317D32ADAA8AL, 0x258E5A80C7204C4BL,
0x8B889D624D44885DL, 0xF4D14597E660F855L, 0xD4347F66EC8941C3L, 0xE699ED85B0DFB40DL,
0x2472F6207C2D0484L, 0xC2A1E7B5B459AEB5L, 0xAB4F6451CC1D45ECL, 0x63767572AE3D6174L,
0xA59E0BD101731A28L, 0x116D0016CB948F09L, 0x2CF9C8CA052F6E9FL, 0x0B090A7560A968E3L,
0xABEEDDB2DDE06FF1L, 0x58EFC10B06A2068DL, 0xC6E57A78FBD986E0L, 0x2EAB8CA63CE802D7L,
0x14A195640116F336L, 0x7C0828DD624EC390L, 0xD74BBE77E6116AC7L, 0x804456AF10F5FB53L,
0xEBE9EA2ADF4321C7L, 0x03219A39EE587A30L, 0x49787FEF17AF9924L, 0xA1E9300CD8520548L,
0x5B45E522E4B1B4EFL, 0xB49C3B3995091A36L, 0xD4490AD526F14431L, 0x12A8F216AF9418C2L,
0x001F837CC7350524L, 0x1877B51E57A764D5L, 0xA2853B80F17F58EEL, 0x993E1DE72D36D310L,
0xB3598080CE64A656L, 0x252F59CF0D9F04BBL, 0xD23C8E176D113600L, 0x1BDA0492E7E4586EL,
0x21E0BD5026C619BFL, 0x3B097ADAF088F94EL, 0x8D14DEDB30BE846EL, 0xF95CFFA23AF5F6F4L,
0x3871700761B3F743L, 0xCA672B91E9E4FA16L, 0x64C8E531BFF53B55L, 0x241260ED4AD1E87DL,
0x106C09B972D2E822L, 0x7FBA195410E5CA30L, 0x7884D9BC6CB569D8L, 0x0647DFEDCD894A29L,
0x63573FF03E224774L, 0x4FC8E9560F91B123L, 0x1DB956E450275779L, 0xB8D91274B9E9D4FBL,
0xA2EBEE47E2FBFCE1L, 0xD9F1F30CCD97FB09L, 0xEFED53D75FD64E6BL, 0x2E6D02C36017F67FL,
0xA9AA4D20DB084E9BL, 0xB64BE8D8B25396C1L, 0x70CB6AF7C2D5BCF0L, 0x98F076A4F7A2322EL,
0xBF84470805E69B5FL, 0x94C3251F06F90CF3L, 0x3E003E616A6591E9L, 0xB925A6CD0421AFF3L,
0x61BDD1307C66E300L, 0xBF8D5108E27E0D48L, 0x240AB57A8B888B20L, 0xFC87614BAF287E07L,
0xEF02CDD06FFDB432L, 0xA1082C0466DF6C0AL, 0x8215E577001332C8L, 0xD39BB9C3A48DB6CFL,
0x2738259634305C14L, 0x61CF4F94C97DF93DL, 0x1B6BACA2AE4E125BL, 0x758F450C88572E0BL,
0x959F587D507A8359L, 0xB063E962E045F54DL, 0x60E8ED72C0DFF5D1L, 0x7B64978555326F9FL,
0xFD080D236DA814BAL, 0x8C90FD9B083F4558L, 0x106F72FE81E2C590L, 0x7976033A39F7D952L,
0xA4EC0132764CA04BL, 0x733EA705FAE4FA77L, 0xB4D8F77BC3E56167L, 0x9E21F4F903B33FD9L,
0x9D765E419FB69F6DL, 0xD30C088BA61EA5EFL, 0x5D94337FBFAF7F5BL, 0x1A4E4822EB4D7A59L,
0x6FFE73E81B637FB3L, 0xDDF957BC36D8B9CAL, 0x64D0E29EEA8838B3L, 0x08DD9BDFD96B9F63L,
0x087E79E5A57D1D13L, 0xE328E230E3E2B3FBL, 0x1C2559E30F0946BEL, 0x720BF5F26F4D2EAAL,
0xB0774D261CC609DBL, 0x443F64EC5A371195L, 0x4112CF68649A260EL, 0xD813F2FAB7F5C5CAL,
0x660D3257380841EEL, 0x59AC2C7873F910A3L, 0xE846963877671A17L, 0x93B633ABFA3469F8L,
0xC0C0F5A60EF4CDCFL, 0xCAF21ECD4377B28CL, 0x57277707199B8175L, 0x506C11B9D90E8B1DL,
0xD83CC2687A19255FL, 0x4A29C6465A314CD1L, 0xED2DF21216235097L, 0xB5635C95FF7296E2L,
0x22AF003AB672E811L, 0x52E762596BF68235L, 0x9AEBA33AC6ECC6B0L, 0x944F6DE09134DFB6L,
0x6C47BEC883A7DE39L, 0x6AD047C430A12104L, 0xA5B1CFDBA0AB4067L, 0x7C45D833AFF07862L,
0x5092EF950A16DA0BL, 0x9338E69C052B8E7BL, 0x455A4B4CFE30E3F5L, 0x6B02E63195AD0CF8L,
0x6B17B224BAD6BF27L, 0xD1E0CCD25BB9C169L, 0xDE0C89A556B9AE70L, 0x50065E535A213CF6L,
0x9C1169FA2777B874L, 0x78EDEFD694AF1EEDL, 0x6DC93D9526A50E68L, 0xEE97F453F06791EDL,
0x32AB0EDB696703D3L, 0x3A6853C7E70757A7L, 0x31865CED6120F37DL, 0x67FEF95D92607890L,
0x1F2B1D1F15F6DC9CL, 0xB69E38A8965C6B65L, 0xAA9119FF184CCCF4L, 0xF43C732873F24C13L,
0xFB4A3D794A9A80D2L, 0x3550C2321FD6109CL, 0x371F77E76BB8417EL, 0x6BFA9AAE5EC05779L,
0xCD04F3FF001A4778L, 0xE3273522064480CAL, 0x9F91508BFFCFC14AL, 0x049A7F41061A9E60L,
0xFCB6BE43A9F2FE9BL, 0x08DE8A1C7797DA9BL, 0x8F9887E6078735A1L, 0xB5B4071DBFC73A66L,
0x230E343DFBA08D33L, 0x43ED7F5A0FAE657DL, 0x3A88A0FBBCB05C63L, 0x21874B8B4D2DBC4FL,
0x1BDEA12E35F6A8C9L, 0x53C065C6C8E63528L, 0xE34A1D250E7A8D6BL, 0xD6B04D3B7651DD7EL,
0x5E90277E7CB39E2DL, 0x2C046F22062DC67DL, 0xB10BB459132D0A26L, 0x3FA9DDFB67E2F199L,
0x0E09B88E1914F7AFL, 0x10E8B35AF3EEAB37L, 0x9EEDECA8E272B933L, 0xD4C718BC4AE8AE5FL,
0x81536D601170FC20L, 0x91B534F885818A06L, 0xEC8177F83F900978L, 0x190E714FADA5156EL,
0xB592BF39B0364963L, 0x89C350C893AE7DC1L, 0xAC042E70F8B383F2L, 0xB49B52E587A1EE60L,
0xFB152FE3FF26DA89L, 0x3E666E6F69AE2C15L, 0x3B544EBE544C19F9L, 0xE805A1E290CF2456L,
0x24B33C9D7ED25117L, 0xE74733427B72F0C1L, 0x0A804D18B7097475L, 0x57E3306D881EDB4FL,
0x4AE7D6A36EB5DBCBL, 0x2D8D5432157064C8L, 0xD1E649DE1E7F268BL, 0x8A328A1CEDFE552CL,
0x07A3AEC79624C7DAL, 0x84547DDC3E203C94L, 0x990A98FD5071D263L, 0x1A4FF12616EEFC89L,
0xF6F7FD1431714200L, 0x30C05B1BA332F41CL, 0x8D2636B81555A786L, 0x46C9FEB55D120902L,
0xCCEC0A73B49C9921L, 0x4E9D2827355FC492L, 0x19EBB029435DCB0FL, 0x4659D2B743848A2CL,
0x963EF2C96B33BE31L, 0x74F85198B05A2E7DL, 0x5A0F544DD2B1FB18L, 0x03727073C2E134B1L,
0xC7F6AA2DE59AEA61L, 0x352787BAA0D7C22FL, 0x9853EAB63B5E0B35L, 0xABBDCDD7ED5C0860L,
0xCF05DAF5AC8D77B0L, 0x49CAD48CEBF4A71EL, 0x7A4C10EC2158C4A6L, 0xD9E92AA246BF719EL,
0x13AE978D09FE5557L, 0x730499AF921549FFL, 0x4E4B705B92903BA4L, 0xFF577222C14F0A3AL,
0x55B6344CF97AAFAEL, 0xB862225B055B6960L, 0xCAC09AFBDDD2CDB4L, 0xDAF8E9829FE96B5FL,
0xB5FDFC5D3132C498L, 0x310CB380DB6F7503L, 0xE87FBB46217A360EL, 0x2102AE466EBB1148L,
0xF8549E1A3AA5E00DL, 0x07A69AFDCC42261AL, 0xC4C118BFE78FEAAEL, 0xF9F4892ED96BD438L,
0x1AF3DBE25D8F45DAL, 0xF5B4B0B0D2DEEEB4L, 0x962ACEEFA82E1C84L, 0x046E3ECAAF453CE9L,
0xF05D129681949A4CL, 0x964781CE734B3C84L, 0x9C2ED44081CE5FBDL, 0x522E23F3925E319EL,
0x177E00F9FC32F791L, 0x2BC60A63A6F3B3F2L, 0x222BBFAE61725606L, 0x486289DDCC3D6780L,
0x7DC7785B8EFDFC80L, 0x8AF38731C02BA980L, 0x1FAB64EA29A2DDF7L, 0xE4D9429322CD065AL,
0x9DA058C67844F20CL, 0x24C0E332B70019B0L, 0x233003B5A6CFE6ADL, 0xD586BD01C5C217F6L,
0x5E5637885F29BC2BL, 0x7EBA726D8C94094BL, 0x0A56A5F0BFE39272L, 0xD79476A84EE20D06L,
0x9E4C1269BAA4BF37L, 0x17EFEE45B0DEE640L, 0x1D95B0A5FCF90BC6L, 0x93CBE0B699C2585DL,
0x65FA4F227A2B6D79L, 0xD5F9E858292504D5L, 0xC2B5A03F71471A6FL, 0x59300222B4561E00L,
0xCE2F8642CA0712DCL, 0x7CA9723FBB2E8988L, 0x2785338347F2BA08L, 0xC61BB3A141E50E8CL,
0x150F361DAB9DEC26L, 0x9F6A419D382595F4L, 0x64A53DC924FE7AC9L, 0x142DE49FFF7A7C3DL,
0x0C335248857FA9E7L, 0x0A9C32D5EAE45305L, 0xE6C42178C4BBB92EL, 0x71F1CE2490D20B07L,
0xF1BCC3D275AFE51AL, 0xE728E8C83C334074L, 0x96FBF83A12884624L, 0x81A1549FD6573DA5L,
0x5FA7867CAF35E149L, 0x56986E2EF3ED091BL, 0x917F1DD5F8886C61L, 0xD20D8C88C8FFE65FL,
0x31D71DCE64B2C310L, 0xF165B587DF898190L, 0xA57E6339DD2CF3A0L, 0x1EF6E6DBB1961EC9L,
0x70CC73D90BC26E24L, 0xE21A6B35DF0C3AD7L, 0x003A93D8B2806962L, 0x1C99DED33CB890A1L,
0xCF3145DE0ADD4289L, 0xD0E4427A5514FB72L, 0x77C621CC9FB3A483L, 0x67A34DAC4356550BL,
0xF8D626AAAF278509L,
};
static boolean canHandle(BookOptions options) {
return options.filename.endsWith(".bin");
}
/** Return true if the external book is available. */
@Override
public final boolean enabled() {
return bookFile.canRead();
}
private static class PGBookEntry {
private byte[] data;
PGBookEntry() {
data = new byte[16];
}
private final long getBytes(int start, int len) {
long ret = 0;
int stop = start + len;
for (int i = start; i < stop; i++) {
int val = data[i];
if (val < 0) val += 256;
ret = (ret << 8) + val;
}
return ret;
}
final long getKey() {
return getBytes(0, 8);
}
final Move getMove(Position pos) {
short move = (short)getBytes(8, 2);
boolean wtm = pos.whiteMove;
int toFile = move & 7;
int toRow = (move >> 3) & 7;
int fromFile = (move >> 6) & 7;
int fromRow = (move >> 9) & 7;
int prom = (move >> 12) & 7;
int from = Position.getSquare(fromFile, fromRow);
int to = Position.getSquare(toFile, toRow);
int promoteTo = Piece.EMPTY;
switch (prom) {
case 1: promoteTo = wtm ? Piece.WKNIGHT : Piece.BKNIGHT; break;
case 2: promoteTo = wtm ? Piece.WBISHOP : Piece.BBISHOP; break;
case 3: promoteTo = wtm ? Piece.WROOK : Piece.BROOK; break;
case 4: promoteTo = wtm ? Piece.WQUEEN : Piece.BQUEEN; break;
default: promoteTo = Piece.EMPTY; break;
}
// Convert castling moves
if ((from == 4) && (pos.getPiece(from) == Piece.WKING)) {
if (to == 7)
to = 6;
else if (to == 0)
to = 2;
}
if ((from == 60) && (pos.getPiece(from) == Piece.BKING)) {
if (to == 56+7)
to = 56+6;
else if (to == 56+0)
to = 56+2;
}
Move m = new Move(from, to, promoteTo);
return m;
}
final int getWeight() { return (int)getBytes(10, 2); }
}
private final void readEntry(RandomAccessFile f, long entNo, PGBookEntry ent) throws IOException {
f.seek(entNo * 16);
if (f.read(ent.data) != 16) {
for (int i = 0; i < 16; i++) ent.data[i] = 0;
}
}
/** Return true if key1 < key2, when compared as unsigned longs. */
private final boolean keyLess(long key1, long key2) {
if ((key1 < 0) == (key2 < 0)) { // Same sign, normal compare
return key1 < key2;
} else { // The negative number is largest
return key2 < 0;
}
}
@Override
public final List<BookEntry> getBookEntries(Position pos) {
try {
RandomAccessFile f = new RandomAccessFile(bookFile, "r");
long numEntries = f.length() / 16;
long key = getHashKey(pos);
PGBookEntry ent = new PGBookEntry();
// Find first entry with hash key >= wantedKey
long lo = -1;
long hi = numEntries;
// ent[lo] < key <= ent[hi]
while (hi - lo > 1) {
long mid = (lo + hi) / 2;
readEntry(f, mid, ent);
long midKey = ent.getKey();
if (keyLess(midKey, key)) {
lo = mid;
} else {
hi = mid;
}
}
// Read all entries with matching hash key
List<BookEntry> ret = new ArrayList<BookEntry>();
long entNo = hi;
while (entNo < numEntries) {
readEntry(f, entNo, ent);
if (ent.getKey() != key)
break;
Move m = ent.getMove(pos);
BookEntry be = new BookEntry(m);
be.weight = ent.getWeight();
ret.add(be);
entNo++;
}
f.close();
return ret;
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
return null;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine;
public interface UCIEngine {
/** Start engine. */
public void initialize();
/** Shut down engine. */
public void shutDown();
/**
* Read a line from the engine.
* @param timeoutMillis Maximum time to wait for data
* @return The line, without terminating newline characters,
* or empty string if no data available,
* or null if I/O error.
*/
public String readLineFromEngine(int timeoutMillis);
/** Write a line to the engine. \n will be added automatically. */
public void writeLineToEngine(String data);
/** Set the engine strength, allowed values 0 - 1000. */
public void setStrength(int strength);
/** Add strength information to the engine name. */
public String addStrengthToName();
/** Set an engine option. */
public void setOption(String name, int value);
public void setOption(String name, boolean value);
public void setOption(String name, String value);
}

View File

@@ -0,0 +1,57 @@
package org.petero.droidfish.engine;
import java.util.HashMap;
public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive;
protected int strength = 1000;
protected UCIEngineBase() {
processAlive = false;
}
protected abstract void startProcess();
@Override
public final void initialize() {
if (!processAlive) {
startProcess();
processAlive = true;
}
}
@Override
public final void shutDown() {
if (processAlive) {
writeLineToEngine("quit");
processAlive = false;
}
}
@Override
public String addStrengthToName() {
return strength < 1000 ? String.format(" (%.1f%%)", strength * 0.1) : "";
}
@Override
public void setOption(String name, int value) {
setOption(name, String.format("%d", value));
}
@Override
public void setOption(String name, boolean value) {
setOption(name, value ? "true" : "false");
}
private HashMap<String, String> options = new HashMap<String, String>();
@Override
public void setOption(String name, String value) {
String currVal = options.get(name.toLowerCase());
if (value.equals(currVal))
return;
writeLineToEngine(String.format("setoption name %s value %s", name, value));
options.put(name.toLowerCase(), value);
}
}

View File

@@ -0,0 +1,252 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import chess.ChessParseError;
import chess.ComputerPlayer;
import chess.Move;
import chess.Position;
import chess.TextIO;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.util.ArrayList;
import org.petero.droidfish.engine.UCIEngineBase;
/**
* UCI interface to cuckoochess engine.
* @author petero
*/
public class CuckooChessEngine extends UCIEngineBase {
// Data set by the "position" command.
private Position pos;
private ArrayList<Move> moves;
// Engine data
private DroidEngineControl engine;
// Set to true to break out of main loop
private boolean quit;
private Pipe guiToEngine;
private Pipe engineToGui;
private NioInputStream inFromEngine;
public CuckooChessEngine() {
try {
pos = TextIO.readFEN(TextIO.startPosFEN);
} catch (ChessParseError ex) {
throw new RuntimeException();
}
moves = new ArrayList<Move>();
quit = false;
try {
guiToEngine = Pipe.open();
engineToGui = Pipe.open();
inFromEngine = new NioInputStream(engineToGui);
} catch (IOException e) {
}
}
@Override
public void setStrength(int strength) {
this.strength = strength;
setOption("strength", strength);
}
protected final void startProcess() {
new Thread(new Runnable() {
public void run() {
NioInputStream in = new NioInputStream(guiToEngine);
NioPrintStream out = new NioPrintStream(engineToGui);
mainLoop(in, out);
}
}).start();
}
private final void mainLoop(NioInputStream is, NioPrintStream os) {
String line;
while ((line = is.readLine()) != null) {
handleCommand(line, os);
if (quit) {
break;
}
}
}
@Override
public final String readLineFromEngine(int timeoutMillis) {
String ret = inFromEngine.readLine(timeoutMillis);
if (ret == null)
return null;
if (ret.length() > 0) {
// System.out.printf("Engine -> GUI: %s\n", ret);
}
return ret;
}
@Override
public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data);
try {
String s = data + "\n";
guiToEngine.sink().write(ByteBuffer.wrap(s.getBytes()));
} catch (IOException e) {
}
}
private final void handleCommand(String cmdLine, NioPrintStream os) {
String[] tokens = tokenize(cmdLine);
try {
String cmd = tokens[0];
if (cmd.equals("uci")) {
os.printf("id name %s%n", ComputerPlayer.engineName);
os.printf("id author Peter Osterlund%n");
DroidEngineControl.printOptions(os);
os.printf("uciok%n");
} else if (cmd.equals("isready")) {
initEngine(os);
os.printf("readyok%n");
} else if (cmd.equals("setoption")) {
initEngine(os);
StringBuilder optionName = new StringBuilder();
StringBuilder optionValue = new StringBuilder();
if (tokens[1].endsWith("name")) {
int idx = 2;
while ((idx < tokens.length) && !tokens[idx].equals("value")) {
optionName.append(tokens[idx++].toLowerCase());
optionName.append(' ');
}
if ((idx < tokens.length) && tokens[idx++].equals("value")) {
while ((idx < tokens.length)) {
optionValue.append(tokens[idx++].toLowerCase());
optionValue.append(' ');
}
}
engine.setOption(optionName.toString().trim(), optionValue.toString().trim());
}
} else if (cmd.equals("ucinewgame")) {
if (engine != null) {
engine.newGame();
}
} else if (cmd.equals("position")) {
String fen = null;
int idx = 1;
if (tokens[idx].equals("startpos")) {
idx++;
fen = TextIO.startPosFEN;
} else if (tokens[idx].equals("fen")) {
idx++;
StringBuilder sb = new StringBuilder();
while ((idx < tokens.length) && !tokens[idx].equals("moves")) {
sb.append(tokens[idx++]);
sb.append(' ');
}
fen = sb.toString().trim();
}
if (fen != null) {
pos = TextIO.readFEN(fen);
moves.clear();
if ((idx < tokens.length) && tokens[idx++].equals("moves")) {
for (int i = idx; i < tokens.length; i++) {
Move m = TextIO.uciStringToMove(tokens[i]);
if (m != null) {
moves.add(m);
} else {
break;
}
}
}
}
} else if (cmd.equals("go")) {
initEngine(os);
int idx = 1;
SearchParams sPar = new SearchParams();
boolean ponder = false;
while (idx < tokens.length) {
String subCmd = tokens[idx++];
if (subCmd.equals("searchmoves")) {
while (idx < tokens.length) {
Move m = TextIO.uciStringToMove(tokens[idx]);
if (m != null) {
sPar.searchMoves.add(m);
idx++;
} else {
break;
}
}
} else if (subCmd.equals("ponder")) {
ponder = true;
} else if (subCmd.equals("wtime")) {
sPar.wTime = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("btime")) {
sPar.bTime = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("winc")) {
sPar.wInc = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("binc")) {
sPar.bInc = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("movestogo")) {
sPar.movesToGo = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("depth")) {
sPar.depth = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("nodes")) {
sPar.nodes = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("mate")) {
sPar.mate = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("movetime")) {
sPar.moveTime = Integer.parseInt(tokens[idx++]);
} else if (subCmd.equals("infinite")) {
sPar.infinite = true;
}
}
if (ponder) {
engine.startPonder(pos, moves, sPar);
} else {
engine.startSearch(pos, moves, sPar);
}
} else if (cmd.equals("stop")) {
engine.stopSearch();
} else if (cmd.equals("ponderhit")) {
engine.ponderHit();
} else if (cmd.equals("quit")) {
if (engine != null) {
engine.stopSearch();
}
quit = true;
}
} catch (ChessParseError ex) {
} catch (ArrayIndexOutOfBoundsException e) {
} catch (NumberFormatException nfe) {
}
}
private final void initEngine(NioPrintStream os) {
if (engine == null) {
engine = new DroidEngineControl(os);
}
}
/** Convert a string to tokens by splitting at whitespace characters. */
private final String[] tokenize(String cmdLine) {
cmdLine = cmdLine.trim();
return cmdLine.split("\\s+");
}
}

View File

@@ -0,0 +1,388 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import chess.Book;
import chess.ComputerPlayer;
import chess.Move;
import chess.MoveGen;
import chess.Piece;
import chess.Position;
import chess.Search;
import chess.TextIO;
import chess.TranspositionTable;
import chess.TranspositionTable.TTEntry;
import chess.UndoInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* Control the search thread.
* @author petero
*/
public class DroidEngineControl {
NioPrintStream os;
Thread engineThread;
private final Object threadMutex;
Search sc;
TranspositionTable tt;
MoveGen moveGen;
Position pos;
long[] posHashList;
int posHashListSize;
boolean ponder; // True if currently doing pondering
boolean onePossibleMove;
boolean infinite;
int minTimeLimit;
int maxTimeLimit;
int maxDepth;
int maxNodes;
List<Move> searchMoves;
// Options
int hashSizeMB = 2;
boolean ownBook = false;
boolean analyseMode = false;
boolean ponderMode = true;
// Reduced strength variables
private int strength = 1000;
private long randomSeed = 0;
private Random rndGen = new Random();
/**
* This class is responsible for sending "info" strings during search.
*/
static class SearchListener implements Search.Listener {
NioPrintStream os;
SearchListener(NioPrintStream os) {
this.os = os;
}
public void notifyDepth(int depth) {
os.printf("info depth %d%n", depth);
}
public void notifyCurrMove(Move m, int moveNr) {
os.printf("info currmove %s currmovenumber %d%n", moveToString(m), moveNr);
}
public void notifyPV(int depth, int score, int time, int nodes, int nps, boolean isMate,
boolean upperBound, boolean lowerBound, ArrayList<Move> pv) {
StringBuilder pvBuf = new StringBuilder();
for (Move m : pv) {
pvBuf.append(" ");
pvBuf.append(moveToString(m));
}
String bound = "";
if (upperBound) {
bound = " upperbound";
} else if (lowerBound) {
bound = " lowerbound";
}
os.printf("info depth %d score %s %d%s time %d nodes %d nps %d pv%s%n",
depth, isMate ? "mate" : "cp", score, bound, time, nodes, nps, pvBuf.toString());
}
public void notifyStats(int nodes, int nps, int time) {
os.printf("info nodes %d nps %d time %d%n", nodes, nps, time);
}
}
public DroidEngineControl(NioPrintStream os) {
this.os = os;
threadMutex = new Object();
setupTT();
moveGen = new MoveGen();
}
final public void startSearch(Position pos, ArrayList<Move> moves, SearchParams sPar) {
setupPosition(new Position(pos), moves);
computeTimeLimit(sPar);
ponder = false;
infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0);
startThread(minTimeLimit, maxTimeLimit, maxDepth, maxNodes);
searchMoves = sPar.searchMoves;
}
final public void startPonder(Position pos, List<Move> moves, SearchParams sPar) {
setupPosition(new Position(pos), moves);
computeTimeLimit(sPar);
ponder = true;
infinite = false;
startThread(-1, -1, -1, -1);
}
final public void ponderHit() {
Search mySearch;
synchronized (threadMutex) {
mySearch = sc;
}
if (mySearch != null) {
if (onePossibleMove) {
if (minTimeLimit > 1) minTimeLimit = 1;
if (maxTimeLimit > 1) maxTimeLimit = 1;
}
mySearch.timeLimit(minTimeLimit, maxTimeLimit);
}
infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0);
ponder = false;
}
final public void stopSearch() {
stopThread();
}
final public void newGame() {
randomSeed = rndGen.nextLong();
tt.clear();
}
/**
* Compute thinking time for current search.
*/
final public void computeTimeLimit(SearchParams sPar) {
minTimeLimit = -1;
maxTimeLimit = -1;
maxDepth = -1;
maxNodes = -1;
if (sPar.infinite) {
minTimeLimit = -1;
maxTimeLimit = -1;
maxDepth = -1;
} else if (sPar.depth > 0) {
maxDepth = sPar.depth;
} else if (sPar.mate > 0) {
maxDepth = sPar.mate * 2 - 1;
} else if (sPar.moveTime > 0) {
minTimeLimit = maxTimeLimit = sPar.moveTime;
} else if (sPar.nodes > 0) {
maxNodes = sPar.nodes;
} else {
int moves = sPar.movesToGo;
if (moves == 0) {
moves = 999;
}
moves = Math.min(moves, 45); // Assume 45 more moves until end of game
if (ponderMode) {
final double ponderHitRate = 0.35;
moves = (int)Math.ceil(moves * (1 - ponderHitRate));
}
boolean white = pos.whiteMove;
int time = white ? sPar.wTime : sPar.bTime;
int inc = white ? sPar.wInc : sPar.bInc;
final int margin = Math.min(1000, time * 9 / 10);
int timeLimit = (time + inc * (moves - 1) - margin) / moves;
minTimeLimit = (int)(timeLimit * 0.85);
maxTimeLimit = (int)(minTimeLimit * (Math.max(2.5, Math.min(4.0, moves / 2.0))));
// Leave at least 1s on the clock, but can't use negative time
minTimeLimit = clamp(minTimeLimit, 1, time - margin);
maxTimeLimit = clamp(maxTimeLimit, 1, time - margin);
}
}
static final int clamp(int val, int min, int max) {
if (val < min) {
return min;
} else if (val > max) {
return max;
} else {
return val;
}
}
final private void startThread(final int minTimeLimit, final int maxTimeLimit,
int maxDepth, final int maxNodes) {
synchronized (threadMutex) {} // Must not start new search until old search is finished
sc = new Search(pos, posHashList, posHashListSize, tt);
sc.timeLimit(minTimeLimit, maxTimeLimit);
sc.setListener(new SearchListener(os));
sc.setStrength(strength, randomSeed);
sc.nodesBetweenTimeCheck = 500;
MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
MoveGen.removeIllegal(pos, moves);
if ((searchMoves != null) && (searchMoves.size() > 0)) {
Arrays.asList(moves.m).retainAll(searchMoves);
}
final MoveGen.MoveList srchMoves = moves;
onePossibleMove = false;
if ((srchMoves.size < 2) && !infinite) {
onePossibleMove = true;
if (!ponder) {
if ((maxDepth < 0) || (maxDepth > 2)) maxDepth = 2;
}
}
tt.nextGeneration();
final int srchmaxDepth = maxDepth;
Runnable run = new Runnable() {
public void run() {
Move m = null;
if (ownBook && !analyseMode) {
Book book = new Book(false);
m = book.getBookMove(pos);
}
if (m == null) {
m = sc.iterativeDeepening(srchMoves, srchmaxDepth, maxNodes, false);
}
while (ponder || infinite) {
// We should not respond until told to do so. Just wait until
// we are allowed to respond.
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
break;
}
}
Move ponderMove = getPonderMove(pos, m);
synchronized (threadMutex) {
if (ponderMove != null) {
os.printf("bestmove %s ponder %s%n", moveToString(m), moveToString(ponderMove));
} else {
os.printf("bestmove %s%n", moveToString(m));
}
engineThread = null;
sc = null;
}
}
};
ThreadGroup tg = new ThreadGroup("searcher");
engineThread = new Thread(tg, run, "searcher", 32768);
engineThread.start();
}
final public void stopThread() {
Thread myThread;
Search mySearch;
synchronized (threadMutex) {
myThread = engineThread;
mySearch = sc;
}
if (myThread != null) {
mySearch.timeLimit(0, 0);
infinite = false;
ponder = false;
try {
myThread.join();
} catch (InterruptedException ex) {
throw new RuntimeException();
}
}
}
private final void setupTT() {
int nEntries = hashSizeMB > 0 ? hashSizeMB * (1 << 20) / 24 : 1024;
int logSize = (int) Math.floor(Math.log(nEntries) / Math.log(2));
tt = new TranspositionTable(logSize);
}
final void setupPosition(Position pos, List<Move> moves) {
UndoInfo ui = new UndoInfo();
posHashList = new long[200 + moves.size()];
posHashListSize = 0;
for (Move m : moves) {
posHashList[posHashListSize++] = pos.zobristHash();
pos.makeMove(m, ui);
}
this.pos = pos;
}
/**
* Try to find a move to ponder from the transposition table.
*/
final Move getPonderMove(Position pos, Move m) {
Move ret = null;
UndoInfo ui = new UndoInfo();
pos.makeMove(m, ui);
TTEntry ent = tt.probe(pos.historyHash());
if (ent.type != TTEntry.T_EMPTY) {
ret = new Move(0, 0, 0);
ent.getMove(ret);
MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos);
MoveGen.removeIllegal(pos, moves);
if (!Arrays.asList(moves.m).contains(ret)) {
ret = null;
}
}
pos.unMakeMove(m, ui);
return ret;
}
static final String moveToString(Move m) {
String ret = TextIO.squareToString(m.from);
ret += TextIO.squareToString(m.to);
switch (m.promoteTo) {
case Piece.WQUEEN:
case Piece.BQUEEN:
ret += "q";
break;
case Piece.WROOK:
case Piece.BROOK:
ret += "r";
break;
case Piece.WBISHOP:
case Piece.BBISHOP:
ret += "b";
break;
case Piece.WKNIGHT:
case Piece.BKNIGHT:
ret += "n";
break;
default:
break;
}
return ret;
}
static void printOptions(NioPrintStream os) {
os.printf("option name Hash type spin default 2 min 1 max 2048%n");
os.printf("option name OwnBook type check default false%n");
os.printf("option name Ponder type check default true%n");
os.printf("option name UCI_AnalyseMode type check default false%n");
os.printf("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://web.comhem.se/petero2home/javachess/index.html%n",
ComputerPlayer.engineName);
os.printf("option name Strength type spin default 1000 min 0 max 1000\n");
}
final void setOption(String optionName, String optionValue) {
try {
if (optionName.equals("hash")) {
hashSizeMB = Integer.parseInt(optionValue);
setupTT();
} else if (optionName.equals("ownbook")) {
ownBook = Boolean.parseBoolean(optionValue);
} else if (optionName.equals("ponder")) {
ponderMode = Boolean.parseBoolean(optionValue);
} else if (optionName.equals("uci_analysemode")) {
analyseMode = Boolean.parseBoolean(optionValue);
} else if (optionName.equals("strength")) {
strength = Integer.parseInt(optionValue);
}
} catch (NumberFormatException nfe) {
}
}
}

View File

@@ -0,0 +1,109 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
/** Simple InputStream look-alike on top of nio. */
class NioInputStream {
Pipe.SourceChannel in;
ByteBuffer buffer;
Selector selector;
ArrayList<Character> inBuf;
StringBuilder lineBuf;
public NioInputStream(Pipe pipe) {
in = pipe.source();
try {
in.configureBlocking(false);
selector = Selector.open();
in.register(selector, SelectionKey.OP_READ);
buffer = ByteBuffer.allocate(1024);
inBuf = new ArrayList<Character>();
lineBuf = new StringBuilder(128);
} catch (IOException e) {
}
}
public String readLine() {
while (true) {
String s = readLine(1000);
if (s != null)
return s;
}
}
public String readLine(int timeoutMillis) {
try {
boolean haveNewLine = false;
for (int i = 0; i < inBuf.size(); i++) {
if (inBuf.get(i) == '\n') {
haveNewLine = true;
break;
}
}
if (!haveNewLine) {
// Refill inBuf
if (timeoutMillis < 1)
timeoutMillis = 1;
selector.select(timeoutMillis);
buffer.clear();
for (SelectionKey sk : selector.selectedKeys())
if (sk.isValid() && sk.isReadable())
in.read(buffer);
buffer.flip();
while (buffer.position() < buffer.limit()) {
byte b = buffer.get();
inBuf.add((char)b);
}
}
// Extract line
String ret = "";
int i;
for (i = 0; i < inBuf.size(); i++) {
char c = inBuf.get(i);
if (c == '\n') {
int newSize = inBuf.size() - i - 1;
for (int j = 0; j < newSize; j++)
inBuf.set(j, inBuf.get(j+i+1));
while (inBuf.size() > newSize)
inBuf.remove(inBuf.size() - 1);
ret = lineBuf.toString();
lineBuf = new StringBuilder(128);
break;
} else {
lineBuf.append(c);
}
}
if (i == inBuf.size())
inBuf.clear();
return ret;
} catch (IOException e) {
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
/** Simple PrintStream look-alike on top of nio. */
class NioPrintStream {
Pipe.SinkChannel out;
public NioPrintStream(Pipe pipe) {
out = pipe.sink();
}
public void printf(String format) {
try {
String s = String.format(format, new Object[]{});
out.write(ByteBuffer.wrap(s.getBytes()));
} catch (IOException e) {
}
}
public void printf(String format, Object ... args) {
try {
String s = String.format(format, args);
out.write(ByteBuffer.wrap(s.getBytes()));
} catch (IOException e) {
}
}
}

View File

@@ -0,0 +1,45 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.engine.cuckoochess;
import chess.Move;
import java.util.ArrayList;
import java.util.List;
/**
* Store search parameters (times, increments, max depth, etc).
* @author petero
*/
public class SearchParams {
List<Move> searchMoves; // If non-empty, search only these moves
int wTime; // White remaining time, ms
int bTime; // Black remaining time, ms
int wInc; // White increment per move, ms
int bInc; // Black increment per move, ms
int movesToGo; // Moves to next time control
int depth; // If >0, don't search deeper than this
int nodes; // If >0, don't search more nodes than this
int mate; // If >0, search for mate-in-x
int moveTime; // If >0, search for exactly this amount of time, ms
boolean infinite;
public SearchParams() {
searchMoves = new ArrayList<Move>();
}
}

View File

@@ -0,0 +1,40 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
/**
* Exception class to represent parse errors in FEN or algebraic notation.
* @author petero
*/
public class ChessParseError extends Exception {
private static final long serialVersionUID = -6051856171275301175L;
public Position pos;
public ChessParseError() {
}
public ChessParseError(String msg) {
super(msg);
pos = null;
}
public ChessParseError(String msg, Position pos) {
super(msg);
this.pos = pos;
}
}

View File

@@ -0,0 +1,937 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.BookOptions;
import org.petero.droidfish.GUIInterface;
import org.petero.droidfish.GameMode;
import org.petero.droidfish.PGNOptions;
import org.petero.droidfish.engine.DroidComputerPlayer;
import org.petero.droidfish.gamelogic.Game.GameState;
import org.petero.droidfish.gamelogic.GameTree.Node;
/**
* The glue between the chess engine and the GUI.
* @author petero
*/
public class DroidChessController {
private DroidComputerPlayer computerPlayer = null;
private PgnToken.PgnTokenReceiver gameTextListener = null;
private BookOptions bookOptions = new BookOptions();
private Game game = null;
private Move ponderMove = null;
private GUIInterface gui;
private GameMode gameMode;
private PGNOptions pgnOptions;
private Thread computerThread;
private Thread analysisThread;
private String engine = "";
private int strength = 1000;
private int numPV = 1;
private int timeControl;
private int movesPerSession;
private int timeIncrement;
class SearchListener implements org.petero.droidfish.gamelogic.SearchListener {
private int currDepth = 0;
private int currMoveNr = 0;
private String currMove = "";
private int currNodes = 0;
private int currNps = 0;
private int currTime = 0;
private boolean whiteMove = true;
private String bookInfo = "";
private List<Move> bookMoves = null;
private ArrayList<PvInfo> pvInfoV = new ArrayList<PvInfo>();
public final void clearSearchInfo() {
pvInfoV.clear();
currDepth = 0;
bookInfo = "";
bookMoves = null;
setSearchInfo();
}
private final void setSearchInfo() {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < pvInfoV.size(); i++) {
PvInfo pvi = pvInfoV.get(i);
if (pvi.depth <= 0)
continue;
buf.append(String.format("[%d] ", pvi.depth));
boolean negateScore = !whiteMove && gui.whiteBasedScores();
if (pvi.upperBound || pvi.lowerBound) {
boolean upper = pvi.upperBound ^ negateScore;
buf.append(upper ? "<=" : ">=");
}
int score = negateScore ? -pvi.score : pvi.score;
if (pvi.isMate) {
buf.append(String.format("m%d", score));
} else {
buf.append(String.format("%.2f", score / 100.0));
}
buf.append(pvi.pvStr);
buf.append("\n");
}
if (currDepth > 0) {
buf.append(String.format("d:%d %d:%s t:%.2f n:%d nps:%d", currDepth,
currMoveNr, currMove, currTime / 1000.0, currNodes, currNps));
}
final String newPV = buf.toString();
final String newBookInfo = bookInfo;
final SearchStatus localSS = ss;
gui.runOnUIThread(new Runnable() {
public void run() {
if (!localSS.searchResultWanted && (bookMoves != null))
return;
ArrayList<ArrayList<Move>> pvMoves = new ArrayList<ArrayList<Move>>();
for (int i = 0; i < pvInfoV.size(); i++)
pvMoves.add(pvInfoV.get(i).pv);
gui.setThinkingInfo(newPV, newBookInfo, pvMoves, bookMoves);
}
});
}
@Override
public void notifyDepth(int depth) {
currDepth = depth;
setSearchInfo();
}
@Override
public void notifyCurrMove(Position pos, Move m, int moveNr) {
currMove = TextIO.moveToString(pos, m, false);
currMoveNr = moveNr;
setSearchInfo();
}
@SuppressWarnings("unchecked")
@Override
public void notifyPV(Position pos, ArrayList<PvInfo> pvInfo, boolean isPonder) {
pvInfoV = (ArrayList<PvInfo>) pvInfo.clone();
for (PvInfo pv : pvInfo) {
currTime = pv.time;
currNodes = pv.nodes;
currNps = pv.nps;
StringBuilder buf = new StringBuilder();
Position tmpPos = new Position(pos);
UndoInfo ui = new UndoInfo();
boolean first = true;
for (Move m : pv.pv) {
String moveStr = TextIO.moveToString(tmpPos, m, false);
if (first && isPonder) {
buf.append(String.format(" [%s]", moveStr));
first = false;
} else {
buf.append(String.format(" %s", moveStr));
}
tmpPos.makeMove(m, ui);
}
pv.pvStr = buf.toString();
}
whiteMove = pos.whiteMove ^ isPonder;
setSearchInfo();
}
@Override
public void notifyStats(int nodes, int nps, int time) {
currNodes = nodes;
currNps = nps;
currTime = time;
setSearchInfo();
}
@Override
public void notifyBookInfo(String bookInfo, List<Move> moveList) {
this.bookInfo = bookInfo;
bookMoves = moveList;
setSearchInfo();
}
public void prefsChanged() {
setSearchInfo();
}
}
SearchListener listener;
public DroidChessController(GUIInterface gui, PgnToken.PgnTokenReceiver gameTextListener, PGNOptions options) {
this.gui = gui;
this.gameTextListener = gameTextListener;
pgnOptions = options;
listener = new SearchListener();
}
public final void setBookOptions(BookOptions options) {
if (!bookOptions.equals(options)) {
bookOptions = options;
if (computerPlayer != null) {
computerPlayer.setBookOptions(bookOptions);
if (analysisThread != null) {
stopAnalysis();
startAnalysis();
}
updateBookHints();
}
}
}
private final void updateBookHints() {
if (gameMode != null) {
boolean analysis = gameMode.analysisMode();
if (!analysis && humansTurn()) {
ss = new SearchStatus();
Pair<String, ArrayList<Move>> bi = computerPlayer.getBookHints(game.currPos());
listener.notifyBookInfo(bi.first, bi.second);
}
}
}
private final static class SearchStatus {
boolean searchResultWanted = true;
}
SearchStatus ss = new SearchStatus();
public final void newGame(GameMode gameMode) {
ss.searchResultWanted = false;
stopComputerThinking();
stopAnalysis();
this.gameMode = gameMode;
ponderMove = null;
if (computerPlayer == null) {
computerPlayer = new DroidComputerPlayer(engine);
computerPlayer.setListener(listener);
computerPlayer.setBookOptions(bookOptions);
}
computerPlayer.setEngineStrength(engine, strength);
computerPlayer.setNumPV(numPV);
game = new Game(computerPlayer, gameTextListener, timeControl, movesPerSession, timeIncrement);
setPlayerNames(game);
updateGameMode();
}
public final void startGame() {
updateComputeThreads(true);
setSelection();
updateGUI();
updateGameMode();
}
private boolean guiPaused = false;
public final void setGuiPaused(boolean paused) {
guiPaused = paused;
updateGameMode();
}
private final void updateGameMode() {
if (game != null) {
boolean gamePaused = !gameMode.clocksActive() || (humansTurn() && guiPaused);
game.setGamePaused(gamePaused);
updateRemainingTime();
boolean addFirst = gameMode.clocksActive();
game.setAddFirst(addFirst);
}
}
private final void updateComputeThreads(boolean clearPV) {
boolean analysis = gameMode.analysisMode();
boolean computersTurn = !humansTurn();
boolean ponder = gui.ponderMode() && !analysis && !computersTurn && (ponderMove != null);
if (!analysis)
stopAnalysis();
if (!(computersTurn || ponder))
stopComputerThinking();
if (clearPV) {
listener.clearSearchInfo();
updateBookHints();
}
if (analysis)
startAnalysis();
if (computersTurn || ponder)
startComputerThinking(ponder);
}
/** Set game mode. */
public final void setGameMode(GameMode newMode) {
if (!gameMode.equals(newMode)) {
if (newMode.humansTurn(game.currPos().whiteMove))
ss.searchResultWanted = false;
gameMode = newMode;
if (!gameMode.playerWhite() || !gameMode.playerBlack())
setPlayerNames(game); // If computer player involved, set player names
updateGameMode();
ponderMove = null;
ss.searchResultWanted = false;
stopComputerThinking();
updateComputeThreads(true);
updateGUI();
}
}
public final void prefsChanged() {
updateBookHints();
updateMoveList();
listener.prefsChanged();
}
private final void setPlayerNames(Game game) {
if (game != null) {
String engine = (computerPlayer != null) ? computerPlayer.getEngineName() :
"Computer";
String white = gameMode.playerWhite() ? "Player" : engine;
String black = gameMode.playerBlack() ? "Player" : engine;
game.tree.setPlayerNames(white, black);
}
}
public final void fromByteArray(byte[] data) {
game.fromByteArray(data);
}
public final byte[] toByteArray() {
return game.tree.toByteArray();
}
public final String getFEN() {
return TextIO.toFEN(game.tree.currentPos);
}
/** Convert current game to PGN format. */
public final String getPGN() {
return game.tree.toPGN(pgnOptions);
}
public final void setFENOrPGN(String fenPgn) throws ChessParseError {
Game newGame = new Game(null, gameTextListener, timeControl, movesPerSession, timeIncrement);
try {
Position pos = TextIO.readFEN(fenPgn);
newGame.setPos(pos);
setPlayerNames(newGame);
} catch (ChessParseError e) {
// Try read as PGN instead
if (!newGame.readPGN(fenPgn, pgnOptions)) {
throw e;
}
}
ss.searchResultWanted = false;
game = newGame;
game.setComputerPlayer(computerPlayer);
gameTextListener.clear();
updateGameMode();
stopAnalysis();
stopComputerThinking();
computerPlayer.clearTT();
ponderMove = null;
updateComputeThreads(true);
gui.setSelection(-1);
updateGUI();
}
/** True if human's turn to make a move. (True in analysis mode.) */
public final boolean humansTurn() {
return gameMode.humansTurn(game.currPos().whiteMove);
}
/** Return true if computer player is using CPU power. */
public final boolean computerBusy() {
return (computerThread != null) || (analysisThread != null);
}
private final boolean undoMoveNoUpdate() {
if (game.getLastMove() == null)
return false;
ss.searchResultWanted = false;
game.undoMove();
ponderMove = null;
if (!humansTurn()) {
if (game.getLastMove() != null) {
game.undoMove();
if (!humansTurn()) {
game.redoMove();
}
} else {
// Don't undo first white move if playing black vs computer,
// because that would cause computer to immediately make
// a new move and the whole redo history will be lost.
if (gameMode.playerWhite() || gameMode.playerBlack()) {
game.redoMove();
return false;
}
}
}
return true;
}
public final void undoMove() {
if (game.getLastMove() != null) {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
boolean didUndo = undoMoveNoUpdate();
updateComputeThreads(true);
setSelection();
if (didUndo)
setAnimMove(game.currPos(), game.getNextMove(), false);
updateGUI();
}
}
private final void redoMoveNoUpdate() {
if (game.canRedoMove()) {
ss.searchResultWanted = false;
game.redoMove();
ponderMove = null;
if (!humansTurn() && game.canRedoMove()) {
game.redoMove();
if (!humansTurn())
game.undoMove();
}
}
}
public final void redoMove() {
if (game.canRedoMove()) {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
redoMoveNoUpdate();
updateComputeThreads(true);
setSelection();
setAnimMove(game.prevPos(), game.getLastMove(), true);
updateGUI();
}
}
public final int numVariations() {
return game.numVariations();
}
public final int currVariation() {
return game.currVariation();
}
public final void changeVariation(int delta) {
if (game.numVariations() > 1) {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
game.changeVariation(delta);
ponderMove = null;
updateComputeThreads(true);
setSelection();
updateGUI();
}
}
public final void removeSubTree() {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
game.removeSubTree();
ponderMove = null;
updateComputeThreads(true);
setSelection();
updateGUI();
}
public final void moveVariation(int delta) {
if (game.numVariations() > 1) {
game.moveVariation(delta);
updateGUI();
}
}
public final void addVariation(String preComment, List<Move> pvMoves, boolean updateDefault) {
for (int i = 0; i < pvMoves.size(); i++) {
Move m = pvMoves.get(i);
String moveStr = TextIO.moveToUCIString(m);
String pre = (i == 0) ? preComment : "";
int varNo = game.tree.addMove(moveStr, "", 0, pre, "");
game.tree.goForward(varNo, updateDefault);
}
for (int i = 0; i < pvMoves.size(); i++)
game.tree.goBack();
gameTextListener.clear();
updateGUI();
}
public final void gotoMove(int moveNr) {
boolean needUpdate = false;
while (game.currPos().fullMoveCounter > moveNr) { // Go backward
int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1);
undoMoveNoUpdate();
int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1);
if (after >= before)
break;
needUpdate = true;
}
while (game.currPos().fullMoveCounter < moveNr) { // Go forward
int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1);
redoMoveNoUpdate();
int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1);
if (after <= before)
break;
needUpdate = true;
}
if (needUpdate) {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
ponderMove = null;
updateComputeThreads(true);
setSelection();
updateGUI();
}
}
public final void gotoStartOfVariation() {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
while (true) {
if (!undoMoveNoUpdate())
break;
if (game.numVariations() > 1)
break;
}
ponderMove = null;
updateComputeThreads(true);
setSelection();
updateGUI();
}
public final void makeHumanMove(Move m) {
if (humansTurn()) {
Position oldPos = new Position(game.currPos());
if (doMove(m)) {
if (m.equals(ponderMove) && !gameMode.analysisMode() &&
(analysisThread == null) && (computerThread != null)) {
computerPlayer.ponderHit(oldPos, ponderMove);
} else {
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
updateComputeThreads(true);
}
setAnimMove(oldPos, m, true);
updateGUI();
} else {
gui.setSelection(-1);
}
}
}
public final void makeHumanNullMove() {
if (humansTurn()) {
int varNo = game.tree.addMove("--", "", 0, "", "");
game.tree.goForward(varNo);
ss.searchResultWanted = false;
stopAnalysis();
stopComputerThinking();
ponderMove = null;
updateComputeThreads(true);
updateGUI();
gui.setSelection(-1);
}
}
Move promoteMove;
public final void reportPromotePiece(int choice) {
final boolean white = game.currPos().whiteMove;
int promoteTo;
switch (choice) {
case 1:
promoteTo = white ? Piece.WROOK : Piece.BROOK;
break;
case 2:
promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP;
break;
case 3:
promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT;
break;
default:
promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN;
break;
}
promoteMove.promoteTo = promoteTo;
Move m = promoteMove;
promoteMove = null;
makeHumanMove(m);
}
/**
* Move a piece from one square to another.
* @return True if the move was legal, false otherwise.
*/
final private boolean doMove(Move move) {
Position pos = game.currPos();
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(pos);
moves = MoveGen.removeIllegal(pos, moves);
int promoteTo = move.promoteTo;
for (Move m : moves) {
if ((m.from == move.from) && (m.to == move.to)) {
if ((m.promoteTo != Piece.EMPTY) && (promoteTo == Piece.EMPTY)) {
promoteMove = m;
gui.requestPromotePiece();
return false;
}
if (m.promoteTo == promoteTo) {
String strMove = TextIO.moveToString(pos, m, false);
game.processString(strMove);
return true;
}
}
}
gui.reportInvalidMove(move);
return false;
}
final private void updateGUI() {
String str;
if (game.getGameState() == Game.GameState.ALIVE) {
str = Integer.valueOf(game.currPos().fullMoveCounter).toString();
str += game.currPos().whiteMove ? ". White's move" : "... Black's move";
if (computerThread != null)
str += humansTurn() ? " (ponder)" : " (thinking)";
if (analysisThread != null) str += " (analyzing)";
} else {
str = game.getGameStateString();
}
gui.setStatusString(str);
updateMoveList();
StringBuilder sb = new StringBuilder();
if (game.tree.currentNode != game.tree.rootNode) {
game.tree.goBack();
Position pos = game.currPos();
List<Move> prevVarList = game.tree.variations();
for (int i = 0; i < prevVarList.size(); i++) {
if (i > 0) sb.append(' ');
if (i == game.tree.currentNode.defaultChild)
sb.append("<b>");
sb.append(TextIO.moveToString(pos, prevVarList.get(i), false));
if (i == game.tree.currentNode.defaultChild)
sb.append("</b>");
}
game.tree.goForward(-1);
}
gui.setPosition(game.currPos(), sb.toString(), game.tree.variations());
updateRemainingTime();
}
final private void updateMoveList() {
if (game == null)
return;
if (!gameTextListener.isUpToDate()) {
PGNOptions tmpOptions = new PGNOptions();
tmpOptions.exp.variations = pgnOptions.view.variations;
tmpOptions.exp.comments = pgnOptions.view.comments;
tmpOptions.exp.nag = pgnOptions.view.nag;
tmpOptions.exp.playerAction = false;
tmpOptions.exp.clockInfo = false;
tmpOptions.exp.moveNrAfterNag = false;
gameTextListener.clear();
game.tree.pgnTreeWalker(tmpOptions, gameTextListener);
}
gameTextListener.setCurrent(game.tree.currentNode);
gui.moveListUpdated();
}
final public void updateRemainingTime() {
// Update remaining time
long now = System.currentTimeMillis();
long wTime = game.timeController.getRemainingTime(true, now);
long bTime = game.timeController.getRemainingTime(false, now);
long nextUpdate = 0;
if (game.timeController.clockRunning()) {
long t = game.currPos().whiteMove ? wTime : bTime;
nextUpdate = (t % 1000);
if (nextUpdate < 0) nextUpdate += 1000;
nextUpdate += 1;
}
gui.setRemainingTime(wTime, bTime, nextUpdate);
}
final private void setSelection() {
Move m = game.getLastMove();
int sq = ((m != null) && (m.from != m.to)) ? m.to : -1;
gui.setSelection(sq);
}
private void setAnimMove(Position sourcePos, Move move, boolean forward) {
gui.setAnimMove(sourcePos, move, forward);
}
private final synchronized void startComputerThinking(boolean ponder) {
if (analysisThread != null) return;
if (game.getGameState() != GameState.ALIVE) return;
if (computerThread == null) {
ss = new SearchStatus();
final Pair<Position, ArrayList<Move>> ph = game.getUCIHistory();
final Game g = game;
final boolean haveDrawOffer = g.haveDrawOffer();
final Position currPos = new Position(g.currPos());
long now = System.currentTimeMillis();
final int wTime = game.timeController.getRemainingTime(true, now);
final int bTime = game.timeController.getRemainingTime(false, now);
final int inc = game.timeController.getIncrement();
int movesToGo = game.timeController.getMovesToTC();
if (ponder && !currPos.whiteMove && (movesToGo > 0)) {
movesToGo--;
if (movesToGo <= 0)
movesToGo += game.timeController.getMovesPerSession();
}
final int fMovesToGo = movesToGo;
final Move fPonderMove = ponder ? ponderMove : null;
computerThread = new Thread(new Runnable() {
public void run() {
computerPlayer.setEngineStrength(engine, strength);
computerPlayer.setNumPV(1);
final Pair<String,Move> pair =
computerPlayer.doSearch(ph.first, ph.second, currPos, haveDrawOffer,
wTime, bTime, inc, fMovesToGo,
gui.ponderMode(), fPonderMove,
gui.engineThreads());
final String cmd = pair.first;
final Move ponder = pair.second;
final SearchStatus localSS = ss;
gui.runOnUIThread(new Runnable() {
public void run() {
synchronized (shutdownEngineLock) {
if (!localSS.searchResultWanted)
return;
Position oldPos = new Position(g.currPos());
g.processString(cmd);
ponderMove = ponder;
updateGameMode();
gui.computerMoveMade();
listener.clearSearchInfo();
stopComputerThinking();
stopAnalysis(); // To force analysis to restart for new position
updateComputeThreads(true);
setSelection();
setAnimMove(oldPos, g.getLastMove(), true);
updateGUI();
}
}
});
}
});
listener.clearSearchInfo();
computerPlayer.shouldStop = false;
computerThread.start();
updateGUI();
}
}
private final synchronized void stopComputerThinking() {
if (computerThread != null) {
computerPlayer.stopSearch();
try {
computerThread.join();
} catch (InterruptedException ex) {
System.out.printf("Could not stop computer thread%n");
}
computerThread = null;
updateGUI();
}
}
private final synchronized void startAnalysis() {
if (gameMode.analysisMode()) {
if (computerThread != null) return;
if (analysisThread == null) {
ss = new SearchStatus();
final Pair<Position, ArrayList<Move>> ph = game.getUCIHistory();
final boolean haveDrawOffer = game.haveDrawOffer();
final Position currPos = new Position(game.currPos());
final boolean alive = game.tree.getGameState() == GameState.ALIVE;
analysisThread = new Thread(new Runnable() {
public void run() {
if (alive) {
computerPlayer.setEngineStrength(engine, 1000);
computerPlayer.setNumPV(numPV);
computerPlayer.analyze(ph.first, ph.second, currPos, haveDrawOffer,
gui.engineThreads());
}
}
});
listener.clearSearchInfo();
computerPlayer.shouldStop = false;
analysisThread.start();
updateGUI();
}
}
}
private final synchronized void stopAnalysis() {
if (analysisThread != null) {
computerPlayer.stopSearch();
try {
analysisThread.join();
} catch (InterruptedException ex) {
System.out.printf("Could not stop analysis thread%n");
}
analysisThread = null;
listener.clearSearchInfo();
updateGUI();
}
}
public final synchronized void setEngineStrength(String engine, int strength) {
boolean newEngine = !engine.equals(this.engine);
if (newEngine)
numPV = 1;
if (newEngine || (strength != this.strength)) {
this.engine = engine;
this.strength = strength;
if (newEngine && ((analysisThread != null) || (computerThread != null))) {
stopAnalysis();
updateComputeThreads(true);
updateGUI();
}
}
}
public final synchronized int maxPV() {
if (computerPlayer == null)
return 1;
return computerPlayer.getMaxPV();
}
public final int getNumPV() {
return this.numPV;
}
public final synchronized void setMultiPVMode(int numPV) {
if (numPV < 1) numPV = 1;
if (numPV > maxPV()) numPV = maxPV();
if (numPV != this.numPV) {
this.numPV = numPV;
if (analysisThread != null) {
stopAnalysis();
ponderMove = null;
updateComputeThreads(true);
updateGUI();
}
}
}
public final synchronized void setTimeLimit(int time, int moves, int inc) {
timeControl = time;
movesPerSession = moves;
timeIncrement = inc;
if (game != null)
game.timeController.setTimeControl(timeControl, movesPerSession, timeIncrement);
}
public final void stopSearch() {
if (computerThread != null) {
computerPlayer.stopSearch();
}
}
public final void stopPonder() {
if ((computerThread != null) && humansTurn())
stopComputerThinking();
}
private Object shutdownEngineLock = new Object();
public final synchronized void shutdownEngine() {
synchronized (shutdownEngineLock) {
gameMode = new GameMode(GameMode.TWO_PLAYERS);
ss.searchResultWanted = false;
stopComputerThinking();
stopAnalysis();
ponderMove = null;
computerPlayer.shutdownEngine();
}
}
/** Help human to claim a draw by trying to find and execute a valid draw claim. */
public final boolean claimDrawIfPossible() {
if (!findValidDrawClaim())
return false;
updateGUI();
return true;
}
private final boolean findValidDrawClaim() {
if (game.getGameState() != GameState.ALIVE) return true;
game.processString("draw accept");
if (game.getGameState() != GameState.ALIVE) return true;
game.processString("draw rep");
if (game.getGameState() != GameState.ALIVE) return true;
game.processString("draw 50");
if (game.getGameState() != GameState.ALIVE) return true;
return false;
}
public final void resignGame() {
if (game.getGameState() == GameState.ALIVE) {
game.processString("resign");
updateGUI();
}
}
public final void setHeaders(ArrayList<String> tags, ArrayList<String> tagValues) {
game.tree.setHeaders(tags, tagValues);
gameTextListener.clear();
updateGUI();
}
public final void getHeaders(ArrayList<String> tags, ArrayList<String> tagValues) {
game.tree.getHeaders(tags, tagValues);
}
public static final class CommentInfo {
public String move;
public String preComment, postComment;
public int nag;
}
public final CommentInfo getComments() {
Node cur = game.tree.currentNode;
CommentInfo ret = new CommentInfo();
ret.move = cur.moveStr;
ret.preComment = cur.preComment;
ret.postComment = cur.postComment;
ret.nag = cur.nag;
return ret;
}
public final void setComments(CommentInfo commInfo) {
Node cur = game.tree.currentNode;
cur.preComment = commInfo.preComment;
cur.postComment = commInfo.postComment;
cur.nag = commInfo.nag;
gameTextListener.clear();
updateGUI();
}
}

View File

@@ -0,0 +1,466 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.PGNOptions;
import org.petero.droidfish.engine.DroidComputerPlayer;
import org.petero.droidfish.gamelogic.GameTree.Node;
/**
*
* @author petero
*/
public class Game {
boolean pendingDrawOffer;
GameTree tree;
private DroidComputerPlayer computerPlayer;
TimeControl timeController;
private boolean gamePaused;
private boolean addFirst;
PgnToken.PgnTokenReceiver gameTextListener;
public Game(DroidComputerPlayer computerPlayer, PgnToken.PgnTokenReceiver gameTextListener,
int timeControl, int movesPerSession, int timeIncrement) {
this.computerPlayer = computerPlayer;
this.gameTextListener = gameTextListener;
tree = new GameTree(gameTextListener);
timeController = new TimeControl();
timeController.setTimeControl(timeControl, movesPerSession, timeIncrement);
gamePaused = false;
newGame();
}
final void fromByteArray(byte[] data) {
tree.fromByteArray(data);
updateTimeControl(true);
}
public final void setComputerPlayer(DroidComputerPlayer computerPlayer) {
this.computerPlayer = computerPlayer;
}
public final void setGamePaused(boolean gamePaused) {
if (gamePaused != this.gamePaused) {
this.gamePaused = gamePaused;
updateTimeControl(false);
}
}
public final void setAddFirst(boolean addFirst) {
this.addFirst = addFirst;
}
final void setPos(Position pos) {
tree.setStartPos(new Position(pos));
updateTimeControl(false);
}
final boolean readPGN(String pgn, PGNOptions options) throws ChessParseError {
boolean ret = tree.readPGN(pgn, options);
if (ret)
updateTimeControl(false);
return ret;
}
final Position currPos() {
return tree.currentPos;
}
final Position prevPos() {
Move m = tree.currentNode.move;
if (m != null) {
tree.goBack();
Position ret = new Position(currPos());
tree.goForward(-1);
return ret;
} else {
return currPos();
}
}
public final Move getNextMove() {
if (canRedoMove()) {
tree.goForward(-1);
Move ret = tree.currentNode.move;
tree.goBack();
return ret;
} else {
return null;
}
}
/**
* Update the game state according to move/command string from a player.
* @param str The move or command to process.
* @return True if str was understood, false otherwise.
*/
public final boolean processString(String str) {
if (getGameState() != GameState.ALIVE)
return false;
if (str.startsWith("draw ")) {
String drawCmd = str.substring(str.indexOf(" ") + 1);
handleDrawCmd(drawCmd);
return true;
} else if (str.equals("resign")) {
addToGameTree(new Move(0, 0, 0), "resign");
return true;
}
Move m = TextIO.UCIstringToMove(str);
if (m != null) {
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos());
moves = MoveGen.removeIllegal(currPos(), moves);
boolean legal = false;
for (int i = 0; i < moves.size(); i++) {
if (m.equals(moves.get(i))) {
legal = true;
break;
}
}
if (!legal)
m = null;
}
if (m == null) {
m = TextIO.stringToMove(currPos(), str);
}
if (m == null) {
return false;
}
addToGameTree(m, pendingDrawOffer ? "draw offer" : "");
return true;
}
private final void addToGameTree(Move m, String playerAction) {
if (m.equals(new Move(0, 0, 0))) { // Don't create more than one null move at a node
List<Move> varMoves = tree.variations();
for (int i = varMoves.size() - 1; i >= 0; i--) {
if (varMoves.get(i).equals(m)) {
tree.deleteVariation(i);
}
}
}
List<Move> varMoves = tree.variations();
boolean movePresent = false;
int varNo;
for (varNo = 0; varNo < varMoves.size(); varNo++) {
if (varMoves.get(varNo).equals(m)) {
movePresent = true;
break;
}
}
if (!movePresent) {
String moveStr = TextIO.moveToUCIString(m);
varNo = tree.addMove(moveStr, playerAction, 0, "", "");
}
int newPos = addFirst ? 0 : varNo;
tree.reorderVariation(varNo, newPos);
tree.goForward(newPos);
int remaining = timeController.moveMade(System.currentTimeMillis(), !gamePaused);
tree.setRemainingTime(remaining);
updateTimeControl(true);
pendingDrawOffer = false;
}
private final void updateTimeControl(boolean discardElapsed) {
int move = currPos().fullMoveCounter;
boolean wtm = currPos().whiteMove;
if (discardElapsed || (move != timeController.currentMove) || (wtm != timeController.whiteToMove)) {
int initialTime = timeController.getInitialTime();
int whiteBaseTime = tree.getRemainingTime(true, initialTime);
int blackBaseTime = tree.getRemainingTime(false, initialTime);
timeController.setCurrentMove(move, wtm, whiteBaseTime, blackBaseTime);
}
long now = System.currentTimeMillis();
if (gamePaused || (getGameState() != GameState.ALIVE)) {
timeController.stopTimer(now);
} else {
timeController.startTimer(now);
}
}
public final String getGameStateString() {
switch (getGameState()) {
case ALIVE:
return "";
case WHITE_MATE:
return "Game over, white mates!";
case BLACK_MATE:
return "Game over, black mates!";
case WHITE_STALEMATE:
case BLACK_STALEMATE:
return "Game over, draw by stalemate!";
case DRAW_REP:
{
String ret = "Game over, draw by repetition!";
String drawInfo = tree.getGameStateInfo();
if (drawInfo.length() > 0) {
ret = ret + " [" + drawInfo+ "]";
}
return ret;
}
case DRAW_50:
{
String ret = "Game over, draw by 50 move rule!";
String drawInfo = tree.getGameStateInfo();
if (drawInfo.length() > 0) {
ret = ret + " [" + drawInfo + "]";
}
return ret;
}
case DRAW_NO_MATE:
return "Game over, draw by impossibility of mate!";
case DRAW_AGREE:
return "Game over, draw by agreement!";
case RESIGN_WHITE:
return "Game over, white resigns!";
case RESIGN_BLACK:
return "Game over, black resigns!";
default:
throw new RuntimeException();
}
}
/**
* Get the last played move, or null if no moves played yet.
*/
public final Move getLastMove() {
return tree.currentNode.move;
}
/** Return true if there is a move to redo. */
public final boolean canRedoMove() {
int nVar = tree.variations().size();
return nVar > 0;
}
public final int numVariations() {
if (tree.currentNode == tree.rootNode)
return 1;
tree.goBack();
int nChildren = tree.variations().size();
tree.goForward(-1);
return nChildren;
}
public final int currVariation() {
if (tree.currentNode == tree.rootNode)
return 0;
tree.goBack();
int defChild = tree.currentNode.defaultChild;
tree.goForward(-1);
return defChild;
}
public final void changeVariation(int delta) {
if (tree.currentNode == tree.rootNode)
return;
tree.goBack();
int defChild = tree.currentNode.defaultChild;
int nChildren = tree.variations().size();
int newChild = defChild + delta;
newChild = Math.max(newChild, 0);
newChild = Math.min(newChild, nChildren - 1);
tree.goForward(newChild);
pendingDrawOffer = false;
updateTimeControl(true);
}
public final void moveVariation(int delta) {
if (tree.currentNode == tree.rootNode)
return;
tree.goBack();
int varNo = tree.currentNode.defaultChild;
int nChildren = tree.variations().size();
int newPos = varNo + delta;
newPos = Math.max(newPos, 0);
newPos = Math.min(newPos, nChildren - 1);
tree.reorderVariation(varNo, newPos);
tree.goForward(newPos);
pendingDrawOffer = false;
updateTimeControl(true);
}
public final void removeSubTree() {
if (getLastMove() != null) {
tree.goBack();
int defChild = tree.currentNode.defaultChild;
tree.deleteVariation(defChild);
} else {
while (canRedoMove())
tree.deleteVariation(0);
}
pendingDrawOffer = false;
updateTimeControl(true);
}
public static enum GameState {
ALIVE,
WHITE_MATE, // White mates
BLACK_MATE, // Black mates
WHITE_STALEMATE, // White is stalemated
BLACK_STALEMATE, // Black is stalemated
DRAW_REP, // Draw by 3-fold repetition
DRAW_50, // Draw by 50 move rule
DRAW_NO_MATE, // Draw by impossibility of check mate
DRAW_AGREE, // Draw by agreement
RESIGN_WHITE, // White resigns
RESIGN_BLACK // Black resigns
}
/**
* Get the current state (draw, mate, ongoing, etc) of the game.
*/
public final GameState getGameState() {
return tree.getGameState();
}
/**
* Check if a draw offer is available.
* @return True if the current player has the option to accept a draw offer.
*/
public final boolean haveDrawOffer() {
return tree.currentNode.playerAction.equals("draw offer");
}
public final void undoMove() {
Move m = tree.currentNode.move;
if (m != null) {
tree.goBack();
pendingDrawOffer = false;
updateTimeControl(true);
}
}
public final void redoMove() {
if (canRedoMove()) {
tree.goForward(-1);
pendingDrawOffer = false;
updateTimeControl(true);
}
}
public final void newGame() {
tree = new GameTree(gameTextListener);
if (computerPlayer != null)
computerPlayer.clearTT();
timeController.reset();
pendingDrawOffer = false;
updateTimeControl(true);
}
/**
* Return the last zeroing position and a list of moves
* to go from that position to the current position.
*/
public final Pair<Position, ArrayList<Move>> getUCIHistory() {
Pair<List<Node>, Integer> ml = tree.getMoveList();
List<Node> moveList = ml.first;
Position pos = new Position(tree.startPos);
ArrayList<Move> mList = new ArrayList<Move>();
Position currPos = new Position(pos);
UndoInfo ui = new UndoInfo();
int nMoves = ml.second;
for (int i = 0; i < nMoves; i++) {
Node n = moveList.get(i);
mList.add(n.move);
currPos.makeMove(n.move, ui);
if (currPos.halfMoveClock == 0) {
pos = new Position(currPos);
mList.clear();
}
}
return new Pair<Position, ArrayList<Move>>(pos, mList);
}
private final void handleDrawCmd(String drawCmd) {
Position pos = tree.currentPos;
if (drawCmd.startsWith("rep") || drawCmd.startsWith("50")) {
boolean rep = drawCmd.startsWith("rep");
Move m = null;
String ms = null;
int firstSpace = drawCmd.indexOf(" ");
if (firstSpace >= 0) {
ms = drawCmd.substring(firstSpace + 1);
if (ms.length() > 0) {
m = TextIO.stringToMove(pos, ms);
}
}
boolean valid;
if (rep) {
valid = false;
UndoInfo ui = new UndoInfo();
int repetitions = 0;
Position posToCompare = new Position(tree.currentPos);
if (m != null) {
posToCompare.makeMove(m, ui);
repetitions = 1;
}
Pair<List<Node>, Integer> ml = tree.getMoveList();
List<Node> moveList = ml.first;
Position tmpPos = new Position(tree.startPos);
if (tmpPos.drawRuleEquals(posToCompare))
repetitions++;
int nMoves = ml.second;
for (int i = 0; i < nMoves; i++) {
Node n = moveList.get(i);
tmpPos.makeMove(n.move, ui);
TextIO.fixupEPSquare(tmpPos);
if (tmpPos.drawRuleEquals(posToCompare))
repetitions++;
}
if (repetitions >= 3)
valid = true;
} else {
Position tmpPos = new Position(pos);
if (m != null) {
UndoInfo ui = new UndoInfo();
tmpPos.makeMove(m, ui);
}
valid = tmpPos.halfMoveClock >= 100;
}
if (valid) {
String playerAction = rep ? "draw rep" : "draw 50";
if (m != null)
playerAction += " " + TextIO.moveToString(pos, m, false);
addToGameTree(new Move(0, 0, 0), playerAction);
} else {
pendingDrawOffer = true;
if (m != null) {
processString(ms);
}
}
} else if (drawCmd.startsWith("offer ")) {
pendingDrawOffer = true;
String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1);
if (TextIO.stringToMove(pos, ms) != null) {
processString(ms);
}
} else if (drawCmd.equals("accept")) {
if (haveDrawOffer())
addToGameTree(new Move(0, 0, 0), "draw accept");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
/**
*
* @author petero
*/
public class Move {
/** From square, 0-63. */
public int from;
/** To square, 0-63. */
public int to;
/** Promotion piece. */
public int promoteTo;
/** Create a move object. */
public Move(int from, int to, int promoteTo) {
this.from = from;
this.to = to;
this.promoteTo = promoteTo;
}
public Move(Move m) {
this.from = m.from;
this.to = m.to;
this.promoteTo = m.promoteTo;
}
@Override
public boolean equals(Object o) {
if ((o == null) || (o.getClass() != this.getClass()))
return false;
Move other = (Move)o;
if (from != other.from)
return false;
if (to != other.to)
return false;
if (promoteTo != other.promoteTo)
return false;
return true;
}
@Override
public int hashCode() {
return (from * 64 + to) * 16 + promoteTo;
}
/** Useful for debugging. */
public final String toString() {
return TextIO.moveToUCIString(this);
}
}

View File

@@ -0,0 +1,355 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
/**
*
* @author petero
*/
public class MoveGen {
static MoveGen instance;
static {
instance = new MoveGen();
}
/**
* Generate and return a list of pseudo-legal moves.
* Pseudo-legal means that the moves doesn't necessarily defend from check threats.
*/
public final ArrayList<Move> pseudoLegalMoves(Position pos) {
ArrayList<Move> moveList = getMoveListObj();
final boolean wtm = pos.whiteMove;
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
int sq = Position.getSquare(x, y);
int p = pos.getPiece(sq);
if ((p == Piece.EMPTY) || (Piece.isWhite(p) != wtm)) {
continue;
}
if ((p == Piece.WROOK) || (p == Piece.BROOK) || (p == Piece.WQUEEN) || (p == Piece.BQUEEN)) {
if (addDirection(moveList, pos, sq, 7-x, 1)) return moveList;
if (addDirection(moveList, pos, sq, 7-y, 8)) return moveList;
if (addDirection(moveList, pos, sq, x, -1)) return moveList;
if (addDirection(moveList, pos, sq, y, -8)) return moveList;
}
if ((p == Piece.WBISHOP) || (p == Piece.BBISHOP) || (p == Piece.WQUEEN) || (p == Piece.BQUEEN)) {
if (addDirection(moveList, pos, sq, Math.min(7-x, 7-y), 9)) return moveList;
if (addDirection(moveList, pos, sq, Math.min( x, 7-y), 7)) return moveList;
if (addDirection(moveList, pos, sq, Math.min( x, y), -9)) return moveList;
if (addDirection(moveList, pos, sq, Math.min(7-x, y), -7)) return moveList;
}
if ((p == Piece.WKNIGHT) || (p == Piece.BKNIGHT)) {
if (x < 6 && y < 7 && addDirection(moveList, pos, sq, 1, 10)) return moveList;
if (x < 7 && y < 6 && addDirection(moveList, pos, sq, 1, 17)) return moveList;
if (x > 0 && y < 6 && addDirection(moveList, pos, sq, 1, 15)) return moveList;
if (x > 1 && y < 7 && addDirection(moveList, pos, sq, 1, 6)) return moveList;
if (x > 1 && y > 0 && addDirection(moveList, pos, sq, 1, -10)) return moveList;
if (x > 0 && y > 1 && addDirection(moveList, pos, sq, 1, -17)) return moveList;
if (x < 7 && y > 1 && addDirection(moveList, pos, sq, 1, -15)) return moveList;
if (x < 6 && y > 0 && addDirection(moveList, pos, sq, 1, -6)) return moveList;
}
if ((p == Piece.WKING) || (p == Piece.BKING)) {
if (x < 7 && addDirection(moveList, pos, sq, 1, 1)) return moveList;
if (x < 7 && y < 7 && addDirection(moveList, pos, sq, 1, 9)) return moveList;
if ( y < 7 && addDirection(moveList, pos, sq, 1, 8)) return moveList;
if (x > 0 && y < 7 && addDirection(moveList, pos, sq, 1, 7)) return moveList;
if (x > 0 && addDirection(moveList, pos, sq, 1, -1)) return moveList;
if (x > 0 && y > 0 && addDirection(moveList, pos, sq, 1, -9)) return moveList;
if ( y > 0 && addDirection(moveList, pos, sq, 1, -8)) return moveList;
if (x < 7 && y > 0 && addDirection(moveList, pos, sq, 1, -7)) return moveList;
int k0 = wtm ? Position.getSquare(4,0) : Position.getSquare(4,7);
if (Position.getSquare(x,y) == k0) {
int aCastle = wtm ? Position.A1_CASTLE : Position.A8_CASTLE;
int hCastle = wtm ? Position.H1_CASTLE : Position.H8_CASTLE;
int rook = wtm ? Piece.WROOK : Piece.BROOK;
if (((pos.getCastleMask() & (1 << hCastle)) != 0) &&
(pos.getPiece(k0 + 1) == Piece.EMPTY) &&
(pos.getPiece(k0 + 2) == Piece.EMPTY) &&
(pos.getPiece(k0 + 3) == rook) &&
!sqAttacked(pos, k0) &&
!sqAttacked(pos, k0 + 1)) {
moveList.add(getMoveObj(k0, k0 + 2, Piece.EMPTY));
}
if (((pos.getCastleMask() & (1 << aCastle)) != 0) &&
(pos.getPiece(k0 - 1) == Piece.EMPTY) &&
(pos.getPiece(k0 - 2) == Piece.EMPTY) &&
(pos.getPiece(k0 - 3) == Piece.EMPTY) &&
(pos.getPiece(k0 - 4) == rook) &&
!sqAttacked(pos, k0) &&
!sqAttacked(pos, k0 - 1)) {
moveList.add(getMoveObj(k0, k0 - 2, Piece.EMPTY));
}
}
}
if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) {
int yDir = wtm ? 8 : -8;
if (pos.getPiece(sq + yDir) == Piece.EMPTY) { // non-capture
addPawnMoves(moveList, sq, sq + yDir);
if ((y == (wtm ? 1 : 6)) &&
(pos.getPiece(sq + 2 * yDir) == Piece.EMPTY)) { // double step
addPawnMoves(moveList, sq, sq + yDir * 2);
}
}
if (x > 0) { // Capture to the left
int toSq = sq + yDir - 1;
int cap = pos.getPiece(toSq);
if (cap != Piece.EMPTY) {
if (Piece.isWhite(cap) != wtm) {
if (cap == (wtm ? Piece.BKING : Piece.WKING)) {
returnMoveList(moveList);
moveList = getMoveListObj();
moveList.add(getMoveObj(sq, toSq, Piece.EMPTY));
return moveList;
} else {
addPawnMoves(moveList, sq, toSq);
}
}
} else if (toSq == pos.getEpSquare()) {
addPawnMoves(moveList, sq, toSq);
}
}
if (x < 7) { // Capture to the right
int toSq = sq + yDir + 1;
int cap = pos.getPiece(toSq);
if (cap != Piece.EMPTY) {
if (Piece.isWhite(cap) != wtm) {
if (cap == (wtm ? Piece.BKING : Piece.WKING)) {
returnMoveList(moveList);
moveList = getMoveListObj();
moveList.add(getMoveObj(sq, toSq, Piece.EMPTY));
return moveList;
} else {
addPawnMoves(moveList, sq, toSq);
}
}
} else if (toSq == pos.getEpSquare()) {
addPawnMoves(moveList, sq, toSq);
}
}
}
}
}
return moveList;
}
/**
* Return true if the side to move is in check.
*/
public static final boolean inCheck(Position pos) {
int kingSq = pos.getKingSq(pos.whiteMove);
if (kingSq < 0)
return false;
return sqAttacked(pos, kingSq);
}
/**
* Return true if a square is attacked by the opposite side.
*/
public static final boolean sqAttacked(Position pos, int sq) {
int x = Position.getX(sq);
int y = Position.getY(sq);
boolean isWhiteMove = pos.whiteMove;
final int oQueen= isWhiteMove ? Piece.BQUEEN: Piece.WQUEEN;
final int oRook = isWhiteMove ? Piece.BROOK : Piece.WROOK;
final int oBish = isWhiteMove ? Piece.BBISHOP : Piece.WBISHOP;
final int oKnight = isWhiteMove ? Piece.BKNIGHT : Piece.WKNIGHT;
int p;
if (y > 0) {
p = checkDirection(pos, sq, y, -8); if ((p == oQueen) || (p == oRook)) return true;
p = checkDirection(pos, sq, Math.min( x, y), -9); if ((p == oQueen) || (p == oBish)) return true;
p = checkDirection(pos, sq, Math.min(7-x, y), -7); if ((p == oQueen) || (p == oBish)) return true;
if (x > 1 ) { p = checkDirection(pos, sq, 1, -10); if (p == oKnight) return true; }
if (x > 0 && y > 1) { p = checkDirection(pos, sq, 1, -17); if (p == oKnight) return true; }
if (x < 7 && y > 1) { p = checkDirection(pos, sq, 1, -15); if (p == oKnight) return true; }
if (x < 6 ) { p = checkDirection(pos, sq, 1, -6); if (p == oKnight) return true; }
if (!isWhiteMove) {
if (x < 7 && y > 1) { p = checkDirection(pos, sq, 1, -7); if (p == Piece.WPAWN) return true; }
if (x > 0 && y > 1) { p = checkDirection(pos, sq, 1, -9); if (p == Piece.WPAWN) return true; }
}
}
if (y < 7) {
p = checkDirection(pos, sq, 7-y, 8); if ((p == oQueen) || (p == oRook)) return true;
p = checkDirection(pos, sq, Math.min(7-x, 7-y), 9); if ((p == oQueen) || (p == oBish)) return true;
p = checkDirection(pos, sq, Math.min( x, 7-y), 7); if ((p == oQueen) || (p == oBish)) return true;
if (x < 6 ) { p = checkDirection(pos, sq, 1, 10); if (p == oKnight) return true; }
if (x < 7 && y < 6) { p = checkDirection(pos, sq, 1, 17); if (p == oKnight) return true; }
if (x > 0 && y < 6) { p = checkDirection(pos, sq, 1, 15); if (p == oKnight) return true; }
if (x > 1 ) { p = checkDirection(pos, sq, 1, 6); if (p == oKnight) return true; }
if (isWhiteMove) {
if (x < 7 && y < 6) { p = checkDirection(pos, sq, 1, 9); if (p == Piece.BPAWN) return true; }
if (x > 0 && y < 6) { p = checkDirection(pos, sq, 1, 7); if (p == Piece.BPAWN) return true; }
}
}
p = checkDirection(pos, sq, 7-x, 1); if ((p == oQueen) || (p == oRook)) return true;
p = checkDirection(pos, sq, x, -1); if ((p == oQueen) || (p == oRook)) return true;
int oKingSq = pos.getKingSq(!isWhiteMove);
if (oKingSq >= 0) {
int ox = Position.getX(oKingSq);
int oy = Position.getY(oKingSq);
if ((Math.abs(x - ox) <= 1) && (Math.abs(y - oy) <= 1))
return true;
}
return false;
}
/**
* Remove all illegal moves from moveList.
* "moveList" is assumed to be a list of pseudo-legal moves.
* This function removes the moves that don't defend from check threats.
*/
public static final ArrayList<Move> removeIllegal(Position pos, ArrayList<Move> moveList) {
ArrayList<Move> ret = new ArrayList<Move>();
UndoInfo ui = new UndoInfo();
int mlSize = moveList.size();
for (int mi = 0; mi < mlSize; mi++) {
Move m = moveList.get(mi);
pos.makeMove(m, ui);
pos.setWhiteMove(!pos.whiteMove);
if (!inCheck(pos))
ret.add(m);
pos.setWhiteMove(!pos.whiteMove);
pos.unMakeMove(m, ui);
}
return ret;
}
/**
* Add all moves from square sq0 in direction delta.
* @param maxSteps Max steps until reaching a border. Set to 1 for non-sliding pieces.
* @ return True if the enemy king could be captured, false otherwise.
*/
private final boolean addDirection(ArrayList<Move> moveList, Position pos, int sq0, int maxSteps, int delta) {
int sq = sq0;
boolean wtm = pos.whiteMove;
final int oKing = (wtm ? Piece.BKING : Piece.WKING);
while (maxSteps > 0) {
sq += delta;
int p = pos.getPiece(sq);
if (p == Piece.EMPTY) {
moveList.add(getMoveObj(sq0, sq, Piece.EMPTY));
} else {
if (Piece.isWhite(p) != wtm) {
if (p == oKing) {
returnMoveList(moveList);
moveList = getMoveListObj(); // Ugly! this only works because we get back the same object
moveList.add(getMoveObj(sq0, sq, Piece.EMPTY));
return true;
} else {
moveList.add(getMoveObj(sq0, sq, Piece.EMPTY));
}
}
break;
}
maxSteps--;
}
return false;
}
/**
* Generate all possible pawn moves from (x0,y0) to (x1,y1), taking pawn promotions into account.
*/
private final void addPawnMoves(ArrayList<Move> moveList, int sq0, int sq1) {
if (sq1 >= 56) { // White promotion
moveList.add(getMoveObj(sq0, sq1, Piece.WQUEEN));
moveList.add(getMoveObj(sq0, sq1, Piece.WKNIGHT));
moveList.add(getMoveObj(sq0, sq1, Piece.WROOK));
moveList.add(getMoveObj(sq0, sq1, Piece.WBISHOP));
} else if (sq1 < 8) { // Black promotion
moveList.add(getMoveObj(sq0, sq1, Piece.BQUEEN));
moveList.add(getMoveObj(sq0, sq1, Piece.BKNIGHT));
moveList.add(getMoveObj(sq0, sq1, Piece.BROOK));
moveList.add(getMoveObj(sq0, sq1, Piece.BBISHOP));
} else { // No promotion
moveList.add(getMoveObj(sq0, sq1, Piece.EMPTY));
}
}
/**
* Check if there is an attacking piece in a given direction starting from sq.
* The direction is given by delta.
* @param maxSteps Max steps until reaching a border. Set to 1 for non-sliding pieces.
* @return The first piece in the given direction, or EMPTY if there is no piece
* in that direction.
*/
private static final int checkDirection(Position pos, int sq, int maxSteps, int delta) {
while (maxSteps > 0) {
sq += delta;
int p = pos.getPiece(sq);
if (p != Piece.EMPTY)
return p;
maxSteps--;
}
return Piece.EMPTY;
}
// Code to handle the Move cache.
private Move[] moveCache = new Move[2048];
private int movesInCache = 0;
private Object[] moveListCache = new Object[200];
private int moveListsInCache = 0;
private final Move getMoveObj(int from, int to, int promoteTo) {
if (movesInCache > 0) {
Move m = moveCache[--movesInCache];
m.from = from;
m.to = to;
m.promoteTo = promoteTo;
return m;
}
return new Move(from, to, promoteTo);
}
@SuppressWarnings("unchecked")
private final ArrayList<Move> getMoveListObj() {
if (moveListsInCache > 0) {
return (ArrayList<Move>)moveListCache[--moveListsInCache];
}
return new ArrayList<Move>(60);
}
/** Return all move objects in moveList to the move cache. */
public final void returnMoveList(ArrayList<Move> moveList) {
if (movesInCache + moveList.size() <= moveCache.length) {
int mlSize = moveList.size();
for (int mi = 0; mi < mlSize; mi++) {
moveCache[movesInCache++] = moveList.get(mi);
}
}
moveList.clear();
if (moveListsInCache < moveListCache.length) {
moveListCache[moveListsInCache++] = moveList;
}
}
public final void returnMove(Move m) {
if (movesInCache < moveCache.length) {
moveCache[movesInCache++] = m;
}
}
}

View File

@@ -0,0 +1,33 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
/**
* A small helper class that makes it possible to return two values from a function.
* @author petero
*/
public final class Pair<T1, T2> {
public final T1 first;
public final T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
}

View File

@@ -0,0 +1,62 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
/** A token in a PGN data stream. Used by the PGN parser. */
public class PgnToken {
// These are tokens according to the PGN spec
public static final int STRING = 0;
public static final int INTEGER = 1;
public static final int PERIOD = 2;
public static final int ASTERISK = 3;
public static final int LEFT_BRACKET = 4;
public static final int RIGHT_BRACKET = 5;
public static final int LEFT_PAREN = 6;
public static final int RIGHT_PAREN = 7;
public static final int NAG = 8;
public static final int SYMBOL = 9;
// These are not tokens according to the PGN spec, but the parser
// extracts these anyway for convenience.
public static final int COMMENT = 10;
public static final int EOF = 11;
// Actual token data
int type;
String token;
PgnToken(int type, String token) {
this.type = type;
this.token = token;
}
public interface PgnTokenReceiver {
/** If this method returns false, the object needs a full reinitialization, using clear() and processToken(). */
public boolean isUpToDate();
/** Clear object state. */
public void clear();
/** Update object state with one token from a PGN game. */
public void processToken(GameTree.Node node, int type, String token);
/** Change current move number. */
public void setCurrent(GameTree.Node node);
};
}

View File

@@ -0,0 +1,57 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
/**
* Constants for different piece types.
* @author petero
*/
public class Piece {
public static final int EMPTY = 0;
public static final int WKING = 1;
public static final int WQUEEN = 2;
public static final int WROOK = 3;
public static final int WBISHOP = 4;
public static final int WKNIGHT = 5;
public static final int WPAWN = 6;
public static final int BKING = 7;
public static final int BQUEEN = 8;
public static final int BROOK = 9;
public static final int BBISHOP = 10;
public static final int BKNIGHT = 11;
public static final int BPAWN = 12;
public static final int nPieceTypes = 13;
/**
* Return true if p is a white piece, false otherwise.
* Note that if p is EMPTY, an unspecified value is returned.
*/
public static boolean isWhite(int pType) {
return pType < BKING;
}
public static int makeWhite(int pType) {
return pType < BKING ? pType : pType - (BKING - WKING);
}
public static int makeBlack(int pType) {
return ((pType >= WKING) && (pType <= WPAWN)) ? pType + (BKING - WKING) : pType;
}
}

View File

@@ -0,0 +1,430 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Stores the state of a chess position.
* All required state is stored, except for all previous positions
* since the last capture or pawn move. That state is only needed
* for three-fold repetition draw detection, and is better stored
* in a separate hash table.
* @author petero
*/
public class Position {
private int[] squares;
public boolean whiteMove;
/** Bit definitions for the castleMask bit mask. */
public static final int A1_CASTLE = 0; /** White long castle. */
public static final int H1_CASTLE = 1; /** White short castle. */
public static final int A8_CASTLE = 2; /** Black long castle. */
public static final int H8_CASTLE = 3; /** Black short castle. */
private int castleMask;
private int epSquare;
/** Number of half-moves since last 50-move reset. */
public int halfMoveClock;
/** Game move number, starting from 1. */
public int fullMoveCounter;
private long hashKey; // Cached Zobrist hash key
private int wKingSq, bKingSq; // Cached king positions
/** Initialize board to empty position. */
public Position() {
squares = new int[64];
for (int i = 0; i < 64; i++)
squares[i] = Piece.EMPTY;
whiteMove = true;
castleMask = 0;
epSquare = -1;
halfMoveClock = 0;
fullMoveCounter = 1;
hashKey = computeZobristHash();
wKingSq = bKingSq = -1;
}
public Position(Position other) {
squares = new int[64];
System.arraycopy(other.squares, 0, squares, 0, 64);
whiteMove = other.whiteMove;
castleMask = other.castleMask;
epSquare = other.epSquare;
halfMoveClock = other.halfMoveClock;
fullMoveCounter = other.fullMoveCounter;
hashKey = other.hashKey;
wKingSq = other.wKingSq;
bKingSq = other.bKingSq;
}
@Override
public boolean equals(Object o) {
if ((o == null) || (o.getClass() != this.getClass()))
return false;
Position other = (Position)o;
if (!drawRuleEquals(other))
return false;
if (halfMoveClock != other.halfMoveClock)
return false;
if (fullMoveCounter != other.fullMoveCounter)
return false;
if (hashKey != other.hashKey)
return false;
return true;
}
@Override
public int hashCode() {
return (int)hashKey;
}
/**
* Return Zobrish hash value for the current position.
* Everything except the move counters are included in the hash value.
*/
public final long zobristHash() {
return hashKey;
}
/**
* Decide if two positions are equal in the sense of the draw by repetition rule.
* @return True if positions are equal, false otherwise.
*/
final public boolean drawRuleEquals(Position other) {
for (int i = 0; i < 64; i++) {
if (squares[i] != other.squares[i])
return false;
}
if (whiteMove != other.whiteMove)
return false;
if (castleMask != other.castleMask)
return false;
if (epSquare != other.epSquare)
return false;
return true;
}
public final void setWhiteMove(boolean whiteMove) {
if (whiteMove != this.whiteMove) {
hashKey ^= whiteHashKey;
this.whiteMove = whiteMove;
}
}
/** Return index in squares[] vector corresponding to (x,y). */
public final static int getSquare(int x, int y) {
return y * 8 + x;
}
/** Return x position (file) corresponding to a square. */
public final static int getX(int square) {
return square & 7;
}
/** Return y position (rank) corresponding to a square. */
public final static int getY(int square) {
return square >> 3;
}
/** Return true if (x,y) is a dark square. */
public final static boolean darkSquare(int x, int y) {
return (x & 1) == (y & 1);
}
/** Return piece occuping a square. */
public final int getPiece(int square) {
return squares[square];
}
/** Set a square to a piece value. */
public final void setPiece(int square, int piece) {
// Update hash key
int oldPiece = squares[square];
hashKey ^= psHashKeys[oldPiece][square];
hashKey ^= psHashKeys[piece][square];
// Update board
squares[square] = piece;
// Update king position
if (piece == Piece.WKING) {
wKingSq = square;
} else if (piece == Piece.BKING) {
bKingSq = square;
}
}
/** Return true if white long castling right has not been lost. */
public final boolean a1Castle() {
return (castleMask & (1 << A1_CASTLE)) != 0;
}
/** Return true if white short castling right has not been lost. */
public final boolean h1Castle() {
return (castleMask & (1 << H1_CASTLE)) != 0;
}
/** Return true if black long castling right has not been lost. */
public final boolean a8Castle() {
return (castleMask & (1 << A8_CASTLE)) != 0;
}
/** Return true if black short castling right has not been lost. */
public final boolean h8Castle() {
return (castleMask & (1 << H8_CASTLE)) != 0;
}
/** Bitmask describing castling rights. */
public final int getCastleMask() {
return castleMask;
}
public final void setCastleMask(int castleMask) {
hashKey ^= castleHashKeys[this.castleMask];
hashKey ^= castleHashKeys[castleMask];
this.castleMask = castleMask;
}
/** En passant square, or -1 if no ep possible. */
public final int getEpSquare() {
return epSquare;
}
public final void setEpSquare(int epSquare) {
if (this.epSquare != epSquare) {
hashKey ^= epHashKeys[(this.epSquare >= 0) ? getX(this.epSquare) + 1 : 0];
hashKey ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0];
this.epSquare = epSquare;
}
}
public final int getKingSq(boolean whiteMove) {
return whiteMove ? wKingSq : bKingSq;
}
/**
* Count number of pieces of a certain type.
*/
public final int nPieces(int pType) {
int ret = 0;
for (int sq = 0; sq < 64; sq++) {
if (squares[sq] == pType)
ret++;
}
return ret;
}
/** Apply a move to the current position. */
public final void makeMove(Move move, UndoInfo ui) {
ui.capturedPiece = squares[move.to];
ui.castleMask = castleMask;
ui.epSquare = epSquare;
ui.halfMoveClock = halfMoveClock;
boolean wtm = whiteMove;
int p = squares[move.from];
int capP = squares[move.to];
boolean nullMove = (move.from == 0) && (move.to == 0);
if (nullMove || (capP != Piece.EMPTY) || (p == (wtm ? Piece.WPAWN : Piece.BPAWN))) {
halfMoveClock = 0;
} else {
halfMoveClock++;
}
if (!wtm) {
fullMoveCounter++;
}
// Handle castling
int king = wtm ? Piece.WKING : Piece.BKING;
int k0 = move.from;
if (p == king) {
if (move.to == k0 + 2) { // O-O
setPiece(k0 + 1, squares[k0 + 3]);
setPiece(k0 + 3, Piece.EMPTY);
} else if (move.to == k0 - 2) { // O-O-O
setPiece(k0 - 1, squares[k0 - 4]);
setPiece(k0 - 4, Piece.EMPTY);
}
if (wtm) {
setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
} else {
setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
}
}
if (!nullMove) {
int rook = wtm ? Piece.WROOK : Piece.BROOK;
if (p == rook) {
removeCastleRights(move.from);
}
int oRook = wtm ? Piece.BROOK : Piece.WROOK;
if (capP == oRook) {
removeCastleRights(move.to);
}
}
// Handle en passant and epSquare
int prevEpSquare = epSquare;
setEpSquare(-1);
if (p == Piece.WPAWN) {
if (move.to - move.from == 2 * 8) {
int x = Position.getX(move.to);
if ( ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) ||
((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) {
setEpSquare(move.from + 8);
}
} else if (move.to == prevEpSquare) {
setPiece(move.to - 8, Piece.EMPTY);
}
} else if (p == Piece.BPAWN) {
if (move.to - move.from == -2 * 8) {
int x = Position.getX(move.to);
if ( ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) ||
((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) {
setEpSquare(move.from - 8);
}
} else if (move.to == prevEpSquare) {
setPiece(move.to + 8, Piece.EMPTY);
}
}
// Perform move
setPiece(move.from, Piece.EMPTY);
// Handle promotion
if (move.promoteTo != Piece.EMPTY) {
setPiece(move.to, move.promoteTo);
} else {
setPiece(move.to, p);
}
setWhiteMove(!wtm);
}
public final void unMakeMove(Move move, UndoInfo ui) {
setWhiteMove(!whiteMove);
int p = squares[move.to];
setPiece(move.from, p);
setPiece(move.to, ui.capturedPiece);
setCastleMask(ui.castleMask);
setEpSquare(ui.epSquare);
halfMoveClock = ui.halfMoveClock;
boolean wtm = whiteMove;
if (move.promoteTo != Piece.EMPTY) {
p = wtm ? Piece.WPAWN : Piece.BPAWN;
setPiece(move.from, p);
}
if (!wtm) {
fullMoveCounter--;
}
// Handle castling
int king = wtm ? Piece.WKING : Piece.BKING;
int k0 = move.from;
if (p == king) {
if (move.to == k0 + 2) { // O-O
setPiece(k0 + 3, squares[k0 + 1]);
setPiece(k0 + 1, Piece.EMPTY);
} else if (move.to == k0 - 2) { // O-O-O
setPiece(k0 - 4, squares[k0 - 1]);
setPiece(k0 - 1, Piece.EMPTY);
}
}
// Handle en passant
if (move.to == epSquare) {
if (p == Piece.WPAWN) {
setPiece(move.to - 8, Piece.BPAWN);
} else if (p == Piece.BPAWN) {
setPiece(move.to + 8, Piece.WPAWN);
}
}
}
private final void removeCastleRights(int square) {
if (square == Position.getSquare(0, 0)) {
setCastleMask(castleMask & ~(1 << Position.A1_CASTLE));
} else if (square == Position.getSquare(7, 0)) {
setCastleMask(castleMask & ~(1 << Position.H1_CASTLE));
} else if (square == Position.getSquare(0, 7)) {
setCastleMask(castleMask & ~(1 << Position.A8_CASTLE));
} else if (square == Position.getSquare(7, 7)) {
setCastleMask(castleMask & ~(1 << Position.H8_CASTLE));
}
}
/* ------------- Hashing code ------------------ */
private static long[][] psHashKeys; // [piece][square]
private static long whiteHashKey;
private static long[] castleHashKeys; // [castleMask]
private static long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep)
static {
psHashKeys = new long[Piece.nPieceTypes][64];
castleHashKeys = new long[16];
epHashKeys = new long[9];
int rndNo = 0;
for (int p = 0; p < Piece.nPieceTypes; p++) {
for (int sq = 0; sq < 64; sq++) {
psHashKeys[p][sq] = getRandomHashVal(rndNo++);
}
}
whiteHashKey = getRandomHashVal(rndNo++);
for (int cm = 0; cm < castleHashKeys.length; cm++)
castleHashKeys[cm] = getRandomHashVal(rndNo++);
for (int f = 0; f < epHashKeys.length; f++)
epHashKeys[f] = getRandomHashVal(rndNo++);
}
/**
* Compute the Zobrist hash value non-incrementally. Only useful for test programs.
*/
final long computeZobristHash() {
long hash = 0;
for (int sq = 0; sq < 64; sq++) {
int p = squares[sq];
hash ^= psHashKeys[p][sq];
}
if (whiteMove)
hash ^= whiteHashKey;
hash ^= castleHashKeys[castleMask];
hash ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0];
return hash;
}
private final static long getRandomHashVal(int rndNo) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] input = new byte[4];
for (int i = 0; i < 4; i++)
input[i] = (byte)((rndNo >> (i * 8)) & 0xff);
byte[] digest = md.digest(input);
long ret = 0;
for (int i = 0; i < 8; i++) {
ret ^= ((long)digest[i]) << (i * 8);
}
return ret;
} catch (NoSuchAlgorithmException ex) {
throw new UnsupportedOperationException("SHA-1 not available");
}
}
/** Useful for debugging. */
public final String toString() {
return TextIO.asciiBoard(this);
}
}

View File

@@ -0,0 +1,80 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
import java.util.List;
/**
* Used to get various search information during search
*/
public interface SearchListener {
public final static class PvInfo {
int depth;
int score;
int time;
int nodes;
int nps;
boolean isMate;
boolean upperBound;
boolean lowerBound;
ArrayList<Move> pv;
String pvStr = "";
public PvInfo(PvInfo pvi) {
depth = pvi.depth;
score = pvi.score;
time = pvi.time;
nodes = pvi.nodes;
nps = pvi.nps;
isMate = pvi.isMate;
upperBound = pvi.upperBound;
lowerBound = pvi.lowerBound;
pv = new ArrayList<Move>(pvi.pv.size());
for (int i = 0; i < pvi.pv.size(); i++)
pv.add(pvi.pv.get(i));
pvStr = pvi.pvStr;
}
public PvInfo(int depth, int score, int time, int nodes, int nps,
boolean isMate, boolean upperBound, boolean lowerBound, ArrayList<Move> pv) {
this.depth = depth;
this.score = score;
this.time = time;
this.nodes = nodes;
this.nps = nps;
this.isMate = isMate;
this.upperBound = upperBound;
this.lowerBound = lowerBound;
this.pv = pv;
}
public final void removeFirstMove() {
if (!pv.isEmpty())
pv.remove(0);
}
}
public void notifyDepth(int depth);
public void notifyCurrMove(Position pos, Move m, int moveNr);
public void notifyPV(Position pos, ArrayList<PvInfo> pvInfo, boolean isPonder);
public void notifyStats(int nodes, int nps, int time);
public void notifyBookInfo(String bookInfo, List<Move> moveList);
}

View File

@@ -0,0 +1,708 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author petero
*/
public class TextIO {
static public final String startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
/** Parse a FEN string and return a chess Position object. */
public static final Position readFEN(String fen) throws ChessParseError {
Position pos = new Position();
String[] words = fen.split(" ");
if (words.length < 2) {
throw new ChessParseError("Too few spaces");
}
for (int i = 0; i < words.length; i++) {
words[i] = words[i].trim();
}
// Piece placement
int row = 7;
int col = 0;
for (int i = 0; i < words[0].length(); i++) {
char c = words[0].charAt(i);
switch (c) {
case '1': col += 1; break;
case '2': col += 2; break;
case '3': col += 3; break;
case '4': col += 4; break;
case '5': col += 5; break;
case '6': col += 6; break;
case '7': col += 7; break;
case '8': col += 8; break;
case '/': row--; col = 0; break;
case 'P': safeSetPiece(pos, col, row, Piece.WPAWN); col++; break;
case 'N': safeSetPiece(pos, col, row, Piece.WKNIGHT); col++; break;
case 'B': safeSetPiece(pos, col, row, Piece.WBISHOP); col++; break;
case 'R': safeSetPiece(pos, col, row, Piece.WROOK); col++; break;
case 'Q': safeSetPiece(pos, col, row, Piece.WQUEEN); col++; break;
case 'K': safeSetPiece(pos, col, row, Piece.WKING); col++; break;
case 'p': safeSetPiece(pos, col, row, Piece.BPAWN); col++; break;
case 'n': safeSetPiece(pos, col, row, Piece.BKNIGHT); col++; break;
case 'b': safeSetPiece(pos, col, row, Piece.BBISHOP); col++; break;
case 'r': safeSetPiece(pos, col, row, Piece.BROOK); col++; break;
case 'q': safeSetPiece(pos, col, row, Piece.BQUEEN); col++; break;
case 'k': safeSetPiece(pos, col, row, Piece.BKING); col++; break;
default: throw new ChessParseError("Invalid piece", pos);
}
}
if (words[1].length() == 0) {
throw new ChessParseError("Invalid side", pos);
}
pos.setWhiteMove(words[1].charAt(0) == 'w');
// Castling rights
int castleMask = 0;
if (words.length > 2) {
for (int i = 0; i < words[2].length(); i++) {
char c = words[2].charAt(i);
switch (c) {
case 'K':
castleMask |= (1 << Position.H1_CASTLE);
break;
case 'Q':
castleMask |= (1 << Position.A1_CASTLE);
break;
case 'k':
castleMask |= (1 << Position.H8_CASTLE);
break;
case 'q':
castleMask |= (1 << Position.A8_CASTLE);
break;
case '-':
break;
default:
throw new ChessParseError("Invalid castling flags", pos);
}
}
}
pos.setCastleMask(castleMask);
removeBogusCastleFlags(pos);
if (words.length > 3) {
// En passant target square
String epString = words[3];
if (!epString.equals("-")) {
if (epString.length() < 2) {
throw new ChessParseError("Invalid en passant square", pos);
}
pos.setEpSquare(getSquare(epString));
}
}
try {
if (words.length > 4) {
pos.halfMoveClock = Integer.parseInt(words[4]);
}
if (words.length > 5) {
pos.fullMoveCounter = Integer.parseInt(words[5]);
}
} catch (NumberFormatException nfe) {
// Ignore errors here, since the fields are optional
}
// Each side must have exactly one king
int wKings = 0;
int bKings = 0;
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
int p = pos.getPiece(Position.getSquare(x, y));
if (p == Piece.WKING) {
wKings++;
} else if (p == Piece.BKING) {
bKings++;
}
}
}
if (wKings != 1) {
throw new ChessParseError("White must have exactly one king", pos);
}
if (bKings != 1) {
throw new ChessParseError("Black must have exactly one king", pos);
}
// Make sure king can not be captured
Position pos2 = new Position(pos);
pos2.setWhiteMove(!pos.whiteMove);
if (MoveGen.inCheck(pos2)) {
throw new ChessParseError("King capture possible", pos);
}
fixupEPSquare(pos);
return pos;
}
public static final void removeBogusCastleFlags(Position pos) {
int castleMask = pos.getCastleMask();
int validCastle = 0;
if (pos.getPiece(4) == Piece.WKING) {
if (pos.getPiece(0) == Piece.WROOK) validCastle |= (1 << Position.A1_CASTLE);
if (pos.getPiece(7) == Piece.WROOK) validCastle |= (1 << Position.H1_CASTLE);
}
if (pos.getPiece(60) == Piece.BKING) {
if (pos.getPiece(56) == Piece.BROOK) validCastle |= (1 << Position.A8_CASTLE);
if (pos.getPiece(63) == Piece.BROOK) validCastle |= (1 << Position.H8_CASTLE);
}
castleMask &= validCastle;
pos.setCastleMask(castleMask);
}
/** Remove pseudo-legal EP square if it is not legal, ie would leave king in check. */
public static final void fixupEPSquare(Position pos) {
int epSquare = pos.getEpSquare();
if (epSquare >= 0) {
ArrayList<Move> moves = MoveGen.instance.pseudoLegalMoves(pos);
moves = MoveGen.removeIllegal(pos, moves);
boolean epValid = false;
for (Move m : moves) {
if (m.to == epSquare) {
if (pos.getPiece(m.from) == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) {
epValid = true;
break;
}
}
}
if (!epValid) {
pos.setEpSquare(-1);
}
}
}
private static final void safeSetPiece(Position pos, int col, int row, int p) throws ChessParseError {
if (row < 0) throw new ChessParseError("Too many rows");
if (col > 7) throw new ChessParseError("Too many columns");
if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) {
if ((row == 0) || (row == 7))
throw new ChessParseError("Pawn on first/last rank");
}
pos.setPiece(Position.getSquare(col, row), p);
}
/** Return a FEN string corresponding to a chess Position object. */
public static final String toFEN(Position pos) {
StringBuilder ret = new StringBuilder();
// Piece placement
for (int r = 7; r >=0; r--) {
int numEmpty = 0;
for (int c = 0; c < 8; c++) {
int p = pos.getPiece(Position.getSquare(c, r));
if (p == Piece.EMPTY) {
numEmpty++;
} else {
if (numEmpty > 0) {
ret.append(numEmpty);
numEmpty = 0;
}
switch (p) {
case Piece.WKING: ret.append('K'); break;
case Piece.WQUEEN: ret.append('Q'); break;
case Piece.WROOK: ret.append('R'); break;
case Piece.WBISHOP: ret.append('B'); break;
case Piece.WKNIGHT: ret.append('N'); break;
case Piece.WPAWN: ret.append('P'); break;
case Piece.BKING: ret.append('k'); break;
case Piece.BQUEEN: ret.append('q'); break;
case Piece.BROOK: ret.append('r'); break;
case Piece.BBISHOP: ret.append('b'); break;
case Piece.BKNIGHT: ret.append('n'); break;
case Piece.BPAWN: ret.append('p'); break;
default: throw new RuntimeException();
}
}
}
if (numEmpty > 0) {
ret.append(numEmpty);
}
if (r > 0) {
ret.append('/');
}
}
ret.append(pos.whiteMove ? " w " : " b ");
// Castling rights
boolean anyCastle = false;
if (pos.h1Castle()) {
ret.append('K');
anyCastle = true;
}
if (pos.a1Castle()) {
ret.append('Q');
anyCastle = true;
}
if (pos.h8Castle()) {
ret.append('k');
anyCastle = true;
}
if (pos.a8Castle()) {
ret.append('q');
anyCastle = true;
}
if (!anyCastle) {
ret.append('-');
}
// En passant target square
{
ret.append(' ');
if (pos.getEpSquare() >= 0) {
int x = Position.getX(pos.getEpSquare());
int y = Position.getY(pos.getEpSquare());
ret.append((char)(x + 'a'));
ret.append((char)(y + '1'));
} else {
ret.append('-');
}
}
// Move counters
ret.append(' ');
ret.append(pos.halfMoveClock);
ret.append(' ');
ret.append(pos.fullMoveCounter);
return ret.toString();
}
/**
* Convert a chess move to human readable form.
* @param pos The chess position.
* @param move The executed move.
* @param longForm If true, use long notation, eg Ng1-f3.
* Otherwise, use short notation, eg Nf3
*/
public static final String moveToString(Position pos, Move move, boolean longForm) {
ArrayList<Move> moves = MoveGen.instance.pseudoLegalMoves(pos);
moves = MoveGen.removeIllegal(pos, moves);
return moveToString(pos, move, longForm, moves);
}
private static final String moveToString(Position pos, Move move, boolean longForm,
List<Move> moves) {
if (move.equals(new Move(0, 0, 0)))
return "--";
StringBuilder ret = new StringBuilder();
int wKingOrigPos = Position.getSquare(4, 0);
int bKingOrigPos = Position.getSquare(4, 7);
if (move.from == wKingOrigPos && pos.getPiece(wKingOrigPos) == Piece.WKING) {
// Check white castle
if (move.to == Position.getSquare(6, 0)) {
ret.append("O-O");
} else if (move.to == Position.getSquare(2, 0)) {
ret.append("O-O-O");
}
} else if (move.from == bKingOrigPos && pos.getPiece(bKingOrigPos) == Piece.BKING) {
// Check white castle
if (move.to == Position.getSquare(6, 7)) {
ret.append("O-O");
} else if (move.to == Position.getSquare(2, 7)) {
ret.append("O-O-O");
}
}
if (ret.length() == 0) {
int p = pos.getPiece(move.from);
ret.append(pieceToChar(p));
int x1 = Position.getX(move.from);
int y1 = Position.getY(move.from);
int x2 = Position.getX(move.to);
int y2 = Position.getY(move.to);
if (longForm) {
ret.append((char)(x1 + 'a'));
ret.append((char) (y1 + '1'));
ret.append(isCapture(pos, move) ? 'x' : '-');
} else {
if (p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) {
if (isCapture(pos, move)) {
ret.append((char) (x1 + 'a'));
}
} else {
int numSameTarget = 0;
int numSameFile = 0;
int numSameRow = 0;
int mSize = moves.size();
for (int mi = 0; mi < mSize; mi++) {
Move m = moves.get(mi);
if ((pos.getPiece(m.from) == p) && (m.to == move.to)) {
numSameTarget++;
if (Position.getX(m.from) == x1)
numSameFile++;
if (Position.getY(m.from) == y1)
numSameRow++;
}
}
if (numSameTarget < 2) {
// No file/row info needed
} else if (numSameFile < 2) {
ret.append((char) (x1 + 'a')); // Only file info needed
} else if (numSameRow < 2) {
ret.append((char) (y1 + '1')); // Only row info needed
} else {
ret.append((char) (x1 + 'a')); // File and row info needed
ret.append((char) (y1 + '1'));
}
}
if (isCapture(pos, move)) {
ret.append('x');
}
}
ret.append((char) (x2 + 'a'));
ret.append((char) (y2 + '1'));
if (move.promoteTo != Piece.EMPTY)
ret.append(pieceToChar(move.promoteTo));
}
UndoInfo ui = new UndoInfo();
pos.makeMove(move, ui);
boolean givesCheck = MoveGen.inCheck(pos);
if (givesCheck) {
ArrayList<Move> nextMoves = MoveGen.instance.pseudoLegalMoves(pos);
nextMoves = MoveGen.removeIllegal(pos, nextMoves);
if (nextMoves.size() == 0) {
ret.append('#');
} else {
ret.append('+');
}
}
pos.unMakeMove(move, ui);
return ret.toString();
}
private static final boolean isCapture(Position pos, Move move) {
if (pos.getPiece(move.to) == Piece.EMPTY) {
int p = pos.getPiece(move.from);
if ((p == (pos.whiteMove ? Piece.WPAWN : Piece.BPAWN)) && (move.to == pos.getEpSquare())) {
return true;
} else {
return false;
}
} else {
return true;
}
}
private final static class MoveInfo {
int piece; // -1 for unspecified
int fromX, fromY, toX, toY; // -1 for unspecified
int promPiece; // -1 for unspecified
MoveInfo() { piece = fromX = fromY = toX = toY = promPiece = -1; }
}
/**
* Convert a chess move string to a Move object.
* The string may specify any combination of piece/source/target/promotion
* information as long as it matches exactly one valid move.
*/
public static final Move stringToMove(Position pos, String strMove) {
if (strMove.equals("--"))
return new Move(0, 0, 0);
strMove = strMove.replaceAll("=", "");
strMove = strMove.replaceAll("\\+", "");
strMove = strMove.replaceAll("#", "");
boolean wtm = pos.whiteMove;
MoveInfo info = new MoveInfo();
boolean capture = false;
if (strMove.equals("O-O") || strMove.equals("0-0") || strMove.equals("o-o")) {
info.piece = wtm ? Piece.WKING : Piece.BKING;
info.fromX = 4;
info.toX = 6;
info.fromY = info.toY = wtm ? 0 : 7;
info.promPiece= Piece.EMPTY;
} else if (strMove.equals("O-O-O") || strMove.equals("0-0-0") || strMove.equals("o-o-o")) {
info.piece = wtm ? Piece.WKING : Piece.BKING;
info.fromX = 4;
info.toX = 2;
info.fromY = info.toY = wtm ? 0 : 7;
info.promPiece= Piece.EMPTY;
} else {
boolean atToSq = false;
for (int i = 0; i < strMove.length(); i++) {
char c = strMove.charAt(i);
if (i == 0) {
int piece = charToPiece(wtm, c);
if (piece >= 0) {
info.piece = piece;
continue;
}
}
int tmpX = c - 'a';
if ((tmpX >= 0) && (tmpX < 8)) {
if (atToSq || (info.fromX >= 0))
info.toX = tmpX;
else
info.fromX = tmpX;
}
int tmpY = c - '1';
if ((tmpY >= 0) && (tmpY < 8)) {
if (atToSq || (info.fromY >= 0))
info.toY = tmpY;
else
info.fromY = tmpY;
}
if ((c == 'x') || (c == '-')) {
atToSq = true;
if (c == 'x')
capture = true;
}
if (i == strMove.length() - 1) {
int promPiece = charToPiece(wtm, c);
if (promPiece >= 0) {
info.promPiece = promPiece;
}
}
}
if ((info.fromX >= 0) && (info.toX < 0)) {
info.toX = info.fromX;
info.fromX = -1;
}
if ((info.fromY >= 0) && (info.toY < 0)) {
info.toY = info.fromY;
info.fromY = -1;
}
if (info.piece < 0) {
boolean haveAll = (info.fromX >= 0) && (info.fromY >= 0) &&
(info.toX >= 0) && (info.toY >= 0);
if (!haveAll)
info.piece = wtm ? Piece.WPAWN : Piece.BPAWN;
}
if (info.promPiece < 0)
info.promPiece = Piece.EMPTY;
}
ArrayList<Move> moves = MoveGen.instance.pseudoLegalMoves(pos);
moves = MoveGen.removeIllegal(pos, moves);
ArrayList<Move> matches = new ArrayList<Move>(2);
for (int i = 0; i < moves.size(); i++) {
Move m = moves.get(i);
int p = pos.getPiece(m.from);
boolean match = true;
if ((info.piece >= 0) && (info.piece != p))
match = false;
if ((info.fromX >= 0) && (info.fromX != Position.getX(m.from)))
match = false;
if ((info.fromY >= 0) && (info.fromY != Position.getY(m.from)))
match = false;
if ((info.toX >= 0) && (info.toX != Position.getX(m.to)))
match = false;
if ((info.toY >= 0) && (info.toY != Position.getY(m.to)))
match = false;
if ((info.promPiece >= 0) && (info.promPiece != m.promoteTo))
match = false;
if (match) {
matches.add(m);
}
}
int nMatches = matches.size();
if (nMatches == 0)
return null;
else if (nMatches == 1)
return matches.get(0);
if (!capture)
return null;
Move move = null;
for (int i = 0; i < matches.size(); i++) {
Move m = matches.get(i);
int capt = pos.getPiece(m.to);
if (capt != Piece.EMPTY) {
if (move == null)
move = m;
else
return null;
}
}
return move;
}
/** Convert a move object to UCI string format. */
public static final String moveToUCIString(Move m) {
String ret = squareToString(m.from);
ret += squareToString(m.to);
switch (m.promoteTo) {
case Piece.WQUEEN:
case Piece.BQUEEN:
ret += "q";
break;
case Piece.WROOK:
case Piece.BROOK:
ret += "r";
break;
case Piece.WBISHOP:
case Piece.BBISHOP:
ret += "b";
break;
case Piece.WKNIGHT:
case Piece.BKNIGHT:
ret += "n";
break;
default:
break;
}
return ret;
}
/**
* Convert a string in UCI move format to a Move object.
* @return A move object, or null if move has invalid syntax
*/
public static final Move UCIstringToMove(String move) {
Move m = null;
if ((move.length() < 4) || (move.length() > 5))
return m;
int fromSq = TextIO.getSquare(move.substring(0, 2));
int toSq = TextIO.getSquare(move.substring(2, 4));
if ((fromSq < 0) || (toSq < 0)) {
return m;
}
char prom = ' ';
boolean white = true;
if (move.length() == 5) {
prom = move.charAt(4);
if (Position.getY(toSq) == 7) {
white = true;
} else if (Position.getY(toSq) == 0) {
white = false;
} else {
return m;
}
}
int promoteTo;
switch (prom) {
case ' ':
promoteTo = Piece.EMPTY;
break;
case 'q':
promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN;
break;
case 'r':
promoteTo = white ? Piece.WROOK : Piece.BROOK;
break;
case 'b':
promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP;
break;
case 'n':
promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT;
break;
default:
return m;
}
m = new Move(fromSq, toSq, promoteTo);
return m;
}
/**
* Convert a string, such as "e4" to a square number.
* @return The square number, or -1 if not a legal square.
*/
public static final int getSquare(String s) {
int x = s.charAt(0) - 'a';
int y = s.charAt(1) - '1';
if ((x < 0) || (x > 7) || (y < 0) || (y > 7))
return -1;
return Position.getSquare(x, y);
}
/**
* Convert a square number to a string, such as "e4".
*/
public static final String squareToString(int square) {
StringBuilder ret = new StringBuilder();
int x = Position.getX(square);
int y = Position.getY(square);
ret.append((char) (x + 'a'));
ret.append((char) (y + '1'));
return ret.toString();
}
/**
* Create an ascii representation of a position.
*/
public static final String asciiBoard(Position pos) {
StringBuilder ret = new StringBuilder(400);
String nl = String.format("%n");
ret.append(" +----+----+----+----+----+----+----+----+"); ret.append(nl);
for (int y = 7; y >= 0; y--) {
ret.append(" |");
for (int x = 0; x < 8; x++) {
ret.append(' ');
int p = pos.getPiece(Position.getSquare(x, y));
if (p == Piece.EMPTY) {
boolean dark = Position.darkSquare(x, y);
ret.append(dark ? ".. |" : " |");
} else {
ret.append(Piece.isWhite(p) ? ' ' : '*');
String pieceName = pieceToChar(p);
if (pieceName.length() == 0)
pieceName = "P";
ret.append(pieceName);
ret.append(" |");
}
}
ret.append(nl);
ret.append(" +----+----+----+----+----+----+----+----+");
ret.append(nl);
}
return ret.toString();
}
private final static String pieceToChar(int p) {
switch (p) {
case Piece.WQUEEN: case Piece.BQUEEN: return "Q";
case Piece.WROOK: case Piece.BROOK: return "R";
case Piece.WBISHOP: case Piece.BBISHOP: return "B";
case Piece.WKNIGHT: case Piece.BKNIGHT: return "N";
case Piece.WKING: case Piece.BKING: return "K";
}
return "";
}
private final static int charToPiece(boolean white, char c) {
switch (c) {
case 'Q': case 'q': return white ? Piece.WQUEEN : Piece.BQUEEN;
case 'R': case 'r': return white ? Piece.WROOK : Piece.BROOK;
case 'B': return white ? Piece.WBISHOP : Piece.BBISHOP;
case 'N': case 'n': return white ? Piece.WKNIGHT : Piece.BKNIGHT;
case 'K': case 'k': return white ? Piece.WKING : Piece.BKING;
case 'P': case 'p': return white ? Piece.WPAWN : Piece.BPAWN;
}
return -1;
}
/** Add an = sign to a promotion move, as required by the PGN standard. */
public final static String pgnPromotion(String str) {
int idx = str.length() - 1;
while (idx > 0) {
char c = str.charAt(idx);
if ((c != '#') && (c != '+'))
break;
idx--;
}
if ((idx > 0) && (charToPiece(true, str.charAt(idx)) != -1))
idx--;
return str.substring(0, idx + 1) + '=' + str.substring(idx + 1, str.length());
}
}

View File

@@ -0,0 +1,131 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
public class TimeControl {
private long timeControl;
private int movesPerSession;
private long increment;
private long whiteBaseTime;
private long blackBaseTime;
int currentMove;
boolean whiteToMove;
long elapsed; // Accumulated elapsed time for this move.
long timerT0; // Time when timer started. 0 if timer is stopped.
/** Constructor. Sets time control to "game in 5min". */
public TimeControl() {
setTimeControl(5 * 60 * 1000, 0, 0);
reset();
}
public final void reset() {
currentMove = 1;
whiteToMove = true;
elapsed = 0;
timerT0 = 0;
}
/** Set time control to "moves" moves in "time" milliseconds, + inc milliseconds per move. */
public final void setTimeControl(long time, int moves, long inc) {
timeControl = time;
movesPerSession = moves;
increment = inc;
}
public final void setCurrentMove(int move, boolean whiteToMove, long whiteBaseTime, long blackBaseTime) {
currentMove = move;
this.whiteToMove = whiteToMove;
this.whiteBaseTime = whiteBaseTime;
this.blackBaseTime = blackBaseTime;
timerT0 = 0;
elapsed = 0;
}
public final boolean clockRunning() {
return timerT0 != 0;
}
public final void startTimer(long now) {
if (!clockRunning()) {
timerT0 = now;
}
}
public final void stopTimer(long now) {
if (clockRunning()) {
long timerT1 = now;
long currElapsed = timerT1 - timerT0;
timerT0 = 0;
if (currElapsed > 0) {
elapsed += currElapsed;
}
}
}
/** Compute new remaining time after a move is made. */
public final int moveMade(long now, boolean useIncrement) {
stopTimer(now);
long remaining = getRemainingTime(whiteToMove, now);
if (useIncrement) {
remaining += increment;
if (getMovesToTC() == 1)
remaining += timeControl;
}
elapsed = 0;
return (int)remaining;
}
/** Get remaining time */
public final int getRemainingTime(boolean whiteToMove, long now) {
long remaining = whiteToMove ? whiteBaseTime : blackBaseTime;
if (whiteToMove == this.whiteToMove) {
remaining -= elapsed;
if (timerT0 != 0) {
remaining -= now - timerT0;
}
}
return (int)remaining;
}
public final int getInitialTime() {
return (int)timeControl;
}
public final int getIncrement() {
return (int)increment;
}
public final int getMovesToTC() {
if (movesPerSession <= 0)
return 0;
int nextTC = 1;
while (nextTC <= currentMove)
nextTC += movesPerSession;
return nextTC - currentMove;
}
public final int getMovesPerSession() {
return movesPerSession;
}
}

View File

@@ -0,0 +1,31 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gamelogic;
/**
* Contains enough information to undo a previous move.
* Set by makeMove(). Used by unMakeMove().
* @author petero
*/
public class UndoInfo {
int capturedPiece;
int castleMask;
int epSquare;
int halfMoveClock;
}