Implement the rubiks-cube-scrambler custom element, including JS and template files. Put these things in the body-extras.html partial that the termlite theme added. resource-builders: Update submodule commit termlite: Update submodule commit
123 lines
3.9 KiB
JavaScript
123 lines
3.9 KiB
JavaScript
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);
|