DroidFish: English text to speech support for announcing moves.

This commit is contained in:
Peter Osterlund 2016-12-25 11:48:04 +01:00
parent 38295b516f
commit 0c33791ddd
9 changed files with 490 additions and 51 deletions

View File

@ -12,6 +12,7 @@
<string name="book_line_length_default">1000000</string>
<string name="viewPieceType_default">1</string>
<string name="autoDelay_default">5000</string>
<string name="move_announce_type_default">off</string>
<string name="cpu_warning">\
<b>CPU Usage</b>\n\
If you leave DroidFish running in the background and <i>GameMode</i> is set to \
@ -219,6 +220,9 @@ you are not actively using the program.\
<string name="network_port">Port</string>
<string name="network_engine">Network Engine</string>
<string name="failed_to_start_engine">Failed to start engine</string>
<string name="tts_failed_to_init">Failed to initialize text to speech</string>
<string name="tts_data_missing">Text to speech data missing</string>
<string name="tts_not_supported_for_lang">Text to speech not supported for this language</string>
<string name="engine_terminated">Engine terminated</string>
<string name="uci_protocol_error">UCI protocol error</string>
<string name="network_engine_config_error">Network engine configuration error</string>
@ -296,8 +300,8 @@ you are not actively using the program.\
<string name="prefs_autoScrollTitle_summary">Auto scroll titlebar if player names are too long</string>
<string name="prefs_quickMoveInput_title">Quick Move Input</string>
<string name="prefs_quickMoveInput_summary">From and To squares can be touched in any order. Move is played as soon as uniquely defined.</string>
<string name="prefs_soundEnabled_title">Enable Sounds</string>
<string name="prefs_soundEnabled_summary">Play sound when computer makes a move</string>
<string name="prefs_moveAnnounceType_title">Move Announcement</string>
<string name="prefs_moveAnnounceType_summary">Announcement sound when computer makes a move</string>
<string name="prefs_vibrateEnabled_title">Enable Vibration</string>
<string name="prefs_vibrateEnabled_summary">Vibrate when computer makes a move</string>
<string name="prefs_fullScreenMode_title">Fullscreen Mode</string>
@ -696,4 +700,14 @@ you are not actively using the program.\
<item>selectEngine</item>
<item>engineOptions</item>
</string-array>
<string-array name="move_announce_type_values">
<item>off</item>
<item>sound</item>
<item>speech_en</item>
</string-array>
<string-array name="move_announce_type_texts">
<item>Off</item>
<item>Play sound</item>
<item>English Speech</item>
</string-array>
</resources>

View File

@ -157,12 +157,14 @@
android:summary="@string/prefs_vibrateEnabled_summary"
android:defaultValue="false">
</CheckBoxPreference>
<CheckBoxPreference
android:key="soundEnabled"
android:title="@string/prefs_soundEnabled_title"
android:summary="@string/prefs_soundEnabled_summary"
android:defaultValue="false">
</CheckBoxPreference>
<ListPreference
android:key="moveAnnounceType"
android:title="@string/prefs_moveAnnounceType_title"
android:summary="@string/prefs_moveAnnounceType_summary"
android:entryValues="@array/move_announce_type_values"
android:entries="@array/move_announce_type_texts"
android:defaultValue="@string/move_announce_type_default">
</ListPreference>
<CheckBoxPreference
android:key="wakeLock"
android:title="@string/prefs_wakeLock_title"

View File

