divided main.js sections into submodules for dev convenience
This commit is contained in:
parent
8138567b29
commit
f5faff5791
11 changed files with 375 additions and 358 deletions
File diff suppressed because one or more lines are too long
2
dist/index.html
vendored
2
dist/index.html
vendored
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "tripeaks-solitaire-solver-js-73k",
|
"name": "tripeaks-solitaire-solver-js-73k",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "A brute force solver for tri peaks solitaire written in javascript. Forked from work by Courtney Pitcher at https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver",
|
"description": "A brute force solver for tri peaks solitaire written in javascript. Forked from work by Courtney Pitcher at https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tripeaks",
|
"tripeaks",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { suits, ranks, deck } from "./deck";
|
||||||
|
|
||||||
// backs & joker
|
// backs & joker
|
||||||
import card1B from "../cards/1B.svg?raw";
|
import card1B from "../cards/1B.svg?raw";
|
||||||
import card2B from "../cards/2B.svg?raw";
|
import card2B from "../cards/2B.svg?raw";
|
||||||
|
@ -125,4 +127,31 @@ let cardSvgs = {
|
||||||
KS: cardKS,
|
KS: cardKS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Do some SVG processing
|
||||||
|
const cardSvgTitle = (ckey) => {
|
||||||
|
return deck.includes(ckey)
|
||||||
|
? `${ranks[ckey[0]]} of ${suits[ckey[1]]}`
|
||||||
|
: "Unknown Card";
|
||||||
|
};
|
||||||
|
Object.keys(cardSvgs).forEach((ckey) => {
|
||||||
|
var cardDoc = new DOMParser().parseFromString(
|
||||||
|
cardSvgs[ckey],
|
||||||
|
"image/svg+xml"
|
||||||
|
);
|
||||||
|
var svgRoot = cardDoc.documentElement;
|
||||||
|
svgRoot.removeAttribute("height");
|
||||||
|
svgRoot.removeAttribute("width");
|
||||||
|
svgRoot.removeAttribute("class");
|
||||||
|
var titleEl = cardDoc.createElementNS(
|
||||||
|
svgRoot.lookupNamespaceURI(null),
|
||||||
|
"title"
|
||||||
|
);
|
||||||
|
var titleText = document.createTextNode(cardSvgTitle(ckey));
|
||||||
|
titleEl.appendChild(titleText);
|
||||||
|
svgRoot.insertBefore(titleEl, svgRoot.firstElementChild);
|
||||||
|
cardSvgs[ckey] = new XMLSerializer().serializeToString(
|
||||||
|
cardDoc.documentElement
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default cardSvgs;
|
export default cardSvgs;
|
||||||
|
|
111
src/js/cardsInputForm.js
Normal file
111
src/js/cardsInputForm.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import { deck } from "./deck";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// "constants" for validation etc
|
||||||
|
deck,
|
||||||
|
nonAlphaNumRegEx: /[\W_]+/g,
|
||||||
|
|
||||||
|
// input validation
|
||||||
|
inputValue: "",
|
||||||
|
validCards: [],
|
||||||
|
dupedCards: [],
|
||||||
|
invalidCards: [],
|
||||||
|
validMessages: [],
|
||||||
|
invalidMessages: [],
|
||||||
|
get isFormValid() {
|
||||||
|
return this.isValidCardsLengthInRange && this.invalidMessages.length === 0;
|
||||||
|
},
|
||||||
|
get isValidCardsLengthTooSmall() {
|
||||||
|
return this.validCards.length < 34;
|
||||||
|
},
|
||||||
|
get isValidCardsLengthTooBig() {
|
||||||
|
return this.validCards.length > this.deck.length;
|
||||||
|
},
|
||||||
|
get isValidCardsLengthInRange() {
|
||||||
|
return !this.isValidCardsLengthTooSmall && !this.isValidCardsLengthTooBig;
|
||||||
|
},
|
||||||
|
validateCardsInput() {
|
||||||
|
// reset arrays and parse the input
|
||||||
|
this.validCards = [];
|
||||||
|
this.invalidCards = [];
|
||||||
|
this.dupedCards = [];
|
||||||
|
this.validMessages = [];
|
||||||
|
this.invalidMessages = [];
|
||||||
|
// handle if input has alphanum chars - treat them as delimeters
|
||||||
|
// if no alphanum chars, split by 2 chars except for 0
|
||||||
|
let userCards = this.nonAlphaNumRegEx.test(this.inputValue)
|
||||||
|
? this.inputValue
|
||||||
|
.toUpperCase()
|
||||||
|
.replace(this.nonAlphaNumRegEx, " ")
|
||||||
|
.split(" ")
|
||||||
|
.filter((c) => c)
|
||||||
|
: this.inputValue
|
||||||
|
.toUpperCase()
|
||||||
|
.split(/0|(..)/g)
|
||||||
|
.filter((c) => c !== "")
|
||||||
|
.map((c) => (!c ? "0" : c));
|
||||||
|
|
||||||
|
// check the input
|
||||||
|
userCards.forEach((card) => {
|
||||||
|
if (card === "0") {
|
||||||
|
// user marking a slot with an unknown card
|
||||||
|
this.validCards.push(card);
|
||||||
|
} else if (this.validCards.includes(card)) {
|
||||||
|
// this card was already seen in user's input, now it's a duplicate
|
||||||
|
this.dupedCards.push(card);
|
||||||
|
} else if (this.deck.includes(card)) {
|
||||||
|
// not a duplicate, and in the reference deck? Valid, add to valid cards
|
||||||
|
this.validCards.push(card);
|
||||||
|
} else {
|
||||||
|
// not a dupe, but not in reference deck: invalid, add to invalid cards
|
||||||
|
this.invalidCards.push(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// set validation messages based on length
|
||||||
|
if (this.isValidCardsLengthTooSmall) {
|
||||||
|
this.invalidMessages.push(`Must enter at least 34 cards`);
|
||||||
|
} else if (this.isValidCardsLengthTooBig) {
|
||||||
|
this.invalidMessages.push(
|
||||||
|
`Must not enter more than ${this.deck.length} cards`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.validCards.slice(this.validCards.length - 34).includes("0")) {
|
||||||
|
this.invalidMessages.push(
|
||||||
|
`Stock + bottom row (last 34 cards) must not contain unknown ('0') cards`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set other validation messages
|
||||||
|
if (this.dupedCards.length > 0) {
|
||||||
|
let s = this.dupedCards.length > 1 ? "s" : "";
|
||||||
|
this.invalidMessages.push(
|
||||||
|
`${this.dupedCards.length} duplicate card${s}: ${this.dupedCards.join(
|
||||||
|
" "
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.invalidCards.length > 0) {
|
||||||
|
let s = this.invalidCards.length > 1 ? "s" : "";
|
||||||
|
this.invalidMessages.push(
|
||||||
|
`${this.invalidCards.length} invalid card${s}: ${this.invalidCards.join(
|
||||||
|
" "
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.validCards.length > 0) {
|
||||||
|
let s = this.validCards.length > 1 ? "s" : "";
|
||||||
|
this.validMessages.push(
|
||||||
|
`${this.validCards.length} valid card${s}: ${this.validCards.join(" ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the game cards to try solving, based on current input
|
||||||
|
this.$store.global.cardsToSolve = Array(
|
||||||
|
this.deck.length - this.validCards.length
|
||||||
|
)
|
||||||
|
.fill(0)
|
||||||
|
.concat(this.validCards)
|
||||||
|
.map((c) => (c === "0" ? 0 : c));
|
||||||
|
},
|
||||||
|
};
|
28
src/js/deck.js
Normal file
28
src/js/deck.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
const suits = {
|
||||||
|
C: "Clubs",
|
||||||
|
D: "Diamonds",
|
||||||
|
H: "Hearts",
|
||||||
|
S: "Spades",
|
||||||
|
};
|
||||||
|
const ranks = {
|
||||||
|
A: "Ace",
|
||||||
|
2: "Two",
|
||||||
|
3: "Three",
|
||||||
|
4: "Four",
|
||||||
|
5: "Five",
|
||||||
|
6: "Six",
|
||||||
|
7: "Seven",
|
||||||
|
8: "Eight",
|
||||||
|
9: "Nine",
|
||||||
|
T: "Ten",
|
||||||
|
J: "Jack",
|
||||||
|
Q: "Queen",
|
||||||
|
K: "King",
|
||||||
|
};
|
||||||
|
const deck = Object.keys(suits).flatMap((suit) => {
|
||||||
|
return Object.keys(ranks).map((cval) => {
|
||||||
|
return cval + suit;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export { suits, ranks, deck };
|
42
src/js/encouragements.js
Normal file
42
src/js/encouragements.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// long-running solve messages
|
||||||
|
export default [
|
||||||
|
"Hang in there!",
|
||||||
|
"Let go like a bird flies, not fighting the wind but gliding on it",
|
||||||
|
"Stay patient and trust the journey.",
|
||||||
|
"Everything is coming together…",
|
||||||
|
"Solitaire is a journey, not a destination.",
|
||||||
|
"For things to reveal themselves to us, we need to be ready to abandon our views about them.",
|
||||||
|
"Patience is bitter, but its fruit is sweet.",
|
||||||
|
"Strive for progress, not perfection.",
|
||||||
|
"I wish only to be alive and to experience this living to the fullest.",
|
||||||
|
"This too shall pass.",
|
||||||
|
"Life is available only in the present moment.",
|
||||||
|
"Patience is the companion of wisdom.",
|
||||||
|
"The mountains are calling and I must go.",
|
||||||
|
"Give time time.",
|
||||||
|
"If you find a path with no obstacles, it probably doesn't lead anywhere",
|
||||||
|
"A smooth sea never made a good sailor.",
|
||||||
|
"To be upset over what you don’t have is to waste what you do have.",
|
||||||
|
"The ultimate freedom lies in being able to wait patiently for a good thing.",
|
||||||
|
"If it isn't good, let it die. If it doesn't die, make it good.",
|
||||||
|
"A watched pot never boils without applying heat.",
|
||||||
|
"Stick with the winners.",
|
||||||
|
"I immerse myself in the experience of living without having to evaluate or understand it.",
|
||||||
|
"Why fit in when you were born to stand out?",
|
||||||
|
"Don't let yesterday take up too much of today.",
|
||||||
|
"The least I owe the mountains is a body.",
|
||||||
|
"Getting so close!",
|
||||||
|
"Many people think excitement is happiness. But when you are excited you are not peaceful.",
|
||||||
|
"Misery is optional.",
|
||||||
|
"Mistakes are proof that you're trying.",
|
||||||
|
"Life would be so much easier if we only had the source code.",
|
||||||
|
"A computer once beat me at chess, but it was no match for me at kickboxing.",
|
||||||
|
"Patience is not simply the ability to wait, it's how we behave while we're waiting.",
|
||||||
|
"We must let go of the life we have planned so as to accept the one that is waiting for us.",
|
||||||
|
"Somewhere, something incredible is waiting to be known.",
|
||||||
|
"If you spend your whole life waiting for the storm, you'll never enjoy the sunshine.",
|
||||||
|
"My actions are my only true belongings.",
|
||||||
|
"The way to have enough time is to never be in a hurry.",
|
||||||
|
"The more you know, the less you think you know.",
|
||||||
|
"Patience is also a form of action.",
|
||||||
|
];
|
77
src/js/gameSolving.js
Normal file
77
src/js/gameSolving.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import encouragements from "./encouragements";
|
||||||
|
import SolverWorker from "./solverWorker?worker";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
encouragements,
|
||||||
|
encourageIndex: null,
|
||||||
|
solverWorker: null,
|
||||||
|
headerText: "Solution",
|
||||||
|
moveCount: 23,
|
||||||
|
statusMessages: [],
|
||||||
|
solutionMoves: [],
|
||||||
|
nodesTried: 0,
|
||||||
|
nodesTriedFloor: 0,
|
||||||
|
reset() {
|
||||||
|
this.$store.global.solvingInProgress = false;
|
||||||
|
this.moveCount = 0;
|
||||||
|
this.statusMessages = [];
|
||||||
|
this.nodesTried = 0;
|
||||||
|
this.nodesTriedFloor = 0;
|
||||||
|
this.encourageIndex = null;
|
||||||
|
},
|
||||||
|
onInit() {
|
||||||
|
this.solverWorker = new SolverWorker();
|
||||||
|
this.solverWorker.addEventListener("message", async (e) => {
|
||||||
|
if (e.data.msg === "solve-progress") {
|
||||||
|
this.nodesTried++;
|
||||||
|
this.moveCount = e.data.moveCount;
|
||||||
|
this.statusMessages[0] = `${this.moveCount} card-clearing moves found so far.`;
|
||||||
|
|
||||||
|
let newFloor = Math.floor(this.nodesTried / 10000) * 10000;
|
||||||
|
if (newFloor > this.nodesTriedFloor) {
|
||||||
|
this.nodesTriedFloor = newFloor;
|
||||||
|
if (this.nodesTriedFloor > 50000) {
|
||||||
|
this.statusMessages[1] = `Over ${this.nodesTriedFloor.toLocaleString(
|
||||||
|
"en"
|
||||||
|
)} possibilities tried. Still working…`;
|
||||||
|
}
|
||||||
|
if (this.nodesTriedFloor % 250000 === 0) {
|
||||||
|
if (this.encourageIndex === null) {
|
||||||
|
this.encourageIndex = Math.floor(
|
||||||
|
Math.random() * this.encouragements.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.statusMessages.splice(
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
this.encouragements[this.encourageIndex]
|
||||||
|
);
|
||||||
|
let newEncourageIndex = this.encourageIndex + 1;
|
||||||
|
this.encourageIndex =
|
||||||
|
newEncourageIndex === this.encouragements.length
|
||||||
|
? 0
|
||||||
|
: newEncourageIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.data.msg === "solve-result") {
|
||||||
|
if (e.data.result[0]) {
|
||||||
|
this.headerText = "Solution found:";
|
||||||
|
this.solutionMoves = e.data.result[1];
|
||||||
|
this.reset();
|
||||||
|
} else {
|
||||||
|
this.headerText = "Could not solve. Best moves found:";
|
||||||
|
this.solutionMoves = e.data.result[2];
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async startSolver() {
|
||||||
|
this.headerText = "Solving…";
|
||||||
|
this.solutionMoves = [];
|
||||||
|
this.$store.global.solvingInProgress = true;
|
||||||
|
await this.$nextTick();
|
||||||
|
let game = JSON.parse(JSON.stringify(this.$store.global.cardsToSolve));
|
||||||
|
this.solverWorker.postMessage({ msg: "try-to-solve", game: game });
|
||||||
|
},
|
||||||
|
};
|
306
src/js/main.js
306
src/js/main.js
|
@ -1,313 +1,25 @@
|
||||||
import "../scss/style.scss";
|
import "../scss/style.scss";
|
||||||
import Alpine from "alpinejs";
|
import Alpine from "alpinejs";
|
||||||
import cardSvgs from "./cardSvgs";
|
import globalStore from "./store";
|
||||||
import SolverWorker from "./solverWorker?worker";
|
|
||||||
import navbar from "./navbar";
|
import navbar from "./navbar";
|
||||||
|
import cardsInputForm from "./cardsInputForm";
|
||||||
// Some helpful constants
|
import playingCardsPreview from "./playingCardsPreview";
|
||||||
const suits = {
|
import gameSolving from "./gameSolving";
|
||||||
C: "Clubs",
|
|
||||||
D: "Diamonds",
|
|
||||||
H: "Hearts",
|
|
||||||
S: "Spades",
|
|
||||||
};
|
|
||||||
const ranks = {
|
|
||||||
A: "Ace",
|
|
||||||
2: "Two",
|
|
||||||
3: "Three",
|
|
||||||
4: "Four",
|
|
||||||
5: "Five",
|
|
||||||
6: "Six",
|
|
||||||
7: "Seven",
|
|
||||||
8: "Eight",
|
|
||||||
9: "Nine",
|
|
||||||
T: "Ten",
|
|
||||||
J: "Jack",
|
|
||||||
Q: "Queen",
|
|
||||||
K: "King",
|
|
||||||
};
|
|
||||||
const deck = Object.keys(suits).flatMap((suit) => {
|
|
||||||
return Object.keys(ranks).map((cval) => {
|
|
||||||
return cval + suit;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do some SVG processing
|
|
||||||
const cardSvgTitle = (ckey) => {
|
|
||||||
return deck.includes(ckey)
|
|
||||||
? `${ranks[ckey[0]]} of ${suits[ckey[1]]}`
|
|
||||||
: "Unknown Card";
|
|
||||||
};
|
|
||||||
Object.keys(cardSvgs).forEach((ckey) => {
|
|
||||||
var cardDoc = new DOMParser().parseFromString(
|
|
||||||
cardSvgs[ckey],
|
|
||||||
"image/svg+xml"
|
|
||||||
);
|
|
||||||
var svgRoot = cardDoc.documentElement;
|
|
||||||
svgRoot.removeAttribute("height");
|
|
||||||
svgRoot.removeAttribute("width");
|
|
||||||
svgRoot.removeAttribute("class");
|
|
||||||
var titleEl = cardDoc.createElementNS(
|
|
||||||
svgRoot.lookupNamespaceURI(null),
|
|
||||||
"title"
|
|
||||||
);
|
|
||||||
var titleText = document.createTextNode(cardSvgTitle(ckey));
|
|
||||||
titleEl.appendChild(titleText);
|
|
||||||
svgRoot.insertBefore(titleEl, svgRoot.firstElementChild);
|
|
||||||
cardSvgs[ckey] = new XMLSerializer().serializeToString(
|
|
||||||
cardDoc.documentElement
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keep some constants in global store for components
|
// Keep some constants in global store for components
|
||||||
Alpine.store("global", {
|
Alpine.store("global", globalStore);
|
||||||
deck,
|
|
||||||
cardsToSolve: Array(deck.length).fill(0),
|
|
||||||
solvingInProgress: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// navbar logic
|
// navbar logic
|
||||||
Alpine.data("navbar", () => navbar);
|
Alpine.data("navbar", () => navbar);
|
||||||
|
|
||||||
// card preview component logic
|
|
||||||
Alpine.data("playingCardsPreview", () => ({
|
|
||||||
cardSvgs,
|
|
||||||
cardsBySlice(start, length) {
|
|
||||||
return this.$store.global.cardsToSolve.slice(start, length);
|
|
||||||
},
|
|
||||||
cardSvg(card) {
|
|
||||||
return card === 0 ? this.cardSvgs["2B"] : this.cardSvgs[card];
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// input component logic
|
// input component logic
|
||||||
Alpine.data("cardsInputForm", () => ({
|
Alpine.data("cardsInputForm", () => cardsInputForm);
|
||||||
// "constants" for validation etc
|
|
||||||
nonAlphaNumRegEx: /[\W_]+/g,
|
|
||||||
|
|
||||||
// input validation
|
// card preview component logic
|
||||||
inputValue: "",
|
Alpine.data("playingCardsPreview", () => playingCardsPreview);
|
||||||
validCards: [],
|
|
||||||
dupedCards: [],
|
|
||||||
invalidCards: [],
|
|
||||||
validMessages: [],
|
|
||||||
invalidMessages: [],
|
|
||||||
get isFormValid() {
|
|
||||||
return this.isValidCardsLengthInRange && this.invalidMessages.length === 0;
|
|
||||||
},
|
|
||||||
get isValidCardsLengthTooSmall() {
|
|
||||||
return this.validCards.length < 34;
|
|
||||||
},
|
|
||||||
get isValidCardsLengthTooBig() {
|
|
||||||
return this.validCards.length > this.$store.global.deck.length;
|
|
||||||
},
|
|
||||||
get isValidCardsLengthInRange() {
|
|
||||||
return !this.isValidCardsLengthTooSmall && !this.isValidCardsLengthTooBig;
|
|
||||||
},
|
|
||||||
validateCardsInput() {
|
|
||||||
// reset arrays and parse the input
|
|
||||||
this.validCards = [];
|
|
||||||
this.invalidCards = [];
|
|
||||||
this.dupedCards = [];
|
|
||||||
this.validMessages = [];
|
|
||||||
this.invalidMessages = [];
|
|
||||||
// handle if input has alphanum chars - treat them as delimeters
|
|
||||||
// if no alphanum chars, split by 2 chars except for 0
|
|
||||||
let userCards = this.nonAlphaNumRegEx.test(this.inputValue)
|
|
||||||
? this.inputValue
|
|
||||||
.toUpperCase()
|
|
||||||
.replace(this.nonAlphaNumRegEx, " ")
|
|
||||||
.split(" ")
|
|
||||||
.filter((c) => c)
|
|
||||||
: this.inputValue
|
|
||||||
.toUpperCase()
|
|
||||||
.split(/0|(..)/g)
|
|
||||||
.filter((c) => c !== "")
|
|
||||||
.map((c) => (!c ? "0" : c));
|
|
||||||
|
|
||||||
// check the input
|
|
||||||
userCards.forEach((card) => {
|
|
||||||
if (card === "0") {
|
|
||||||
// user marking a slot with an unknown card
|
|
||||||
this.validCards.push(card);
|
|
||||||
} else if (this.validCards.includes(card)) {
|
|
||||||
// this card was already seen in user's input, now it's a duplicate
|
|
||||||
this.dupedCards.push(card);
|
|
||||||
} else if (this.$store.global.deck.includes(card)) {
|
|
||||||
// not a duplicate, and in the reference deck? Valid, add to valid cards
|
|
||||||
this.validCards.push(card);
|
|
||||||
} else {
|
|
||||||
// not a dupe, but not in reference deck: invalid, add to invalid cards
|
|
||||||
this.invalidCards.push(card);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// set validation messages based on length
|
|
||||||
if (this.isValidCardsLengthTooSmall) {
|
|
||||||
this.invalidMessages.push(`Must enter at least 34 cards`);
|
|
||||||
} else if (this.isValidCardsLengthTooBig) {
|
|
||||||
this.invalidMessages.push(
|
|
||||||
`Must not enter more than ${this.$store.global.deck.length} cards`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (this.validCards.slice(this.validCards.length - 34).includes("0")) {
|
|
||||||
this.invalidMessages.push(
|
|
||||||
`Stock + bottom row (last 34 cards) must not contain unknown ('0') cards`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set other validation messages
|
|
||||||
if (this.dupedCards.length > 0) {
|
|
||||||
let s = this.dupedCards.length > 1 ? "s" : "";
|
|
||||||
this.invalidMessages.push(
|
|
||||||
`${this.dupedCards.length} duplicate card${s}: ${this.dupedCards.join(
|
|
||||||
" "
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (this.invalidCards.length > 0) {
|
|
||||||
let s = this.invalidCards.length > 1 ? "s" : "";
|
|
||||||
this.invalidMessages.push(
|
|
||||||
`${this.invalidCards.length} invalid card${s}: ${this.invalidCards.join(
|
|
||||||
" "
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (this.validCards.length > 0) {
|
|
||||||
let s = this.validCards.length > 1 ? "s" : "";
|
|
||||||
this.validMessages.push(
|
|
||||||
`${this.validCards.length} valid card${s}: ${this.validCards.join(" ")}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the game cards to try solving, based on current input
|
|
||||||
this.$store.global.cardsToSolve = Array(
|
|
||||||
this.$store.global.deck.length - this.validCards.length
|
|
||||||
)
|
|
||||||
.fill(0)
|
|
||||||
.concat(this.validCards)
|
|
||||||
.map((c) => (c === "0" ? 0 : c));
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// long-running solve messages
|
|
||||||
const encouragements = [
|
|
||||||
"Hang in there!",
|
|
||||||
"Let go like a bird flies, not fighting the wind but gliding on it",
|
|
||||||
"Stay patient and trust the journey.",
|
|
||||||
"Everything is coming together…",
|
|
||||||
"Solitaire is a journey, not a destination.",
|
|
||||||
"For things to reveal themselves to us, we need to be ready to abandon our views about them.",
|
|
||||||
"Patience is bitter, but its fruit is sweet.",
|
|
||||||
"Strive for progress, not perfection.",
|
|
||||||
"I wish only to be alive and to experience this living to the fullest.",
|
|
||||||
"This too shall pass.",
|
|
||||||
"Life is available only in the present moment.",
|
|
||||||
"Patience is the companion of wisdom.",
|
|
||||||
"The mountains are calling and I must go.",
|
|
||||||
"Give time time.",
|
|
||||||
"If you find a path with no obstacles, it probably doesn't lead anywhere",
|
|
||||||
"A smooth sea never made a good sailor.",
|
|
||||||
"To be upset over what you don’t have is to waste what you do have.",
|
|
||||||
"The ultimate freedom lies in being able to wait patiently for a good thing.",
|
|
||||||
"If it isn't good, let it die. If it doesn't die, make it good.",
|
|
||||||
"A watched pot never boils without applying heat.",
|
|
||||||
"Stick with the winners.",
|
|
||||||
"I immerse myself in the experience of living without having to evaluate or understand it.",
|
|
||||||
"Why fit in when you were born to stand out?",
|
|
||||||
"Don't let yesterday take up too much of today.",
|
|
||||||
"The least I owe the mountains is a body.",
|
|
||||||
"Getting so close!",
|
|
||||||
"Many people think excitement is happiness. But when you are excited you are not peaceful.",
|
|
||||||
"Misery is optional.",
|
|
||||||
"Mistakes are proof that you're trying.",
|
|
||||||
"Life would be so much easier if we only had the source code.",
|
|
||||||
"A computer once beat me at chess, but it was no match for me at kickboxing.",
|
|
||||||
"Patience is not simply the ability to wait, it's how we behave while we're waiting.",
|
|
||||||
"We must let go of the life we have planned so as to accept the one that is waiting for us.",
|
|
||||||
"Somewhere, something incredible is waiting to be known.",
|
|
||||||
"If you spend your whole life waiting for the storm, you'll never enjoy the sunshine.",
|
|
||||||
"My actions are my only true belongings.",
|
|
||||||
"The way to have enough time is to never be in a hurry.",
|
|
||||||
"The more you know, the less you think you know.",
|
|
||||||
"Patience is also a form of action.",
|
|
||||||
];
|
|
||||||
|
|
||||||
// game solving component logic
|
// game solving component logic
|
||||||
Alpine.data("gameSolving", () => ({
|
Alpine.data("gameSolving", () => gameSolving);
|
||||||
encouragements,
|
|
||||||
encourageIndex: null,
|
|
||||||
solverWorker: null,
|
|
||||||
headerText: "Solution",
|
|
||||||
moveCount: 23,
|
|
||||||
statusMessages: [],
|
|
||||||
solutionMoves: [],
|
|
||||||
nodesTried: 0,
|
|
||||||
nodesTriedFloor: 0,
|
|
||||||
reset() {
|
|
||||||
this.$store.global.solvingInProgress = false;
|
|
||||||
this.moveCount = 0;
|
|
||||||
this.statusMessages = [];
|
|
||||||
this.nodesTried = 0;
|
|
||||||
this.nodesTriedFloor = 0;
|
|
||||||
this.encourageIndex = null;
|
|
||||||
},
|
|
||||||
onInit() {
|
|
||||||
this.solverWorker = new SolverWorker();
|
|
||||||
this.solverWorker.addEventListener("message", async (e) => {
|
|
||||||
if (e.data.msg === "solve-progress") {
|
|
||||||
this.nodesTried++;
|
|
||||||
this.moveCount = e.data.moveCount;
|
|
||||||
this.statusMessages[0] = `${this.moveCount} card-clearing moves found so far.`;
|
|
||||||
|
|
||||||
let newFloor = Math.floor(this.nodesTried / 10000) * 10000;
|
|
||||||
if (newFloor > this.nodesTriedFloor) {
|
|
||||||
this.nodesTriedFloor = newFloor;
|
|
||||||
if (this.nodesTriedFloor > 50000) {
|
|
||||||
this.statusMessages[1] = `Over ${this.nodesTriedFloor.toLocaleString(
|
|
||||||
"en"
|
|
||||||
)} possibilities tried. Still working…`;
|
|
||||||
}
|
|
||||||
if (this.nodesTriedFloor % 250000 === 0) {
|
|
||||||
if (this.encourageIndex === null) {
|
|
||||||
this.encourageIndex = Math.floor(
|
|
||||||
Math.random() * this.encouragements.length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.statusMessages.splice(
|
|
||||||
2,
|
|
||||||
0,
|
|
||||||
this.encouragements[this.encourageIndex]
|
|
||||||
);
|
|
||||||
let newEncourageIndex = this.encourageIndex + 1;
|
|
||||||
this.encourageIndex =
|
|
||||||
newEncourageIndex === this.encouragements.length
|
|
||||||
? 0
|
|
||||||
: newEncourageIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (e.data.msg === "solve-result") {
|
|
||||||
if (e.data.result[0]) {
|
|
||||||
this.headerText = "Solution found:";
|
|
||||||
this.solutionMoves = e.data.result[1];
|
|
||||||
this.reset();
|
|
||||||
} else {
|
|
||||||
this.headerText = "Could not solve. Best moves found:";
|
|
||||||
this.solutionMoves = e.data.result[2];
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async startSolver() {
|
|
||||||
this.headerText = "Solving…";
|
|
||||||
this.solutionMoves = [];
|
|
||||||
this.$store.global.solvingInProgress = true;
|
|
||||||
await this.$nextTick();
|
|
||||||
let game = JSON.parse(JSON.stringify(this.$store.global.cardsToSolve));
|
|
||||||
this.solverWorker.postMessage({ msg: "try-to-solve", game: game });
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
window.Alpine = Alpine;
|
window.Alpine = Alpine;
|
||||||
|
|
||||||
|
|
11
src/js/playingCardsPreview.js
Normal file
11
src/js/playingCardsPreview.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import cardSvgs from "./cardSvgs";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
cardSvgs,
|
||||||
|
cardsBySlice(start, length) {
|
||||||
|
return this.$store.global.cardsToSolve.slice(start, length);
|
||||||
|
},
|
||||||
|
cardSvg(card) {
|
||||||
|
return card === 0 ? this.cardSvgs["2B"] : this.cardSvgs[card];
|
||||||
|
},
|
||||||
|
};
|
7
src/js/store.js
Normal file
7
src/js/store.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { deck } from "./deck";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
deck,
|
||||||
|
cardsToSolve: Array(deck.length).fill(0),
|
||||||
|
solvingInProgress: false,
|
||||||
|
};
|
Loading…
Reference in a new issue