From 2b2e1ec0c9c553ce1df9efebcf53971a6934ff05 Mon Sep 17 00:00:00 2001 From: Adam Piontek Date: Wed, 7 Sep 2022 08:49:25 -0400 Subject: [PATCH] after running a lint + format --- .eslintrc.js | 2 +- README.md | 8 +- solver.js | 212 ++++++++++++++++++++++++++++----------------------- test.js | 20 ++--- 4 files changed, 131 insertions(+), 111 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f096561..40c9aad 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,5 +8,5 @@ module.exports = { node: true, mocha: true, }, - extends: ["eslint:recommended", "prettier"] + extends: ["eslint:recommended", "prettier"], }; diff --git a/README.md b/README.md index 75b4b08..769182c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # javascript-tri-peaks-solitaire-solver + 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/): - 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" - 3. Click "solve" +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" +3. Click "solve" ## Notes This is probably quite a poor implementation. Please don't fault me, I am teaching myself javascript. - diff --git a/solver.js b/solver.js index 8555ef6..98cc044 100644 --- a/solver.js +++ b/solver.js @@ -1,75 +1,98 @@ class Card { - constructor(cardString) { - this.rank = cardString[0]; - this.suit = cardString[1]; - } + 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); - } + /** + * 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 (this.integerValue + 1) % 13 === card.integerValue || (this.integerValue - 1) % 13 === card.integerValue; - } + /** + * 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 ( + (this.integerValue + 1) % 13 === card.integerValue || + (this.integerValue - 1) % 13 === card.integerValue + ); + } - get toString() { - return this.rank + this.suit; - } + get toString() { + return this.rank + this.suit; + } } class Pyramid { - constructor(pyramidArray) { - this.array = pyramidArray; - } + constructor(pyramidArray) { + this.array = pyramidArray; + } - get isCleared() { - for (let i = 0; i < this.array.length; i++) { - if (this.array[i] !== 0) - return false; - } - return true; + get isCleared() { + for (let i = 0; i < this.array.length; i++) { + if (this.array[i] !== 0) return false; } + return true; + } - 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); - } - return freeIndices; + 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); } + return freeIndices; + } } class MoveString { - 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.";} + 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."; + } } -const GameStates = Object.freeze({"won": true, "lost": false}); +const GameStates = Object.freeze({ won: true, lost: false }); /** * Solves a Tri Peaks solitaire game. @@ -80,53 +103,50 @@ const GameStates = Object.freeze({"won": true, "lost": false}); * @returns {*[]|([*, *]|[*, *]|[*, *])} */ function solve(pyramidArray, stockArray, stockIndex = 0, moveArray = []) { - let newMoveArray = JSON.parse(JSON.stringify(moveArray)); - let pyramid = new Pyramid(pyramidArray); + let newMoveArray = JSON.parse(JSON.stringify(moveArray)); + let pyramid = new Pyramid(pyramidArray); - // We cleared the pyramid - if (pyramid.isCleared) { - newMoveArray.push(MoveString.gameWon()); - return [GameStates.won, newMoveArray]; - } + // We cleared the pyramid + if (pyramid.isCleared) { + newMoveArray.push(MoveString.gameWon()); + return [GameStates.won, newMoveArray]; + } - // We have run out of stock cards - if (stockIndex >= stockArray.length) { - newMoveArray.push(MoveString.gameLost()); - return [GameStates.lost, newMoveArray]; - } + // We have run out of stock cards + if (stockIndex >= stockArray.length) { + newMoveArray.push(MoveString.gameLost()); + return [GameStates.lost, newMoveArray]; + } - const topStock = new Card(stockArray[stockIndex]); + 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)); - newStock.splice(stockIndex + 1, 0, cardA.toString); - 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)); + newStock.splice(stockIndex + 1, 0, cardA.toString); + 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.push(MoveString.flipStock()); - let result = solve(pyramidArray, stockArray, stockIndex + 1, newMoveArray); - if (result[0] === GameStates.won) - return result; + newMoveArray.push(MoveString.match(cardA.toString)); - // This node was useless - return [GameStates.lost, moveArray]; + 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.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; diff --git a/test.js b/test.js index 9463f2e..59aa081 100644 --- a/test.js +++ b/test.js @@ -1,16 +1,16 @@ -const assert = require('assert'); -const solver = require('./solver'); +const assert = require("assert"); +const solver = require("./solver"); 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", - "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" + "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", ]; -it('should solve known games', () => { +it("should solve known games", () => { assert.equal(true, true); - solvable_games.forEach(function (i) { - const array = i.split(' '); - const result = solver.solve(array.slice(0, 28), array.slice(28, 52), 0, []); - assert.equal(result[0], true); - }); + solvable_games.forEach(function (i) { + const array = i.split(" "); + const result = solver.solve(array.slice(0, 28), array.slice(28, 52), 0, []); + assert.equal(result[0], true); + }); });