@ -225,7 +225,7 @@ public class DroidFish extends Activity
private boolean autoScrollMoveList;
private boolean leftHanded;
private boolean soundEnabled;
private String moveAnnounceType;
private MediaPlayer moveSound;
private boolean vibrateEnabled;
private boolean animateMoves;
@ -276,6 +276,8 @@ public class DroidFish extends Activity
private boolean guideShowOnStart;
private TourGuide tourGuide;
private Speech speech;
/** Defines all configurable button actions. */
private ActionFactory actionFactory = new ActionFactory() {
@ -485,6 +487,8 @@ public class DroidFish extends Activity
ctrl.shutdownEngine();
ctrl = new DroidChessController(this, gameTextListener, pgnOptions);
egtbForceReload = true;
if (speech == null)
speech = new Speech();
readPrefs();
TimeControlData tcData = new TimeControlData();
tcData.setTimeControl(timeControl, movesPerSession, timeIncrement);
@ -1147,6 +1151,8 @@ public class DroidFish extends Activity
if (ctrl != null)
ctrl.shutdownEngine();
setNotification(false);
if (speech != null)
speech.shutdown();
super.onDestroy();
}
@ -1210,7 +1216,8 @@ public class DroidFish extends Activity
if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
statusFontSize = Math.min(statusFontSize, 16);
status.setTextSize(statusFontSize);
soundEnabled = settings.getBoolean("soundEnabled", false);
moveAnnounceType = settings.getString("moveAnnounceType", "off");
initSpeech();
vibrateEnabled = settings.getBoolean("vibrateEnabled", false);
animateMoves = settings.getBoolean("animateMoves", true);
autoScrollTitle = settings.getBoolean("autoScrollTitle", true);
@ -3766,9 +3773,16 @@ public class DroidFish extends Activity
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
/** Initialize text to speech if enabled in settings. */
private void initSpeech() {
if (moveAnnounceType.startsWith("speech_"))
speech.initialize(this);
}
@Override
public void computerMoveMade() {
if (soundEnabled) {
public void movePlayed(Position pos, Move move, boolean computerMove) {
if ("sound".equals(moveAnnounceType)) {
if (computerMove) {
if (moveSound != null)
moveSound.release();
try {
@ -3778,6 +3792,9 @@ public class DroidFish extends Activity
} catch (NotFoundException ex) {
}
}
} else if (moveAnnounceType.startsWith("speech_")) {
speech.say(pos, move, moveAnnounceType.substring(7));
}
if (vibrateEnabled) {
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(500);

View File

@ -79,8 +79,8 @@ public interface GUIInterface {
/** Report UCI engine error message. */
public void reportEngineError(String errMsg);
/** Called when computer made a move. GUI can notify user, for example by playing a sound. */
public void computerMoveMade();
/** Called when a move is played. GUI can notify user, for example by playing a sound. */
public void movePlayed(Position pos, Move move, boolean computerMove);
/** Report remaining thinking time to GUI. */
public void setRemainingTime(int wTime, int bTime, int nextUpdate);

View File

@ -0,0 +1,209 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2016 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.Locale;
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.content.Context;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.widget.Toast;
/** Handles text to speech translation. */
public class Speech {
private TextToSpeech tts;
boolean initialized = false;
boolean supported = false;
String toSpeak = null;
public void initialize(final Context context) {
if (initialized)
return;
tts = new TextToSpeech(context, new OnInitListener() {
@Override
public void onInit(int status) {
initialized = true;
int toast = -1;
if (status == TextToSpeech.SUCCESS) {
int code = tts.setLanguage(Locale.US);
switch (code) {
case TextToSpeech.LANG_AVAILABLE:
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
supported = true;
say(toSpeak);
break;
case TextToSpeech.LANG_MISSING_DATA:
toast = R.string.tts_data_missing;
break;
case TextToSpeech.LANG_NOT_SUPPORTED:
toast = R.string.tts_not_supported_for_lang;
break;
default:
break;
}
} else {
toast = R.string.tts_failed_to_init;
}
if (toast != -1)
Toast.makeText(context, toast, Toast.LENGTH_LONG).show();
}
});
}
@SuppressWarnings("deprecation")
public void say(String text) {
if (initialized) {
if (supported && text != null)
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
toSpeak = null;
} else {
toSpeak = text;
}
}
/** Immediately cancel all speech output. */
public void flushQueue() {
toSpeak = null;
if (tts != null)
tts.stop();
}
/** Shut down the speech engine. */
public void shutdown() {
if (tts != null) {
tts.shutdown();
tts = null;
initialized = false;
supported = false;
}
}
/** Convert move "move" in position "pos" to a sentence and speak it. */
public void say(Position pos, Move move, String langStr) {
String s = moveToText(pos, move, langStr);
// System.out.printf("%.3f Speech.say(): %s\n", System.currentTimeMillis() * 1e-3, s);
if (!s.isEmpty())
say(s);
}
/** Convert move "move" in position "pos" to a sentence that can be spoken. */
public static String moveToText(Position pos, Move move, String langStr) {
if (move == null || !langStr.equals("en"))
return "";
String moveStr = TextIO.moveToString(pos, move, false, false);
int piece = Piece.makeWhite(pos.getPiece(move.from));
boolean capture = pos.getPiece(move.to) != Piece.EMPTY;
boolean promotion = move.promoteTo != Piece.EMPTY;
boolean check = moveStr.endsWith("+");
boolean checkMate = moveStr.endsWith("#");
boolean castle = false;
if (piece == Piece.WPAWN && !capture) {
int fx = Position.getX(move.from);
int tx = Position.getX(move.to);
if (fx != tx)
capture = true; // En passant
}
StringBuilder sentence = new StringBuilder();
if (piece == Piece.WKING) {
int fx = Position.getX(move.from);
int tx = Position.getX(move.to);
if (fx == 4 && tx == 6) {
sentence.append("Short castle");
castle = true;
} else if (fx == 4 && (tx == 2)) {
sentence.append("Long castle");
castle = true;
}
}
if (!castle) {
boolean pawnMove = piece == Piece.WPAWN;
if (!pawnMove)
sentence.append(pieceName(piece)).append(' ');
if (capture) {
int i = moveStr.indexOf("x");
String from = moveStr.substring(pawnMove ? 0 : 1, i);
if (!from.isEmpty())
sentence.append(getFromWord(from)).append(' ');
String to = moveStr.substring(i + 1, i + 3);
sentence.append(to.startsWith("e") ? "take " : "takes ");
sentence.append(to).append(' ');
} else {
int nSkip = (promotion ? 1 : 0) + ((check | checkMate) ? 1 : 0);
int i = moveStr.length() - nSkip;
String from = moveStr.substring(pawnMove ? 0 : 1, i - 2);
if (!from.isEmpty())
sentence.append(from).append(' ');
String to = moveStr.substring(i - 2, i);
sentence.append(to).append(' ');
}
if (promotion)
sentence.append(pieceName(move.promoteTo)).append(' ');
}
if (checkMate) {
removeLastSpace(sentence);
sentence.append(". Check mate!");
} else if (check) {
removeLastSpace(sentence);
sentence.append(". Check!");
}
return sentence.toString().trim();
}
/** Get the name of a non-pawn piece. Return empty string if no such piece. */
private static String pieceName(int piece) {
piece = Piece.makeWhite(piece);
switch (piece) {
case Piece.WKING: return "King";
case Piece.WQUEEN: return "Queen";
case Piece.WROOK: return "Rook";
case Piece.WBISHOP: return "Bishop";
case Piece.WKNIGHT: return "Knight";
default: return "";
}
}
/** Transform a "from" file or file+rank to a word. */
private static String getFromWord(String from) {
if ("a".equals(from))
return "ae";
return from;
}
/** If the last character in the StringBuilder is a space, remove it. */
private static void removeLastSpace(StringBuilder sb) {
int len = sb.length();
if (len > 0 && sb.charAt(len - 1) == ' ')
sb.setLength(len - 1);
}
}

View File

@ -1023,10 +1023,10 @@ public class DroidChessController {
return;
searchId++;
Position oldPos = new Position(game.currPos());
game.processString(cmd);
Pair<Boolean,Move> res = game.processString(cmd);
ponderMove = ponder;
updateGameMode();
gui.computerMoveMade();
gui.movePlayed(game.prevPos(), res.second, true);
listener.clearSearchInfo(searchId);
updateComputeThreads();
setSelection();
@ -1113,7 +1113,8 @@ public class DroidChessController {
}
if (m.promoteTo == promoteTo) {
String strMove = TextIO.moveToString(pos, m, false, false, moves);
game.processString(strMove);
Pair<Boolean,Move> res = game.processString(strMove);
gui.movePlayed(game.prevPos(), res.second, false);
return true;
}
}

View File

@ -133,18 +133,19 @@ public class Game {
/**
* 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) {
* @return Pair where first item is true if str was understood, false otherwise.
* Second item is move played, or null if no move was played. */
/** Like processString, but also returns the move played, if any. */
public final Pair<Boolean, Move> processString(String str) {
if (getGameState() != GameState.ALIVE)
return false;
return new Pair<Boolean,Move>(false, null);
if (str.startsWith("draw ")) {
String drawCmd = str.substring(str.indexOf(" ") + 1);
handleDrawCmd(drawCmd, true);
return true;
Move m = handleDrawCmd(drawCmd, true);
return new Pair<Boolean,Move>(true, m);
} else if (str.equals("resign")) {
addToGameTree(new Move(0, 0, 0), "resign");
return true;
return new Pair<Boolean,Move>(true, null);
}
Move m = TextIO.UCIstringToMove(str);
@ -154,10 +155,10 @@ public class Game {
if (m == null)
m = TextIO.stringToMove(currPos(), str);
if (m == null)
return false;
return new Pair<Boolean,Move>(false, null);
addToGameTree(m, pendingDrawOffer ? "draw offer" : "");
return true;
return new Pair<Boolean,Move>(true, m);
}
/** Try claim a draw using a command string. Does not play the move involved
@ -455,7 +456,8 @@ public class Game {
return new Pair<Position, ArrayList<Move>>(pos, mList);
}
private final void handleDrawCmd(String drawCmd, boolean playDrawMove) {
private final Move handleDrawCmd(String drawCmd, boolean playDrawMove) {
Move ret = null;
Position pos = tree.currentPos;
if (drawCmd.startsWith("rep") || drawCmd.startsWith("50")) {
boolean rep = drawCmd.startsWith("rep");
@ -509,18 +511,19 @@ public class Game {
} else {
pendingDrawOffer = true;
if (m != null && playDrawMove) {
processString(ms);
ret = processString(ms).second;
}
}
} else if (drawCmd.startsWith("offer ")) {
pendingDrawOffer = true;
String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1);
if (TextIO.stringToMove(pos, ms) != null) {
processString(ms);
ret = processString(ms).second;
}
} else if (drawCmd.equals("accept")) {
if (haveDrawOffer())
addToGameTree(new Move(0, 0, 0), "draw accept");
}
return ret;
}
}

View File

@ -39,36 +39,48 @@ public class GameTest extends TestCase {
Game game = new Game(null, new TimeControlData());
assertEquals(false, game.haveDrawOffer());
boolean res = game.processString("e4");
Pair<Boolean,Move> p = game.processString("e4");
boolean res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("e2e4"), p.second);
assertEquals(false, game.haveDrawOffer());
res = game.processString("draw offer e5");
p = game.processString("draw offer e5");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("e7e5"), p.second);
assertEquals(true, game.haveDrawOffer());
assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw
assertEquals(Piece.BPAWN, game.currPos().getPiece(Position.getSquare(4, 4))); // e5 move made
res = game.processString("draw offer Nf3");
p = game.processString("draw offer Nf3");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("g1f3"), p.second);
assertEquals(true, game.haveDrawOffer());
assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw offer does not imply draw
assertEquals(Piece.WKNIGHT, game.currPos().getPiece(Position.getSquare(5, 2))); // Nf3 move made
res = game.processString("Nc6");
p = game.processString("Nc6");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("b8c6"), p.second);
assertEquals(false, game.haveDrawOffer());
assertEquals(Game.GameState.ALIVE, game.getGameState());
assertEquals(Piece.BKNIGHT, game.currPos().getPiece(Position.getSquare(2, 5))); // Nc6 move made
res = game.processString("draw offer Bb5");
p = game.processString("draw offer Bb5");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("f1b5"), p.second);
assertEquals(true, game.haveDrawOffer());
assertEquals(Game.GameState.ALIVE, game.getGameState());
assertEquals(Piece.WBISHOP, game.currPos().getPiece(Position.getSquare(1, 4))); // Bb5 move made
res = game.processString("draw accept");
p = game.processString("draw accept");
res = p.first;
assertEquals(true, res);
assertEquals(null, p.second);
assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Draw by agreement
game.undoMove(); // Undo "draw accept"
@ -100,11 +112,15 @@ public class GameTest extends TestCase {
assertEquals(false, game.haveDrawOffer());
assertEquals(Game.GameState.ALIVE, game.getGameState());
res = game.processString("draw offer e5");
p = game.processString("draw offer e5");
res = p.first;
assertEquals(true, res);
assertEquals(null, p.second);
assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos())); // Move invalid, not executed
res = game.processString("e4");
p = game.processString("e4");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("e2e4"), p.second);
assertEquals(true, game.haveDrawOffer()); // Previous draw offer still valid
assertEquals(Piece.WPAWN, game.currPos().getPiece(Position.getSquare(4, 3))); // e4 move made
@ -126,15 +142,20 @@ public class GameTest extends TestCase {
public void testDraw50() throws ChessParseError {
Game game = new Game(null, new TimeControlData());
assertEquals(false, game.haveDrawOffer());
boolean res = game.processString("draw 50");
Pair<Boolean,Move> p = game.processString("draw 50");
boolean res = p.first;
assertEquals(true, res);
assertEquals(null, p.second);
assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid
res = game.processString("e4");
p = game.processString("e4");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("e2e4"), p.second);
assertEquals(true, game.haveDrawOffer()); // Invalid claim converted to draw offer
String fen = "8/4k3/8/P7/8/8/8/1N2K2R w K - 99 83";
game.setPos(TextIO.readFEN(fen));
res = game.processString("draw 50");
game.processString("draw 50");
assertEquals(Game.GameState.ALIVE, game.getGameState()); // Draw claim invalid
game.setPos(TextIO.readFEN(fen));
@ -163,8 +184,10 @@ public class GameTest extends TestCase {
assertEquals(true, game.haveDrawOffer()); // Previous invalid claim converted to offer
game.processString("draw 50");
assertEquals(Game.GameState.ALIVE, game.getGameState()); // 50 move counter reset.
res = game.processString("draw accept");
p = game.processString("draw accept");
res = p.first;
assertEquals(true, res);
assertEquals(null, p.second);
assertEquals(Game.GameState.DRAW_AGREE, game.getGameState()); // Can accept previous implicit offer
fen = "3k4/R7/3K4/8/8/8/8/8 w - - 99 78";
@ -301,12 +324,16 @@ public class GameTest extends TestCase {
public void testProcessString() throws ChessParseError {
Game game = new Game(null, new TimeControlData());
assertEquals(TextIO.startPosFEN, TextIO.toFEN(game.currPos()));
boolean res = game.processString("Nf3");
Pair<Boolean,Move> p = game.processString("Nf3");
boolean res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("g1f3"), p.second);
assertEquals(1, game.currPos().halfMoveClock);
assertEquals(1, game.currPos().fullMoveCounter);
res = game.processString("d5");
p = game.processString("d5");
res = p.first;
assertEquals(true, res);
assertEquals(TextIO.UCIstringToMove("d7d5"), p.second);
assertEquals(0, game.currPos().halfMoveClock);
assertEquals(2, game.currPos().fullMoveCounter);
@ -336,12 +363,16 @@ public class GameTest extends TestCase {
game.setPos(TextIO.readFEN(fen));
assertEquals(pos, game.currPos());
res = game.processString("junk");
p = game.processString("junk");
res = p.first;
assertEquals(false, res);
assertEquals(null, p.second);
game.newGame();
res = game.processString("e7e5");
p = game.processString("e7e5");
res = p.first;
assertEquals(false, res);
assertEquals(null, p.second);
}
/**

View File

@ -0,0 +1,162 @@
/*
DroidFish - An Android chess program.
Copyright (C) 2016 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 org.petero.droidfish.Speech;
import junit.framework.TestCase;
public class SpeechTest extends TestCase {
public SpeechTest() {
}
public void testEnglish() {
String lang = "en";
{
Game game = new Game(null, new TimeControlData());
Pair<Boolean,Move> res = game.processString("e4");
assertEquals("e4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("d5");
assertEquals("d5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("exd5");
assertEquals("e takes d5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Qxd5");
assertEquals("Queen takes d5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Ne2");
assertEquals("Knight e2", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Nf6");
assertEquals("Knight f6", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Nbc3");
assertEquals("Knight b c3", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("e5");
assertEquals("e5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("b4");
assertEquals("b4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("a5");
assertEquals("a5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("a3");
assertEquals("a3", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("axb4");
assertEquals("ae takes b4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("axb4");
assertEquals("ae takes b4", Speech.moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
Pair<Boolean,Move> res = game.processString("d4");
assertEquals("d4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("e5");
assertEquals("e5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("dxe5");
assertEquals("d take e5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("f6");
assertEquals("f6", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("exf6");
assertEquals("e takes f6", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Bb4");
assertEquals("Bishop b4. Check!", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("c3");
assertEquals("c3", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Ne7");
assertEquals("Knight e7", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("cxb4");
assertEquals("c takes b4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("O-O");
assertEquals("Short castle", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("fxg7");
assertEquals("f takes g7", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("h6");
assertEquals("h6", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("gxf8Q+");
assertEquals("g takes f8 Queen. Check!", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Kxf8");
assertEquals("King takes f8", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("b5");
assertEquals("b5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("a5");
assertEquals("a5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("bxa6");
assertEquals("b takes a6", Speech.moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
Pair<Boolean,Move> res = game.processString("f4");
assertEquals("f4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("e5");
assertEquals("e5", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("g4");
assertEquals("g4", Speech.moveToText(game.prevPos(), res.second, lang));
res = game.processString("Qh4");
assertEquals("Queen h4. Check mate!", Speech.moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
playMoves(game, "d4 d5 Nc3 Nc6 Bf4 Bf5 Qd2 Qd7");
Pair<Boolean,Move> res = game.processString("O-O-O");
assertEquals("Long castle", Speech.moveToText(game.prevPos(), res.second, lang));
playMoves(game, "Nxd4 Nxd5 Qxd5 Qxd4 Qxd4 Nf3 Qxd1 Kxd1");
res = game.processString("O-O-O");
assertEquals("Long castle. Check!", Speech.moveToText(game.prevPos(), res.second, lang));
}
{
Game game = new Game(null, new TimeControlData());
playMoves(game, "e4 e5 h3 Bb4 Ne2 Bc3");
Pair<Boolean,Move> res = game.processString("Nexc3");
assertEquals("Knight e takes c3", Speech.moveToText(game.prevPos(), res.second, lang));
}
}
private void playMoves(Game game, String moves) {
for (String move : moves.split(" ")) {
Pair<Boolean,Move> res = game.processString(move);
assertTrue(res.first);
}
}
}