您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Helper for Gartic.io with auto-guess, drawing assistance, and drawing bot
当前为
// ==UserScript== // @name Kawaii Helper & Drawing Bot for Gartic.io // @namespace http://tampermonkey.net/ // @version 2025-03-12 // @description Helper for Gartic.io with auto-guess, drawing assistance, and drawing bot // @author anonimbiri & Gartic-Developers // @license MIT // @match https://gartic.io/* // @icon https://www.google.com/s2/favicons?sz=64&domain=gartic.io // @run-at document-start // @grant none // ==/UserScript== // I used the word list from 'https://github.com/Gartic-Developers/Gartic-WordList/'. // Thanks to Gartic Developers for providing this resource. Also, thanks to Qwyua! (function() { 'use strict'; // Script interception (unchanged) Node.prototype.appendChild = new Proxy(Node.prototype.appendChild, { apply: function(target, thisArg, argumentsList) { const node = argumentsList[0]; if (node.nodeName.toLowerCase() === 'script' && node.src && node.src.includes('room')) { console.log('Target script detected:', node.src); fetch(node.src) .then(response => response.text()) .then(scriptContent => { let modifiedContent = scriptContent .replace( 'r.created||c?Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:i,placeholder:this._lang.chatHere,maxLength:100,enterKeyHint:"send",onChange:this.handleText,ref:this._ref}):Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:this._lang.loginChat,maxLength:100,ref:this._ref,disabled:!0})', 'Rt("input",{type:"text",name:"chat",className:"mousetrap",autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",value:i,placeholder:this._lang.chatHere,maxLength:100,enterKeyHint:"send",onChange:this.handleText,ref:this._ref})' ) .replace( 'this._timerAtivo=setInterval((function(){Date.now()-e._ativo>15e4&&(O(Object(f.a)(n.prototype),"emit",e).call(e,"avisoInativo"),e._ativo=Date.now())}),1e3)', 'this._timerAtivo=setInterval((function(){Date.now()-e._ativo>15e4&&e.active()}),1e3)' ) .replace( 'e.unlock()}', 'e.unlock();window.game=e;setInterval(()=>{window.game=e},1000);e.on("votekick",(t,i,o)=>{if(i.id===e.me.id){e.votekick(t.id,true);}});}' ); let blob = new Blob([modifiedContent], { type: 'application/javascript' }); let blobUrl = URL.createObjectURL(blob); node.src = blobUrl; node.textContent = ''; return target.apply(thisArg, [node]); }) .catch(error => console.error('Failed to fetch/modify script:', error)); return node; } return target.apply(thisArg, argumentsList); } }); // Load fonts const fontLink = document.createElement('link'); fontLink.rel = 'stylesheet'; fontLink.href = 'https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@400;700&display=swap'; document.head.appendChild(fontLink); // Inject HTML const kawaiiHTML = ` <div class="kawaii-cheat" id="kawaiiCheat"> <div class="kawaii-header" id="kawaiiHeader"> <img src="https://i.imgur.com/ptRhAHj.png" alt="Anime Girl" class="header-icon"> <h2>✧ Kawaii Helper ✧</h2> <button class="minimize-btn" id="minimizeBtn">▼</button> </div> <div class="kawaii-body" id="kawaiiBody"> <div class="kawaii-tabs"> <button class="kawaii-tab active" data-tab="guessing">Guessing</button> <button class="kawaii-tab" data-tab="drawing">Drawing</button> </div> <div class="kawaii-content" id="guessing-tab"> <div class="checkbox-container"> <input type="checkbox" id="autoGuess"> <label for="autoGuess">Auto Guess</label> </div> <div class="slider-container" id="speedContainer" style="display: none;"> <div class="slider-label">Speed</div> <div class="custom-slider"> <input type="range" id="guessSpeed" min="100" max="5000" value="1000" step="100"> <div class="slider-track"></div> <span id="speedValue">1s</span> </div> </div> <div class="checkbox-container"> <input type="checkbox" id="customWords"> <label for="customWords">Custom Words</label> </div> <div class="dropzone-container" id="wordListContainer" style="display: none;"> <div class="dropzone" id="wordListDropzone"> <input type="file" id="wordList" accept=".txt"> <div class="dropzone-content"> <div class="dropzone-icon">❀</div> <p>Drop word list here or click to upload</p> </div> </div> </div> <div class="input-container"> <input type="text" id="guessPattern" placeholder="Enter pattern (e.g., ___e___)"> </div> <div class="hit-list" id="hitList"> <div class="message">Type a pattern to see matches ✧</div> </div> </div> <div class="kawaii-content" id="drawing-tab" style="display: none;"> <div class="dropzone-container"> <div class="dropzone" id="imageDropzone"> <input type="file" id="imageUpload" accept="image/*"> <div class="dropzone-content"> <div class="dropzone-icon">✎</div> <p>Drop image here or click to upload</p> </div> </div> <div class="image-preview" id="imagePreview" style="display: none;"> <img id="previewImg"> <div class="preview-controls"> <button class="cancel-btn" id="cancelImage">✕</button> </div> </div> </div> <div class="slider-container"> <div class="slider-label">Draw Speed</div> <div class="custom-slider"> <input type="range" id="drawSpeed" min="20" max="5000" value="20" step="100"> <div class="slider-track"></div> <span id="drawSpeedValue">20ms</span> </div> </div> <div class="slider-container"> <div class="slider-label">Max Colors</div> <div class="custom-slider"> <input type="range" id="maxColors" min="3" max="100" value="20" step="1"> <div class="slider-track"></div> <span id="maxColorsValue">20</span> </div> </div> <button class="draw-btn" id="sendDraw" disabled>Draw Now ✧</button> </div> <div class="kawaii-footer"> <span class="credit-text">Made with ♥ by Anonimbiri & Gartic-Developers</span> </div> </div> </div> `; const waitForBody = setInterval(() => { if (document.body) { clearInterval(waitForBody); document.body.insertAdjacentHTML('beforeend', kawaiiHTML); addStylesAndBehavior(); } }, 100); function addStylesAndBehavior() { const style = document.createElement('style'); style.textContent = ` :root { --primary-color: #FF69B4; --primary-dark: #FF1493; --primary-light: #FFC0CB; --bg-color: #FFB6C1; --text-color: #5d004f; --panel-bg: rgba(255, 182, 193, 0.95); --panel-border: #FF69B4; --element-bg: rgba(255, 240, 245, 0.7); --element-hover: rgba(255, 240, 245, 0.9); --element-active: #FF69B4; --element-active-text: #FFF0F5; } .kawaii-cheat { position: fixed; top: 20px; right: 20px; width: 280px; background: var(--panel-bg); border-radius: 15px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); padding: 10px; display: flex; flex-direction: column; gap: 10px; color: var(--text-color); user-select: none; z-index: 1000; font-family: 'M PLUS Rounded 1c', sans-serif; border: 2px solid var(--panel-border); transition: all 0.4s ease-in-out; max-height: calc(100vh - 40px); overflow: hidden; } .kawaii-cheat.minimized { height: 50px; opacity: 0.9; transform: scale(0.95); overflow: hidden; } .kawaii-cheat:not(.minimized) { opacity: 1; transform: scale(1); } .kawaii-cheat.minimized .kawaii-body { opacity: 0; max-height: 0; overflow: hidden; transition: opacity 0.2s ease-in-out, max-height 0.4s ease-in-out; } .kawaii-cheat:not(.minimized) .kawaii-body { opacity: 1; max-height: 500px; transition: opacity 0.2s ease-in-out 0.2s, max-height 0.4s ease-in-out; } .kawaii-cheat.dragging { opacity: 0.8; transition: none; /* Sürükleme sırasında animasyonu devre dışı bırak */ } .kawaii-header { display: flex; justify-content: space-between; align-items: center; padding: 5px 10px; cursor: move; background: var(--element-bg); border-radius: 10px; border: 2px solid var(--primary-color); } .header-icon { width: 30px; height: 30px; border-radius: 50%; margin-right: 10px; border: 1px dashed var(--primary-color); } .kawaii-header h2 { margin: 0; font-size: 18px; font-weight: 700; color: var(--primary-dark); text-shadow: 1px 1px 2px var(--primary-light); } .minimize-btn { background: transparent; border: 2px solid var(--primary-dark); border-radius: 6px; width: 24px; height: 24px; color: var(--primary-dark); font-size: 16px; line-height: 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; } .minimize-btn:hover { background: var(--primary-color); color: var(--element-active-text); border-color: var(--primary-color); transform: rotate(180deg); } .kawaii-tabs { display: flex; gap: 8px; padding: 5px 0; } .kawaii-tab { flex: 1; background: var(--element-bg); border: 1px dashed var(--primary-color); padding: 6px; border-radius: 10px; font-size: 12px; font-weight: 700; color: var(--text-color); cursor: pointer; transition: background 0.3s ease, transform 0.3s ease; text-align: center; } .kawaii-tab.active { background: var(--primary-color); color: var(--element-active-text); border-color: var(--primary-dark); } .kawaii-tab:hover:not(.active) { background: var(--element-hover); transform: scale(1.05); } .kawaii-content { display: flex; flex-direction: column; gap: 10px; max-height: 55vh; overflow-y: auto; padding: 5px; } .checkbox-container { display: flex; align-items: center; gap: 8px; background: var(--element-bg); padding: 8px; border-radius: 10px; border: 1px dashed var(--primary-color); cursor: pointer; transition: background 0.3s ease; } .checkbox-container:hover { background: var(--element-hover); } .checkbox-container input[type="checkbox"] { appearance: none; width: 18px; height: 18px; background: var(--element-active-text); border: 1px dashed var(--primary-color); border-radius: 50%; cursor: pointer; position: relative; } .checkbox-container input[type="checkbox"]:checked { background: var(--primary-color); border-color: var(--primary-dark); } .checkbox-container input[type="checkbox"]:checked::after { content: "♥"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: var(--element-active-text); font-size: 12px; } .checkbox-container label { font-size: 12px; font-weight: 700; color: var(--text-color); cursor: pointer; } .input-container { background: var(--element-bg); padding: 8px; border-radius: 10px; border: 1px dashed var(--primary-color); } .input-container input[type="text"] { width: 100%; background: var(--element-active-text); border: 1px dashed var(--primary-light); border-radius: 8px; padding: 6px 10px; color: var(--text-color); font-size: 12px; font-weight: 500; box-sizing: border-box; transition: border-color 0.3s ease; outline: none; } .input-container input[type="text"]:focus { border-color: var(--primary-dark); } .dropzone-container { display: flex; flex-direction: column; gap: 10px; } .dropzone { position: relative; background: var(--element-bg); border: 1px dashed var(--primary-color); border-radius: 10px; padding: 15px; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; transition: background 0.3s ease, border-color 0.3s ease; min-height: 80px; } .dropzone:hover, .dropzone.drag-over { background: var(--element-hover); border-color: var(--primary-dark); } .dropzone input[type="file"] { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; } .dropzone-content { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; pointer-events: none; } .dropzone-icon { font-size: 24px; color: var(--primary-color); animation: pulse 1.5s infinite ease-in-out; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } .dropzone-content p { margin: 0; color: var(--text-color); font-size: 12px; font-weight: 500; } .slider-container { display: flex; flex-direction: column; gap: 6px; background: var(--element-bg); padding: 8px; border-radius: 10px; border: 1px dashed var(--primary-color); } .slider-label { font-size: 12px; color: var(--text-color); font-weight: 700; text-align: center; } .custom-slider { position: relative; height: 25px; padding: 0 8px; } .custom-slider input[type="range"] { -webkit-appearance: none; width: 100%; height: 6px; background: transparent; position: absolute; top: 50%; left: 0; transform: translateY(-50%); z-index: 2; } .custom-slider .slider-track { position: absolute; top: 50%; left: 0; width: 100%; height: 6px; background: linear-gradient(to right, var(--primary-dark) 0%, var(--primary-dark) var(--slider-progress), var(--primary-light) var(--slider-progress), var(--primary-light) 100%); border-radius: 3px; transform: translateY(-50%); z-index: 1; } .custom-slider input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: var(--primary-color); border-radius: 50%; border: 1px dashed var(--element-active-text); cursor: pointer; transition: transform 0.3s ease; } .custom-slider input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); } .custom-slider span { position: absolute; bottom: -15px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-color); background: var(--element-active-text); padding: 2px 6px; border-radius: 8px; border: 1px dashed var(--primary-color); white-space: nowrap; } .hit-list { max-height: 180px; overflow-y: scroll; background: var(--element-bg); border: 1px dashed var(--primary-color); border-radius: 10px; padding: 8px; display: flex; flex-direction: column; gap: 6px; scrollbar-width: thin; scrollbar-color: var(--primary-color) var(--element-bg); } .hit-list::-webkit-scrollbar { width: 6px; } .hit-list::-webkit-scrollbar-thumb { background-color: var(--primary-color); border-radius: 10px; } .hit-list::-webkit-scrollbar-track { background: var(--element-bg); } .hit-list button { background: rgba(255, 240, 245, 0.8); border: 1px dashed var(--primary-color); padding: 6px 10px; border-radius: 8px; color: var(--text-color); font-size: 12px; font-weight: 700; cursor: pointer; transition: background 0.3s ease, transform 0.3s ease; text-align: left; } .hit-list button:hover:not(.tried) { background: var(--primary-color); color: var(--element-active-text); transform: scale(1.03); } .hit-list button.tried { background: rgba(255, 182, 193, 0.6); border-color: var(--primary-light); color: var(--primary-dark); opacity: 0.7; cursor: not-allowed; } .hit-list .tried-label { font-size: 10px; color: var(--primary-dark); text-align: center; padding: 4px; background: var(--element-active-text); border-radius: 8px; border: 1px dashed var(--primary-color); } .hit-list .message { font-size: 12px; color: var(--text-color); text-align: center; padding: 8px; } .image-preview { position: relative; margin-top: 10px; background: var(--element-bg); padding: 8px; border-radius: 10px; border: 1px dashed var(--primary-color); } .image-preview img { max-width: 100%; max-height: 120px; border-radius: 8px; display: block; margin: 0 auto; } .preview-controls { position: absolute; top: 12px; right: 12px; display: flex; gap: 6px; } .cancel-btn { background: transparent; border: 2px solid var(--primary-dark); border-radius: 6px; width: 24px; height: 24px; color: var(--primary-dark); font-size: 16px; line-height: 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; } .cancel-btn:hover { background: var(--primary-dark); color: var(--element-active-text); transform: scale(1.1); } .draw-btn { background: var(--primary-color); border: 1px dashed var(--primary-dark); padding: 8px; border-radius: 10px; color: var(--element-active-text); font-size: 14px; font-weight: 700; cursor: pointer; transition: background 0.3s ease, transform 0.3s ease; text-align: center; } .draw-btn:hover:not(:disabled) { background: var(--primary-dark); transform: scale(1.05); } .draw-btn:disabled { background: rgba(255, 105, 180, 0.5); cursor: not-allowed; } .kawaii-footer { display: flex; justify-content: center; align-items: center; margin-top: 10px; padding: 6px; background: var(--element-bg); border-radius: 10px; border: 2px solid var(--primary-color); } .credit-text { font-size: 10px; color: var(--text-color); font-weight: 700; } `; document.head.appendChild(style); // DOM Elements const kawaiiCheat = document.getElementById('kawaiiCheat'); const kawaiiHeader = document.getElementById('kawaiiHeader'); const minimizeBtn = document.getElementById('minimizeBtn'); const tabButtons = document.querySelectorAll('.kawaii-tab'); const tabContents = document.querySelectorAll('.kawaii-content'); const autoGuessCheckbox = document.getElementById('autoGuess'); const speedContainer = document.getElementById('speedContainer'); const guessSpeed = document.getElementById('guessSpeed'); const speedValue = document.getElementById('speedValue'); const customWordsCheckbox = document.getElementById('customWords'); const wordListContainer = document.getElementById('wordListContainer'); const wordListDropzone = document.getElementById('wordListDropzone'); const wordListInput = document.getElementById('wordList'); const guessPattern = document.getElementById('guessPattern'); const hitList = document.getElementById('hitList'); const imageDropzone = document.getElementById('imageDropzone'); const imageUpload = document.getElementById('imageUpload'); const imagePreview = document.getElementById('imagePreview'); const previewImg = document.getElementById('previewImg'); const cancelImage = document.getElementById('cancelImage'); const drawSpeed = document.getElementById('drawSpeed'); const drawSpeedValue = document.getElementById('drawSpeedValue'); const maxColors = document.getElementById('maxColors'); const maxColorsValue = document.getElementById('maxColorsValue'); const sendDraw = document.getElementById('sendDraw'); // Variables let isDragging = false; let initialX, initialY; let xOffset = 0, yOffset = 0; let rafId = null; // requestAnimationFrame ID let autoGuessInterval = null; let wordList = { "Custom": [] }; let triedLabelAdded = false; const wordListURLs = { "General (en)": "https://cdn.jsdelivr.net/gh/Gartic-Developers/Gartic-WordList@master/languages/English/general.json", "General (tr)": "https://cdn.jsdelivr.net/gh/Gartic-Developers/Gartic-WordList@master/languages/Turkish/general.json" }; // Utility Functions function updateSliderTrack(slider) { const min = parseInt(slider.min); const max = parseInt(slider.max); const value = parseInt(slider.value); const progress = ((value - min) / (max - min)) * 100; slider.parentElement.querySelector('.slider-track').style.setProperty('--slider-progress', `${progress}%`); } function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } // Initial Setup updateSliderTrack(guessSpeed); updateSliderTrack(drawSpeed); updateSliderTrack(maxColors); // Dragging Functionality with Optimization kawaiiHeader.addEventListener('mousedown', (e) => { if (e.target !== minimizeBtn) { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; kawaiiCheat.classList.add('dragging'); if (rafId) cancelAnimationFrame(rafId); // Önceki frame'i iptal et } }); document.addEventListener('mousemove', (e) => { if (isDragging) { e.preventDefault(); const newX = e.clientX - initialX; const newY = e.clientY - initialY; if (rafId) cancelAnimationFrame(rafId); // Tekrarlanan frame'leri önle rafId = requestAnimationFrame(() => { kawaiiCheat.style.transform = `translate(${newX}px, ${newY}px)`; xOffset = newX; yOffset = newY; }); } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; kawaiiCheat.classList.remove('dragging'); if (rafId) cancelAnimationFrame(rafId); // Son frame'i temizle } }); // Minimize Button minimizeBtn.addEventListener('click', () => { kawaiiCheat.classList.toggle('minimized'); minimizeBtn.textContent = kawaiiCheat.classList.contains('minimized') ? '▲' : '▼'; }); // Tab Switching tabButtons.forEach(btn => { btn.addEventListener('click', () => { tabButtons.forEach(b => b.classList.remove('active')); tabContents.forEach(c => c.style.display = 'none'); btn.classList.add('active'); document.getElementById(`${btn.dataset.tab}-tab`).style.display = 'flex'; }); }); // Checkbox Container Click document.querySelectorAll('.checkbox-container').forEach(container => { container.addEventListener('click', (e) => { const checkbox = container.querySelector('input[type="checkbox"]'); if (e.target !== checkbox) { checkbox.checked = !checkbox.checked; checkbox.dispatchEvent(new Event('change')); } }); }); // Auto Guess Checkbox autoGuessCheckbox.addEventListener('change', (e) => { speedContainer.style.display = e.target.checked ? 'flex' : 'none'; if (!e.target.checked) stopAutoGuess(); else if (guessPattern.value) startAutoGuess(); }); // Guess Speed Slider guessSpeed.addEventListener('input', (e) => { updateSliderTrack(e.target); speedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`; if (autoGuessCheckbox.checked && autoGuessInterval) { stopAutoGuess(); startAutoGuess(); } }); // Custom Words Checkbox customWordsCheckbox.addEventListener('change', (e) => { wordListContainer.style.display = e.target.checked ? 'block' : 'none'; updateHitList(guessPattern.value.trim()); }); // Word List Dropzone ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { wordListDropzone.addEventListener(eventName, preventDefaults, false); }); wordListDropzone.addEventListener('dragenter', () => wordListDropzone.classList.add('drag-over')); wordListDropzone.addEventListener('dragover', () => wordListDropzone.classList.add('drag-over')); wordListDropzone.addEventListener('dragleave', () => wordListDropzone.classList.remove('drag-over')); wordListDropzone.addEventListener('drop', (e) => { wordListDropzone.classList.remove('drag-over'); const file = e.dataTransfer.files[0]; if (file && file.type === 'text/plain') handleWordListFile(file); }); wordListInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { handleWordListFile(file); e.target.value = ''; } }); function handleWordListFile(file) { const reader = new FileReader(); reader.onload = function(event) { wordList["Custom"] = event.target.result.split('\n').map(word => word.trim()).filter(word => word.length > 0); alert(`Loaded ${wordList["Custom"].length} words from ${file.name}`); updateHitList(guessPattern.value.trim()); }; reader.readAsText(file); } // Guess Pattern Input guessPattern.addEventListener('input', (e) => updateHitList(e.target.value.trim())); // Hit List Functionality hitList.addEventListener('click', (e) => { if (e.target.tagName === 'BUTTON' && !e.target.classList.contains('tried')) { const button = e.target; button.classList.add('tried'); if (!triedLabelAdded && hitList.querySelectorAll('button.tried').length === 1) { const triedLabel = document.createElement('div'); triedLabel.classList.add('tried-label'); triedLabel.textContent = 'Tried Words'; hitList.appendChild(triedLabel); triedLabelAdded = true; } if (window.game && window.game._socket) { window.game._socket.emit(13, window.game._codigo, button.textContent); } hitList.appendChild(button); } }); function startAutoGuess() { if (!autoGuessCheckbox.checked) return; stopAutoGuess(); const speed = parseInt(guessSpeed.value); autoGuessInterval = setInterval(() => { const buttons = hitList.querySelectorAll('button:not(.tried)'); if (buttons.length > 0 && window.game && window.game._socket) { const word = buttons[0].textContent; buttons[0].classList.add('tried'); window.game._socket.emit(13, window.game._codigo, word); if (!triedLabelAdded && hitList.querySelectorAll('button.tried').length === 1) { const triedLabel = document.createElement('div'); triedLabel.classList.add('tried-label'); triedLabel.textContent = 'Tried Words'; hitList.appendChild(triedLabel); triedLabelAdded = true; } hitList.appendChild(buttons[0]); } }, speed); } function stopAutoGuess() { if (autoGuessInterval) { clearInterval(autoGuessInterval); autoGuessInterval = null; } } function updateHitList(pattern) { hitList.innerHTML = ''; triedLabelAdded = false; const activeTheme = customWordsCheckbox.checked || !window.game || !window.game._dadosSala || !window.game._dadosSala.tema ? "Custom" : window.game._dadosSala.tema; const activeList = wordList[activeTheme] || []; if (!pattern) { if (activeList.length === 0) { hitList.innerHTML = `<div class="message">${customWordsCheckbox.checked ? 'Upload a custom word list ✧' : 'No words available ✧'}</div>`; } else { activeList.forEach(word => { const button = document.createElement('button'); button.textContent = word; hitList.appendChild(button); }); } return; } const regex = new RegExp(`^${pattern.split('').map(char => char === '_' ? '.' : char).join('')}$`, 'i'); const matches = activeList.filter(word => regex.test(word)); if (matches.length === 0) { hitList.innerHTML = '<div class="message">No matches found ✧</div>'; } else { matches.forEach(word => { const button = document.createElement('button'); button.textContent = word; hitList.appendChild(button); }); } } async function fetchWordList(theme) { if (!wordList[theme] && wordListURLs[theme]) { try { const response = await fetch(wordListURLs[theme]); if (!response.ok) throw new Error(`Failed to fetch ${theme} word list`); const data = await response.json(); wordList[theme] = data.words || data; console.log(`Loaded ${wordList[theme].length} words for ${theme}`); } catch (error) { console.error(`Error fetching word list for ${theme}:`, error); wordList[theme] = []; } } } // Image Upload ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { imageDropzone.addEventListener(eventName, preventDefaults, false); }); imageDropzone.addEventListener('dragenter', () => imageDropzone.classList.add('drag-over')); imageDropzone.addEventListener('dragover', () => imageDropzone.classList.add('drag-over')); imageDropzone.addEventListener('dragleave', () => imageDropzone.classList.remove('drag-over')); imageDropzone.addEventListener('drop', (e) => { imageDropzone.classList.remove('drag-over'); const file = e.dataTransfer.files[0]; if (file && file.type.startsWith('image/')) handleImageFile(file); }); imageUpload.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { handleImageFile(file); e.target.value = ''; } }); function handleImageFile(file) { const reader = new FileReader(); reader.onload = function(event) { previewImg.src = event.target.result; imageDropzone.style.display = 'none'; imagePreview.style.display = 'block'; sendDraw.disabled = false; }; reader.readAsDataURL(file); } cancelImage.addEventListener('click', () => { previewImg.src = ''; imageDropzone.style.display = 'flex'; imagePreview.style.display = 'none'; sendDraw.disabled = true; imageUpload.value = ''; }); drawSpeed.addEventListener('input', (e) => { updateSliderTrack(e.target); drawSpeedValue.textContent = e.target.value >= 1000 ? `${e.target.value / 1000}s` : `${e.target.value}ms`; }); maxColors.addEventListener('input', (e) => { updateSliderTrack(e.target); maxColorsValue.textContent = e.target.value; }); sendDraw.addEventListener('click', () => { if (previewImg.src) { if (!window.game || !window.game.turn) { alert('Not your turn or game not loaded! ✧'); return; } sendDraw.disabled = true; processAndDrawImage(previewImg.src); } }); // Socket Integration const checkGame = setInterval(() => { if (window.game && window.game._socket) { clearInterval(checkGame); const currentTheme = window.game._dadosSala.tema || "Custom"; if (currentTheme !== "Custom") { fetchWordList(currentTheme).then(() => updateHitList(guessPattern.value.trim())); } window.game._socket.on(30, (hint) => { hint = String(hint).replace(/,/g, ''); guessPattern.value = hint; updateHitList(hint); if (autoGuessCheckbox.checked) startAutoGuess(); }); window.game._socket.on(19, () => { guessPattern.value = ''; stopAutoGuess(); updateHitList(''); }); window.game._socket.on(15, (playerId) => { if (playerId === window.game.me.id) { guessPattern.value = ''; stopAutoGuess(); updateHitList(''); } }); let lastTheme = currentTheme; setInterval(() => { const newTheme = window.game._dadosSala.tema || "Custom"; if (newTheme !== lastTheme && newTheme !== "Custom") { lastTheme = newTheme; fetchWordList(newTheme).then(() => updateHitList(guessPattern.value.trim())); } }, 1000); } }, 100); } function processAndDrawImage(imageSrc) { if (!window.game || !window.game._socket || !window.game._desenho || !window.game.turn) { alert('Game not ready or not your turn! ✧'); return; } const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = async function() { const gameCanvas = window.game._desenho._canvas.canvas; if (!gameCanvas || !gameCanvas.width || !gameCanvas.height) { alert('Canvas not accessible! ✧'); sendDraw.disabled = false; return; } const ctx = gameCanvas.getContext('2d'); if (!ctx) { alert('Canvas context not available! ✧'); sendDraw.disabled = false; return; } const canvasWidth = Math.floor(gameCanvas.width); const canvasHeight = Math.floor(gameCanvas.height); const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); if (!tempCtx) { alert('Temp canvas context failed! ✧'); sendDraw.disabled = false; return; } const scale = Math.min(canvasWidth / img.width, canvasHeight / img.height); const newWidth = Math.floor(img.width * scale); const newHeight = Math.floor(img.height * scale); tempCanvas.width = canvasWidth; tempCanvas.height = canvasHeight; const offsetX = Math.floor((canvasWidth - newWidth) / 2); const offsetY = Math.floor((canvasHeight - newHeight) / 2); tempCtx.drawImage(img, offsetX, offsetY, newWidth, newHeight); let imageData; try { imageData = tempCtx.getImageData(0, 0, canvasWidth, canvasHeight); } catch (e) { alert('Image data error: ' + e.message + ' ✧'); sendDraw.disabled = false; return; } const data = imageData.data; const drawSpeedValue = parseInt(drawSpeed.value) || 20; // Get maxColors from menu const maxColorsValue = parseInt(maxColors.value) || 20; // Image bounds const imgLeft = offsetX; const imgRight = offsetX + newWidth - 1; const imgTop = offsetY; const imgBottom = offsetY + newHeight - 1; // Background detection const colorCounts = new Map(); let backgroundColor = [255, 255, 255]; const sampleStep = Math.max(1, Math.floor(newWidth / 50)); for (let x = imgLeft; x <= imgRight; x += sampleStep) { for (let y = imgTop; y <= imgBottom; y += sampleStep) { const index = (y * canvasWidth + x) * 4; const r = Math.round(data[index] / 20) * 20; const g = Math.round(data[index+1] / 20) * 20; const b = Math.round(data[index+2] / 20) * 20; const key = `${r},${g},${b}`; colorCounts.set(key, (colorCounts.get(key) || 0) + 1); } } let maxCount = 0; for (const [key, count] of colorCounts) { if (count > maxCount) { maxCount = count; backgroundColor = key.split(',').map(Number); } } const bgHex = 'x' + backgroundColor.map(c => c.toString(16).padStart(2, '0').toUpperCase() ).join(''); // Clear and set background (both socket and local) window.game._socket.emit(10, window.game._codigo, [4]); ctx.clearRect(0, 0, canvasWidth, canvasHeight); await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); window.game._socket.emit(10, window.game._codigo, [5, bgHex]); ctx.fillStyle = `#${bgHex.slice(1)}`; await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); window.game._socket.emit(10, window.game._codigo, [3, 0, 0, canvasWidth, canvasHeight]); ctx.fillRect(0, 0, canvasWidth, canvasHeight); await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); // Color clustering for foreground const colorClusters = new Map(); for (let y = imgTop; y < imgBottom; y += sampleStep) { for (let x = imgLeft; x < imgRight; x += sampleStep) { const index = (y * canvasWidth + x) * 4; const r = Math.round(data[index] / 20) * 20; const g = Math.round(data[index+1] / 20) * 20; const b = Math.round(data[index+2] / 20) * 20; const key = `${r},${g},${b}`; if (colorDistance([r, g, b], backgroundColor) > 60) { colorClusters.set(key, (colorClusters.get(key) || 0) + 1); } } } const topColors = [...colorClusters.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, maxColorsValue) .map(([key]) => ({ rgb: key.split(',').map(Number), hex: 'x' + key.split(',').map(c => Number(c).toString(16).padStart(2, '0').toUpperCase() ).join('') })); // Fill regions by color (horizontal and vertical passes) const fillsByColor = {}; const visited = new Set(); const stripHeight = 1; const stripWidth = 1; const minStripSize = 1; // Single pixels now included // Horizontal fills for (let y = imgTop; y < imgBottom; y += stripHeight) { let startX = null; let currentColor = null; let stripLength = 0; for (let x = imgLeft; x < imgRight; x += 1) { const index = (y * canvasWidth + x) * 4; const pixelColor = [data[index], data[index+1], data[index+2]]; const bgDist = colorDistance(pixelColor, backgroundColor); if (bgDist > 60 && !visited.has(`${x},${y}`)) { const nearestColor = topColors.reduce((prev, curr) => colorDistance(pixelColor, prev.rgb) < colorDistance(pixelColor, curr.rgb) ? prev : curr ); if (startX === null || currentColor?.hex !== nearestColor.hex) { if (startX !== null && stripLength >= minStripSize) { if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = []; fillsByColor[currentColor.hex].push([startX, y, stripLength, stripHeight]); for (let dx = 0; dx < stripLength; dx++) { visited.add(`${startX + dx},${y}`); } } startX = x; currentColor = nearestColor; stripLength = 1; } else { stripLength++; } } else if (startX !== null && bgDist <= 60) { if (stripLength >= minStripSize) { if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = []; fillsByColor[currentColor.hex].push([startX, y, stripLength, stripHeight]); for (let dx = 0; dx < stripLength; dx++) { visited.add(`${startX + dx},${y}`); } } startX = null; currentColor = null; stripLength = 0; } } if (startX !== null && stripLength >= minStripSize) { if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = []; fillsByColor[currentColor.hex].push([startX, y, stripLength, stripHeight]); for (let dx = 0; dx < stripLength; dx++) { visited.add(`${startX + dx},${y}`); } } } // Vertical fills for (let x = imgLeft; x < imgRight; x += stripWidth) { let startY = null; let currentColor = null; let stripLength = 0; for (let y = imgTop; y < imgBottom; y += 1) { const index = (y * canvasWidth + x) * 4; const pixelColor = [data[index], data[index+1], data[index+2]]; const bgDist = colorDistance(pixelColor, backgroundColor); if (bgDist > 60 && !visited.has(`${x},${y}`)) { const nearestColor = topColors.reduce((prev, curr) => colorDistance(pixelColor, prev.rgb) < colorDistance(pixelColor, curr.rgb) ? prev : curr ); if (startY === null || currentColor?.hex !== nearestColor.hex) { if (startY !== null && stripLength >= minStripSize) { if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = []; fillsByColor[currentColor.hex].push([x, startY, stripWidth, stripLength]); for (let dy = 0; dy < stripLength; dy++) { visited.add(`${x},${startY + dy}`); } } startY = y; currentColor = nearestColor; stripLength = 1; } else { stripLength++; } } else if (startY !== null && bgDist <= 60) { if (stripLength >= minStripSize) { if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = []; fillsByColor[currentColor.hex].push([x, startY, stripWidth, stripLength]); for (let dy = 0; dy < stripLength; dy++) { visited.add(`${x},${startY + dy}`); } } startY = null; currentColor = null; stripLength = 0; } } if (startY !== null && stripLength >= minStripSize) { if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = []; fillsByColor[currentColor.hex].push([x, startY, stripWidth, stripLength]); for (let dy = 0; dy < stripLength; dy++) { visited.add(`${x},${startY + dy}`); } } } // Draw fills grouped by color (socket and local) for (const color in fillsByColor) { const fillCommand = [3]; fillsByColor[color].forEach(([x, y, width, height]) => { fillCommand.push(x, y, width, height); }); window.game._socket.emit(10, window.game._codigo, [5, color]); ctx.fillStyle = `#${color.slice(1)}`; await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); window.game._socket.emit(10, window.game._codigo, fillCommand); fillsByColor[color].forEach(([x, y, width, height]) => { ctx.fillRect(x, y, width, height); }); await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); } // Straight lines for remaining details const lines = []; const lineStep = 1; // Still pixel-by-pixel const minLineLength = 1; // Draw even single pixels for (let y = imgTop; y < imgBottom; y += lineStep) { for (let x = imgLeft; x < imgRight; x += lineStep) { if (visited.has(`${x},${y}`)) continue; const index = (y * canvasWidth + x) * 4; const pixelColor = [data[index], data[index+1], data[index+2]]; if (colorDistance(pixelColor, backgroundColor) <= 60) continue; const nearestColor = topColors.reduce((prev, curr) => colorDistance(pixelColor, prev.rgb) < colorDistance(pixelColor, curr.rgb) ? prev : curr ); // Check horizontal vs vertical preference (even for 1 pixel) let horizontalScore = 0; let verticalScore = 0; // Check right (horizontal) for (let dx = 1; dx <= minLineLength; dx++) { const nx = x + dx; if (nx >= imgRight) break; const ni = (y * canvasWidth + nx) * 4; const nextColor = [data[ni], data[ni+1], data[ni+2]]; if (colorDistance(nextColor, pixelColor) < 50 && !visited.has(`${nx},${y}`)) { horizontalScore++; } else { break; } } // Check down (vertical) for (let dy = 1; dy <= minLineLength; dy++) { const ny = y + dy; if (ny >= imgBottom) break; const ni = (ny * canvasWidth + x) * 4; const nextColor = [data[ni], data[ni+1], data[ni+2]]; if (colorDistance(nextColor, pixelColor) < 50 && !visited.has(`${x},${ny}`)) { verticalScore++; } else { break; } } // Decide direction and length (default to 1 if no longer segment) let lineLength, points; if (horizontalScore >= verticalScore && horizontalScore >= 1) { lineLength = Math.min(horizontalScore, imgRight - x); points = [[x, y], [x + lineLength - 1, y]]; } else if (verticalScore >= 1) { lineLength = Math.min(verticalScore, imgBottom - y); points = [[x, y], [x, y + lineLength - 1]]; } else { // Single pixel case lineLength = 1; points = [[x, y], [x, y]]; // Same point, will draw as 1x1 fill } lines.push({ points: points, color: nearestColor.hex }); for (let dx = 0; dx < lineLength && points[0][0] === points[1][0]; dx++) { visited.add(`${x},${y + dx}`); } for (let dy = 0; dy < lineLength && points[0][1] === points[1][1]; dy++) { visited.add(`${x + dy},${y}`); } } } // Draw straight lines (socket and local) for (const { points, color } of lines) { window.game._socket.emit(10, window.game._codigo, [5, color]); ctx.strokeStyle = `#${color.slice(1)}`; await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); window.game._socket.emit(10, window.game._codigo, [6, 4]); ctx.lineWidth = 4; await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); const drawCommand = [1, 6, points[0][0], points[0][1], points[1][0], points[1][1]]; window.game._socket.emit(10, window.game._codigo, drawCommand); ctx.beginPath(); ctx.moveTo(points[0][0], points[0][1]); ctx.lineTo(points[1][0], points[1][1]); ctx.stroke(); await new Promise(resolve => setTimeout(resolve, drawSpeedValue)); } alert('Drawing completed! ✧'); sendDraw.disabled = false; }; img.onerror = function() { alert('Failed to load image! ✧'); sendDraw.disabled = false; }; img.src = imageSrc; } // Color distance helper function colorDistance(color1, color2) { return Math.sqrt( Math.pow(color1[0] - color2[0], 2) + Math.pow(color1[1] - color2[1], 2) + Math.pow(color1[2] - color2[2], 2) ); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址