Added some quality of life features
All checks were successful
/ publish (push) Successful in 1m25s

- Now preload the letters on successive rows
- Hid the keyboard during intermissions
- Added a give-up easter egg when tapping the enter six times
- Reshuffled the letters
This commit is contained in:
Leon Mika 2025-10-30 21:50:58 +11:00
parent 566f55ed12
commit a2211030a0
6 changed files with 478 additions and 400 deletions

File diff suppressed because one or more lines are too long

View file

@ -49,6 +49,16 @@ export default class extends Controller {
} }
} }
onShow() {
let keyboard = this.element;
keyboard.style.display = "block";
}
onHide() {
let keyboard = this.element;
keyboard.style.display = "none";
}
colorizeKeys(ev) { colorizeKeys(ev) {
let hits = ev.detail.hits; let hits = ev.detail.hits;

View file

@ -1,256 +1,310 @@
import { Controller } from "https://unpkg.com/@hotwired/stimulus@v3.2.2/dist/stimulus.js" import {Controller} from "https://unpkg.com/@hotwired/stimulus@v3.2.2/dist/stimulus.js"
import { GUESS_RESULT, MARKERS, GameController } from "../models/gamecontroller.js"; import {GameController, GUESS_RESULT, MARKERS} from "../models/gamecontroller.js";
import { WordSource } from "../models/words.js"; import {WordSource} from "../models/words.js";
const CONSECUTIVE_ENTERS_TO_GIVE_UP = 6;
export default class extends Controller { export default class extends Controller {
static targets = ["row", "playfield", "topMessage", "nextPuzzleButtons", "loader"]; static targets = ["row", "playfield", "topMessage", "nextPuzzleButtons", "loader"];
static outlets = ["overlay"]; static outlets = ["overlay"];
async connect() { async connect() {
this._wordSource = new WordSource(); this._wordSource = new WordSource();
this._gameController = new GameController(this._wordSource); this._gameController = new GameController(this._wordSource);
this._consectiveEnters = 0;
await this._gameController.start(); await this._gameController.start();
this._buildPlayfield(); this._buildPlayfield();
this.loaderTarget.classList.add("hide"); this.loaderTarget.classList.add("hide");
}
tappedKey(key) {
this._addLetter(key);
}
_addLetter(letter) {
if (this._activeRowIndex < 0) {
return;
} else if (this._activeLetter >= this._gameController.wordLength()) {
return;
} }
let rowElem = this.rowTargets[this._activeRowIndex]; tappedKey(key) {
let colElem = rowElem.querySelectorAll("span")[this._activeLetter]; this._consectiveEnters = 0;
this._addLetter(key);
colElem.innerText = letter.toUpperCase();
colElem.classList.remove("hint");
this._activeLetter += 1;
}
enterGuess() {
if (this._activeLetter >= this._gameController.wordLength()) {
let rowElem = this.rowTargets[this._activeRowIndex];
this._verifyGuess(rowElem);
}
}
loadDef(ev) {
ev.preventDefault()
let word = this._gameController.currentWord();
window.open(`https://www.ecosia.org/search?q=define+${word}`, "_blank");
}
tappedBackspace() {
if (this._activeLetter == 0) {
return;
} }
this._activeLetter -= 1; _addLetter(letter) {
let rowElem = this.rowTargets[this._activeRowIndex]; if (this._activeRowIndex < 0) {
let colElem = rowElem.querySelectorAll("span")[this._activeLetter]; return;
} else if (this._activeLetter >= this._gameController.wordLength()) {
return;
}
let colHint = colElem.dataset["hint"]; let rowElem = this.rowTargets[this._activeRowIndex];
if (colHint) { this._addLetterAtRow(rowElem, letter);
colElem.classList.add("hint");
colElem.innerText = colHint;
} else {
colElem.innerText = "";
} }
}
async _verifyGuess(rowElem) { _addLetterAtRow(rowElem, letter) {
let guessedWord = Array.from(rowElem.querySelectorAll("span")).map((x) => x.innerText).join(""); let colElem = rowElem.querySelectorAll("span")[this._activeLetter];
let results = this._gameController.submitGuess(guessedWord);
switch (results.guessResult) { colElem.innerText = letter.toUpperCase();
case GUESS_RESULT.FOUL: colElem.classList.remove("hint");
this.overlayOutlet.showMessage("Not in dictionary");
let newRow = this._buildPlayfieldRow(this._gameController.wordLength()); this._activeLetter += 1;
this._showHints(newRow); }
rowElem.replaceWith(newRow);
this._activeLetter = 0;
window.dispatchEvent(new CustomEvent("guessResults", { enterGuess() {
detail: results if (this._activeLetter >= this._gameController.wordLength()) {
})); let rowElem = this.rowTargets[this._activeRowIndex];
break; this._verifyGuess(rowElem);
case GUESS_RESULT.MISS: this._consectiveEnters = 0;
await this._colorizeRow(rowElem, results, true); } else {
this._broadcastGuessResults(results); this._consectiveEnters += 1;
if (this._consectiveEnters >= CONSECUTIVE_ENTERS_TO_GIVE_UP) {
this._giveUp();
this._consectiveEnters = 0;
}
}
}
this._activeRowIndex += 1; loadDef(ev) {
if (this._activeRowIndex >= this._gameController.guesses()) { ev.preventDefault()
this._revealAnswer();
} else { let word = this._gameController.currentWord();
window.open(`https://www.ecosia.org/search?q=define+${word}`, "_blank");
}
_giveUp() {
if (confirm("Are you sure you want to reveal the answer?")) {
this._gameController.giveUp();
this._revealAnswer();
}
}
tappedBackspace() {
this._consectiveEnters = 0;
if (this._activeLetter == 0) {
return;
}
this._activeLetter -= 1;
let rowElem = this.rowTargets[this._activeRowIndex];
let colElem = rowElem.querySelectorAll("span")[this._activeLetter];
let colHint = colElem.dataset["hint"];
if (colHint) {
colElem.classList.add("hint");
colElem.innerText = colHint;
} else {
colElem.innerText = "";
}
}
async _verifyGuess(rowElem) {
let guessedWord = Array.from(rowElem.querySelectorAll("span")).map((x) => x.innerText).join("");
let results = this._gameController.submitGuess(guessedWord);
switch (results.guessResult) {
case GUESS_RESULT.FOUL:
this.overlayOutlet.showMessage("Not in dictionary");
let newRow = this._buildPlayfieldRow(this._gameController.wordLength());
this._showHints(newRow);
rowElem.replaceWith(newRow);
this._activeLetter = 0;
if (this._activeRowIndex > 0) {
this._prefillFirstFewLetters(newRow);
}
window.dispatchEvent(new CustomEvent("guessResults", {
detail: results
}));
break;
case GUESS_RESULT.MISS:
await this._colorizeRow(rowElem, results, true);
this._broadcastGuessResults(results);
this._activeRowIndex += 1;
if (this._activeRowIndex >= this._gameController.guesses()) {
this._revealAnswer();
} else {
this._activeLetter = 0;
this._showHints(this.rowTargets[this._activeRowIndex], results.markers);
this._prefillFirstFewLetters(this.rowTargets[this._activeRowIndex]);
}
break;
case GUESS_RESULT.WIN:
await this._colorizeRow(rowElem, results, true);
this._broadcastGuessResults(results);
this._showWin();
break;
}
}
async loadNextPuzzle(ev) {
ev.preventDefault();
this.loaderTarget.classList.remove("hide");
if (await this._gameController.nextWord()) {
this._buildPlayfield();
} else {
this.overlayOutlet.showMessage("No more words available.");
}
this.loaderTarget.classList.add("hide");
}
_showWin() {
this.topMessageTarget.innerText = "Hooray! You did it.";
this.nextPuzzleButtonsTarget.classList.remove("hide");
this._setKeyboardVisibility(false);
}
_revealAnswer() {
this.topMessageTarget.innerText = this._gameController.currentWord().toUpperCase();
this.nextPuzzleButtonsTarget.classList.remove("hide");
this._setKeyboardVisibility(false);
}
_buildPlayfield() {
let rows = this._gameController.guesses();
let wordLength = this._gameController.wordLength();
let {currentGuesses, wasWin, wasGiveUp} = this._gameController.currentState();
this._activeRowIndex = currentGuesses.length;
this._activeLetter = 0; this._activeLetter = 0;
this._showHints(this.rowTargets[this._activeRowIndex], results.markers);
}
break; window.dispatchEvent(new CustomEvent("resetKeyColors"));
case GUESS_RESULT.WIN:
await this._colorizeRow(rowElem, results, true);
this._broadcastGuessResults(results);
this._showWin();
break;
}
}
async loadNextPuzzle(ev) { let newRows = [];
ev.preventDefault(); let lastGuessResults = null;
for (let r = 0; r < rows; r++) {
let currentGuess = null;
let currentGuessResults = null;
this.loaderTarget.classList.remove("hide"); if (r < currentGuesses.length) {
currentGuess = currentGuesses[r];
currentGuessResults = this._gameController.checkGuess(currentGuess);
}
if (await this._gameController.nextWord()) { newRows.push(this._buildPlayfieldRow(wordLength, currentGuess, currentGuessResults));
this._buildPlayfield(); if (currentGuessResults) {
} else { lastGuessResults = currentGuessResults;
this.overlayOutlet.showMessage("No more words available."); }
}
if (lastGuessResults != null) {
this._broadcastGuessResults(lastGuessResults);
}
if (wasWin) {
this._showWin();
} else if ((currentGuesses.length >= rows) || wasGiveUp) {
// User has already used up all their guesses so just show the results;
this._revealAnswer();
} else {
this._showHints(newRows[currentGuesses.length]);
if (currentGuesses.length > 0) {
this._prefillFirstFewLetters(newRows[currentGuesses.length]);
}
this.topMessageTarget.innerHTML = "&nbsp;"
this.nextPuzzleButtonsTarget.classList.add("hide");
this._setKeyboardVisibility(true);
}
this.playfieldTarget.replaceChildren.apply(this.playfieldTarget, newRows);
} }
this.loaderTarget.classList.add("hide"); _buildPlayfieldRow(wordLength, currentGuess, currentGuessResults) {
} let divElem = document.createElement("div");
divElem.classList.add("row");
divElem.setAttribute("data-playfield-target", "row");
_showWin() { for (let c = 0; c < wordLength; c++) {
this.topMessageTarget.innerText = "Hooray! You did it."; let letterSpan = document.createElement("span");
this.nextPuzzleButtonsTarget.classList.remove("hide"); if (currentGuess) {
} letterSpan.innerText = currentGuess[c].toUpperCase();
}
divElem.appendChild(letterSpan);
}
_revealAnswer() { if (currentGuess) {
this.topMessageTarget.innerText = this._gameController.currentWord().toUpperCase(); this._colorizeRow(divElem, currentGuessResults, false);
this.nextPuzzleButtonsTarget.classList.remove("hide"); }
}
_buildPlayfield() { return divElem;
let rows = this._gameController.guesses();
let wordLength = this._gameController.wordLength();
let {currentGuesses, wasWin} = this._gameController.currentState();
this._activeRowIndex = currentGuesses.length;
this._activeLetter = 0;
window.dispatchEvent(new CustomEvent("resetKeyColors"));
let newRows = [];
let lastGuessResults = null;
for (let r = 0; r < rows; r++) {
let currentGuess = null;
let currentGuessResults = null;
if (r < currentGuesses.length) {
currentGuess = currentGuesses[r];
currentGuessResults = this._gameController.checkGuess(currentGuess);
}
newRows.push(this._buildPlayfieldRow(wordLength, currentGuess, currentGuessResults));
if (currentGuessResults) {
lastGuessResults = currentGuessResults;
}
} }
if (lastGuessResults != null) { async _colorizeRow(row, results, delayed) {
this._broadcastGuessResults(lastGuessResults); let markers = results.markers;
if (delayed) {
row.classList.add("animated-colors");
}
for (let i = 0; i < this._gameController.wordLength(); i++) {
this._colorizeCell(row, results, i);
}
if (delayed) {
return new Promise(resolve => {
row.children[row.children.length - 1].addEventListener("transitionend", () => {
resolve();
});
});
}
return Promise.resolve();
} }
if (wasWin) { _broadcastGuessResults(results) {
this._showWin(); window.dispatchEvent(new CustomEvent("guessResults", {
} else if (currentGuesses.length >= rows) { detail: results
// User has already used up all their guesses so just show the results; }));
this._revealAnswer();
} else {
this._showHints(newRows[currentGuesses.length]);
this.topMessageTarget.innerHTML = "&nbsp;"
this.nextPuzzleButtonsTarget.classList.add("hide");
} }
this.playfieldTarget.replaceChildren.apply(this.playfieldTarget, newRows); _colorizeCell(row, results, cellIndex) {
} let markers = results.markers;
switch (markers[cellIndex]) {
_buildPlayfieldRow(wordLength, currentGuess, currentGuessResults) { case MARKERS.RIGHT_POS:
let divElem = document.createElement("div"); row.children[cellIndex].classList.add("right-pos");
divElem.classList.add("row"); break;
divElem.setAttribute("data-playfield-target", "row"); case MARKERS.RIGHT_CHAR:
row.children[cellIndex].classList.add("right-char");
for (let c = 0; c < wordLength; c++) { break;
let letterSpan = document.createElement("span"); case MARKERS.MISS:
if (currentGuess) { row.children[cellIndex].classList.add("miss");
letterSpan.innerText = currentGuess[c].toUpperCase(); break;
} }
divElem.appendChild(letterSpan);
} }
if (currentGuess) { _showHints(row) {
this._colorizeRow(divElem, currentGuessResults, false); let hint = this._gameController.showHint();
for (let i = 0; i < this._gameController.wordLength(); i++) {
if (hint[i]) {
let colElem = row.children[i];
colElem.classList.add("hint");
colElem.innerText = hint[i].toUpperCase();
colElem.dataset["hint"] = hint[i].toUpperCase();
}
}
} }
return divElem; _prefillFirstFewLetters(row) {
} let hint = this._gameController.showHint();
async _colorizeRow(row, results, delayed) { for (let i = 0; i < this._gameController.wordLength(); i++) {
let markers = results.markers; if (hint[i]) {
this._addLetterAtRow(row, hint[i]);
if (delayed) { } else {
row.classList.add("animated-colors"); return;
}
}
} }
for (let i = 0; i < this._gameController.wordLength(); i++) { _setKeyboardVisibility(visible) {
this._colorizeCell(row, results, i); if (visible) {
window.dispatchEvent(new CustomEvent("showKeyboard"));
} else {
window.dispatchEvent(new CustomEvent("hideKeyboard"));
}
} }
if (delayed) {
return new Promise(resolve => {
row.children[row.children.length - 1].addEventListener("transitionend", () => {
resolve();
});
});
}
return Promise.resolve();
}
_broadcastGuessResults(results) {
window.dispatchEvent(new CustomEvent("guessResults", {
detail: results
}));
}
_colorizeCell(row, results, cellIndex) {
let markers = results.markers;
switch (markers[cellIndex]) {
case MARKERS.RIGHT_POS:
row.children[cellIndex].classList.add("right-pos");
break;
case MARKERS.RIGHT_CHAR:
row.children[cellIndex].classList.add("right-char");
break;
case MARKERS.MISS:
row.children[cellIndex].classList.add("miss");
break;
}
}
_showHints(row) {
let hint = this._gameController.showHint();
for (let i = 0; i < this._gameController.wordLength(); i++) {
if (hint[i]) {
let colElem = row.children[i];
colElem.classList.add("hint");
colElem.innerText = hint[i].toUpperCase();
colElem.dataset["hint"] = hint[i].toUpperCase();
}
}
}
} }

View file

@ -1,7 +1,7 @@
export const GUESS_RESULT = { export const GUESS_RESULT = {
MISS: 'm', MISS: 'm',
WIN: 'w', WIN: 'w',
FOUL: 'f' FOUL: 'f'
}; };
export const MARKERS = { export const MARKERS = {
@ -12,207 +12,217 @@ export const MARKERS = {
}; };
class ProgressionState { class ProgressionState {
getProgression() { getProgression() {
let prog = localStorage.getItem('progression'); let prog = localStorage.getItem('progression');
if (prog) { if (prog) {
return JSON.parse(prog); return JSON.parse(prog);
}
this.clearProgression("");
return prog;
} }
this.clearProgression(""); clearProgression(shuffleId) {
return prog; let prog = {
} shuffleId: shuffleId,
wordLength: 4,
wordIndex: {"4": 0, "5": 0, "6": 0, "7": 0},
currentGuesses: []
};
localStorage.setItem('progression', JSON.stringify(prog));
return prog;
}
clearProgression(shuffleId) { saveProgression(prog) {
let prog = { localStorage.setItem('progression', JSON.stringify(prog));
shuffleId: shuffleId, }
wordLength: 4,
wordIndex: {"4": 0, "5": 0, "6": 0, "7": 0},
currentGuesses: []
};
localStorage.setItem('progression', JSON.stringify(prog));
return prog;
}
saveProgression(prog) {
localStorage.setItem('progression', JSON.stringify(prog));
}
} }
export class GameController { export class GameController {
constructor(wordSource) { constructor(wordSource) {
this._wordSource = wordSource; this._wordSource = wordSource;
this._progressionState = new ProgressionState(); this._progressionState = new ProgressionState();
}
async start() {
let prog = this._progressionState.getProgression();
if (await this._wordSource.needToResetProgression(prog)) {
console.log("Clearing patten shuffle id")
prog = this._progressionState.clearProgression(await this._wordSource.getPattenShuffleID());
} }
this._currentWord = await this._wordSource.getCurrentWord(prog); async start() {
this._guesses = 6; let prog = this._progressionState.getProgression();
console.log("The current word: " + this._currentWord); if (await this._wordSource.needToResetProgression(prog)) {
} console.log("Clearing patten shuffle id")
prog = this._progressionState.clearProgression(await this._wordSource.getPattenShuffleID());
wordLength() {
this._checkHasStarted();
return this._currentWord.length;
}
guesses() {
this._checkHasStarted();
return this._guesses;
}
currentState() {
let prog = this._progressionState.getProgression();
if (!prog || !prog.currentGuesses) {
return [];
}
return {
wasWin: prog.wasWin,
currentGuesses: prog.currentGuesses
};
}
currentWord() {
this._checkHasStarted();
return this._currentWord;
}
async nextWord() {
// Increment the progress
let prog = this._progressionState.getProgression();
prog.wordIndex[prog.wordLength + ""] += 1;
//prog.wordLength = (((Math.random() * 23) | 0) / 10 | 0) + 4;
prog.wordLength = ((Math.random() * 4) | 0) + 4;
prog.currentGuesses = [];
prog.wasWin = false;
this._progressionState.saveProgression(prog);
this._currentWord = await this._wordSource.getCurrentWord(prog);
//this._guesses = Math.max(this._currentWord.length, 5) + 1;
this._guesses = 6;
return true;
}
showHint() {
let hints = new Array(this._currentWord.length);
let priorGuesses = this._progressionState.getProgression().currentGuesses;
hints[0] = this._currentWord[0];
for (let i = 1; i < this._currentWord.length; i++) {
for (let guess of priorGuesses) {
if (guess[i] === this._currentWord[i]) {
hints[i] = this._currentWord[i];
} }
}
this._currentWord = await this._wordSource.getCurrentWord(prog);
this._guesses = 6;
console.log("The current word: " + this._currentWord);
} }
return hints; wordLength() {
} this._checkHasStarted();
return this._currentWord.length;
submitGuess(guess) {
guess = guess.toLowerCase();
let results = this.checkGuess(guess);
// Add this guess to the progression state
if (results.guessResult !== GUESS_RESULT.FOUL) {
let prog = this._progressionState.getProgression();
prog.currentGuesses.push(guess);
prog.wasWin = results.guessResult === GUESS_RESULT.WIN;
this._progressionState.saveProgression(prog);
} }
return results; guesses() {
} this._checkHasStarted();
return this._guesses;
checkGuess(guess) {
this._checkHasStarted();
guess = guess.toLowerCase();
if (guess.length != this._currentWord.length) {
throw Error(`Expected length to be ${this._currentWord.length} but was ${guess.length}`);
} }
// Check correct placements currentState() {
let guessResult; let prog = this._progressionState.getProgression();
let markers = new Array(guess.length); if (!prog || !prog.currentGuesses) {
let misses = {}; return {
let hits = {}; currentGuesses: []
};
if (!this._wordSource.isWord(guess)) { }
hits = {}; return {
for (let i = 0; i < guess.length; i++) { wasWin: prog.wasWin,
hits[guess[i]] = MARKERS.ATTEMPTED; currentGuesses: prog.currentGuesses,
} wasGiveUp: prog.wasGiveUp,
};
return {
hits: hits,
guessResult: GUESS_RESULT.FOUL,
};
} }
for (let i = 0; i < guess.length; i++) { currentWord() {
if (guess[i] == this._currentWord[i]) { this._checkHasStarted();
markers[i] = MARKERS.RIGHT_POS; return this._currentWord;
hits[guess[i]] = MARKERS.RIGHT_POS; }
} else {
if (this._currentWord[i] in misses) { async nextWord() {
misses[this._currentWord[i]] += 1; // Increment the progress
let prog = this._progressionState.getProgression();
prog.wordIndex[prog.wordLength + ""] += 1;
//prog.wordLength = (((Math.random() * 23) | 0) / 10 | 0) + 4;
prog.wordLength = ((Math.random() * 4) | 0) + 4;
prog.currentGuesses = [];
prog.wasWin = false;
prog.wasGiveUp = false;
this._progressionState.saveProgression(prog);
this._currentWord = await this._wordSource.getCurrentWord(prog);
//this._guesses = Math.max(this._currentWord.length, 5) + 1;
this._guesses = 6;
return true;
}
showHint() {
let hints = new Array(this._currentWord.length);
let priorGuesses = this._progressionState.getProgression().currentGuesses;
hints[0] = this._currentWord[0];
for (let i = 1; i < this._currentWord.length; i++) {
for (let guess of priorGuesses) {
if (guess[i] === this._currentWord[i]) {
hints[i] = this._currentWord[i];
}
}
}
return hints;
}
submitGuess(guess) {
guess = guess.toLowerCase();
let results = this.checkGuess(guess);
// Add this guess to the progression state
if (results.guessResult !== GUESS_RESULT.FOUL) {
let prog = this._progressionState.getProgression();
prog.currentGuesses.push(guess);
prog.wasWin = results.guessResult === GUESS_RESULT.WIN;
this._progressionState.saveProgression(prog);
}
return results;
}
giveUp() {
let prog = this._progressionState.getProgression();
prog.wasGiveUp = true;
this._progressionState.saveProgression(prog);
}
checkGuess(guess) {
this._checkHasStarted();
guess = guess.toLowerCase();
if (guess.length != this._currentWord.length) {
throw Error(`Expected length to be ${this._currentWord.length} but was ${guess.length}`);
}
// Check correct placements
let guessResult;
let markers = new Array(guess.length);
let misses = {};
let hits = {};
if (!this._wordSource.isWord(guess)) {
hits = {};
for (let i = 0; i < guess.length; i++) {
hits[guess[i]] = MARKERS.ATTEMPTED;
}
return {
hits: hits,
guessResult: GUESS_RESULT.FOUL,
};
}
for (let i = 0; i < guess.length; i++) {
if (guess[i] == this._currentWord[i]) {
markers[i] = MARKERS.RIGHT_POS;
hits[guess[i]] = MARKERS.RIGHT_POS;
} else {
if (this._currentWord[i] in misses) {
misses[this._currentWord[i]] += 1;
} else {
misses[this._currentWord[i]] = 1;
}
}
}
// Check words that are wrong placement but are in the word
// Distribute based on the words position
for (let i = 0; i < guess.length; i++) {
if (markers[i]) {
continue;
}
if (misses[guess[i]] && misses[guess[i]] > 0) {
misses[guess[i]] -= 1;
markers[i] = MARKERS.RIGHT_CHAR;
if (!hits[guess[i]]) {
hits[guess[i]] = MARKERS.RIGHT_CHAR;
}
} else {
if (!hits[guess[i]]) {
hits[guess[i]] = MARKERS.MISS;
}
markers[i] = MARKERS.MISS;
}
}
let isRight = markers.filter((x) => x == MARKERS.RIGHT_POS).length == this._currentWord.length;
if (isRight) {
guessResult = GUESS_RESULT.WIN;
} else { } else {
misses[this._currentWord[i]] = 1; guessResult = GUESS_RESULT.MISS;
} }
}
return {
guessResult: guessResult,
hits: hits,
markers: markers
};
} }
// Check words that are wrong placement but are in the word _checkHasStarted() {
// Distribute based on the words position if (!this._currentWord) {
for (let i = 0; i < guess.length; i++) { throw new Error("call start() first");
if (markers[i]) {
continue;
}
if (misses[guess[i]] && misses[guess[i]] > 0) {
misses[guess[i]] -= 1;
markers[i] = MARKERS.RIGHT_CHAR;
if (!hits[guess[i]]) {
hits[guess[i]] = MARKERS.RIGHT_CHAR;
} }
} else {
if (!hits[guess[i]]) {
hits[guess[i]] = MARKERS.MISS;
}
markers[i] = MARKERS.MISS;
}
} }
let isRight = markers.filter((x) => x == MARKERS.RIGHT_POS).length == this._currentWord.length;
if (isRight) {
guessResult = GUESS_RESULT.WIN;
} else {
guessResult = GUESS_RESULT.MISS;
}
return {
guessResult: guessResult,
hits: hits,
markers: markers
};
}
_checkHasStarted() {
if (!this._currentWord) {
throw new Error("call start() first");
}
}
} }

View file

@ -69,7 +69,8 @@ div.playfield div[data-playfield-target="playfield"] {
div.playfield div[data-playfield-target="nextPuzzleButtons"] { div.playfield div[data-playfield-target="nextPuzzleButtons"] {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 0.8rem; gap: 1rem;
margin-block-start: 1.2rem;
margin-block-end: 0.8rem; margin-block-end: 0.8rem;
} }

View file

@ -30,10 +30,13 @@
</div> </div>
<div class="keyboard" data-controller="keyboard" <div class="keyboard" data-controller="keyboard"
data-keyboard-playfield-outlet=".playfield" data-keyboard-playfield-outlet=".playfield"
style="display: none;"
data-action=" data-action="
keydown@window->keyboard#onKeyPress keydown@window->keyboard#onKeyPress
guessResults@window->keyboard#colorizeKeys guessResults@window->keyboard#colorizeKeys
resetKeyColors@window->keyboard#resetKeyColors resetKeyColors@window->keyboard#resetKeyColors
showKeyboard@window->keyboard#onShow
hideKeyboard@window->keyboard#onHide
"> ">
<div> <div>
<button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="q">q</button> <button data-keyboard-target="key" data-action="keyboard#tappedKey" data-key="q">q</button>