Skip to content

Commit

Permalink
Merge pull request #1167 from lichess-org/analysis_tabs
Browse files Browse the repository at this point in the history
Analysis tabs
  • Loading branch information
veloce authored Nov 22, 2024
2 parents 28cb95f + befa759 commit 62a5d79
Show file tree
Hide file tree
Showing 38 changed files with 2,855 additions and 2,556 deletions.
418 changes: 231 additions & 187 deletions lib/src/model/analysis/analysis_controller.dart

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions lib/src/model/analysis/analysis_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ class AnalysisPreferences extends _$AnalysisPreferences
return fetch();
}

Future<void> toggleEnableComputerAnalysis() {
return save(
state.copyWith(
enableComputerAnalysis: !state.enableComputerAnalysis,
),
);
}

Future<void> toggleEnableLocalEvaluation() {
return save(
state.copyWith(
Expand Down Expand Up @@ -90,6 +98,7 @@ class AnalysisPrefs with _$AnalysisPrefs implements Serializable {
const AnalysisPrefs._();

const factory AnalysisPrefs({
@JsonKey(defaultValue: true) required bool enableComputerAnalysis,
required bool enableLocalEvaluation,
required bool showEvaluationGauge,
required bool showBestMoveArrow,
Expand All @@ -101,6 +110,7 @@ class AnalysisPrefs with _$AnalysisPrefs implements Serializable {
}) = _AnalysisPrefs;

static const defaults = AnalysisPrefs(
enableComputerAnalysis: true,
enableLocalEvaluation: true,
showEvaluationGauge: true,
showBestMoveArrow: true,
Expand Down
6 changes: 2 additions & 4 deletions lib/src/model/analysis/server_analysis_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,9 @@ class ServerAnalysisService {
///
/// This will return a future that completes when the server analysis is
/// launched (but not when it is finished).
Future<void> requestAnalysis(GameAnyId id, [Side? side]) async {
Future<void> requestAnalysis(GameId id, [Side? side]) async {
final socketPool = ref.read(socketPoolProvider);
final uri = id.isFullId
? Uri(path: '/play/$id/v6')
: Uri(path: '/watch/$id/${side?.name ?? Side.white}/v6');
final uri = Uri(path: '/watch/$id/${side?.name ?? Side.white}/v6');
final socketClient = socketPool.open(uri);

_socketSubscription?.$2.cancel();
Expand Down
19 changes: 13 additions & 6 deletions lib/src/model/common/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ abstract class Node {
return null;
}

/// Updates all nodes.
/// Recursively applies [update] to all nodes of the tree.
void updateAll(void Function(Node node) update) {
update(this);
for (final child in children) {
Expand Down Expand Up @@ -360,16 +360,20 @@ class Branch extends Node {
super.eval,
super.opening,
required this.sanMove,
this.isHidden = false,
this.isComputerVariation = false,
this.isCollapsed = false,
this.lichessAnalysisComments,
// below are fields from dartchess [PgnNodeData]
this.startingComments,
this.comments,
this.nags,
});

/// Whether this branch is from a variation generated by lichess computer analysis.
final bool isComputerVariation;

/// Whether the branch should be hidden in the tree view.
bool isHidden;
bool isCollapsed;

/// The id of the branch, using a concise notation of associated move.
UciCharPair get id => UciCharPair.fromMove(sanMove.move);
Expand Down Expand Up @@ -398,7 +402,8 @@ class Branch extends Node {
eval: eval,
opening: opening,
children: IList(children.map((child) => child.view)),
isHidden: isHidden,
isComputerVariation: isComputerVariation,
isCollapsed: isCollapsed,
lichessAnalysisComments: lichessAnalysisComments?.lock,
startingComments: startingComments?.lock,
comments: comments?.lock,
Expand Down Expand Up @@ -487,7 +492,8 @@ class Root extends Node {
final branch = Branch(
sanMove: SanMove(childFrom.data.san, move),
position: newPos,
isHidden: frame.nesting > 2 || hideVariations && childIdx > 0,
isCollapsed: frame.nesting > 2 || hideVariations && childIdx > 0,
isComputerVariation: isLichessAnalysis && childIdx > 0,
lichessAnalysisComments:
isLichessAnalysis ? comments?.toList() : null,
startingComments: isLichessAnalysis
Expand Down Expand Up @@ -587,7 +593,8 @@ class ViewBranch extends ViewNode with _$ViewBranch {
required Position position,
Opening? opening,
required IList<ViewBranch> children,
@Default(false) bool isHidden,
@Default(false) bool isCollapsed,
required bool isComputerVariation,
ClientEval? eval,
IList<PgnComment>? lichessAnalysisComments,
IList<PgnComment>? startingComments,
Expand Down
25 changes: 2 additions & 23 deletions lib/src/model/game/game_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lichess_mobile/src/model/account/account_preferences.dart';
import 'package:lichess_mobile/src/model/account/account_repository.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/analysis/server_analysis_service.dart';
import 'package:lichess_mobile/src/model/clock/chess_clock.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
Expand Down Expand Up @@ -424,21 +423,6 @@ class GameController extends _$GameController {
_socketClient.send('rematch-no', null);
}

Future<void> requestServerAnalysis() {
return state.mapOrNull(
data: (d) {
if (!d.value.game.finished) {
return Future<void>.error(
'Cannot request server analysis on a non finished game',
);
}
final service = ref.read(serverAnalysisServiceProvider);
return service.requestAnalysis(gameFullId);
},
) ??
Future<void>.value();
}

/// Gets the live game clock if available.
LiveGameClock? get _liveClock => _clock != null
? (
Expand Down Expand Up @@ -1183,13 +1167,8 @@ class GameState with _$GameState {
String get analysisPgn => game.makePgn();

AnalysisOptions get analysisOptions => AnalysisOptions(
isLocalEvaluationAllowed: true,
variant: game.meta.variant,
initialMoveCursor: stepCursor,
orientation: game.youAre ?? Side.white,
id: gameFullId,
opening: game.meta.opening,
serverAnalysis: game.serverAnalysis,
division: game.meta.division,
initialMoveCursor: stepCursor,
gameId: gameFullId.gameId,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:lichess_mobile/src/model/common/speed.dart';
import 'package:lichess_mobile/src/model/opening_explorer/opening_explorer.dart';
import 'package:lichess_mobile/src/model/opening_explorer/opening_explorer_preferences.dart';
import 'package:lichess_mobile/src/network/http.dart';
import 'package:lichess_mobile/src/utils/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'opening_explorer_repository.g.dart';
Expand All @@ -20,6 +21,7 @@ class OpeningExplorer extends _$OpeningExplorer {
Future<({OpeningExplorerEntry entry, bool isIndexing})?> build({
required String fen,
}) async {
await ref.debounce(const Duration(milliseconds: 300));
ref.onDispose(() {
_openingExplorerSubscription?.cancel();
});
Expand Down
18 changes: 9 additions & 9 deletions lib/src/model/study/study_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier {
currentNode: StudyCurrentNode.illegalPosition(),
pgnRootComments: rootComments,
pov: orientation,
isLocalEvaluationAllowed: false,
isComputerAnalysisAllowed: false,
isLocalEvaluationEnabled: false,
gamebookActive: false,
pgn: pgn,
Expand All @@ -121,7 +121,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier {
pgnRootComments: rootComments,
lastMove: lastMove,
pov: orientation,
isLocalEvaluationAllowed:
isComputerAnalysisAllowed:
study.chapter.features.computer && !study.chapter.gamebook,
isLocalEvaluationEnabled: prefs.enableLocalEvaluation,
gamebookActive: study.chapter.gamebook,
Expand Down Expand Up @@ -289,9 +289,9 @@ class StudyController extends _$StudyController implements PgnTreeNotifier {
_root.isOnMainline(path) ? node.children.skip(1) : node.children;

for (final child in childrenToShow) {
child.isHidden = false;
child.isCollapsed = false;
for (final grandChild in child.children) {
grandChild.isHidden = false;
grandChild.isCollapsed = false;
}
}
state = AsyncValue.data(state.requireValue.copyWith(root: _root.view));
Expand All @@ -304,7 +304,7 @@ class StudyController extends _$StudyController implements PgnTreeNotifier {
final node = _root.nodeAt(path);

for (final child in node.children) {
child.isHidden = true;
child.isCollapsed = true;
}

state = AsyncValue.data(state.requireValue.copyWith(root: _root.view));
Expand Down Expand Up @@ -417,9 +417,9 @@ class StudyController extends _$StudyController implements PgnTreeNotifier {
// always show variation if the user plays a move
if (shouldForceShowVariation &&
currentNode is Branch &&
currentNode.isHidden) {
currentNode.isCollapsed) {
_root.updateAt(path, (node) {
if (node is Branch) node.isHidden = false;
if (node is Branch) node.isCollapsed = false;
});
}

Expand Down Expand Up @@ -559,7 +559,7 @@ class StudyState with _$StudyState {
required Side pov,

/// Whether local evaluation is allowed for this study.
required bool isLocalEvaluationAllowed,
required bool isComputerAnalysisAllowed,

/// Whether we're currently in gamebook mode, where the user has to find the right moves.
required bool gamebookActive,
Expand All @@ -583,7 +583,7 @@ class StudyState with _$StudyState {

/// Whether the engine is available for evaluation
bool get isEngineAvailable =>
isLocalEvaluationAllowed &&
isComputerAnalysisAllowed &&
engineSupportedVariants.contains(variant) &&
isLocalEvaluationEnabled;

Expand Down
43 changes: 24 additions & 19 deletions lib/src/view/analysis/analysis_board.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ import 'package:lichess_mobile/src/widgets/pgn.dart';

class AnalysisBoard extends ConsumerStatefulWidget {
const AnalysisBoard(
this.pgn,
this.options,
this.boardSize, {
this.borderRadius,
this.enableDrawingShapes = true,
this.shouldReplaceChildOnUserMove = false,
});

final String pgn;
final AnalysisOptions options;
final double boardSize;
final BorderRadiusGeometry? borderRadius;

final bool enableDrawingShapes;
final bool shouldReplaceChildOnUserMove;

@override
ConsumerState<AnalysisBoard> createState() => AnalysisBoardState();
Expand All @@ -38,26 +38,28 @@ class AnalysisBoardState extends ConsumerState<AnalysisBoard> {

@override
Widget build(BuildContext context) {
final ctrlProvider = analysisControllerProvider(widget.pgn, widget.options);
final analysisState = ref.watch(ctrlProvider);
final ctrlProvider = analysisControllerProvider(widget.options);
final analysisState = ref.watch(ctrlProvider).requireValue;
final boardPrefs = ref.watch(boardPreferencesProvider);
final showBestMoveArrow = ref.watch(
analysisPreferencesProvider.select(
(value) => value.showBestMoveArrow,
),
);
final showAnnotationsOnBoard = ref.watch(
analysisPreferencesProvider.select((value) => value.showAnnotations),
);

final evalBestMoves = ref.watch(
engineEvaluationProvider.select((s) => s.eval?.bestMoves),
);
final analysisPrefs = ref.watch(analysisPreferencesProvider);
final enableComputerAnalysis = analysisPrefs.enableComputerAnalysis;
final showBestMoveArrow =
enableComputerAnalysis && analysisPrefs.showBestMoveArrow;
final showAnnotationsOnBoard =
enableComputerAnalysis && analysisPrefs.showAnnotations;
final evalBestMoves = enableComputerAnalysis
? ref.watch(
engineEvaluationProvider.select((s) => s.eval?.bestMoves),
)
: null;

final currentNode = analysisState.currentNode;
final annotation = makeAnnotation(currentNode.nags);
final annotation =
showAnnotationsOnBoard ? makeAnnotation(currentNode.nags) : null;

final bestMoves = evalBestMoves ?? currentNode.eval?.bestMoves;
final bestMoves = enableComputerAnalysis
? evalBestMoves ?? currentNode.eval?.bestMoves
: null;

final sanMove = currentNode.sanMove;

Expand Down Expand Up @@ -87,7 +89,10 @@ class AnalysisBoardState extends ConsumerState<AnalysisBoard> {
validMoves: analysisState.validMoves,
promotionMove: analysisState.promotionMove,
onMove: (move, {isDrop, captured}) =>
ref.read(ctrlProvider.notifier).onUserMove(move),
ref.read(ctrlProvider.notifier).onUserMove(
move,
shouldReplace: widget.shouldReplaceChildOnUserMove,
),
onPromotionSelection: (role) =>
ref.read(ctrlProvider.notifier).onPromotionSelection(role),
),
Expand Down
Loading

0 comments on commit 62a5d79

Please sign in to comment.