tripeaks-solitaire-solver-j.../main.js

189 lines
5.2 KiB
JavaScript

import "./style.scss";
import Alpine from "alpinejs";
import cardSvgs from "./cardSvgs";
// 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
);
});
// Keep some constants in global store for components
Alpine.store("global", {
deck,
cardsToSolve: Array(deck.length).fill(0),
});
// card preview component data
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 data
Alpine.data("cardsInputForm", () => ({
// "constants" for validation etc
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.$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));
},
}));
window.Alpine = Alpine;
Alpine.start();