class RubiksCubeScrambler extends HTMLElement { static #RandomMoveHysteresisMaxLength = 2; #shadowRoot; #movesListElement; #numberOfMovesToGenerate = 25; constructor() { super(); this.#shadowRoot = this.attachShadow({ mode: "open" }); } scramble() { console.log("Randomizing Rubik's cube..."); const movesList = this.#movesListElement; while (movesList.childElementCount > this.#numberOfMovesToGenerate) { movesList.removeChild(movesList.lastChild); } let randomMoveHysteresis = []; for (let i = 0; i < this.#numberOfMovesToGenerate; i++) { const randomMove = this.#randomMove(randomMoveHysteresis); let moveItem; if (i < movesList.childElementCount) { moveItem = movesList.children[i]; } else { moveItem = document.createElement("li"); movesList.appendChild(moveItem); } moveItem.classList.add("scrambler__move"); moveItem.classList.remove("scrambler__move--start", "scrambler__move--end"); if (randomMove.includes("2")) { moveItem.classList.add("scrambler__move--start"); } else if (randomMove.includes("'")) { moveItem.classList.add("scrambler__move--end"); } moveItem.innerText = randomMove; } } #randomMove(hysteresis) { const faces = "FBLRUD"; let move; do { move = faces.charAt(Math.floor(Math.random() * faces.length)); } while (hysteresis && hysteresis.includes(move)); if (hysteresis) { hysteresis.unshift(move); while (hysteresis.length > RubiksCubeScrambler.#RandomMoveHysteresisMaxLength) { hysteresis.pop(); } } const modifierFactor = Math.random(); if (modifierFactor < 0.33333) { move = "2" + move; } else if (modifierFactor < 0.666666) { move = move + "'"; } return move; } #removeAllMoves() { const element = this.#movesListElement; while (element.hasChildNodes()) { element.removeChild(element.lastChild); } } // MARK: Custom Element connectedCallback() { let template = document.getElementById("rubiks-cube-scrambler-template"); console.assert(template, "Couldn't find RubiksCubeScrambler component template in the document"); const shadowRoot = this.#shadowRoot; shadowRoot.appendChild(template.content.cloneNode(true)); this.#movesListElement = shadowRoot.querySelector(".scrambler__move-list"); shadowRoot .querySelector("button[name='scramble']") .addEventListener("click", () => this.scramble()); const patternLengthInputElement = shadowRoot.querySelector(".scrambler__pattern-length > input"); patternLengthInputElement.value = this.#numberOfMovesToGenerate; patternLengthInputElement.addEventListener("input", event => { try { const integerValue = parseInt(event.target.value); this.#numberOfMovesToGenerate = integerValue; } catch (e) { console.error("Non-integer value of pattern length field", e); } }); this.scramble(); } attributeChangedCallback(name, oldValue, newValue) { console.debug("RubiksCubeScrambler attribute changed", name, oldValue, newValue); if (name === "count") { try { let newIntValue = parseInt(newValue); this.#numberOfMovesToGenerate = newIntValue; } catch (e) { console.error("`count` attribute should have an integer value.", e); } } } } window.customElements.define("rubiks-cube-scrambler", RubiksCubeScrambler);