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
1 change: 1 addition & 0 deletions lib/src/model/analysis/analysis_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class AnalysisOptions with _$AnalysisOptions {
required String pgn,
int? initialMoveCursor,
LightOpening? opening,
Division? division,

/// Optional server analysis to display player stats.
({PlayerAnalysis white, PlayerAnalysis black})? serverAnalysis,
Expand Down
11 changes: 11 additions & 0 deletions lib/src/model/common/chess.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ class LightOpening with _$LightOpening implements Opening {
_$LightOpeningFromJson(json);
}

@Freezed(fromJson: true, toJson: true)
class Division with _$Division {
const factory Division({
double? middlegame,
double? endgame,
}) = _Division;

factory Division.fromJson(Map<String, dynamic> json) =>
_$DivisionFromJson(json);
}

@freezed
class FullOpening with _$FullOpening implements Opening {
const FullOpening._();
Expand Down
9 changes: 9 additions & 0 deletions lib/src/model/game/archived_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ ArchivedGame _archivedGameFromPick(RequiredPick pick) {
final clocks = pick('clocks').asListOrNull<Duration>(
(p0) => Duration(milliseconds: p0.asIntOrThrow() * 10),
);
final division = pick('division').letOrNull(_divisionFromPick);

final initialFen = pick('initialFen').asStringOrNull();

Expand All @@ -147,6 +148,7 @@ ArchivedGame _archivedGameFromPick(RequiredPick pick) {
)
: null,
opening: data.opening,
division: division,
),
data: data,
status: data.status,
Expand Down Expand Up @@ -243,3 +245,10 @@ PlayerAnalysis _playerAnalysisFromPick(RequiredPick pick) {
accuracy: pick('accuracy').asIntOrNull(),
);
}

Division _divisionFromPick(RequiredPick pick) {
return Division(
middlegame: pick('middle').asDoubleOrNull(),
endgame: pick('end').asDoubleOrNull(),
);
}
3 changes: 3 additions & 0 deletions lib/src/model/game/game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ class GameMeta with _$GameMeta {

/// Opening of the game, only available once finished
LightOpening? opening,

/// Game phases of the game, only avaible once finished
Division? division,
}) = _GameMeta;

factory GameMeta.fromJson(Map<String, dynamic> json) =>
Expand Down
1 change: 1 addition & 0 deletions lib/src/model/game/game_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -961,5 +961,6 @@ class GameState with _$GameState {
id: gameFullId,
opening: game.meta.opening,
serverAnalysis: game.serverAnalysis,
division: game.meta.division,
);
}
8 changes: 8 additions & 0 deletions lib/src/model/game/playable_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ GameMeta _playableGameMetaFromPick(RequiredPick pick) {
),
),
),
division: pick('division').letOrNull(_divisionFromPick),
);
}

Expand Down Expand Up @@ -273,3 +274,10 @@ Message _messageFromPick(RequiredPick pick) {
username: pick('u').asStringOrThrow(),
);
}

