Skip to content

Instantly share code, notes, and snippets.

@Nouchey
Last active March 11, 2026 18:02
Show Gist options
  • Select an option

  • Save Nouchey/19395c9f24be93170048ef43aa8daafa to your computer and use it in GitHub Desktop.

Select an option

Save Nouchey/19395c9f24be93170048ef43aa8daafa to your computer and use it in GitHub Desktop.
Wordle Hardcore Mode (Tampermonkey Userscript)
// ==UserScript==
// @name Wordle Hardcore Mode
// @description Adds a more restrictive “Hard Mode” to New York Times Games' Wordle to warn the player about impossible guesses
// @version 2026-03-04
// @author Nouche
// @match https://www.nytimes.com/games/*wordle*
// @run-at document-start
// @tag games
// ==/UserScript==
(function() {
'use strict';
function parseGrid() {
const grid = {
word: '',
history: [],
};
const rowEls = document.querySelectorAll('[class^="Board-"] > [class^="Row-"]');
for (const rowEl of rowEls) {
const slotEls = rowEl.querySelectorAll('[class^="Tile-"]');
if (['tbd', 'empty'].includes(Array.from(slotEls).at(-1).getAttribute('data-state'))) {
// Current guess
for (const slotEl of slotEls) {
grid.word += slotEl.textContent.trim().toUpperCase();
}
break; // no point processing further empty rows
} else {
// Prior guess
const entry = [];
for (const slotEl of slotEls) {
let color;
switch (slotEl.getAttribute('data-state')) {
case 'correct':
color = 'green';
break;
case 'present':
color = 'yellow';
break;
case 'absent':
default:
color = 'gray';
break;
}
entry.push({
letter: slotEl.textContent.trim().toUpperCase(),
color,
});
}
grid.history.push(entry);
}
}
return grid;
}
function checkRequirements(grid) {
const counts = { };
for (const letter of grid.word) {
if (!(letter in counts)) {
counts[letter] = 0;
}
counts[letter]++;
}
for (const entry of grid.history) {
const entryCounts = { };
for (const slot of entry) {
if (['green', 'yellow'].includes(slot.color)) {
const letter = slot.letter;
if (!(letter in entryCounts)) {
entryCounts[letter] = 0;
}
entryCounts[letter]++;
}
}
for (const letter in entryCounts) {
const count = counts[letter] ?? 0;
const entryCount = entryCounts[letter];
if (count < entryCount) {
return {
letter,
count: entryCount,
};
}
}
}
}
function checkEstablishments(grid) {
const length = grid.history[0].length;
for (let i = 0; i < length; i++) {
const letter = grid.word[i];
for (const entry of grid.history) {
const slot = entry[i];
if (slot.color === 'green' && slot.letter !== letter) {
return {
letter: slot.letter,
position: i,
};
}
}
}
}
function checkBans(grid) {
const counts = { };
for (const letter of grid.word) {
if (!(letter in counts)) {
counts[letter] = 0;
}
counts[letter]++;
}
for (const letter in counts) {
const count = counts[letter];
for (const entry of grid.history) {
let foundCount = 0;
let grayCount = 0;
for (const slot of entry) {
if (slot.letter === letter) {
foundCount++;
if (slot.color === 'gray') {
grayCount++;
}
}
}
if (grayCount === 0) continue;
const validCount = foundCount - grayCount;
if (count > validCount) {
return {
letter,
count: validCount,
};
}
}
}
}
function checkMisplacements(grid) {
const length = grid.history[0].length;
for (let i = 0; i < length; i++) {
const letter = grid.word[i];
for (const entry of grid.history) {
const slot = entry[i];
if (['yellow', 'gray'].includes(slot.color) && slot.letter === letter) {
return {
letter,
position: i,
};
}
}
}
}
function findOversight() {
const grid = parseGrid();
if (!grid.history[0] || grid.word.length < grid.history[0].length) return;
const requirement = checkRequirements(grid);
if (requirement) return `The “${requirement.letter}” must ${requirement.count <= 1 ? 'appear' : 'be used at least ' + (requirement.count == 2 ? 'twice' : requirement.count + ' times')}.`;
const establishment = checkEstablishments(grid);
if (establishment) return `The ${formatOrdinal(establishment.position + 1)} letter must be “${establishment.letter}”.`;
const ban = checkBans(grid);
if (ban) return `The “${ban.letter}” cannot ${ban.count <= 0 ? 'be used' : 'appear more than ' + (ban.count == 1 ? 'once' : ban.count == 2 ? 'twice' : ban.count + ' times')}.`;
const misplacement = checkMisplacements(grid);
if (misplacement) return `The ${formatOrdinal(misplacement.position + 1)} letter cannot be “${misplacement.letter}”.`;
}
function formatOrdinal(n) {
const suffixes = new Map([
['one', 'ˢᵗ'],
['two', 'ⁿᵈ'],
['few', 'ʳᵈ'],
['other', 'ᵗʰ'],
]);
const pr = new Intl.PluralRules('en-US', { type: 'ordinal' });
const rule = pr.select(n);
const suffix = suffixes.get(rule);
return `${n}${suffix}`;
}
document.addEventListener(
'keydown',
(e) => {
if (e.key !== 'Enter') return;
const oversight = findOversight();
if (oversight) {
const proceed = confirm(oversight + '\nPlay word anyway?');
if (!proceed) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation(); // this is the crucial call
}
}
},
true,
);
const copyToClipboard = navigator.clipboard.writeText.bind(navigator.clipboard);
navigator.clipboard.writeText = (text) => copyToClipboard(text.replace(/^(.*\/\d+)\*?$/m, '$1**').replace(/(?<=[🟩🟨⬛].*)\s*https:\/\/www\.nytimes\.com\/games\/.*\bwordle\b.*$/, ''));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment