Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions lib/src/model/broadcast/broadcast_analysis_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/common/eval.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/common/node.dart';
import 'package:lichess_mobile/src/model/common/service/move_feedback.dart';
Expand Down Expand Up @@ -181,6 +182,9 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
// Sent when a pgn tag changes
case 'setTags':
_handleSetTagsEvent(event);
// Sent when a new eval is received
case 'evalHit':
_handleEvalHitEvent(event);
}
}

Expand Down Expand Up @@ -240,6 +244,42 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController implemen
state = AsyncData(state.requireValue.copyWith(pgnHeaders: pgnHeaders));
}

void _handleEvalHitEvent(SocketEvent event) {
final path = pick(event.data, 'path').asUciPathOrThrow();

final depth = pick(event.data, 'depth').asIntOrThrow();
final pvs =
pick(event.data, 'pvs')
.asListOrThrow(
(pv) => PvData(
moves: pv('moves').asStringOrThrow().split(' ').toIList(),
cp: pv('cp').asIntOrNull(),
mate: pv('mate').asIntOrNull(),
),
)
.toIList();

final cloudEval = CloudEval(depth: depth, position: state.requireValue.position, pvs: pvs);

_root.updateAt(path, (node) => node.eval = cloudEval);

if (state.requireValue.currentPath != path) return;

_refreshCurrentNode(shouldRecomputeRootView: true);
}

// ignore: unused_element
void _sendEvalGetEvent() {
final numEvalLines = ref.read(analysisPreferencesProvider).numEvalLines;

_socketClient.send('evalGet', {
'fen': state.requireValue.currentNode.position.fen,
'path': state.requireValue.currentPath.value,
'mpv': numEvalLines,
'up': true,
});
}

EvaluationContext get _evaluationContext =>
EvaluationContext(variant: Variant.standard, initialPosition: _root.position);

Expand Down
1 change: 1 addition & 0 deletions lib/src/model/common/eval.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ part 'eval.g.dart';

