Compare commits
2 commits
60971a2d13
...
f5faff5791
Author | SHA1 | Date | |
---|---|---|---|
f5faff5791 | |||
8138567b29 |
15 changed files with 390 additions and 371 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",
|
||||
"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",
|
||||
"keywords": [
|
||||
"tripeaks",
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 716 B |
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
|
||||
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 640 B After Width: | Height: | Size: 640 B |
|
@ -1,3 +1,5 @@
|
|||
import { suits, ranks, deck } from "./deck";
|
||||
|
||||
// backs & joker
|
||||
import card1B from "../cards/1B.svg?raw";
|
||||
import card2B from "../cards/2B.svg?raw";
|
||||
|
@ -125,4 +127,31 @@ let cardSvgs = {
|
|||
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;
|
||||
|
|
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 });
|
||||
},
|
||||
};
|
318
src/js/main.js
318
src/js/main.js
|
@ -1,321 +1,25 @@
|
|||
import "../scss/style.scss";
|
||||
import Alpine from "alpinejs";
|
||||
import cardSvgs from "./cardSvgs";
|
||||
import SolverWorker from "./solverWorker?worker";
|
||||
|
||||
// navbar svg icons
|
||||
import svg73k from "../img/73k.svg?raw";
|
||||
import biTwitter from "../img/bi-twitter.svg?raw";
|
||||
import biGithub from "../img/bi-github.svg?raw";
|
||||
|
||||
// Some helpful constants
|
||||
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;
|
||||
});
|
||||
});
|
||||
|
||||
// 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
|
||||
);
|
||||
});
|
||||
import globalStore from "./store";
|
||||
import navbar from "./navbar";
|
||||
import cardsInputForm from "./cardsInputForm";
|
||||
import playingCardsPreview from "./playingCardsPreview";
|
||||
import gameSolving from "./gameSolving";
|
||||
|
||||
// Keep some constants in global store for components
|
||||
Alpine.store("global", {
|
||||
deck,
|
||||
cardsToSolve: Array(deck.length).fill(0),
|
||||
solvingInProgress: false,
|
||||
});
|
||||
Alpine.store("global", globalStore);
|
||||
|
||||
// navbar logic
|
||||
Alpine.data("navbar", () => ({
|
||||
svg73k: svg73k.replaceAll('="16"', '="24"'),
|
||||
biTwitter: biTwitter.replaceAll('="16"', '="24"'),
|
||||
biGithub: biGithub.replaceAll('="16"', '="24"'),
|
||||
}));
|
||||
|
||||
// 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];
|
||||
},
|
||||
}));
|
||||
Alpine.data("navbar", () => navbar);
|
||||
|
||||
// input component logic
|
||||
Alpine.data("cardsInputForm", () => ({
|
||||
// "constants" for validation etc
|
||||
nonAlphaNumRegEx: /[\W_]+/g,
|
||||
Alpine.data("cardsInputForm", () => cardsInputForm);
|
||||
|
||||
// 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.$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.",
|
||||
];
|
||||
// card preview component logic
|
||||
Alpine.data("playingCardsPreview", () => playingCardsPreview);
|
||||
|
||||
// game solving component logic
|
||||
Alpine.data("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 });
|
||||
},
|
||||
}));
|
||||
Alpine.data("gameSolving", () => gameSolving);
|
||||
|
||||
window.Alpine = Alpine;
|
||||
|
||||
|
|
10
src/js/navbar.js
Normal file
10
src/js/navbar.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
// navbar svg icons
|
||||
import svg73k from "../img/73k.svg?raw";
|
||||
import biTwitter from "../img/bi-twitter.svg?raw";
|
||||
import biGithub from "../img/bi-github.svg?raw";
|
||||
|
||||
export default {
|
||||
svg73k,
|
||||
biTwitter,
|
||||
biGithub,
|
||||
};
|
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