From 7cb1900a54fc54745bb79971bc0c108d3a11658d Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 29 May 2016 20:55:54 +0200 Subject: [PATCH] Add a training mode. --- www/index.html | 17 ++++- www/js/book.js | 183 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 191 insertions(+), 9 deletions(-) diff --git a/www/index.html b/www/index.html index baaa40d..163ac84 100644 --- a/www/index.html +++ b/www/index.html @@ -24,13 +24,16 @@

+

- +
diff --git a/www/js/book.js b/www/js/book.js index 95f423a..5234eff 100644 --- a/www/js/book.js +++ b/www/js/book.js @@ -2,7 +2,7 @@ var board = null; var game = new Chess(); -var fens = []; +var fens = []; // Position after each. var move_override = 0; var includetransp = true; var stockfish = new Worker('/js/stockfish.js'); @@ -10,6 +10,13 @@ var engine_running = false; var engine_replacement_callback = null; var recommended_move = null; var reverse_dragging_from = null; +var practice_mode = false; +var practice_side = 'W'; + +// TODO: Make this configurable. +var practice_top_moves_limit = 5; +var practice_minimum_move_fraction_start = 0.05; +var practice_minimum_move_fraction_move10 = 0.30; var entity_map = { "&": "&", @@ -26,10 +33,14 @@ function escape_html(string) { } var current_display_fen = function() { - if (move_override == 0) { + return fen_before_move(move_override); +} + +var fen_before_move = function(move_num) { + if (move_num == 0) { return 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; } else { - return fens[move_override - 1]; + return fens[move_num - 1]; } } @@ -62,6 +73,15 @@ var update = function() { $("#board").find('.square-' + highlight_to).addClass('nonuglyhighlight'); } + if (practice_mode) { + find_last_move_score(); + var side_to_move = (move_override % 2 == 0) ? 'W' : 'B'; + if (side_to_move !== practice_side) { + find_computer_move(); + } + // Fall through to get the line name and such. + } + fetch_analysis(); } @@ -81,6 +101,122 @@ var fetch_analysis = function() { }); } +var find_last_move_score = function() { + var history = game.history({ verbose: true }); + var side_to_move = (move_override % 2 == 0) ? 'W' : 'B'; + var move_num = (side_to_move === practice_side) ? (move_override - 2) : (move_override - 1); + if (move_num < 0) { + $("#yourmove").text("(none)"); + $("#yourfraction").text("N/A"); + $("#yourrank").text("N/A"); + $("#yourawin").text("??.?%"); + $("#yourawindiff").text("+?.?%"); + return; + } + + var chosen_move = history[move_num].san; + $("#yourmove").text(chosen_move); + $.ajax({ + url: "/opening-stats.pl?fen=" + encodeURIComponent(fen_before_move(move_num)) + + ";includetransp=0" + }).done(function(data, textstatus, xhr) { + var moves = data['moves']; + var root_move = sort_moves_by_common(moves, data); + var your_move, your_index; + + for (var i = 0; i < moves.length; ++i) { + var move = moves[i]; + if (move['move'] === chosen_move) { + your_move = move; + your_index = i; + } + } + moves.sort(function(a, b) { return b['num'] - a['num'] }); + + if (your_move) { + $("#yourfraction").text(format_fraction(your_move['fraction'])); + $("#yourawin").text(format_fraction(your_move['corrected_win_ratio'])); + $("#yourrank").text(format_ordinal(your_index + 1) + ", based on " + root_move['num'] + " games"); + var diff = your_move['corrected_win_ratio'] - root_move['corrected_win_ratio']; + $("#yourawindiff").css("color", "black"); + if (diff === 0) { + $("#yourawindiff").text("0.0%"); + } else if (diff > 0) { + $("#yourawindiff").text("+" + format_fraction(diff)); + $("#yourawindiff").css("color", "green"); + } else { + $("#yourawindiff").text(format_fraction(diff)); + if (diff < -0.02) { + $("#yourawindiff").css("color", "red"); + } + } + } else { + $("#yourfraction").text("??.?%"); + $("#yourrank").text("?th"); + $("#yourawin").text("??.?%"); + $("#yourawindiff").text("+?.?%"); + } + }); +} + +var find_computer_move = function() { + var fen = current_display_fen(); + $.ajax({ + url: "/opening-stats.pl?fen=" + encodeURIComponent(fen) + ";includetransp=0" + }).done(function(data, textstatus, xhr) { + var candidate_moves = []; + + var moves = data['moves']; + var root_move = sort_moves_by_common(moves, data); + + var practice_minimum_move_fraction; + if (move_override > 20) { + practice_minimum_move_fraction = practice_minimum_move_fraction_move10; + } else { + practice_minimum_move_fraction = practice_minimum_move_fraction_start + + ((move_override-1)/20.0) * (practice_minimum_move_fraction_move10 - practice_minimum_move_fraction_start); + } + console.log(practice_minimum_move_fraction); + + for (var i = 0; i < Math.min(moves.length, practice_top_moves_limit); ++i) { + var move = moves[i]; + if (i == 0 || move['fraction'] >= practice_minimum_move_fraction) { + candidate_moves.push(move); + } + } + + // Pick one at random. + var chosen_index = Math.floor(Math.random() * candidate_moves.length); + var chosen = candidate_moves[chosen_index]; + $("#compmove").text(chosen['move']); + $("#compfraction").text((100.0 * chosen['fraction']).toFixed(1) + "%"); + if (candidate_moves.length == 1) { + $("#comprank").text("only candidate move"); + } else { + $("#comprank").text(format_ordinal(chosen_index + 1) + " out of " + candidate_moves.length + " candidate moves"); + } + make_move(chosen['move']); + }); +} + +// Add deried data and then sort moves to get the most common ones (in-place). +// Remove the root mode and return it. Currently usable for practice mode only! +var sort_moves_by_common = function(moves, data) +{ + var total_num = find_total_games(moves); + var root_move; + for (var i = 0; i < moves.length; ++i) { + var move = moves[i]; + calc_move_derived_data(move, total_num, data, (practice_side === 'W')); + if (!move['move']) { + root_move = (moves.splice(i, 1))[0]; + --i; + } + } + moves.sort(function(a, b) { return b['num'] - a['num'] }); + return root_move; +} + var add_td = function(tr, value) { var td = document.createElement("td"); tr.appendChild(td); @@ -88,6 +224,23 @@ var add_td = function(tr, value) { $(td).text(value); } +var format_ordinal = function(x) { + var tens = Math.floor(x / 10) % 10; + if (tens == 1) { + return x + "th"; + } else { + var ones = x % 10; + if (ones == 1) return x + "st"; + if (ones == 2) return x + "nd"; + if (ones == 3) return x + "rd"; + return x + "th"; + } +} + +var format_fraction = function(x) { + return (100.0 * x).toFixed(1) + '%'; +} + var TYPE_MOVE = 0; var TYPE_INTEGER = 1; var TYPE_FLOAT = 2; @@ -250,7 +403,7 @@ var show_lines = function(data, game) { if (isNaN(line[j]) || !isFinite(line[j])) { add_td(tr, ''); } else { - add_td(tr, (100.0 * line[j]).toFixed(1) + "%"); + add_td(tr, format_fraction(line[j])); } } } @@ -312,6 +465,22 @@ var set_flipboard = function(value) { } window['set_flipboard'] = set_flipboard; +var set_practice = function(value) { + practice_mode = value; + if (practice_mode) { + practice_side = (move_override % 2 == 0) ? 'W' : 'B'; + find_last_move_score(); + $("#stats").hide(); + $("#practiceoutput").show(); + document.getElementById("includetransp").checked = false; + } else { + $("#stats").show(); + $("#practiceoutput").hide(); + } + update(); +} +window['set_practice'] = set_practice; + var make_move = function(move, do_update) { var history = game.history({ verbose: true }); if (move_override < history.length && history[move_override].san == move) { @@ -340,8 +509,9 @@ var make_move = function(move, do_update) { window['make_move'] = make_move; var prev_move = function() { - if (move_override > 0) { - --move_override; + var moves_to_skip = practice_mode ? 2 : 1; + if (move_override >= moves_to_skip) { + move_override -= moves_to_skip; update(); window.history.replaceState(null, null, get_history_url()); } @@ -557,6 +727,7 @@ var init = function() { window.onpopstate = onpopstate; onpopstate(); + set_practice(false); $(window).keyup(function(event) { if (event.which == 39) { -- 2.39.2