- // ==UserScript==
- // @name Chess.com Real-Time AI Analysis with Player Color Indicator
- // @namespace http://tampermonkey.net/
- // @version 2.9
- // @description 실시간으로 FEN을 업로드하고 AI 분석을 받아오며, 마지막 수와 총 수(전체 수), 그리고 탭 플레이어의 색상을 표시하고 업데이트 시 무지개 색 표시기를 변경합니다.
- // @author You
- // @match *://www.chess.com/play/computer*
- // @grant GM_xmlhttpRequest
- // @connect lichess.org
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- const rainbowColors = ['#FF0000','#FF7F00','#FFFF00','#00FF00','#0000FF','#4B0082','#8F00FF'];
- let colorIndex = 0;
- let lastFen = '';
- let lastFullMoveCount = 0;
- let lastOutput = '';
-
- // UI 요소 생성
- const infoBox = document.createElement('div');
- Object.assign(infoBox.style, {
- position: 'fixed', top: '10px', right: '10px',
- padding: '8px 12px', background: 'rgba(0,0,0,0.7)', color: '#fff',
- fontFamily: 'monospace', fontSize: '14px', zIndex: 9999,
- borderRadius: '4px', whiteSpace: 'pre'
- });
- const textNode = document.createTextNode('분석 대기 중...');
- const indicator = document.createElement('div');
- Object.assign(indicator.style, {
- width: '12px', height: '12px', marginTop: '6px', borderRadius: '50%',
- backgroundColor: rainbowColors[0]
- });
- infoBox.append(textNode, document.createElement('br'), indicator);
- document.body.append(infoBox);
-
- // 보드 말 배치에서 FEN 위치 필드만 추출
- function extractPosition() {
- const board = Array.from({ length: 8 }, () => Array(8).fill(''));
- document.querySelectorAll('.piece').forEach(el => {
- const cls = el.className;
- const sqMatch = cls.match(/square-(\d\d)/);
- if (!sqMatch) return;
- const sq = sqMatch[1];
- const file = parseInt(sq.charAt(0), 10) - 1;
- const rank = parseInt(sq.charAt(1), 10) - 1;
- if (isNaN(file) || isNaN(rank)) return;
- const pCharMatch = cls.match(/piece [wb]([prnbqk])/);
- if (!pCharMatch) return;
- const map = { p:'p', r:'r', n:'n', b:'b', q:'q', k:'k' };
- let p = map[pCharMatch[1]];
- if (cls.includes(' wp ')) p = p.toUpperCase();
- board[7 - rank][file] = p;
- });
- return board.map(row => {
- let empty = 0, str = '';
- row.forEach(cell => {
- if (!cell) empty++; else { if (empty) { str += empty; empty = 0; } str += cell; }
- });
- return str + (empty ? empty : '');
- }).join('/');
- }
-
- // 수 정보(전체 수, half moves, 마지막 수) 가져오기
- function getMovesInfo() {
- const rows = document.querySelectorAll('.main-line-row.move-list-row');
- let fullCount = 0;
- if (rows.length) {
- fullCount = parseInt(rows[rows.length - 1].getAttribute('data-whole-move-number'), 10) || 0;
- }
- const ply = document.querySelectorAll('.node.main-line-ply');
- const halfCount = ply.length;
- const lastMove = halfCount ? ply[halfCount - 1].textContent.trim() : '';
- return { fullCount, halfCount, lastMove };
- }
-
- // 전체 FEN 생성
- function makeFEN(pos, turn) {
- return `${pos} ${turn} KQkq - 0 1`;
- }
-
- // indicator 색상 순환
- function rotateIndicator() {
- colorIndex = (colorIndex + 1) % rainbowColors.length;
- indicator.style.backgroundColor = rainbowColors[colorIndex];
- }
-
- // AI 분석 요청 및 화면 업데이트
- function updateAnalysis(fen, myColor, lastMove, fullCount) {
- GM_xmlhttpRequest({
- method: 'GET',
- url: 'https://lichess.org/api/cloud-eval?fen=' + encodeURIComponent(fen) + '&multiPv=1',
- headers: { 'Accept': 'application/json' },
- onload: res => {
- try {
- const data = JSON.parse(res.responseText);
- let out = `총 수: ${fullCount}\n내 색상: ${myColor}\n마지막 수: ${lastMove || '-'}\n`;
- if (data.pvs && data.pvs.length > 0) {
- const pv = data.pvs[0];
- const cp = pv.cp, mate = pv.mate;
- const move = pv.moves.split(' ')[0];
- const human = move.slice(0,2) + '→' + move.slice(2,4);
- const ev = mate != null ? `M${mate}` : (cp != null ? (cp/100).toFixed(2) : '?');
- out += `평가: ${ev}\n추천: ${human}`;
- } else {
- out += `분석 정보 없음`;
- }
- if (out !== lastOutput) {
- lastOutput = out;
- textNode.nodeValue = out;
- rotateIndicator();
- }
- } catch (e) {
- console.error('AI 분석 오류:', e);
- }
- },
- onerror: err => console.error('AI 분석 요청 실패:', err)
- });
- }
-
- // 메인 루프: 0.1초마다 실행
- setInterval(() => {
- rotateIndicator();
- const pos = extractPosition();
- const { fullCount, halfCount, lastMove } = getMovesInfo();
-
- // 탭 플레이어 색상 감지 (보드 뒤집힘 상태로 판단)
- const boardEl = document.getElementById('board-play-computer');
- const myColor = boardEl.classList.contains('flipped') ? '흑' : '백';
-
- const turn = (halfCount % 2 === 0) ? 'w' : 'b';
- const fen = makeFEN(pos, turn);
- if (fen !== lastFen || fullCount !== lastFullMoveCount) {
- lastFen = fen;
- lastFullMoveCount = fullCount;
- updateAnalysis(fen, myColor, lastMove, fullCount);
- }
- }, 100);
- })();