diff --git a/DroidFishApp/build.gradle b/DroidFishApp/build.gradle index 47a055f6..1ce7de24 100644 --- a/DroidFishApp/build.gradle +++ b/DroidFishApp/build.gradle @@ -81,6 +81,25 @@ task copyToAssets(type: Copy, dependsOn: 'externalNativeBuildRelease') { } into 'src/main/assets' } + +// Download Url to File +def download(address, directory, file) { + def dest_dir = new File(directory) + if (!dest_dir.exists()) + dest_dir.mkdirs() + new File(dest_dir, file).withOutputStream { + os -> os << new URL(address).openStream() + } +} + +// Copy Leela binaries to assets directory +task copyLeelaAssets() { + def assetsPath = 'DroidFishApp/src/main/assets/' + download('file:///home/leandro/projects/chess/leela/android-bin/0.25borg/arm64-v8a/lc0', assetsPath + 'arm64-v8a', 'lc0') + download('file:///home/leandro/projects/chess/leela/android-bin/0.25borg/armeabi-v7a/lc0', assetsPath + 'armeabi-v7a', 'lc0') + download('file:///home/leandro/projects/chess/leela/android-bin/0.25borg/networks/weights', assetsPath + 'networks', 'weights') +} + tasks.withType(JavaCompile) { - t -> t.dependsOn copyToAssets + t -> t.dependsOn copyToAssets, copyLeelaAssets } diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java index 19957864..701237ee 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java @@ -1341,9 +1341,13 @@ private void setEngineTitle(String engine, int strength) { int idx = engine.lastIndexOf('/'); eName = engine.substring(idx + 1); } else { - eName = getString("cuckoochess".equals(engine) ? - R.string.cuckoochess_engine : - R.string.stockfish_engine); + if ("cuckoochess".equals(engine)) + eName = getString(R.string.cuckoochess_engine); + else if ("stockfish".equals(engine)) + eName = getString(R.string.stockfish_engine); + else + eName = getString(R.string.leela_engine); + boolean analysis = (ctrl != null) && ctrl.analysisMode(); if ((strength < 1000) && !analysis) eName = String.format(Locale.US, "%s: %d%%", eName, strength / 10); @@ -2450,6 +2454,7 @@ else if (item == numFiles + 2) private static boolean reservedEngineName(String name) { return "cuckoochess".equals(name) || "stockfish".equals(name) || + "leela".equals(name) || name.endsWith(".ini"); } @@ -2457,6 +2462,7 @@ private Dialog selectEngineDialog(final boolean abortOnCancel) { final ArrayList items = new ArrayList<>(); final ArrayList ids = new ArrayList<>(); ids.add("stockfish"); items.add(getString(R.string.stockfish_engine)); + ids.add("leela"); items.add(getString(R.string.leela_engine)); ids.add("cuckoochess"); items.add(getString(R.string.cuckoochess_engine)); if (storageAvailable()) { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java index 4fd04b4c..fb53c522 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/EngineUtil.java @@ -33,15 +33,24 @@ public class EngineUtil { System.loadLibrary("nativeutil"); } - /** Return file name of the internal stockfish executable. */ - public static String internalStockFishName() { + static String getABI() { String abi = Build.CPU_ABI; if (!"x86".equals(abi) && - !"x86_64".equals(abi) && - !"arm64-v8a".equals(abi)) { + !"x86_64".equals(abi) && + !"arm64-v8a".equals(abi)) { abi = "armeabi-v7a"; // Unknown ABI, assume 32-bit arm } - return abi + "/stockfish"; + return abi; + } + + /** Return file name of the internal stockfish executable. */ + public static String internalStockFishName() { + return getABI() + "/stockfish"; + } + + /** Return file name of the internal Leela executable. */ + public static String internalLeelaName() { + return getABI() + "/lc0"; } /** Return true if file "engine" is a network engine. */ diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/ExternalEngine.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/ExternalEngine.java index 26bb3dd4..b398026e 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/engine/ExternalEngine.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/ExternalEngine.java @@ -67,6 +67,10 @@ protected String internalSFPath() { return context.getFilesDir().getAbsolutePath() + "/internal_sf"; } + protected String internalSWPath() { + return context.getFilesDir().getAbsolutePath() + "/internal_sw"; + } + /** @inheritDoc */ @Override protected void startProcess() { @@ -181,7 +185,7 @@ private void cleanUpExeDir(File exeDir, String exePath) { if (files == null) return; for (File f : files) { - if (!f.getCanonicalPath().equals(exePath)) + if (!f.getCanonicalPath().equals(exePath) && !f.isDirectory()) f.delete(); } new File(context.getFilesDir(), "engine.exe").delete(); diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalLeela.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalLeela.java new file mode 100644 index 00000000..924e88ea --- /dev/null +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/InternalLeela.java @@ -0,0 +1,153 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2011-2014 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 . +*/ + +package org.petero.droidfish.engine; + +import android.os.Environment; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Locale; + +/** Leela engine running as process, started from assets resource. */ +public class InternalLeela extends ExternalEngine { + + public InternalLeela(Report report, String workDir) { + super("", workDir, report); + } + + @Override + protected File getOptionsFile() { + File extDir = Environment.getExternalStorageDirectory(); + return new File(extDir, "/DroidFish/uci/leela.ini"); + } + + private long readCheckSum(File f) { + try (InputStream is = new FileInputStream(f); + DataInputStream dis = new DataInputStream(is)) { + return dis.readLong(); + } catch (IOException e) { + return 0; + } + } + + private void writeCheckSum(File f, long checkSum) { + try (OutputStream os = new FileOutputStream(f); + DataOutputStream dos = new DataOutputStream(os)) { + dos.writeLong(checkSum); + } catch (IOException ignore) { + } + } + + private long computeAssetsCheckSum(String sfExe) { + + try (InputStream is = context.getAssets().open(sfExe)) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buf = new byte[8192]; + while (true) { + int len = is.read(buf); + if (len <= 0) + break; + md.update(buf, 0, len); + } + byte[] digest = md.digest(new byte[]{0}); + long ret = 0; + for (int i = 0; i < 8; i++) { + ret ^= ((long)digest[i]) << (i * 8); + } + return ret; + } catch (IOException e) { + return -1; + } catch (NoSuchAlgorithmException e) { + return -1; + } + } + + @Override + protected String copyFile(File from, File exeDir) throws IOException { + File to = new File(exeDir, "engine.exe"); + final String leelaExe = EngineUtil.internalLeelaName(); + + // The checksum test is to avoid writing to /data unless necessary, + // on the assumption that it will reduce memory wear. + long oldCSum = readCheckSum(new File(internalSFPath())); + long newCSum = computeAssetsCheckSum(leelaExe); + if (oldCSum == newCSum) + return to.getAbsolutePath(); + + // Copy engine + if (to.exists()) + to.delete(); + to.createNewFile(); + + try (InputStream is = context.getAssets().open(leelaExe); + OutputStream os = new FileOutputStream(to)) { + byte[] buf = new byte[8192]; + while (true) { + int len = is.read(buf); + if (len <= 0) + break; + os.write(buf, 0, len); + } + } + + writeCheckSum(new File(internalSFPath()), newCSum); + final String exePath = to.getAbsolutePath(); + + // Check if weights file changed + final String networkPath = "networks/"; + final String weightsFile = "weights"; + + oldCSum = readCheckSum(new File(internalSWPath())); + newCSum = computeAssetsCheckSum(networkPath + weightsFile); + if (oldCSum == newCSum) + return exePath; + + // Copy weights file + File networksDir = new File(exeDir, networkPath); + if (!networksDir.exists()) + networksDir.mkdir(); + to = new File(networksDir, weightsFile); + if (to.exists()) + to.delete(); + to.createNewFile(); + + try (InputStream is = context.getAssets().open(networkPath + weightsFile); + OutputStream os = new FileOutputStream(to)) { + byte[] buf = new byte[8192]; + while (true) { + int len = is.read(buf); + if (len <= 0) + break; + os.write(buf, 0, len); + } + } + + writeCheckSum(new File(internalSWPath()), newCSum); + + return exePath; + } +} diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java index b540e456..5c66d11f 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/UCIEngineBase.java @@ -43,6 +43,8 @@ public static UCIEngine getEngine(String engine, return new CuckooChessEngine(); else if ("stockfish".equals(engine)) return new InternalStockFish(report, engineOptions.workDir); + else if ("leela".equals(engine)) + return new InternalLeela(report, engineOptions.workDir); else if (EngineUtil.isOpenExchangeEngine(engine)) return new OpenExchangeEngine(engine, engineOptions.workDir, report); else if (EngineUtil.isNetEngine(engine)) diff --git a/DroidFishApp/src/main/res/values-be/strings.xml b/DroidFishApp/src/main/res/values-be/strings.xml index 1f7bac23..8d2ee114 100644 --- a/DroidFishApp/src/main/res/values-be/strings.xml +++ b/DroidFishApp/src/main/res/values-be/strings.xml @@ -802,4 +802,5 @@ Па-нямецку Па-іспанску + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-de/strings.xml b/DroidFishApp/src/main/res/values-de/strings.xml index 49ad3aac..4f95ef1a 100644 --- a/DroidFishApp/src/main/res/values-de/strings.xml +++ b/DroidFishApp/src/main/res/values-de/strings.xml @@ -802,4 +802,5 @@ Um gegebenenfalls Strom zu sparen, ist es empfehlenswert, dass Sie diese Paramet Zugansage auf Deutsch Zugansage auf Spanisch + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-es/strings.xml b/DroidFishApp/src/main/res/values-es/strings.xml index d322fdd0..44904fb2 100644 --- a/DroidFishApp/src/main/res/values-es/strings.xml +++ b/DroidFishApp/src/main/res/values-es/strings.xml @@ -802,4 +802,5 @@ Si está usted utilizando la batería, se recomienda que cambie los ajustes para Texto hablado en alemán Texto hablado en español + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-fr/strings.xml b/DroidFishApp/src/main/res/values-fr/strings.xml index a4d66561..aea1a7ee 100644 --- a/DroidFishApp/src/main/res/values-fr/strings.xml +++ b/DroidFishApp/src/main/res/values-fr/strings.xml @@ -802,4 +802,5 @@ Lorsque que vous êtes sur batterie, il est recommandé de changer les paramètr German Speech Spanish Speech + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-it/strings.xml b/DroidFishApp/src/main/res/values-it/strings.xml index 57a21cbd..facf4b3f 100644 --- a/DroidFishApp/src/main/res/values-it/strings.xml +++ b/DroidFishApp/src/main/res/values-it/strings.xml @@ -802,4 +802,5 @@ Se l\'alimentazione è a batteria, è consigliabile modificare le impostazioni p Annuncio vocale in tedesco Annuncio vocale in Spagnolo + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-ko/strings.xml b/DroidFishApp/src/main/res/values-ko/strings.xml index 76e89121..b18c3a21 100644 --- a/DroidFishApp/src/main/res/values-ko/strings.xml +++ b/DroidFishApp/src/main/res/values-ko/strings.xml @@ -806,4 +806,5 @@ DroidFish는 백그라운드에 실행 중인 상태에서 다음과 같이 설 독일어 말하기 스페인어 말하기 + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-nl/strings.xml b/DroidFishApp/src/main/res/values-nl/strings.xml index a477b895..f3b0d1b9 100644 --- a/DroidFishApp/src/main/res/values-nl/strings.xml +++ b/DroidFishApp/src/main/res/values-nl/strings.xml @@ -802,4 +802,5 @@ Als uw telefoon op batterij werkt is het aan te raden om deze instellingen te wi German Speech Spanish Speech + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-pl/strings.xml b/DroidFishApp/src/main/res/values-pl/strings.xml index 9257ce5c..7d3b52a2 100644 --- a/DroidFishApp/src/main/res/values-pl/strings.xml +++ b/DroidFishApp/src/main/res/values-pl/strings.xml @@ -802,4 +802,5 @@ Jeśli pracujesz na baterii, zalecana jest zmiana ustawień, w celu oszczędzani Mowa niemiecka Mowa hiszpańska + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-pt/strings.xml b/DroidFishApp/src/main/res/values-pt/strings.xml index 91c0a535..6b899c7a 100644 --- a/DroidFishApp/src/main/res/values-pt/strings.xml +++ b/DroidFishApp/src/main/res/values-pt/strings.xml @@ -802,4 +802,5 @@ Se você está usando somente a bateria, recomenda-se que você mude as configur Voz em alemão Voz em espanhol + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-ru/strings.xml b/DroidFishApp/src/main/res/values-ru/strings.xml index 4a69e48d..be24cf6f 100644 --- a/DroidFishApp/src/main/res/values-ru/strings.xml +++ b/DroidFishApp/src/main/res/values-ru/strings.xml @@ -802,4 +802,5 @@ По-немецки По-испански + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-tr/strings.xml b/DroidFishApp/src/main/res/values-tr/strings.xml index cc71edd3..9a0cffd7 100644 --- a/DroidFishApp/src/main/res/values-tr/strings.xml +++ b/DroidFishApp/src/main/res/values-tr/strings.xml @@ -802,4 +802,5 @@ Pil gücüyle çalışıyorsanız, pil gücünden tasarruf etmek için ayarları Almanca konuşma İspanyolca konuşma + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-uk/strings.xml b/DroidFishApp/src/main/res/values-uk/strings.xml index 72d472d6..b01add3c 100644 --- a/DroidFishApp/src/main/res/values-uk/strings.xml +++ b/DroidFishApp/src/main/res/values-uk/strings.xml @@ -802,4 +802,5 @@ Німецька вимова Іспанська вимова + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values-zh-rCN/strings.xml b/DroidFishApp/src/main/res/values-zh-rCN/strings.xml index 32e2a668..09ef1fa4 100644 --- a/DroidFishApp/src/main/res/values-zh-rCN/strings.xml +++ b/DroidFishApp/src/main/res/values-zh-rCN/strings.xml @@ -802,4 +802,5 @@ 德语语音 西班牙语音 + Leela Chess Zero diff --git a/DroidFishApp/src/main/res/values/strings.xml b/DroidFishApp/src/main/res/values/strings.xml index 9e1bd4f6..42afd74b 100644 --- a/DroidFishApp/src/main/res/values/strings.xml +++ b/DroidFishApp/src/main/res/values/strings.xml @@ -214,6 +214,7 @@ If you are running on battery power, it is recommended that you change settings Character / not allowed Engine error Stockfish + Leela Chess Zero CuckooChess Select Engine Engine Options