Skip to content

Instantly share code, notes, and snippets.

@nicolas-goudry
Last active April 30, 2025 12:23
Show Gist options
  • Save nicolas-goudry/3912ca31f1d8f630a8af160eedbe4df6 to your computer and use it in GitHub Desktop.
Save nicolas-goudry/3912ca31f1d8f630a8af160eedbe4df6 to your computer and use it in GitHub Desktop.
Want to be the best at Lucca Faces? Run the game, then execute this code into your console. Let it play until you are the best!
// New version courtesy of JCluzet. Thanks!
(() => {
const STORAGE_KEY = 'lucca_faces_data_v2';
let people = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
let currentImageHash = '';
let retryAttempts = 0;
const MAX_RETRY_ATTEMPTS = 5; // Adjust max attempts count
function getImageHash(imageSrc) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
canvas.toBlob(blob => {
const reader = new FileReader();
reader.onloadend = () => {
crypto.subtle.digest('SHA-256', reader.result).then(hashBuffer => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
resolve(hashHex);
});
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
};
img.onerror = () => {
reject('Image load error');
};
img.src = imageSrc;
});
}
async function handleNewImage(imageSrc) {
try {
const hashHex = await getImageHash(imageSrc);
if (hashHex !== currentImageHash) {
currentImageHash = hashHex;
if (people[currentImageHash]) {
const choices = document.querySelectorAll('#game .answers .answer');
const correctAnswer = [...choices].find(choice => choice.textContent.trim() === people[currentImageHash].trim());
if (correctAnswer) {
correctAnswer.click();
}
} else {
chooseRandomAnswer();
}
}
retryAttempts = 0; // Reset attempts counter on success
} catch (error) {
console.error('Error processing image:', error);
retryAttempts++;
if (retryAttempts <= MAX_RETRY_ATTEMPTS) {
console.log(`Retrying (${retryAttempts}/${MAX_RETRY_ATTEMPTS})...`);
retryHandleImage(imageSrc);
} else {
console.log('Max retry attempts reached. Skipping image.');
retryAttempts = 0; // Reset attempts counter if limit reached
clickGoButton(); // Click on "Go" button after reaching end of the game
}
}
}
function retryHandleImage(imageSrc) {
setTimeout(() => handleNewImage(imageSrc), 100);
}
function chooseRandomAnswer() {
const choices = document.querySelectorAll('#game .answers .answer');
const randomChoice = choices[Math.floor(Math.random() * choices.length)];
randomChoice.click();
setTimeout(() => {
const correctAnswer = document.querySelector('#game .answers .is-right');
if (correctAnswer) {
const name = correctAnswer.textContent.trim();
people[currentImageHash] = name;
localStorage.setItem(STORAGE_KEY, JSON.stringify(people));
console.log(`Discovered ${name} for hash ${currentImageHash}`);
} else {
console.error('Failed to discover the correct answer!');
}
}, 500); // Wait a bit to allow correct answer to be shown
}
function startGame() {
const startButtonContainer = document.querySelector('.main-container.has-loaded .logo .rotation-loader');
if (startButtonContainer) {
startButtonContainer.click();
console.log('Clicked the start button');
} else {
console.error('Start button not found. Make sure the game is loaded.');
}
}
function restartGame() {
const replayButton = document.querySelector('.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL');
if (replayButton) {
replayButton.click();
console.log('Clicked the replay button');
} else {
console.log('Please click "Replay" to restart the game.');
}
}
function clickGoButton() {
// Try to click on several elements until "Go" button is found
const elementsToClick = [
document.querySelector('.main-container .logo .rotation-loader'),
document.querySelector('.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'),
document.querySelector('.main-container .results-card button.button.mod-pill.palette-primary.mod-L'),
document.querySelector('.main-container .results-card button.button.mod-pill.palette-primary.mod-XL'),
document.querySelector('.main-container .results-card button.button.mod-pill.palette-tertiary.mod-XL')
// Add other CSS selectors here if needed
];
let clicked = false;
elementsToClick.forEach(element => {
if (element && !clicked) {
element.click();
console.log('Clicked a button to restart the game.');
clicked = true;
}
});
if (!clicked) {
console.log('Go button not found. Please click "Replay" and "Go" manually to restart.');
}
}
function observeGame() {
const gameElement = document.querySelector('#game');
if (!gameElement) {
console.error('Game element not found. Make sure the game is loaded.');
return;
}
const observer = new MutationObserver(() => {
const imageElement = document.querySelector('#game app-timer .image');
if (imageElement) {
const imageSrc = imageElement.style.backgroundImage.match(/url\("(.*)"\)/)[1];
handleNewImage(imageSrc);
}
const gameEnded = !!document.querySelector('.main-container .results-card');
if (gameEnded) {
restartGame();
startGame(); // Restart game after click on "Rejouer" and "Go"
}
});
observer.observe(gameElement, { childList: true, subtree: true });
}
function main() {
startGame();
observeGame();
console.log('Script loaded: It will automatically start the game and handle answers.');
}
// Start main function right away
main();
})();
@lpierrewanadev
Copy link

