So I really think this can be done within a day or two for those who know how to code OGS features. I was able to do this on Tsumego Dragon in a dozen or so hours.
I even made drop downs with clickable moves. When you click the move number, the game navigates to that move.
For accuracy, I did number of moves within >= AI move - n where n is an offset I was playing around with. This number could also be flexible to ranks but that gets into adjustable ranks like AI Sensei has.
My suggested spot would be to put it right under the graph toggle and have it hidden by default with a toggle to show the results. The toggle can also be only for premium users and when clicked by non premium users perhaps have a popup redirecting them to the premium page. Or just have it hidden if they are not premium.
I think this would be an amazing feature and all of the numbers are already there.
–
Here is my amateur-ish code.
var gameStages = {
"opening": {"min": 1, "max":29},
"early_Middle_Game": {"min": 30, "max":69},
"late_Middle_Game": {"min": 70, "max":119},
"endgame": {"min": 120, "max":1000}
};
// Returns 1 for odd and 0 for even.
function IsOdd(num) { return num % 2;}
/*
Key Moves
The top ai move is >= 25% more than the second best ai move.
The top ai move is repeated multiple times.
The top ai move is >= 15 points.
*/
// Weights for player's accuracy score
var accuracyWinRateErrorMargin = 0.02;
var accuracyScoreErrorMargin = 1;
// List to send to bubble
var p1Brilliant = [];
var p2Brilliant = [];
var p1Best = [];
var p2Best = [];
var p1Excellent = [];
var p2Excellent = [];
var p1Good = [];
var p2Good = [];
var p1Inaccuracy = [];
var p2Inaccuracy = [];
var p1Mistake = [];
var p2Mistake = [];
var p1Blunder = [];
var p2Blunder = [];
// Moves that made the most dramatic difference
var keyMoves = [];
// Get the accuracy scores of the players.
function CheckAccuracy (data, moves) {
var p1TopWinRateMoves = [];
var p1TopScoreMoves = [];
var p2TopWinRateMoves = [];
var p2TopScoreMoves = [];
var oddNum = false;
for (var i=0; i<moves.length; i++) {
oddNum = (IsOdd(i) === 1);
var moveKey = "move-" + i;
var prevMoveKey = "move-" + (i-1);
var currentMove;
var branches;
var previousBranches = [];
var move = moves[i];
if(data[moveKey] != undefined) {
currentMove = data[moveKey];
branches = currentMove.branches;
}
if(i > 0) {
if(data[prevMoveKey] != undefined) {
previousBranches = data[prevMoveKey].branches;
}
var good_win_rate = false;
var good_score = false;
var isBrilliant = IsBrilliant(currentMove, previousBranches, oddNum);
var isBest = IsBest(currentMove, previousBranches, oddNum);
var isExcellent = IsExcellent(move);
var isGood = IsGood(move);
var isInaccuracy = IsInaccuracy(move);
var isMistake = IsMistake(move);
var isBlunder = IsBlunder(move);
// P1 Winrate Check
if(oddNum) {
if(isBrilliant) {p1Brilliant.push(move.move_number);}
if(isBest) {p1Best.push(move.move_number);}
if(isExcellent) {p1Excellent.push(move.move_number);}
if(isGood) {p1Good.push(move.move_number);}
if(isInaccuracy) {p1Inaccuracy.push(move.move_number);}
if(isMistake) {p1Mistake.push(move.move_number);}
if(isBlunder) {p1Blunder.push(move.move_number);}
var currentWinRate = move.win_rate;
var currentScore = move.score;
if(previousBranches.length > 0) {
var bestMoveWinRate = previousBranches[0].win_rate - accuracyWinRateErrorMargin;
var bestScore = previousBranches[0].score;
if(currentWinRate >= bestMoveWinRate)
{good_win_rate = true;}
if(currentScore >= bestScore - accuracyScoreErrorMargin)
{good_score = true;}
}
}
// P2 Winrate Check
else {
if(isBrilliant) {p2Brilliant.push(move.move_number);}
if(isBest) {p2Best.push(move.move_number);}
if(isExcellent) {p2Excellent.push(move.move_number);}
if(isGood) {p2Good.push(move.move_number);}
if(isInaccuracy) {p2Inaccuracy.push(move.move_number);}
if(isMistake) {p2Mistake.push(move.move_number);}
if(isBlunder) {p2Blunder.push(move.move_number);}
var currentWinRate = move.win_rate;
var currentScore = move.score;
if(previousBranches.length > 0) {
var worstMoveWinRate = previousBranches[0].win_rate;
var worstScore = previousBranches[0].score;
if(currentWinRate <= worstMoveWinRate + accuracyWinRateErrorMargin)
{good_win_rate = true;}
if(currentScore <= worstScore + accuracyScoreErrorMargin)
{good_score = true;}
}
}
// Compare current move to best move.
if(good_win_rate) {
if(oddNum) {
p1TopWinRateMoves.push(i);
} else {
p2TopWinRateMoves.push(i);
}
}
if(good_score) {
if(oddNum) {
p1TopScoreMoves.push(i);
} else {
p2TopScoreMoves.push(i);
}
}
}
oddNum = !oddNum;
}
var bubbleData = {
"outputlist1": p1TopWinRateMoves,
"outputlist2":p1TopScoreMoves,
"outputlist3": p2TopWinRateMoves,
"outputlist4":p2TopScoreMoves
}
bubble_fn_top_moves_accuracy(bubbleData);
bubbleData = {
"outputlist1": p1Brilliant,
"outputlist2":p2Brilliant,
}
bubble_fn_brilliant(bubbleData);
bubbleData = {
"outputlist1": p1Best,
"outputlist2":p2Best,
}
bubble_fn_best(bubbleData);
bubbleData = {
"outputlist1": p1Excellent,
"outputlist2":p2Excellent,
}
bubble_fn_excellent(bubbleData);
bubbleData = {
"outputlist1": p1Good,
"outputlist2":p2Good,
}
bubble_fn_good(bubbleData);
bubbleData = {
"outputlist1": p1Inaccuracy,
"outputlist2":p2Inaccuracy,
}
bubble_fn_inaccuracy(bubbleData);
bubbleData = {
"outputlist1": p1Mistake,
"outputlist2":p2Mistake,
}
bubble_fn_mistake(bubbleData);
bubbleData = {
"outputlist1": p1Blunder,
"outputlist2":p2Blunder,
}
bubble_fn_blunder(bubbleData);
}
function IsBrilliant (move, prevBranches, isOdd) {
/*
Brilliant
Opening - NA
Early > 0
Late > 0
End > 0
*/
if(prevBranches.length < 1) {return false;}
if(move.move_number <= gameStages.opening.max) {return false;}
var aiTopScore = prevBranches[0].score;
var score = move.score;
if(score > aiTopScore && isOdd) {return true;}
else if (score < aiTopScore && !isOdd) {return true;}
return false;
}
function IsBest (move, prevBranches, isOdd) {
/*
Best
Opening - NA
Early <= 0.2
Late <= 0.2
End <= 0.2
*/
if(prevBranches.length < 1) {return false;}
if(move.move_number <= gameStages.opening.max) {return false;}
if(move.move.x != prevBranches[0].moves[0].x) {return false;}
if(move.move.y != prevBranches[0].moves[0].y) {return false;}
return true;
}
function IsExcellent (move) {
/*
Excellent
Opening <= 2% -- Pro Moves
Early > 0.2 < 1
Late > 0.2 < 1
End > 0.2 < 1
*/
var isOdd = IsOdd(move.move_number);
if(move.move_number <= gameStages.opening.max) {
return false;
} else {
if (move.score_change >= -0.2 && move.score_change <= 0.2)
{return true;}
else {return false;}
}
}
function IsGood (move) {
/*
Good
Opening > 20% <=10% -- Less than 10% is top Amateur moves
Early >= 1 < 2
Late >= 1 < 2
End >= 1 < 2
*/
var isOdd = IsOdd(move.move_number);
if(move.move_number <= gameStages.opening.max) {
return false;
} else {
if(isOdd) {
if (move.score_change >= -2 && move.score_change <= 10)
{return true;}
else {return false;}
} else {
if (move.score_change > 0.2 && move.score_change <= 2)
{return true;}
else {return false;}
}
}
}
function IsInaccuracy (move) {
/*
Inaccuracy
Opening > 10% <=24% -- Rest of the Opening
Early >= 2 < 10
Early >= 2 < 10
End >= 2 < 10
*/
var isOdd = IsOdd(move.move_number);
if(move.move_number <= gameStages.opening.max) {
if(isOdd) {
if (move.win_rate_change >= -25 && move.win_rate_change < -10)
{return true;}
else {return false;}
} else {
if (move.win_rate_change <= 25 && move.win_rate_change > 10)
{return true;}
else {return false;}
}
} else {
if(isOdd) {
if (move.score_change < -2 && move.score_change >= -10)
{return true;}
else {return false;}
} else {
if (move.score_change > 2 && move.score_change <= 10)
{return true;}
else {return false;}
}
}
}
function IsMistake (move) {
/*
Mistake
Opening > 25% <= 35% -- Some mistake likely in joseki.
Early > 10 <= 14
Late > 10 <= 14
End > 10 <= 14
*/
var isOdd = IsOdd(move.move_number);
if(move.move_number <= gameStages.opening.max) {
if(isOdd) {
if (move.win_rate_change >= -35 && move.win_rate_change < -24)
{return true;}
else {return false;}
} else {
if (move.win_rate_change <= 35 && move.win_rate_change > 24)
{return true;}
else {return false;}
}
} else {
if(isOdd) {
if (move.score_change < -10 && move.score_change >= -14)
{return true;}
else {return false;}
} else {
if (move.score_change > 10 && move.score_change <= 14)
{return true;}
else {return false;}
}
}
}
function IsBlunder (move) {
/*
Mistake
Opening > 35% -- Some mistake likely in joseki.
Early > 10 <= 14
Late > 10 <= 14
End > 10 <= 14
*/
var isOdd = IsOdd(move.move_number);
if(move.move_number <= gameStages.opening.max) {
if(isOdd) {
if (move.win_rate_change < -35)
{return true;}
else {return false;}
} else {
if (move.win_rate_change > 35)
{return true;}
else {return false;}
}
} else {
if(isOdd) {
if (move.score_change <= -15)
{return true;}
else {return false;}
} else {
if (move.score_change >= 15)
{return true;}
else {return false;}
}
}
}