/// Base class for evals.
sealed class Eval {
/// The string to display the eval
String get evalString;

/// The winning chances for the given [Side].
Expand Down
6 changes: 2 additions & 4 deletions lib/src/model/engine/evaluation_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ const engineSupportedVariants = {Variant.standard, Variant.chess960, Variant.fro

/// A service to evaluate chess positions using an engine.
class EvaluationService {
EvaluationService(this.ref, {required this.maxMemory});

final Ref ref;
EvaluationService({required this.maxMemory});

final int maxMemory;

Expand Down Expand Up @@ -191,7 +189,7 @@ class EvaluationService {
EvaluationService evaluationService(Ref ref) {
final maxMemory = ref.read(preloadedDataProvider).requireValue.engineMaxMemoryInMb;

final service = EvaluationService(ref, maxMemory: maxMemory);
final service = EvaluationService(maxMemory: maxMemory);
ref.onDispose(() {
service.disposeEngine();
});
Expand Down
4 changes: 2 additions & 2 deletions lib/src/view/analysis/analysis_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class _AnalysisScreenState extends ConsumerState<_AnalysisScreen>

final appBarActions = [
if (prefs.enableComputerAnalysis)
EngineDepth(defaultEval: asyncState.valueOrNull?.currentNode.eval),
EngineDepth(savedEval: asyncState.valueOrNull?.currentNode.eval),
AppBarAnalysisTabIndicator(tabs: tabs, controller: _tabController),
AppBarIconButton(
onPressed: () {
Expand Down Expand Up @@ -214,7 +214,7 @@ class _Body extends ConsumerWidget {
isEngineAvailable && numEvalLines > 0
? EngineLines(
onTapMove: ref.read(ctrlProvider.notifier).onUserMove,
localEval: currentNode.eval,
savedEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
)
: null,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/view/broadcast/broadcast_game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class _Body extends ConsumerWidget {
engineLines:
isLocalEvaluationEnabled && numEvalLines > 0
? EngineLines(
localEval: currentNode.eval,
savedEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
onTapMove:
ref
Expand Down
128 changes: 75 additions & 53 deletions lib/src/view/engine/engine_depth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,88 @@ import 'package:lichess_mobile/src/widgets/list.dart';
import 'package:popover/popover.dart';

class EngineDepth extends ConsumerWidget {
const EngineDepth({this.defaultEval});
const EngineDepth({this.savedEval});

final ClientEval? defaultEval;
final ClientEval? savedEval;

@override
Widget build(BuildContext context, WidgetRef ref) {
final depth =
ref.watch(engineEvaluationProvider.select((value) => value.eval?.depth)) ??
defaultEval?.depth;
final eval = ref.watch(engineEvaluationProvider).eval ?? savedEval;

return depth != null
return eval != null
? AppBarTextButton(
onPressed: () {
showPopover(
context: context,
bodyBuilder: (context) {
return _StockfishInfo(defaultEval);
},
direction: PopoverDirection.top,
width: 240,
backgroundColor:
Theme.of(context).platform == TargetPlatform.android
? DialogTheme.of(context).backgroundColor ??
ColorScheme.of(context).surfaceContainerHigh
: CupertinoDynamicColor.resolve(
CupertinoColors.tertiarySystemBackground,
context,
),
transitionDuration: Duration.zero,
popoverTransitionBuilder: (_, child) => child,
);
if (eval is LocalEval) {
showPopover(
context: context,
bodyBuilder: (context) {
return _StockfishInfo(eval);
},
direction: PopoverDirection.top,
width: 240,
backgroundColor:
Theme.of(context).platform == TargetPlatform.android
? DialogTheme.of(context).backgroundColor ??
ColorScheme.of(context).surfaceContainerHigh
: CupertinoDynamicColor.resolve(
CupertinoColors.tertiarySystemBackground,
context,
),
transitionDuration: Duration.zero,
popoverTransitionBuilder: (_, child) => child,
);
}
},
child: RepaintBoundary(
child: Container(
width: 20.0,
height: 20.0,
padding: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
color: ColorScheme.of(context).secondary,
borderRadius: BorderRadius.circular(4.0),
),
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'${math.min(99, depth)}',
style: TextStyle(
color: ColorScheme.of(context).onSecondary,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
child: _AppBarButtonChild(eval),
)
: const SizedBox.shrink();
}
}

class _AppBarButtonChild extends StatelessWidget {
final ClientEval eval;

const _AppBarButtonChild(this.eval);

@override
Widget build(BuildContext context) {
return switch (eval) {
LocalEval(:final depth) => RepaintBoundary(
child: Container(
width: 20.0,
height: 20.0,
padding: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
color: ColorScheme.of(context).secondary,
borderRadius: BorderRadius.circular(4.0),
),
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'${math.min(99, depth)}',
style: TextStyle(
color: ColorScheme.of(context).onSecondary,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
),
)
: const SizedBox.shrink();
),
),
CloudEval(:final depth) => Stack(
alignment: const AlignmentDirectional(-0.05, 0.15),
children: [
Icon(Icons.cloud, size: 30, color: ColorScheme.of(context).secondary),
Text(
'${math.min(99, depth)}',
style: TextStyle(
color: ColorScheme.of(context).onSecondary,
fontFeatures: const [FontFeature.tabularFigures()],
fontSize: 12,
),
),
],
),
};
}
}

Expand All @@ -86,15 +113,10 @@ class _StockfishInfo extends ConsumerWidget {
final knps = engineState == EngineState.computing ? ', ${eval?.knps.round()}kn/s' : '';
final depth = currentEval?.depth ?? 0;

return Column(
mainAxisSize: MainAxisSize.min,
children: [
PlatformListTile(
leading: Image.asset('assets/images/stockfish/icon.png', width: 44, height: 44),
title: Text(engineName),
subtitle: Text(context.l10n.depthX('$depth$knps')),
),
],
return PlatformListTile(
leading: Image.asset('assets/images/stockfish/icon.png', width: 44, height: 44),
title: Text(engineName),
subtitle: Text(context.l10n.depthX('$depth$knps')),
);
}
}
31 changes: 9 additions & 22 deletions lib/src/view/engine/engine_gauge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,15 @@ class EngineGauge extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final localEval =
params.isLocalEngineAvailable ? ref.watch(engineEvaluationProvider).eval : null;

return localEval != null
? _EvalGauge(
displayMode: displayMode,
position: params.position,
orientation: params.orientation,
eval: localEval,
)
: params.savedEval != null
? _EvalGauge(
displayMode: displayMode,
position: params.position,
orientation: params.orientation,
eval: params.savedEval,
)
: _EvalGauge(
displayMode: displayMode,
position: params.position,
orientation: params.orientation,
);
final eval =
params.isLocalEngineAvailable ? ref.watch(engineEvaluationProvider).eval : params.savedEval;

return _EvalGauge(
displayMode: displayMode,
position: params.position,
orientation: params.orientation,
eval: eval,
);
}
}

Expand Down
6 changes: 3 additions & 3 deletions lib/src/view/engine/engine_lines.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
import 'package:lichess_mobile/src/widgets/buttons.dart';

class EngineLines extends ConsumerWidget {
const EngineLines({required this.onTapMove, required this.localEval, required this.isGameOver});
const EngineLines({required this.onTapMove, required this.savedEval, required this.isGameOver});

final void Function(NormalMove move) onTapMove;
final ClientEval? localEval;
final ClientEval? savedEval;
final bool isGameOver;

@override
Widget build(BuildContext context, WidgetRef ref) {
final numEvalLines = ref.watch(analysisPreferencesProvider.select((p) => p.numEvalLines));
final engineEval = ref.watch(engineEvaluationProvider).eval;
final eval = engineEval ?? localEval;
final eval = engineEval ?? savedEval;

final emptyLines = List.filled(numEvalLines, const Engineline.empty());

Expand Down
2 changes: 1 addition & 1 deletion lib/src/view/study/study_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ class _Body extends ConsumerWidget {
engineLines:
isComputerAnalysisAllowed && isLocalEvaluationEnabled && numEvalLines > 0
? EngineLines(
localEval: currentNode.eval,
savedEval: currentNode.eval,
isGameOver: currentNode.position?.isGameOver ?? false,
onTapMove: ref.read(studyControllerProvider(id).notifier).onUserMove,
)
Expand Down