wordle-clone/site/assets/scripts/controllers/playfield.js
Leon Mika da4a87eb86
Some checks failed
/ publish (push) Has been cancelled
Added animation revealing the word colors
2025-03-14 22:00:13 +11:00

253 lines
6.9 KiB
JavaScript

import { Controller } from "https://unpkg.com/@hotwired/stimulus@v3.2.2/dist/stimulus.js"
import { GUESS_RESULT, MARKERS, GameController } from "../models/gamecontroller.js";
import { WordSource } from "../models/words.js";
export default class extends Controller {
static targets = ["row", "playfield", "topMessage", "nextPuzzleButtons"];
static outlets = ["overlay"];
async connect() {
this._wordSource = new WordSource();
this._gameController = new GameController(this._wordSource);
await this._gameController.start();
this._buildPlayfield();
}
tappedKey(key) {
console.log(`Key ${key} was tapped via outliet`);
this._addLetter(key);
}
_addLetter(letter) {
if (this._activeRowIndex < 0) {
return;
} else if (this._activeLetter >= this._gameController.wordLength()) {
return;
}
let rowElem = this.rowTargets[this._activeRowIndex];
let colElem = rowElem.querySelectorAll("span")[this._activeLetter];
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;
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("");
console.log("The guessed word is: " + guessedWord);
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;
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);
}
break;
case GUESS_RESULT.WIN:
await this._colorizeRow(rowElem, results, true);
this._broadcastGuessResults(results);
this._showWin();
break;
}
}
async loadNextPuzzle(ev) {
ev.preventDefault();
if (await this._gameController.nextWord()) {
this._buildPlayfield();
} else {
this.overlayOutlet.showMessage("No more words available.");
}
}
_showWin() {
this.topMessageTarget.innerText = "Hooray! You did it.";
this.nextPuzzleButtonsTarget.classList.remove("hide");
}
_revealAnswer() {
this.topMessageTarget.innerText = this._gameController.currentWord().toUpperCase();
this.nextPuzzleButtonsTarget.classList.remove("hide");
}
_buildPlayfield() {
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) {
this._broadcastGuessResults(lastGuessResults);
}
if (wasWin) {
this._showWin();
} else if (currentGuesses.length >= rows) {
// 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);
}
_buildPlayfieldRow(wordLength, currentGuess, currentGuessResults) {
let divElem = document.createElement("div");
divElem.classList.add("row");
divElem.setAttribute("data-playfield-target", "row");
for (let c = 0; c < wordLength; c++) {
let letterSpan = document.createElement("span");
if (currentGuess) {
letterSpan.innerText = currentGuess[c].toUpperCase();
}
divElem.appendChild(letterSpan);
}
if (currentGuess) {
this._colorizeRow(divElem, currentGuessResults, false);
}
return divElem;
}
async _colorizeRow(row, results, delayed) {
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();
}
_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();
}
}
}
}