let puzzleStartTime = null; document.addEventListener('DOMContentLoaded', () => { // DOM elements const submitBtn = document.getElementById('submit-answer'); const userAnswerInput = document.getElementById('user-answer'); const puzzleImage = document.getElementById('puzzle-image'); const puzzleImageContainer = document.querySelector('.puzzle-image-container'); const resultMessage = document.getElementById('result-message'); const totalCount = document.getElementById('total-count'); const correctCount = document.getElementById('correct-count'); const accuracyEl = document.getElementById('accuracy'); const puzzlePrompt = document.getElementById('puzzle-prompt'); const puzzleContainer = document.getElementById('puzzle-container'); const inputGroup = document.querySelector('.input-group'); const difficultyStars = document.getElementById('difficulty-stars'); // Debug mode - set to true to show ground truth areas const DEBUG_MODE = false; // Tracking state let currentPuzzle = null; let benchmarkStats = { total: 0, correct: 0 }; let clickCoordinates = null; let processingClick = false; // Flag to prevent multiple clicks while processing let currentRotationAngle = 0; // Track current rotation for Rotation_Match let selectedCells = []; // Track selected cells for Unusual_Detection let bingoSelectedCells = []; // Track selected cells for Bingo swap let selectedAnimalIndex = -1; // Track selected animal index for Select_Animal // Add debug type tracking variable // let debugPuzzleType = null; // Initialize difficulty stars with default value (to show something immediately) displayDifficultyStars('Dice_Count'); // Event listeners submitBtn.addEventListener('click', submitAnswer); userAnswerInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { submitAnswer(); } }); // Add click event handler directly to the puzzle image puzzleImage.addEventListener('click', handleImageClick); // Add debug mode selector // setupDebugModeSelector(); // Functions function handleImageClick(e) { if (currentPuzzle && currentPuzzle.input_type === 'click' && !processingClick) { // Prevent multiple clicks while processing processingClick = true; // Get click coordinates relative to the image const rect = e.target.getBoundingClientRect(); const x = Math.round(e.clientX - rect.left); const y = Math.round(e.clientY - rect.top); // Store coordinates for submission clickCoordinates = [x, y]; // Show where user clicked showClickMarker(x, y); // Log for debugging console.log('Click received:', { x, y, target: e.target.id }); // Special handling for Misleading_Click to show if click is in avoid area if (currentPuzzle.puzzle_type === 'Misleading_Click' && currentPuzzle.avoid_area) { const { x: areaX, y: areaY, width: areaWidth, height: areaHeight } = currentPuzzle.avoid_area; // Check if click is within the avoid area const inAvoidArea = ( areaX <= x && x <= areaX + areaWidth && areaY <= y && y <= areaY + areaHeight ); if (inAvoidArea) { console.log('Click is inside the avoid area! This is incorrect.'); // Add a visual indicator const marker = document.querySelector('.click-marker'); if (marker) { marker.style.borderColor = 'red'; marker.style.backgroundColor = 'rgba(255, 0, 0, 0.7)'; } } else { console.log('Click is outside the avoid area! This is correct.'); // Add a visual indicator const marker = document.querySelector('.click-marker'); if (marker) { marker.style.borderColor = 'green'; marker.style.backgroundColor = 'rgba(0, 255, 0, 0.7)'; } } } // Special handling for Pick_Area to show if click is in the target area else if (currentPuzzle.puzzle_type === 'Pick_Area') { // Get the ground truth data to validate the click fetch('/api/get_ground_truth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }) }) .then(response => response.json()) .then(gtData => { if (gtData.answer && gtData.answer.area) { // Extract area boundaries from the ground truth const [[minX, minY], [maxX, maxY]] = gtData.answer.area; // Basic rectangular check const inRectArea = (minX <= x && x <= maxX && minY <= y && y <= maxY); // For more accurate curve detection: let inPolygonArea = false; if (gtData.answer.polygon) { // If we have a polygon definition for the curved area inPolygonArea = pointInPolygon(x, y, gtData.answer.polygon); } // Determine if the click is in the target area // Use polygon if available, otherwise fall back to rectangular check const inArea = gtData.answer.polygon ? inPolygonArea : inRectArea; // Get the marker element const marker = document.querySelector('.click-marker'); if (marker) { if (inArea) { console.log('Click is inside the target area! This is correct.'); marker.style.borderColor = 'green'; marker.style.backgroundColor = 'rgba(0, 255, 0, 0.7)'; // Add a success message const successMsg = document.createElement('div'); successMsg.className = 'success-msg'; successMsg.textContent = 'In largest area!'; successMsg.style.position = 'absolute'; successMsg.style.top = '-25px'; successMsg.style.left = '50%'; successMsg.style.transform = 'translateX(-50%)'; successMsg.style.backgroundColor = 'rgba(0, 128, 0, 0.9)'; successMsg.style.color = 'white'; successMsg.style.padding = '3px 8px'; successMsg.style.borderRadius = '3px'; successMsg.style.fontSize = '12px'; successMsg.style.fontWeight = 'bold'; marker.appendChild(successMsg); } else { console.log('Click is outside the target area! This is incorrect.'); marker.style.borderColor = 'red'; marker.style.backgroundColor = 'rgba(255, 0, 0, 0.7)'; // Add an error message const errorMsg = document.createElement('div'); errorMsg.className = 'error-msg'; errorMsg.textContent = 'Not in largest area!'; errorMsg.style.position = 'absolute'; errorMsg.style.top = '-25px'; errorMsg.style.left = '50%'; errorMsg.style.transform = 'translateX(-50%)'; errorMsg.style.backgroundColor = 'rgba(255, 0, 0, 0.9)'; errorMsg.style.color = 'white'; errorMsg.style.padding = '3px 8px'; errorMsg.style.borderRadius = '3px'; errorMsg.style.fontSize = '12px'; errorMsg.style.fontWeight = 'bold'; marker.appendChild(errorMsg); } } } }) .catch(error => { console.error('Error validating click for Pick_Area:', error); }); } // Auto-submit after click setTimeout(() => { submitAnswer(); }, 500); // Increase delay slightly to allow for fetch response } } // Function to handle rotation function setupRotationControls() { // Remove any existing controls first const existingControls = document.querySelector('.rotation-controls'); if (existingControls) { existingControls.remove(); } // Create rotation controls const rotationControls = document.createElement('div'); rotationControls.className = 'rotation-controls'; // Create left rotation button const leftBtn = document.createElement('button'); leftBtn.className = 'rotate-left'; leftBtn.innerHTML = '↶'; // Counter-clockwise arrow leftBtn.setAttribute('aria-label', 'Rotate left'); // Create right rotation button const rightBtn = document.createElement('button'); rightBtn.className = 'rotate-right'; rightBtn.innerHTML = '↷'; // Clockwise arrow rightBtn.setAttribute('aria-label', 'Rotate right'); // Add buttons to controls rotationControls.appendChild(leftBtn); rotationControls.appendChild(rightBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); // Create a container for the reference image const referenceContainer = document.createElement('div'); referenceContainer.className = 'reference-image-container'; const referenceImg = document.createElement('img'); referenceImg.id = 'reference-image'; referenceImg.src = currentPuzzle.reference_image; referenceImg.alt = 'Reference direction'; referenceContainer.appendChild(referenceImg); // Create a container for the object image const objectContainer = document.createElement('div'); objectContainer.className = 'object-image-container'; const objectImg = document.createElement('img'); objectImg.id = 'object-image'; objectImg.src = currentPuzzle.object_image; objectImg.alt = 'Rotatable object'; objectContainer.appendChild(objectImg); // Create a two-column layout for rotation puzzle const rotationLayout = document.createElement('div'); rotationLayout.className = 'rotation-layout'; rotationLayout.appendChild(referenceContainer); rotationLayout.appendChild(objectContainer); // Replace the existing puzzle image puzzleImageContainer.innerHTML = ''; puzzleImageContainer.appendChild(rotationLayout); // Add rotation controls below the image imageWrapper.appendChild(rotationControls); // Add event listeners for rotation buttons leftBtn.addEventListener('click', () => rotateObject(-45)); rightBtn.addEventListener('click', () => rotateObject(45)); // Set initial angle currentRotationAngle = currentPuzzle.current_angle || 0; updateObjectRotation(); } function rotateObject(angleDelta) { // Update the current angle currentRotationAngle = (currentRotationAngle + angleDelta) % 360; if (currentRotationAngle < 0) { currentRotationAngle += 360; } // Apply the rotation updateObjectRotation(); // Log for debugging console.log('Rotated to:', currentRotationAngle); } function updateObjectRotation() { const objectImg = document.getElementById('object-image'); if (objectImg) { // Option 1: Use CSS transform to rotate the image objectImg.style.transform = `rotate(${currentRotationAngle}deg)`; // Option 2: Load a pre-rotated image if available // This would require having images at each rotation angle const baseName = currentPuzzle.object_base; // Find the closest pre-rotated image (0, 90, 180, 270) const angles = [0, 45, 90, 135, 180, 225, 270, 315]; const closestAngle = angles.reduce((prev, curr) => Math.abs(curr - currentRotationAngle) < Math.abs(prev - currentRotationAngle) ? curr : prev ); // Load the pre-rotated image const rotatedImagePath = `/captcha_data/${currentPuzzle.puzzle_type}/${baseName}_${closestAngle}.png`; objectImg.src = rotatedImagePath; // Apply any additional rotation needed const remainingRotation = currentRotationAngle - closestAngle; if (remainingRotation !== 0) { objectImg.style.transform = `rotate(${remainingRotation}deg)`; } else { objectImg.style.transform = 'none'; } } } // Function to set up sliding puzzle function setupSlidePuzzle() { // Remove any existing controls first const existingSlider = document.querySelector('.slider-component'); if (existingSlider) { existingSlider.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Create a container for the background image const backgroundContainer = document.createElement('div'); backgroundContainer.className = 'background-container'; backgroundContainer.style.position = 'relative'; backgroundContainer.style.width = '100%'; backgroundContainer.style.height = 'auto'; // Add background image const backgroundImg = document.createElement('img'); backgroundImg.src = currentPuzzle.background_image; backgroundImg.alt = 'Slide puzzle background'; backgroundImg.style.width = '100%'; backgroundImg.style.height = 'auto'; backgroundImg.style.display = 'block'; backgroundContainer.appendChild(backgroundImg); // Create draggable slider component const sliderComponent = document.createElement('div'); sliderComponent.className = 'slider-component'; sliderComponent.style.position = 'absolute'; sliderComponent.style.cursor = 'move'; sliderComponent.style.zIndex = '10'; sliderComponent.style.userSelect = 'none'; sliderComponent.style.touchAction = 'none'; sliderComponent.style.width = '50px'; // Add component image const componentImg = document.createElement('img'); componentImg.src = currentPuzzle.component_image; componentImg.alt = 'Slide component'; componentImg.style.width = '150%'; componentImg.style.height = 'auto'; componentImg.style.display = 'block'; componentImg.draggable = false; // Prevent default dragging behavior sliderComponent.appendChild(componentImg); // Add slider component to the background container backgroundContainer.appendChild(sliderComponent); // Add the whole setup to the puzzle image container puzzleImageContainer.appendChild(backgroundContainer); // Wait for images to load to get proper dimensions backgroundImg.onload = () => { // Get container dimensions const containerWidth = backgroundImg.width; const containerHeight = backgroundImg.height; // Load component image to get its dimensions componentImg.onload = () => { const originalComponentWidth = componentImg.naturalWidth; const originalComponentHeight = componentImg.naturalHeight; const componentWidth = containerWidth * 0.08; const aspectRatio = originalComponentWidth / originalComponentHeight; const componentHeight = componentWidth / aspectRatio; sliderComponent.style.width = `${componentWidth}px`; // Initial position for the slider component - bottom right corner (far from typical target) const initialLeft = containerWidth - componentWidth - 20; const initialTop = containerHeight - componentHeight - 20; sliderComponent.style.left = `${initialLeft}px`; sliderComponent.style.top = `${initialTop}px`; // Initialize current position tracking variables currentX = initialLeft; currentY = initialTop; // In debug mode, fetch and show the target area if (DEBUG_MODE) { fetch('/api/get_ground_truth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }) }) .then(response => response.json()) .then(gtData => { if (gtData.answer) { // Get tolerance value if available const tolerance = gtData.answer.tolerance || 15; // Default to 15px showSliderTargetArea(gtData.answer, backgroundContainer, tolerance); } }) .catch(error => { console.error('Error fetching ground truth:', error); }); } }; }; // Set up draggable functionality let isDragging = false; let startX, startY, startLeft, startTop; // Track current position let currentX = 0; let currentY = 0; // Mouse events for desktop sliderComponent.addEventListener('mousedown', (e) => { isDragging = true; startX = e.clientX; startY = e.clientY; startLeft = parseInt(sliderComponent.style.left) || 0; startTop = parseInt(sliderComponent.style.top) || 0; sliderComponent.style.opacity = '0.8'; // Prevent default browser behavior e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; // Calculate new position const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; // Calculate new position let newLeft = startLeft + deltaX; let newTop = startTop + deltaY; // Get container dimensions const containerRect = backgroundContainer.getBoundingClientRect(); const sliderRect = sliderComponent.getBoundingClientRect(); // Ensure the slider stays within the container bounds if (newLeft < 0) newLeft = 0; if (newTop < 0) newTop = 0; if (newLeft > containerRect.width - sliderRect.width) newLeft = containerRect.width - sliderRect.width; if (newTop > containerRect.height - sliderRect.height) newTop = containerRect.height - sliderRect.height; sliderComponent.style.left = `${newLeft}px`; sliderComponent.style.top = `${newTop}px`; // Update current position currentX = newLeft; currentY = newTop; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; sliderComponent.style.opacity = '1'; // Calculate center point const componentRect = componentImg.getBoundingClientRect(); const centerX = currentX + (componentRect.width / 2); const centerY = currentY + (componentRect.height / 2); // Log final position for debugging console.log('Slider final position (top-left):', { x: currentX, y: currentY }); console.log('Slider center position:', { x: centerX, y: centerY }); } }); // Touch events for mobile sliderComponent.addEventListener('touchstart', (e) => { isDragging = true; startX = e.touches[0].clientX; startY = e.touches[0].clientY; startLeft = parseInt(sliderComponent.style.left) || 0; startTop = parseInt(sliderComponent.style.top) || 0; sliderComponent.style.opacity = '0.8'; // Prevent default browser behavior e.preventDefault(); }); document.addEventListener('touchmove', (e) => { if (!isDragging) return; // Calculate new position const deltaX = e.touches[0].clientX - startX; const deltaY = e.touches[0].clientY - startY; // Calculate new position let newLeft = startLeft + deltaX; let newTop = startTop + deltaY; // Get container dimensions const containerRect = backgroundContainer.getBoundingClientRect(); const sliderRect = sliderComponent.getBoundingClientRect(); // Ensure the slider stays within the container bounds if (newLeft < 0) newLeft = 0; if (newTop < 0) newTop = 0; if (newLeft > containerRect.width - sliderRect.width) newLeft = containerRect.width - sliderRect.width; if (newTop > containerRect.height - sliderRect.height) newTop = containerRect.height - sliderRect.height; sliderComponent.style.left = `${newLeft}px`; sliderComponent.style.top = `${newTop}px`; // Update current position currentX = newLeft; currentY = newTop; }); document.addEventListener('touchend', () => { if (isDragging) { isDragging = false; sliderComponent.style.opacity = '1'; // Calculate center point const componentRect = componentImg.getBoundingClientRect(); const centerX = currentX + (componentRect.width / 2); const centerY = currentY + (componentRect.height / 2); // Log final position for debugging console.log('Slider final position (top-left):', { x: currentX, y: currentY }); console.log('Slider center position:', { x: centerX, y: centerY }); } }); // Add submit button for the sliding puzzle const submitSection = document.createElement('div'); submitSection.className = 'slider-submit'; const sliderSubmitBtn = document.createElement('button'); sliderSubmitBtn.textContent = 'Submit'; sliderSubmitBtn.className = 'submit-slider'; sliderSubmitBtn.addEventListener('click', () => { // When submitting, we need to get the final position // and normalize it to the image dimensions const componentRect = componentImg.getBoundingClientRect(); // Calculate center point of the component const centerX = currentX + (componentRect.width / 2); const centerY = currentY + (componentRect.height / 2); // Submit this position console.log('Submitting slider position:', { x: centerX, y: centerY }); submitSliderPosition(centerX, centerY); }); submitSection.appendChild(sliderSubmitBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); imageWrapper.appendChild(submitSection); } // Function to show the target area for the slider in debug mode function showSliderTargetArea(targetPosition, container, tolerance = 15) { if (!DEBUG_MODE || !targetPosition) return; // Remove any existing debug targets const existingTarget = document.querySelector('.target-area'); if (existingTarget) { existingTarget.remove(); } // Create a target element const targetArea = document.createElement('div'); targetArea.className = 'target-area'; // Get target coordinates const [targetX, targetY] = targetPosition; // We'll visualize this as a circle const diameter = tolerance * 2; // Style the target area targetArea.style.position = 'absolute'; targetArea.style.left = `${targetX - tolerance}px`; targetArea.style.top = `${targetY - tolerance}px`; targetArea.style.width = `${diameter}px`; targetArea.style.height = `${diameter}px`; targetArea.style.borderRadius = '50%'; targetArea.style.border = '2px dashed green'; targetArea.style.backgroundColor = 'rgba(0, 255, 0, 0.2)'; targetArea.style.zIndex = '5'; targetArea.style.pointerEvents = 'none'; // Allow clicks to pass through // Add coordinates label const coordsLabel = document.createElement('div'); coordsLabel.className = 'coords-label'; coordsLabel.textContent = `Target: (${targetX}, ${targetY}) ±${tolerance}px`; coordsLabel.style.position = 'absolute'; coordsLabel.style.top = '-25px'; coordsLabel.style.left = '0'; coordsLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; coordsLabel.style.color = 'white'; coordsLabel.style.padding = '2px 5px'; coordsLabel.style.fontSize = '10px'; coordsLabel.style.borderRadius = '3px'; coordsLabel.style.whiteSpace = 'nowrap'; targetArea.appendChild(coordsLabel); // Add to the container container.appendChild(targetArea); // Log the target details console.log('Target position:', { x: targetX, y: targetY, tolerance: tolerance }); } // Function to submit slider position function submitSliderPosition(x, y) { if (!currentPuzzle) { resultMessage.textContent = 'Loading puzzle, please wait...'; resultMessage.className = 'result-message incorrect'; return; } // Send position to the server for verification fetch('/api/check_answer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id, answer: [x, y] }) }) .then(response => response.json()) .then(data => { // Update stats benchmarkStats.total++; if (data.correct) { benchmarkStats.correct++; resultMessage.textContent = 'Correct! The slider was placed in the right position.'; resultMessage.className = 'result-message correct'; } else { resultMessage.textContent = 'Incorrect. Please try again with a better position.'; resultMessage.className = 'result-message incorrect'; } updateStats(); // Record benchmark result recordBenchmarkResult({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id, user_answer: [x, y], correct_answer: data.correct_answer, correct: data.correct }); // Disable the submit button to prevent multiple submissions const submitBtn = document.querySelector('.submit-slider'); if (submitBtn) { submitBtn.disabled = true; } // Also disable rotation submit button if it exists const rotateSubmitBtn = document.querySelector('.submit-rotation'); if (rotateSubmitBtn) { rotateSubmitBtn.disabled = true; } // Also disable image recognition submit button if it exists const imageRecognitionSubmitBtn = document.querySelector('.submit-image-recognition'); if (imageRecognitionSubmitBtn) { imageRecognitionSubmitBtn.disabled = true; } // Also disable bingo submit button if it exists const bingoSubmitBtn = document.querySelector('.submit-bingo'); if (bingoSubmitBtn) { bingoSubmitBtn.disabled = true; } // Also disable image matching submit button if it exists const imageMatchingSubmitBtn = document.querySelector('.submit-image-matching'); if (imageMatchingSubmitBtn) { imageMatchingSubmitBtn.disabled = true; } // Load a new puzzle after a delay setTimeout(loadNewPuzzle, 2000); }) .catch(error => { console.error('Error checking answer:', error); resultMessage.textContent = 'Error checking answer. Please try again.'; resultMessage.className = 'result-message incorrect'; }); } // Add this new function to show the ground truth area function showGroundTruthArea(answer) { if (!DEBUG_MODE) return; // Remove any existing debug areas const existingArea = document.querySelector('.debug-area'); if (existingArea) { existingArea.remove(); } // Create and style the debug area element const debugArea = document.createElement('div'); debugArea.className = 'debug-area'; debugArea.style.position = 'absolute'; debugArea.style.pointerEvents = 'none'; // Allow clicks to pass through debugArea.style.zIndex = '5'; if (answer && answer.area) { // For standard area format (geometry_click, etc.) const [[x1, y1], [x2, y2]] = answer.area; debugArea.style.left = `${x1}px`; debugArea.style.top = `${y1}px`; debugArea.style.width = `${x2 - x1}px`; debugArea.style.height = `${y2 - y1}px`; debugArea.style.border = '2px dashed yellow'; debugArea.style.backgroundColor = 'rgba(255, 255, 0, 0.2)'; // Add coordinates label const coordsLabel = document.createElement('div'); coordsLabel.className = 'coords-label'; coordsLabel.textContent = `TL: (${x1},${y1}) BR: (${x2},${y2})`; coordsLabel.style.position = 'absolute'; coordsLabel.style.bottom = '0'; coordsLabel.style.right = '0'; coordsLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; coordsLabel.style.color = 'white'; coordsLabel.style.padding = '2px 5px'; coordsLabel.style.fontSize = '10px'; coordsLabel.style.borderRadius = '3px'; debugArea.appendChild(coordsLabel); // Log the area details console.log('Ground truth area:', { topLeft: [x1, y1], bottomRight: [x2, y2], width: x2 - x1, height: y2 - y1, type: answer.type }); } else if (answer && answer.avoid_area) { // For Misleading_Click avoid_area format const { x, y, width, height } = answer.avoid_area; debugArea.style.left = `${x}px`; debugArea.style.top = `${y}px`; debugArea.style.width = `${width}px`; debugArea.style.height = `${height}px`; debugArea.style.border = '3px dashed red'; debugArea.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // Add coordinates label const coordsLabel = document.createElement('div'); coordsLabel.className = 'coords-label'; coordsLabel.textContent = `Avoid Area: (${x},${y}) ${width}x${height}`; coordsLabel.style.position = 'absolute'; coordsLabel.style.bottom = '0'; coordsLabel.style.right = '0'; coordsLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; coordsLabel.style.color = 'white'; coordsLabel.style.padding = '2px 5px'; coordsLabel.style.fontSize = '10px'; coordsLabel.style.borderRadius = '3px'; debugArea.appendChild(coordsLabel); // Add a "DO NOT CLICK HERE" sign in the middle of the area const warningSign = document.createElement('div'); warningSign.className = 'warning-sign'; warningSign.textContent = 'DO NOT CLICK HERE'; warningSign.style.position = 'absolute'; warningSign.style.top = '50%'; warningSign.style.left = '50%'; warningSign.style.transform = 'translate(-50%, -50%)'; warningSign.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; warningSign.style.color = '#ff5555'; warningSign.style.padding = '5px 10px'; warningSign.style.fontSize = '12px'; warningSign.style.fontWeight = 'bold'; warningSign.style.borderRadius = '3px'; warningSign.style.whiteSpace = 'nowrap'; warningSign.style.zIndex = '10'; debugArea.appendChild(warningSign); // Log the area details console.log('Avoid area:', { x, y, width, height }); } else { // If we don't have a valid format, don't show anything return; } // Add to the image container puzzleImageContainer.appendChild(debugArea); } // Function to fetch and show geometry click target area function fetchAndShowGeometryClickArea(container) { if (!DEBUG_MODE || !currentPuzzle) return; // Fetch ground truth data to show the correct geometric shape area fetch('/api/get_ground_truth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }) }) .then(response => response.json()) .then(gtData => { if (gtData.answer) { // Call showGroundTruthArea with the answer data showGroundTruthArea(gtData.answer); // Log for debugging console.log('Geometry_Click ground truth fetched:', gtData.answer); } }) .catch(error => { console.error('Error fetching ground truth for Geometry_Click:', error); }); } function showClickMarker(x, y) { // Remove any existing markers const existingMarker = document.querySelector('.click-marker'); if (existingMarker) { existingMarker.remove(); } // Create and add new marker const marker = document.createElement('div'); marker.className = 'click-marker'; marker.style.left = `${x}px`; marker.style.top = `${y}px`; // Add coordinates label to the marker const coordsLabel = document.createElement('div'); coordsLabel.className = 'coords-label'; coordsLabel.textContent = `(${x},${y})`; coordsLabel.style.position = 'absolute'; coordsLabel.style.top = '20px'; coordsLabel.style.left = '20px'; coordsLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; coordsLabel.style.color = 'white'; coordsLabel.style.padding = '2px 5px'; coordsLabel.style.fontSize = '10px'; coordsLabel.style.borderRadius = '3px'; coordsLabel.style.whiteSpace = 'nowrap'; marker.appendChild(coordsLabel); // Add it directly to the image container for proper positioning puzzleImageContainer.appendChild(marker); // Log for debugging console.log('Marker placed at:', { x, y }); // Check if this is a Misleading_Click puzzle and we're in debug mode if (DEBUG_MODE && currentPuzzle && currentPuzzle.puzzle_type === 'Misleading_Click' && currentPuzzle.avoid_area) { // Get the avoid area const { x: areaX, y: areaY, width: areaWidth, height: areaHeight } = currentPuzzle.avoid_area; // Check if click is within the avoid area const inAvoidArea = ( areaX <= x && x <= areaX + areaWidth && areaY <= y && y <= areaY + areaHeight ); // Add status indicator const statusIndicator = document.createElement('div'); statusIndicator.className = 'click-status'; statusIndicator.style.position = 'absolute'; statusIndicator.style.top = '40px'; statusIndicator.style.left = '20px'; statusIndicator.style.padding = '3px 6px'; statusIndicator.style.borderRadius = '3px'; statusIndicator.style.fontSize = '10px'; statusIndicator.style.fontWeight = 'bold'; if (inAvoidArea) { statusIndicator.textContent = 'INSIDE AVOID AREA - WRONG'; statusIndicator.style.backgroundColor = 'rgba(255, 0, 0, 0.8)'; statusIndicator.style.color = 'white'; marker.style.borderColor = 'red'; } else { statusIndicator.textContent = 'OUTSIDE AVOID AREA - CORRECT'; statusIndicator.style.backgroundColor = 'rgba(0, 255, 0, 0.8)'; statusIndicator.style.color = 'black'; marker.style.borderColor = 'green'; } marker.appendChild(statusIndicator); // Log result console.log('Click check:', { inAvoidArea, message: inAvoidArea ? 'INSIDE avoid area (incorrect)' : 'OUTSIDE avoid area (correct)' }); } } // Function to set up unusual detection grid function setupUnusualDetectionGrid() { // Remove any existing grid const existingGrid = document.querySelector('.unusual-detection-grid'); if (existingGrid) { existingGrid.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Get the grid dimensions from the current puzzle data const gridSize = currentPuzzle.grid_size || [2, 3]; // Default to 2x3 if not specified const [rows, cols] = gridSize; // Create the grid container const gridContainer = document.createElement('div'); gridContainer.className = 'unusual-detection-grid'; gridContainer.style.display = 'grid'; gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; gridContainer.style.gridTemplateRows = `repeat(${rows}, 1fr)`; gridContainer.style.gap = '2px'; gridContainer.style.width = '100%'; gridContainer.style.aspectRatio = `${cols} / ${rows}`; // First, load the full image to get its dimensions const fullImg = new Image(); fullImg.onload = () => { const imgWidth = fullImg.width; const imgHeight = fullImg.height; const cellWidth = imgWidth / cols; const cellHeight = imgHeight / rows; // Create individual image elements for each cell const totalCells = rows * cols; for (let i = 0; i < totalCells; i++) { const cell = document.createElement('div'); cell.className = 'grid-cell'; cell.dataset.index = i; cell.style.position = 'relative'; cell.style.border = '2px solid #333'; cell.style.cursor = 'pointer'; cell.style.overflow = 'hidden'; // Create an individual image for this cell const cellImg = document.createElement('img'); cellImg.className = 'cell-image'; cellImg.style.width = '100%'; cellImg.style.height = '100%'; cellImg.style.objectFit = 'cover'; cellImg.style.display = 'block'; cell.appendChild(cellImg); // Calculate which part of the source image this cell represents const row = Math.floor(i / cols); const col = i % cols; // Create a canvas to extract just this portion of the image const canvas = document.createElement('canvas'); canvas.width = cellWidth; canvas.height = cellHeight; const ctx = canvas.getContext('2d'); // Draw just the portion we want ctx.drawImage( fullImg, col * cellWidth, row * cellHeight, // Source x, y cellWidth, cellHeight, // Source width, height 0, 0, // Destination x, y cellWidth, cellHeight // Destination width, height ); // Set the cell image source to this canvas data cellImg.src = canvas.toDataURL(); // Create an overlay for selection state const overlay = document.createElement('div'); overlay.className = 'cell-overlay'; overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(0, 120, 255, 0.5)'; overlay.style.opacity = '0'; overlay.style.transition = 'opacity 0.2s ease'; overlay.style.pointerEvents = 'none'; cell.appendChild(overlay); // Add a checkmark icon to indicate selection const checkmark = document.createElement('div'); checkmark.className = 'checkmark'; checkmark.innerHTML = '✓'; checkmark.style.position = 'absolute'; checkmark.style.top = '50%'; checkmark.style.left = '50%'; checkmark.style.transform = 'translate(-50%, -50%)'; checkmark.style.color = 'white'; checkmark.style.fontSize = '32px'; checkmark.style.fontWeight = 'bold'; checkmark.style.opacity = '0'; checkmark.style.transition = 'opacity 0.2s ease'; checkmark.style.pointerEvents = 'none'; cell.appendChild(checkmark); // Add click event handler for selection cell.addEventListener('click', (e) => { toggleCellSelection(i, cell); }); // Add the cell to the grid gridContainer.appendChild(cell); } // Add the grid to the puzzle image container puzzleImageContainer.appendChild(gridContainer); // Add a submit button below the grid const submitSection = document.createElement('div'); submitSection.className = 'unusual-submit'; submitSection.style.textAlign = 'center'; submitSection.style.marginTop = '15px'; const unusualSubmitBtn = document.createElement('button'); unusualSubmitBtn.textContent = 'Submit'; unusualSubmitBtn.className = 'submit-unusual'; unusualSubmitBtn.addEventListener('click', submitAnswer); submitSection.appendChild(unusualSubmitBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); imageWrapper.appendChild(submitSection); // Reset selected cells selectedCells = []; }; // Set the source to load the image fullImg.src = currentPuzzle.image_path; fullImg.style.display = 'none'; } function toggleCellSelection(index, cellElement) { // Check if this cell is already selected const isSelected = selectedCells.includes(index); if (isSelected) { // Deselect the cell selectedCells = selectedCells.filter(i => i !== index); cellElement.querySelector('.cell-overlay').style.opacity = '0'; cellElement.querySelector('.checkmark').style.opacity = '0'; cellElement.style.transform = 'scale(1)'; cellElement.style.borderColor = '#333'; } else { // Select the cell selectedCells.push(index); cellElement.querySelector('.cell-overlay').style.opacity = '1'; cellElement.querySelector('.checkmark').style.opacity = '1'; cellElement.style.transform = 'scale(0.95)'; cellElement.style.borderColor = '#0078ff'; } console.log('Selected cells:', selectedCells); } // Function to set up Bingo swap puzzle function setupBingoSwap() { // Remove any existing grid const existingGrid = document.querySelector('.bingo-grid'); if (existingGrid) { existingGrid.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Get the grid dimensions from the current puzzle data const gridSize = currentPuzzle.grid_size || [3, 3]; // Default to 3x3 if not specified const [rows, cols] = gridSize; // Create the grid container const gridContainer = document.createElement('div'); gridContainer.className = 'bingo-grid'; gridContainer.style.display = 'grid'; gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; gridContainer.style.gridTemplateRows = `repeat(${rows}, 1fr)`; gridContainer.style.gap = '2px'; gridContainer.style.width = '100%'; gridContainer.style.aspectRatio = `${cols} / ${rows}`; // First, load the full image to get its dimensions const fullImg = new Image(); fullImg.onload = () => { const imgWidth = fullImg.width; const imgHeight = fullImg.height; const cellWidth = imgWidth / cols; const cellHeight = imgHeight / rows; // Create individual image elements for each cell const totalCells = rows * cols; for (let i = 0; i < totalCells; i++) { const cell = document.createElement('div'); cell.className = 'grid-cell'; cell.dataset.index = i; cell.style.position = 'relative'; cell.style.border = '2px solid #333'; cell.style.cursor = 'pointer'; cell.style.overflow = 'hidden'; // Create an individual image for this cell const cellImg = document.createElement('img'); cellImg.className = 'cell-image'; cellImg.style.width = '100%'; cellImg.style.height = '100%'; cellImg.style.objectFit = 'cover'; cellImg.style.display = 'block'; cell.appendChild(cellImg); // Calculate which part of the source image this cell represents const row = Math.floor(i / cols); const col = i % cols; // Create a canvas to extract just this portion of the image const canvas = document.createElement('canvas'); canvas.width = cellWidth; canvas.height = cellHeight; const ctx = canvas.getContext('2d'); // Draw just the portion we want ctx.drawImage( fullImg, col * cellWidth, row * cellHeight, // Source x, y cellWidth, cellHeight, // Source width, height 0, 0, // Destination x, y cellWidth, cellHeight // Destination width, height ); // Create a data URL and set it as the image source cellImg.src = canvas.toDataURL(); // Create an overlay for selection state const overlay = document.createElement('div'); overlay.className = 'cell-overlay'; overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(0, 120, 255, 0.5)'; overlay.style.opacity = '0'; overlay.style.transition = 'opacity 0.2s ease'; overlay.style.pointerEvents = 'none'; cell.appendChild(overlay); // Add click handler for selection cell.addEventListener('click', (e) => { toggleBingoCellSelection(i, cell); }); // Add the cell to the grid gridContainer.appendChild(cell); } // Add the grid to the puzzle image container puzzleImageContainer.appendChild(gridContainer); // Add a submit button below the grid const submitSection = document.createElement('div'); submitSection.className = 'bingo-submit'; submitSection.style.textAlign = 'center'; submitSection.style.marginTop = '15px'; const bingoSubmitBtn = document.createElement('button'); bingoSubmitBtn.textContent = 'Swap and Submit'; bingoSubmitBtn.className = 'submit-bingo'; bingoSubmitBtn.addEventListener('click', () => { if (bingoSelectedCells.length === 2) { // Visually swap the cells swapBingoCells(); // Submit the answer setTimeout(submitAnswer, 500); } else { resultMessage.textContent = 'Please select exactly two cells to swap.'; resultMessage.className = 'result-message error'; } }); submitSection.appendChild(bingoSubmitBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); imageWrapper.appendChild(submitSection); // Reset selected cells bingoSelectedCells = []; }; // Set the source to load the image fullImg.src = currentPuzzle.image_path; fullImg.style.display = 'none'; } function toggleBingoCellSelection(index, cellElement) { const overlay = cellElement.querySelector('.cell-overlay'); // Check if this cell is already selected const selectedIndex = bingoSelectedCells.indexOf(index); if (selectedIndex !== -1) { // If already selected, unselect it bingoSelectedCells.splice(selectedIndex, 1); overlay.style.opacity = '0'; } else { // If we already have 2 selected cells, remove the first one if (bingoSelectedCells.length >= 2) { const firstCell = document.querySelector(`.grid-cell[data-index="${bingoSelectedCells[0]}"]`); if (firstCell) { firstCell.querySelector('.cell-overlay').style.opacity = '0'; } bingoSelectedCells.shift(); // Remove the first element } // Add this cell to selected bingoSelectedCells.push(index); overlay.style.opacity = '0.5'; } console.log('Selected cells for Bingo:', bingoSelectedCells); } function swapBingoCells() { if (bingoSelectedCells.length !== 2) return; // Get the two cells to swap const cell1 = document.querySelector(`.grid-cell[data-index="${bingoSelectedCells[0]}"]`); const cell2 = document.querySelector(`.grid-cell[data-index="${bingoSelectedCells[1]}"]`); if (!cell1 || !cell2) return; // Get the images inside the cells const img1 = cell1.querySelector('.cell-image'); const img2 = cell2.querySelector('.cell-image'); if (!img1 || !img2) return; // Swap the image sources const tempSrc = img1.src; img1.src = img2.src; img2.src = tempSrc; // Apply a highlight to the solution line if it exists if (currentPuzzle.solution_line) { // Get the answer from the ground truth const correctSwaps = currentPuzzle.answer; const selectedSwapSet = new Set(bingoSelectedCells); // Check which solution was achieved by comparing our selection with possible answers let solutionKey = null; // Check vertical solution if (currentPuzzle.solution_line.vertical && checkIfSolutionMatches(correctSwaps, selectedSwapSet)) { solutionKey = 'vertical'; } // Check horizontal solution else if (currentPuzzle.solution_line.horizontal && checkIfSolutionMatches(correctSwaps, selectedSwapSet)) { solutionKey = 'horizontal'; } // Check diagonal solution else if (currentPuzzle.solution_line.diagonal && checkIfSolutionMatches(correctSwaps, selectedSwapSet)) { solutionKey = 'diagonal'; } // If we found a matching solution, highlight it if (solutionKey && currentPuzzle.solution_line[solutionKey]) { for (const cellIndex of currentPuzzle.solution_line[solutionKey]) { const solutionCell = document.querySelector(`.grid-cell[data-index="${cellIndex}"]`); if (solutionCell) { solutionCell.style.border = '2px solid green'; } } } } } // Helper function to check if selected cells match any solution function checkIfSolutionMatches(correctSwaps, selectedSwapSet) { // Go through each possible correct swap and check if our selection matches any of them for (const correctSwap of correctSwaps) { const correctSwapSet = new Set(correctSwap); // Check if our selected cells match this solution (order doesn't matter) if (setsEqual(selectedSwapSet, correctSwapSet)) { return true; } } return false; } // Helper function to compare sets for equality function setsEqual(set1, set2) { if (set1.size !== set2.size) return false; for (const item of set1) { if (!set2.has(item)) return false; } return true; } // // Function to set up the debug mode selector // function setupDebugModeSelector() { // // Create the debug selector container // const debugContainer = document.createElement('div'); // debugContainer.className = 'debug-selector'; // debugContainer.style.marginTop = '10px'; // debugContainer.style.marginBottom = '10px'; // debugContainer.style.padding = '10px'; // debugContainer.style.backgroundColor = '#f0f0f0'; // debugContainer.style.borderRadius = '4px'; // debugContainer.style.display = 'flex'; // debugContainer.style.alignItems = 'center'; // debugContainer.style.justifyContent = 'center'; // debugContainer.style.flexWrap = 'wrap'; // // Create a label // const label = document.createElement('label'); // label.htmlFor = 'debug-type-selector'; // label.textContent = 'Puzzle Type: '; // label.style.marginRight = '10px'; // label.style.fontWeight = 'bold'; // // Create the select element // const select = document.createElement('select'); // select.id = 'debug-type-selector'; // select.style.padding = '5px'; // select.style.marginRight = '10px'; // // Default option - random puzzles // const defaultOption = document.createElement('option'); // defaultOption.value = ''; // defaultOption.textContent = 'Random (All Types)'; // select.appendChild(defaultOption); // // Fetch available CAPTCHA types from the API // fetch('/api/types') // .then(response => response.json()) // .then(data => { // if (data.types && data.types.length > 0) { // // Add options for each CAPTCHA type // data.types.forEach(type => { // const option = document.createElement('option'); // option.value = type; // option.textContent = type; // select.appendChild(option); // }); // // Check if there's a debug type in URL parameters // const urlParams = new URLSearchParams(window.location.search); // const typeParam = urlParams.get('type'); // if (typeParam) { // select.value = typeParam; // debugPuzzleType = typeParam; // } // } // }) // .catch(error => { // console.error('Error fetching CAPTCHA types:', error); // }); // // Create apply button // const applyBtn = document.createElement('button'); // applyBtn.textContent = 'Apply'; // applyBtn.style.padding = '5px 10px'; // applyBtn.style.backgroundColor = '#4CAF50'; // applyBtn.style.color = 'white'; // applyBtn.style.border = 'none'; // applyBtn.style.borderRadius = '4px'; // applyBtn.style.cursor = 'pointer'; // // Add event listener to the button // applyBtn.addEventListener('click', () => { // debugPuzzleType = select.value; // // Update URL parameter // const url = new URL(window.location); // if (debugPuzzleType) { // url.searchParams.set('type', debugPuzzleType); // // Show the debug indicator // const debugIndicator = document.getElementById('debug-indicator'); // const debugTypeDisplay = document.getElementById('debug-type-display'); // if (debugIndicator && debugTypeDisplay) { // debugTypeDisplay.textContent = debugPuzzleType; // debugIndicator.style.display = 'block'; // } // } else { // url.searchParams.delete('type'); // // Hide the debug indicator // const debugIndicator = document.getElementById('debug-indicator'); // if (debugIndicator) { // debugIndicator.style.display = 'none'; // } // } // window.history.pushState({}, '', url); // // Load a new puzzle with the selected type // loadNewPuzzle(); // }); // // Initialize the debug indicator if there's a type parameter // if (debugPuzzleType) { // const debugIndicator = document.getElementById('debug-indicator'); // const debugTypeDisplay = document.getElementById('debug-type-display'); // if (debugIndicator && debugTypeDisplay) { // debugTypeDisplay.textContent = debugPuzzleType; // debugIndicator.style.display = 'block'; // } // } // // Add elements to container // debugContainer.appendChild(label); // debugContainer.appendChild(select); // debugContainer.appendChild(applyBtn); // // Add container to the benchmark stats section // const benchmarkStats = document.querySelector('.benchmark-stats'); // benchmarkStats.parentNode.insertBefore(debugContainer, benchmarkStats.nextSibling); // } // // Function to set up the debug mode selector function loadNewPuzzle() { // Reset state clickCoordinates = null; processingClick = false; currentRotationAngle = 0; selectedCells = []; bingoSelectedCells = []; // Remove any click markers and debug areas const existingMarker = document.querySelector('.click-marker'); if (existingMarker) { existingMarker.remove(); } const existingArea = document.querySelector('.debug-area'); if (existingArea) { existingArea.remove(); } // Remove any rotation controls const existingControls = document.querySelector('.rotation-controls'); if (existingControls) { existingControls.remove(); } // Remove any existing rotation submit buttons const existingRotationSubmit = document.querySelector('.rotation-submit'); if (existingRotationSubmit) { existingRotationSubmit.remove(); } // Remove any slider components and submit buttons const existingSliderSubmit = document.querySelector('.slider-submit'); if (existingSliderSubmit) { existingSliderSubmit.remove(); } // Remove any unusual detection grid and submit buttons const existingUnusualSubmit = document.querySelector('.unusual-submit'); if (existingUnusualSubmit) { existingUnusualSubmit.remove(); } // Remove any image recognition grid and submit buttons const existingImageRecognitionSubmit = document.querySelector('.image-recognition-submit'); if (existingImageRecognitionSubmit) { existingImageRecognitionSubmit.remove(); } // Remove any bingo grid and submit buttons const existingBingoSubmit = document.querySelector('.bingo-submit'); if (existingBingoSubmit) { existingBingoSubmit.remove(); } // After checking and removing existingImageMatchingControls const existingImageMatchingControls = document.querySelector('.image-matching-controls'); if (existingImageMatchingControls) { existingImageMatchingControls.remove(); } const existingImageMatchingSubmit = document.querySelector('.image-matching-submit'); if (existingImageMatchingSubmit) { existingImageMatchingSubmit.remove(); } // Remove any dart count controls and submit buttons const existingDartCountSubmit = document.querySelector('.dart-count-submit'); if (existingDartCountSubmit) { existingDartCountSubmit.remove(); } // Remove any object match controls and submit buttons const existingObjectMatchSubmit = document.querySelector('.object-match-submit'); if (existingObjectMatchSubmit) { existingObjectMatchSubmit.remove(); } // Remove any connect icon controls and submit buttons const existingConnectIconSubmit = document.querySelector('.connect-icon-submit'); if (existingConnectIconSubmit) { existingConnectIconSubmit.remove(); } // Remove any hold button components const existingHoldButton = document.querySelector('.hold-button-container'); if (existingHoldButton) { existingHoldButton.remove(); } // Reset the puzzle prompt and image puzzlePrompt.textContent = 'Loading puzzle...'; resultMessage.textContent = ''; resultMessage.className = 'result-message'; // Reset the submit button text submitBtn.textContent = 'Submit'; submitBtn.disabled = false; // Reset input field display userAnswerInput.style.display = 'block'; // Construct URL with debug type parameter if set let url = '/api/get_puzzle?mode=sequential'; // // Function to set up the debug mode selector // if (debugPuzzleType) { // url = `/api/get_puzzle?debug_type=${encodeURIComponent(debugPuzzleType)}`; // } // Get a random puzzle from any available type fetch(url) .then(response => response.json()) .then(data => { console.log("Received puzzle data:", data); currentPuzzle = data; // Update the puzzle prompt if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else if (data.puzzle_type === 'Dice_Count') { puzzlePrompt.textContent = "Sum up the numbers on all the dice"; } // Important: Always display difficulty stars based on puzzle type displayDifficultyStars(data.puzzle_type); // Reset container puzzleImageContainer.innerHTML = ''; // Configure input based on puzzle type if (data.input_type === 'click') { // Setup for click-based CAPTCHAs (Geometry_Click, Misleading_Click, Pick_Area) puzzleImage.src = data.image_path; inputGroup.style.display = 'none'; puzzleImage.style.cursor = 'pointer'; puzzleImage.classList.add('clickable'); // Add puzzle image back to container if (puzzleImageContainer.innerHTML === '') { puzzleImageContainer.appendChild(puzzleImage); } puzzleImageContainer.style.display = 'block'; puzzleImage.style.display = 'block'; // Reset click coordinates for new puzzle clickCoordinates = null; // Update prompt text if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else if (data.puzzle_type === 'Geometry_Click') { puzzlePrompt.textContent = "Click on the geometric shape"; } else if (data.puzzle_type === 'Misleading_Click') { puzzlePrompt.textContent = "Click the image to continue"; // Make sure avoid_area is stored in currentPuzzle object if (data.avoid_area) { currentPuzzle.avoid_area = data.avoid_area; console.log('Loaded avoid_area:', data.avoid_area); } } else if (data.puzzle_type === 'Pick_Area') { puzzlePrompt.textContent = "Click on the largest area outlined by the dotted line"; } // For debugging, when image loads, show the target areas puzzleImage.onload = () => { if (DEBUG_MODE) { // Show ground truth area differently based on puzzle type if (data.puzzle_type === 'Pick_Area') { showPickAreaTargets(puzzleImageContainer); } else if (data.puzzle_type === 'Geometry_Click') { fetchAndShowGeometryClickArea(puzzleImageContainer); } else if (data.puzzle_type === 'Misleading_Click') { // For misleading click, show the area to avoid if (data.avoid_area) { showMisleadingClickArea(puzzleImageContainer, data.avoid_area); } } } }; } else if (data.input_type === 'rotation') { // Setup for rotation-based CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt first to ensure it's from the rotation puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Use the arrows to rotate the object to match the reference direction."; } // Set up rotation interface setupRotationControls(); // Auto-show submit button for rotation puzzles const submitSection = document.createElement('div'); submitSection.className = 'rotation-submit'; const rotateSubmitBtn = document.createElement('button'); rotateSubmitBtn.textContent = 'Submit'; rotateSubmitBtn.className = 'submit-rotation'; rotateSubmitBtn.addEventListener('click', submitAnswer); submitSection.appendChild(rotateSubmitBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); imageWrapper.appendChild(submitSection); } else if (data.input_type === 'slide') { // Setup for slide-based CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt for the slide puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Drag the slider component to the correct position."; } // Set up sliding puzzle interface setupSlidePuzzle(); } else if (data.input_type === 'multiselect') { // Setup for unusual detection CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt for the unusual detection puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Select the unusual items in the image."; } // Set up unusual detection grid setupUnusualDetectionGrid(); } else if (data.input_type === 'image_grid') { // Setup for image recognition CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt for the image recognition puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else if (data.question) { puzzlePrompt.textContent = data.question; } else { puzzlePrompt.textContent = "Select all images that match the description."; } // Set up image recognition grid setupImageRecognition(); } else if (data.input_type === 'bingo_swap') { // Setup for Bingo swap CAPTCHA inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt for the Bingo puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Please click two images to exchange their position to line up the same images to a line, you can only exchange the images once."; } // Set up Bingo grid setupBingoSwap(); } else if (data.input_type === 'image_matching') { // Setup for Image Matching CAPTCHA inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt for the Image Matching puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Using the arrows, match the animal in the left and right image."; } // Set up Image Matching interface setupImageMatching(); } else if (data.input_type === 'patch_select') { // Hide standard input display but keep it for value storage userAnswerInput.style.display = 'none'; // Customize submit button submitBtn.textContent = 'Verify'; submitBtn.style.display = 'block'; // Setup patch selection grid setupPatchSelectGrid(); } else if (data.input_type === 'dart_count') { // Hide standard input display but keep it for value storage userAnswerInput.style.display = 'none'; inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt for the dart count puzzle if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Use the arrows to find the darts that add up to the target number."; } // Debug log console.log('Setting up Dart Count puzzle with data:', data); // Setup dart count interface setupDartCount(); } else if (data.input_type === 'select_animal') { // Hide standard input display but keep it for value storage userAnswerInput.style.display = 'none'; // Customize submit button submitBtn.textContent = 'Submit'; submitBtn.style.display = 'block'; // Setup animal selection grid setupSelectAnimalGrid(); } else if (data.input_type === 'object_match') { // Setup for object match puzzles inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Use the arrows to change the number of objects until it matches the left image."; } // Set up object match interface setupObjectMatch(); } else if (data.input_type === 'place_dot') { // Setup for Place_Dot CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Click to place a Dot at the end of the car's path"; } // Set up place dot interface setupPlaceDot(); } else if (data.input_type === 'connect_icon') { // Setup for Connect_icon CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Using the arrows, connect the same two icons with the dotted line as shown on the left."; } // Set up connect icon interface setupConnectIcon(); } else if (data.input_type === 'click_order') { // Setup for Click_Order CAPTCHAs inputGroup.style.display = 'none'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Click the icons in order as shown in the reference image."; } // Set up click order interface setupClickOrder(); } else if (data.input_type === 'hold_button') { // Setup for Hold_Button CAPTCHAs inputGroup.style.display = 'flex'; puzzleImage.style.display = 'none'; puzzleImageContainer.style.display = 'block'; // Update prompt if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else { puzzlePrompt.textContent = "Hold the button until it finishes loading."; } // Set up hold button interface setupHoldButton(); // Ensure input field and submit button are visible userAnswerInput.style.display = 'block'; submitBtn.style.display = 'inline-block'; } else { // Default for text-based CAPTCHAs puzzleImage.src = data.image_path; inputGroup.style.display = 'flex'; puzzleImage.style.cursor = 'default'; puzzleImage.classList.remove('clickable'); // Add puzzle image back to container if (puzzleImageContainer.innerHTML === '') { puzzleImageContainer.appendChild(puzzleImage); } puzzleImageContainer.style.display = 'block'; puzzleImage.style.display = 'block'; // Update prompt after clearing if (data.prompt) { puzzlePrompt.textContent = data.prompt; } else if (data.puzzle_type === 'Dice_Count') { puzzlePrompt.textContent = "Sum up the numbers on all the dice"; } // Reset submit button submitBtn.disabled = false; submitBtn.textContent = 'Submit'; // Clear and focus input userAnswerInput.value = ''; userAnswerInput.focus(); // Set input type based on puzzle type if (data.input_type === 'number') { userAnswerInput.setAttribute('type', 'number'); userAnswerInput.setAttribute('placeholder', 'Enter the sum'); } else { userAnswerInput.setAttribute('type', 'text'); userAnswerInput.setAttribute('placeholder', 'Your answer'); } // Ensure the input is visible userAnswerInput.style.display = 'block'; } }) .catch(error => { console.error('Error loading puzzle:', error); // Try again after a delay if there was an error setTimeout(loadNewPuzzle, 3000); }); } // Function to create fireworks effect for correct answers function createFireworks() { // Create container for fireworks const fireworksContainer = document.createElement('div'); fireworksContainer.className = 'fireworks-container'; document.body.appendChild(fireworksContainer); // Create happy face animation const happyFaceContainer = document.createElement('div'); happyFaceContainer.className = 'happy-face-container'; happyFaceContainer.textContent = '😄'; happyFaceContainer.style.zIndex = '10000'; // Ensure it's above everything document.body.appendChild(happyFaceContainer); // Create multiple fireworks at random positions const colors = [ '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', '#FFA500', '#FF4500', '#FFD700', '#32CD32', '#8A2BE2', '#FF69B4' ]; // Create more fireworks (150 instead of 100) for (let i = 0; i < 150; i++) { const firework = document.createElement('div'); firework.className = 'firework'; // Random position - spread across the screen, with more concentration near center const centerBias = Math.random() > 0.7; // 30% chance to be centered const x = centerBias ? window.innerWidth/2 + (Math.random() - 0.5) * window.innerWidth/2 : Math.random() * window.innerWidth; const y = centerBias ? window.innerHeight/2 + (Math.random() - 0.5) * window.innerHeight/2 : Math.random() * window.innerHeight; // Random color const color = colors[Math.floor(Math.random() * colors.length)]; // Random size (larger particles) const size = 5 + Math.random() * 8; // Random delay and duration const delay = Math.random() * 1.5; const duration = 0.8 + Math.random() * 1.2; // Apply styles firework.style.left = `${x}px`; firework.style.top = `${y}px`; firework.style.backgroundColor = color; firework.style.width = `${size}px`; firework.style.height = `${size}px`; firework.style.animationDelay = `${delay}s`; firework.style.animationDuration = `${duration}s`; // Add to container fireworksContainer.appendChild(firework); } // Remove containers after animation completes setTimeout(() => { fireworksContainer.remove(); happyFaceContainer.remove(); }, 3500); } // Function to create sad face effect for incorrect answers function createSadFace() { // Create container for sad face const sadFaceContainer = document.createElement('div'); sadFaceContainer.className = 'sad-face-container'; sadFaceContainer.textContent = '😢'; document.body.appendChild(sadFaceContainer); // Remove container after animation completes setTimeout(() => { sadFaceContainer.remove(); }, 2000); } function submitAnswer() { // Don't submit if there's no input for number/text input types if ((currentPuzzle.input_type === 'number' || currentPuzzle.input_type === 'text') && !userAnswerInput.value.trim()) { // Don't submit empty answers for number/text inputs return; } // Disable submit button to prevent double submissions submitBtn.disabled = true; submitBtn.textContent = 'Processing...'; let answerData = { puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }; // Handle different input types if (currentPuzzle.input_type === 'click' && clickCoordinates) { // For click input, send the click coordinates answerData.answer = clickCoordinates; } else if (currentPuzzle.input_type === 'rotation') { // For rotation input, send the current rotation angle answerData.answer = currentRotationAngle; } else if (currentPuzzle.input_type === 'slide') { // For slide puzzle, calculate the current position of the slider const sliderComponent = document.querySelector('.slider-component'); if (sliderComponent) { // Get the current position (from CSS left/top values) const currentX = parseInt(sliderComponent.style.left) || 0; const currentY = parseInt(sliderComponent.style.top) || 0; // Add slider position to answer data answerData.answer = [currentX, currentY]; } else { console.error('Slider component not found'); // Re-enable submit button submitBtn.disabled = false; submitBtn.textContent = 'Check Position'; return; } } else if (currentPuzzle.input_type === 'multiselect') { // For multiselect input, send the selected cell indices answerData.answer = selectedCells; } else if (currentPuzzle.input_type === 'image_grid') { // For image grid selection, send the selected image indices answerData.answer = selectedCells; } else if (currentPuzzle.input_type === 'bingo_swap') { // For bingo swap, send the selected cells to swap answerData.answer = bingoSelectedCells; } else if (currentPuzzle.input_type === 'image_matching') { // For image matching, send the current option index const currentOptionIndex = currentPuzzle.current_option_index || 0; answerData.answer = currentOptionIndex; } else if (currentPuzzle.input_type === 'dart_count') { // For dart count, send the selected option index const selectedIndex = parseInt(userAnswerInput.value); answerData.answer = selectedIndex; } else if (currentPuzzle.input_type === 'patch_select') { // For patch select, send the selected patch indices try { // Try to parse the JSON value from the input const parsedSelection = JSON.parse(userAnswerInput.value); // If parsed array is empty but global selectedCells is not, use global if (parsedSelection.length === 0 && selectedCells.length > 0) { answerData.answer = selectedCells; } else { answerData.answer = parsedSelection; } } catch (error) { console.error('Error parsing selected patches:', error); // Fallback to the global array if parsing fails answerData.answer = selectedCells; } } else if (currentPuzzle.input_type === 'select_animal') { // For select animal, send the selected animal index try { // If the value is empty, use the global selectedAnimalIndex if (userAnswerInput.value === '[]' || userAnswerInput.value.trim() === '') { answerData.answer = selectedAnimalIndex >= 0 ? [selectedAnimalIndex] : []; } else { // Otherwise parse the JSON from the input const selectedAnimal = JSON.parse(userAnswerInput.value); answerData.answer = selectedAnimal; } } catch (error) { console.error('Error parsing selected animal:', error); // Use the global variable as a fallback answerData.answer = selectedAnimalIndex >= 0 ? [selectedAnimalIndex] : []; } } else if (currentPuzzle.input_type === 'object_match') { // For object match, send the selected option index const selectedIndex = parseInt(puzzleImageContainer.dataset.currentOptionIndex); answerData.answer = selectedIndex; } else if (currentPuzzle.input_type === 'place_dot') { // For place_dot input, send the click coordinates if (!clickCoordinates) { console.error('No dot coordinates found'); // Re-enable submit button submitBtn.disabled = false; submitBtn.textContent = 'Submit'; return; } answerData.answer = clickCoordinates; } else if (currentPuzzle.input_type === 'connect_icon') { // For connect_icon, send the current option index answerData.answer = parseInt(userAnswerInput.value) || 0; } else if (currentPuzzle.input_type === 'hold_button') { // For hold button, get the elapsed time from the input field answerData.answer = parseFloat(userAnswerInput.value) || 0; answerData.elapsed_time = ((Date.now() - puzzleStartTime) / 1000).toFixed(2); } else { // For text/number inputs, use the input value answerData.answer = userAnswerInput.value.trim(); } // Send answer to server for verification fetch('/api/check_answer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(answerData) }) .then(response => response.json()) .then(data => { // Update stats benchmarkStats.total++; if (data.correct) { benchmarkStats.correct++; resultMessage.textContent = 'Correct!'; resultMessage.className = 'result-message correct'; // Create fireworks effect for correct answer createFireworks(); } else { // Just show "Incorrect" without revealing the correct answer resultMessage.textContent = 'Incorrect.'; resultMessage.className = 'result-message incorrect'; // Create sad face effect for incorrect answer createSadFace(); } updateStats(); // Record benchmark result recordBenchmarkResult({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id, user_answer: answerData.answer, correct_answer: data.correct_answer, correct: data.correct }); // Disable the submit button after submission if (currentPuzzle.input_type !== 'click') { submitBtn.disabled = true; // Also disable rotation submit button if it exists const rotateSubmitBtn = document.querySelector('.submit-rotation'); if (rotateSubmitBtn) { rotateSubmitBtn.disabled = true; } // Also disable image recognition submit button if it exists const imageRecognitionSubmitBtn = document.querySelector('.submit-image-recognition'); if (imageRecognitionSubmitBtn) { imageRecognitionSubmitBtn.disabled = true; } // Also disable bingo submit button if it exists const bingoSubmitBtn = document.querySelector('.submit-bingo'); if (bingoSubmitBtn) { bingoSubmitBtn.disabled = true; } // Also disable image matching submit button if it exists const imageMatchingSubmitBtn = document.querySelector('.submit-image-matching'); if (imageMatchingSubmitBtn) { imageMatchingSubmitBtn.disabled = true; } } // After handling the result and before loading a new puzzle setTimeout(() => { // Reset the submit button text before loading new puzzle submitBtn.textContent = 'Submit'; // Make sure we reset input visibility before loading a new puzzle userAnswerInput.style.display = 'block'; loadNewPuzzle(); }, 2000); }) .catch(error => { console.error('Error checking answer:', error); resultMessage.textContent = 'Error checking answer. Please try again.'; resultMessage.className = 'result-message incorrect'; // Re-enable the submit button on error submitBtn.disabled = false; submitBtn.textContent = 'Submit'; }); } function updateStats() { totalCount.textContent = benchmarkStats.total; correctCount.textContent = benchmarkStats.correct; const accuracy = benchmarkStats.total > 0 ? ((benchmarkStats.correct / benchmarkStats.total) * 100).toFixed(1) : '0.0'; accuracyEl.textContent = `${accuracy}%`; } function recordBenchmarkResult(result) { // Ensure we have the timestamp field if (!result.timestamp) { result.timestamp = new Date().toISOString(); } // Send the benchmark result to be recorded fetch('/api/benchmark_results', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(result) }) .then(response => response.json()) .then(data => { console.log('Benchmark result recorded:', data); }) .catch(error => { console.error('Error recording benchmark result:', error); }); } // Auto-start benchmark when page loads loadNewPuzzle(); // Function to update position display for the slider function updateSliderPositionDisplay(x, y, componentWidth, componentHeight) { // Remove any existing position display const existingDisplay = document.querySelector('.slider-position-display'); if (existingDisplay) { existingDisplay.remove(); } if (!DEBUG_MODE) return; // Calculate center point const centerX = x + (componentWidth / 2); const centerY = y + (componentHeight / 2); // Create the position display element const posDisplay = document.createElement('div'); posDisplay.className = 'slider-position-display'; posDisplay.textContent = `Position: (${Math.round(centerX)}, ${Math.round(centerY)})`; posDisplay.style.position = 'fixed'; posDisplay.style.top = '10px'; posDisplay.style.right = '10px'; posDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; posDisplay.style.color = 'white'; posDisplay.style.padding = '5px 10px'; posDisplay.style.borderRadius = '4px'; posDisplay.style.fontSize = '12px'; posDisplay.style.zIndex = '1000'; // Add to document body document.body.appendChild(posDisplay); } // Function to set up image recognition grid function setupImageRecognition() { // Remove any existing grid const existingGrid = document.querySelector('.image-recognition-grid'); if (existingGrid) { existingGrid.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Get the grid dimensions const gridSize = currentPuzzle.grid_size || [3, 3]; // Default to 3x3 grid const [rows, cols] = gridSize; // Create the grid container const gridContainer = document.createElement('div'); gridContainer.className = 'image-recognition-grid'; gridContainer.style.display = 'grid'; gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; gridContainer.style.gridTemplateRows = `repeat(${rows}, 1fr)`; gridContainer.style.gap = '5px'; gridContainer.style.width = '100%'; gridContainer.style.aspectRatio = `${cols} / ${rows}`; // Get the list of images const images = currentPuzzle.images || []; // Create individual cells for each image for (let i = 0; i < images.length; i++) { const cell = document.createElement('div'); cell.className = 'grid-cell'; cell.dataset.index = i; cell.style.position = 'relative'; cell.style.border = '2px solid #333'; cell.style.cursor = 'pointer'; cell.style.overflow = 'hidden'; // Create image element const img = document.createElement('img'); img.src = images[i]; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; img.style.display = 'block'; cell.appendChild(img); // Create an overlay for selection state const overlay = document.createElement('div'); overlay.className = 'cell-overlay'; overlay.style.position = 'absolute'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(0, 120, 255, 0.5)'; overlay.style.opacity = '0'; overlay.style.transition = 'opacity 0.2s ease'; overlay.style.pointerEvents = 'none'; cell.appendChild(overlay); // Add a checkmark icon to indicate selection const checkmark = document.createElement('div'); checkmark.className = 'checkmark'; checkmark.innerHTML = '✓'; checkmark.style.position = 'absolute'; checkmark.style.top = '50%'; checkmark.style.left = '50%'; checkmark.style.transform = 'translate(-50%, -50%)'; checkmark.style.color = 'white'; checkmark.style.fontSize = '32px'; checkmark.style.fontWeight = 'bold'; checkmark.style.opacity = '0'; checkmark.style.transition = 'opacity 0.2s ease'; checkmark.style.pointerEvents = 'none'; cell.appendChild(checkmark); // Add click handler for selection cell.addEventListener('click', (e) => { toggleCellSelection(i, cell); }); // Add the cell to the grid gridContainer.appendChild(cell); } // Add the grid to the puzzle image container puzzleImageContainer.appendChild(gridContainer); // Add a submit button below the grid const submitSection = document.createElement('div'); submitSection.className = 'image-recognition-submit'; submitSection.style.textAlign = 'center'; submitSection.style.marginTop = '15px'; const submitBtn = document.createElement('button'); submitBtn.textContent = 'Submit'; submitBtn.className = 'submit-image-recognition'; submitBtn.addEventListener('click', submitAnswer); submitSection.appendChild(submitBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); imageWrapper.appendChild(submitSection); // Reset selected cells selectedCells = []; } // Function to set up Image Matching puzzle function setupImageMatching() { // Remove any existing controls first const existingControls = document.querySelector('.image-matching-controls'); if (existingControls) { existingControls.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Create a container for the reference image const referenceContainer = document.createElement('div'); referenceContainer.className = 'reference-image-container'; const referenceImg = document.createElement('img'); referenceImg.id = 'reference-image'; referenceImg.src = currentPuzzle.reference_image; referenceImg.alt = 'Reference image'; referenceContainer.appendChild(referenceImg); // Create a container for the option image const optionContainer = document.createElement('div'); optionContainer.className = 'option-image-container'; const optionImg = document.createElement('img'); optionImg.id = 'option-image'; optionImg.src = currentPuzzle.option_images[0]; // Start with the first option optionImg.alt = 'Option image'; optionContainer.appendChild(optionImg); // Create a two-column layout for image matching puzzle const matchingLayout = document.createElement('div'); matchingLayout.className = 'matching-layout'; matchingLayout.appendChild(referenceContainer); matchingLayout.appendChild(optionContainer); // Replace the existing puzzle image puzzleImageContainer.innerHTML = ''; puzzleImageContainer.appendChild(matchingLayout); // Create navigation controls const navControls = document.createElement('div'); navControls.className = 'image-matching-controls'; // Create left navigation button const leftBtn = document.createElement('button'); leftBtn.className = 'navigate-left'; leftBtn.innerHTML = '◀'; // Left arrow leftBtn.setAttribute('aria-label', 'Previous image'); // Create right navigation button const rightBtn = document.createElement('button'); rightBtn.className = 'navigate-right'; rightBtn.innerHTML = '▶'; // Right arrow rightBtn.setAttribute('aria-label', 'Next image'); // Create indicator dots const indicatorContainer = document.createElement('div'); indicatorContainer.className = 'indicator-dots'; for (let i = 0; i < currentPuzzle.option_images.length; i++) { const dot = document.createElement('span'); dot.className = i === 0 ? 'dot active' : 'dot'; indicatorContainer.appendChild(dot); } // Add buttons and indicators to controls navControls.appendChild(leftBtn); navControls.appendChild(indicatorContainer); navControls.appendChild(rightBtn); // Add to puzzle container const imageWrapper = document.querySelector('.puzzle-image-wrapper'); imageWrapper.appendChild(navControls); // Add event listeners for navigation buttons let currentIndex = 0; leftBtn.addEventListener('click', () => { currentIndex = (currentIndex - 1 + currentPuzzle.option_images.length) % currentPuzzle.option_images.length; updateOptionImage(); }); rightBtn.addEventListener('click', () => { currentIndex = (currentIndex + 1) % currentPuzzle.option_images.length; updateOptionImage(); }); function updateOptionImage() { // Update the option image optionImg.src = currentPuzzle.option_images[currentIndex]; // Update the indicator dots const dots = indicatorContainer.querySelectorAll('.dot'); dots.forEach((dot, i) => { dot.className = i === currentIndex ? 'dot active' : 'dot'; }); // Update the current index in the puzzle data currentPuzzle.current_option_index = currentIndex; } // Auto-show submit button for image matching puzzles const submitSection = document.createElement('div'); submitSection.className = 'image-matching-submit'; const matchingSubmitBtn = document.createElement('button'); matchingSubmitBtn.textContent = 'Submit'; matchingSubmitBtn.className = 'submit-image-matching'; matchingSubmitBtn.addEventListener('click', submitAnswer); submitSection.appendChild(matchingSubmitBtn); // Add to puzzle container imageWrapper.appendChild(submitSection); } // Function to set up patch selection grid function setupPatchSelectGrid() { // Remove any existing grid first const existingGrid = document.querySelector('.patch-select-grid'); if (existingGrid) { existingGrid.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // IMPORTANT: Reset the global selectedCells array to fix the bug // when encountering these puzzles multiple times selectedCells = []; // Create a container for the patch select grid const gridContainer = document.createElement('div'); gridContainer.className = 'patch-select-grid'; // Get grid dimensions from the puzzle data const gridSize = currentPuzzle.grid_size || [6, 6]; const rows = gridSize[0]; const cols = gridSize[1]; // Set grid styles gridContainer.style.display = 'grid'; gridContainer.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; gridContainer.style.gridTemplateRows = `repeat(${rows}, 1fr)`; gridContainer.style.gap = '3px'; gridContainer.style.width = '100%'; gridContainer.style.aspectRatio = `${cols}/${rows}`; gridContainer.style.position = 'relative'; // Create image container const imageContainer = document.createElement('div'); imageContainer.className = 'patch-select-image-container'; imageContainer.style.position = 'absolute'; imageContainer.style.top = '0'; imageContainer.style.left = '0'; imageContainer.style.width = '100%'; imageContainer.style.height = '100%'; imageContainer.style.zIndex = '0'; // Add the puzzle image const img = document.createElement('img'); img.src = currentPuzzle.image_path; img.alt = 'CAPTCHA image'; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; imageContainer.appendChild(img); // Add image container to grid container gridContainer.appendChild(imageContainer); // Create grid cells for selection // Use the global selectedCells array directly for (let i = 0; i < rows * cols; i++) { const cell = document.createElement('div'); cell.className = 'patch-select-cell'; cell.dataset.index = i; cell.style.position = 'relative'; cell.style.zIndex = '1'; cell.style.cursor = 'pointer'; // Add a checkmark icon to indicate selection const checkmark = document.createElement('div'); checkmark.className = 'checkmark'; checkmark.innerHTML = '✓'; checkmark.style.position = 'absolute'; checkmark.style.top = '50%'; checkmark.style.left = '50%'; checkmark.style.transform = 'translate(-50%, -50%)'; checkmark.style.color = 'white'; checkmark.style.fontSize = '32px'; checkmark.style.fontWeight = 'bold'; checkmark.style.opacity = '0'; checkmark.style.transition = 'opacity 0.2s ease'; checkmark.style.pointerEvents = 'none'; checkmark.style.textShadow = '1px 1px 3px rgba(0, 0, 0, 0.7)'; checkmark.style.zIndex = '3'; cell.appendChild(checkmark); // Add click event to toggle selection cell.addEventListener('click', () => { // Toggle selection if (cell.classList.contains('selected')) { cell.classList.remove('selected'); // Hide checkmark checkmark.style.opacity = '0'; // Remove from selected array const index = selectedCells.indexOf(i); if (index > -1) { selectedCells.splice(index, 1); } } else { cell.classList.add('selected'); // Show checkmark checkmark.style.opacity = '1'; // Add to selected array selectedCells.push(i); } // Update the answer in the UI userAnswerInput.value = JSON.stringify(selectedCells); // Enable the submit button when squares are selected submitBtn.disabled = false; // Log selected patches for debugging console.log('Selected patches:', selectedCells); }); gridContainer.appendChild(cell); } // Add the grid to the puzzle container puzzleImageContainer.appendChild(gridContainer); // Update the prompt to include the target object puzzlePrompt.textContent = `Select all squares with ${currentPuzzle.target_object}`; // Hide the regular input and replace with verify button userAnswerInput.style.display = 'none'; submitBtn.textContent = 'Verify'; submitBtn.style.display = 'inline-block'; // Changed to inline-block inputGroup.style.display = 'flex'; submitBtn.disabled = false; // Ensure the button is enabled // Clear any previous answer userAnswerInput.value = '[]'; } // Function to set up Select_Animal grid function setupSelectAnimalGrid() { // Remove any existing grid first const existingGrid = document.querySelector('.animal-select-grid'); if (existingGrid) { existingGrid.remove(); } // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // IMPORTANT: Reset the selectedAnimalIndex to -1 to fix the bug when encountering this puzzle multiple times selectedAnimalIndex = -1; // Create a simple container directly const container = document.createElement('div'); container.style.width = '100%'; container.style.maxWidth = '800px'; container.style.margin = '0 auto'; container.style.position = 'relative'; // Display the image directly const img = document.createElement('img'); img.src = currentPuzzle.image_path; img.alt = 'CAPTCHA image with animals'; img.style.width = '100%'; img.style.display = 'block'; img.style.border = '2px solid #ccc'; img.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; container.appendChild(img); // Get grid dimensions from the puzzle data const gridSize = currentPuzzle.grid_size || [2, 3]; const rows = gridSize[0]; const cols = gridSize[1]; // Wait for image to load to ensure dimensions are available img.onload = function() { // Create overlay grid that matches the image dimensions const grid = document.createElement('div'); grid.style.position = 'absolute'; grid.style.top = '0'; grid.style.left = '0'; grid.style.width = '100%'; grid.style.height = '100%'; grid.style.display = 'grid'; grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`; grid.style.gridTemplateRows = `repeat(${rows}, 1fr)`; // IMPORTANT: Create a fresh selectedAnimal object with -1 index to fix the bug // when encountering these puzzles multiple times const selectedAnimal = { index: -1 }; for (let i = 0; i < rows * cols; i++) { const cell = document.createElement('div'); cell.style.border = '1px solid rgba(255, 255, 255, 0.3)'; cell.style.cursor = 'pointer'; cell.style.position = 'relative'; cell.style.transition = 'all 0.2s ease'; // Add hover effect cell.addEventListener('mouseover', () => { cell.style.backgroundColor = 'rgba(76, 175, 80, 0.2)'; cell.style.border = '1px solid rgba(76, 175, 80, 0.7)'; }); cell.addEventListener('mouseout', () => { if (selectedAnimal.index !== i) { cell.style.backgroundColor = 'transparent'; cell.style.border = '1px solid rgba(255, 255, 255, 0.3)'; } }); // Add click event to toggle selection cell.addEventListener('click', () => { // Clear previous selection grid.querySelectorAll('div').forEach((c, index) => { if (index !== i) { c.style.backgroundColor = 'transparent'; c.style.border = '1px solid rgba(255, 255, 255, 0.3)'; } }); // Update selection selectedAnimal.index = i; selectedAnimalIndex = i; // Update the global variable cell.style.backgroundColor = 'rgba(76, 175, 80, 0.3)'; cell.style.border = '2px solid rgba(76, 175, 80, 0.9)'; // Update the answer in the UI userAnswerInput.value = JSON.stringify([i]); // Enable the submit button submitBtn.disabled = false; // Log selected animal for debugging console.log('Selected animal at index:', i); }); grid.appendChild(cell); } // Add the grid to the container container.appendChild(grid); }; // Add the container to the puzzle container puzzleImageContainer.appendChild(container); // Make sure the prompt is clearly visible puzzlePrompt.style.fontSize = '20px'; puzzlePrompt.style.fontWeight = 'bold'; puzzlePrompt.style.marginBottom = '20px'; // Update the prompt to include the target animal puzzlePrompt.textContent = `Pick a ${currentPuzzle.target_object}`; // Hide the regular input and replace with verify button userAnswerInput.style.display = 'none'; submitBtn.textContent = 'Submit'; submitBtn.style.display = 'inline-block'; inputGroup.style.display = 'flex'; submitBtn.disabled = true; // Disabled until selection is made // Clear any previous answer userAnswerInput.value = '[]'; } /** * Setup the Object Match interface with reference image and option controls */ function setupObjectMatch() { // Create container for the object match interface const matchContainer = document.createElement('div'); matchContainer.className = 'object-match-container'; // Create a horizontal layout const horizontalLayout = document.createElement('div'); horizontalLayout.className = 'object-match-horizontal-layout'; // Create reference image container const referenceContainer = document.createElement('div'); referenceContainer.className = 'object-match-reference'; // Add reference image const referenceImage = document.createElement('img'); referenceImage.src = currentPuzzle.reference_image || currentPuzzle.additional_data.reference_image; referenceImage.alt = 'Reference Image'; referenceImage.className = 'object-match-reference-img'; referenceContainer.appendChild(referenceImage); // Add reference caption const referenceCaption = document.createElement('div'); referenceCaption.className = 'object-match-caption'; referenceCaption.textContent = 'Match This!'; referenceContainer.appendChild(referenceCaption); // Create options container const optionsContainer = document.createElement('div'); optionsContainer.className = 'object-match-options'; // Add option image const optionImage = document.createElement('img'); const optionImages = currentPuzzle.option_images || currentPuzzle.additional_data.option_images; optionImage.src = optionImages[0]; // Start with first option optionImage.alt = 'Option Image'; optionImage.className = 'object-match-option-img'; optionsContainer.appendChild(optionImage); // Create navigation controls const controlsContainer = document.createElement('div'); controlsContainer.className = 'object-match-controls'; // Left arrow const leftArrow = document.createElement('button'); leftArrow.innerHTML = '←'; leftArrow.className = 'object-match-arrow left-arrow'; leftArrow.addEventListener('click', () => updateObjectOption(-1)); // Right arrow const rightArrow = document.createElement('button'); rightArrow.innerHTML = '→'; rightArrow.className = 'object-match-arrow right-arrow'; rightArrow.addEventListener('click', () => updateObjectOption(1)); // Add arrows to controls controlsContainer.appendChild(leftArrow); controlsContainer.appendChild(rightArrow); // Add controls to options container optionsContainer.appendChild(controlsContainer); // Add reference and options to horizontal layout horizontalLayout.appendChild(referenceContainer); horizontalLayout.appendChild(optionsContainer); // Add horizontal layout to main container matchContainer.appendChild(horizontalLayout); // Add option indicators (dots) const indicators = document.createElement('div'); indicators.className = 'object-match-indicators'; const numOptions = optionImages.length; for (let i = 0; i < numOptions; i++) { const dot = document.createElement('span'); dot.className = 'object-match-dot'; if (i === 0) { dot.classList.add('active'); } indicators.appendChild(dot); } // Add indicators to main container matchContainer.appendChild(indicators); // Add submit button const submitBtn = document.createElement('button'); submitBtn.textContent = 'Submit'; submitBtn.className = 'object-match-submit'; submitBtn.addEventListener('click', submitAnswer); // Add containers to puzzle image container puzzleImageContainer.appendChild(matchContainer); puzzleImageContainer.appendChild(submitBtn); // Store current index in data attribute puzzleImageContainer.dataset.currentOptionIndex = '0'; // Log for debugging console.log('Object Match images:', { reference: referenceImage.src, options: optionImages }); } /** * Update the displayed option image based on navigation direction * @param {number} direction - Direction to navigate (-1 for left, 1 for right) */ function updateObjectOption(direction) { const container = document.querySelector('.object-match-container'); const optionImage = document.querySelector('.object-match-option-img'); const dots = document.querySelectorAll('.object-match-dot'); // Get current index let currentIndex = parseInt(puzzleImageContainer.dataset.currentOptionIndex); const optionImages = currentPuzzle.option_images || currentPuzzle.additional_data.option_images; const numOptions = optionImages.length; // Calculate new index with wrap-around let newIndex = (currentIndex + direction + numOptions) % numOptions; // Update the option image optionImage.src = optionImages[newIndex]; // Update dots dots.forEach((dot, index) => { if (index === newIndex) { dot.classList.add('active'); } else { dot.classList.remove('active'); } }); // Store new index puzzleImageContainer.dataset.currentOptionIndex = newIndex.toString(); // Store selected answer for submission userAnswerInput.value = newIndex.toString(); // Log for debugging console.log('Updated option image:', { index: newIndex, src: optionImage.src }); } /** * Setup the Place_Dot interface allowing the user to click on the image to place a dot */ function setupPlaceDot() { // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Create a container for the image with relative positioning const container = document.createElement('div'); container.style.position = 'relative'; container.style.width = '100%'; container.style.maxWidth = '800px'; container.style.margin = '0 auto'; // Create and add the image const img = document.createElement('img'); img.src = `/captcha_data/${currentPuzzle.puzzle_type}/${currentPuzzle.puzzle_id}`; img.alt = 'Car path image'; img.style.width = '100%'; img.style.display = 'block'; img.style.cursor = 'crosshair'; container.appendChild(img); // Reset any previous click coordinates clickCoordinates = null; // Add click handler to the image img.addEventListener('click', (e) => { // Remove any existing dot const existingDot = container.querySelector('.place-dot-marker'); if (existingDot) { existingDot.remove(); } // Get click coordinates relative to the image const rect = e.target.getBoundingClientRect(); const x = Math.round(e.clientX - rect.left); const y = Math.round(e.clientY - rect.top); // Store coordinates for submission clickCoordinates = [x, y]; // Create dot marker const dot = document.createElement('div'); dot.className = 'place-dot-marker'; dot.style.position = 'absolute'; dot.style.width = '20px'; dot.style.height = '20px'; dot.style.borderRadius = '50%'; dot.style.backgroundColor = 'rgba(255, 0, 0, 0.7)'; dot.style.border = '2px solid #ff0000'; dot.style.left = `${x}px`; dot.style.top = `${y}px`; dot.style.transform = 'translate(-50%, -50%)'; dot.style.pointerEvents = 'none'; dot.style.zIndex = '10'; // Add animation dot.style.animation = 'pulse 1s infinite alternate'; // Add dot to container container.appendChild(dot); // Enable submit button submitBtn.disabled = false; // Log coordinates for debugging console.log('Dot placed at:', { x, y }); }); // Add the container to the puzzle container puzzleImageContainer.appendChild(container); // In debug mode, fetch the ground truth to show the target area if (DEBUG_MODE) { fetch('/api/get_ground_truth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }) }) .then(response => response.json()) .then(gtData => { // Check if we have a target position in the answer if (gtData.answer && gtData.answer.target_position) { const targetPosition = gtData.answer.target_position; const tolerance = gtData.answer.tolerance || 15; // Default to 15px showTargetDotArea(container, targetPosition, tolerance); } }) .catch(error => { console.error('Error fetching ground truth for Place_Dot:', error); }); } // Update prompt and input elements puzzlePrompt.textContent = currentPuzzle.prompt || "Click to place a Dot at the end of the car's path"; // Hide the input field and adjust the submit button userAnswerInput.style.display = 'none'; submitBtn.textContent = 'Submit'; submitBtn.disabled = true; // Disabled until user places a dot submitBtn.style.display = 'inline-block'; inputGroup.style.display = 'flex'; } /** * Show the target area for the Place_Dot puzzle in debug mode * @param {HTMLElement} container - The container element * @param {Array} targetPosition - The target position [x, y] * @param {number} tolerance - The tolerance radius in pixels */ function showTargetDotArea(container, targetPosition, tolerance = 15) { if (!DEBUG_MODE) return; // Remove any existing target visualization const existingTarget = container.querySelector('.target-dot-area'); if (existingTarget) { existingTarget.remove(); } // Get target coordinates const [targetX, targetY] = targetPosition; // Create a target element - visualized as a circle const targetArea = document.createElement('div'); targetArea.className = 'target-dot-area'; // Calculate diameter based on tolerance const diameter = tolerance * 2; // Style the target area targetArea.style.position = 'absolute'; targetArea.style.left = `${targetX - tolerance}px`; targetArea.style.top = `${targetY - tolerance}px`; targetArea.style.width = `${diameter}px`; targetArea.style.height = `${diameter}px`; targetArea.style.borderRadius = '50%'; targetArea.style.border = '2px dashed green'; targetArea.style.backgroundColor = 'rgba(0, 255, 0, 0.2)'; targetArea.style.zIndex = '5'; targetArea.style.pointerEvents = 'none'; // Allow clicks to pass through // Add coordinates label const coordsLabel = document.createElement('div'); coordsLabel.className = 'coords-label'; coordsLabel.textContent = `Target: (${targetX}, ${targetY}) ±${tolerance}px`; coordsLabel.style.position = 'absolute'; coordsLabel.style.top = '-25px'; coordsLabel.style.left = '0'; coordsLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; coordsLabel.style.color = 'white'; coordsLabel.style.padding = '2px 5px'; coordsLabel.style.fontSize = '10px'; coordsLabel.style.borderRadius = '3px'; coordsLabel.style.whiteSpace = 'nowrap'; targetArea.appendChild(coordsLabel); // Add to the container container.appendChild(targetArea); // Log the target details console.log('Place_Dot target position:', { x: targetX, y: targetY, tolerance: tolerance }); } // Function to set up connect icon interface function setupConnectIcon() { // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Create a layout container for the two-column layout const layoutContainer = document.createElement('div'); layoutContainer.className = 'connect-icon-layout'; layoutContainer.style.display = 'flex'; layoutContainer.style.justifyContent = 'space-between'; // Create container for reference image const refContainer = document.createElement('div'); refContainer.className = 'reference-image-container'; refContainer.style.flex = '1'; refContainer.style.marginRight = '10px'; refContainer.style.textAlign = 'center'; // Add "Match This!" label above reference image const matchLabel = document.createElement('div'); matchLabel.className = 'match-label'; matchLabel.textContent = 'Match This!'; matchLabel.style.backgroundColor = 'black'; matchLabel.style.color = 'white'; matchLabel.style.padding = '2px 5px'; matchLabel.style.marginBottom = '5px'; matchLabel.style.fontSize = '12px'; refContainer.appendChild(matchLabel); // Add reference image const refImg = document.createElement('img'); refImg.id = 'connect-reference-image'; refImg.src = currentPuzzle.reference_image; refImg.alt = 'Reference image'; refImg.style.maxWidth = '100%'; refImg.style.border = '1px solid #ccc'; refContainer.appendChild(refImg); // Container for option images with arrows const optionContainer = document.createElement('div'); optionContainer.className = 'connect-option-container'; optionContainer.style.flex = '1'; optionContainer.style.position = 'relative'; // Create option image display const optionImgContainer = document.createElement('div'); optionImgContainer.className = 'option-image-container'; optionImgContainer.style.textAlign = 'center'; // Create option image const optionImg = document.createElement('img'); optionImg.id = 'connect-option-image'; optionImg.src = currentPuzzle.option_images[0]; // Start with the first option optionImg.alt = 'Option image'; optionImg.style.maxWidth = '100%'; optionImg.style.border = '1px solid #ccc'; optionImgContainer.appendChild(optionImg); optionContainer.appendChild(optionImgContainer); // Add arrow navigation const arrowsContainer = document.createElement('div'); arrowsContainer.className = 'connect-arrows-container'; arrowsContainer.style.display = 'flex'; arrowsContainer.style.justifyContent = 'center'; arrowsContainer.style.marginTop = '10px'; // Left arrow const leftArrow = document.createElement('button'); leftArrow.className = 'arrow-btn left-arrow'; leftArrow.innerHTML = '←'; // Left arrow character leftArrow.setAttribute('aria-label', 'Previous option'); leftArrow.style.margin = '0 10px'; leftArrow.style.padding = '5px 15px'; leftArrow.style.fontSize = '20px'; leftArrow.style.backgroundColor = '#f0f0f0'; leftArrow.style.border = '1px solid #ccc'; leftArrow.style.borderRadius = '4px'; leftArrow.style.cursor = 'pointer'; // Right arrow const rightArrow = document.createElement('button'); rightArrow.className = 'arrow-btn right-arrow'; rightArrow.innerHTML = '→'; // Right arrow character rightArrow.setAttribute('aria-label', 'Next option'); rightArrow.style.margin = '0 10px'; rightArrow.style.padding = '5px 15px'; rightArrow.style.fontSize = '20px'; rightArrow.style.backgroundColor = '#f0f0f0'; rightArrow.style.border = '1px solid #ccc'; rightArrow.style.borderRadius = '4px'; rightArrow.style.cursor = 'pointer'; arrowsContainer.appendChild(leftArrow); arrowsContainer.appendChild(rightArrow); optionContainer.appendChild(arrowsContainer); // Add pagination dots const dotsContainer = document.createElement('div'); dotsContainer.className = 'pagination-dots'; dotsContainer.style.display = 'flex'; dotsContainer.style.justifyContent = 'center'; dotsContainer.style.marginTop = '10px'; // Create dots based on the number of options for (let i = 0; i < currentPuzzle.option_images.length; i++) { const dot = document.createElement('span'); dot.className = 'pagination-dot'; dot.style.height = '10px'; dot.style.width = '10px'; dot.style.margin = '0 5px'; dot.style.borderRadius = '50%'; dot.style.backgroundColor = i === 0 ? '#4CAF50' : '#ccc'; // Highlight first dot dotsContainer.appendChild(dot); } optionContainer.appendChild(dotsContainer); // Add all containers to the layout layoutContainer.appendChild(refContainer); layoutContainer.appendChild(optionContainer); puzzleImageContainer.appendChild(layoutContainer); // Add a submit button const submitSection = document.createElement('div'); submitSection.className = 'connect-icon-submit'; submitSection.style.textAlign = 'center'; submitSection.style.marginTop = '15px'; const submitBtn = document.createElement('button'); submitBtn.textContent = 'Submit'; submitBtn.className = 'submit-connect'; submitBtn.style.padding = '10px 20px'; submitBtn.style.backgroundColor = '#4CAF50'; submitBtn.style.color = 'white'; submitBtn.style.border = 'none'; submitBtn.style.borderRadius = '4px'; submitBtn.style.fontSize = '16px'; submitBtn.style.cursor = 'pointer'; submitBtn.addEventListener('click', submitAnswer); submitSection.appendChild(submitBtn); // Add to puzzle container puzzleImageContainer.appendChild(submitSection); // Set up current option tracking let currentOptionIndex = 0; // Initialize the answer input with the current index userAnswerInput.value = currentOptionIndex.toString(); // Function to update the option image function updateConnectOptionImage() { const optionImg = document.getElementById('connect-option-image'); if (optionImg) { optionImg.src = currentPuzzle.option_images[currentOptionIndex]; } // Update dots to highlight current option const dots = document.querySelectorAll('.pagination-dot'); dots.forEach((dot, index) => { dot.style.backgroundColor = index === currentOptionIndex ? '#4CAF50' : '#ccc'; }); // Update the answer input with the current index userAnswerInput.value = currentOptionIndex.toString(); } // Event listeners for arrows leftArrow.addEventListener('click', () => { currentOptionIndex = (currentOptionIndex - 1 + currentPuzzle.option_images.length) % currentPuzzle.option_images.length; updateConnectOptionImage(); }); rightArrow.addEventListener('click', () => { currentOptionIndex = (currentOptionIndex + 1) % currentPuzzle.option_images.length; updateConnectOptionImage(); }); } // Function to set up Click Order interface function setupClickOrder() { // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Create a container for the layout const layoutContainer = document.createElement('div'); layoutContainer.className = 'click-order-layout'; layoutContainer.style.display = 'flex'; layoutContainer.style.flexDirection = 'column'; layoutContainer.style.alignItems = 'center'; // Create a container for the main image const mainImageContainer = document.createElement('div'); mainImageContainer.className = 'main-image-container'; mainImageContainer.style.position = 'relative'; mainImageContainer.style.marginBottom = '20px'; mainImageContainer.style.width = '100%'; // Add main image const mainImg = document.createElement('img'); mainImg.id = 'click-order-main-image'; mainImg.src = currentPuzzle.image_path; mainImg.alt = 'Click the icons in order'; mainImg.style.maxWidth = '100%'; mainImg.style.border = '1px solid #ccc'; mainImageContainer.appendChild(mainImg); // Create a container for the order reference image const orderImageContainer = document.createElement('div'); orderImageContainer.className = 'order-image-container'; orderImageContainer.style.textAlign = 'center'; orderImageContainer.style.marginBottom = '20px'; // Add "Order Reference" label const orderLabel = document.createElement('div'); orderLabel.className = 'order-label'; orderLabel.textContent = 'Click icons in this order:'; orderLabel.style.backgroundColor = 'black'; orderLabel.style.color = 'white'; orderLabel.style.padding = '5px'; orderLabel.style.marginBottom = '5px'; orderLabel.style.fontSize = '14px'; orderImageContainer.appendChild(orderLabel); // Add order reference image const orderImg = document.createElement('img'); orderImg.id = 'click-order-reference-image'; orderImg.src = currentPuzzle.order_image; orderImg.alt = 'Reference order'; orderImg.style.maxWidth = '100%'; orderImg.style.border = '1px solid #ccc'; orderImageContainer.appendChild(orderImg); // Add click markers container to show user clicks const markersContainer = document.createElement('div'); markersContainer.className = 'click-markers-container'; markersContainer.style.position = 'absolute'; markersContainer.style.top = '0'; markersContainer.style.left = '0'; markersContainer.style.width = '100%'; markersContainer.style.height = '100%'; markersContainer.style.pointerEvents = 'none'; // Don't block clicks mainImageContainer.appendChild(markersContainer); // Track user clicks let userClicks = []; // Add click indicator const clickIndicator = document.createElement('div'); clickIndicator.className = 'click-indicator'; clickIndicator.style.marginTop = '10px'; clickIndicator.style.fontSize = '16px'; clickIndicator.textContent = 'Clicks: 0'; // Add reset button const resetButton = document.createElement('button'); resetButton.textContent = 'Reset Clicks'; resetButton.className = 'reset-clicks-btn'; resetButton.style.padding = '8px 15px'; resetButton.style.backgroundColor = '#f44336'; resetButton.style.color = 'white'; resetButton.style.border = 'none'; resetButton.style.borderRadius = '4px'; resetButton.style.marginRight = '10px'; resetButton.style.cursor = 'pointer'; // Add click event handler for the main image mainImg.addEventListener('click', function(e) { // Get click coordinates relative to the image const rect = e.target.getBoundingClientRect(); const x = Math.round(e.clientX - rect.left); const y = Math.round(e.clientY - rect.top); // Add click to the array userClicks.push([x, y]); // Show click marker addClickMarker(x, y, userClicks.length, markersContainer); // Update click indicator clickIndicator.textContent = `Clicks: ${userClicks.length}`; // Enable the dedicated submit button if at least one click has been made clickOrderSubmitBtn.disabled = false; // Log for debugging console.log(`Click ${userClicks.length} at:`, { x, y }); }); // Event listener for reset button resetButton.addEventListener('click', function() { // Clear user clicks userClicks = []; // Clear markers markersContainer.innerHTML = ''; // Update click indicator clickIndicator.textContent = 'Clicks: 0'; // Disable submit button submitBtn.disabled = true; }); // Add components to layout layoutContainer.appendChild(orderImageContainer); layoutContainer.appendChild(mainImageContainer); // Add controls container const controlsContainer = document.createElement('div'); controlsContainer.className = 'controls-container'; controlsContainer.style.display = 'flex'; controlsContainer.style.justifyContent = 'center'; controlsContainer.style.alignItems = 'center'; controlsContainer.style.marginTop = '15px'; // Add controls to container controlsContainer.appendChild(resetButton); controlsContainer.appendChild(clickIndicator); // Add controls to layout layoutContainer.appendChild(controlsContainer); // Create a dedicated submit button for the Click Order puzzle const clickOrderSubmitBtn = document.createElement('button'); clickOrderSubmitBtn.textContent = 'Submit Order'; clickOrderSubmitBtn.className = 'click-order-submit-btn'; clickOrderSubmitBtn.style.padding = '10px 20px'; clickOrderSubmitBtn.style.backgroundColor = '#4CAF50'; clickOrderSubmitBtn.style.color = 'white'; clickOrderSubmitBtn.style.border = 'none'; clickOrderSubmitBtn.style.borderRadius = '4px'; clickOrderSubmitBtn.style.marginTop = '15px'; clickOrderSubmitBtn.style.cursor = 'pointer'; clickOrderSubmitBtn.style.fontSize = '16px'; clickOrderSubmitBtn.disabled = true; // Disabled until clicks are made // Add submit button to layout layoutContainer.appendChild(clickOrderSubmitBtn); // Add layout to puzzle container puzzleImageContainer.appendChild(layoutContainer); // Hide the original input field and submit button userAnswerInput.style.display = 'none'; submitBtn.style.display = 'none'; inputGroup.style.display = 'none'; // Enable the dedicated submit button when clicks are made mainImg.addEventListener('click', function() { if (userClicks.length > 0) { clickOrderSubmitBtn.disabled = false; } }); // Reset button should disable submit button resetButton.addEventListener('click', function() { clickOrderSubmitBtn.disabled = true; }); // Add event listener to the dedicated submit button clickOrderSubmitBtn.addEventListener('click', function() { // Set the clicks as the answer userAnswerInput.value = JSON.stringify(userClicks); // Send the data to the server fetch('/api/check_answer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id, answer: userClicks }) }) .then(response => response.json()) .then(data => { // Update stats benchmarkStats.total++; if (data.correct) { benchmarkStats.correct++; resultMessage.textContent = 'Correct!'; resultMessage.className = 'result-message correct'; } else { resultMessage.textContent = 'Incorrect.'; resultMessage.className = 'result-message incorrect'; } updateStats(); // Record benchmark result recordBenchmarkResult({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id, user_answer: userClicks, correct_answer: data.correct_answer, correct: data.correct }); // Disable the submit button clickOrderSubmitBtn.disabled = true; // Load a new puzzle after a delay setTimeout(loadNewPuzzle, 2000); }) .catch(error => { console.error('Error checking answer:', error); resultMessage.textContent = 'Error checking answer. Please try again.'; resultMessage.className = 'result-message incorrect'; // Re-enable the submit button on error clickOrderSubmitBtn.disabled = false; }); }); // In debug mode, show the correct click positions if (DEBUG_MODE) { showClickOrderAnswerPositions(mainImageContainer); } } // Function to add a numbered click marker function addClickMarker(x, y, number, container) { const marker = document.createElement('div'); marker.className = 'click-marker'; marker.style.position = 'absolute'; marker.style.left = `${x - 15}px`; marker.style.top = `${y - 15}px`; marker.style.width = '30px'; marker.style.height = '30px'; marker.style.borderRadius = '50%'; marker.style.backgroundColor = 'rgba(255, 0, 0, 0.6)'; marker.style.border = '2px solid white'; marker.style.color = 'white'; marker.style.fontWeight = 'bold'; marker.style.display = 'flex'; marker.style.justifyContent = 'center'; marker.style.alignItems = 'center'; marker.style.fontSize = '14px'; marker.style.zIndex = '100'; marker.style.pointerEvents = 'none'; // Don't block future clicks marker.textContent = number.toString(); container.appendChild(marker); } // Function to show correct answer positions in debug mode function showClickOrderAnswerPositions(container) { fetch('/api/get_ground_truth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }) }) .then(response => response.json()) .then(gtData => { if (gtData.answer && Array.isArray(gtData.answer)) { const correctPositions = gtData.answer; const tolerance = currentPuzzle.tolerance || 20; // Create a debug layer const debugLayer = document.createElement('div'); debugLayer.className = 'debug-layer'; debugLayer.style.position = 'absolute'; debugLayer.style.top = '0'; debugLayer.style.left = '0'; debugLayer.style.width = '100%'; debugLayer.style.height = '100%'; debugLayer.style.pointerEvents = 'none'; // Add correct position indicators correctPositions.forEach((pos, index) => { const [x, y] = pos; // Create circle for tolerance area const toleranceCircle = document.createElement('div'); toleranceCircle.className = 'tolerance-circle'; toleranceCircle.style.position = 'absolute'; toleranceCircle.style.left = `${x - tolerance}px`; toleranceCircle.style.top = `${y - tolerance}px`; toleranceCircle.style.width = `${tolerance * 2}px`; toleranceCircle.style.height = `${tolerance * 2}px`; toleranceCircle.style.borderRadius = '50%'; toleranceCircle.style.border = '2px dashed green'; toleranceCircle.style.backgroundColor = 'rgba(0, 255, 0, 0.1)'; // Create label with position number const posLabel = document.createElement('div'); posLabel.className = 'position-label'; posLabel.style.position = 'absolute'; posLabel.style.left = `${x}px`; posLabel.style.top = `${y - 20}px`; posLabel.style.transform = 'translate(-50%, -50%)'; posLabel.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; posLabel.style.color = 'white'; posLabel.style.padding = '2px 5px'; posLabel.style.borderRadius = '3px'; posLabel.style.fontSize = '10px'; posLabel.textContent = `${index + 1}: (${x}, ${y})`; debugLayer.appendChild(toleranceCircle); debugLayer.appendChild(posLabel); }); container.appendChild(debugLayer); } }) .catch(error => { console.error('Error fetching ground truth for Click_Order:', error); }); } // Function to setup the Hold Button CAPTCHA function setupHoldButton() { // record the start time puzzleStartTime = Date.now(); // Clear the puzzle image container first puzzleImageContainer.innerHTML = ''; // Create a container for the button const buttonContainer = document.createElement('div'); buttonContainer.className = 'hold-button-container'; buttonContainer.style.position = 'relative'; buttonContainer.style.width = '100%'; buttonContainer.style.maxWidth = '400px'; buttonContainer.style.margin = '0 auto'; buttonContainer.style.textAlign = 'center'; // If the CAPTCHA has an image, show it above the button if (currentPuzzle.image_path) { const imageElement = document.createElement('img'); imageElement.src = currentPuzzle.image_path; imageElement.alt = 'Hold Button CAPTCHA'; imageElement.style.display = 'block'; imageElement.style.width = '100%'; imageElement.style.maxWidth = '400px'; imageElement.style.margin = '0 auto 20px'; imageElement.style.borderRadius = '8px'; buttonContainer.appendChild(imageElement); } // Create button element const button = document.createElement('div'); button.className = 'hold-button'; button.style.position = 'relative'; button.style.width = '100%'; button.style.height = 'auto'; button.style.cursor = 'pointer'; button.style.userSelect = 'none'; button.style.borderRadius = '50px'; button.style.border = '3px solid #333'; button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; button.style.backgroundColor = '#f8f8f8'; button.style.padding = '30px 0'; button.style.fontSize = '28px'; button.style.fontWeight = 'bold'; button.style.color = '#333'; button.style.textAlign = 'center'; button.style.transition = 'background-color 0.3s'; button.textContent = 'HOLD'; // Create progress bar const progressBar = document.createElement('div'); progressBar.className = 'hold-progress'; progressBar.style.position = 'absolute'; progressBar.style.left = '0'; progressBar.style.bottom = '0'; progressBar.style.height = '8px'; progressBar.style.width = '0%'; progressBar.style.backgroundColor = '#4CAF50'; progressBar.style.transition = 'width 0.1s linear'; progressBar.style.borderRadius = '0 0 50px 50px'; // Get hold time from data const requiredHoldTime = currentPuzzle.hold_time || 3; // Default to 3 seconds // Variables to track holding let isHolding = false; let holdStartTime = 0; let holdTimer = null; let completed = false; let currentHoldTime = 0; // Add event listeners for hold detection button.addEventListener('mousedown', startHolding); button.addEventListener('touchstart', startHolding); document.addEventListener('mouseup', stopHolding); document.addEventListener('touchend', stopHolding); function startHolding(e) { if (completed) return; // Prevent default behaviors for touch if (e.type === 'touchstart') { e.preventDefault(); } isHolding = true; holdStartTime = Date.now(); button.style.backgroundColor = '#e0e0e0'; // Start progress animation holdTimer = setInterval(() => { if (!isHolding) return; const elapsedTime = (Date.now() - holdStartTime) / 1000; // in seconds currentHoldTime = elapsedTime; // Update progress bar const progress = Math.min((elapsedTime / requiredHoldTime) * 100, 100); progressBar.style.width = `${progress}%`; // Check if hold is complete if (elapsedTime >= requiredHoldTime && !completed) { completeHold(); } }, 100); // Update every 100ms } function stopHolding() { if (!isHolding || completed) return; isHolding = false; button.style.backgroundColor = '#f8f8f8'; // Reset progress if not completed if (!completed) { progressBar.style.width = '0%'; clearInterval(holdTimer); } } function completeHold() { completed = true; clearInterval(holdTimer); // Change button appearance button.style.backgroundColor = '#4CAF50'; button.style.color = 'white'; button.textContent = 'COMPLETED'; // Set the user answer to the current hold time userAnswerInput.value = currentHoldTime.toFixed(2); // Enable submit button submitBtn.disabled = false; resultMessage.textContent = "Button hold completed! Click 'Submit' to continue."; resultMessage.className = 'result-message instruction'; } // Add the progress bar to button button.appendChild(progressBar); // Add button to container buttonContainer.appendChild(button); // Add to puzzle container puzzleImageContainer.appendChild(buttonContainer); // Reset and clear input field userAnswerInput.value = ''; userAnswerInput.style.display = 'none'; submitBtn.disabled = false; // Disable submit button until hold is complete } // Function to show dotted areas in debug mode for Pick_Area function showPickAreaTargets(container) { if (!DEBUG_MODE || !currentPuzzle) return; // Fetch ground truth data to show the correct area fetch('/api/get_ground_truth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ puzzle_type: currentPuzzle.puzzle_type, puzzle_id: currentPuzzle.puzzle_id }) }) .then(response => response.json()) .then(gtData => { if (gtData.answer && gtData.answer.area) { // Get the area from ground truth const areaCoords = gtData.answer.area; const areaType = gtData.answer.type || 'largest region'; // Create a marker for the area const areaMarker = document.createElement('div'); areaMarker.className = 'area-marker debug-marker'; areaMarker.style.position = 'absolute'; areaMarker.style.border = '3px dashed #ff3333'; // Use a more transparent background to show the underlying dotted lines areaMarker.style.backgroundColor = 'rgba(255, 51, 51, 0.15)'; areaMarker.style.zIndex = '999'; // Add border radius to better represent curved areas areaMarker.style.borderRadius = '25%'; // Set position and size const [topLeft, bottomRight] = areaCoords; const [minX, minY] = topLeft; const [maxX, maxY] = bottomRight; areaMarker.style.left = `${minX}px`; areaMarker.style.top = `${minY}px`; areaMarker.style.width = `${maxX - minX}px`; areaMarker.style.height = `${maxY - minY}px`; // Add a label that better explains what to do const label = document.createElement('div'); label.className = 'debug-label'; label.style.position = 'absolute'; label.style.top = '5px'; label.style.left = '50%'; label.style.transform = 'translateX(-50%)'; label.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; label.style.color = 'white'; label.style.padding = '5px 10px'; label.style.fontSize = '14px'; label.style.fontWeight = 'bold'; label.style.borderRadius = '3px'; label.style.whiteSpace = 'nowrap'; label.style.textAlign = 'center'; label.textContent = `${areaType}: (${minX},${minY}) to (${maxX},${maxY})`; areaMarker.appendChild(label); // Add a note to explain that the actual area follows the dotted lines const note = document.createElement('div'); note.className = 'area-note'; note.style.position = 'absolute'; note.style.bottom = '10px'; note.style.left = '50%'; note.style.transform = 'translateX(-50%)'; note.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; note.style.color = 'white'; note.style.padding = '5px 10px'; note.style.fontSize = '12px'; note.style.borderRadius = '3px'; note.style.maxWidth = '90%'; note.style.textAlign = 'center'; note.textContent = 'Follow the dotted white lines to identify the actual area'; areaMarker.appendChild(note); container.appendChild(areaMarker); // Create indicators to highlight the dotted lines // This is a simplistic approach; ideally we would trace the actual dotted lines highlightDottedLines(container, areaCoords); } }) .catch(error => { console.error('Error fetching ground truth for Pick_Area:', error); }); } // Function to highlight the dotted lines that define the area function highlightDottedLines(container, areaCoords) { const [topLeft, bottomRight] = areaCoords; const [minX, minY] = topLeft; const [maxX, maxY] = bottomRight; // Create a canvas element to draw over the image const canvas = document.createElement('canvas'); canvas.className = 'dotted-line-highlight'; canvas.style.position = 'absolute'; canvas.style.top = '0'; canvas.style.left = '0'; canvas.style.pointerEvents = 'none'; // Don't interfere with clicks canvas.style.zIndex = '998'; // Just below the area marker // Wait for the image to load to get the correct dimensions const img = container.querySelector('img'); if (!img) return; if (img.complete) { setupCanvas(); } else { img.onload = setupCanvas; } function setupCanvas() { canvas.width = img.clientWidth; canvas.height = img.clientHeight; const ctx = canvas.getContext('2d'); ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; ctx.lineWidth = 3; ctx.setLineDash([5, 5]); // Create a dashed line effect // Draw a path that approximates the dotted lines // This is just a rough approximation - would need image processing to trace actual lines ctx.beginPath(); // Top line ctx.moveTo(minX, minY); ctx.lineTo(maxX, minY); // Right line ctx.moveTo(maxX, minY); ctx.lineTo(maxX, maxY); // Bottom line ctx.moveTo(maxX, maxY); ctx.lineTo(minX, maxY); // Left line ctx.moveTo(minX, maxY); ctx.lineTo(minX, minY); ctx.stroke(); container.appendChild(canvas); } } // Function to show the area to avoid for Misleading_Click puzzles function showMisleadingClickArea(container, avoidArea) { if (!DEBUG_MODE || !avoidArea) return; // Create a marker for the area to avoid const areaMarker = document.createElement('div'); areaMarker.className = 'avoid-area-marker debug-marker'; areaMarker.style.position = 'absolute'; areaMarker.style.border = '3px dashed red'; areaMarker.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; areaMarker.style.zIndex = '999'; // Set position and size const { x, y, width, height } = avoidArea; areaMarker.style.left = `${x}px`; areaMarker.style.top = `${y}px`; areaMarker.style.width = `${width}px`; areaMarker.style.height = `${height}px`; // Add a label const label = document.createElement('div'); label.className = 'debug-label'; label.style.position = 'absolute'; label.style.top = '-20px'; label.style.left = '0'; label.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; label.style.color = 'white'; label.style.padding = '2px 5px'; label.style.fontSize = '12px'; label.style.borderRadius = '3px'; label.textContent = `DO NOT CLICK IN THIS AREA: (${x},${y}) ${width}x${height}`; // Add a warning sign in the middle const warningSign = document.createElement('div'); warningSign.className = 'warning-sign'; warningSign.textContent = 'DO NOT CLICK HERE'; warningSign.style.position = 'absolute'; warningSign.style.top = '50%'; warningSign.style.left = '50%'; warningSign.style.transform = 'translate(-50%, -50%)'; warningSign.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; warningSign.style.color = '#ff5555'; warningSign.style.padding = '5px 10px'; warningSign.style.fontSize = '14px'; warningSign.style.fontWeight = 'bold'; warningSign.style.borderRadius = '3px'; warningSign.style.whiteSpace = 'nowrap'; warningSign.style.zIndex = '10'; areaMarker.appendChild(label); areaMarker.appendChild(warningSign); container.appendChild(areaMarker); console.log('Misleading Click area to avoid:', avoidArea); } /** * Checks if a point is inside a polygon defined by an array of points * Uses ray-casting algorithm * @param {number} x - X coordinate of the point to check * @param {number} y - Y coordinate of the point to check * @param {array} polygon - Array of points defining the polygon [[x1,y1], [x2,y2], ...] * @returns {boolean} True if the point is inside the polygon */ function pointInPolygon(x, y, polygon) { if (!polygon || polygon.length < 3) return false; let inside = false; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const xi = polygon[i][0], yi = polygon[i][1]; const xj = polygon[j][0], yj = polygon[j][1]; const intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; } /** * Sets up the Dart Count interface with reference number and dart images */ function setupDartCount() { // Clear the puzzle image container puzzleImageContainer.innerHTML = ''; // Create container for the dart count interface const dartContainer = document.createElement('div'); dartContainer.className = 'dart-count-container'; // Create a horizontal layout const horizontalLayout = document.createElement('div'); horizontalLayout.className = 'dart-count-horizontal-layout'; // Create reference container (shows the target number) const referenceContainer = document.createElement('div'); referenceContainer.className = 'dart-count-reference'; // Add reference image - check all possible locations for data const referenceImage = document.createElement('img'); if (currentPuzzle.additional_data && currentPuzzle.additional_data.reference_image) { referenceImage.src = currentPuzzle.additional_data.reference_image; } else if (currentPuzzle.reference_image) { referenceImage.src = currentPuzzle.reference_image; } else { console.error('Reference image not found for Dart Count puzzle'); } referenceImage.alt = 'Target Number'; referenceImage.className = 'dart-count-reference-img'; referenceContainer.appendChild(referenceImage); // Add reference caption const referenceCaption = document.createElement('div'); referenceCaption.className = 'dart-count-caption'; referenceCaption.textContent = 'Find sum of darts equal to this'; referenceContainer.appendChild(referenceCaption); // Create options container const optionsContainer = document.createElement('div'); optionsContainer.className = 'dart-count-options'; // Get option images from all possible locations let optionImages = []; if (currentPuzzle.additional_data && currentPuzzle.additional_data.option_images) { optionImages = currentPuzzle.additional_data.option_images; } else if (currentPuzzle.option_images) { optionImages = currentPuzzle.option_images; } else { console.error('Option images not found for Dart Count puzzle'); optionImages = []; } // Add option image const optionImage = document.createElement('img'); if (optionImages.length > 0) { optionImage.src = optionImages[0]; // Start with first option } optionImage.alt = 'Dart Option'; optionImage.className = 'dart-count-option-img'; optionsContainer.appendChild(optionImage); // Create navigation controls const controlsContainer = document.createElement('div'); controlsContainer.className = 'dart-count-controls'; // Left arrow const leftArrow = document.createElement('button'); leftArrow.innerHTML = '←'; leftArrow.className = 'dart-count-arrow left-arrow'; leftArrow.addEventListener('click', () => updateDartOption(-1)); // Right arrow const rightArrow = document.createElement('button'); rightArrow.innerHTML = '→'; rightArrow.className = 'dart-count-arrow right-arrow'; rightArrow.addEventListener('click', () => updateDartOption(1)); // Add arrows to controls controlsContainer.appendChild(leftArrow); controlsContainer.appendChild(rightArrow); // Add controls to options container optionsContainer.appendChild(controlsContainer); // Add reference and options to horizontal layout horizontalLayout.appendChild(referenceContainer); horizontalLayout.appendChild(optionsContainer); // Add horizontal layout to main container dartContainer.appendChild(horizontalLayout); // Add option indicators (dots) const indicators = document.createElement('div'); indicators.className = 'dart-count-indicators'; const numOptions = optionImages.length; for (let i = 0; i < numOptions; i++) { const dot = document.createElement('span'); dot.className = 'dart-count-dot'; if (i === 0) { dot.classList.add('active'); } indicators.appendChild(dot); } // Add indicators to main container dartContainer.appendChild(indicators); // Add submit button const submitBtn = document.createElement('button'); submitBtn.textContent = 'Submit'; submitBtn.className = 'dart-count-submit'; submitBtn.addEventListener('click', submitAnswer); // Add containers to puzzle image container puzzleImageContainer.appendChild(dartContainer); puzzleImageContainer.appendChild(submitBtn); // Store current index in the hidden input for submission userAnswerInput.value = '0'; // Log all available data for debugging console.log('Dart Count puzzle data:', currentPuzzle); } /** * Update the displayed dart option image based on navigation direction * @param {number} direction - Direction to navigate (-1 for left, 1 for right) */ function updateDartOption(direction) { const optionImage = document.querySelector('.dart-count-option-img'); const dots = document.querySelectorAll('.dart-count-dot'); // Get option images from all possible locations let optionImages = []; if (currentPuzzle.additional_data && currentPuzzle.additional_data.option_images) { optionImages = currentPuzzle.additional_data.option_images; } else if (currentPuzzle.option_images) { optionImages = currentPuzzle.option_images; } else { console.error('Option images not found for Dart Count puzzle'); return; } // Get current index from input field let currentIndex = parseInt(userAnswerInput.value) || 0; const numOptions = optionImages.length; // Calculate new index with wrap-around let newIndex = (currentIndex + direction + numOptions) % numOptions; // Update the option image optionImage.src = optionImages[newIndex]; // Update dots dots.forEach((dot, index) => { if (index === newIndex) { dot.classList.add('active'); } else { dot.classList.remove('active'); } }); // Store selected answer for submission userAnswerInput.value = newIndex.toString(); // Log for debugging console.log('Updated dart option:', { index: newIndex, src: optionImage.src }); } /** * Display difficulty stars based on CAPTCHA type * @param {string} puzzleType - The type of CAPTCHA puzzle */ function displayDifficultyStars(puzzleType) { const difficultyRatings = { 'Dice_Count': 1, 'Geometry_Click': 1, 'Rotation_Match': 3, 'Slide_Puzzle': 2, 'Unusual_Detection': 3, 'Image_Recognition': 2, 'Bingo': 4, 'Image_Matching': 2, 'Patch_Select': 3, 'Dart_Count': 3, 'Object_Match': 2, 'Select_Animal': 1, 'Coordinates': 3, 'Path_Finder': 3, 'Place_Dot': 2, 'Connect_icon': 3, 'Click_Order': 4, 'Hold_Button': 2, 'Misleading_Click': 4, 'Pick_Area': 5, }; const difficulty = difficultyRatings[puzzleType] || 1; const starsContainer = document.getElementById('difficulty-stars'); // Safety check to ensure the container exists if (!starsContainer) { console.error('Stars container not found!'); return; } // Clear the container starsContainer.innerHTML = ''; // Create and append stars for (let i = 0; i < 5; i++) { const star = document.createElement('span'); star.className = 'star'; star.innerHTML = i < difficulty ? '★' : '☆'; // Filled or empty star starsContainer.appendChild(star); } // Log for debugging console.log(`Displayed ${difficulty} stars for puzzle type: ${puzzleType}`); } // Function to get a new puzzle function getPuzzle(callback) { let queryParams = ''; // Check if debug mode is active and add the debug_type parameter if it is if (DEBUG_MODE && DEBUG_TYPE) { queryParams = `?debug_type=${encodeURIComponent(DEBUG_TYPE)}`; } fetch('/api/get_puzzle' + queryParams) .then(response => response.json()) .then(data => { currentPuzzle = data; // Log the data for debugging console.log('Puzzle data:', data); // Set the prompt and update debug information const promptElement = document.getElementById('puzzle-prompt'); promptElement.textContent = data.prompt; // Display difficulty stars based on puzzle type displayDifficultyStars(data.puzzle_type); // Update debug indicator if in debug mode const debugIndicator = document.getElementById('debug-indicator'); const debugTypeDisplay = document.getElementById('debug-type-display'); if (DEBUG_MODE && DEBUG_TYPE) { debugIndicator.style.display = 'block'; debugTypeDisplay.textContent = DEBUG_TYPE; } else { debugIndicator.style.display = 'none'; } // Handle different input types const imageContainer = document.getElementById('puzzle-image-container'); const userAnswerInput = document.getElementById('user-answer'); const submitBtn = document.getElementById('submit-answer'); // Reset the input field and enable submit button userAnswerInput.value = ''; submitBtn.disabled = false; // Clear any previous result message const resultMessage = document.getElementById('result-message'); resultMessage.textContent = ''; resultMessage.className = 'result-message'; // Clear the puzzle image container imageContainer.innerHTML = ''; // Set up UI based on input type if (data.input_type === 'number') { // For numeric input (e.g., Dice_Count) userAnswerInput.type = 'number'; userAnswerInput.placeholder = 'Enter number'; userAnswerInput.style.display = 'block'; submitBtn.style.display = 'block'; // Load the image const img = document.createElement('img'); img.src = data.image_path; img.alt = 'CAPTCHA Puzzle'; img.id = 'puzzle-image'; img.onload = function() { imageContainer.appendChild(img); }; } else if (data.input_type === 'click') { // For click-based puzzles (Geometry_Click, Place_Dot, etc.) userAnswerInput.style.display = 'none'; submitBtn.style.display = 'none'; // Load the image and set up click handler const img = document.createElement('img'); img.src = data.image_path; img.alt = 'CAPTCHA Puzzle'; img.id = 'puzzle-image'; img.onclick = handleImageClick; img.onload = function() { imageContainer.appendChild(img); // For Misleading_Click, show the area to avoid in debug mode if (data.puzzle_type === 'Misleading_Click' && DEBUG_MODE) { showMisleadingClickArea(imageContainer, data.avoid_area); } // For Pick_Area, show the target areas in debug mode if (data.puzzle_type === 'Pick_Area' && DEBUG_MODE) { showPickAreaTargets(imageContainer); } }; } else if (data.input_type === 'rotation') { // For rotation puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up rotation controls setupRotationControls(); } else if (data.input_type === 'slide') { // For slide puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up slide puzzle setupSlidePuzzle(); } else if (data.input_type === 'multiselect') { // For multiple selection puzzles (Unusual_Detection) userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up grid for unusual detection setupUnusualDetectionGrid(); } else if (data.input_type === 'bingo_swap') { // For bingo swap puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up bingo swap interface setupBingoSwap(); } else if (data.input_type === 'image_grid') { // For image grid puzzles (Image_Recognition) userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up image recognition grid setupImageRecognition(); } else if (data.input_type === 'image_matching') { // For image matching puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up image matching interface setupImageMatching(); } else if (data.input_type === 'patch_select') { // For patch select puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up patch select grid setupPatchSelectGrid(); } else if (data.input_type === 'dart_count') { // For dart count puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'none'; // Set up dart count interface setupDartCount(); } else if (data.input_type === 'object_match') { // For object match puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up object match interface setupObjectMatch(); } else if (data.input_type === 'select_animal') { // For animal selection puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up animal selection grid setupSelectAnimalGrid(); } else if (data.input_type === 'place_dot') { // For place dot puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'none'; // Set up place dot interface setupPlaceDot(); } else if (data.input_type === 'connect_icon') { // For connect icon puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up connect icon interface setupConnectIcon(); } else if (data.input_type === 'click_order') { // For click order puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up click order interface setupClickOrder(); } else if (data.input_type === 'hold_button') { // For hold button puzzles userAnswerInput.style.display = 'none'; submitBtn.style.display = 'block'; // Set up hold button interface setupHoldButton(); } else { // Default to text input for other types userAnswerInput.type = 'text'; userAnswerInput.placeholder = 'Your answer'; userAnswerInput.style.display = 'block'; submitBtn.style.display = 'block'; // Load the image const img = document.createElement('img'); img.src = data.image_path; img.alt = 'CAPTCHA Puzzle'; img.id = 'puzzle-image'; img.onload = function() { imageContainer.appendChild(img); }; } // Call the callback if provided if (callback && typeof callback === 'function') { callback(); } }) .catch(error => { console.error('Error fetching puzzle:', error); }); } });