after running a lint + format

This commit is contained in:
Adam Piontek 2022-09-07 08:49:25 -04:00
parent 1a47184c29
commit 2b2e1ec0c9
4 changed files with 131 additions and 111 deletions

View file

@ -8,5 +8,5 @@ module.exports = {
node: true, node: true,
mocha: true, mocha: true,
}, },
extends: ["eslint:recommended", "prettier"] extends: ["eslint:recommended", "prettier"],
}; };

View file

@ -1,13 +1,13 @@
# javascript-tri-peaks-solitaire-solver # javascript-tri-peaks-solitaire-solver
A brute force solver for Microsoft Tri-Peaks solitaire written in javascript. A brute force solver for Microsoft Tri-Peaks solitaire written in javascript.
You can see it in action on my [website](https://igniparoustempest.github.io/tri-peaks-solitaire-solver/): You can see it in action on my [website](https://igniparoustempest.github.io/tri-peaks-solitaire-solver/):
1. Enter the string "8S TS 4D 7S 5D 7C 2D JH AC 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H 5C 5S 4C 2C QD 8C KD 3D KS JD 2S 7D KH AH 5H 9S 4S QH 6S 6D 3C JC TC 8H 6C TH AS AD 6H" into the textfield. 1. Enter the string "8S TS 4D 7S 5D 7C 2D JH AC 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H 5C 5S 4C 2C QD 8C KD 3D KS JD 2S 7D KH AH 5H 9S 4S QH 6S 6D 3C JC TC 8H 6C TH AS AD 6H" into the textfield.
2. Click "import" 2. Click "import"
3. Click "solve" 3. Click "solve"
## Notes ## Notes
This is probably quite a poor implementation. Please don't fault me, I am teaching myself javascript. This is probably quite a poor implementation. Please don't fault me, I am teaching myself javascript.

212
solver.js
View file

@ -1,75 +1,98 @@
class Card { class Card {
constructor(cardString) { constructor(cardString) {
this.rank = cardString[0]; this.rank = cardString[0];
this.suit = cardString[1]; this.suit = cardString[1];
} }
/** /**
* Gets the face value of the card, from [0 .. 12]. * Gets the face value of the card, from [0 .. 12].
* @returns {number} The value. * @returns {number} The value.
*/ */
get integerValue() { get integerValue() {
return "A23456789TJQK".indexOf(this.rank); return "A23456789TJQK".indexOf(this.rank);
} }
/** /**
* Returns true if the other card has a face value one above or below this card. * Returns true if the other card has a face value one above or below this card.
* @param card Another card. * @param card Another card.
* @returns {boolean} True if sequential, false otherwise. * @returns {boolean} True if sequential, false otherwise.
*/ */
isSequential(card) { isSequential(card) {
return (this.integerValue + 1) % 13 === card.integerValue || (this.integerValue - 1) % 13 === card.integerValue; return (
} (this.integerValue + 1) % 13 === card.integerValue ||
(this.integerValue - 1) % 13 === card.integerValue
);
}
get toString() { get toString() {
return this.rank + this.suit; return this.rank + this.suit;
} }
} }
class Pyramid { class Pyramid {
constructor(pyramidArray) { constructor(pyramidArray) {
this.array = pyramidArray; this.array = pyramidArray;
} }
get isCleared() { get isCleared() {
for (let i = 0; i < this.array.length; i++) { for (let i = 0; i < this.array.length; i++) {
if (this.array[i] !== 0) if (this.array[i] !== 0) return false;
return false;
}
return true;
} }
return true;
}
get freeCardIndices() { get freeCardIndices() {
let freeIndices = []; let freeIndices = [];
for (let i = this.array.length - 1; i >= 0; i--) { for (let i = this.array.length - 1; i >= 0; i--) {
if (this.array[i] === 0) if (this.array[i] === 0) continue;
continue; const secondOffset = Math.floor((i - 3) / 2);
const secondOffset = Math.floor((i - 3) / 2); // This is the last row
// This is the last row if (i >= 18) freeIndices.push(i);
if (i >= 18) // Third row
freeIndices.push(i); else if (
// Third row i <= 17 &&
else if (i <= 17 && i >= 9 && this.array[i + 9] === 0 && this.array[i + 10] === 0) i >= 9 &&
freeIndices.push(i); this.array[i + 9] === 0 &&
// Second row this.array[i + 10] === 0
else if (i <= 8 && i >= 3 && this.array[i + 6 + secondOffset] === 0 && this.array[i + 7 + secondOffset] === 0) )
freeIndices.push(i); freeIndices.push(i);
// First row // Second row
else if (i <= 2 && i >= 0 && this.array[i + 3 + i] === 0 && this.array[i + 4 + i] === 0) else if (
freeIndices.push(i); i <= 8 &&
} i >= 3 &&
return freeIndices; 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);
} }
return freeIndices;
}
} }
class MoveString { class MoveString {
static gameWon() {return "You have won.";} static gameWon() {
static gameLost() {return "There are no more valid moves.";} return "You have won.";
static match(cardA) {return "Move " + cardA + " onto the stock.";} }
static flipStock() {return "Draw a new stock card.";} 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.";
}
} }
const GameStates = Object.freeze({"won": true, "lost": false}); const GameStates = Object.freeze({ won: true, lost: false });
/** /**
* Solves a Tri Peaks solitaire game. * Solves a Tri Peaks solitaire game.
@ -80,53 +103,50 @@ const GameStates = Object.freeze({"won": true, "lost": false});
* @returns {*[]|([*, *]|[*, *]|[*, *])} * @returns {*[]|([*, *]|[*, *]|[*, *])}
*/ */
function solve(pyramidArray, stockArray, stockIndex = 0, moveArray = []) { function solve(pyramidArray, stockArray, stockIndex = 0, moveArray = []) {
let newMoveArray = JSON.parse(JSON.stringify(moveArray)); let newMoveArray = JSON.parse(JSON.stringify(moveArray));
let pyramid = new Pyramid(pyramidArray); let pyramid = new Pyramid(pyramidArray);
// We cleared the pyramid // We cleared the pyramid
if (pyramid.isCleared) { if (pyramid.isCleared) {
newMoveArray.push(MoveString.gameWon()); newMoveArray.push(MoveString.gameWon());
return [GameStates.won, newMoveArray]; return [GameStates.won, newMoveArray];
} }
// We have run out of stock cards // We have run out of stock cards
if (stockIndex >= stockArray.length) { if (stockIndex >= stockArray.length) {
newMoveArray.push(MoveString.gameLost()); newMoveArray.push(MoveString.gameLost());
return [GameStates.lost, newMoveArray]; return [GameStates.lost, newMoveArray];
} }
const topStock = new Card(stockArray[stockIndex]); const topStock = new Card(stockArray[stockIndex]);
let freeCardsIndices = pyramid.freeCardIndices; let freeCardsIndices = pyramid.freeCardIndices;
// match free cards with stock // match free cards with stock
for (let i = 0; i < freeCardsIndices.length; i++) { for (let i = 0; i < freeCardsIndices.length; i++) {
let cardA = new Card(pyramidArray[freeCardsIndices[i]]); let cardA = new Card(pyramidArray[freeCardsIndices[i]]);
if (!cardA.isSequential(topStock)) if (!cardA.isSequential(topStock)) continue;
continue; let newStock = JSON.parse(JSON.stringify(stockArray));
let newStock = JSON.parse(JSON.stringify(stockArray)); newStock.splice(stockIndex + 1, 0, cardA.toString);
newStock.splice(stockIndex + 1, 0, cardA.toString); stockIndex++;
stockIndex++;
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.match(cardA.toString));
let newPyramidArray = JSON.parse(JSON.stringify(pyramidArray));
newPyramidArray[freeCardsIndices[i]] = 0;
let result = solve(newPyramidArray, newStock, stockIndex, newMoveArray);
if (result[0] === GameStates.won)
return result;
}
// Flip over a new card
newMoveArray = JSON.parse(JSON.stringify(moveArray)); newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.flipStock()); newMoveArray.push(MoveString.match(cardA.toString));
let result = solve(pyramidArray, stockArray, stockIndex + 1, newMoveArray);
if (result[0] === GameStates.won)
return result;
// This node was useless let newPyramidArray = JSON.parse(JSON.stringify(pyramidArray));
return [GameStates.lost, moveArray]; newPyramidArray[freeCardsIndices[i]] = 0;
let result = solve(newPyramidArray, newStock, stockIndex, newMoveArray);
if (result[0] === GameStates.won) return result;
}
// Flip over a new card
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.flipStock());
let result = solve(pyramidArray, stockArray, stockIndex + 1, newMoveArray);
if (result[0] === GameStates.won) return result;
// This node was useless
return [GameStates.lost, moveArray];
} }
exports.solve = solve; exports.solve = solve;

