diff --git a/dist/assets/index.e57a41a0.js b/dist/assets/index.438c9d52.js similarity index 66% rename from dist/assets/index.e57a41a0.js rename to dist/assets/index.438c9d52.js index a19a6d4..4d22ac0 100644 --- a/dist/assets/index.e57a41a0.js +++ b/dist/assets/index.438c9d52.js @@ -1,64 +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 n of r)if(n.type==="childList")for(const o of n.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&s(o)}).observe(document,{childList:!0,subtree:!0});function i(r){const n={};return r.integrity&&(n.integrity=r.integrity),r.referrerpolicy&&(n.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?n.credentials="include":r.crossorigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function s(r){if(r.ep)return;r.ep=!0;const n=i(r);fetch(r.href,n)}})();var _0=!1,L0=!1,R=[];function Rt(e){Bt(e)}function Bt(e){R.includes(e)||R.push(e),Dt()}function xe(e){let t=R.indexOf(e);t!==-1&&R.splice(t,1)}function Dt(){!L0&&!_0&&(_0=!0,queueMicrotask(Ht))}function Ht(){_0=!1,L0=!0;for(let e=0;ee.effect(t,{scheduler:i=>{T0?Rt(i):i()}}),ge=e.raw}function le(e){z=e}function Ot(e){let t=()=>{};return[s=>{let r=z(s);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(n=>n())}),e._x_effects.add(r),t=()=>{r!==void 0&&(e._x_effects.delete(r),a0(r))},r},()=>{t()}]}var we=[],pe=[],Ce=[];function Ft(e){Ce.push(e)}function ye(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,pe.push(t))}function Yt(e){we.push(e)}function $t(e,t,i){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(i)}function ve(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 P0=new MutationObserver(N0),Z0=!1;function me(){P0.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),Z0=!0}function It(){Ut(),P0.disconnect(),Z0=!1}var j=[],S0=!1;function Ut(){j=j.concat(P0.takeRecords()),j.length&&!S0&&(S0=!0,queueMicrotask(()=>{Kt(),S0=!1}))}function Kt(){N0(j),j.length=0}function C(e){if(!Z0)return e();It();let t=e();return me(),t}var j0=!1,o0=[];function qt(){j0=!0}function Pt(){j0=!1,N0(o0),o0=[]}function N0(e){if(j0){o0=o0.concat(e);return}let t=[],i=[],s=new Map,r=new Map;for(let n=0;no.nodeType===1&&t.push(o)),e[n].removedNodes.forEach(o=>o.nodeType===1&&i.push(o))),e[n].type==="attributes")){let o=e[n].target,l=e[n].attributeName,h=e[n].oldValue,a=()=>{s.has(o)||s.set(o,[]),s.get(o).push({name:l,value:o.getAttribute(l)})},d=()=>{r.has(o)||r.set(o,[]),r.get(o).push(l)};o.hasAttribute(l)&&h===null?a():o.hasAttribute(l)?(d(),a()):d()}r.forEach((n,o)=>{ve(o,n)}),s.forEach((n,o)=>{we.forEach(l=>l(o,n))});for(let n of i)if(!t.includes(n)&&(pe.forEach(o=>o(n)),n._x_cleanups))for(;n._x_cleanups.length;)n._x_cleanups.pop()();t.forEach(n=>{n._x_ignoreSelf=!0,n._x_ignore=!0});for(let n of t)i.includes(n)||!n.isConnected||(delete n._x_ignoreSelf,delete n._x_ignore,Ce.forEach(o=>o(n)),n._x_ignore=!0,n._x_ignoreSelf=!0);t.forEach(n=>{delete n._x_ignoreSelf,delete n._x_ignore}),t=null,i=null,s=null,r=null}function ke(e){return G(Y(e))}function W(e,t,i){return e._x_dataStack=[t,...Y(i||e)],()=>{e._x_dataStack=e._x_dataStack.filter(s=>s!==t)}}function he(e,t){let i=e._x_dataStack[0];Object.entries(t).forEach(([s,r])=>{i[s]=r})}function Y(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?Y(e.host):e.parentNode?Y(e.parentNode):[]}function G(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 n=Object.getOwnPropertyDescriptor(r,s);if(n.get&&n.get._x_alreadyBound||n.set&&n.set._x_alreadyBound)return!0;if((n.get||n.set)&&n.enumerable){let o=n.get,l=n.set,h=n;o=o&&o.bind(t),l=l&&l.bind(t),o&&(o._x_alreadyBound=!0),l&&(l._x_alreadyBound=!0),Object.defineProperty(r,s,{...h,get:o,set:l})}return!0}return!1})||{})[s],set:(i,s,r)=>{let n=e.find(o=>o.hasOwnProperty(s));return n?n[s]=r:e[e.length-1][s]=r,!0}});return t}function Se(e){let t=s=>typeof s=="object"&&!Array.isArray(s)&&s!==null,i=(s,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(s)).forEach(([n,{value:o,enumerable:l}])=>{if(l===!1||o===void 0)return;let h=r===""?n:`${r}.${n}`;typeof o=="object"&&o!==null&&o._x_interceptor?s[n]=o.initialize(e,h,n):t(o)&&o!==s&&!(o instanceof Element)&&i(o,h)})};return i(e)}function be(e,t=()=>{}){let i={initialValue:void 0,_x_interceptor:!0,initialize(s,r,n){return e(this.initialValue,()=>Zt(s,r),o=>R0(s,r,o),r,n)}};return t(i),s=>{if(typeof s=="object"&&s!==null&&s._x_interceptor){let r=i.initialize.bind(i);i.initialize=(n,o,l)=>{let h=s.initialize(n,o,l);return i.initialValue=h,r(n,o,l)}}else i.initialValue=s;return i}}function Zt(e,t){return t.split(".").reduce((i,s)=>i[s],e)}function R0(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]]={}),R0(e[t[0]],t.slice(1),i)}}var Ae={};function S(e,t){Ae[e]=t}function B0(e,t){return Object.entries(Ae).forEach(([i,s])=>{Object.defineProperty(e,`$${i}`,{get(){let[r,n]=Be(t);return r={interceptor:be,...r},ye(t,n),s(t,r)},enumerable:!1})}),e}function jt(e,t,i,...s){try{return i(...s)}catch(r){Q(r,e,t)}}function Q(e,t,i=void 0){Object.assign(e,{el:t,expression:i}),console.warn(`Alpine Expression Error: ${e.message} +(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 n of r)if(n.type==="childList")for(const o of n.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&s(o)}).observe(document,{childList:!0,subtree:!0});function i(r){const n={};return r.integrity&&(n.integrity=r.integrity),r.referrerpolicy&&(n.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?n.credentials="include":r.crossorigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function s(r){if(r.ep)return;r.ep=!0;const n=i(r);fetch(r.href,n)}})();var L0=!1,T0=!1,R=[];function Rt(e){Bt(e)}function Bt(e){R.includes(e)||R.push(e),Dt()}function xe(e){let t=R.indexOf(e);t!==-1&&R.splice(t,1)}function Dt(){!T0&&!L0&&(L0=!0,queueMicrotask(Ht))}function Ht(){L0=!1,T0=!0;for(let e=0;ee.effect(t,{scheduler:i=>{R0?Rt(i):i()}}),ge=e.raw}function le(e){z=e}function Ot(e){let t=()=>{};return[s=>{let r=z(s);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(n=>n())}),e._x_effects.add(r),t=()=>{r!==void 0&&(e._x_effects.delete(r),d0(r))},r},()=>{t()}]}var we=[],pe=[],Ce=[];function Ft(e){Ce.push(e)}function ye(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,pe.push(t))}function Yt(e){we.push(e)}function It(e,t,i){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(i)}function ve(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 q0=new MutationObserver(N0),Z0=!1;function me(){q0.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),Z0=!0}function $t(){Ut(),q0.disconnect(),Z0=!1}var j=[],b0=!1;function Ut(){j=j.concat(q0.takeRecords()),j.length&&!b0&&(b0=!0,queueMicrotask(()=>{Kt(),b0=!1}))}function Kt(){N0(j),j.length=0}function C(e){if(!Z0)return e();$t();let t=e();return me(),t}var j0=!1,o0=[];function Pt(){j0=!0}function qt(){j0=!1,N0(o0),o0=[]}function N0(e){if(j0){o0=o0.concat(e);return}let t=[],i=[],s=new Map,r=new Map;for(let n=0;no.nodeType===1&&t.push(o)),e[n].removedNodes.forEach(o=>o.nodeType===1&&i.push(o))),e[n].type==="attributes")){let o=e[n].target,l=e[n].attributeName,h=e[n].oldValue,a=()=>{s.has(o)||s.set(o,[]),s.get(o).push({name:l,value:o.getAttribute(l)})},d=()=>{r.has(o)||r.set(o,[]),r.get(o).push(l)};o.hasAttribute(l)&&h===null?a():o.hasAttribute(l)?(d(),a()):d()}r.forEach((n,o)=>{ve(o,n)}),s.forEach((n,o)=>{we.forEach(l=>l(o,n))});for(let n of i)if(!t.includes(n)&&(pe.forEach(o=>o(n)),n._x_cleanups))for(;n._x_cleanups.length;)n._x_cleanups.pop()();t.forEach(n=>{n._x_ignoreSelf=!0,n._x_ignore=!0});for(let n of t)i.includes(n)||!n.isConnected||(delete n._x_ignoreSelf,delete n._x_ignore,Ce.forEach(o=>o(n)),n._x_ignore=!0,n._x_ignoreSelf=!0);t.forEach(n=>{delete n._x_ignoreSelf,delete n._x_ignore}),t=null,i=null,s=null,r=null}function ke(e){return G(Y(e))}function W(e,t,i){return e._x_dataStack=[t,...Y(i||e)],()=>{e._x_dataStack=e._x_dataStack.filter(s=>s!==t)}}function he(e,t){let i=e._x_dataStack[0];Object.entries(t).forEach(([s,r])=>{i[s]=r})}function Y(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?Y(e.host):e.parentNode?Y(e.parentNode):[]}function G(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 n=Object.getOwnPropertyDescriptor(r,s);if(n.get&&n.get._x_alreadyBound||n.set&&n.set._x_alreadyBound)return!0;if((n.get||n.set)&&n.enumerable){let o=n.get,l=n.set,h=n;o=o&&o.bind(t),l=l&&l.bind(t),o&&(o._x_alreadyBound=!0),l&&(l._x_alreadyBound=!0),Object.defineProperty(r,s,{...h,get:o,set:l})}return!0}return!1})||{})[s],set:(i,s,r)=>{let n=e.find(o=>o.hasOwnProperty(s));return n?n[s]=r:e[e.length-1][s]=r,!0}});return t}function Se(e){let t=s=>typeof s=="object"&&!Array.isArray(s)&&s!==null,i=(s,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(s)).forEach(([n,{value:o,enumerable:l}])=>{if(l===!1||o===void 0)return;let h=r===""?n:`${r}.${n}`;typeof o=="object"&&o!==null&&o._x_interceptor?s[n]=o.initialize(e,h,n):t(o)&&o!==s&&!(o instanceof Element)&&i(o,h)})};return i(e)}function be(e,t=()=>{}){let i={initialValue:void 0,_x_interceptor:!0,initialize(s,r,n){return e(this.initialValue,()=>Zt(s,r),o=>B0(s,r,o),r,n)}};return t(i),s=>{if(typeof s=="object"&&s!==null&&s._x_interceptor){let r=i.initialize.bind(i);i.initialize=(n,o,l)=>{let h=s.initialize(n,o,l);return i.initialValue=h,r(n,o,l)}}else i.initialValue=s;return i}}function Zt(e,t){return t.split(".").reduce((i,s)=>i[s],e)}function B0(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]]={}),B0(e[t[0]],t.slice(1),i)}}var Ae={};function S(e,t){Ae[e]=t}function D0(e,t){return Object.entries(Ae).forEach(([i,s])=>{Object.defineProperty(e,`$${i}`,{get(){let[r,n]=Be(t);return r={interceptor:be,...r},ye(t,n),s(t,r)},enumerable:!1})}),e}function jt(e,t,i,...s){try{return i(...s)}catch(r){Q(r,e,t)}}function Q(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 Nt(e){let t=r0;r0=!1,e(),r0=t}function F(e,t,i={}){let s;return v(e,t)(r=>s=r,i),s}function v(...e){return Me(...e)}var Me=_e;function Jt(e){Me=e}function _e(e,t){let i={};B0(i,e);let s=[i,...Y(e)];if(typeof t=="function")return Qt(s,t);let r=Wt(s,t,e);return jt.bind(null,e,t,r)}function Qt(e,t){return(i=()=>{},{scope:s={},params:r=[]}={})=>{let n=t.apply(G([s,...e]),r);l0(i,n)}}var b0={};function zt(e,t){if(b0[e])return b0[e];let i=Object.getPrototypeOf(async function(){}).constructor,s=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e,n=(()=>{try{return new i(["__self","scope"],`with (scope) { __self.result = ${s} }; __self.finished = true; return __self.result;`)}catch(o){return Q(o,t,e),Promise.resolve()}})();return b0[e]=n,n}function Wt(e,t,i){let s=zt(t,i);return(r=()=>{},{scope:n={},params:o=[]}={})=>{s.result=void 0,s.finished=!1;let l=G([n,...e]);if(typeof s=="function"){let h=s(s,l).catch(a=>Q(a,i,t));s.finished?(l0(r,s.result,l,o,i),s.result=void 0):h.then(a=>{l0(r,a,l,o,i)}).catch(a=>Q(a,i,t)).finally(()=>s.result=void 0)}}}function l0(e,t,i,s,r){if(r0&&typeof t=="function"){let n=t.apply(i,s);n instanceof Promise?n.then(o=>l0(e,o,i,s)).catch(o=>Q(o,r,t)):e(n)}else e(t)}var J0="x-";function K(e=""){return J0+e}function Gt(e){J0=e}var Le={};function w(e,t){Le[e]=t}function Q0(e,t,i){if(t=Array.from(t),e._x_virtualDirectives){let n=Object.entries(e._x_virtualDirectives).map(([l,h])=>({name:l,value:h})),o=Te(n);n=n.map(l=>o.find(h=>h.name===l.name)?{name:`x-bind:${l.name}`,value:`"${l.value}"`}:l),t=t.concat(n)}let s={};return t.map(Ee((n,o)=>s[n]=o)).filter(Oe).map(t1(s,i)).sort(i1).map(n=>e1(e,n))}function Te(e){return Array.from(e).map(Ee()).filter(t=>!Oe(t))}var D0=!1,Z=new Map,Re=Symbol();function Xt(e){D0=!0;let t=Symbol();Re=t,Z.set(t,[]);let i=()=>{for(;Z.get(t).length;)Z.get(t).shift()();Z.delete(t)},s=()=>{D0=!1,i()};e(i),s()}function Be(e){let t=[],i=l=>t.push(l),[s,r]=Ot(e);return t.push(r),[{Alpine:X,effect:s,cleanup:i,evaluateLater:v.bind(v,e),evaluate:F.bind(F,e)},()=>t.forEach(l=>l())]}function e1(e,t){let i=()=>{},s=Le[t.type]||i,[r,n]=Be(e);$t(e,t.original,n);let o=()=>{e._x_ignore||e._x_ignoreSelf||(s.inline&&s.inline(e,t,r),s=s.bind(s,e,t,r),D0?Z.get(Re).push(s):s())};return o.runCleanups=n,o}var De=(e,t)=>({name:i,value:s})=>(i.startsWith(e)&&(i=i.replace(e,t)),{name:i,value:s}),He=e=>e;function Ee(e=()=>{}){return({name:t,value:i})=>{let{name:s,value:r}=Ve.reduce((n,o)=>o(n),{name:t,value:i});return s!==t&&e(s,t),{name:s,value:r}}}var Ve=[];function z0(e){Ve.push(e)}function Oe({name:e}){return Fe().test(e)}var Fe=()=>new RegExp(`^${J0}([^:^.]+)\\b`);function t1(e,t){return({name:i,value:s})=>{let r=i.match(Fe()),n=i.match(/:([a-zA-Z0-9\-:]+)/),o=i.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],l=t||e[i]||i;return{type:r?r[1]:null,value:n?n[1]:null,modifiers:o.map(h=>h.replace(".","")),expression:s,original:l}}}var H0="DEFAULT",i0=["ignore","ref","data","id","bind","init","for","mask","model","modelable","transition","show","if",H0,"teleport"];function i1(e,t){let i=i0.indexOf(e.type)===-1?H0:e.type,s=i0.indexOf(t.type)===-1?H0:t.type;return i0.indexOf(i)-i0.indexOf(s)}function N(e,t,i={}){e.dispatchEvent(new CustomEvent(t,{detail:i,bubbles:!0,composed:!0,cancelable:!0}))}var E0=[],W0=!1;function Ye(e=()=>{}){return queueMicrotask(()=>{W0||setTimeout(()=>{V0()})}),new Promise(t=>{E0.push(()=>{e(),t()})})}function V0(){for(W0=!1;E0.length;)E0.shift()()}function s1(){W0=!0}function H(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(r=>H(r,t));return}let i=!1;if(t(e,()=>i=!0),i)return;let s=e.firstElementChild;for(;s;)H(s,t),s=s.nextElementSibling}function $(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function r1(){document.body||$("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 +Tripeaks Solver 73k

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.json b/package.json index cde2504..87b303c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tripeaks-solitaire-solver-js-73k", - "version": "1.0.3", + "version": "1.0.4", "description": "A brute force solver for tri peaks solitaire written in javascript. Forked from work by Courtney Pitcher at https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver", "keywords": [ "tripeaks", diff --git a/src/js/cardSvgs.js b/src/js/cardSvgs.js index 43cfaec..f535370 100644 --- a/src/js/cardSvgs.js +++ b/src/js/cardSvgs.js @@ -1,3 +1,5 @@ +import { suits, ranks, deck } from "./deck"; + // backs & joker import card1B from "../cards/1B.svg?raw"; import card2B from "../cards/2B.svg?raw"; @@ -125,4 +127,31 @@ let cardSvgs = { KS: cardKS, }; +// 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 + ); +}); + export default cardSvgs; diff --git a/src/js/cardsInputForm.js b/src/js/cardsInputForm.js new file mode 100644 index 0000000..a2cd070 --- /dev/null +++ b/src/js/cardsInputForm.js @@ -0,0 +1,111 @@ +import { deck } from "./deck"; + +export default { + // "constants" for validation etc + deck, + 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.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.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.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.deck.length - this.validCards.length + ) + .fill(0) + .concat(this.validCards) + .map((c) => (c === "0" ? 0 : c)); + }, +}; diff --git a/src/js/deck.js b/src/js/deck.js new file mode 100644 index 0000000..f604f5b --- /dev/null +++ b/src/js/deck.js @@ -0,0 +1,28 @@ +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; + }); +}); + +export { suits, ranks, deck }; diff --git a/src/js/encouragements.js b/src/js/encouragements.js new file mode 100644 index 0000000..6fe33bd --- /dev/null +++ b/src/js/encouragements.js @@ -0,0 +1,42 @@ +// long-running solve messages +export default [ + "Hang in there!", + "Let go like a bird flies, not fighting the wind but gliding on it", + "Stay patient and trust the journey.", + "Everything is coming together…", + "Solitaire is a journey, not a destination.", + "For things to reveal themselves to us, we need to be ready to abandon our views about them.", + "Patience is bitter, but its fruit is sweet.", + "Strive for progress, not perfection.", + "I wish only to be alive and to experience this living to the fullest.", + "This too shall pass.", + "Life is available only in the present moment.", + "Patience is the companion of wisdom.", + "The mountains are calling and I must go.", + "Give time time.", + "If you find a path with no obstacles, it probably doesn't lead anywhere", + "A smooth sea never made a good sailor.", + "To be upset over what you don’t have is to waste what you do have.", + "The ultimate freedom lies in being able to wait patiently for a good thing.", + "If it isn't good, let it die. If it doesn't die, make it good.", + "A watched pot never boils without applying heat.", + "Stick with the winners.", + "I immerse myself in the experience of living without having to evaluate or understand it.", + "Why fit in when you were born to stand out?", + "Don't let yesterday take up too much of today.", + "The least I owe the mountains is a body.", + "Getting so close!", + "Many people think excitement is happiness. But when you are excited you are not peaceful.", + "Misery is optional.", + "Mistakes are proof that you're trying.", + "Life would be so much easier if we only had the source code.", + "A computer once beat me at chess, but it was no match for me at kickboxing.", + "Patience is not simply the ability to wait, it's how we behave while we're waiting.", + "We must let go of the life we have planned so as to accept the one that is waiting for us.", + "Somewhere, something incredible is waiting to be known.", + "If you spend your whole life waiting for the storm, you'll never enjoy the sunshine.", + "My actions are my only true belongings.", + "The way to have enough time is to never be in a hurry.", + "The more you know, the less you think you know.", + "Patience is also a form of action.", +]; diff --git a/src/js/gameSolving.js b/src/js/gameSolving.js new file mode 100644 index 0000000..6b01644 --- /dev/null +++ b/src/js/gameSolving.js @@ -0,0 +1,77 @@ +import encouragements from "./encouragements"; +import SolverWorker from "./solverWorker?worker"; + +export default { + encouragements, + encourageIndex: null, + solverWorker: null, + headerText: "Solution", + moveCount: 23, + statusMessages: [], + solutionMoves: [], + nodesTried: 0, + nodesTriedFloor: 0, + reset() { + this.$store.global.solvingInProgress = false; + this.moveCount = 0; + this.statusMessages = []; + this.nodesTried = 0; + this.nodesTriedFloor = 0; + this.encourageIndex = null; + }, + onInit() { + this.solverWorker = new SolverWorker(); + this.solverWorker.addEventListener("message", async (e) => { + if (e.data.msg === "solve-progress") { + this.nodesTried++; + this.moveCount = e.data.moveCount; + this.statusMessages[0] = `${this.moveCount} card-clearing moves found so far.`; + + let newFloor = Math.floor(this.nodesTried / 10000) * 10000; + if (newFloor > this.nodesTriedFloor) { + this.nodesTriedFloor = newFloor; + if (this.nodesTriedFloor > 50000) { + this.statusMessages[1] = `Over ${this.nodesTriedFloor.toLocaleString( + "en" + )} possibilities tried. Still working…`; + } + if (this.nodesTriedFloor % 250000 === 0) { + if (this.encourageIndex === null) { + this.encourageIndex = Math.floor( + Math.random() * this.encouragements.length + ); + } + this.statusMessages.splice( + 2, + 0, + this.encouragements[this.encourageIndex] + ); + let newEncourageIndex = this.encourageIndex + 1; + this.encourageIndex = + newEncourageIndex === this.encouragements.length + ? 0 + : newEncourageIndex; + } + } + } else if (e.data.msg === "solve-result") { + if (e.data.result[0]) { + this.headerText = "Solution found:"; + this.solutionMoves = e.data.result[1]; + this.reset(); + } else { + this.headerText = "Could not solve. Best moves found:"; + this.solutionMoves = e.data.result[2]; + this.reset(); + } + } + }); + }, + async startSolver() { + this.headerText = "Solving…"; + this.solutionMoves = []; + this.$store.global.solvingInProgress = true; + await this.$nextTick(); + let game = JSON.parse(JSON.stringify(this.$store.global.cardsToSolve)); + this.solverWorker.postMessage({ msg: "try-to-solve", game: game }); + }, +}; diff --git a/src/js/main.js b/src/js/main.js index ff23005..79dbf4a 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,313 +1,25 @@ import "../scss/style.scss"; import Alpine from "alpinejs"; -import cardSvgs from "./cardSvgs"; -import SolverWorker from "./solverWorker?worker"; +import globalStore from "./store"; import navbar from "./navbar"; - -// 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 - ); -}); +import cardsInputForm from "./cardsInputForm"; +import playingCardsPreview from "./playingCardsPreview"; +import gameSolving from "./gameSolving"; // Keep some constants in global store for components -Alpine.store("global", { - deck, - cardsToSolve: Array(deck.length).fill(0), - solvingInProgress: false, -}); +Alpine.store("global", globalStore); // navbar logic Alpine.data("navbar", () => navbar); -// card preview component logic -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 logic -Alpine.data("cardsInputForm", () => ({ - // "constants" for validation etc - nonAlphaNumRegEx: /[\W_]+/g, +Alpine.data("cardsInputForm", () => cardsInputForm); - // 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)); - }, -})); - -// long-running solve messages -const encouragements = [ - "Hang in there!", - "Let go like a bird flies, not fighting the wind but gliding on it", - "Stay patient and trust the journey.", - "Everything is coming together…", - "Solitaire is a journey, not a destination.", - "For things to reveal themselves to us, we need to be ready to abandon our views about them.", - "Patience is bitter, but its fruit is sweet.", - "Strive for progress, not perfection.", - "I wish only to be alive and to experience this living to the fullest.", - "This too shall pass.", - "Life is available only in the present moment.", - "Patience is the companion of wisdom.", - "The mountains are calling and I must go.", - "Give time time.", - "If you find a path with no obstacles, it probably doesn't lead anywhere", - "A smooth sea never made a good sailor.", - "To be upset over what you don’t have is to waste what you do have.", - "The ultimate freedom lies in being able to wait patiently for a good thing.", - "If it isn't good, let it die. If it doesn't die, make it good.", - "A watched pot never boils without applying heat.", - "Stick with the winners.", - "I immerse myself in the experience of living without having to evaluate or understand it.", - "Why fit in when you were born to stand out?", - "Don't let yesterday take up too much of today.", - "The least I owe the mountains is a body.", - "Getting so close!", - "Many people think excitement is happiness. But when you are excited you are not peaceful.", - "Misery is optional.", - "Mistakes are proof that you're trying.", - "Life would be so much easier if we only had the source code.", - "A computer once beat me at chess, but it was no match for me at kickboxing.", - "Patience is not simply the ability to wait, it's how we behave while we're waiting.", - "We must let go of the life we have planned so as to accept the one that is waiting for us.", - "Somewhere, something incredible is waiting to be known.", - "If you spend your whole life waiting for the storm, you'll never enjoy the sunshine.", - "My actions are my only true belongings.", - "The way to have enough time is to never be in a hurry.", - "The more you know, the less you think you know.", - "Patience is also a form of action.", -]; +// card preview component logic +Alpine.data("playingCardsPreview", () => playingCardsPreview); // game solving component logic -Alpine.data("gameSolving", () => ({ - encouragements, - encourageIndex: null, - solverWorker: null, - headerText: "Solution", - moveCount: 23, - statusMessages: [], - solutionMoves: [], - nodesTried: 0, - nodesTriedFloor: 0, - reset() { - this.$store.global.solvingInProgress = false; - this.moveCount = 0; - this.statusMessages = []; - this.nodesTried = 0; - this.nodesTriedFloor = 0; - this.encourageIndex = null; - }, - onInit() { - this.solverWorker = new SolverWorker(); - this.solverWorker.addEventListener("message", async (e) => { - if (e.data.msg === "solve-progress") { - this.nodesTried++; - this.moveCount = e.data.moveCount; - this.statusMessages[0] = `${this.moveCount} card-clearing moves found so far.`; - - let newFloor = Math.floor(this.nodesTried / 10000) * 10000; - if (newFloor > this.nodesTriedFloor) { - this.nodesTriedFloor = newFloor; - if (this.nodesTriedFloor > 50000) { - this.statusMessages[1] = `Over ${this.nodesTriedFloor.toLocaleString( - "en" - )} possibilities tried. Still working…`; - } - if (this.nodesTriedFloor % 250000 === 0) { - if (this.encourageIndex === null) { - this.encourageIndex = Math.floor( - Math.random() * this.encouragements.length - ); - } - this.statusMessages.splice( - 2, - 0, - this.encouragements[this.encourageIndex] - ); - let newEncourageIndex = this.encourageIndex + 1; - this.encourageIndex = - newEncourageIndex === this.encouragements.length - ? 0 - : newEncourageIndex; - } - } - } else if (e.data.msg === "solve-result") { - if (e.data.result[0]) { - this.headerText = "Solution found:"; - this.solutionMoves = e.data.result[1]; - this.reset(); - } else { - this.headerText = "Could not solve. Best moves found:"; - this.solutionMoves = e.data.result[2]; - this.reset(); - } - } - }); - }, - async startSolver() { - this.headerText = "Solving…"; - this.solutionMoves = []; - this.$store.global.solvingInProgress = true; - await this.$nextTick(); - let game = JSON.parse(JSON.stringify(this.$store.global.cardsToSolve)); - this.solverWorker.postMessage({ msg: "try-to-solve", game: game }); - }, -})); +Alpine.data("gameSolving", () => gameSolving); window.Alpine = Alpine; diff --git a/src/js/playingCardsPreview.js b/src/js/playingCardsPreview.js new file mode 100644 index 0000000..ed1df94 --- /dev/null +++ b/src/js/playingCardsPreview.js @@ -0,0 +1,11 @@ +import cardSvgs from "./cardSvgs"; + +export default { + cardSvgs, + cardsBySlice(start, length) { + return this.$store.global.cardsToSolve.slice(start, length); + }, + cardSvg(card) { + return card === 0 ? this.cardSvgs["2B"] : this.cardSvgs[card]; + }, +}; diff --git a/src/js/store.js b/src/js/store.js new file mode 100644 index 0000000..9cb0305 --- /dev/null +++ b/src/js/store.js @@ -0,0 +1,7 @@ +import { deck } from "./deck"; + +export default { + deck, + cardsToSolve: Array(deck.length).fill(0), + solvingInProgress: false, +};