Division _divisionFromPick(RequiredPick pick) {
return Division(
middlegame: pick('middle').asDoubleOrNull(),
endgame: pick('end').asDoubleOrNull(),
);
}
21 changes: 21 additions & 0 deletions lib/src/view/analysis/analysis_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,8 @@ class ServerAnalysisSummary extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AcplChart(options),
// may be removed if game phases text is displayed vertically instead of horizontally
const SizedBox(height: 10.0),
Center(
child: SizedBox(
width: math.min(MediaQuery.sizeOf(context).width, 500),
Expand Down Expand Up @@ -1030,6 +1032,17 @@ class AcplChart extends ConsumerWidget {
final belowLineColor = Colors.white.withOpacity(0.7);
final aboveLineColor = Colors.grey.shade800.withOpacity(0.8);

VerticalLine phaseVerticalBar(double x, String label) => VerticalLine(
x: x,
color: const Color(0xFF707070),
strokeWidth: 0.5,
label: VerticalLineLabel(
style: const TextStyle(fontSize: 10),
labelResolver: (line) => label,
show: true,
),
);

final data = ref.watch(
analysisControllerProvider(options)
.select((value) => value.acplChartData),
Expand Down Expand Up @@ -1116,6 +1129,14 @@ class AcplChart extends ConsumerWidget {
color: mainLineColor,
strokeWidth: 1.0,
),
phaseVerticalBar(0.0, 'Opening'),
if (options.division?.endgame != null)
phaseVerticalBar(options.division!.endgame!, 'Endgame'),
if (options.division?.middlegame != null)
phaseVerticalBar(
options.division!.middlegame!,
'Middlegame',
),
],
),
gridData: const FlGridData(show: false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ class _BodyState extends ConsumerState<_Body> {
initialMoveCursor: stepCursor,
orientation: game.youAre,
id: game.id,
division: game.meta.division,
),
title: context.l10n.analysis,
),
Expand Down
1 change: 1 addition & 0 deletions lib/src/view/game/archived_game_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class _BottomBar extends ConsumerWidget {
id: gameData.id,
opening: gameData.opening,
serverAnalysis: game.serverAnalysis,
division: game.meta.division,
),
),
);
Expand Down
1 change: 1 addition & 0 deletions lib/src/view/game/game_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class _ContextMenu extends ConsumerWidget {
id: game.id,
opening: game.opening,
serverAnalysis: serverAnalysis,
division: archivedGame.meta.division,
),
),
);
Expand Down
4 changes: 2 additions & 2 deletions test/model/game/game_repository_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void main() {
group('GameRepository.getGame', () {
test('game with clocks', () async {
const testResponse = '''
{"id":"qVChCOTc","rated":false,"variant":"standard","speed":"blitz","perf":"blitz","createdAt":1673443822389,"lastMoveAt":1673444036416,"status":"mate","players":{"white":{"aiLevel":1},"black":{"user":{"name":"veloce","patron":true,"id":"veloce"},"rating":1435,"provisional":true}},"winner":"black","opening":{"eco":"C20","name":"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit","ply":4},"moves":"e4 e5 Qh5 Nf6 Qxe5+ Be7 b3 d6 Qb5+ Bd7 Qxb7 Nc6 Ba3 Rb8 Qa6 Nxe4 Bb2 O-O Nc3 Nb4 Nf3 Nxa6 Nd5 Nb4 Nxe7+ Qxe7 Nd4 Qf6 f4 Qe7 Ke2 Ng3+ Kd1 Nxh1 Bc4 Nf2+ Kc1 Qe1#","clocks":[18003,18003,17915,17627,17771,16691,17667,16243,17475,15459,17355,14779,17155,13795,16915,13267,14771,11955,14451,10995,14339,10203,13899,9099,12427,8379,12003,7547,11787,6691,11355,6091,11147,5763,10851,5099,10635,4657],"clock":{"initial":180,"increment":0,"totalTime":180}}
{"id":"qVChCOTc","rated":false,"variant":"standard","speed":"blitz","perf":"blitz","createdAt":1673443822389,"lastMoveAt":1673444036416,"status":"mate","players":{"white":{"aiLevel":1},"black":{"user":{"name":"veloce","patron":true,"id":"veloce"},"rating":1435,"provisional":true}},"winner":"black","opening":{"eco":"C20","name":"King's Pawn Game: Wayward Queen Attack, Kiddie Countergambit","ply":4},"moves":"e4 e5 Qh5 Nf6 Qxe5+ Be7 b3 d6 Qb5+ Bd7 Qxb7 Nc6 Ba3 Rb8 Qa6 Nxe4 Bb2 O-O Nc3 Nb4 Nf3 Nxa6 Nd5 Nb4 Nxe7+ Qxe7 Nd4 Qf6 f4 Qe7 Ke2 Ng3+ Kd1 Nxh1 Bc4 Nf2+ Kc1 Qe1#","clocks":[18003,18003,17915,17627,17771,16691,17667,16243,17475,15459,17355,14779,17155,13795,16915,13267,14771,11955,14451,10995,14339,10203,13899,9099,12427,8379,12003,7547,11787,6691,11355,6091,11147,5763,10851,5099,10635,4657],"clock":{"initial":180,"increment":0,"totalTime":180},"division":{"middle":18,"end":42}}
''';

when(
Expand All @@ -104,7 +104,7 @@ void main() {

test('threeCheck game', () async {
const testResponse = '''
{"id":"1vdsvmxp","rated":true,"variant":"threeCheck","speed":"bullet","perf":"threeCheck","createdAt":1604194310939,"lastMoveAt":1604194361831,"status":"variantEnd","players":{"white":{"user":{"name":"Zhigalko_Sergei","title":"GM","patron":true,"id":"zhigalko_sergei"},"rating":2448,"ratingDiff":6,"analysis":{"inaccuracy":1,"mistake":1,"blunder":1,"acpl":75}},"black":{"user":{"name":"catask","id":"catask"},"rating":2485,"ratingDiff":-6,"analysis":{"inaccuracy":1,"mistake":0,"blunder":2,"acpl":115}}},"winner":"white","opening":{"eco":"B02","name":"Alekhine Defense: Scandinavian Variation, Geschev Gambit","ply":6},"moves":"e4 c6 Nc3 d5 exd5 Nf6 Nf3 e5 Bc4 Bd6 O-O O-O h3 e4 Kh1 exf3 Qxf3 cxd5 Nxd5 Nxd5 Bxd5 Nc6 Re1 Be6 Rxe6 fxe6 Bxe6+ Kh8 Qh5 h6 Qg6 Qf6 Qh7+ Kxh7 Bf5+","analysis":[{"eval":340},{"eval":359},{"eval":231},{"eval":300,"best":"g8f6","variation":"Nf6 e5 d5 d4 Ne4 Bd3 Bf5 Nf3 e6 O-O","judgment":{"name":"Inaccuracy","comment":"Inaccuracy. Nf6 was best."}},{"eval":262},{"eval":286},{"eval":184,"best":"f1c4","variation":"Bc4 e6 dxe6 Bxe6 Bxe6 fxe6 Qe2 Qd7 Nf3 Bd6","judgment":{"name":"Inaccuracy","comment":"Inaccuracy. Bc4 was best."}},{"eval":235},{"eval":193},{"eval":243},{"eval":269},{"eval":219},{"eval":-358,"best":"d2d3","variation":"d3 Bg4 h3 e4 Nxe4 Bh2+ Kh1 Nxe4 dxe4 Qf6","judgment":{"name":"Blunder","comment":"Blunder. d3 was best."}},{"eval":-376},{"eval":-386},{"eval":-383},{"eval":-405},{"eval":-363},{"eval":-372},{"eval":-369},{"eval":-345},{"eval":-276},{"eval":-507,"best":"b2b3","variation":"b3 Be6","judgment":{"name":"Mistake","comment":"Mistake. b3 was best."}},{"eval":-49,"best":"c6e5","variation":"Ne5 Qh5","judgment":{"name":"Blunder","comment":"Blunder. Ne5 was best."}},{"eval":-170},{"mate":7,"best":"g8h8","variation":"Kh8 Rh6","judgment":{"name":"Blunder","comment":"Checkmate is now unavoidable. Kh8 was best."}},{"mate":6},{"mate":6},{"mate":5},{"mate":3},{"mate":2},{"mate":2},{"mate":1},{"mate":1}],"clock":{"initial":60,"increment":0,"totalTime":60}}
{"id":"1vdsvmxp","rated":true,"variant":"threeCheck","speed":"bullet","perf":"threeCheck","createdAt":1604194310939,"lastMoveAt":1604194361831,"status":"variantEnd","players":{"white":{"user":{"name":"Zhigalko_Sergei","title":"GM","patron":true,"id":"zhigalko_sergei"},"rating":2448,"ratingDiff":6,"analysis":{"inaccuracy":1,"mistake":1,"blunder":1,"acpl":75}},"black":{"user":{"name":"catask","id":"catask"},"rating":2485,"ratingDiff":-6,"analysis":{"inaccuracy":1,"mistake":0,"blunder":2,"acpl":115}}},"winner":"white","opening":{"eco":"B02","name":"Alekhine Defense: Scandinavian Variation, Geschev Gambit","ply":6},"moves":"e4 c6 Nc3 d5 exd5 Nf6 Nf3 e5 Bc4 Bd6 O-O O-O h3 e4 Kh1 exf3 Qxf3 cxd5 Nxd5 Nxd5 Bxd5 Nc6 Re1 Be6 Rxe6 fxe6 Bxe6+ Kh8 Qh5 h6 Qg6 Qf6 Qh7+ Kxh7 Bf5+","analysis":[{"eval":340},{"eval":359},{"eval":231},{"eval":300,"best":"g8f6","variation":"Nf6 e5 d5 d4 Ne4 Bd3 Bf5 Nf3 e6 O-O","judgment":{"name":"Inaccuracy","comment":"Inaccuracy. Nf6 was best."}},{"eval":262},{"eval":286},{"eval":184,"best":"f1c4","variation":"Bc4 e6 dxe6 Bxe6 Bxe6 fxe6 Qe2 Qd7 Nf3 Bd6","judgment":{"name":"Inaccuracy","comment":"Inaccuracy. Bc4 was best."}},{"eval":235},{"eval":193},{"eval":243},{"eval":269},{"eval":219},{"eval":-358,"best":"d2d3","variation":"d3 Bg4 h3 e4 Nxe4 Bh2+ Kh1 Nxe4 dxe4 Qf6","judgment":{"name":"Blunder","comment":"Blunder. d3 was best."}},{"eval":-376},{"eval":-386},{"eval":-383},{"eval":-405},{"eval":-363},{"eval":-372},{"eval":-369},{"eval":-345},{"eval":-276},{"eval":-507,"best":"b2b3","variation":"b3 Be6","judgment":{"name":"Mistake","comment":"Mistake. b3 was best."}},{"eval":-49,"best":"c6e5","variation":"Ne5 Qh5","judgment":{"name":"Blunder","comment":"Blunder. Ne5 was best."}},{"eval":-170},{"mate":7,"best":"g8h8","variation":"Kh8 Rh6","judgment":{"name":"Blunder","comment":"Checkmate is now unavoidable. Kh8 was best."}},{"mate":6},{"mate":6},{"mate":5},{"mate":3},{"mate":2},{"mate":2},{"mate":1},{"mate":1}],"clock":{"initial":60,"increment":0,"totalTime":60},"division":{"middle":18,"end":42}}
''';

when(
Expand Down