lpierrewanadev commented Apr 30, 2025

I tweaked the code a bit so it could run before even pressing "Go."

So, it's very simple:

  1. Enter the code in the console
  2. Enter
  3. Press "Go"
;(() => {
    const STORAGE_KEY = 'lucca_faces_data_v2'
    let people = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}
    let currentImageHash = ''
    let retryAttempts = 0
    const MAX_RETRY_ATTEMPTS = 5
    let totalPointsNeeded = 0
    let questionsAnswered = 0
    let currentTotalScore = 0

    const targetScore = (() => {
        const input = prompt('Quel score cible veux-tu atteindre ? (entre 500 et 1700)', '1700')
        const parsed = parseInt(input, 10)
        return isNaN(parsed) || parsed < 500 || parsed > 1700 ? 1700 : parsed
    })()
    const minScore = 500
    const maxScore = 1700
    const minTimePerQuestion = 3000
    const maxTimePerQuestion = 0

    const timePerQuestion = calculateTimePerQuestion(
        targetScore,
        minScore,
        maxScore,
        minTimePerQuestion,
        maxTimePerQuestion
    )
    console.log(`Time per question: ${timePerQuestion}ms for target score: ${targetScore}`)

    function calculateTimePerQuestion(score, minScore, maxScore, minTime, maxTime) {
        if (score <= minScore) return minTime
        if (score >= maxScore) return maxTime

        const scoreRange = maxScore - minScore
        const timeRange = minTime - maxTime
        const timePerQuestion = maxTime + ((score - minScore) / scoreRange) * timeRange
        return Math.round(timePerQuestion)
    }

    function getTotalPointsFromHeaderText(headerText) {
        const regex = /(\d+) pts/
        const match = headerText.match(regex)
        return match ? parseInt(match[1], 10) : 0
    }

    function getPointsFromSpanText(spanText) {
        const regex = /\+ (\d+) pts/
        const match = spanText.match(regex)
        return match ? parseInt(match[1], 10) : 0
    }

    function getImageHash(imageSrc) {
        return new Promise((resolve, reject) => {
            const img = new Image()
            img.crossOrigin = 'Anonymous'
            img.onload = () => {
                const canvas = document.createElement('canvas')
                const ctx = canvas.getContext('2d')
                canvas.width = img.width
                canvas.height = img.height
                ctx.drawImage(img, 0, 0)
                canvas.toBlob((blob) => {
                    const reader = new FileReader()
                    reader.onloadend = () => {
                        crypto.subtle.digest('SHA-256', reader.result).then((hashBuffer) => {
                            const hashArray = Array.from(new Uint8Array(hashBuffer))
                            const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
                            resolve(hashHex)
                        })
                    }
                    reader.onerror = reject
                    reader.readAsArrayBuffer(blob)
                })
            }
            img.onerror = () => reject('Image load error')
            img.src = imageSrc
        })
    }

    function handleNewImage(imageSrc) {
        getImageHash(imageSrc)
            .then((hashHex) => {
                if (hashHex !== currentImageHash) {
                    currentImageHash = hashHex
                    if (people[currentImageHash]) {
                        const choices = document.querySelectorAll('#game .answers .answer')
                        const correctAnswer = [...choices].find(
                            (choice) => choice.textContent.trim() === people[currentImageHash].trim()
                        )
                        if (correctAnswer) {
                            setTimeout(() => {
                                correctAnswer.click()

                                pollForScoreElement((pointsEarned, newTotalScore) => {
                                    questionsAnswered++
                                    currentTotalScore = newTotalScore
                                    const questionsRemaining = 10 - questionsAnswered
                                    const adjustedTimePerQuestion = calculateAdjustedTimePerQuestion(
                                        targetScore - currentTotalScore,
                                        questionsRemaining
                                    )
                                    const scoreNeededForNextQuestion =
                                        questionsRemaining > 0
                                            ? Math.ceil((targetScore - currentTotalScore) / questionsRemaining)
                                            : 0

                                    console.log(
                                        `${questionsAnswered}/10: ${people[currentImageHash]}, score obtenu: ${pointsEarned}, score total: ${currentTotalScore}, score visé pour la prochaine question: ${scoreNeededForNextQuestion}`
                                    )

                                    if (questionsAnswered < 10) {
                                        setTimeout(() => {
                                            handleNewImage(imageSrc)
                                        }, adjustedTimePerQuestion)
                                    } else {
                                        console.log('Game completed. Total points achieved.')
                                    }
                                })
                            }, timePerQuestion)
                        } else {
                            console.error('Correct answer element not found.')
                            chooseRandomAnswer()
                        }
                    } else {
                        chooseRandomAnswer()
                        console.log(
                            `PERSONNE INCONNUE : Enregistrement du hash ${currentImageHash} pour la personne ${imageSrc}`
                        )
                    }
                }
                retryAttempts = 0
            })
            .catch((error) => {
                console.error('Error processing image:', error)
                retryAttempts++
                if (retryAttempts <= MAX_RETRY_ATTEMPTS) {
                    console.log(`Retrying (${retryAttempts}/${MAX_RETRY_ATTEMPTS})...`)
                    retryHandleImage(imageSrc)
                } else {
                    console.log('Max retry attempts reached. Skipping image.')
                    retryAttempts = 0
                    clickGoButton()
                }
            })
    }

    function pollForScoreElement(callback) {
        const interval = setInterval(() => {
            const imageOverlayElement = document.querySelector('.image-container .image-overlay span.score')
            if (imageOverlayElement) {
                clearInterval(interval)
                const pointsEarned = getPointsFromSpanText(imageOverlayElement.textContent)
                const currentTotalScore = getTotalPointsFromHeaderText(
                    document.querySelector('.score-header .score').textContent
                )
                callback(pointsEarned, currentTotalScore)
            }
        }, 100)
    }

    function calculateAdjustedTimePerQuestion(totalPointsNeeded, questionsRemaining) {
        const baseTime = 3000
        const targetTime = 0
        const timeRange = baseTime - targetTime
        const adjustedTime = targetTime + (totalPointsNeeded / targetScore) * timeRange
        const adjustedTimePerQuestion = adjustedTime / questionsRemaining
        return Math.round(adjustedTimePerQuestion)
    }

    function retryHandleImage(imageSrc) {
        setTimeout(() => handleNewImage(imageSrc), 100)
    }

    function chooseRandomAnswer() {
        const choices = document.querySelectorAll('#game .answers .answer')
        if (choices.length > 0) {
            const randomChoice = choices[Math.floor(Math.random() * choices.length)]
            randomChoice.click()
            setTimeout(() => {
                const correctAnswer = document.querySelector('#game .answers .is-right')
                if (correctAnswer) {
                    const name = correctAnswer.textContent.trim()
                    people[currentImageHash] = name
                    localStorage.setItem(STORAGE_KEY, JSON.stringify(people))
                    console.log(`Discovered ${name} for hash ${currentImageHash}`)
                } else {
                    console.error('Failed to discover the correct answer!')
                }
            }, 500)
        } else {
            console.error('No choices available to click.')
        }
    }

    function startGame() {
        const startButtonContainer = document.querySelector('.main-container.has-loaded .logo .rotation-loader')
        if (startButtonContainer) {
            startButtonContainer.click()
            console.log('Clicked the start button')
        } else {
            console.error('Start button not found. Make sure the game is loaded.')
        }
    }

    function restartGame() {
        const replayButton = document.querySelector(
            '.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'
        )
        if (replayButton) {
            replayButton.click()
            console.log('Clicked the replay button')
        } else {
            console.log('Please click "Replay" to restart the game.')
        }

        const goButton = document.querySelector(
            '.main-container .results-card button.button.mod-pill.palette-primary.mod-L, .main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'
        )
        if (goButton) {
            goButton.click()
            console.log('Clicked the go button')
        } else {
            console.log('Please click "Go" to restart the game.')
        }
    }

    function waitForGameElement() {
        const checkInterval = setInterval(() => {
            const gameElement = document.querySelector('#game')
            if (gameElement) {
                clearInterval(checkInterval)
                observeGame()
            }
        }, 200)
    }

    function observeGame() {
        const gameElement = document.querySelector('#game')
        if (!gameElement) {
            console.error('Game element not found. Make sure the game is loaded.')
            return
        }

        const observer = new MutationObserver(() => {
            const imageElement = document.querySelector('#game app-timer .image')
            if (imageElement) {
                const imageSrcMatch = imageElement.style.backgroundImage.match(/url\("(.*)"\)/)
                if (imageSrcMatch) {
                    const imageSrc = imageSrcMatch[1]
                    handleNewImage(imageSrc)
                }
            }

            const gameEnded = !!document.querySelector('.main-container .results-card')
            if (gameEnded) {
                const headerText = document.querySelector('.main-container .results-card .header').textContent
                const pointsEarned = getTotalPointsFromHeaderText(headerText)
                totalPointsNeeded -= pointsEarned

                if (questionsAnswered < 10) {
                    restartGame()
                    startGame()
                } else {
                    console.log('Game completed. Total points achieved.')
                }
            }
        })

        observer.observe(gameElement, { childList: true, subtree: true })
    }

    waitForGameElement()
})()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment