sourcehypertextpublicrumpelstiltskingame.js

/* RUMPELSTILTSKIN */
/* Lovingly ripped off from Josh Wardle and the flailing New York Times’ Wordle */

const Rs = {
	settings: {
		wordLength: 5,
		validLetters: [
			"a",
			"b",
			"c",
			"d",
			"e",
			"f",
			"g",
			"h",
			"i",
			"j",
			"k",
			"l",
			"m",
			"n",
			"o",
			"p",
			"q",
			"r",
			"s",
			"t",
			"u",
			"v",
			"w",
			"x",
			"y",
			"z"
		]
	},

	lang: $("html")
		.getAttribute("lang")
		.match(/^[a-z]*/)[0],
	word: ["S", "K", "I", "R", "T"],
	turn: 0,
	guess: [],
	shareable: ``,
	resetted: false,

	addToGuess: function (letter, device) {
		if (
			Dict[Rs.lang].validLetters.includes(Rs.guess.at(-1) + letter) &&
			device == "physical"
		) {
			Rs.guess[Rs.guess.length - 1] = Rs.guess.at(-1) + letter;
			const tile =
				$$("#game-board .tile")[
					Rs.turn * Rs.settings.wordLength + Rs.guess.length - 1
				];
			tile.innerHTML = Rs.guess.at(-1);
		} else if (Dict[Rs.lang]?.validAliases?.[Rs.guess.at(-1) + letter]) {
			Rs.guess[Rs.guess.length - 1] =
				Dict[Rs.lang].validAliases[Rs.guess.at(-1) + letter];
			const tile =
				$$("#game-board .tile")[
					Rs.turn * Rs.settings.wordLength + Rs.guess.length - 1
				];
			tile.innerHTML = Rs.guess.at(-1);
		} else if (
			Rs.guess.length < 5 &&
			Dict[Rs.lang].validLetters.includes(letter)
		) {
			Rs.guess.push(letter);
			const tile =
				$$("#game-board .tile")[
					Rs.turn * Rs.settings.wordLength + Rs.guess.length - 1
				];
			tile.innerHTML = Rs.guess.at(-1);
			tile.classList.replace("empty", "tba");
		}
	},
	backspace: function () {
		if (Rs.guess.length > 0) {
			Rs.guess.pop();
			const tile =
				$$("#game-board .tile")[
					Rs.turn * Rs.settings.wordLength + Rs.guess.length
				];
			tile.innerHTML = "";
			tile.classList.replace("tba", "empty");
		}
	},
	enter: function () {
		if (Rs.guess.length == 5) {
			if (Dict[Rs.lang].validWords.includes(Rs.guess.join(""))) {
				let answer = [...Rs.word];
				let squares = [
					"⬜\ufe0f",
					"⬜\ufe0f",
					"⬜\ufe0f",
					"⬜\ufe0f",
					"⬜\ufe0f"
				];

				for (let i = 0; i < 5; i++) {
					if (Rs.guess[i] == answer[i]) {
						$$("#game-board .tile")[
							Rs.turn * Rs.settings.wordLength + i
						].classList.replace("tba", "correct");
						$(`#key-${Rs.guess[i]}`).classList.replace(
							"empty",
							"correct"
						);
						$(`#key-${Rs.guess[i]}`).classList.replace(
							"misplaced",
							"correct"
						);
						squares[i] = "✅";
						answer[i] = "";
					}
				}
				console.log(answer);
				if (
					JSON.stringify(answer) ==
					JSON.stringify(["", "", "", "", ""])
				) {
					Rs.shareable +=
						"\n" +
						squares.join("") +
						"\n" +
						Rs.strings[Rs.lang].gameUrl;
					$(
						"#toasts"
					).innerHTML += `<div class="toast long" id="game-end"><strong>${Rs.strings[
						Rs.lang
					].toasts.scoreReveal()}</strong><p>${
						Rs.strings[Rs.lang].toasts.whyNotShare
					}</p><pre>${
						Rs.shareable
					}</pre><button onclick='navigator.clipboard.writeText(Rs.shareable)'>${
						Rs.strings[Rs.lang].toasts.copyToClipboard
					}</button><br><button onclick='Rs.reset()'>${
						Rs.strings[Rs.lang].toasts.keepPlaying
					}</button></div>`;
					Rs.guess = [];
					document.removeEventListener("keydown", Rs.logKey);
					return;
				}

				for (let i = 0; i < 5; i++) {
					console.log(Rs.guess);
					console.log(answer);
					if (
						answer.includes(Rs.guess[i]) &&
						!$$("#game-board .tile")[
							Rs.turn * Rs.settings.wordLength + i
						].classList.contains("correct")
					) {
						$$("#game-board .tile")[
							Rs.turn * Rs.settings.wordLength + i
						].classList.replace("tba", "misplaced");
						$(`#key-${Rs.guess[i]}`).classList.replace(
							"empty",
							"misplaced"
						);
						squares[i] = "🟨";
						answer[answer.indexOf(Rs.guess[i])] = "";
					} else {
						$$("#game-board .tile")[
							Rs.turn * Rs.settings.wordLength + i
						].classList.replace("tba", "wrong");
						$(`#key-${Rs.guess[i]}`).classList.replace(
							"empty",
							"wrong"
						);
					}
				}

				if (Rs.turn == 4) {
					Rs.shareable += "\n" + squares.join("");
					$(
						"#toasts"
					).innerHTML += `<div class="toast long" id="game-end"><strong>${
						Rs.strings[Rs.lang].toasts.betterLuckNextTime
					}</strong><p>${Rs.strings[
						Rs.lang
					].toasts.wordReveal()}</p><pre>${
						Rs.shareable
					}</pre><button onclick='navigator.clipboard.writeText(Rs.shareable)'>${
						Rs.strings[Rs.lang].toasts.copyToClipboard
					}</button><br><button onclick='Rs.reset()'>${
						Rs.strings[Rs.lang].toasts.keepPlaying
					}</button></div>`;
					Rs.guess = [];
					document.removeEventListener("keydown", Rs.logKey);
					return;
				}

				Rs.guess = [];
				Rs.shareable += "\n" + squares.join("");
				Rs.turn++;
			} else {
				Rs.backspace();
				Rs.backspace();
				Rs.backspace();
				Rs.backspace();
				Rs.backspace();
				$("#toasts").innerHTML += Rs.strings[Rs.lang].toasts.notInDict;
				setTimeout(function () {
					$(".toast.short").remove();
				}, 3490);
			}
		}
	},
	logKey: function (e) {
		switch (e.key) {
			case "Backspace":
				e.preventDefault();
				Rs.backspace();
				break;
			case "Enter":
				Rs.enter();
				break;
			default:
				Rs.addToGuess(e.key, "physical");
		}
	},
	splitWord: function (word) {
		let array = [];
		if (Array.isArray(word)) {
			return word;
		}

		for (x of word) {
			if (Dict[Rs.lang].validLetters.includes(`${array.at(-1)}${x}`)) {
				array[array.length - 1] += x;
			} else {
				array.push(x);
			}
		}
		return array;
	},
	open: function (sel) {
		$(sel).style.display = "block";
	},
	close: function (sel) {
		$(sel).style.display = "none";
	},
	reset: function () {
		Rs.guess = [];
		Rs.turn = 0;
		Rs.resetted = true;
		Rs.shareable = `${Rs.strings[Rs.lang].gameName} ∞`;
		$("#game-end").remove();
		for (const x of $$("#game-board .tile")) {
			x.outerHTML = `<div class="tile empty"></div>`;
		}
		for (const x of $$("#keyboard button:not(.special)")) {
			x.setAttribute("class", "empty");
		}
		Rs.word = Rs.splitWord(
			Dict[Rs.lang].answers[
				Math.floor(Dict[Rs.lang].answers.length * Math.random())
			]
		);
		document.addEventListener("keydown", Rs.logKey);
	},
	toggle: function (data) {
		if ($(`html`).dataset[data] == "true") {
			$(`html`).dataset[data] = "false";
			$(`#${data}`).setAttribute("checked", null);
			localStorage.setItem(`rumpelstiltskin-${data}`, "false");
		} else {
			$(`html`).dataset[data] = "true";
			$(`#${data}`).setAttribute("checked", "true");
			localStorage.setItem(`rumpelstiltskin-${data}`, "true");
		}
	},
	changeKeyboard: function (layout) {
		keyboardOutput = "";
		for (row of Rs.keyboards[Rs.lang][layout].keys) {
			keyboardOutput += `<div class="row">`;
			for (key of row) {
				switch (key) {
					case "HALFSPACE":
						keyboardOutput += `<div class="halfwidth"></div>`;
						break;
					case "FULLSPACE":
						keyboardOutput += `<div class="fullwidth"></div>`;
						break;
					case "BACK":
						keyboardOutput += `<button class="empty special" onclick="Rs.backspace()">⌫</button>`;
						break;
					case "ENTER":
						keyboardOutput += `<button class="empty special" onclick="Rs.enter()">${
							Rs.strings[Rs.lang].keyboard.enter
						}</button>`;
						break;
					default:
						keyboardOutput += `<button class="${
							$(`#key-${key}`)
								? $(`#key-${key}`).getAttribute("class")
								: "empty"
						}" onclick="Rs.addToGuess('${key}','onscreen')" id="key-${key}">${key}</button>`;
				}
			}
			keyboardOutput += `</div>`;
		}
		$("#keyboard").innerHTML = keyboardOutput;
		$(`html`).dataset.keyboardLayout = layout;
		localStorage.setItem(`rumpelstiltskin-keyboard-layout`, layout);
	},
	changeTheme: function () {
		$(`html`).dataset.theme = $(`#theme`).value;

		if ($(`#theme [selected]`)) {
			$(`#theme [selected]`).removeAttribute("selected");
		}

		$(`#theme [value="${$(`#theme`).value}"]`).setAttribute(
			"selected",
			"true"
		);
		localStorage.setItem("rumpelstiltskin-theme", $(`#theme`).value);
	},

	keyboards: {
		en: {
			qwerty: {
				name: "Qwerty",
				keys: [
					["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
					[
						"HALFSPACE",
						"a",
						"s",
						"d",
						"f",
						"g",
						"h",
						"j",
						"k",
						"l",
						"HALFSPACE"
					],
					["ENTER", "z", "x", "c", "v", "b", "n", "m", "BACK"]
				]
			},
			azerty: {
				name: "Azerty",
				keys: [
					["a", "z", "e", "r", "t", "y", "u", "i", "o", "p"],
					["q", "s", "d", "f", "g", "h", "j", "k", "l", "m"],
					["ENTER", "w", "x", "c", "v", "b", "n", "BACK"]
				]
			},
			qwertz: {
				name: "Qwertz",
				keys: [
					["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"],
					[
						"HALFSPACE",
						"a",
						"s",
						"d",
						"f",
						"g",
						"h",
						"j",
						"k",
						"l",
						"HALFSPACE"
					],
					["ENTER", "y", "x", "c", "v", "b", "n", "m", "BACK"]
				]
			},
			dvorak: {
				name: "Dvorak",
				keys: [
					["ENTER", "BACK", "p", "y", "f", "g", "c", "r", "l"],
					["a", "o", "e", "u", "i", "d", "h", "t", "n", "s"],
					["FULLSPACE", "q", "j", "k", "x", "b", "m", "w", "v", "z"]
				]
			},
			colemak: {
				name: "Colemak",
				keys: [
					["q", "w", "f", "p", "g", "j", "l", "u", "y"],
					["a", "r", "s", "t", "d", "h", "n", "e", "i", "o"],
					["ENTER", "z", "x", "c", "v", "b", "k", "m", "BACK"]
				]
			},
			alphabetical: {
				name: "Alphabetical",
				keys: [
					["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
					["k", "l", "m", "n", "o", "p", "q", "r", "s", "t"],
					["ENTER", "u", "v", "w", "x", "y", "z", "BACK"]
				]
			}
		},

		nl: {
			qwerty: {
				name: "Qwerty",
				keys: [
					["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
					["a", "s", "d", "f", "g", "h", "j", "k", "l", "ij"],
					["ENTER", "z", "x", "c", "v", "b", "n", "m", "BACK"]
				]
			},
			azerty: {
				name: "Azerty",
				keys: [
					["a", "z", "e", "r", "t", "y", "u", "i", "o", "p"],
					["q", "s", "d", "f", "g", "h", "j", "k", "l", "m"],
					["ENTER", "w", "x", "c", "v", "b", "n", "ij", "BACK"]
				]
			},
			qwertz: {
				name: "Qwertz",
				keys: [
					["q", "w", "e", "r", "t", "z", "u", "i", "o", "p"],
					["a", "s", "d", "f", "g", "h", "j", "k", "l", "ij"],
					["ENTER", "y", "x", "c", "v", "b", "n", "m", "BACK"]
				]
			},
			dvorak: {
				name: "Dvorak",
				keys: [
					["ENTER", "BACK", "p", "y", "f", "g", "c", "r", "l"],
					["a", "o", "e", "u", "i", "d", "h", "t", "n", "s"],
					["ij", "q", "j", "k", "x", "b", "m", "w", "v", "z"]
				]
			},
			colemak: {
				name: "Colemak",
				keys: [
					["q", "w", "f", "p", "g", "j", "l", "u", "y", "ij"],
					["a", "r", "s", "t", "d", "h", "n", "e", "i", "o"],
					["ENTER", "z", "x", "c", "v", "b", "k", "m", "BACK"]
				]
			},
			alphabetical: {
				name: "Alfabetisch",
				keys: [
					["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
					["k", "l", "m", "n", "o", "p", "q", "r", "s", "t"],
					["ENTER", "u", "v", "w", "x", "ij", "y", "z", "BACK"]
				]
			}
		},

		eo: {
			qwerty: {
				name: "ŜĜERTŬ",
				keys: [
					["ŝ", "ĝ", "e", "r", "t", "ŭ", "u", "i", "o", "p"],
					["a", "s", "d", "f", "g", "h", "j", "k", "l", "ĵ"],
					["ENTER", "z", "ĉ", "c", "v", "b", "n", "m", "ĥ", "BACK"]
				]
			},
			azerty: {
				name: "AZERTŬ",
				keys: [
					["a", "z", "e", "r", "t", "ŭ", "u", "i", "o", "p"],
					["ŝ", "s", "d", "f", "g", "h", "j", "k", "l", "m"],
					["ENTER", "ĝ", "ĵ", "ĉ", "c", "v", "b", "n", "ĥ", "BACK"]
				]
			},
			qwertz: {
				name: "ŜĜERTZ",
				keys: [
					["ŝ", "ĝ", "e", "r", "t", "z", "u", "i", "o", "p"],
					["a", "s", "d", "f", "g", "h", "j", "k", "l", "ĵ"],
					["ENTER", "ŭ", "ĉ", "c", "v", "b", "n", "m", "ĥ", "BACK"]
				]
			},
			dvorak: {
				name: "Dvorak",
				keys: [
					["ENTER", "BACK", "p", "ĝ", "f", "g", "c", "r", "l", "ĵ"],
					["a", "o", "e", "u", "i", "d", "h", "t", "n", "s"],
					["ĥ", "ĉ", "j", "k", "ŝ", "b", "m", "ŭ", "v", "z"]
				]
			},
			colemak: {
				name: "Kolmak",
				keys: [
					["ŝ", "ĝ", "f", "p", "g", "j", "l", "u", "ŭ", "ĵ"],
					["a", "r", "s", "t", "d", "h", "n", "e", "i", "o"],
					["ENTER", "z", "ĉ", "c", "v", "b", "k", "m", "ĥ", "BACK"]
				]
			},
			alphabetical: {
				name: "Alfabeta",
				keys: [
					["a", "b", "c", "ĉ", "d", "e", "f", "g", "ĝ", "h"],
					["ĥ", "i", "j", "ĵ", "k", "l", "m", "n", "o", "p"],
					["ENTER", "r", "s", "ŝ", "t", "u", "ŭ", "v", "z", "BACK"]
				]
			}
		}
	},

	strings: {
		en: {
			gameName: `Rumpelstiltskin`,
			currentDate: `${new Date().getFullYear()} ${
				[
					"Jan",
					"Feb",
					"Mar",
					"Apr",
					"May",
					"Jun",
					"Jul",
					"Aug",
					"Sep",
					"Oct",
					"Nov",
					"Dec"
				][new Date().getMonth()]
			} ${new Date().getDate()}`,
			gameUrl: `https://satyrs.eu/rs-en`,
			toasts: {
				notInDict: `<div class="toast short">That word isn’t in our dictionary, i’m afraid!</div>`,
				keepPlaying: `Keep playing indefinitely`,
				copyToClipboard: `Copy to clipboard`,
				whyNotShare: `Why not share your result with your friends?`,
				wordReveal: () =>
					`The correct word was <b>${Rs.word
						.join("")
						.toUpperCase()}</b>. Why not commiserate with your friends?`,
				betterLuckNextTime: `Ah, gubbins. Better luck next time.`,
				scoreReveal: () =>
					`You got ${
						[
							"a hole in one",
							"an albatross",
							"a birdie",
							"a par",
							"a bogey",
							"a double bogey"
						][Rs.turn]
					}!`
			},
			keyboard: {
				enter: `Enter`
			}
		},
		nl: {
			gameName: `Repelsteeltje`,
			currentDate: `${new Date().getDate()} ${
				[
					"januari",
					"februari",
					"maart",
					"april",
					"mei",
					"juni",
					"juli",
					"augustus",
					"september",
					"oktober",
					"november",
					"december"
				][new Date().getMonth()]
			} ${new Date().getFullYear()}`,
			gameUrl: `https://satyrs.eu/rs-nl`,
			toasts: {
				notInDict: `<div class="toast short">Dat woord staat niet in ons woordenboek.</div>`,
				keepPlaying: `Blijven spelen`,
				copyToClipboard: `Naar klembord kopiëren`,
				whyNotShare: `Wil je je resultaat met je vrienden delen?`,
				wordReveal: () =>
					`Het juiste woord was <b>${Rs.word
						.join("")
						.toUpperCase()}</b>. Wil je je mislukking met je vrienden delen?`,
				betterLuckNextTime: `Ah, chips. Volgende keer beter!`,
				scoreReveal: () =>
					`Je hebt een ${
						[
							"hole-in-one",
							"albatros",
							"birdie",
							"par",
							"bogey",
							"double bogey"
						][Rs.turn]
					} gemaakt!`
			},
			keyboard: {
				enter: `Enter`
			}
		},
		eo: {
			gameName: `Tremotino`,
			currentDate: `${new Date().getDate()}-an de ${
				[
					"januaro",
					"februaro",
					"marto",
					"aprilo",
					"majo",
					"junio",
					"julio",
					"auĝusto",
					"septembro",
					"oktobro",
					"novembro",
					"decembro"
				][new Date().getMonth()]
			} ${new Date().getFullYear()}`,
			gameUrl: `https://satyrs.eu/rs-eo`,
			toasts: {
				notInDict: `<div class="toast short">Nia vortaro ne enhavas tiun vorton.</div>`,
				keepPlaying: `Plue ludi`,
				copyToClipboard: `Kopii al kliptabulo`,
				whyNotShare: `Ĉu vi montrus vian rezulton al viaj amikoj?`,
				wordReveal: () =>
					`La ĝusta vorto estis <b>${Rs.word
						.join("")
						.toUpperCase()}</b>. Ĉu vi montrus vian eraron al viaj amikoj?`,
				betterLuckNextTime: `Ho, ve. Pli bonŝancon venontfoje!`,
				scoreReveal: () =>
					`Vi divenis la vorton je ${
						[
							"unu provo",
							"du provoj",
							"tri provoj",
							"kvar provoj",
							"kvin provoj",
							"ses provoj"
						][Rs.turn]
					}!`
			},
			keyboard: {
				enter: `Enigi`
			}
		}
	}
};

for (const x of ["highContrast", "legibleText"]) {
	const storedValue = localStorage.getItem(`rumpelstiltskin-${x}`);
	if (storedValue) {
		$(`html`).dataset[x] = storedValue;
		if (storedValue == "true") {
			$(`#${x}`).checked = true;
		}
	}
}

if (localStorage.getItem(`rumpelstiltskin-keyboard-layout`)) {
	Rs.changeKeyboard(localStorage.getItem(`rumpelstiltskin-keyboard-layout`));
} else {
	Rs.changeKeyboard("qwerty");
}

if (localStorage.getItem(`rumpelstiltskin-theme`)) {
	$(
		`#theme [value="${localStorage.getItem(`rumpelstiltskin-theme`)}"]`
	).setAttribute("selected", "true");
	Rs.changeTheme();
}

Rs.word = Rs.splitWord(
	Dict[Rs.lang].answers[
		Math.floor(
			(new Date() - new Date("2022-01-01 00:00")) / (1000 * 60 * 60 * 24)
		) % Dict[Rs.lang].answers.length
	]
);
Rs.shareable = `${Rs.strings[Rs.lang].gameName} ${
	Rs.strings[Rs.lang].currentDate
}`;
document.addEventListener("keydown", Rs.logKey);