mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2025-12-18 19:52:19 +01:00
Moved DroidFish project to trunk/
This commit is contained in:
57
DroidFish/src/org/petero/droidfish/BookOptions.java
Normal file
57
DroidFish/src/org/petero/droidfish/BookOptions.java
Normal 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;
|
||||
}
|
||||
}
|
||||
665
DroidFish/src/org/petero/droidfish/ChessBoard.java
Normal file
665
DroidFish/src/org/petero/droidfish/ChessBoard.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
DroidFish/src/org/petero/droidfish/ColorTheme.java
Normal file
105
DroidFish/src/org/petero/droidfish/ColorTheme.java
Normal 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];
|
||||
}
|
||||
}
|
||||
1929
DroidFish/src/org/petero/droidfish/DroidFish.java
Normal file
1929
DroidFish/src/org/petero/droidfish/DroidFish.java
Normal file
File diff suppressed because it is too large
Load Diff
72
DroidFish/src/org/petero/droidfish/GUIInterface.java
Normal file
72
DroidFish/src/org/petero/droidfish/GUIInterface.java
Normal 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();
|
||||
}
|
||||
111
DroidFish/src/org/petero/droidfish/GameMode.java
Normal file
111
DroidFish/src/org/petero/droidfish/GameMode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
27
DroidFish/src/org/petero/droidfish/MyScrollView.java
Normal file
27
DroidFish/src/org/petero/droidfish/MyScrollView.java
Normal 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);
|
||||
}
|
||||
}
|
||||
54
DroidFish/src/org/petero/droidfish/PGNOptions.java
Normal file
54
DroidFish/src/org/petero/droidfish/PGNOptions.java
Normal 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;
|
||||
}
|
||||
}
|
||||
183
DroidFish/src/org/petero/droidfish/SeekBarPreference.java
Normal file
183
DroidFish/src/org/petero/droidfish/SeekBarPreference.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
427
DroidFish/src/org/petero/droidfish/activities/EditBoard.java
Normal file
427
DroidFish/src/org/petero/droidfish/activities/EditBoard.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
121
DroidFish/src/org/petero/droidfish/activities/EditHeaders.java
Normal file
121
DroidFish/src/org/petero/droidfish/activities/EditHeaders.java
Normal 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();
|
||||
}
|
||||
}
|
||||
433
DroidFish/src/org/petero/droidfish/activities/EditPGN.java
Normal file
433
DroidFish/src/org/petero/droidfish/activities/EditPGN.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
262
DroidFish/src/org/petero/droidfish/activities/LoadScid.java
Normal file
262
DroidFish/src/org/petero/droidfish/activities/LoadScid.java
Normal 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();
|
||||
}
|
||||
}
|
||||
392
DroidFish/src/org/petero/droidfish/activities/PGNFile.java
Normal file
392
DroidFish/src/org/petero/droidfish/activities/PGNFile.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
740
DroidFish/src/org/petero/droidfish/engine/CtgBook.java
Normal file
740
DroidFish/src/org/petero/droidfish/engine/CtgBook.java
Normal 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;
|
||||
}
|
||||
}
|
||||
267
DroidFish/src/org/petero/droidfish/engine/DroidBook.java
Normal file
267
DroidFish/src/org/petero/droidfish/engine/DroidBook.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
36
DroidFish/src/org/petero/droidfish/engine/IOpeningBook.java
Normal file
36
DroidFish/src/org/petero/droidfish/engine/IOpeningBook.java
Normal 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);
|
||||
}
|
||||
159
DroidFish/src/org/petero/droidfish/engine/InternalBook.java
Normal file
159
DroidFish/src/org/petero/droidfish/engine/InternalBook.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
42
DroidFish/src/org/petero/droidfish/engine/NullBook.java
Normal file
42
DroidFish/src/org/petero/droidfish/engine/NullBook.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
420
DroidFish/src/org/petero/droidfish/engine/PolyglotBook.java
Normal file
420
DroidFish/src/org/petero/droidfish/engine/PolyglotBook.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
Normal file
51
DroidFish/src/org/petero/droidfish/engine/UCIEngine.java
Normal 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);
|
||||
}
|
||||
57
DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
Normal file
57
DroidFish/src/org/petero/droidfish/engine/UCIEngineBase.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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+");
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
466
DroidFish/src/org/petero/droidfish/gamelogic/Game.java
Normal file
466
DroidFish/src/org/petero/droidfish/gamelogic/Game.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
1396
DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java
Normal file
1396
DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java
Normal file
File diff suppressed because it is too large
Load Diff
70
DroidFish/src/org/petero/droidfish/gamelogic/Move.java
Normal file
70
DroidFish/src/org/petero/droidfish/gamelogic/Move.java
Normal 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);
|
||||
}
|
||||
}
|
||||
355
DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java
Normal file
355
DroidFish/src/org/petero/droidfish/gamelogic/MoveGen.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
DroidFish/src/org/petero/droidfish/gamelogic/Pair.java
Normal file
33
DroidFish/src/org/petero/droidfish/gamelogic/Pair.java
Normal 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;
|
||||
}
|
||||
}
|
||||
62
DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java
Normal file
62
DroidFish/src/org/petero/droidfish/gamelogic/PgnToken.java
Normal 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);
|
||||
};
|
||||
}
|
||||
57
DroidFish/src/org/petero/droidfish/gamelogic/Piece.java
Normal file
57
DroidFish/src/org/petero/droidfish/gamelogic/Piece.java
Normal 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;
|
||||
}
|
||||
}
|
||||
430
DroidFish/src/org/petero/droidfish/gamelogic/Position.java
Normal file
430
DroidFish/src/org/petero/droidfish/gamelogic/Position.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
708
DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java
Normal file
708
DroidFish/src/org/petero/droidfish/gamelogic/TextIO.java
Normal 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());
|
||||
}
|
||||
}
|
||||
131
DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java
Normal file
131
DroidFish/src/org/petero/droidfish/gamelogic/TimeControl.java
Normal 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;
|
||||
}
|
||||
}
|
||||
31
DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java
Normal file
31
DroidFish/src/org/petero/droidfish/gamelogic/UndoInfo.java
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user