Skip to content

Instantly share code, notes, and snippets.

@gwillen
Last active September 24, 2016 23:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gwillen/3e72ae7756b8ac8449e56a7d157de69b to your computer and use it in GitHub Desktop.
Save gwillen/3e72ae7756b8ac8449e56a7d157de69b to your computer and use it in GitHub Desktop.
Userscript to find OGS games
// ==UserScript==
// @name Find good Go games
// @namespace http://nerdnet.org/
// @version 0.1
// @description find good Go games on online-go.com
// @author Glenn Willen (gwillen@nerdnet.org)
// @match *://online-go.com/*
// @grant none
// ==/UserScript==
/* Configuration */
// We use our OGS 'overall' rank for comparison. See the definition of 'get_my_rank' below to change this.
// A game with handicap is always considered ok; with no handicap, the below restrictions
// are enforced relative to myrank:
var max_ranks_above = 2;
var max_ranks_below = 3;
// I wrote a whole general scheme for parsing time controls, but right now everything but
// byoyomi is ranked "bad", and byoyomi is "good" if it meets all the following criteria:
var min_byoyomi_seconds = 30;
var min_byoyomi_periods = 3;
// Our "seconds per move" formula divides the main time PLUS all the byoyomi periods (assuming
// each is used only once), over the expected average number of moves per player for the
// whole game (using a bullshit formula I made up from observation). If we meet the "good"
// number, it's green; "ok" is yellow.
// The default numbers are very conservative relative to how most people seem to time their
// OGS games, because I'm a slow player.
var min_seconds_per_move_good = 30;
var min_seconds_per_move_ok = 20;
/* End of Configuration */
///////////////////////////////////////////////////////////////////////////////////////////
function parse_rank(r) {
// 30k => 0
// Nk => 30 - N
// 1k => 29
// 1d => 30
// Nd => 29 + N
// 9d => 38
var m = r.match(/^(\d+)([dk])$/);
if (m[2] == "k") {
return 30 - (+m[1]);
} else {
return 29 + (+m[1]);
}
}
// Defer this until later, when everything has loaded.
var myrank;
function get_my_rank() {
myrank = window.profile.ranking;
}
function parse_time(t) {
if (t === "") {
return 0;
}
if (t.match(/ /)) {
var total = 0;
var parts = t.split(" ");
for (var i = 0; i < parts.length; i++) {
total += parse_time(parts[i]);
}
return total;
}
var modifiers = {
"s": 1,
"m": 60,
"h": 60 * 60,
"d": 60 * 60 * 24,
"wk": 60 * 60 * 24 * 7
};
var m = t.match(/^(\d+)([a-z]+)$/);
if (m) {
return m[1] * modifiers[m[2]];
} else {
return undefined;
}
}
function parse_time_control(t) {
// remove our own added notes
t = t.replace(/ \(.*\)$/, '');
if (t == "None") {
//console.log("None");
return {
type: "none"
};
} else if (t.match(/^(.*)\+ (.*) up to (.*)$/)) {
var fischer = t.match(/^(.*)\+ (.*) up to (.*)$/);
//console.log("Fischer(" + fischer[1] + ", " + fischer[2] + ", " + fischer[3] + ")");
return {
type: "fischer",
main: parse_time(fischer[1]),
increment: parse_time(fischer[2]),
max: parse_time(fischer[3])
};
} else if (t.match(/^(.*)\+ (.*)\/(.*)$/)) {
var canadian = t.match(/^(.*)\+ (.*)\/(.*)$/);
//console.log("Canadian(" + canadian[1] + ", " + canadian[2] + ", " + canadian[3] + ")");
return {
type: "canadian",
main: parse_time(canadian[1]),
increment: parse_time(canadian[2]),
moves_per_increment: +canadian[3]
};
} else if (t.match(/^(.*)\+(.*)x(.*)$/)) {
var byoyomi = t.match(/^(.*)\+(.*)x(.*)$/);
//console.log("Byo-yomi(" + byoyomi[1] + ", " + byoyomi[2] + ", " + byoyomi[3] + ")");
return {
type: "byoyomi",
main: parse_time(byoyomi[1]),
periods: +byoyomi[2],
increment: parse_time(byoyomi[3])
};
} else if (t.match(/^(.*)\/move$/)) {
var simple = t.match(/^(.*)\/move$/);
//console.log("Simple(" + simple[1] + ")");
return {
type: "simple",
increment: parse_time(simple[1])
};
} else {
//console.log("Absolute(" + t + ")");
return {
type: "absolute",
main: parse_time(t)
};
}
}
function typical_moves(board_size) {
return Math.pow(board_size, 1.75) * 0.925; // empirical curve-fitting bullshit
}
function main_time_per_move(tc, size) {
// The name lies a little:
// - In byoyomi, we count main time + all periods (since you can at LEAST treat them as main time.)
// - In fischer, we SHOULD count main time + avg moves * increment.
var avg_moves = typical_moves(size) / 2; // per-player
if (tc.type == "byoyomi") {
return (tc.main + tc.periods * tc.increment) / avg_moves;
}
return 0/0;
}
function good_time_control(size, tc) {
var avg_moves = typical_moves(size) / 2; // per-player
var mtm = main_time_per_move(tc, size);
console.log("main time per average move: ", mtm);
var moves_per_inc = tc.moves_per_increment || 1;
//console.log("increment per average move: ", tc.increment / moves_per_inc);
if (
tc.type == "byoyomi" &&
tc.increment >= min_byoyomi_seconds &&
tc.periods >= min_byoyomi_periods) {
if (mtm >= min_seconds_per_move_good) {
return 2;
} else if (mtm >= min_seconds_per_move_ok) {
return 1;
}
}
return 0;
}
// XXX copypasta from above
function time_add_info(size, tc) {
var avg_moves = typical_moves(size) / 2; // per-player
var mtm = main_time_per_move(tc, size);
if (tc.type == "byoyomi") {
// main time per average move
return "m/m: " + mtm.toFixed(2);
} else {
return "X";
}
}
function check_challenges() {
if (!myrank) {
get_my_rank();
}
var challenges = $('.challenge-row.ng-scope');
console.log("challenges", challenges);
for (var i = 0; i < challenges.length; i++) {
var is_live = challenges[i].attributes["ng-repeat"].textContent.match(/live/);
if (!is_live) {
continue;
}
var accept_s = (challenges[i].children[0].textContent || challenges[i].children[0].toString()).replace(/(^\s*)|(\s*$)/g,'');
var user_s = challenges[i].children[1].children[0].children[0].textContent.replace(/(^\s*)|(\s*$)/g,'');
var board_s = challenges[i].children[2].textContent.replace(/(^\s*)|(\s*$)/g,'');
var time_s = challenges[i].children[3].textContent.replace(/(^\s*)|(\s*$)/g,'');
var ranked_s = challenges[i].children[4].textContent.replace(/(^\s*)|(\s*$)/g,'');
var handicap_s = challenges[i].children[5].textContent.replace(/(^\s*)|(\s*$)/g,'');
var accept = false;
if (accept_s == "Accept") {
accept = true;
}
var rank = parse_rank(user_s.match(/\((\d+[dk])\)$/)[1]);
var size = +(board_s.match(/(\d+)x/)[1]);
var tc = parse_time_control(time_s);
var gtc = good_time_control(size, tc);
var ranked = (ranked_s == "Yes");
var handicap_ok = (
handicap_s == "Auto" ||
(handicap_s == "No" && rank - myrank <= max_ranks_above && myrank - rank <= max_ranks_below));
var handicap_good = (
handicap_s == "Auto" ||
(handicap_s == "No" && rank == myrank));
// If we didn't already add a note to the time control field, add it now
if (!challenges[i].children[3].textContent.match(/\(/)) {
challenges[i].children[3].textContent = time_s + " (" + time_add_info(size, tc) + ")";
}
if (gtc == 2) {
challenges[i].children[3].style.backgroundColor = "green";
} else if (gtc == 1) {
challenges[i].children[3].style.backgroundColor = "yellow";
} else {
challenges[i].children[3].style.backgroundColor = "red";
}
if (accept) {
console.log("challenge: ", challenges[i], accept, user_s, rank, size, time_s, tc, ranked, handicap_s);
challenges[i].children[0].style.backgroundColor = "green";
} else {
challenges[i].children[0].style.backgroundColor = "red";
}
if (ranked) {
challenges[i].children[4].style.backgroundColor = "green";
} else {
challenges[i].children[4].style.backgroundColor = "red";
}
if (handicap_good) {
challenges[i].children[5].style.backgroundColor = "green";
} else if (handicap_ok) {
challenges[i].children[5].style.backgroundColor = "yellow";
} else {
challenges[i].children[5].style.backgroundColor = "red";
}
}
setTimeout(check_challenges, 2500);
}
(function() {
'use strict';
setTimeout(check_challenges, 2500);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment