From f3e1875a1b89e473d0c4572387fc40db196c06c7 Mon Sep 17 00:00:00 2001 From: Adam Piontek Date: Wed, 14 Sep 2022 09:54:03 -0400 Subject: [PATCH] several layout & appearance changes --- README.md | 18 +- dist/assets/index.432054e5.js | 64 +++++ dist/assets/index.58a4d054.css | 6 + dist/assets/solverWorker.25a6ccd7.js | 1 + dist/index.html | 1 + package-lock.json | 4 +- run.js | 39 --- solver.js | 2 + src/cards/2B.svg | 2 +- src/img/73k.svg | 1 + src/img/bi-github.svg | 3 + src/img/bi-twitter.svg | 3 + src/index.html | 410 +++++++++++++-------------- src/js/main.js | 42 ++- vite.config.js | 5 +- 15 files changed, 330 insertions(+), 271 deletions(-) create mode 100644 dist/assets/index.432054e5.js create mode 100644 dist/assets/index.58a4d054.css create mode 100644 dist/assets/solverWorker.25a6ccd7.js create mode 100644 dist/index.html delete mode 100644 run.js create mode 100644 src/img/73k.svg create mode 100644 src/img/bi-github.svg create mode 100644 src/img/bi-twitter.svg diff --git a/README.md b/README.md index 5f53558..e28aaf0 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,15 @@ -# javascript-tri-peaks-solitaire-solver +# Tripeaks Solitaire Solver 73k -A brute force solver for Microsoft Tri-Peaks solitaire written in javascript. +A brute force solver for Microsoft Tripeaks solitaire written in javascript. -This is a fork of [Courtney Pitcher's project](https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver), with several changes. +The `dist` folder contains an HTML+JS interface for entering cards and getting solutions. You can open it directly in a browser to try it out. -## Changes +The solver can provide a "best moves" list (the first set of moves found that removes the most cards from the board) for unsolvable games, and games-in-progress (when you don't already know all the cards). -- fixed card matching, implemented card matching tests -- solver now returns a "first best set of moves found" for unsolvable games (the first set of moves found that removes the most cards from the board) +This began as a fork of [Courtney Pitcher's project](https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver), and I'm grateful for the solving algorithm & inspiration. ## Notes - Unsolvable games can take 6 or more minutes to solve, so be patient. -- Per Courtney Pitcher, "This is probably quite a poor implementation." Please don't fault either of us, he was teaching himself javascript, and I'm probably even less qualified... - -## TODO - -- [ ] HTML ui/demo _in progress_ +- Per Courtney Pitcher, "This is probably quite a poor implementation." Please don't fault either of us, he was teaching himself javascript, and I'm just having fun. +- The HTML+JS interface is built with [Vite](https://vitejs.dev/), [Alpine.js](https://alpinejs.dev/), [Bootstrap](https://getbootstrap.com/), and some [free customized SVG playing cards](https://www.me.uk/cards/). Linting & formatting is included. diff --git a/dist/assets/index.432054e5.js b/dist/assets/index.432054e5.js new file mode 100644 index 0000000..d450aaf --- /dev/null +++ b/dist/assets/index.432054e5.js @@ -0,0 +1,64 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))s(r);new MutationObserver(r=>{for(const c of r)if(c.type==="childList")for(const l of c.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&s(l)}).observe(document,{childList:!0,subtree:!0});function i(r){const c={};return r.integrity&&(c.integrity=r.integrity),r.referrerpolicy&&(c.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?c.credentials="include":r.crossorigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function s(r){if(r.ep)return;r.ep=!0;const c=i(r);fetch(r.href,c)}})();var b0=!1,L0=!1,H=[];function H5(e){V5(e)}function V5(e){H.includes(e)||H.push(e),D5()}function C1(e){let t=H.indexOf(e);t!==-1&&H.splice(t,1)}function D5(){!L0&&!b0&&(b0=!0,queueMicrotask(R5))}function R5(){b0=!1,L0=!0;for(let e=0;ee.effect(t,{scheduler:i=>{_0?H5(i):i()}}),f1=e.raw}function n1(e){N=e}function Z5(e){let t=()=>{};return[s=>{let r=N(s);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(c=>c())}),e._x_effects.add(r),t=()=>{r!==void 0&&(e._x_effects.delete(r),a0(r))},r},()=>{t()}]}var x1=[],w1=[],g1=[];function K5(e){g1.push(e)}function m1(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,w1.push(t))}function F5(e){x1.push(e)}function J5(e,t,i){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(i)}function p1(e,t){!e._x_attributeCleanups||Object.entries(e._x_attributeCleanups).forEach(([i,s])=>{(t===void 0||t.includes(i))&&(s.forEach(r=>r()),delete e._x_attributeCleanups[i])})}var j0=new MutationObserver(U0),$0=!1;function v1(){j0.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),$0=!0}function E5(){Q5(),j0.disconnect(),$0=!1}var I=[],y0=!1;function Q5(){I=I.concat(j0.takeRecords()),I.length&&!y0&&(y0=!0,queueMicrotask(()=>{O5(),y0=!1}))}function O5(){U0(I),I.length=0}function g(e){if(!$0)return e();E5();let t=e();return v1(),t}var I0=!1,l0=[];function Y5(){I0=!0}function j5(){I0=!1,U0(l0),l0=[]}function U0(e){if(I0){l0=l0.concat(e);return}let t=[],i=[],s=new Map,r=new Map;for(let c=0;cl.nodeType===1&&t.push(l)),e[c].removedNodes.forEach(l=>l.nodeType===1&&i.push(l))),e[c].type==="attributes")){let l=e[c].target,n=e[c].attributeName,h=e[c].oldValue,a=()=>{s.has(l)||s.set(l,[]),s.get(l).push({name:n,value:l.getAttribute(n)})},o=()=>{r.has(l)||r.set(l,[]),r.get(l).push(n)};l.hasAttribute(n)&&h===null?a():l.hasAttribute(n)?(o(),a()):o()}r.forEach((c,l)=>{p1(l,c)}),s.forEach((c,l)=>{x1.forEach(n=>n(l,c))});for(let c of i)if(!t.includes(c)&&(w1.forEach(l=>l(c)),c._x_cleanups))for(;c._x_cleanups.length;)c._x_cleanups.pop()();t.forEach(c=>{c._x_ignoreSelf=!0,c._x_ignore=!0});for(let c of t)i.includes(c)||!c.isConnected||(delete c._x_ignoreSelf,delete c._x_ignore,g1.forEach(l=>l(c)),c._x_ignore=!0,c._x_ignoreSelf=!0);t.forEach(c=>{delete c._x_ignoreSelf,delete c._x_ignore}),t=null,i=null,s=null,r=null}function k1(e){return W(F(e))}function X(e,t,i){return e._x_dataStack=[t,...F(i||e)],()=>{e._x_dataStack=e._x_dataStack.filter(s=>s!==t)}}function h1(e,t){let i=e._x_dataStack[0];Object.entries(t).forEach(([s,r])=>{i[s]=r})}function F(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?F(e.host):e.parentNode?F(e.parentNode):[]}function W(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(i=>Object.keys(i)))),has:(i,s)=>e.some(r=>r.hasOwnProperty(s)),get:(i,s)=>(e.find(r=>{if(r.hasOwnProperty(s)){let c=Object.getOwnPropertyDescriptor(r,s);if(c.get&&c.get._x_alreadyBound||c.set&&c.set._x_alreadyBound)return!0;if((c.get||c.set)&&c.enumerable){let l=c.get,n=c.set,h=c;l=l&&l.bind(t),n=n&&n.bind(t),l&&(l._x_alreadyBound=!0),n&&(n._x_alreadyBound=!0),Object.defineProperty(r,s,{...h,get:l,set:n})}return!0}return!1})||{})[s],set:(i,s,r)=>{let c=e.find(l=>l.hasOwnProperty(s));return c?c[s]=r:e[e.length-1][s]=r,!0}});return t}function y1(e){let t=s=>typeof s=="object"&&!Array.isArray(s)&&s!==null,i=(s,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(s)).forEach(([c,{value:l,enumerable:n}])=>{if(n===!1||l===void 0)return;let h=r===""?c:`${r}.${c}`;typeof l=="object"&&l!==null&&l._x_interceptor?s[c]=l.initialize(e,h,c):t(l)&&l!==s&&!(l instanceof Element)&&i(l,h)})};return i(e)}function S1(e,t=()=>{}){let i={initialValue:void 0,_x_interceptor:!0,initialize(s,r,c){return e(this.initialValue,()=>$5(s,r),l=>H0(s,r,l),r,c)}};return t(i),s=>{if(typeof s=="object"&&s!==null&&s._x_interceptor){let r=i.initialize.bind(i);i.initialize=(c,l,n)=>{let h=s.initialize(c,l,n);return i.initialValue=h,r(c,l,n)}}else i.initialValue=s;return i}}function $5(e,t){return t.split(".").reduce((i,s)=>i[s],e)}function H0(e,t,i){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=i;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),H0(e[t[0]],t.slice(1),i)}}var z1={};function y(e,t){z1[e]=t}function V0(e,t){return Object.entries(z1).forEach(([i,s])=>{Object.defineProperty(e,`$${i}`,{get(){let[r,c]=V1(t);return r={interceptor:S1,...r},m1(t,c),s(t,r)},enumerable:!1})}),e}function I5(e,t,i,...s){try{return i(...s)}catch(r){P(r,e,t)}}function P(e,t,i=void 0){Object.assign(e,{el:t,expression:i}),console.warn(`Alpine Expression Error: ${e.message} + +${i?'Expression: "'+i+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var r0=!0;function U5(e){let t=r0;r0=!1,e(),r0=t}function K(e,t,i={}){let s;return p(e,t)(r=>s=r,i),s}function p(...e){return A1(...e)}var A1=b1;function q5(e){A1=e}function b1(e,t){let i={};V0(i,e);let s=[i,...F(e)];if(typeof t=="function")return P5(s,t);let r=X5(s,t,e);return I5.bind(null,e,t,r)}function P5(e,t){return(i=()=>{},{scope:s={},params:r=[]}={})=>{let c=t.apply(W([s,...e]),r);n0(i,c)}}var S0={};function N5(e,t){if(S0[e])return S0[e];let i=Object.getPrototypeOf(async function(){}).constructor,s=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e,c=(()=>{try{return new i(["__self","scope"],`with (scope) { __self.result = ${s} }; __self.finished = true; return __self.result;`)}catch(l){return P(l,t,e),Promise.resolve()}})();return S0[e]=c,c}function X5(e,t,i){let s=N5(t,i);return(r=()=>{},{scope:c={},params:l=[]}={})=>{s.result=void 0,s.finished=!1;let n=W([c,...e]);if(typeof s=="function"){let h=s(s,n).catch(a=>P(a,i,t));s.finished?(n0(r,s.result,n,l,i),s.result=void 0):h.then(a=>{n0(r,a,n,l,i)}).catch(a=>P(a,i,t)).finally(()=>s.result=void 0)}}}function n0(e,t,i,s,r){if(r0&&typeof t=="function"){let c=t.apply(i,s);c instanceof Promise?c.then(l=>n0(e,l,i,s)).catch(l=>P(l,r,t)):e(c)}else e(t)}var q0="x-";function O(e=""){return q0+e}function W5(e){q0=e}var L1={};function x(e,t){L1[e]=t}function P0(e,t,i){if(t=Array.from(t),e._x_virtualDirectives){let c=Object.entries(e._x_virtualDirectives).map(([n,h])=>({name:n,value:h})),l=_1(c);c=c.map(n=>l.find(h=>h.name===n.name)?{name:`x-bind:${n.name}`,value:`"${n.value}"`}:n),t=t.concat(c)}let s={};return t.map(B1((c,l)=>s[c]=l)).filter(Z1).map(t2(s,i)).sort(i2).map(c=>e2(e,c))}function _1(e){return Array.from(e).map(B1()).filter(t=>!Z1(t))}var D0=!1,$=new Map,H1=Symbol();function G5(e){D0=!0;let t=Symbol();H1=t,$.set(t,[]);let i=()=>{for(;$.get(t).length;)$.get(t).shift()();$.delete(t)},s=()=>{D0=!1,i()};e(i),s()}function V1(e){let t=[],i=n=>t.push(n),[s,r]=Z5(e);return t.push(r),[{Alpine:G,effect:s,cleanup:i,evaluateLater:p.bind(p,e),evaluate:K.bind(K,e)},()=>t.forEach(n=>n())]}function e2(e,t){let i=()=>{},s=L1[t.type]||i,[r,c]=V1(e);J5(e,t.original,c);let l=()=>{e._x_ignore||e._x_ignoreSelf||(s.inline&&s.inline(e,t,r),s=s.bind(s,e,t,r),D0?$.get(H1).push(s):s())};return l.runCleanups=c,l}var D1=(e,t)=>({name:i,value:s})=>(i.startsWith(e)&&(i=i.replace(e,t)),{name:i,value:s}),R1=e=>e;function B1(e=()=>{}){return({name:t,value:i})=>{let{name:s,value:r}=T1.reduce((c,l)=>l(c),{name:t,value:i});return s!==t&&e(s,t),{name:s,value:r}}}var T1=[];function N0(e){T1.push(e)}function Z1({name:e}){return K1().test(e)}var K1=()=>new RegExp(`^${q0}([^:^.]+)\\b`);function t2(e,t){return({name:i,value:s})=>{let r=i.match(K1()),c=i.match(/:([a-zA-Z0-9\-:]+)/),l=i.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],n=t||e[i]||i;return{type:r?r[1]:null,value:c?c[1]:null,modifiers:l.map(h=>h.replace(".","")),expression:s,original:n}}}var R0="DEFAULT",i0=["ignore","ref","data","id","bind","init","for","mask","model","modelable","transition","show","if",R0,"teleport"];function i2(e,t){let i=i0.indexOf(e.type)===-1?R0:e.type,s=i0.indexOf(t.type)===-1?R0:t.type;return i0.indexOf(i)-i0.indexOf(s)}function U(e,t,i={}){e.dispatchEvent(new CustomEvent(t,{detail:i,bubbles:!0,composed:!0,cancelable:!0}))}var B0=[],X0=!1;function F1(e=()=>{}){return queueMicrotask(()=>{X0||setTimeout(()=>{T0()})}),new Promise(t=>{B0.push(()=>{e(),t()})})}function T0(){for(X0=!1;B0.length;)B0.shift()()}function s2(){X0=!0}function R(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(r=>R(r,t));return}let i=!1;if(t(e,()=>i=!0),i)return;let s=e.firstElementChild;for(;s;)R(s,t),s=s.nextElementSibling}function J(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function r2(){document.body||J("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `

