- // ==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="1" max="50" value="20" step="1">
- <div class="slider-track"></div>
- <span id="drawSpeedValue">20</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;
- });
-
- 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 = Math.max(300, parseInt(drawSpeed.value) || 500);
-
- // Get maxColors from menu (fixed from drawSpeed to maxColorsSelect)
- 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
- const fillsByColor = {};
- const visited = new Set();
- const stripHeight = 2;
- const minStripWidth = 10;
-
- for (let y = imgTop; y < imgBottom; y += stripHeight) {
- let startX = null;
- let currentColor = null;
- let stripWidth = 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 && stripWidth >= minStripWidth) {
- if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
- fillsByColor[currentColor.hex].push([startX, y, stripWidth, stripHeight]);
- for (let dx = 0; dx < stripWidth; dx++) {
- visited.add(`${startX + dx},${y}`);
- }
- }
- startX = x;
- currentColor = nearestColor;
- stripWidth = 1;
- } else {
- stripWidth++;
- }
- } else if (startX !== null && bgDist <= 60) {
- if (stripWidth >= minStripWidth) {
- if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
- fillsByColor[currentColor.hex].push([startX, y, stripWidth, stripHeight]);
- for (let dx = 0; dx < stripWidth; dx++) {
- visited.add(`${startX + dx},${y}`);
- }
- }
- startX = null;
- currentColor = null;
- stripWidth = 0;
- }
- }
-
- if (startX !== null && stripWidth >= minStripWidth) {
- if (!fillsByColor[currentColor.hex]) fillsByColor[currentColor.hex] = [];
- fillsByColor[currentColor.hex].push([startX, y, stripWidth, stripHeight]);
- for (let dx = 0; dx < stripWidth; dx++) {
- visited.add(`${startX + dx},${y}`);
- }
- }
- }
-
- // 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 = 6; // Larger step for efficiency
- const minLineLength = 10; // Minimum length for a line to be drawn
-
- 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 based on neighboring pixels
- 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
- if (horizontalScore >= verticalScore && horizontalScore >= minLineLength / 2) {
- const lineLength = Math.min(horizontalScore, imgRight - x);
- if (lineLength >= minLineLength) {
- lines.push({
- points: [[x, y], [x + lineLength - 1, y]],
- color: nearestColor.hex
- });
- for (let dx = 0; dx < lineLength; dx++) {
- visited.add(`${x + dx},${y}`);
- }
- }
- } else if (verticalScore >= minLineLength / 2) {
- const lineLength = Math.min(verticalScore, imgBottom - y);
- if (lineLength >= minLineLength) {
- lines.push({
- points: [[x, y], [x, y + lineLength - 1]],
- color: nearestColor.hex
- });
- for (let dy = 0; dy < lineLength; dy++) {
- visited.add(`${x},${y + dy}`);
- }
- }
- }
- }
- }
-
- // 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)
- );
- }
- })();