您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculate tournament top cuts on Limitless TCG
- // ==UserScript==
- // @name Top Cut Calculator - Limitless TCG
- // @name:pt-BR Calculadora de Top Cut - Limitless TCG
- // @namespace http://tampermonkey.net/
- // @version 1.0.1
- // @description Calculate tournament top cuts on Limitless TCG
- // @description:pt-BR Calculadora de top cut para torneios no Limitless TCG
- // @author Marcsx (https://github.com/Marcsx)
- // @match https://play.limitlesstcg.com/tournament/*
- // @include https://play.limitlesstcg.com/tournament/*
- // @include https://play.limitlesstcg.com/tournaments
- // @exclude https://play.limitlesstcg.com/tournament/*/match/*
- // @exclude https://play.limitlesstcg.com/tournament/*/round/*
- // @grant none
- // @source https://github.com/Marcsx/limitless-topcut-calculator
- // @supportURL https://github.com/Marcsx/limitless-topcut-calculator/issues
- // @license MIT
- // ==/UserScript==
- /*
- This is a Tampermonkey adaptation of the Chrome extension created by Marcsx
- Original repository: https://github.com/Marcsx/limitless-topcut-calculator
- */
- (function() {
- 'use strict';
- const translations = {
- en: {
- title: "Top Cut Calculator",
- players: "Players",
- rounds: "Rounds",
- topCut: "Top Cut",
- records: "Records",
- calculate: "Calculate",
- noTopCut: "No Top Cut"
- },
- pt: {
- title: "Calculadora de Top Cut",
- players: "Jogadores",
- rounds: "Rodadas",
- topCut: "Top Cut",
- records: "Recordes",
- calculate: "Calcular",
- noTopCut: "Sem Top Cut"
- }
- };
- class I18n {
- constructor() {
- this.currentLocale = navigator.language.startsWith('pt') ? 'pt' : 'en';
- }
- t(key) {
- return translations[this.currentLocale][key] || translations['en'][key];
- }
- }
- class TopCutCalculator {
- constructor() {
- this.i18n = new I18n();
- this.defaultTopCutRules = [
- { maxPlayers: 8, rounds: 3, topCut: 0 },
- { maxPlayers: 16, rounds: 4, topCut: 4 },
- { maxPlayers: 32, rounds: 6, topCut: 8 },
- { maxPlayers: 64, rounds: 7, topCut: 8 },
- { maxPlayers: 128, rounds: 6, topCut: 16 },
- { maxPlayers: 256, rounds: 7, topCut: 16 },
- { maxPlayers: 512, rounds: 8, topCut: 16 },
- { maxPlayers: 1024, rounds: 9, topCut: 32 },
- { maxPlayers: 2048, rounds: 10, topCut: 32 },
- { maxPlayers: Infinity, rounds: 10, topCut: 64 }
- ];
- this.createStyles();
- this.createElements();
- this.attachEventListeners();
- }
- createStyles() {
- const style = document.createElement('style');
- style.textContent = `
- :root {
- --primary-color: #121212;
- --text-color: #ffffff;
- --surface-color: #1e1e1e;
- --accent-color: #bb86fc;
- }
- #topcut-fab {
- position: fixed;
- bottom: 20px;
- right: 20px;
- width: 56px;
- height: 56px;
- border-radius: 50%;
- background-color: var(--accent-color);
- box-shadow: 0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12);
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- border: none;
- }
- #topcut-fab:hover {
- background-color: #9965db;
- }
- #topcut-modal {
- position: fixed;
- bottom: 90px;
- right: 20px;
- width: 320px;
- max-width: 90vw;
- background-color: var(--surface-color);
- border-radius: 8px;
- box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);
- padding: 16px;
- color: var(--text-color);
- z-index: 999;
- display: none;
- }
- .modal-header {
- font-size: 18px;
- font-weight: 500;
- margin-bottom: 16px;
- }
- .input-group {
- margin-bottom: 16px;
- }
- .input-group label {
- display: block;
- margin-bottom: 8px;
- color: rgba(255, 255, 255, 0.87);
- }
- .input-group input, .input-group select {
- width: 100%;
- padding: 8px;
- border-radius: 4px;
- border: 1px solid rgba(255,255,255,0.12);
- background-color: var(--primary-color);
- color: var(--text-color);
- }
- .button {
- background-color: var(--accent-color);
- color: var(--primary-color);
- padding: 8px 16px;
- border-radius: 4px;
- border: none;
- cursor: pointer;
- width: 100%;
- }
- .button:hover {
- background-color: #9965db;
- }
- .results {
- margin-top: 16px;
- padding-top: 16px;
- border-top: 1px solid rgba(255,255,255,0.12);
- }
- `;
- document.head.appendChild(style);
- }
- createElements() {
- const fab = document.createElement('button');
- fab.id = 'topcut-fab';
- fab.innerHTML = '📊';
- document.body.appendChild(fab);
- const modal = document.createElement('div');
- modal.id = 'topcut-modal';
- modal.innerHTML = `
- <div class="modal-header">
- ${this.i18n.t('title')}
- </div>
- <div class="input-group">
- <label>${this.i18n.t('players')}</label>
- <input type="number" id="players-input" placeholder="Auto-detect">
- </div>
- <div class="input-group">
- <label>${this.i18n.t('rounds')}</label>
- <input type="number" id="rounds-input" placeholder="Auto-detect">
- </div>
- <div class="input-group">
- <label>${this.i18n.t('topCut')}</label>
- <select id="topcut-input">
- <option value="auto">Auto</option>
- <option value="0">${this.i18n.t('noTopCut')}</option>
- <option value="4">Top 4</option>
- <option value="8">Top 8</option>
- <option value="16">Top 16</option>
- <option value="32">Top 32</option>
- <option value="64">Top 64</option>
- <option value="128">Top 128</option>
- </select>
- </div>
- <button class="button" id="calculate-button">${this.i18n.t('calculate')}</button>
- <div class="results" id="results"></div>
- `;
- document.body.appendChild(modal);
- }
- attachEventListeners() {
- const fab = document.getElementById('topcut-fab');
- const modal = document.getElementById('topcut-modal');
- const calculateButton = document.getElementById('calculate-button');
- const playersInput = document.getElementById('players-input');
- fab.addEventListener('click', () => {
- const isVisible = modal.style.display === 'block';
- modal.style.display = isVisible ? 'none' : 'block';
- if (!isVisible) {
- this.autoDetectValues();
- }
- });
- calculateButton.addEventListener('click', () => {
- this.calculateTopCut();
- });
- playersInput.addEventListener('change', () => {
- const topCutSelect = document.getElementById('topcut-input');
- if (topCutSelect.value === 'auto') {
- const players = parseInt(playersInput.value);
- const suggestedTopCut = this.determineTopCutSize(players);
- this.updateTopCutSuggestion(suggestedTopCut);
- }
- });
- }
- async autoDetectValues() {
- const url = window.location.href;
- const tournamentId = url.match(/tournament\/(.*?)(\/|$)/)?.[1];
- if (!tournamentId) return;
- try {
- const standingsResponse = await fetch(`https://play.limitlesstcg.com/tournament/${tournamentId}/standings`);
- const standingsText = await standingsResponse.text();
- const playersCount = this.extractPlayersCount(standingsText);
- if (playersCount) {
- document.getElementById('players-input').value = playersCount;
- const rule = this.defaultTopCutRules.find(r => playersCount <= r.maxPlayers);
- if (rule) {
- document.getElementById('rounds-input').value = rule.rounds;
- document.getElementById('topcut-input').value = rule.topCut;
- this.calculateTopCut();
- }
- }
- } catch (error) {
- console.error('Error auto-detecting values:', error);
- }
- }
- extractPlayersCount(html) {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
- const rows = doc.querySelectorAll('tbody tr');
- return rows.length > 0 ? rows.length - 1 : 0;
- }
- determineTopCutSize(players) {
- const rule = this.defaultTopCutRules.find(r => players <= r.maxPlayers);
- return rule ? rule.topCut : 64;
- }
- calculateTopCut() {
- const playersCount = parseInt(document.getElementById('players-input').value);
- const roundsCount = parseInt(document.getElementById('rounds-input').value);
- const topCutSelect = document.getElementById('topcut-input');
- if (!playersCount || !roundsCount) {
- document.getElementById('results').innerHTML =
- '<span style="color: rgba(255, 255, 255, 0.87)">Please enter all values.</span>';
- return;
- }
- let topCutSize = topCutSelect.value === 'auto'
- ? this.determineTopCutSize(playersCount)
- : parseInt(topCutSelect.value);
- const results = this.calculatePossibleRecords(roundsCount, topCutSize);
- const topCutPercentage = topCutSize > 0
- ? Math.round((topCutSize/playersCount) * 100)
- : 0;
- document.getElementById('results').innerHTML = `
- <div style="color: rgba(255, 255, 255, 0.87)">
- <div style="margin-bottom: 2px">${this.i18n.t('topCut')}: ${topCutSize === 0 ? this.i18n.t('noTopCut') : `Top ${topCutSize} (${topCutPercentage}%)`}</div>
- <div style="white-space: nowrap">${this.i18n.t('records')}: ${results}</div>
- </div>
- `;
- }
- calculatePossibleRecords(rounds, topCutSize) {
- const possibleRecords = [];
- const totalPlayers = parseInt(document.getElementById('players-input').value);
- let remainingSpots = topCutSize;
- for (let wins = rounds; wins >= 0; wins--) {
- const losses = rounds - wins;
- const playersWithThisRecord = Math.round(
- totalPlayers *
- this.binomialCoefficient(rounds, wins) *
- Math.pow(0.5, rounds)
- );
- if (remainingSpots > 0) {
- const playersAdvancing = Math.min(remainingSpots, playersWithThisRecord);
- const percentageAdvancing = Math.round((playersAdvancing / playersWithThisRecord) * 100);
- if (percentageAdvancing > 0) {
- possibleRecords.push({
- record: `${wins}-${losses}`,
- percentage: percentageAdvancing
- });
- remainingSpots -= playersAdvancing;
- }
- }
- }
- return possibleRecords
- .map(r => `${r.record} (${r.percentage}%)`)
- .join(' | ');
- }
- binomialCoefficient(n, k) {
- let result = 1;
- for (let i = 1; i <= k; i++) {
- result *= (n + 1 - i);
- result /= i;
- }
- return result;
- }
- updateTopCutSuggestion(topCut) {
- const results = document.getElementById('results');
- results.innerHTML = topCut === 0
- ? this.i18n.t('noTopCut')
- : `Top ${topCut}`;
- }
- }
- // Initialize the calculator
- new TopCutCalculator();
- })();