Enter cards representing a Tripeaks game below. Enter each card as 2 characters representing the rank (A, 2, 3, 4, 5, 6, 7, 8, 9, T, J, Q, K) and suit (C, D, H, S). For example, "TH" is the Ten of Hearts.

You don't have to know all the cards, this solver will give you as many moves as it can. Enter a "0" (a single zero) for unknown cards. You don't need to enter unknown cards before the first card you know. (However, the last 34 cards (base of peaks, stock) must be known.)

Entry is case-insensitive, and you can separate cards with any character (space, comma, etc), or use no separator.

Valid examples

These will complete quickly:

  • 7S 5D 7C 2D 0 0 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
  • 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
  • 7c03s0qsjc00JSasTSadtcqd9s4s2h9h8sjh6c3dks5s5c6h9C2Cac8C6d5DTH8dkckd9d4c5h8hqh6s

This example will take several minutes, but will provide a solution, and should demonstrate that the page stays responsive and provides feedback on the solving progress:

  • 2D 6D AD 9S 4C 7C 7S 7D 9C 2S AC 8D 6S 6H 3C 5H QS JS 4S JH 5C AS 3H 3S AH TD 4D 5S TH 7H KS QH 6C KD 8S 2C TC JC 5D 3D 2H TS 4H JD KC KH 8H QC 8C QD 9D 9H

Enter Your Cards

Game Row 1, Top of PeaksGame Row 2, Second RowGame Row 3, Third RowGame Row 4, Base of PeaksGame Stock, the Draw Cards

Enter cards and click Solve to get a solution.

\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1285467..cff31ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "javascript-tri-peaks-solitaire-solver", + "name": "tripeaks-solitaire-solver-js-73k", "version": "1.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "javascript-tri-peaks-solitaire-solver", + "name": "tripeaks-solitaire-solver-js-73k", "version": "1.0.1", "license": "ISC", "devDependencies": { diff --git a/run.js b/run.js deleted file mode 100644 index 2fb3f27..0000000 --- a/run.js +++ /dev/null @@ -1,39 +0,0 @@ -const solver = require("./solver"); - -// // Easy MS Tripeaks level -// let userCardsInput = " js as ts ad tc qd 9s 4s 2h 9h 8s jh 6c 3d ks 5s 5c 6h 9c 2c ac 8c 6d 5d th 8d kc kd 9d 4c 5h 8h qh 6s " -// userCardsInput = " 2d 7c 7d 3s kh qs jc 2s 7s " + userCardsInput -// userCardsInput = " qc 3h 3c 7h td 4h " + userCardsInput -// userCardsInput = " jd 4d ah " + userCardsInput -// Unsolvable board -let userCardsInput = - "2D 6D AD 9S 4C 7C 7S 7D 9C 2S AC 8D 6S 6H 3C 5H QS JS 4S JH 5C AS 3H 3S AH TD 4D 5S TH 7H KS QH 6C KD 8S 2C TC JC 5D 3D 2H TS 4H JD KC KH 8H QC 8C QD 9D 9H"; - -// continue! -userCardsInput = userCardsInput.toUpperCase(); -let userCards = userCardsInput.split(" ").filter((s) => s); -userCards = Array(52 - userCards.length) - .fill("0") - .concat(userCards) - .map((c) => (c === "0" ? 0 : c)); - -let start_time = process.hrtime.bigint(); -let result = solver.solve( - userCards.slice(0, 28), - userCards.slice(28, 52), - 0, - [] -); -let end_time = process.hrtime.bigint(); - -let resultStr = JSON.stringify(result, null, 2); -process.stdout.write(resultStr + "\n"); -process.stdout.write(`moves array length: ${result[1].length}\n`); -process.stdout.write(`best moves array length: ${result[2].length}\n`); - -const MS_PER_NS = 1e-6; -const NS_PER_SEC = 1e9; -let elapsedMs = Number(end_time - start_time) * MS_PER_NS; -let elapsedS = Number(end_time - start_time) / NS_PER_SEC; -process.stdout.write(`solving took: ${elapsedMs} milliseconds\n`); -process.stdout.write(`solving took: ${elapsedS} seconds\n`); diff --git a/solver.js b/solver.js index 3306929..dfc313a 100644 --- a/solver.js +++ b/solver.js @@ -101,8 +101,10 @@ function getBestMoveArray(bestMoveArray, newMoveArray) { * 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. + * @param worker Optional Web Worker for reporting progress. * @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. + * @param bestMoveArray The best list of moves that have been found so far. * @returns {Promise<{*[]|([*, *, *]|[*, *, *]|[*, *, *])}>} */ async function solve( diff --git a/src/cards/2B.svg b/src/cards/2B.svg index 5f222f5..ece3711 100644 --- a/src/cards/2B.svg +++ b/src/cards/2B.svg @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/img/73k.svg b/src/img/73k.svg new file mode 100644 index 0000000..0400a9b --- /dev/null +++ b/src/img/73k.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/img/bi-github.svg b/src/img/bi-github.svg new file mode 100644 index 0000000..bb4e45c --- /dev/null +++ b/src/img/bi-github.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/img/bi-twitter.svg b/src/img/bi-twitter.svg new file mode 100644 index 0000000..8a83fa6 --- /dev/null +++ b/src/img/bi-twitter.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/index.html b/src/index.html index 50e6070..a949c37 100644 --- a/src/index.html +++ b/src/index.html @@ -1,69 +1,33 @@ - - Tripeaks Solver 73k -