20
test.js
View file

@ -1,16 +1,16 @@
const assert = require('assert'); const assert = require("assert");
const solver = require('./solver'); const solver = require("./solver");
const solvable_games = [ const solvable_games = [
"8S TS 4D 7S 5D 7C 2D JH AC 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H 5C 5S 4C 2C QD 8C KD 3D KS JD 2S 7D KH AH 5H 9S 4S QH 6S 6D 3C JC TC 8H 6C TH AS AD 6H", "8S TS 4D 7S 5D 7C 2D JH AC 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H 5C 5S 4C 2C QD 8C KD 3D KS JD 2S 7D KH AH 5H 9S 4S QH 6S 6D 3C JC TC 8H 6C TH AS AD 6H",
"5D 9C 5S QS 8S 9D AS 5C 2S QD KC 9H 4H QC 2H 8D 4C 4D JC TS 6D 7H QH 3S 5H JH 6H 2D AC 7S 7C 3D KD 9S 3C TH 6C AH 8H TC 4S 8C AD 3H KS 6S JS 7D JD TD 2C KH" "5D 9C 5S QS 8S 9D AS 5C 2S QD KC 9H 4H QC 2H 8D 4C 4D JC TS 6D 7H QH 3S 5H JH 6H 2D AC 7S 7C 3D KD 9S 3C TH 6C AH 8H TC 4S 8C AD 3H KS 6S JS 7D JD TD 2C KH",
]; ];
it('should solve known games', () => { it("should solve known games", () => {
assert.equal(true, true); assert.equal(true, true);
solvable_games.forEach(function (i) { solvable_games.forEach(function (i) {
const array = i.split(' '); const array = i.split(" ");
const result = solver.solve(array.slice(0, 28), array.slice(28, 52), 0, []); const result = solver.solve(array.slice(0, 28), array.slice(28, 52), 0, []);
assert.equal(result[0], true); assert.equal(result[0], true);
}); });
}); });