Skip to content

Instantly share code, notes, and snippets.

@maxint137
Last active April 8, 2025 14:21
Show Gist options
  • Save maxint137/c380678630730494376459975633e7bf to your computer and use it in GitHub Desktop.
Save maxint137/c380678630730494376459975633e7bf to your computer and use it in GitHub Desktop.
assist with chess studying
// ==UserScript==
// @name Flip the board
// @namespace http://tampermonkey.net/
// @version 2024-04-06
// @description try to take over the world!
// @author You
// @match https://www.chess.com/game/live/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=chess.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Find button by role and accessible name
function getByRole(role, options = {}) {
// Get all elements with the specified role
const elements = Array.from(document.querySelectorAll(`[role="${role}"], ${role}`));
// Filter by accessible name if specified
if (options.name) {
return elements.find(element => {
return element.textContent.includes(options.name) ||
element.getAttribute('aria-label') === options.name;
});
}
return elements[0];
}
setTimeout(() => {
const [ , paramString ] = window.location.href.split( '?' );
// Use the function to find and click the button
const flipButton = getByRole('button', { name: 'Flip Board' });
if (new URLSearchParams( paramString ).get('flip') && flipButton) {
flipButton.click();
}
// const ply = Number(new URLSearchParams( paramString ).get('ply'));
// document.querySelectorAll(`[data-whole-move-number='${Math.floor(ply/2)+1}']`)[0].children[(ply%2)*2].dispatchEvent(new PointerEvent('pointerdown'));
document.getElementsByClassName('board-modal-header-close game-over-header-close')[0].click();
}, "3000");
})();
import re
import sys
from scoutfish import Scoutfish
def find_link(text):
pattern = r'\[Link "(.*?)"\]'
matches = re.findall(pattern, text)
return matches[0]
def extract_fen_components(fen):
"""
Extract active color, halfmove clock, and fullmove number from a FEN string.
Args:
fen (str): The FEN string representing a chess position
Returns:
tuple: (active_color, halfmove_clock, fullmove_number)
"""
# Split the FEN string into its components
components = fen.split()
# Check if the FEN has all required components
if len(components) < 6:
raise ValueError("Invalid FEN string: missing components")
# Extract the values
active_color = components[1] # 'w' for White, 'b' for Black
halfmove_clock = int(
components[4]
) # Number of halfmoves since last capture or pawn advance
fullmove_number = int(components[5]) # The number of the full move
return active_color, halfmove_clock, fullmove_number
def extract_players(pgn_string):
pattern = r'\[White "(.*?)"\]\s*\[Black "(.*?)"\]'
match = re.search(pattern, pgn_string)
return match.groups() if match else (None, None)
def extract_move(pgn_string, move_number):
"""
Extracts the specified move number (White and Black) from a PGN string.
Args:
pgn_string: The PGN string of the chess game.
move_number: The move number to extract (e.g., 5 for move #5).
Returns:
A tuple containing White's move and Black's move as strings, or
(None, None) if the move number is not found.
"""
# Regular expression to find the move number and the subsequent moves
# This regex looks for the move number followed by a dot (.), then captures
# White's move and Black's move (if it exists). It accounts for variations
# in PGN formatting (e.g., annotations, comments).
# Clean up the PGN text by removing clock annotations
pattern = rf"{move_number}\.\s+(\S+)\s+(?:{move_number}\.{{3}}\s+)?(\S+)"
cleaned_pgn = re.sub(r"\{[^}]*\}", "", pgn_string)
match = re.search(pattern, cleaned_pgn)
if match:
white_move = match.group(1)
black_move = (
match.group(2) if match.group(2) else None
) # Black's move might be missing at the end
return white_move, black_move
else:
return None, None # Move number not found
p = Scoutfish()
p.setoption("threads", 4) # Will use 4 threads for searching
p.open(sys.argv[1])
fen0 = sys.argv[2] if 3 <= len(sys.argv) else None
while True:
fen = fen0 or input("FEN:")
fen0 = None
if not fen:
break
q = {"sub-fen": fen}
result = p.scout(q)
num = result["match count"]
games = p.get_games(result["matches"]) # Load the pgn games from my_big.pgn
ac, hm, fm = extract_fen_components(fen)
data = []
for g in games:
white, black = extract_players(g["pgn"])
wm0, black_move = extract_move(g["pgn"], fm)
wm1, _bm = extract_move(g["pgn"], fm + 1)
white_move = wm0 if "w" == ac else wm1
data.append(
{
"ac": ac,
"white_move": white_move,
"black_move": black_move,
"url": f"{find_link(g['pgn'])}?move={g['ply'][0]}{'&flip=da' if black=='redwinereduction' else ''}",
}
)
# print(
# f'[{white_move if "w" == ac else "_":^3}, {black_move if "b" == ac else "_":^3}]',
# f"{find_link(g['pgn'])}?move={g['ply'][0]}{'&flip=da' if black=='redwinereduction' else ''}",
# )
# This method doesn't require sorting first
grouped = {}
for obj in data:
key = obj["white_move"] if "w" == ac else obj["black_move"]
if key not in grouped:
grouped[key] = []
grouped[key].append(obj)
sorted_items = sorted(grouped.items(), key=lambda values: -len(values[1]))
for key, values in sorted_items:
values.sort(key=lambda x: x["white_move"] if "b" == ac else x["black_move"])
print(f"[{key:^3},.." if "w" == ac else f".., {key:^3}]")
for obj in values:
print(
f" {obj["black_move"]:^3}] {obj['url']}"
if "w" == ac
else f"[{obj["white_move"]:^3},.. {obj['url']}"
)
print("Found " + str(num) + " games")
p.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment