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

199 lines
5.7 KiB
JavaScript
Raw Normal View History

2019-07-27 07:46:57 -04:00
class Card {
2022-09-07 08:49:25 -04:00
constructor(cardString) {
this.rank = cardString[0];
this.suit = cardString[1];
}
/**
* Gets the face value of the card, from [0 .. 12].
* @returns {number} The value.
*/
get integerValue() {
return "A23456789TJQK".indexOf(this.rank);
}
/**
* Returns true if the other card has a face value one above or below this card.
* @param card Another card.
* @returns {boolean} True if sequential, false otherwise.
*/
isSequential(card) {
return (
2022-09-09 17:01:18 -04:00
(this.integerValue + 13 + 1) % 13 === card.integerValue ||
(this.integerValue + 13 - 1) % 13 === card.integerValue
2022-09-07 08:49:25 -04:00
);
}
get toString() {
return this.rank + this.suit;
}
2019-07-27 07:46:57 -04:00
}
class Pyramid {
2022-09-07 08:49:25 -04:00
constructor(pyramidArray) {
this.array = pyramidArray;
}
2019-07-27 07:46:57 -04:00
2022-09-07 08:49:25 -04:00
get isCleared() {
return this.array.every((c) => c === 0);
2022-09-07 08:49:25 -04:00
}
get freeCardIndices() {
let freeIndices = [];
for (let i = this.array.length - 1; i >= 0; i--) {
if (this.array[i] === 0) continue;
const secondOffset = Math.floor((i - 3) / 2);
// This is the last row
if (i >= 18) freeIndices.push(i);
// Third row
else if (
i <= 17 &&
i >= 9 &&
this.array[i + 9] === 0 &&
this.array[i + 10] === 0
)
freeIndices.push(i);
// Second row
else if (
i <= 8 &&
i >= 3 &&
this.array[i + 6 + secondOffset] === 0 &&
this.array[i + 7 + secondOffset] === 0
)
freeIndices.push(i);
// First row
else if (
i <= 2 &&
i >= 0 &&
this.array[i + 3 + i] === 0 &&
this.array[i + 4 + i] === 0
)
freeIndices.push(i);
2019-07-27 07:46:57 -04:00
}
2022-09-07 08:49:25 -04:00
return freeIndices;
}
2019-07-27 07:46:57 -04:00
}
class MoveString {
2022-09-07 08:49:25 -04:00
static gameWon() {
return "You have won.";
}
static gameLost() {
return "There are no more valid moves.";
}
static match(cardA) {
return "Move " + cardA + " onto the stock.";
}
static flipStock() {
return "Draw a new stock card.";
}
2019-07-27 07:46:57 -04:00
}
2022-09-07 08:49:25 -04:00
const GameStates = Object.freeze({ won: true, lost: false });
2019-08-03 07:41:36 -04:00
function getBestMoveArray(bestMoveArray, newMoveArray) {
2022-09-08 06:58:05 -04:00
let bestMoveLength = bestMoveArray.filter((s) => s.startsWith("Move")).length;
let newMoveLength = newMoveArray.filter((s) => s.startsWith("Move")).length;
return newMoveLength > bestMoveLength ? newMoveArray : bestMoveArray;
}
2019-08-03 07:41:36 -04:00
/**
* Solves a Tri Peaks solitaire game.
* @param pyramidArray The cards in the pyramids, starting in the top-left peak. The cards are in left-to-right, then top-to-bottom order.
* @param stockArray The cards in the stock.
2022-09-14 09:54:03 -04:00
* @param worker Optional Web Worker for reporting progress.
2019-08-03 07:41:36 -04:00
* @param stockIndex The index of the top stock card.
* @param moveArray The list of moves that have been made to get a deck in this configuration.
2022-09-14 09:54:03 -04:00
* @param bestMoveArray The best list of moves that have been found so far.
* @returns {Promise<{*[]|([*, *, *]|[*, *, *]|[*, *, *])}>}
2019-08-03 07:41:36 -04:00
*/
async function solve(
2022-09-08 06:58:05 -04:00
pyramidArray,
stockArray,
worker = null,
2022-09-08 06:58:05 -04:00
stockIndex = 0,
moveArray = [],
bestMoveArray = []
) {
2022-09-07 08:49:25 -04:00
let newMoveArray = JSON.parse(JSON.stringify(moveArray));
let newBestMoveArray = JSON.parse(JSON.stringify(bestMoveArray));
2022-09-07 08:49:25 -04:00
let pyramid = new Pyramid(pyramidArray);
// We're in a new game state.
// Are the moves that brought us here better than the previous best moves we received?
// If yes, replace the known best move array
newBestMoveArray = getBestMoveArray(newBestMoveArray, newMoveArray);
newBestMoveArray = JSON.parse(JSON.stringify(newBestMoveArray));
if (worker) {
worker.postMessage({
msg: "solve-progress",
moveCount: newBestMoveArray.length,
});
}
2022-09-07 08:49:25 -04:00
// We cleared the pyramid
if (pyramid.isCleared) {
newMoveArray.push(MoveString.gameWon());
return [GameStates.won, newMoveArray, []];
2022-09-07 08:49:25 -04:00
}
// We have run out of stock cards
if (stockIndex >= stockArray.length) {
newMoveArray.push(MoveString.gameLost());
return [GameStates.lost, newMoveArray, newBestMoveArray];
2022-09-07 08:49:25 -04:00
}
const topStock = new Card(stockArray[stockIndex]);
let freeCardsIndices = pyramid.freeCardIndices;
// match free cards with stock
for (let i = 0; i < freeCardsIndices.length; i++) {
let cardA = new Card(pyramidArray[freeCardsIndices[i]]);
if (!cardA.isSequential(topStock)) continue;
let newStock = JSON.parse(JSON.stringify(stockArray));
2022-09-07 09:49:52 -04:00
newStock.splice(++stockIndex, 0, cardA.toString);
2019-07-27 07:46:57 -04:00
2022-09-07 08:49:25 -04:00
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.match(cardA.toString));
2019-07-27 07:46:57 -04:00
2022-09-07 08:49:25 -04:00
let newPyramidArray = JSON.parse(JSON.stringify(pyramidArray));
newPyramidArray[freeCardsIndices[i]] = 0;
2019-07-27 07:46:57 -04:00
let result = await solve(
2022-09-08 06:58:05 -04:00
newPyramidArray,
newStock,
worker,
2022-09-08 06:58:05 -04:00
stockIndex,
newMoveArray,
newBestMoveArray
);
2022-09-07 08:49:25 -04:00
if (result[0] === GameStates.won) return result;
// if we didn't win from this move tree, let's grab the best move array
// if it's better than what we already have
newBestMoveArray = getBestMoveArray(newBestMoveArray, result[2]);
newBestMoveArray = JSON.parse(JSON.stringify(newBestMoveArray));
2022-09-08 06:58:05 -04:00
}
2019-07-27 07:46:57 -04:00
2022-09-07 08:49:25 -04:00
// Flip over a new card
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.flipStock());
let result = await solve(
2022-09-08 06:58:05 -04:00
pyramidArray,
stockArray,
worker,
2022-09-08 08:58:49 -04:00
++stockIndex,
2022-09-08 06:58:05 -04:00
newMoveArray,
newBestMoveArray
);
2022-09-07 08:49:25 -04:00
if (result[0] === GameStates.won) return result;
// if we didn't win from this move tree, let's grab the best move array
// if it's better than what we already have
newBestMoveArray = getBestMoveArray(newBestMoveArray, result[2]);
newBestMoveArray = JSON.parse(JSON.stringify(newBestMoveArray));
2019-07-27 07:46:57 -04:00
2022-09-07 08:49:25 -04:00
// This node was useless
return [GameStates.lost, moveArray, newBestMoveArray];
2019-07-27 07:46:57 -04:00
}
2019-08-03 07:41:36 -04:00
export { Card, solve };