DroidFish: Run the internal stockfish engine as a separate process instead of using jni + fork, which is not stable.

This commit is contained in:
Peter Osterlund 2012-01-04 21:02:26 +00:00
parent 915b0d6897
commit da605aa225
17 changed files with 253 additions and 271 deletions

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
<booleanAttribute key="org.eclipse.ant.uiSET_INPUTHANDLER" value="false"/>
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1325706655410_174&quot; label=&quot;working set&quot; name=&quot;working set&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/DroidFish/assets&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/DroidFish/build_copy_exe.xml"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<booleanAttribute key="org.eclipse.debug.core.capture_output" value="false"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_CONSOLE_OUTPUT_ON" value="false"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="DroidFish"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1325706830965_218&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/DroidFish/build_copy_exe.xml&quot; type=&quot;1&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/DroidFish/libs&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/DroidFish/build_copy_exe.xml}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/DroidFish}"/>
</launchConfiguration>

View File

@ -30,6 +30,16 @@
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/copy_stockfish.launch</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="copy_stockfish" name="Copy stockfish executables to assets">
<target name="copy_stockfish">
<copy file="${basedir}/libs/armeabi/stockfish" tofile="${basedir}/assets/stockfish-armeabi"/>
<copy file="${basedir}/libs/armeabi-v7a/stockfish" tofile="${basedir}/assets/stockfish-armeabi-v7a"/>
<copy file="${basedir}/libs/x86/stockfish" tofile="${basedir}/assets/stockfish-x86"/>
</target>
</project>

View File

@ -2,16 +2,14 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := stockfishjni
LOCAL_SRC_FILES := stockfishjni.cpp
LOCAL_MODULE := nativeutil
LOCAL_SRC_FILES := nativeutil.cpp
LOCAL_CFLAGS := \
-mandroid \
-DTARGET_OS=android -D__ANDROID__ \
-isystem $(SYSROOT)/usr/include
LOCAL_STATIC_LIBRARIES := stockfish
include $(BUILD_SHARED_LIBRARY)
include jni/stockfish/Android.mk

View File

@ -1,2 +1,3 @@
APP_ABI := all
APP_ABI := armeabi armeabi-v7a x86
APP_STL := stlport_static
APP_OPTIM := release

View File

@ -0,0 +1,32 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011-2012 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/>.
*/
#include <jni.h>
#include <unistd.h>
#include <stdlib.h>
/*
* Class: org_petero_droidfish_engine_NativeUtil
* Method: getNPhysicalProcessors
* Signature: ()I
*/
extern "C" JNIEXPORT jint JNICALL Java_org_petero_droidfish_engine_NativeUtil_getNPhysicalProcessors
(JNIEnv *, jclass)
{
return sysconf(_SC_NPROCESSORS_ONLN);
}

View File

@ -19,4 +19,4 @@ LOCAL_CFLAGS := -I$(LOCAL_PATH)/../stlport/stlport \
LOCAL_STATIC_LIBRARIES := stlport
include $(BUILD_STATIC_LIBRARY)
include $(BUILD_EXECUTABLE)

View File

@ -1,180 +0,0 @@
/*
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/>.
*/
#include <string.h>
#include <jni.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <deque>
#include <vector>
#include <sys/time.h>
int main(int argc, char* argv[]);
static int fdFromChild = -1;
static int fdToChild = -1;
static int childpid = -1;
/*
* Class: org_petero_droidfish_engine_StockFishJNI
* Method: startProcess
* Signature: ()V
*/
extern "C" JNIEXPORT void JNICALL Java_org_petero_droidfish_engine_StockFishJNI_startProcess
(JNIEnv* env, jobject obj)
{
if (childpid != -1)
kill(childpid, SIGKILL);
int fd1[2]; /* parent -> child */
int fd2[2]; /* child -> parent */
if (pipe(fd1) < 0)
exit(1);
if (pipe(fd2) < 0)
exit(1);
childpid = fork();
if (childpid == -1) {
exit(1);
}
if (childpid == 0) {
close(fd1[1]);
close(fd2[0]);
close(0); dup(fd1[0]); close(fd1[0]);
close(1); dup(fd2[1]); close(fd2[1]);
close(2); dup(1);
static char* argv[] = {(char*)"stockfish", NULL};
nice(10);
main(1, argv);
_exit(0);
} else {
close(fd1[0]);
close(fd2[1]);
fdFromChild = fd2[0];
fdToChild = fd1[1];
fcntl(fdFromChild, F_SETFL, O_NONBLOCK);
}
}
static std::deque<char> inBuf;
static bool getNextChar(int& c, int timeoutMillis) {
if (inBuf.empty()) {
fd_set readfds, exceptfds;
FD_ZERO(&readfds);
FD_SET(fdFromChild, &readfds);
FD_ZERO(&exceptfds);
FD_SET(fdFromChild, &exceptfds);
struct timeval tv;
tv.tv_sec = timeoutMillis / 1000;
tv.tv_usec = (timeoutMillis % 1000) * 1000;
int ret = select(fdFromChild + 1, &readfds, NULL, &exceptfds, &tv);
if ((ret < 0) || FD_ISSET(fdFromChild, &exceptfds))
return false;
if (FD_ISSET(fdFromChild, &readfds)) {
static char buf[4096];
int len = read(fdFromChild, &buf[0], sizeof(buf));
if (len == 0)
return false; // EOF
for (int i = 0; i < len; i++)
inBuf.push_back(buf[i]);
}
}
if (inBuf.empty()) {
c = -1;
return true;
}
c = inBuf.front();
inBuf.pop_front();
return true;
}
static std::vector<char> lineBuf;
/*
* Class: org_petero_droidfish_engine_StockFishJNI
* Method: readFromProcess
* Signature: (I)Ljava/lang/String;
*/
extern "C" JNIEXPORT jstring JNICALL Java_org_petero_droidfish_engine_StockFishJNI_readFromProcess
(JNIEnv* env, jobject obj, jint timeoutMillis)
{
struct timeval tv0, tv1;
while (true) {
int c;
gettimeofday(&tv0, NULL);
if (!getNextChar(c, timeoutMillis))
return 0; // Error
gettimeofday(&tv1, NULL);
int elapsedMillis = (tv1.tv_sec - tv0.tv_sec) * 1000 + (tv1.tv_usec - tv0.tv_usec) / 1000;
if (elapsedMillis > 0) {
timeoutMillis -= elapsedMillis;
if (timeoutMillis < 0) timeoutMillis = 0;
}
if (c == -1) { // Timeout
static char emptyString = 0;
return (*env).NewStringUTF(&emptyString);
}
if (c == '\n' || (c == '\r')) {
if (lineBuf.size() > 0) {
lineBuf.push_back(0);
jstring ret = (*env).NewStringUTF(&lineBuf[0]);
lineBuf.clear();
return ret;
}
} else {
lineBuf.push_back((char)c);
}
}
}
/*
* Class: org_petero_droidfish_engine_StockFishJNI
* Method: writeToProcess
* Signature: (Ljava/lang/String;)V
*/
extern "C" JNIEXPORT void JNICALL Java_org_petero_droidfish_engine_StockFishJNI_writeToProcess
(JNIEnv* env, jobject obj, jstring msg)
{
const char* str = (*env).GetStringUTFChars(msg, NULL);
if (str) {
int len = strlen(str);
int written = 0;
while (written < len) {
int n = write(fdToChild, &str[written], len - written);
if (n <= 0)
break;
written += n;
}
(*env).ReleaseStringUTFChars(msg, str);
}
}
/*
* Class: org_petero_droidfish_engine_StockFishJNI
* Method: getNPhysicalProcessors
* Signature: ()I
*/
extern "C" JNIEXPORT jint JNICALL Java_org_petero_droidfish_engine_StockFishJNI_getNPhysicalProcessors
(JNIEnv *, jclass)
{
return sysconf(_SC_NPROCESSORS_ONLN);
}

View File

@ -1,6 +1,6 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
Copyright (C) 2011-2012 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
@ -178,7 +178,7 @@ public class DroidFish extends Activity implements GUIInterface {
super.onCreate(savedInstanceState);
String pgn = getPgnIntent();
createDirectories();
settings = PreferenceManager.getDefaultSharedPreferences(this);
@ -917,6 +917,11 @@ public class DroidFish extends Activity implements GUIInterface {
return mEngineThreads;
}
@Override
public Context getContext() {
return getApplicationContext();
}
/** Report a move made that is a candidate for GUI animation. */
public void setAnimMove(Position sourcePos, Move move, boolean forward) {
if (animateMoves && (move != null))

View File

@ -1,6 +1,6 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
Copyright (C) 2011-2012 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
@ -25,6 +25,8 @@ import org.petero.droidfish.gamelogic.Game;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Position;
import android.content.Context;
/** Interface between the GUI and the ChessController. */
public interface GUIInterface {
@ -87,4 +89,7 @@ public interface GUIInterface {
/** Return the number of engine threads to use. */
int engineThreads();
/** Return application context. */
public Context getContext();
}

View File

@ -1,6 +1,6 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
Copyright (C) 2011-2012 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
@ -34,12 +34,15 @@ import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.UndoInfo;
import org.petero.droidfish.gamelogic.SearchListener.PvInfo;
import android.content.Context;
/**
* A computer algorithm player.
* @author petero
*/
public class DroidComputerPlayer {
private UCIEngine uciEngine = null;
private final Context context;
private final SearchListener listener;
private final DroidBook book;
@ -224,7 +227,8 @@ public class DroidComputerPlayer {
private Thread engineMonitor;
/** Constructor. Starts engine process if not already started. */
public DroidComputerPlayer(SearchListener listener) {
public DroidComputerPlayer(Context context, SearchListener listener) {
this.context = context;
this.listener = listener;
book = DroidBook.getInstance();
engineState = new EngineState();
@ -548,7 +552,7 @@ public class DroidComputerPlayer {
myAssert(searchRequest != null);
engineName = "Computer";
uciEngine = UCIEngineBase.getEngine(searchRequest.engine, new UCIEngine.Report() {
uciEngine = UCIEngineBase.getEngine(context, searchRequest.engine, new UCIEngine.Report() {
@Override
public void reportError(String errMsg) {
if (errMsg != null) {
@ -924,7 +928,7 @@ public class DroidComputerPlayer {
nCPUsFromProc = nCPUs;
} catch (IOException e) {
}
int nCPUsFromOS = StockFishJNI.getNPhysicalProcessors();
int nCPUsFromOS = NativeUtil.getNPhysicalProcessors();
return Math.max(nCPUsFromProc, nCPUsFromOS);
}

View File

@ -1,3 +1,21 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011-2012 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;
@ -11,8 +29,10 @@ import java.nio.channels.FileChannel;
import java.util.LinkedList;
import java.util.List;
/** Engine running as a process started from an external resource. */
public class ExternalEngine extends UCIEngineBase {
private File engineFileName;
protected static final String intSfPath = "/data/data/org.petero.droidfish/internal_sf";
private static final String exePath = "/data/data/org.petero.droidfish/engine.exe";
private final Report report;
private Process engineProc;
@ -170,7 +190,8 @@ public class ExternalEngine extends UCIEngineBase {
stdErrThread.interrupt();
}
private final static void copyFile(File from, File to) throws IOException {
protected void copyFile(File from, File to) throws IOException {
new File(intSfPath).delete();
if (to.exists() && (from.length() == to.length()) && (from.lastModified() == to.lastModified()))
return;
if (to.exists())

View File

@ -0,0 +1,79 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011-2012 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.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.content.Context;
import android.os.Build;
/** Stockfish engine running as process, started from assets resource. */
public class InternalStockFish extends ExternalEngine {
private Context context;
public InternalStockFish(Context context, Report report) {
super("", report);
this.context = context;
}
/** @inheritDoc */
@Override
public final void initOptions() {
setOption("Hash", 16);
}
/** @inheritDoc */
@Override
public final void setStrength(int strength) {
setOption("Skill Level", strength/50);
}
@Override
protected void copyFile(File from, File to) throws IOException {
if (new File(intSfPath).exists())
return;
if (to.exists())
to.delete();
to.createNewFile();
InputStream is = context.getAssets().open("stockfish-" + Build.CPU_ABI);
OutputStream os = new FileOutputStream(to);
try {
byte[] buf = new byte[8192];
while (true) {
int len = is.read(buf);
if (len <= 0)
break;
os.write(buf, 0, len);
}
} finally {
if (is != null) try { is.close(); } catch (IOException ex) {}
if (os != null) try { os.close(); } catch (IOException ex) {}
}
new File(intSfPath).createNewFile();
}
}

View File

@ -0,0 +1,28 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011-2012 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 NativeUtil {
static {
System.loadLibrary("nativeutil");
}
/** Return number of physical processors, i.e. hyper-threading ignored. */
final static native int getNPhysicalProcessors();
}

View File

@ -1,73 +0,0 @@
/*
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 StockFishJNI extends UCIEngineBase {
static {
System.loadLibrary("stockfishjni");
}
/** @inheritDoc */
@Override
public final void initOptions() {
setOption("Hash", 16);
}
/** @inheritDoc */
@Override
public final void setStrength(int strength) {
setOption("Skill Level", strength/50);
}
/** @inheritDoc */
@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;
}
/** @inheritDoc */
@Override
public final synchronized void writeLineToEngine(String data) {
// System.out.printf("GUI -> Engine: %s\n", data);
writeToProcess(data + "\n");
}
/** @inheritDoc */
@Override
protected final native void startProcess();
/**
* Read a line of data from the process.
* Return as soon as there is a full line of data to return,
* or when timeoutMillis milliseconds have passed.
*/
private final native String readFromProcess(int timeoutMillis);
/** Write data to the process. */
private final native void writeToProcess(String data);
/** Return number of physical processors, i.e. hyper-threading ignored. */
final static native int getNPhysicalProcessors();
}

View File

@ -1,18 +1,38 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011-2012 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.HashMap;
import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
import android.content.Context;
public abstract class UCIEngineBase implements UCIEngine {
private boolean processAlive;
public static UCIEngine getEngine(String engine, Report report) {
public static UCIEngine getEngine(Context context, String engine, Report report) {
if ("cuckoochess".equals(engine))
return new CuckooChessEngine(report);
else if ("stockfish".equals(engine))
return new StockFishJNI();
return new InternalStockFish(context, report);
else
return new ExternalEngine(engine, report);
}

View File

@ -1,6 +1,6 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com
Copyright (C) 2011-2012 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
@ -79,7 +79,7 @@ public class DroidChessController {
updateGUI();
this.gameMode = gameMode;
if (computerPlayer == null) {
computerPlayer = new DroidComputerPlayer(listener);
computerPlayer = new DroidComputerPlayer(gui.getContext(), listener);
computerPlayer.setBookOptions(bookOptions);
}
computerPlayer.queueStartEngine(searchId, engine);