Last active
April 30, 2025 12:23
-
-
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!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
})(); |
hum 🤔
J'avais une version à exécuter comme ça dans le navigateur mais je trouvais ça trop lent.
Je fais des call en direct à l'API sans délais mais je n'arrive pas à atteindre les 1500.
I tweaked the code a bit so it could run before even pressing "Go."
So, it's very simple:
- Enter the code in the console
- Enter
- 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
by making more than 1550 or 1600 you will be banned for 1 month, you can try my new script :
(() => {
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; // Compteur de questions répondues
let currentTotalScore = 0; // Score total actuel
// Demander le score cible à l'utilisateur
let targetScore = parseInt(prompt('Veuillez entrer le score cible (entre 500 et 1700) :'), 10);
if (isNaN(targetScore) || targetScore < 500 || targetScore > 1700) {
targetScore = 1300; // Valeur par défaut si l'entrée est invalide
}
// Ajuster le calcul du délai en fonction du score cible
const minScore = 500;
const maxScore = 1700;
const minTimePerQuestion = 3000; // Délai pour un score de 500
const maxTimePerQuestion = 0; // Délai pour un score de 1700
// Calculer le délai pour chaque question en fonction du score cible
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;
}
}
function getTotalPointsFromHeaderText(headerText) {
const regex = /(\d+) pts/;
const match = headerText.match(regex);
if (match && match.length > 1) {
return parseInt(match[1], 10);
}
return 0;
}
function getPointsFromSpanText(spanText) {
const regex = /+ (\d+) pts/;
const match = spanText.match(regex);
if (match && match.length > 1) {
return parseInt(match[1], 10);
}
return 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, 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;
});
}
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();
}
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) {
// Calculer un délai ajusté en fonction du nombre de points nécessaires et des questions restantes
// Par exemple, vous pouvez ralentir légèrement si le score nécessaire diminue
const baseTime = 3000; // Délai de base pour un score de 500
const targetTime = 0; // Délai cible pour un score de 1700
const timeRange = baseTime - targetTime;
const adjustedTime = targetTime + ((totalPointsNeeded / targetScore) * timeRange);
}
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.');
}
}
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);
}
}
function main() {
startGame();
observeGame();
console.log('Script loaded: It will automatically start the game and handle answers.');
}
main();
})();
And the speed, you can disable throting in dev console inspect with Chrome, and disable cache, like this you can do the maximum score :)