/* DroidFish - An Android chess program. Copyright (C) 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 com.if3games.chessonline.engine; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException; import com.if3games.chessonline.EngineOptions; import com.if3games.chessonline.R; import com.if3games.chessonline.Util; import android.content.Context; /** Engine running on a different computer. */ public class NetworkEngine extends UCIEngineBase { protected final Context context; private final Report report; private String fileName; private String networkID; private Socket socket; private Thread startupThread; private Thread stdInThread; private Thread stdOutThread; private LocalPipe guiToEngine; private LocalPipe engineToGui; private boolean startedOk; private boolean isRunning; private boolean isError; public NetworkEngine(Context context, String engine, EngineOptions engineOptions, Report report) { this.context = context; this.report = report; fileName = engine; networkID = engineOptions.networkID; startupThread = null; stdInThread = null; guiToEngine = new LocalPipe(); engineToGui = new LocalPipe(); startedOk = false; isRunning = false; isError = false; } /** Create socket connection to remote server. */ private final synchronized void connect() { if (socket == null) { String host = null; String port = null; boolean fail = false; try { String[] lines = Util.readFile(fileName); if ((lines.length < 3) || !lines[0].equals("NETE")) { fail = true; } else { host = lines[1]; port = lines[2]; } } catch (IOException e1) { fail = true; } if (fail) { isError = true; report.reportError(context.getString(R.string.network_engine_config_error)); } else { try { int portNr = Integer.parseInt(port); socket = new Socket(host, portNr); socket.setTcpNoDelay(true); } catch (UnknownHostException e) { isError = true; report.reportError(e.getMessage()); } catch (NumberFormatException nfe) { isError = true; report.reportError(context.getString(R.string.invalid_network_port)); } catch (IOException e) { isError = true; report.reportError(e.getMessage()); } } if (socket == null) socket = new Socket(); } } /** @inheritDoc */ @Override protected void startProcess() { // Start thread to check for startup error startupThread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { return; } if (startedOk && isRunning && !isUCI) { isError = true; report.reportError(context.getString(R.string.uci_protocol_error)); } } }); startupThread.start(); // Start a thread to read data from engine stdInThread = new Thread(new Runnable() { @Override public void run() { connect(); try { InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr, 8192); String line; boolean first = true; while ((line = br.readLine()) != null) { if (Thread.currentThread().isInterrupted()) return; synchronized (engineToGui) { engineToGui.addLine(line); if (first) { startedOk = true; isRunning = true; first = false; } } } } catch (IOException e) { } finally { if (isRunning) { isError = true; isRunning = false; if (!startedOk) report.reportError(context.getString(R.string.failed_to_start_engine)); else report.reportError(context.getString(R.string.engine_terminated)); } try { socket.close(); } catch (IOException e) {} } engineToGui.close(); } }); stdInThread.start(); // Start a thread to write data to engine stdOutThread = new Thread(new Runnable() { @Override public void run() { try { connect(); String line; while ((line = guiToEngine.readLine()) != null) { if (Thread.currentThread().isInterrupted()) return; line += "\n"; socket.getOutputStream().write(line.getBytes()); } } catch (IOException e) { if (isRunning) { isError = true; report.reportError(e.getMessage()); } } finally { if (isRunning && !isError) { isError = true; report.reportError(context.getString(R.string.engine_terminated)); } isRunning = false; try { socket.close(); } catch (IOException ex) {} } } }); stdOutThread.start(); } private int hashMB = -1; private String gaviotaTbPath = ""; private boolean optionsInitialized = false; /** @inheritDoc */ @Override public void initOptions(EngineOptions engineOptions) { super.initOptions(engineOptions); hashMB = engineOptions.hashMB; setOption("Hash", engineOptions.hashMB); if (engineOptions.engineProbe) { gaviotaTbPath = engineOptions.gtbPath; setOption("GaviotaTbPath", engineOptions.gtbPath); setOption("GaviotaTbCache", 8); } optionsInitialized = true; } /** @inheritDoc */ @Override public boolean optionsOk(EngineOptions engineOptions) { if (isError) return false; if (!optionsInitialized) return true; if (!networkID.equals(engineOptions.networkID)) return false; if (hashMB != engineOptions.hashMB) return false; if (haveOption("gaviotatbpath") && !gaviotaTbPath.equals(engineOptions.gtbPath)) return false; return true; } /** @inheritDoc */ @Override public void setStrength(int strength) { } /** @inheritDoc */ @Override public String readLineFromEngine(int timeoutMillis) { String ret = engineToGui.readLine(timeoutMillis); if (ret == null) return null; if (ret.length() > 0) { // System.out.printf("Engine -> GUI: %s\n", ret); } return ret; } /** @inheritDoc */ @Override public void writeLineToEngine(String data) { // System.out.printf("GUI -> Engine: %s\n", data); guiToEngine.addLine(data); } /** @inheritDoc */ @Override public void shutDown() { isRunning = false; if (startupThread != null) startupThread.interrupt(); if (socket != null) { try { socket.getOutputStream().write("quit\n".getBytes()); } catch (IOException e) {} try { socket.close(); } catch (IOException e) {} } super.shutDown(); if (stdOutThread != null) stdOutThread.interrupt(); if (stdInThread != null) stdInThread.interrupt(); } }