├── assets ├── chips.png ├── Jokers.png ├── 8BitDeck.png ├── Editions.png ├── Enhancers.png ├── m6x11plus.ttf ├── ui_assets.png ├── 8BitDeck_opt2.png ├── Balatro logo-new.png └── Discord-Logo-Color.svg ├── README.md ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── hoverCard.js ├── LICENSE ├── worker.js ├── style.css ├── hand-url.js ├── manageWorkers.js ├── index.html ├── structured-data.jsonld ├── main.js └── balatro-sim.js /assets/chips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/chips.png -------------------------------------------------------------------------------- /assets/Jokers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/Jokers.png -------------------------------------------------------------------------------- /assets/8BitDeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/8BitDeck.png -------------------------------------------------------------------------------- /assets/Editions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/Editions.png -------------------------------------------------------------------------------- /assets/Enhancers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/Enhancers.png -------------------------------------------------------------------------------- /assets/m6x11plus.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/m6x11plus.ttf -------------------------------------------------------------------------------- /assets/ui_assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/ui_assets.png -------------------------------------------------------------------------------- /assets/8BitDeck_opt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/8BitDeck_opt2.png -------------------------------------------------------------------------------- /assets/Balatro logo-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFHIII/balatro-calculator/HEAD/assets/Balatro logo-new.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Balatro Calculator 2 | 3 | [Go to site](https://efhiii.github.io/balatro-calculator/) 4 | 5 | This tool lets you input a [Balatro](https://www.playbalatro.com/) hand with a specific scenario of jokers, hand upgrades, and drawn cards, and calculates what the highest scoring play is while showing you both what the play is and what it scores. 6 | 7 | You can also use this tool to try and conceive of your "perfect hand" to see what it would score. 8 | 9 | Currently, a couple of the jokers may not work correctly, but most of them do. 10 | 11 | font used: https://managore.itch.io/m6x11 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report any hands that are miscalculated or other bugs 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description of the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps To Reproduce** 14 | If possible, just provide a link or two to hands that demonstrate the bug. 15 | Otherwise, give detailed steps to reproduce. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature or improvement 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | If applicable, give URLs to hands that would demonstrate the feature, what the look like right now, and what you'd want them to look like. 16 | 17 | **Describe alternatives you've considered** 18 | A clear and concise description of any alternative solutions or features you've considered. 19 | 20 | **Additional context** 21 | Add any other context or screenshots about the feature request here. 22 | -------------------------------------------------------------------------------- /hoverCard.js: -------------------------------------------------------------------------------- 1 | let constrain = 5; 2 | let mouseOverContainer = document.getElementById("ex1-layer"); 3 | 4 | function transforms(x, y, el) { 5 | el.style.transform = ''; 6 | let box = el.getBoundingClientRect(); 7 | 8 | let calcX = -(y - box.y - (box.height / 2)) / (el.dataset.scale == 2 ? constrain * 2 : constrain); 9 | let calcY = (x - box.x - ((box.width ? box.width : 71) / 2)) / (el.dataset.scale == 2 ? constrain * 2 : constrain); 10 | 11 | if(box.width === 0) { 12 | return `translate(35.5px) perspective(${94}px) ` + 13 | `rotateX(${calcX}deg) ` + 14 | `rotateY(${calcY}deg) translate(-35.5px)`; 15 | } 16 | 17 | return `perspective(${94}px) ` + 18 | `rotateX(${calcX}deg) ` + 19 | `rotateY(${calcY}deg)`; 20 | }; 21 | 22 | //mousemove 23 | function hoverCard(e) { 24 | let target = e.target; 25 | let position = [e.clientX, e.clientY, e.target]; 26 | 27 | target.style.transform = transforms.apply(null, position); 28 | } 29 | 30 | // mouseout 31 | function noHoverCard(e) { 32 | e.target.style.transform = ''; 33 | } 34 | 35 | // onmousemove = 'hoverCard(event)' onmouseout = 'noHoverCard(event)' 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Saffron Haas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/Discord-Logo-Color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | importScripts('balatro-sim.js'); 3 | 4 | function sleep(ms) { 5 | return new Promise(resolve => setTimeout(resolve, ms)); 6 | } 7 | 8 | function choose(array, k) { 9 | if (k === 0) { 10 | return [[]]; 11 | } 12 | 13 | const results = []; 14 | 15 | for (let i = 0; i < array.length; i++) { 16 | const remaining = array.slice(i + 1); 17 | const combinations = choose(remaining, k - 1); 18 | 19 | for (const combination of combinations) { 20 | results.push([array[i], ...combination]); 21 | } 22 | } 23 | 24 | return results; 25 | } 26 | 27 | function permutations(inputArr) { 28 | var results = []; 29 | 30 | function permute(arr, memo) { 31 | var cur, memo = memo || []; 32 | 33 | for (var i = 0; i < arr.length; i++) { 34 | cur = arr.splice(i, 1); 35 | if (arr.length === 0) { 36 | results.push(memo.concat(cur)); 37 | } 38 | permute(arr.slice(), memo.concat(cur)); 39 | arr.splice(i, 0, cur[0]); 40 | } 41 | 42 | return results; 43 | } 44 | 45 | return permute(inputArr); 46 | } 47 | 48 | let taskID; 49 | let workerID; 50 | let thisHand; 51 | 52 | let cards; 53 | let jokers; 54 | 55 | let optimizeCards; 56 | let minimize; 57 | let optimizeMode; 58 | 59 | let bestHand; 60 | 61 | // postMessage(); 62 | // sleep(ms); 63 | 64 | function initialize(state) { 65 | thisHand = new Hand({ 66 | hands: state.hands, 67 | TheFlint: state.TheFlint, 68 | TheEye: state.TheEye, 69 | PlasmaDeck: state.PlasmaDeck, 70 | Observatory: state.Observatory 71 | }); 72 | 73 | taskID = state.taskID; 74 | cards = state.cards; 75 | jokers = state.jokers; 76 | optimizeCards = state.optimizeCards; 77 | minimize = state.minimize; 78 | optimizeMode = state.optimizeMode; 79 | bestHand = state.bestHand; 80 | workerID = state.workerID; 81 | } 82 | 83 | function run(jokers = [[]]) { 84 | thisHand.jokers = jokers[0].map(a => a.slice()); 85 | 86 | thisHand.compileJokers(); 87 | 88 | let bestJokers = []; 89 | let bestCards = []; 90 | let bestCardsInHand = []; 91 | let bestScore = false; 92 | let bestSameScore = false; 93 | let bestHighScore = false; 94 | 95 | let possibleHands = [[]]; 96 | 97 | let thisPermutations; 98 | 99 | if(optimizeCards) { 100 | possibleHands = [[]]; 101 | let arr = []; 102 | for(let l = 0; l < cards.length; l++) { 103 | arr.push(l); 104 | } 105 | for(let i = 1; i <= Math.min(5, arr.length); i++) { 106 | possibleHands.push(...choose(arr, i)); 107 | } 108 | thisPermutations = [ 109 | [[]], 110 | [[0]], 111 | [[0,1],[1,0]], 112 | permutations([0,1,2]), 113 | permutations([0,1,2,3]), 114 | permutations([0,1,2,3,4]) 115 | ]; 116 | } 117 | else { 118 | for(let i = 0; i < bestHand.length; i++) { 119 | bestCards.push(cards[bestHand[i]]); 120 | thisHand.cards.push(cards[bestHand[i]]); 121 | } 122 | thisHand.cardsInHand = cards.slice(); 123 | for(let l = 0; l < thisHand.cards.length; l++) { 124 | thisHand.cardsInHand.splice(thisHand.cardsInHand.indexOf(thisHand.cards[l]), 1); 125 | } 126 | bestCardsInHand = thisHand.cardsInHand; 127 | } 128 | 129 | let originalHand = thisHand.cardsInHand.slice(); 130 | 131 | for(let j = 0; j < jokers.length; j++) { 132 | thisHand.jokers = jokers[j].map(a => a.slice()); 133 | thisHand.compileJokerOrder(); 134 | 135 | if(optimizeCards) { 136 | for(let i = 0; i < possibleHands.length; i++) { 137 | const tmpCards = cards.map(a => a.slice()); 138 | thisHand.cards = []; 139 | thisHand.cardsInHand = tmpCards.slice(); 140 | for(let j = 0; j < possibleHands[i].length; j++) { 141 | thisHand.cards.push(cards[possibleHands[i][j]]); 142 | } 143 | for(let j = possibleHands[i].length - 1; j >= 0; j--) { 144 | thisHand.cardsInHand.splice(possibleHands[i][j], 1); 145 | } 146 | 147 | const thisCardsInHand = thisHand.cardsInHand.slice(); 148 | const thisCards = thisHand.cards.slice(); 149 | const thisPerms = thisPermutations[thisHand.cards.length]; 150 | 151 | thisHand.actualCardsInHand = thisCardsInHand.slice(); 152 | thisHand.compileCards(); 153 | 154 | for(let l = 0; l < thisPerms.length; l++) { 155 | thisHand.cards = []; 156 | for(let m = 0; m < thisPerms[l].length; m++) { 157 | thisHand.cards.push(thisCards[thisPerms[l][m]]); 158 | } 159 | thisHand.cardsInHand = thisCardsInHand.slice(); 160 | const thisOriginalHand = thisCardsInHand.slice(); 161 | 162 | let thisScore; 163 | 164 | let sameScore = 0; 165 | for(let i = 0; i < thisHand.cardsInHand.length; i++) { 166 | sameScore += (thisHand.cardsInHand[i][EDITION] * 200 + thisHand.cardsInHand[i][ENHANCEMENT] * 20 + thisHand.cardsInHand[i][RANK]) * (thisHand.cardsInHand[i][CARD_DISABLED] ? 0 : 1); 167 | } 168 | 169 | switch (optimizeMode) { 170 | default: 171 | if(minimize) { 172 | thisScore = thisHand.simulateBestHand(); 173 | if(!bestScore) { 174 | bestScore = thisScore; 175 | bestHighScore = thisHand.simulateBestHand(); 176 | bestSameScore = sameScore; 177 | bestCards = thisHand.cards; 178 | bestJokers = jokers[j]; 179 | bestCardsInHand = thisHand.cardsInHand; 180 | originalHand = thisOriginalHand; 181 | } 182 | if(thisScore[1] < bestScore[1] || (thisScore[1] === bestScore[1] && thisScore[0] < bestScore[0]) || (bestCards.length === 0 && thisHand.cards.length > 0) || (thisScore[1] === bestScore[1] && thisScore[0] === bestScore[0] && sameScore > bestSameScore)) { 183 | bestScore = thisScore; 184 | bestHighScore = thisHand.simulateBestHand(); 185 | bestSameScore = sameScore; 186 | bestCards = thisHand.cards; 187 | bestJokers = jokers[j]; 188 | bestCardsInHand = thisHand.cardsInHand; 189 | originalHand = thisOriginalHand; 190 | } 191 | else if(thisScore[1] === bestScore[1] && thisScore[0] === bestScore[0] && sameScore === bestSameScore) { 192 | const bhs = thisHand.simulateBestHand(); 193 | 194 | if(bhs < bestHighScore) { 195 | bestScore = thisScore; 196 | bestHighScore = bhs; 197 | bestSameScore = sameScore; 198 | bestCards = thisHand.cards; 199 | bestJokers = jokers[j]; 200 | bestCardsInHand = thisHand.cardsInHand; 201 | originalHand = thisOriginalHand; 202 | } 203 | } 204 | 205 | } 206 | else { 207 | thisScore = thisHand.simulateWorstHand(); 208 | 209 | if(!bestScore) { 210 | bestScore = thisScore; 211 | bestHighScore = thisHand.simulateBestHand(); 212 | bestSameScore = sameScore; 213 | bestCards = thisHand.cards; 214 | bestJokers = jokers[j]; 215 | bestCardsInHand = thisHand.cardsInHand; 216 | originalHand = thisOriginalHand; 217 | } 218 | if(thisScore[1] > bestScore[1] || (thisScore[1] === bestScore[1] && thisScore[0] > bestScore[0]) || (bestCards.length === 0 && thisHand.cards.length > 0) || (thisScore[1] === bestScore[1] && thisScore[0] === bestScore[0] && sameScore > bestSameScore)) { 219 | bestScore = thisScore; 220 | bestHighScore = thisHand.simulateBestHand(); 221 | bestSameScore = sameScore; 222 | bestCards = thisHand.cards; 223 | bestJokers = jokers[j]; 224 | bestCardsInHand = thisHand.cardsInHand; 225 | originalHand = thisOriginalHand; 226 | } 227 | else if(thisScore[1] === bestScore[1] && thisScore[0] === bestScore[0] && sameScore === bestSameScore) { 228 | const bhs = thisHand.simulateBestHand(); 229 | 230 | if(bhs > bestHighScore) { 231 | bestScore = thisScore; 232 | bestHighScore = bhs; 233 | bestSameScore = sameScore; 234 | bestCards = thisHand.cards; 235 | bestJokers = jokers[j]; 236 | bestCardsInHand = thisHand.cardsInHand; 237 | originalHand = thisOriginalHand; 238 | } 239 | } 240 | } 241 | } 242 | } 243 | } 244 | //console.log(bestScore); 245 | } 246 | else { 247 | thisHand.compileCards(); 248 | 249 | let thisScore; 250 | 251 | switch (optimizeMode) { 252 | default: 253 | if(minimize) { 254 | thisScore = thisHand.simulateBestHand(); 255 | if(!bestScore) { 256 | bestScore = thisScore; 257 | bestHighScore = thisHand.simulateBestHand(); 258 | bestJokers = jokers[j]; 259 | } 260 | if(thisScore[1] < bestScore[1] || (thisScore[1] === bestScore[1] && thisScore[0] < bestScore[0])) { 261 | bestScore = thisScore; 262 | bestHighScore = thisHand.simulateBestHand(); 263 | bestJokers = jokers[j]; 264 | } 265 | else if(thisScore[1] === bestScore[1] && thisScore[0] === bestScore[0]) { 266 | const bhs = thisHand.simulateBestHand(); 267 | 268 | if(bhs < bestHighScore) { 269 | bestScore = thisScore; 270 | bestHighScore = bhs; 271 | bestJokers = jokers[j]; 272 | } 273 | } 274 | } 275 | else { 276 | thisScore = thisHand.simulateWorstHand(); 277 | 278 | if(!bestScore) { 279 | bestScore = thisScore; 280 | bestHighScore = thisHand.simulateBestHand(); 281 | bestJokers = jokers[j]; 282 | } 283 | if(thisScore[1] > bestScore[1] || (thisScore[1] === bestScore[1] && thisScore[0] > bestScore[0])) { 284 | bestScore = thisScore; 285 | bestHighScore = thisHand.simulateBestHand(); 286 | bestJokers = jokers[j]; 287 | } 288 | else if(thisScore[1] === bestScore[1] && thisScore[0] === bestScore[0]) { 289 | const bhs = thisHand.simulateBestHand(); 290 | 291 | if(bhs > bestHighScore) { 292 | bestScore = thisScore; 293 | bestHighScore = bhs; 294 | bestJokers = jokers[j]; 295 | } 296 | } 297 | } 298 | } 299 | } 300 | } 301 | 302 | thisHand.jokers = bestJokers.map(a => a.slice()); 303 | thisHand.cards = bestCards.slice(); 304 | console.log(originalHand); 305 | thisHand.cardsInHand = originalHand.slice(); 306 | 307 | 308 | thisHand.compileAll(); 309 | 310 | const highestScore = thisHand.simulateBestHand(); 311 | const lowestScore = thisHand.simulateWorstHand(); 312 | const highestScoreScore = normalizeBig(highestScore); 313 | highestScore[0] = highestScoreScore[0]; 314 | highestScore[1] = highestScoreScore[1]; 315 | const lowestScoreScore = normalizeBig(lowestScore); 316 | lowestScore[0] = lowestScoreScore[0]; 317 | lowestScore[1] = lowestScoreScore[1]; 318 | 319 | let medianScore = highestScore; 320 | let meanScore = highestScore; 321 | 322 | if(highestScore[0] !== lowestScore[0] || highestScore[1] !== lowestScore[1]) { 323 | let runScores = []; 324 | meanScore = [1, 0]; 325 | for(let i = 0; i < 10001; i++) { 326 | const thisScore = thisHand.simulateRandomHand(); 327 | runScores.push(thisScore); 328 | meanScore = bigBigAdd(thisScore, meanScore); 329 | } 330 | meanScore = bigTimes(1 / 10001, meanScore); 331 | runScores = runScores.sort((a, b) => { 332 | if(a[1] > b[1] || (a[1] === b[1] && a[0] > b[0])) { 333 | return 1; 334 | } 335 | return -1; 336 | }); 337 | 338 | medianScore = runScores[5000]; 339 | } 340 | 341 | postMessage([taskID, bestScore, bestJokers, bestCards, originalHand, highestScore, lowestScore, thisHand.typeOfHand, normalizeBig(meanScore), normalizeBig(medianScore), workerID, thisHand.compiledValues]); 342 | } 343 | 344 | self.onmessage = async function(msg) { 345 | switch (msg.data[0]) { 346 | case "start": 347 | initialize(msg.data[1]); 348 | break; 349 | case "once": 350 | run([[]]); 351 | break; 352 | case "dontOptimizeJokers": 353 | run([jokers]); 354 | break; 355 | case "optimizeJokers": 356 | let permuted = permutations(jokers); 357 | run(permuted.slice(msg.data[1], msg.data[2])); 358 | break; 359 | default: 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* 2 | MULT = HEX('FE5F55'), 3 | CHIPS = HEX("009dff"), 4 | MONEY = HEX('f3b958'), 5 | XMULT = HEX('FE5F55'), 6 | FILTER = HEX('ff9a00'), 7 | BLUE = HEX("009dff"), 8 | RED = HEX('FE5F55'), 9 | GREEN = HEX("4BC292"), 10 | PALE_GREEN = HEX("56a887"), 11 | ORANGE = HEX("fda200"), 12 | IMPORTANT = HEX("ff9a00"), 13 | GOLD = HEX('eac058'), 14 | YELLOW = {1,1,0,1}, 15 | CLEAR = {0, 0, 0, 0}, 16 | WHITE = {1,1,1,1}, 17 | PURPLE = HEX('8867a5'), 18 | BLACK = HEX("374244"),--4f6367"), 19 | L_BLACK = HEX("4f6367"), 20 | GREY = HEX("5f7377"), 21 | CHANCE = HEX("4BC292"), 22 | JOKER_GREY = HEX('bfc7d5'), 23 | VOUCHER = HEX("cb724c"), 24 | BOOSTER = HEX("646eb7"), 25 | EDITION = {1,1,1,1}, 26 | DARK_EDITION = {0,0,0,1}, 27 | ETERNAL = HEX('c75985'), 28 | DYN_UI = { 29 | MAIN = HEX('374244'), 30 | DARK = HEX('374244'), 31 | BOSS_MAIN = HEX('374244'), 32 | BOSS_DARK = HEX('374244'), 33 | BOSS_PALE = HEX('374244') 34 | }, 35 | */ 36 | 37 | @font-face { 38 | font-family: 'm6x11Plus'; 39 | src: url('assets/m6x11plus.ttf') format('truetype'); 40 | } 41 | 42 | body { 43 | background-color: #3a5055; 44 | color: #fff; 45 | font-family: 'm6x11Plus'; 46 | margin: 0; 47 | overflow-x: hidden; 48 | } 49 | 50 | div { 51 | -webkit-tap-highlight-color: transparent !important; 52 | } 53 | 54 | a { 55 | color: white; 56 | text-decoration: none; 57 | transition-duration: 0.3s; 58 | } 59 | 60 | a:hover { 61 | color: #dfe; 62 | } 63 | 64 | #containerLeft { 65 | position: static; 66 | width: 100%; 67 | text-align: center; 68 | } 69 | 70 | #containerRight { 71 | position: static; 72 | width: 100%; 73 | } 74 | 75 | #footer { 76 | width: 100%; 77 | text-align: center; 78 | margin: 0.5em; 79 | } 80 | 81 | #jokers, #cards, #modifyJoker { 82 | display: flex; 83 | flex-direction: column; 84 | } 85 | 86 | #jokers > div, #cards > div, #modifyJoker > div, #handModifiers { 87 | display: flex; 88 | justify-content: center; 89 | } 90 | 91 | #jokers > div > .tooltip:last-child, 92 | #cards > div > .tooltip:last-child, 93 | #modifyJoker > div > .tooltip:last-child { 94 | width: 100%; 95 | } 96 | 97 | html { 98 | overflow-x: hidden; 99 | } 100 | 101 | .handLevel { 102 | margin: auto; 103 | font-size: 22px; 104 | background-color: #a3acb9; 105 | padding: 0.2em; 106 | margin-top: 0.5em; 107 | box-shadow: 0em 0.2em 0em #686e78; 108 | border-radius: 0.6em; 109 | text-align: center; 110 | height: 1.4em; 111 | min-width: 22em; 112 | max-width: calc(100% - 3em); 113 | white-space: nowrap; 114 | } 115 | 116 | @media (min-width: 850px) { 117 | @media (min-height: 350px) { 118 | html { 119 | height: 100%; 120 | width: 100%; 121 | } 122 | 123 | body { 124 | min-height: 100%; 125 | width: 100%; 126 | display: flex; 127 | flex-direction: column; 128 | } 129 | 130 | #containerLeft { 131 | width: 50%; 132 | text-align: center; 133 | } 134 | 135 | #containerRight { 136 | width: 50%; 137 | } 138 | 139 | #mainContainer { 140 | display: flex; 141 | min-height: 100%; 142 | } 143 | 144 | #footer { 145 | margin-top: auto; 146 | } 147 | 148 | #jokerAera { 149 | width: calc(100% - 1.5em - 8px); 150 | } 151 | } 152 | } 153 | 154 | @media (max-width: 600px) { 155 | .handLevel { 156 | font-size: 18px; 157 | } 158 | } 159 | 160 | @media (min-width: 850px) { 161 | @media (max-width: 1100px) { 162 | .handLevel { 163 | font-size: 18px; 164 | } 165 | } 166 | } 167 | 168 | #Hands { 169 | float: left; 170 | text-align: center; 171 | width: 100%; 172 | } 173 | 174 | #jokerArea, #bestPlay, #cardsInHand { 175 | display: flex; 176 | gap: 0; 177 | grid-auto-columns: auto; 178 | justify-content: center; 179 | } 180 | 181 | #jokerArea { 182 | width: calc(100% - 1.5em); 183 | background-color: #0003; 184 | margin: 0.5em; 185 | height: 94px; 186 | padding: 4px; 187 | border-radius: 0.5em; 188 | margin-bottom: 2em; 189 | } 190 | 191 | #bestPlay { 192 | width: calc(100% - 1.5em); 193 | margin: 0.5em; 194 | height: 94px; 195 | padding: 4px; 196 | margin-bottom: 3em; 197 | } 198 | 199 | #cardsInHand { 200 | background-color: #0003; 201 | width: calc(100% - 1.5em); 202 | margin: 0.5em; 203 | height: 94px; 204 | padding: 4px; 205 | border-radius: 0.5em; 206 | margin-bottom: 1em; 207 | } 208 | 209 | .menuBtns { 210 | height: 3.5em; 211 | } 212 | 213 | .menuBtn { 214 | display: block; 215 | float: left; 216 | font-size: 22px; 217 | width: 25%; 218 | background-color: #333; 219 | text-align: center; 220 | padding-top: 0.75em; 221 | padding-bottom: 0.75em; 222 | user-select: none; 223 | } 224 | 225 | .menuBtn:hover { 226 | cursor: pointer; 227 | background-color: #222; 228 | } 229 | 230 | .active { 231 | background-color: #222; 232 | } 233 | 234 | .lvlBtn { 235 | float: left; 236 | user-select: none; 237 | display: inline-block; 238 | text-align: center; 239 | width: 1em; 240 | background-color: #5f7377; 241 | border-radius: 0.25em; 242 | color: #fff; 243 | border-style: solid; 244 | border-color: #374244; 245 | transition-duration: 0.2s; 246 | margin-left: 0.2em; 247 | border-width: 0.125em; 248 | margin: 0.125em; 249 | } 250 | 251 | .lvlBtn:hover { 252 | border-color: #fff; 253 | cursor: pointer; 254 | } 255 | 256 | .handLvl { 257 | float: left; 258 | display: block; 259 | white-space: nowrap; 260 | text-align: center; 261 | min-width: 2.4em; 262 | background-color: #f0f0f0; 263 | border-radius: 1em; 264 | color: #3a5055; 265 | border-style: solid; 266 | border-color: #fff; 267 | margin: 0.125em; 268 | border-width: 0.125em; 269 | padding-left: 0.3em; 270 | padding-right: 0.3em; 271 | } 272 | 273 | .handCount { 274 | display: inline-block; 275 | white-space: nowrap; 276 | text-align: center; 277 | min-width: 1.4em; 278 | background-color: #3a5055; 279 | border-radius: 1em; 280 | color: #ff8f00; 281 | border-style: solid; 282 | border-color: #3a5055; 283 | margin: 0.125em; 284 | border-width: 0.125em; 285 | padding-left: 0.3em; 286 | padding-right: 0.3em; 287 | } 288 | 289 | .levelStat { 290 | position: sticky; 291 | left: 100%; 292 | width: fit-content; 293 | background-color: #000; 294 | padding: 0.2em; 295 | border-radius: 1em; 296 | color: #ff4d40; 297 | margin: 0px; 298 | } 299 | 300 | .levelStatB { 301 | display: inline-block; 302 | white-space: nowrap; 303 | text-align: right; 304 | min-width: 2.5em; 305 | background-color: #009dff; 306 | padding-left: 0.25em; 307 | padding-right: 0.3em; 308 | margin-right: 0.2em; 309 | border-radius: 1em; 310 | color: white; 311 | } 312 | 313 | .levelStatR { 314 | display: inline-block; 315 | white-space: nowrap; 316 | text-align: left; 317 | min-width: 2.5em; 318 | background-color: #ff4d40; 319 | padding-left: 0.3em; 320 | padding-right: 0.25em; 321 | margin-left: 0.1em; 322 | border-radius: 1em; 323 | color: white; 324 | } 325 | 326 | .handName { 327 | display: inline-block; 328 | white-space: nowrap; 329 | margin-top: 0.2em; 330 | margin-right: -0.4em; 331 | } 332 | 333 | .modifierCard, .jmodifierCard { 334 | position: absolute; 335 | left: 0; 336 | display: inline-block; 337 | width: 100%; 338 | max-width: 71px; 339 | height: 94px; 340 | padding: 0; 341 | image-rendering: pixelated; 342 | cursor: pointer; 343 | } 344 | 345 | .jokerCard { 346 | position: absolute; 347 | left: 0; 348 | display: inline-block; 349 | width: 0; 350 | height: 100%; 351 | padding: 0; 352 | image-rendering: pixelated; 353 | cursor: pointer; 354 | z-index: 1; 355 | } 356 | 357 | .modifierCard:hover, .jmodifierCard:hover, .jokerCard:hover, .playingCard:hover, .playfieldCard:hover { 358 | scale: 1.1; 359 | animation: bounce 0.3s ease; 360 | z-index: 2; 361 | } 362 | 363 | .jokerCard:after, .playingCard:after, .jmodifierCard:after, .modifierCard:after, .playfieldCard:after { 364 | content: ''; 365 | width: 71px; 366 | height: 100%; 367 | left: 0; 368 | top: 0; 369 | background: inherit; 370 | position: absolute; 371 | 372 | mask-image: url(assets/Jokers.png); 373 | mask-position: inherit; 374 | mask-mode: alpha; 375 | } 376 | 377 | .jokerCard:hover:after, .playingCard:hover:after, .jmodifierCard:hover:after, .modifierCard:hover:after, .playfieldCard:hover:after { 378 | box-shadow: 0px 10px 1px -3px #0005; 379 | } 380 | 381 | @keyframes bounce { 382 | 1%: { 383 | scale: 1; 384 | } 385 | 25% { 386 | scale: 1.15; 387 | } 388 | 50% { 389 | scale: 1.075; 390 | } 391 | 51% { 392 | scale: 1.075; 393 | } 394 | 100% { 395 | scale: 1.1; 396 | } 397 | } 398 | 399 | .playingCard { 400 | position: absolute; 401 | left: 0; 402 | display: inline-block; 403 | width: 100%; 404 | height: 100%; 405 | padding: 0; 406 | image-rendering: pixelated; 407 | cursor: pointer; 408 | z-index: 1; 409 | } 410 | 411 | .playfieldCard { 412 | position: absolute; 413 | left: 0; 414 | display: inline-block; 415 | width: 100%; 416 | max-width: 71px; 417 | height: 94px; 418 | padding: 0; 419 | image-rendering: pixelated; 420 | cursor: pointer; 421 | } 422 | 423 | .chipIcon { 424 | margin-bottom: -8px; 425 | margin-left: 4px; 426 | margin-right: 4px; 427 | display: inline-block; 428 | width: 30px; 429 | height: 30px; 430 | background: url(assets/chips.png) 0 0; 431 | image-rendering: pixelated; 432 | } 433 | 434 | .limitText { 435 | color: white; 436 | float: left; 437 | margin-left: 1em; 438 | } 439 | 440 | .tooltip { 441 | display: inline-block; 442 | max-width: 71px; 443 | height: 94px; 444 | padding: 0; 445 | position: relative; 446 | } 447 | 448 | .tooltip:has(.playingCard) { 449 | width: 7.6%; 450 | } 451 | 452 | .tooltip:has(.jokerCard) { 453 | width: 10%; 454 | } 455 | 456 | .tooltip:has(.modifierCard) { 457 | width: 100%; 458 | } 459 | 460 | .tooltip:has(.jmodifierCard) { 461 | width: 33.33%; 462 | } 463 | 464 | #jokerArea .tooltip:has(.jokerCard), 465 | .tooltip:has(.playfieldCard) { 466 | width: 100%; 467 | } 468 | 469 | .tooltip .tooltiptext { 470 | visibility: hidden; 471 | background-color: #3f4a4d; 472 | color: white; 473 | text-align: center; 474 | border-radius: 0.75em; 475 | 476 | min-width: 8em; 477 | 478 | position: absolute; 479 | 480 | border: solid; 481 | border-color: #dee2ea; 482 | border-width: 0.15em; 483 | box-shadow: 0em 0.1em 0em #919499; 484 | 485 | height: fit-content; 486 | width: fit-content; 487 | 488 | left: 50%; 489 | transform: translate(-50%, 0); 490 | } 491 | 492 | .jokerCard + .tooltiptext { 493 | bottom: calc(100% + 1em); 494 | } 495 | 496 | #jokerArea > .tooltip > .tooltiptext { 497 | top: calc(100% + 1em); 498 | } 499 | 500 | .jokerCard:hover + .removeJoker + .tooltiptext, 501 | .jokerCard:hover + .tooltiptext, 502 | .modifierCard:hover + .tooltiptext, 503 | .jmodifierCard:hover + .tooltiptext { 504 | visibility: visible; 505 | z-index: 3; 506 | } 507 | 508 | .title { 509 | font-size: 2em; 510 | display: block; 511 | text-align: center; 512 | white-space: nowrap; 513 | padding-left: 0.5em; 514 | padding-right: 0.5em; 515 | } 516 | 517 | .desc { 518 | display: flex; 519 | align-items: center; 520 | padding: 0.25em; 521 | border-radius: 0.5em; 522 | margin: 0.25em; 523 | text-align: center; 524 | box-shadow: 0 0.1em 0 #ababab; 525 | min-height: 2em; 526 | 527 | background-color: white; 528 | color: black; 529 | white-space: nowrap; 530 | } 531 | 532 | .modifierName { 533 | display: flex; 534 | align-items: center; 535 | padding: 0.25em; 536 | padding-left: 1em; 537 | padding-right: 1em; 538 | border-radius: 0.5em; 539 | margin: 0.25em; 540 | margin-left: auto; 541 | margin-right: auto; 542 | text-align: center; 543 | box-shadow: 0 0.1em 0 #474c8f; 544 | text-shadow: #474c8f -1px 2px; 545 | width: fit-content; 546 | 547 | background-color: #757cdc; 548 | color: #fff; 549 | white-space: nowrap; 550 | } 551 | 552 | .descContent { 553 | width: 100%; 554 | text-align: center; 555 | } 556 | 557 | #cardsInHand .tooltip:last-child, #bestPlay .tooltip:last-child, #jokerArea .tooltip:last-child { 558 | width: 100000%; 559 | } 560 | 561 | .positionButtons { 562 | width: calc(100% - 2px); 563 | padding: 0; 564 | 565 | font-size: 22px; 566 | text-align: center; 567 | height: 1.4em; 568 | display: flex; 569 | justify-content: center; 570 | flex-wrap: wrap; 571 | 572 | container-type: inline-size; 573 | } 574 | 575 | .positionButtons .lvlBtn { 576 | margin: 1px; 577 | font-size: 16.5px; 578 | height: fit-content; 579 | padding-bottom: 2px; 580 | border-width: 1px; 581 | } 582 | 583 | :not(#cardsInHand) > div:first-child > div > .positionButtons > .lvlBtn:first-child, 584 | :not(#cardsInHand) > div:last-child > div > .positionButtons > .lvlBtn:last-child { 585 | background-color: #374244; 586 | } 587 | 588 | @container(max-width: 45px) { 589 | .lvlBtn:first-child + .lvlBtn { 590 | order: 3; 591 | } 592 | } 593 | 594 | #bestPlayScoreX { 595 | border-radius: 0.25em; 596 | } 597 | 598 | #scoreMult, 599 | #scoreChips { 600 | border-radius: 0.125em; 601 | padding: 0.125em; 602 | text-shadow: 0.07em 0.09em #0005; 603 | } 604 | 605 | input { 606 | font-family: inherit; 607 | font-size: inherit; 608 | padding-top: 0; 609 | padding-bottom: 0; 610 | } 611 | 612 | .nameLvl { 613 | font-size: 0.5em; 614 | position: relative; 615 | bottom: 0.2em; 616 | color: #efefef; 617 | } 618 | 619 | #breakdown { 620 | width: 100%; 621 | text-align: center; 622 | } 623 | 624 | .breakdownLine { 625 | color: black; 626 | background-color: #fff; 627 | margin: auto; 628 | padding: 0.2em; 629 | margin-top: 0.5em; 630 | border-radius: 0.3em; 631 | text-align: center; 632 | height: 56px; 633 | min-width: 20em; 634 | max-width: calc(100% - 3em); 635 | white-space: nowrap; 636 | } 637 | 638 | .breakdownLine > div:first-child { 639 | display: inline-block; 640 | float: left; 641 | min-width: 78px; 642 | } 643 | 644 | .breakdownLine > div:first-child > .tooltip .playfieldCard { 645 | animation: none; 646 | } 647 | 648 | .breakdownLine > div:first-child > .tooltip .playfieldCard:hover { 649 | animation: none; 650 | scale: 1; 651 | cursor: default; 652 | } 653 | .breakdownLine > div:first-child > .tooltip .playfieldCard:after { 654 | box-shadow: none; 655 | border-radius: 0; 656 | } 657 | 658 | .breakdownLine > div > .tooltip { 659 | scale: 0.5; 660 | height: 47px; 661 | position: relative; 662 | bottom: 0.5em; 663 | width: 36px; 664 | } 665 | 666 | .breakdownLine > span { 667 | display: inline-block; 668 | white-space: nowrap; 669 | margin-top: 0.2em; 670 | margin-right: -0.4em; 671 | position: relative; 672 | top: 16px; 673 | } 674 | 675 | .breakdownLine > .levelStat { 676 | position: sticky; 677 | float: right; 678 | border-radius: 0.25em; 679 | top: 0px; 680 | margin-top: 14px; 681 | } 682 | 683 | .jokerCard:hover + .removeJoker, 684 | .removeJoker:hover { 685 | visibility: visible; 686 | top: -40%; 687 | height: 60%; 688 | color: #fff; 689 | } 690 | 691 | .removeJoker { 692 | color: #fff0; 693 | background-color: #f00; 694 | position: relative; 695 | margin-left: 20%; 696 | margin-right: 20%; 697 | border-radius: 5px; 698 | cursor: pointer; 699 | visibility: hidden; 700 | height: 0%; 701 | top: 20%; 702 | transition: 0.2s; 703 | padding-top: 8px; 704 | } 705 | 706 | #modifyJoker .tooltip { 707 | scale: 2; 708 | translate: 0 50%; 709 | } 710 | 711 | #modifyJoker .tooltip > .tooltiptext { 712 | top: calc(100% + 1em); 713 | visibility: visible; 714 | scale: 0.75; 715 | translate: -12.5% -12%; 716 | } 717 | 718 | #modifyJoker { 719 | height: 450px; 720 | } 721 | 722 | .modifierCard + .tooltiptext, 723 | .jmodifierCard + .tooltiptext { 724 | left: -1em; 725 | translate: -50%; 726 | } 727 | 728 | .polychrome:after { 729 | background-blend-mode: normal, color; 730 | } 731 | 732 | .jokerCard.polychrome:after { 733 | background-blend-mode: color, normal; 734 | } 735 | 736 | .EV { 737 | font-size: 0.5em; 738 | color: #8d8; 739 | } 740 | 741 | .resetBtn { 742 | background-color: #ff4d40; 743 | color: white; 744 | font-family: inherit; 745 | font-size: 32px; 746 | padding: 0.2em; 747 | cursor: pointer; 748 | border-radius: 8px; 749 | margin-bottom: 1em; 750 | vertical-align: middle; 751 | } 752 | 753 | .resetBtn:hover { 754 | background-color: #df2d20; 755 | } 756 | -------------------------------------------------------------------------------- /hand-url.js: -------------------------------------------------------------------------------- 1 | /* 2 | hand binary format: 3 | 4 | joker: 5 | number of jokers - 16 bits 6 | type[0] - 4 bits 7 | type[1] - 4 bits 8 | edition? - 1 bit 9 | edition - 2 bits 10 | non-zero value? - 1 bit 11 | value - 16 bits 12 | 13 | cards: 14 | number of cards - 16 bits 15 | number of cards in hand - 3 bits 16 | suit - 2 bits 17 | value - 4 bits 18 | edition? - 1 bit 19 | edition - 2 bits 20 | modifier - 4 bits 21 | 22 | hands: 23 | non-zero hand? 1 bits 24 | value - 16 bits 25 | 26 | the flint - 1 bit 27 | plasmaDeck - 1 bit 28 | observatory? - 1 bit 29 | planet cards: 30 | non-zero - 1 bit 31 | value - 16 bits 32 | 33 | */ 34 | 35 | function editionBinary(modifiers) { 36 | if(modifiers.foil || modifiers.holographic || modifiers.polychrome || modifiers.disabled) { 37 | // edition - 2 bits 38 | if(modifiers.foil) { 39 | return [1, 0, 0]; 40 | } 41 | else if(modifiers.holographic) { 42 | return [1, 1, 0]; 43 | } 44 | else if(modifiers.polychrome) { 45 | return [1, 0, 1]; 46 | } 47 | else { 48 | return [1, 1, 1]; 49 | } 50 | } 51 | else { 52 | return [0]; 53 | } 54 | } 55 | 56 | function modifiersBinary(modifiers) { 57 | let double = modifiers.double ? 1 : 0; 58 | 59 | if(modifiers.chance) { 60 | return [1, 0, 0, double]; 61 | } 62 | else if(modifiers.glass) { 63 | return [0, 1, 0, double]; 64 | } 65 | else if(modifiers.increment) { 66 | return [1, 1, 0, double]; 67 | } 68 | else if(modifiers.mult) { 69 | return [0, 0, 1, double]; 70 | } 71 | else if(modifiers.steel) { 72 | return [1, 0, 1, double]; 73 | } 74 | else if(modifiers.stone) { 75 | return [0, 1, 1, double]; 76 | } 77 | else if(modifiers.wild) { 78 | return [1, 1, 1, double]; 79 | } 80 | else { 81 | return [0, 0, 0, double]; 82 | } 83 | } 84 | 85 | function intToBinary(num, bits) { 86 | let value = []; 87 | for(let i = 0; i < bits; i++) { 88 | value.push(num & (1 << i) ? 1 : 0); 89 | } 90 | return value; 91 | } 92 | 93 | function signed16(num) { 94 | return [num < 0 ? 1 : 0, ...intToBinary(Math.abs(num), 15)]; 95 | } 96 | 97 | function bitsToBase64(bitsArray) { 98 | // Convert bits array to binary string 99 | const binaryString = bitsArray.join(''); 100 | 101 | // Pad the binary string to make its length a multiple of 8 102 | const paddedBinaryString = binaryString.padEnd(Math.ceil(binaryString.length / 8) * 8, '0'); 103 | 104 | // Convert binary string to bytes 105 | const bytes = []; 106 | for (let i = 0; i < paddedBinaryString.length; i += 8) { 107 | bytes.push(parseInt(paddedBinaryString.substr(i, 8), 2)); 108 | } 109 | 110 | // Convert bytes to base64 string 111 | const base64String = btoa(String.fromCharCode.apply(null, bytes)); 112 | 113 | return base64String.replace(/=/g, ''); 114 | } 115 | 116 | function base64ToBits(base64String) { 117 | // Decode base64 string to bytes 118 | const binaryString = atob(base64String); 119 | 120 | // Convert bytes to binary 121 | let bits = []; 122 | for (let i = 0; i < binaryString.length; i++) { 123 | const byte = binaryString.charCodeAt(i).toString(2).padStart(8, '0').split('').map(a => a * 1); 124 | bits.push(...byte); 125 | } 126 | 127 | return bits; 128 | } 129 | 130 | function compileHand() { 131 | let binary = []; 132 | 133 | // number of jokers - 16 bits 134 | binary.push(...intToBinary(Object.keys(playfieldJokers).length, 16)); 135 | 136 | for(let i = 0; i < bestJokers.length; i++) { 137 | let joker = playfieldJokers[bestJokers[i]]; 138 | // type[0] - 4 bits 139 | binary.push(...intToBinary(joker.type[0], 4)); 140 | // type[1] - 4 bits 141 | binary.push(...intToBinary(joker.type[1], 4)); 142 | 143 | // edition? - 1 bit 144 | // edition - 2 bits 145 | binary.push(...editionBinary(joker.modifiers)); 146 | 147 | // non-zero value? - 1 bit 148 | if(joker.value === 0) { 149 | binary.push(0); 150 | } 151 | else { 152 | binary.push(1); 153 | // value - 16 bits 154 | binary.push(...signed16(joker.value)); 155 | } 156 | 157 | // non-expected sell value? - 1 bit 158 | if(joker.sell === Math.floor((jokerPrice[joker.type[0]][joker.type[1]] + ((joker.modifiers.foil || joker.modifiers.holographic || joker.modifiers.polychrome) ? 1 : 0)) / 2)) { 159 | binary.push(0); 160 | } 161 | else { 162 | binary.push(1); 163 | // value - 16 bits 164 | binary.push(...intToBinary(joker.sell, 16)); 165 | } 166 | } 167 | 168 | // number of cards - 16 bits 169 | binary.push(...intToBinary(Object.keys(playfieldCards).length, 16)); 170 | // number of cards in hand - 3 bits 171 | binary.push(...intToBinary(bestHand.length, 3)); 172 | 173 | for(let i = 0; i < bestHand.length; i++) { 174 | let card = playfieldCards[bestHand[i]]; 175 | if(!card) continue; 176 | // suit - 2 bits 177 | binary.push(...intToBinary(card.type[0], 2)); 178 | // value - 4 bits 179 | binary.push(...intToBinary(card.type[1], 4)); 180 | 181 | // edition? - 1 bit 182 | // edition - 2 bits 183 | binary.push(...editionBinary(card.modifiers)); 184 | 185 | // modifier - 4 bits 186 | binary.push(...modifiersBinary(card.modifiers)); 187 | } 188 | 189 | for(let id in playfieldCards) { 190 | if(bestHand.indexOf(id) >= 0) continue; 191 | let card = playfieldCards[id]; 192 | 193 | // suit - 2 bits 194 | binary.push(...intToBinary(card.type[0], 2)); 195 | // value - 4 bits 196 | binary.push(...intToBinary(card.type[1], 4)); 197 | 198 | // edition? - 1 bit 199 | // edition - 2 bits 200 | binary.push(...editionBinary(card.modifiers)); 201 | 202 | // modifier - 4 bits 203 | binary.push(...modifiersBinary(card.modifiers)); 204 | } 205 | 206 | binary.push(theFlint ? 1 : 0); 207 | binary.push(plasmaDeck ? 1 : 0); 208 | 209 | for(let i = 0; i < hands.length; i++) { 210 | if(hands[i].level === 1) { 211 | binary.push(0); 212 | } 213 | else { 214 | binary.push(1); 215 | binary.push(...intToBinary(hands[i].level, 16)); 216 | } 217 | } 218 | 219 | binary.push(observatory ? 1 : 0); 220 | 221 | if(observatory) { 222 | for(let i = 0; i < hands.length; i++) { 223 | if(hands[i].planets > 0) { 224 | binary.push(1); 225 | binary.push(...intToBinary(hands[i].planets, 16)); 226 | } 227 | else { 228 | binary.push(0); 229 | } 230 | } 231 | } 232 | 233 | // hand counts (for supernova/obelisk/card sharp) - 1 bit 234 | let nonZeroedHand = false; 235 | for(let i = 0; i < hands.length; i++) { 236 | if(hands[i].playedThisRound || hands[i].played) { 237 | nonZeroedHand = true; 238 | } 239 | } 240 | if(nonZeroedHand) { 241 | binary.push(1); 242 | for(let i = 0; i < hands.length; i++) { 243 | binary.push(hands[i].playedThisRound ? 1 : 0); 244 | 245 | binary.push(hands[i].played ? 1 : 0); 246 | if(hands[i].played) { 247 | binary.push(...intToBinary(hands[i].played, 16)); 248 | } 249 | } 250 | } 251 | else { 252 | binary.push(0); 253 | } 254 | 255 | // remove trailing 0s 256 | while(binary.length > 0 && binary[binary.length - 1] === 0) { 257 | binary.pop(); 258 | } 259 | 260 | // set URL 261 | if(binary.length === 0) { 262 | history.replaceState(null, null, '?'); 263 | } 264 | else { 265 | let queryParams = new URLSearchParams(window.location.search); 266 | queryParams.set("h", toUrlSafe(bitsToBase64(binary))); 267 | history.replaceState(null, null, '?' + queryParams.toString()); 268 | } 269 | } 270 | 271 | let atBit = 0; 272 | 273 | function nextBits(bits, binary) { 274 | let ans = []; 275 | for(let i = 0; i < bits; i++) { 276 | if(atBit >= binary.length) { 277 | ans.push(0); 278 | } 279 | else { 280 | ans.push(binary[atBit++]); 281 | } 282 | } 283 | return ans; 284 | } 285 | 286 | function intFromBits(bits, binary) { 287 | let subBits = nextBits(bits, binary); 288 | let ans = 0; 289 | for(let i = 0; i < bits; i++) { 290 | if(subBits[i]) ans += 1 << i; 291 | } 292 | return ans; 293 | } 294 | 295 | function toUrlSafe(str) { 296 | return str.replace(/\+/g,'-').replace(/\//g,'_'); 297 | } 298 | 299 | function fromUrlSafe(str) { 300 | if(str.indexOf('/') > 0 || str.indexOf('+') > 0) return str; 301 | return str.replace(/_/g,'/').replace(/-/g,'+'); 302 | } 303 | 304 | function parseOldHand(bits) { 305 | if(bits.length === 0) { 306 | return; 307 | } 308 | atBit = 0; 309 | 310 | if(optimizeJokers) toggleJoker(); 311 | if(optimizeCards) toggleCard(); 312 | 313 | // number of jokers - 16 bits 314 | let numberOfJokers = intFromBits(16, bits); 315 | 316 | for(let i = 0; i < numberOfJokers; i++) { 317 | const type = [intFromBits(4, bits), intFromBits(4, bits)]; 318 | 319 | jmodifiers.foil = false; 320 | jmodifiers.holographic = false; 321 | jmodifiers.polychrome = false; 322 | jmodifiers.disabled = false; 323 | 324 | if(intFromBits(1, bits)) { 325 | switch(intFromBits(2, bits)) { 326 | case 0: 327 | jmodifiers.foil = true; 328 | break; 329 | case 1: 330 | jmodifiers.holographic = true; 331 | break; 332 | case 2: 333 | jmodifiers.polychrome = true; 334 | break; 335 | case 3: 336 | jmodifiers.disabled = true; 337 | break; 338 | } 339 | } 340 | 341 | jokerValue = 0; 342 | if(intFromBits(1, bits)) { 343 | let sign = intFromBits(1, bits) ? -1 : 1; 344 | jokerValue = sign * intFromBits(15, bits); 345 | } 346 | 347 | addJoker(...type); 348 | } 349 | 350 | jmodifiers.foil = false; 351 | jmodifiers.holographic = false; 352 | jmodifiers.polychrome = false; 353 | jmodifiers.disabled = false; 354 | jokerValue = 0; 355 | 356 | // number of cards - 16 bits 357 | let numberOfCards = intFromBits(16, bits); 358 | 359 | // number of cards in hand - 3 bits 360 | let numberOfCardsInHand = intFromBits(3, bits); 361 | 362 | for(let i = 0; i < numberOfCards; i++) { 363 | const type = [intFromBits(2, bits), intFromBits(4, bits)]; 364 | 365 | modifiers.foil = false; 366 | modifiers.holographic = false; 367 | modifiers.polychrome = false; 368 | modifiers.disabled = false; 369 | 370 | modifiers.stone = false; 371 | modifiers.increment = false; 372 | modifiers.mult = false; 373 | modifiers.wild = false; 374 | modifiers.chance = false; 375 | modifiers.glass = false; 376 | modifiers.steel = false; 377 | 378 | if(intFromBits(1, bits)) { 379 | switch(intFromBits(2, bits)) { 380 | case 0: 381 | modifiers.foil = true; 382 | break; 383 | case 1: 384 | modifiers.holographic = true; 385 | break; 386 | case 2: 387 | modifiers.polychrome = true; 388 | break; 389 | case 3: 390 | modifiers.disabled = true; 391 | break; 392 | } 393 | } 394 | 395 | switch(intFromBits(3, bits)) { 396 | case 1: modifiers.chance = true; break; 397 | case 2: modifiers.glass = true; break; 398 | case 3: modifiers.increment = true; break; 399 | case 4: modifiers.mult = true; break; 400 | case 5: modifiers.steel = true; break; 401 | case 6: modifiers.stone = true; break; 402 | case 7: modifiers.wild = true; break; 403 | } 404 | 405 | modifiers.double = intFromBits(1, bits) ? true : false; 406 | 407 | setModifierString(); 408 | 409 | addCard(...type); 410 | 411 | if(i < numberOfCardsInHand) { 412 | const keys = Object.keys(playfieldCards); 413 | bestHand.push(keys[keys.length - 1]); 414 | } 415 | } 416 | 417 | modifiers.foil = false; 418 | modifiers.holographic = false; 419 | modifiers.polychrome = false; 420 | modifiers.disabled = false; 421 | 422 | modifiers.stone = false; 423 | modifiers.increment = false; 424 | modifiers.mult = false; 425 | modifiers.wild = false; 426 | modifiers.chance = false; 427 | modifiers.glass = false; 428 | modifiers.steel = false; 429 | 430 | modifiers.double = false; 431 | 432 | if(theFlint != intFromBits(1, bits)) { 433 | toggleTheFlint(); 434 | } 435 | 436 | if(plasmaDeck != intFromBits(1, bits)) { 437 | togglePlasma(); 438 | } 439 | 440 | for(let i = 0; i < hands.length; i++) { 441 | if(intFromBits(1, bits) === 1) { 442 | hands[i].level = intFromBits(16, bits) + 1; 443 | incrementLevel(-1, i); 444 | } 445 | } 446 | 447 | if(observatory != intFromBits(1, bits)) { 448 | toggleObservatory(); 449 | } 450 | 451 | if(observatory) { 452 | for(let i = 0; i < hands.length; i++) { 453 | if(intFromBits(1, bits) === 1) { 454 | hands[i].planets = intFromBits(16, bits) + 1; 455 | incrementPlanet(-1, i); 456 | } 457 | } 458 | } 459 | 460 | redrawPlayfield(); 461 | } 462 | 463 | function parseHand(bits) { 464 | if(bits.length === 0) { 465 | return; 466 | } 467 | atBit = 0; 468 | 469 | if(optimizeJokers) toggleJoker(); 470 | if(optimizeCards) toggleCard(); 471 | 472 | // number of jokers - 16 bits 473 | let numberOfJokers = intFromBits(16, bits); 474 | 475 | for(let i = 0; i < numberOfJokers; i++) { 476 | const type = [intFromBits(4, bits), intFromBits(4, bits)]; 477 | 478 | jmodifiers.foil = false; 479 | jmodifiers.holographic = false; 480 | jmodifiers.polychrome = false; 481 | jmodifiers.disabled = false; 482 | 483 | if(intFromBits(1, bits)) { 484 | switch(intFromBits(2, bits)) { 485 | case 0: 486 | jmodifiers.foil = true; 487 | break; 488 | case 1: 489 | jmodifiers.holographic = true; 490 | break; 491 | case 2: 492 | jmodifiers.polychrome = true; 493 | break; 494 | case 3: 495 | jmodifiers.disabled = true; 496 | break; 497 | } 498 | } 499 | 500 | jokerValue = 0; 501 | if(intFromBits(1, bits)) { 502 | let sign = intFromBits(1, bits) ? -1 : 1; 503 | jokerValue = sign * intFromBits(15, bits); 504 | } 505 | 506 | if(intFromBits(1, bits)) { 507 | addJoker(...type, intFromBits(16, bits)); 508 | } 509 | else { 510 | addJoker(...type); 511 | } 512 | } 513 | 514 | jmodifiers.foil = false; 515 | jmodifiers.holographic = false; 516 | jmodifiers.polychrome = false; 517 | jmodifiers.disabled = false; 518 | jokerValue = 0; 519 | 520 | // number of cards - 16 bits 521 | let numberOfCards = intFromBits(16, bits); 522 | 523 | // number of cards in hand - 3 bits 524 | let numberOfCardsInHand = intFromBits(3, bits); 525 | 526 | for(let i = 0; i < numberOfCards; i++) { 527 | const type = [(intFromBits(2, bits) + 1) % 4, intFromBits(4, bits)]; 528 | 529 | modifiers.foil = false; 530 | modifiers.holographic = false; 531 | modifiers.polychrome = false; 532 | modifiers.disabled = false; 533 | 534 | modifiers.stone = false; 535 | modifiers.increment = false; 536 | modifiers.mult = false; 537 | modifiers.wild = false; 538 | modifiers.chance = false; 539 | modifiers.glass = false; 540 | modifiers.steel = false; 541 | 542 | if(intFromBits(1, bits)) { 543 | switch(intFromBits(2, bits)) { 544 | case 0: 545 | modifiers.foil = true; 546 | break; 547 | case 1: 548 | modifiers.holographic = true; 549 | break; 550 | case 2: 551 | modifiers.polychrome = true; 552 | break; 553 | case 3: 554 | modifiers.disabled = true; 555 | break; 556 | } 557 | } 558 | 559 | switch(intFromBits(3, bits)) { 560 | case 1: modifiers.chance = true; break; 561 | case 2: modifiers.glass = true; break; 562 | case 3: modifiers.increment = true; break; 563 | case 4: modifiers.mult = true; break; 564 | case 5: modifiers.steel = true; break; 565 | case 6: modifiers.stone = true; break; 566 | case 7: modifiers.wild = true; break; 567 | } 568 | 569 | modifiers.double = intFromBits(1, bits) ? true : false; 570 | 571 | setModifierString(); 572 | 573 | addCard(...type); 574 | 575 | if(i < numberOfCardsInHand) { 576 | const keys = Object.keys(playfieldCards); 577 | bestHand.push(keys[keys.length - 1]); 578 | } 579 | } 580 | 581 | modifiers.foil = false; 582 | modifiers.holographic = false; 583 | modifiers.polychrome = false; 584 | modifiers.disabled = false; 585 | 586 | modifiers.stone = false; 587 | modifiers.increment = false; 588 | modifiers.mult = false; 589 | modifiers.wild = false; 590 | modifiers.chance = false; 591 | modifiers.glass = false; 592 | modifiers.steel = false; 593 | 594 | modifiers.double = false; 595 | 596 | setModifierString(); 597 | 598 | if(theFlint != intFromBits(1, bits)) { 599 | toggleTheFlint(); 600 | } 601 | 602 | if(plasmaDeck != intFromBits(1, bits)) { 603 | togglePlasma(); 604 | } 605 | 606 | for(let i = 0; i < hands.length; i++) { 607 | if(intFromBits(1, bits) === 1) { 608 | hands[i].level = intFromBits(16, bits) + 1; 609 | incrementLevel(-1, i); 610 | } 611 | } 612 | 613 | if(observatory != intFromBits(1, bits)) { 614 | toggleObservatory(); 615 | } 616 | 617 | if(observatory) { 618 | for(let i = 0; i < hands.length; i++) { 619 | if(intFromBits(1, bits) === 1) { 620 | hands[i].planets = intFromBits(16, bits) + 1; 621 | incrementPlanet(-1, i); 622 | } 623 | } 624 | } 625 | 626 | // hand counts (for supernova/obelisk/card sharp) - 1 bit 627 | if(intFromBits(1, bits)) { 628 | for(let i = 0; i < hands.length; i++) { 629 | hands[i].playedThisRound = intFromBits(1, bits); 630 | if(hands[i].playedThisRound) { 631 | handLevels.children[i].children[0].innerText = 'X'; 632 | } 633 | else { 634 | handLevels.children[i].children[0].innerHTML = ' '; 635 | } 636 | if(intFromBits(1, bits)) { 637 | hands[i].played = intFromBits(16, bits); 638 | handLevels.children[i].children[1].children[0].innerText = hands[i].played; 639 | } 640 | } 641 | } 642 | 643 | redrawPlayfield(); 644 | } 645 | 646 | (new URL(window.location.href)).searchParams.forEach((x, y) => { 647 | if(y === 'hand') { 648 | parseOldHand(base64ToBits(fromUrlSafe(x))); 649 | } 650 | else if(y === 'h') { 651 | parseHand(base64ToBits(fromUrlSafe(x))); 652 | } 653 | }); 654 | -------------------------------------------------------------------------------- /manageWorkers.js: -------------------------------------------------------------------------------- 1 | const THREADS = navigator.hardwareConcurrency; 2 | 3 | const threads = []; 4 | 5 | let taskID = Math.random(); 6 | let tasks = 0; 7 | 8 | let bestScore; 9 | let bestHand = []; 10 | let bestJokers = []; 11 | 12 | const bestPlayScoreDiv = document.getElementById('bestPlayScore'); 13 | const bestPlayNameDiv = document.getElementById('bestPlayName'); 14 | const scoreChipsDiv = document.getElementById('scoreChips'); 15 | const scoreMultDiv = document.getElementById('scoreMult'); 16 | const chipIcon = ''; 17 | 18 | let optimizeJokers = true; 19 | let optimizeCards = true; 20 | const toggleJokerDiv = document.getElementById('toggleJokerBtn'); 21 | const toggleCardDiv = document.getElementById('toggleCardBtn'); 22 | const minimizeDiv = document.getElementById('toggleMinimizeBtn'); 23 | const toggleTheFlintDiv = document.getElementById('toggleTheFintBtn'); 24 | const toggleTheEyeDiv = document.getElementById('toggleTheEyeBtn'); 25 | const togglePlasmaDiv = document.getElementById('togglePlasmaBtn'); 26 | const toggleObservatoryDiv = document.getElementById('toggleObservatoryBtn'); 27 | 28 | let theFlint = false; 29 | let theEye = false; 30 | let plasmaDeck = false; 31 | let observatory = false; 32 | 33 | let minimize = false; 34 | let optimizeMode = 0; 35 | 36 | function toggleMinimize() { 37 | minimize = !minimize; 38 | redrawPlayfield(); 39 | 40 | if(minimize) { 41 | minimizeDiv.innerText = 'X'; 42 | } 43 | else { 44 | minimizeDiv.innerHTML = ' '; 45 | } 46 | } 47 | 48 | function toggleJoker() { 49 | optimizeJokers = !optimizeJokers; 50 | if(optimizeJokers) { 51 | if(optimizeCards && (Object.keys(playfieldJokers).length >= 8 || Object.keys(playfieldCards).length >= 10)) { 52 | toggleCard(); 53 | } 54 | } 55 | redrawPlayfield(); 56 | 57 | if(optimizeJokers) { 58 | toggleJokerDiv.innerText = 'X'; 59 | } 60 | else { 61 | toggleJokerDiv.innerHTML = ' '; 62 | } 63 | } 64 | 65 | function toggleCard() { 66 | optimizeCards = !optimizeCards; 67 | if(optimizeCards) { 68 | if(optimizeJokers && (Object.keys(playfieldJokers).length >= 8 || Object.keys(playfieldCards).length >= 10)) { 69 | toggleJoker(); 70 | } 71 | } 72 | redrawPlayfield(); 73 | 74 | if(optimizeCards) { 75 | toggleCardDiv.innerText = 'X'; 76 | } 77 | else { 78 | toggleCardDiv.innerHTML = ' '; 79 | } 80 | } 81 | 82 | function togglePlasma() { 83 | plasmaDeck = !plasmaDeck; 84 | redrawPlayfield(); 85 | 86 | if(plasmaDeck) { 87 | togglePlasmaDiv.innerText = 'X'; 88 | } 89 | else { 90 | togglePlasmaDiv.innerHTML = ' '; 91 | } 92 | } 93 | 94 | function toggleTheFlint() { 95 | theFlint = !theFlint; 96 | redrawPlayfield(); 97 | 98 | if(theFlint) { 99 | toggleTheFlintDiv.innerText = 'X'; 100 | } 101 | else { 102 | toggleTheFlintDiv.innerHTML = ' '; 103 | } 104 | } 105 | 106 | function toggleTheEye() { 107 | theEye = !theEye; 108 | redrawPlayfield(); 109 | 110 | if(theEye) { 111 | toggleTheEyeDiv.innerText = 'X'; 112 | } 113 | else { 114 | toggleTheEyeDiv.innerHTML = ' '; 115 | } 116 | } 117 | 118 | function toggleObservatory() { 119 | observatory = !observatory; 120 | redrawPlayfield(); 121 | 122 | if(observatory) { 123 | toggleObservatoryDiv.innerText = 'X'; 124 | consumables.style.display = 'block'; 125 | } 126 | else { 127 | toggleObservatoryDiv.innerHTML = ' '; 128 | consumables.style.display = 'none'; 129 | } 130 | } 131 | 132 | function togglePlayed(index) { 133 | hands[index].playedThisRound = hands[index].playedThisRound ? 0 : 1; 134 | 135 | redrawPlayfield(); 136 | 137 | if(hands[index].playedThisRound) { 138 | handLevels.children[index].children[0].innerText = 'X'; 139 | } 140 | else { 141 | handLevels.children[index].children[0].innerHTML = ' '; 142 | } 143 | } 144 | 145 | function invertPlayedHands(index) { 146 | for(let index = 0; index < hands.length; index++) { 147 | hands[index].playedThisRound = hands[index].playedThisRound ? 0 : 1; 148 | 149 | if(hands[index].playedThisRound) { 150 | handLevels.children[index].children[0].innerText = 'X'; 151 | } 152 | else { 153 | handLevels.children[index].children[0].innerHTML = ' '; 154 | } 155 | } 156 | 157 | redrawPlayfield(); 158 | } 159 | 160 | function permutations(inputArr) { 161 | var results = []; 162 | 163 | function permute(arr, memo) { 164 | var cur, memo = memo || []; 165 | 166 | for (let i = 0; i < arr.length; i++) { 167 | cur = arr.splice(i, 1); 168 | if (arr.length === 0) { 169 | results.push(memo.concat(cur)); 170 | } 171 | permute(arr.slice(), memo.concat(cur)); 172 | arr.splice(i, 0, cur[0]); 173 | } 174 | 175 | return results; 176 | } 177 | 178 | return permute(inputArr); 179 | } 180 | 181 | function getEdition(modifiers) { 182 | if(modifiers.foil) return 1; 183 | if(modifiers.holographic) return 2; 184 | if(modifiers.polychrome) return 3; 185 | //if(modifiers.negative) return 4; 186 | return 0; 187 | } 188 | 189 | function getEnhancement(modifiers) { 190 | if(modifiers.increment) return 1; 191 | if(modifiers.mult) return 2; 192 | if(modifiers.wild) return 3; 193 | if(modifiers.glass) return 4; 194 | if(modifiers.steel) return 5; 195 | if(modifiers.stone) return 6; 196 | if(modifiers.gold) return 7; 197 | if(modifiers.chance) return 8; 198 | return 0; 199 | } 200 | 201 | let breakdownHand = new Hand(); 202 | 203 | function terminateThreads() { 204 | for(let i = 0; i < threads.length; i++) { 205 | threads[i].terminate(); 206 | } 207 | 208 | let state = { 209 | cards: Object.keys(playfieldCards).map((a, index) => { 210 | return [ 211 | playfieldCards[a].type[1], 212 | playfieldCards[a].type[0], 213 | getEdition(playfieldCards[a].modifiers), 214 | getEnhancement(playfieldCards[a].modifiers), 215 | playfieldCards[a].modifiers.double ? 2 : 0, 216 | 0, // extra chips 217 | playfieldCards[a].modifiers.disabled, 218 | index 219 | ]; 220 | }), 221 | hands: hands.map(a => { 222 | return [ 223 | a.level, 224 | a.planets, 225 | a.played, 226 | a.playedThisRound 227 | ]; 228 | }), 229 | TheFlint: theFlint, 230 | TheEye: theEye, 231 | PlasmaDeck: plasmaDeck, 232 | Observatory: observatory, 233 | taskID, 234 | optimizeCards, 235 | minimize, 236 | optimizeMode, 237 | bestHand: bestHand.map(a => { 238 | return Object.keys(playfieldCards).indexOf(a); 239 | }), 240 | jokers: Object.keys(playfieldJokers).map((a, index) => { 241 | return [ 242 | playfieldJokers[a].type[0] * 10 + playfieldJokers[a].type[1], 243 | playfieldJokers[a].value, 244 | getEdition(playfieldJokers[a].modifiers), 245 | playfieldJokers[a].modifiers.disabled, 246 | playfieldJokers[a].sell, 247 | index 248 | ]; 249 | }) 250 | }; 251 | 252 | breakdownHand.TheFlint = theFlint; 253 | breakdownHand.TheEye = theEye; 254 | breakdownHand.PlasmaDeck = plasmaDeck; 255 | breakdownHand.Observatory = observatory; 256 | breakdownHand.hands = state.hands; 257 | 258 | for(let i = 0; i < THREADS; i++) { 259 | threads[i] = new Worker('worker.js'); 260 | threads[i].onmessage = workerMessage; 261 | threads[i].postMessage(['start', {...state, workerID: i}]); 262 | } 263 | } 264 | 265 | let tmpBestJokers; 266 | let tmpBestCards; 267 | let tmpBestCardsInHand; 268 | let tmpBestHighHand; 269 | let tmpBestLowHand; 270 | let tmpTypeOfHand; 271 | let tmpBestID; 272 | let tmpCompiledValues; 273 | 274 | let tmpMeanScore; 275 | let tmpMedianScore; 276 | 277 | function workerMessage(msg) { 278 | if(msg.data[0] === taskID) { 279 | tasks--; 280 | // id, bestScore, bestJokers, bestCards, high, low 281 | if(!bestScore) { 282 | bestScore = msg.data[1]; 283 | tmpBestJokers = msg.data[2]; 284 | tmpBestCards = msg.data[3]; 285 | tmpBestCardsInHand = msg.data[4]; 286 | tmpBestHighHand = msg.data[5]; 287 | tmpBestLowHand = msg.data[6]; 288 | tmpTypeOfHand = msg.data[7]; 289 | tmpMeanScore = msg.data[8]; 290 | tmpMedianScore = msg.data[9]; 291 | tmpBestID = msg.data[10]; 292 | tmpCompiledValues = msg.data[11]; 293 | } 294 | if(minimize) { 295 | if(msg.data[1][1] < bestScore[1] || (msg.data[1][1] === bestScore[1] && msg.data[1][0] < bestScore[0])) { 296 | bestScore = msg.data[1]; 297 | tmpBestJokers = msg.data[2]; 298 | tmpBestCards = msg.data[3]; 299 | tmpBestCardsInHand = msg.data[4]; 300 | tmpBestHighHand = msg.data[5]; 301 | tmpBestLowHand = msg.data[6]; 302 | tmpTypeOfHand = msg.data[7]; 303 | tmpMeanScore = msg.data[8]; 304 | tmpMedianScore = msg.data[9]; 305 | tmpBestID = msg.data[10]; 306 | tmpCompiledValues = msg.data[11]; 307 | } 308 | else if(msg.data[6][1] < tmpBestLowHand[1] || (msg.data[6][1] === tmpBestLowHand[1] && msg.data[6][0] < tmpBestLowHand[0])) { 309 | bestScore = msg.data[1]; 310 | tmpBestJokers = msg.data[2]; 311 | tmpBestCards = msg.data[3]; 312 | tmpBestCardsInHand = msg.data[4]; 313 | tmpBestHighHand = msg.data[5]; 314 | tmpBestLowHand = msg.data[6]; 315 | tmpTypeOfHand = msg.data[7]; 316 | tmpMeanScore = msg.data[8]; 317 | tmpMedianScore = msg.data[9]; 318 | tmpBestID = msg.data[10]; 319 | tmpCompiledValues = msg.data[11]; 320 | } 321 | else if(msg.data[1][1] === bestScore[1] && msg.data[1][0] === bestScore[0] && msg.data[10] < tmpBestID) { 322 | bestScore = msg.data[1]; 323 | tmpBestJokers = msg.data[2]; 324 | tmpBestCards = msg.data[3]; 325 | tmpBestCardsInHand = msg.data[4]; 326 | tmpBestHighHand = msg.data[5]; 327 | tmpBestLowHand = msg.data[6]; 328 | tmpTypeOfHand = msg.data[7]; 329 | tmpMeanScore = msg.data[8]; 330 | tmpMedianScore = msg.data[9]; 331 | tmpBestID = msg.data[10]; 332 | tmpCompiledValues = msg.data[11]; 333 | } 334 | else if(tmpBestCards.length === 0 && msg.data[3].length > 0) { 335 | bestScore = msg.data[1]; 336 | tmpBestJokers = msg.data[2]; 337 | tmpBestCards = msg.data[3]; 338 | tmpBestCardsInHand = msg.data[4]; 339 | tmpBestHighHand = msg.data[5]; 340 | tmpBestLowHand = msg.data[6]; 341 | tmpTypeOfHand = msg.data[7]; 342 | tmpMeanScore = msg.data[8]; 343 | tmpMedianScore = msg.data[9]; 344 | tmpBestID = msg.data[10]; 345 | tmpCompiledValues = msg.data[11]; 346 | } 347 | } 348 | else { 349 | if(msg.data[1][1] > bestScore[1] || (msg.data[1][1] === bestScore[1] && msg.data[1][0] > bestScore[0])) { 350 | bestScore = msg.data[1]; 351 | tmpBestJokers = msg.data[2]; 352 | tmpBestCards = msg.data[3]; 353 | tmpBestCardsInHand = msg.data[4]; 354 | tmpBestHighHand = msg.data[5]; 355 | tmpBestLowHand = msg.data[6]; 356 | tmpTypeOfHand = msg.data[7]; 357 | tmpMeanScore = msg.data[8]; 358 | tmpMedianScore = msg.data[9]; 359 | tmpBestID = msg.data[10]; 360 | tmpCompiledValues = msg.data[11]; 361 | } 362 | else if(msg.data[5][1] > tmpBestHighHand[1] || (msg.data[5][1] === tmpBestHighHand[1] && msg.data[5][0] > tmpBestHighHand[0])) { 363 | bestScore = msg.data[1]; 364 | tmpBestJokers = msg.data[2]; 365 | tmpBestCards = msg.data[3]; 366 | tmpBestCardsInHand = msg.data[4]; 367 | tmpBestHighHand = msg.data[5]; 368 | tmpBestLowHand = msg.data[6]; 369 | tmpTypeOfHand = msg.data[7]; 370 | tmpMeanScore = msg.data[8]; 371 | tmpMedianScore = msg.data[9]; 372 | tmpBestID = msg.data[10]; 373 | tmpCompiledValues = msg.data[11]; 374 | } 375 | else if(msg.data[1][1] === bestScore[1] && msg.data[1][0] === bestScore[0] && msg.data[10] < tmpBestID) { 376 | bestScore = msg.data[1]; 377 | tmpBestJokers = msg.data[2]; 378 | tmpBestCards = msg.data[3]; 379 | tmpBestCardsInHand = msg.data[4]; 380 | tmpBestHighHand = msg.data[5]; 381 | tmpBestLowHand = msg.data[6]; 382 | tmpTypeOfHand = msg.data[7]; 383 | tmpMeanScore = msg.data[8]; 384 | tmpMedianScore = msg.data[9]; 385 | tmpBestID = msg.data[10]; 386 | tmpCompiledValues = msg.data[11]; 387 | } 388 | } 389 | if(tasks === 0) { 390 | bestJokers = tmpBestJokers.map(a => { 391 | return Object.keys(playfieldJokers)[a[5]]; 392 | }); 393 | bestHand = tmpBestCards.map(a => { 394 | return Object.keys(playfieldCards)[a[7]]; 395 | }); 396 | 397 | if(tmpBestHighHand[0] === tmpBestLowHand[0] && tmpBestHighHand[1] === tmpBestLowHand[1]) { 398 | bestPlayScoreDiv.innerHTML = chipIcon + bigNumberWithCommas(tmpBestHighHand, true); 399 | bestPlayNameDiv.innerHTML = hands[tmpTypeOfHand].name + ` lvl.${hands[tmpTypeOfHand].level}`; 400 | scoreChipsDiv.innerText = numberWithCommas(tmpBestLowHand[2]); 401 | scoreMultDiv.innerText = bigNumberWithCommas(tmpBestLowHand[3]); 402 | } 403 | else { 404 | bestPlayScoreDiv.innerHTML = bigNumberWithCommas(tmpBestLowHand, true) + ' <' + chipIcon + '< ' + bigNumberWithCommas(tmpBestHighHand, true); 405 | bestPlayScoreDiv.innerHTML += `
Long-term EV ${bigNumberWithCommas(tmpMeanScore, true)}
Short-term EV ${bigNumberWithCommas(tmpMedianScore, true)}
`; 406 | bestPlayNameDiv.innerHTML = hands[tmpTypeOfHand].name + ` lvl.${hands[tmpTypeOfHand].level}`; 407 | scoreChipsDiv.innerText = '>' + numberWithCommas(tmpBestLowHand[2]); 408 | scoreMultDiv.innerText = '>' + bigNumberWithCommas(tmpBestLowHand[3]); 409 | } 410 | 411 | redrawPlayfieldHTML(); 412 | 413 | document.body.style.cursor = ''; 414 | 415 | breakdownHand.jokers = tmpBestJokers; 416 | breakdownHand.cards = tmpBestCards; 417 | breakdownHand.cardsInHand = tmpBestCardsInHand; 418 | breakdownHand.compileAll(); 419 | breakdownHand.simulateWorstHand(); 420 | updateBreakdown(breakdownHand.breakdown.map(a => { 421 | return { 422 | ...a, 423 | cards: a.cards.map(b => b[7] === undefined ? Object.keys(playfieldJokers)[b[5]] : Object.keys(playfieldCards)[b[7]]) 424 | } 425 | })); 426 | } 427 | } 428 | } 429 | 430 | function updateBreakdown(breakdown) { 431 | breakdownHTML = ''; 432 | let previousChips = 0; 433 | let previousMult = [0, 0]; 434 | for(let line of breakdown) { 435 | let breakdownScore = ''; 436 | let breakdownCards = ''; 437 | for(let id of line.cards) { 438 | if(id[0] === 'j') { 439 | breakdownCards += `
'; 449 | } 450 | if(line.chips !== previousChips || line.mult[0] !== previousMult[0] || line.mult[1] !== previousMult[1]) { 451 | breakdownScore = `
` + 452 | `${numberWithCommas(line.chips)}X` + 453 | `${bigNumberWithCommas(normalizeBig(line.mult))}` + 454 | `
`; 455 | previousChips = line.chips; 456 | previousMult = line.mult; 457 | } 458 | breakdownHTML += `
` + 459 | breakdownCards + 460 | `
` + 461 | line.description + 462 | `` + 463 | breakdownScore + 464 | `
`; 465 | } 466 | 467 | tabs[3].innerHTML = breakdownHTML; 468 | } 469 | 470 | function factorial(n) { 471 | let ans = 1; 472 | for(let i = 2; i <= n; i++) { 473 | ans *= i; 474 | } 475 | return ans; 476 | } 477 | 478 | function calculator() { 479 | document.body.style.cursor = 'wait'; 480 | 481 | tmpBestCards = []; 482 | tmpBestHighHand = [0, 0, 0, 0, 0]; 483 | tmpBestLowHand = [0, 0, 0, 0, 0]; 484 | tmpTypeOfHand = 11; 485 | bestScore = false; 486 | 487 | taskID = Math.random(); 488 | let possibleHands = []; 489 | let chosen = []; 490 | tmpBestJokers = []; 491 | 492 | tasks = 0; 493 | 494 | terminateThreads(); 495 | 496 | if(Object.keys(playfieldJokers).length === 0) { 497 | threads[0].postMessage(['once']); 498 | tasks = 1; 499 | } 500 | else if(!optimizeJokers) { 501 | threads[0].postMessage(['dontOptimizeJokers']); 502 | tasks = 1; 503 | } 504 | else { 505 | let possibleJokers = factorial(Object.keys(playfieldJokers).length); 506 | let tasksPerThread = Math.ceil(possibleJokers / THREADS); 507 | 508 | for(let i = 0; i < THREADS; i++) { 509 | threads[i].postMessage(['optimizeJokers', i * tasksPerThread, (i + 1) * tasksPerThread]); 510 | tasks++; 511 | possibleJokers -= tasksPerThread; 512 | if(possibleJokers <= 0) { 513 | break; 514 | } 515 | } 516 | } 517 | 518 | bestJokers = Object.keys(playfieldJokers); 519 | } 520 | 521 | function numberWithCommas(x) { 522 | if(typeof x === 'object') return bigNumberWithCommas(x); 523 | if(x < 1e11) { 524 | if((Math.floor(x * 10000) / 10000) % 1 !== 0) { 525 | return Math.floor(x).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + '.' + (Math.floor(Math.round((x % 1) * 10000) / 10)+'').padStart(3, 0).replace(/0+$/, ''); 526 | } 527 | return Math.floor(x).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 528 | } 529 | return `${Math.floor(x/(10**Math.floor(Math.log10(x)))*10000)/10000}e${Math.floor(Math.log10(x))}`; 530 | } 531 | 532 | function bigNumberWithCommas(num, whole = false) { 533 | if(num && num[1] > 11) { 534 | return `${Math.floor(num[0] * 10000) / 10000}e${num[1]}`; 535 | } 536 | 537 | const x = num[0] * (10 ** num[1]); 538 | if((Math.floor(x * 10000) / 10000) % 1 !== 0) { 539 | if(whole) { 540 | return Math.floor(x).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 541 | } 542 | return Math.floor(x).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + '.' + (Math.floor(Math.round((x % 1) * 10000) / 10)+'').padStart(3, 0).replace(/0+$/, ''); 543 | } 544 | return Math.floor(x).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 545 | } 546 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Balatro Calculator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 |
26 |
27 |
28 |
29 | X 30 | Optimize Jokers (slow) 31 |
32 |
33 |
34 |
35 | X 36 | Optimize Hand (slow) 37 |
38 |
39 |
40 | 0/7
41 |
42 |
High Card lvl.1
43 |
5
44 |
45 | 5X1 46 |
47 |
48 |
49 |
50 | 0/8
51 |
52 |
53 |   54 | Minimize Score 55 |
56 |
57 |   58 | The Flint 59 |
60 |
61 |   62 | Can't play checked hands (The Eye) 63 |
64 |
65 |   66 | Plasma Deck 67 |
68 |
69 |   70 | Observatory 71 |
72 | 73 |
74 | 75 | 76 | 77 | 78 |
79 | 82 |
83 |
84 |
85 | - 86 | 1 87 | + 88 | Count 89 | 1 90 |
91 |
92 |
93 |
94 | - 95 | 0 96 | + 97 | Value 98 | 0 99 |
100 |
101 |
102 |
103 | Search 104 | 105 |
106 |
107 |
108 |
109 |
110 | +50 ChipsFoil 111 |
112 |
113 | +10 MultHolographic 114 |
115 |
116 | X1.5 MultPolychrome 117 |
118 |
119 | Does not triggerDisabled 120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | - 128 | 1 129 | + 130 | Count 131 | 1 132 |
133 |
134 |
135 |
136 |
137 | +50 ChipsFoil 138 |
139 |
140 | +10 MultHolographic 141 |
142 |
143 | X1.5 MultPolychrome 144 |
145 |
146 | +30 ChipsBonus Card 147 |
148 |
149 | +4 MultMult Card 150 |
151 |
152 | Can be used
as any suit
Wild Card
153 |
154 |
155 | X2 Mult
1 in 4 chance to
destroy card
Glass Card
156 |
157 |
158 | X1.5 Mult
while this card
stays in hand
Steel Card
159 |
160 |
161 | +50 ChipsStone Card 162 |
163 |
164 | $3 if this card
is in hand
at end of round
Gold Card
165 |
166 |
167 | 1 in 5 chance
for +20 Mult
1 in 15 chance
to win $20
Lucky Card
168 |
169 |
170 | Retrigger this
card 1 time
Red Seal
171 |
172 |
173 | Does not triggerDisabled 174 |
175 |
176 |
177 |
 High Contrast
178 |
179 |
180 |
181 | 182 |
183 |
184 |
185 |
186 |
187 | - 188 | 0 189 | + 190 | Value 191 | 0 192 |
193 |
194 |
195 |
196 | - 197 | 0 198 | + 199 | Sell 200 | 0 201 |
202 |
203 |
204 |
205 |
206 | +50 ChipsFoil 207 |
208 |
209 | +10 MultHolographic 210 |
211 |
212 | X1.5 MultPolychrome 213 |
214 |
215 | Does not triggerDisabled 216 |
217 |
218 |
219 |
220 |
221 |
225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /structured-data.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "http://schema.org", 3 | "@type": "VideoGame", 4 | "name": "Balatro Calculator", 5 | "description": "Calculate the score of any Balatro hand", 6 | "gameItem": [ 7 | { 8 | "@type": "Thing", 9 | "name": "Joker", 10 | "description": "+4 Mult" 11 | }, 12 | { 13 | "@type": "Thing", 14 | "name": "Chaos the Clown", 15 | "description": "1 free Reroll per shop" 16 | }, 17 | { 18 | "@type": "Thing", 19 | "name": "Jolly Joker", 20 | "description": "+8 Mult if played hand contains a Pair" 21 | }, 22 | { 23 | "@type": "Thing", 24 | "name": "Zany Joker", 25 | "description": "+12 Mult if played hand contains a Three of a Kind" 26 | }, 27 | { 28 | "@type": "Thing", 29 | "name": "Mad Joker", 30 | "description": "+10 Mult if played hand contains a Two Pair" 31 | }, 32 | { 33 | "@type": "Thing", 34 | "name": "Crazy Joker", 35 | "description": "+12 Mult if played hand contains a Straight" 36 | }, 37 | { 38 | "@type": "Thing", 39 | "name": "Droll Joker", 40 | "description": "+10 Mult if played hand contains a Flush" 41 | }, 42 | { 43 | "@type": "Thing", 44 | "name": "Half Joker", 45 | "description": "+20 Mult if played hand contains 3 or fewer cards" 46 | }, 47 | { 48 | "@type": "Thing", 49 | "name": "Merry Andy", 50 | "description": "+3 discards, -1 hand size" 51 | }, 52 | { 53 | "@type": "Thing", 54 | "name": "Stone Joker", 55 | "description": "This Joker gains +25 Chips for each Stone Card in your full deck (Currently +0 Chips)" 56 | }, 57 | { 58 | "@type": "Thing", 59 | "name": "Juggler", 60 | "description": "+1 hand size" 61 | }, 62 | { 63 | "@type": "Thing", 64 | "name": "Drunkard", 65 | "description": "+1 discard" 66 | }, 67 | { 68 | "@type": "Thing", 69 | "name": "Acrobat", 70 | "description": "X3 Mult on final hand of round (Currently inactive)" 71 | }, 72 | { 73 | "@type": "Thing", 74 | "name": "Sock and Buskin", 75 | "description": "Retrigger all played face cards" 76 | }, 77 | { 78 | "@type": "Thing", 79 | "name": "Mime", 80 | "description": "Retrigger all card held in hand abilities" 81 | }, 82 | { 83 | "@type": "Thing", 84 | "name": "Credit Card", 85 | "description": "Go up to -$20 in debt" 86 | }, 87 | { 88 | "@type": "Thing", 89 | "name": "Greedy Joker", 90 | "description": "Played cards with Diamond suit give +3 Mult when scored" 91 | }, 92 | { 93 | "@type": "Thing", 94 | "name": "Lusty Joker", 95 | "description": "Played cards with Heart suit give +3 Mult when scored" 96 | }, 97 | { 98 | "@type": "Thing", 99 | "name": "Wrathful Joker", 100 | "description": "Played cards with Spade suit give +3 Mult when scored" 101 | }, 102 | { 103 | "@type": "Thing", 104 | "name": "Gluttonous Joker", 105 | "description": "Played cards with Club suit give +3 Mult when scored" 106 | }, 107 | { 108 | "@type": "Thing", 109 | "name": "Troubadour", 110 | "description": "+2 hand size, -1 hands per round" 111 | }, 112 | { 113 | "@type": "Thing", 114 | "name": "Banner", 115 | "description": "+30 Chips for each remaining discard (Currently +0 Chips)" 116 | }, 117 | { 118 | "@type": "Thing", 119 | "name": "Mystic Summit", 120 | "description": "+15 Mult when 0 discards remaining (Currently inactive)" 121 | }, 122 | { 123 | "@type": "Thing", 124 | "name": "Marble Joker", 125 | "description": "Adds one Stone card to deck when Blind is selected" 126 | }, 127 | { 128 | "@type": "Thing", 129 | "name": "Loyalty Card", 130 | "description": "X4 Mult every 6 hands played (0 remaining)" 131 | }, 132 | { 133 | "@type": "Thing", 134 | "name": "Hack", 135 | "description": "Retrigger each played 2, 3, 4, or 5" 136 | }, 137 | { 138 | "@type": "Thing", 139 | "name": "Misprint", 140 | "description": "+0 - +23 Mult" 141 | }, 142 | { 143 | "@type": "Thing", 144 | "name": "Steel Joker", 145 | "description": "This Joker gains X0.2 Mult for each Steel Card in your full deck (Currently X1 Mult)" 146 | }, 147 | { 148 | "@type": "Thing", 149 | "name": "Raised Fist", 150 | "description": "Adds double the rank of lowest card held in hand to Mult" 151 | }, 152 | { 153 | "@type": "Thing", 154 | "name": "Golden Joker", 155 | "description": "Earn $4 at end of round" 156 | }, 157 | { 158 | "@type": "Thing", 159 | "name": "Blueprint", 160 | "description": "Copies ability of Joker to the right" 161 | }, 162 | { 163 | "@type": "Thing", 164 | "name": "Glass Joker", 165 | "description": "This Joker gains X0.75 Mult for every Glass Card that is destroyed (Currently X1 Mult)" 166 | }, 167 | { 168 | "@type": "Thing", 169 | "name": "Scary Face", 170 | "description": "Played face cards give +30 Chips when scored" 171 | }, 172 | { 173 | "@type": "Thing", 174 | "name": "Abstract Joker", 175 | "description": "+3 Mult for each Joker card (Currently +0 Mult)" 176 | }, 177 | { 178 | "@type": "Thing", 179 | "name": "Delayed Gratification", 180 | "description": "Earn $2 per discard if no discards are used by end of the round" 181 | }, 182 | { 183 | "@type": "Thing", 184 | "name": "Golden Ticket", 185 | "description": "Played Gold cards earn $4 when scored" 186 | }, 187 | { 188 | "@type": "Thing", 189 | "name": "Pareidolia", 190 | "description": "All cards are considered Face cards" 191 | }, 192 | { 193 | "@type": "Thing", 194 | "name": "Cartomancer", 195 | "description": "Create a Tarot card when Blind is selected (Must have room)" 196 | }, 197 | { 198 | "@type": "Thing", 199 | "name": "Even Steven", 200 | "description": "Played cards with even rank give +4 Mult when scored (10, 8, 6, 4, 2)" 201 | }, 202 | { 203 | "@type": "Thing", 204 | "name": "Odd Todd", 205 | "description": "Played cards with odd rank give +31 Chips when scored (A, 9, 7, 5, 3)" 206 | }, 207 | { 208 | "@type": "Thing", 209 | "name": "Wee Joker", 210 | "description": "This Joker gains +8 Chips when each played 2 is scored (Currently +0 Chips)" 211 | }, 212 | { 213 | "@type": "Thing", 214 | "name": "Business Card", 215 | "description": "Played Face cards have a 1 in 2 chance to give $2 when scored" 216 | }, 217 | { 218 | "@type": "Thing", 219 | "name": "Supernova", 220 | "description": "Adds the number of times poker hand has been played to Mult (Currently +0 Mult)" 221 | }, 222 | { 223 | "@type": "Thing", 224 | "name": "Mr. Bones", 225 | "description": "Prevents Death if chips scored are at least 25% of required chips self destructs" 226 | }, 227 | { 228 | "@type": "Thing", 229 | "name": "Seeing Double", 230 | "description": "X2 Mult if played hand has a scoring Club card and a scoring card of any other suit" 231 | }, 232 | { 233 | "@type": "Thing", 234 | "name": "The Duo", 235 | "description": "X2 Mult if played hand contains a Pair" 236 | }, 237 | { 238 | "@type": "Thing", 239 | "name": "The Trio", 240 | "description": "X3 Mult if played hand contains a Three of a Kind" 241 | }, 242 | { 243 | "@type": "Thing", 244 | "name": "The Family", 245 | "description": "X4 Mult if played hand contains a Four of a Kind" 246 | }, 247 | { 248 | "@type": "Thing", 249 | "name": "The Order", 250 | "description": "X3 Mult if played hand contains a Straight" 251 | }, 252 | { 253 | "@type": "Thing", 254 | "name": "The Tribe", 255 | "description": "X2 Mult if played hand contains a Flush" 256 | }, 257 | { 258 | "@type": "Thing", 259 | "name": "8 Ball", 260 | "description": "1 in 4 chance for each played 8 to create a Tarot card when scored (Must have room)" 261 | }, 262 | { 263 | "@type": "Thing", 264 | "name": "Fibonacci", 265 | "description": "Each plaed Ace, 2, 3, 5, or 8 gives +8 Mult when scored" 266 | }, 267 | { 268 | "@type": "Thing", 269 | "name": "Joker Stencil", 270 | "description": "X1 Mult for each empty Joker slot Joker stencil included (Currently X1)" 271 | }, 272 | { 273 | "@type": "Thing", 274 | "name": "Space Joker", 275 | "description": "1 in 4 chane to upgrade level of played poker hand" 276 | }, 277 | { 278 | "@type": "Thing", 279 | "name": "Matador", 280 | "description": "Earn $8 if played hand triggers the Boss Blind ability" 281 | }, 282 | { 283 | "@type": "Thing", 284 | "name": "Ceremonial Dagger", 285 | "description": "When Blind is selected, destroy Joker to the right and permanently add double its sell value to this Mult (Currently +0 Mult)" 286 | }, 287 | { 288 | "@type": "Thing", 289 | "name": "Showman", 290 | "description": "Joker, Tarot, Planet, and Spectral cards may appear multiple times" 291 | }, 292 | { 293 | "@type": "Thing", 294 | "name": "Fortune Teller", 295 | "description": "+1 Mult per Tarot card used this run (Currently 0)" 296 | }, 297 | { 298 | "@type": "Thing", 299 | "name": "Hit the Road", 300 | "description": "This Joker gains X0.5 Mult per discarded Jack this round (Currently X1)" 301 | }, 302 | { 303 | "@type": "Thing", 304 | "name": "Swashbuckler", 305 | "description": "Adds the sell value of all other owned Jokers to Mult" 306 | }, 307 | { 308 | "@type": "Thing", 309 | "name": "Flower Pot", 310 | "description": "X3 Mult if poker hand contains a Diamond card, Club card, Heart card, and Spade card" 311 | }, 312 | { 313 | "@type": "Thing", 314 | "name": "Ride the Bus", 315 | "description": "This Joker gains +1 Mult per consecutive hand played without a scoring face card (Currently +0)" 316 | }, 317 | { 318 | "@type": "Thing", 319 | "name": "Shoot the Moon", 320 | "description": "+13 Mult for each Queen held in hand" 321 | }, 322 | { 323 | "@type": "Thing", 324 | "name": "Scholar", 325 | "description": "Played Aces give +20 Chips and +4 Mult when scored" 326 | }, 327 | { 328 | "@type": "Thing", 329 | "name": "Smeared Joker", 330 | "description": "Heart and Diamond count as the same suit, Spade and Club count as the same suit" 331 | }, 332 | { 333 | "@type": "Thing", 334 | "name": "Oops! All 6s", 335 | "description": "Double all listed probabilities (ex: 1 in 3 -> 2 in 3)" 336 | }, 337 | { 338 | "@type": "Thing", 339 | "name": "Four Fingers", 340 | "description": "All Flushes and Straights can be made with 4 cards" 341 | }, 342 | { 343 | "@type": "Thing", 344 | "name": "Gros Michel", 345 | "description": "+15 Mult 1 in 6 chance this card is destroyed at the end of the round" 346 | }, 347 | { 348 | "@type": "Thing", 349 | "name": "Stuntman", 350 | "description": "+250 Chips, -2 hand size" 351 | }, 352 | { 353 | "@type": "Thing", 354 | "name": "Hanging Chad", 355 | "description": "Retrigger first played card used in scoring 2 additional times" 356 | }, 357 | { 358 | "@type": "Thing", 359 | "name": "Driver's License", 360 | "description": "X3 Mult if you have at least 16 Enhanced cards in your full deck (Currently 0)" 361 | }, 362 | { 363 | "@type": "Thing", 364 | "name": "Invisible Joker", 365 | "description": "After 2 rounds, sell this card to duplicate a random Joker (Currently 0/2)" 366 | }, 367 | { 368 | "@type": "Thing", 369 | "name": "Astronomer", 370 | "description": "All Planet cards and Celestial Packs in the shop are free" 371 | }, 372 | { 373 | "@type": "Thing", 374 | "name": "Burnt Joker", 375 | "description": "Upgrade the level of the first discarded poker hand each round" 376 | }, 377 | { 378 | "@type": "Thing", 379 | "name": "Dusk", 380 | "description": "Retrigger all played cards in final hand of round (Currently inactive)" 381 | }, 382 | { 383 | "@type": "Thing", 384 | "name": "Throwback", 385 | "description": "X0.25 Mult for each Blind skipped this run (Currently X1 Mult)" 386 | }, 387 | { 388 | "@type": "Thing", 389 | "name": "The Idol", 390 | "description": "Each played 2 of Hearts gives X2 Mult when scored Card changes every round" 391 | }, 392 | { 393 | "@type": "Thing", 394 | "name": "Brainstorm", 395 | "description": "Copies the ability of the leftmost Joker" 396 | }, 397 | { 398 | "@type": "Thing", 399 | "name": "Satellite", 400 | "description": "Earn $1 at the end of round per unique Planet card used this run (Currently $0)" 401 | }, 402 | { 403 | "@type": "Thing", 404 | "name": "Rough Gem", 405 | "description": "Played cards with Diamond suit earn $1 when scored" 406 | }, 407 | { 408 | "@type": "Thing", 409 | "name": "Bloodstone", 410 | "description": "1 in 2 chance for played cards with Heart suit to give X1.5 Mult when scored" 411 | }, 412 | { 413 | "@type": "Thing", 414 | "name": "Arrowhead", 415 | "description": "Played cards with Spade suit give +50 Chips when scored" 416 | }, 417 | { 418 | "@type": "Thing", 419 | "name": "Onyx Agate", 420 | "description": "Played cards with Club suit give +7 Mult when scored" 421 | }, 422 | { 423 | "@type": "Thing", 424 | "name": "Canio", 425 | "description": "Gains X1 Mult when a face card is destroyed (Currently X1 Mult)" 426 | }, 427 | { 428 | "@type": "Thing", 429 | "name": "Triboulet", 430 | "description": "Played Kings and Queens each give X2 Mult when scored" 431 | }, 432 | { 433 | "@type": "Thing", 434 | "name": "Yorick", 435 | "description": "This Joker gains X1 Mult every 23 [23] cards discarded (Currently X1 Mult)" 436 | }, 437 | { 438 | "@type": "Thing", 439 | "name": "Chicot", 440 | "description": "Disables effect of every Boss Blind" 441 | }, 442 | { 443 | "@type": "Thing", 444 | "name": "Perkeo", 445 | "description": "Creates a Negative copy of 1 random consumable card in your possession at the end of the shop" 446 | }, 447 | { 448 | "@type": "Thing", 449 | "name": "Certificate", 450 | "description": "When round begins, add a random playing card with a random seal to your hand" 451 | }, 452 | { 453 | "@type": "Thing", 454 | "name": "Bootstraps", 455 | "description": "+2 Mult for every $5 you have (Currently +0 Mult)" 456 | }, 457 | { 458 | "@type": "Thing", 459 | "name": "Egg", 460 | "description": "Gains $3 of sell value at end of round" 461 | }, 462 | { 463 | "@type": "Thing", 464 | "name": "Burglar", 465 | "description": "When Blind is selected, gain +3 Hands and lose all discards" 466 | }, 467 | { 468 | "@type": "Thing", 469 | "name": "Blackboard", 470 | "description": "X3 Mult if all cards held in hand are Spade or Club" 471 | }, 472 | { 473 | "@type": "Thing", 474 | "name": "Runner", 475 | "description": "Gains +15 Chips if played hand contains a Straight (Currently +0 Chips)" 476 | }, 477 | { 478 | "@type": "Thing", 479 | "name": "Ice Cream", 480 | "description": "+100 Chips -5 Chips for every hand played" 481 | }, 482 | { 483 | "@type": "Thing", 484 | "name": "DNA", 485 | "description": "If first hand of round has only 1 card, add a permanent copy to deck and draw it to hand" 486 | }, 487 | { 488 | "@type": "Thing", 489 | "name": "Splash", 490 | "description": "Every played card counts in scoring" 491 | }, 492 | { 493 | "@type": "Thing", 494 | "name": "Blue Joker", 495 | "description": "+2 Chips for each remaining card in Deck (Currently +104 Chips)" 496 | }, 497 | { 498 | "@type": "Thing", 499 | "name": "Sixth Sense", 500 | "description": "If first hand of round is a single 6, destroy it and create a Spectral card (Must have room)" 501 | }, 502 | { 503 | "@type": "Thing", 504 | "name": "Constellation", 505 | "description": "This Joker gains X0.1 Mult per Planet card used (Currently X1 Mult)" 506 | }, 507 | { 508 | "@type": "Thing", 509 | "name": "Hiker", 510 | "description": "Every played card permanently gains +5 Chips when scored" 511 | }, 512 | { 513 | "@type": "Thing", 514 | "name": "Faceless Joker", 515 | "description": "Earn $5 if 3 or more face cards are discarded at the same time" 516 | }, 517 | { 518 | "@type": "Thing", 519 | "name": "Green Joker", 520 | "description": "+1 Mult per hand played -1 Mult per discard (Currently +0 Mult)" 521 | }, 522 | { 523 | "@type": "Thing", 524 | "name": "Superposition", 525 | "description": "Create a Tarot card if poker hand contains an Ace and a Straight (Must have room)" 526 | }, 527 | { 528 | "@type": "Thing", 529 | "name": "To Do List", 530 | "description": "Earn $4 if poker hand is a Pair, poker hand changes at end of round" 531 | }, 532 | { 533 | "@type": "Thing", 534 | "name": "Cavendish", 535 | "description": "X3 Mult 1 in 1000 chance this card is destroy at end of round" 536 | }, 537 | { 538 | "@type": "Thing", 539 | "name": "Card Sharp", 540 | "description": "X3 Mult if played poker hand has already been played this round (Currently inactive)" 541 | }, 542 | { 543 | "@type": "Thing", 544 | "name": "Red Card", 545 | "description": "This Joker gains +3 Mult when any Booster Pack is skipped (Currently +0 Mult)" 546 | }, 547 | { 548 | "@type": "Thing", 549 | "name": "Madness", 550 | "description": "When Small Blind or Big Blind is selected, gain X0.5 Mult and destroy a random Joker (Currently X1 Mult)" 551 | }, 552 | { 553 | "@type": "Thing", 554 | "name": "Square Joker", 555 | "description": "This Joker gains +4 Chips if played hand has exactly 4 card (Currently +0 Chips)" 556 | }, 557 | { 558 | "@type": "Thing", 559 | "name": "Séance", 560 | "description": "If poker hand is a Straight Flush, craeate a random Spectral card (Must have room)" 561 | }, 562 | { 563 | "@type": "Thing", 564 | "name": "Riff-raff", 565 | "description": "When Blind is selected, create 2 Common Jokers (Must have room)" 566 | }, 567 | { 568 | "@type": "Thing", 569 | "name": "Vampire", 570 | "description": "This Joker gains X0.1 Mult per scoring Enhanced card played, removes card Enhancement (Currently X1 Mult)" 571 | }, 572 | { 573 | "@type": "Thing", 574 | "name": "Shortcut", 575 | "description": "Allows Straights to be made with gaps of 1 rank (ex: 10 8 6 5 3)" 576 | }, 577 | { 578 | "@type": "Thing", 579 | "name": "Hologram", 580 | "description": "This Joker gains X0.25 Mult per playing card added to your deck (Currently X1 Mult)" 581 | }, 582 | { 583 | "@type": "Thing", 584 | "name": "Vagabond", 585 | "description": "Create a Tarot card if hand is played with $4 or less" 586 | }, 587 | { 588 | "@type": "Thing", 589 | "name": "Baron", 590 | "description": "Each King held in hand gives X1.5 Mult" 591 | }, 592 | { 593 | "@type": "Thing", 594 | "name": "Cloud 9", 595 | "description": "Earn $1 for each 9 in your full deck at end of round (Currently $0)" 596 | }, 597 | { 598 | "@type": "Thing", 599 | "name": "Rocket", 600 | "description": "Earn $1 at end of round. Gains $2 when Boss Blind is defeated" 601 | }, 602 | { 603 | "@type": "Thing", 604 | "name": "Obelisk", 605 | "description": "This Joker gains X0.2 Mult per consecutive hand played without playing your must played poker hand (Currently X1 Mult)" 606 | }, 607 | { 608 | "@type": "Thing", 609 | "name": "Midas Mask", 610 | "description": "All played face cards become Gold cards when scored" 611 | }, 612 | { 613 | "@type": "Thing", 614 | "name": "Luchador", 615 | "description": "Sell this card to disable the current Boss Blind" 616 | }, 617 | { 618 | "@type": "Thing", 619 | "name": "Photograph", 620 | "description": "First played face card gives X2 Mult when scored" 621 | }, 622 | { 623 | "@type": "Thing", 624 | "name": "Gift Card", 625 | "description": "Add $1 of sell value to every Joker and Consumable card at end of round" 626 | }, 627 | { 628 | "@type": "Thing", 629 | "name": "Turtle Bean", 630 | "description": "+5 hand size, reduces by 1 every round" 631 | }, 632 | { 633 | "@type": "Thing", 634 | "name": "Erosion", 635 | "description": "+4 Mult for each card below 52 in your full deck (Currently +0)" 636 | }, 637 | { 638 | "@type": "Thing", 639 | "name": "Reserved Parking", 640 | "description": "Each face card held in hand has a 1 in 2 chance to give $1" 641 | }, 642 | { 643 | "@type": "Thing", 644 | "name": "Mail-In Rebate", 645 | "description": "Earn $5 for each discarded 2, rank changes every round" 646 | }, 647 | { 648 | "@type": "Thing", 649 | "name": "To the Moon", 650 | "description": "Earn an extra $1 of interest for every $5 you have at end of round" 651 | }, 652 | { 653 | "@type": "Thing", 654 | "name": "Hallucination", 655 | "description": "1 in 2 chance to create a Tarot card when any Booster Pack is opened (Must have room)" 656 | }, 657 | { 658 | "@type": "Thing", 659 | "name": "Sly Joker", 660 | "description": "+50 Chips if played hand contains a Pair" 661 | }, 662 | { 663 | "@type": "Thing", 664 | "name": "Wily Joker", 665 | "description": "+100 Chips if played hand contains a Three of a Kind" 666 | }, 667 | { 668 | "@type": "Thing", 669 | "name": "Clever Joker", 670 | "description": "+80 Chips if played hand contains a Two Pair" 671 | }, 672 | { 673 | "@type": "Thing", 674 | "name": "Devious Joker", 675 | "description": "+100 Chips if played hand contains a Straight" 676 | }, 677 | { 678 | "@type": "Thing", 679 | "name": "Crafty Joker", 680 | "description": "+80 Chips if played hand contains a Flush" 681 | }, 682 | { 683 | "@type": "Thing", 684 | "name": "Lucky Cat", 685 | "description": "This Joker gains X0.25 Mult each time a Lucky card successfully triggers (currently X1 Mult)" 686 | }, 687 | { 688 | "@type": "Thing", 689 | "name": "Baseball Card", 690 | "description": "Uncommon Jokers each give X1.5 Mult" 691 | }, 692 | { 693 | "@type": "Thing", 694 | "name": "Bull", 695 | "description": "+2 Chips for each dollar you have (Currently +0 Chips)" 696 | }, 697 | { 698 | "@type": "Thing", 699 | "name": "Diet Cola", 700 | "description": "Sell this card to create a free Double Tag" 701 | }, 702 | { 703 | "@type": "Thing", 704 | "name": "Trading Card", 705 | "description": "If first discard of round has only 1 card, destroy it and earn $3" 706 | }, 707 | { 708 | "@type": "Thing", 709 | "name": "Flash Card", 710 | "description": "This Joker gains +2 Mult per reroll in the shop (Currently +0 Mult)" 711 | }, 712 | { 713 | "@type": "Thing", 714 | "name": "Popcorn", 715 | "description": "+20 Mult -4 Mult per round played" 716 | }, 717 | { 718 | "@type": "Thing", 719 | "name": "Ramen", 720 | "description": "X2 Mult, loses X0.01 Mult per card discarded" 721 | }, 722 | { 723 | "@type": "Thing", 724 | "name": "Seltzer", 725 | "description": "Retrigger all cards played for the next 10 hands" 726 | }, 727 | { 728 | "@type": "Thing", 729 | "name": "Spare Trousers", 730 | "description": "Gain +2 Mult if played hand contains a Two Pair (Currently +0 Mult)" 731 | }, 732 | { 733 | "@type": "Thing", 734 | "name": "Campfire", 735 | "description": "This Joker gains X0.25 Mult for each card sold, resets when Boss Blind is defeated (Currently X1 Mult)" 736 | }, 737 | { 738 | "@type": "Thing", 739 | "name": "Smiley Face", 740 | "description": "Played face cards give +5 Mult when scored" 741 | }, 742 | { 743 | "@type": "Thing", 744 | "name": "Ancient Joker", 745 | "description": "Each played card with Heart suit gives X1.5 Mult when scored suit changes at end of round" 746 | }, 747 | { 748 | "@type": "Thing", 749 | "name": "Walkie Talkie", 750 | "description": "Each played 10 or 4 gives +10 Chips and +4 Mult when scored" 751 | }, 752 | { 753 | "@type": "Thing", 754 | "name": "Castle", 755 | "description": "This Joker gains +3 Chips per discarded Heart card, suit changes every round (Currently +0 Chips)" 756 | } 757 | ] 758 | } 759 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const menuBtns = [ 2 | document.getElementById('JokersBtn'), 3 | document.getElementById('CardsBtn'), 4 | document.getElementById('HandsBtn'), 5 | document.getElementById('BreakdownBtn'), 6 | document.getElementById('ModifyJokerBtn'), 7 | ]; 8 | 9 | const tabs = [ 10 | document.getElementById('Jokers'), 11 | document.getElementById('Cards'), 12 | document.getElementById('Hands'), 13 | document.getElementById('Breakdown'), 14 | document.getElementById('ModifyJoker'), 15 | ]; 16 | 17 | let searchVal = ''; 18 | 19 | for(let i = 0; i < menuBtns.length; i++) { 20 | menuBtns[i].addEventListener('click', changeTab(i)); 21 | tabs[i].style.display = "none"; 22 | } 23 | 24 | let revertToTab = 0; 25 | let modifyingJoker = false; 26 | let modifyingJokerValue = 0; 27 | 28 | let modifyingJokerValTxt = document.getElementById('modValue'); 29 | let modifyingJokerValueDiv = document.getElementById('modifyJokerValue'); 30 | let modifyingJokerValDiv = document.getElementById('modifyJokerVal'); 31 | let modifyingJokerSellValDiv = document.getElementById('modifyJokerSellVal'); 32 | let modifyJokerDiv = document.getElementById('modifyJoker'); 33 | let highContrastDiv = document.getElementById('highContrastBtn'); 34 | 35 | function changeTab(tab) { 36 | return () => { 37 | revertToTab = tab === 4 ? revertToTab : tab; 38 | for(let i = 0; i < menuBtns.length; i++) { 39 | menuBtns[i].classList.remove('active'); 40 | tabs[i].style.display = "none"; 41 | } 42 | menuBtns[tab].classList.add('active'); 43 | tabs[tab].style.display = "block"; 44 | 45 | modifyingJoker = false; 46 | } 47 | } 48 | 49 | changeTab(0)(); 50 | 51 | const hands = [ 52 | { 53 | name: "Flush Five", 54 | planet: "Eris", 55 | mult: 16, 56 | chips: 160, 57 | s_mult: 16, 58 | s_chips: 160, 59 | l_mult: 3, 60 | l_chips: 50 61 | }, 62 | { 63 | name: "Flush House", 64 | planet: "Ceres", 65 | mult: 14, 66 | chips: 140, 67 | s_mult: 14, 68 | s_chips: 140, 69 | l_mult: 4, 70 | l_chips: 40 71 | }, 72 | { 73 | name: "Five of a Kind", 74 | planet: "Planet X", 75 | mult: 12, 76 | chips: 120, 77 | s_mult: 12, 78 | s_chips: 120, 79 | l_mult: 3, 80 | l_chips: 35 81 | }, 82 | { 83 | name: "Straight Flush", 84 | planet: "Neptune", 85 | mult: 8, 86 | chips: 100, 87 | s_mult: 8, 88 | s_chips: 100, 89 | l_mult: 4, 90 | l_chips: 40 91 | }, 92 | { 93 | name: "Four of a Kind", 94 | planet: "Mars", 95 | mult: 7, 96 | chips: 60, 97 | s_mult: 7, 98 | s_chips: 60, 99 | l_mult: 3, 100 | l_chips: 30 101 | }, 102 | { 103 | name: "Full House", 104 | planet: "Earth", 105 | mult: 4, 106 | chips: 40, 107 | s_mult: 4, 108 | s_chips: 40, 109 | l_mult: 2, 110 | l_chips: 25 111 | }, 112 | { 113 | name: "Flush", 114 | planet: "Jupiter", 115 | mult: 4, 116 | chips: 35, 117 | s_mult: 4, 118 | s_chips: 35, 119 | l_mult: 2, 120 | l_chips: 15 121 | }, 122 | { 123 | name: "Straight", 124 | planet: "Saturn", 125 | mult: 4, 126 | chips: 30, 127 | s_mult: 4, 128 | s_chips: 30, 129 | l_mult: 3, 130 | l_chips: 30 131 | }, 132 | { 133 | name: "Three of a Kind", 134 | planet: "Venus", 135 | mult: 3, 136 | chips: 30, 137 | s_mult: 3, 138 | s_chips: 30, 139 | l_mult: 2, 140 | l_chips: 20 141 | }, 142 | { 143 | name: "Two Pair", 144 | planet: "Uranus", 145 | mult: 2, 146 | chips: 20, 147 | s_mult: 2, 148 | s_chips: 20, 149 | l_mult: 1, 150 | l_chips: 20 151 | }, 152 | { 153 | name: "Pair", 154 | planet: "Mercury", 155 | mult: 2, 156 | chips: 10, 157 | s_mult: 2, 158 | s_chips: 10, 159 | l_mult: 1, 160 | l_chips: 15 161 | }, 162 | { 163 | name: "High Card", 164 | planet: "Pluto", 165 | mult: 1, 166 | chips: 5, 167 | s_mult: 1, 168 | s_chips: 5, 169 | l_mult: 1, 170 | l_chips: 10 171 | } 172 | ]; 173 | 174 | const handColors = [ 175 | '#efefef', 176 | '#95acff', 177 | '#65efaf', 178 | '#fae37e', 179 | '#ffc052', 180 | '#f87d75', 181 | '#caa0ef' 182 | ]; 183 | 184 | const handLevels = document.getElementById('hands'); 185 | const consumables = document.getElementById('consumables'); 186 | 187 | function incrementLevel(inc, handIndex) { 188 | const hand = hands[handIndex]; 189 | const div = document.getElementById(hand.id); 190 | hand.level += inc; 191 | if(hand.level < 0) hand.level = 0; 192 | hand.mult = Math.max(1, hand.s_mult + (hand.level-1) * hand.l_mult); 193 | hand.chips = Math.max(0, hand.s_chips + (hand.level-1) * hand.l_chips); 194 | div.children[2].innerText = 'lvl.'+hand.level; 195 | div.children[2].style.backgroundColor = hand.level === 1 ? handColors[0] : handColors[((Math.ceil(Math.abs(hand.level)/6)*6+hand.level+4)%6)+1]; 196 | div.children[4].children[0].innerText = numberWithCommas(hand.chips); 197 | div.children[4].children[1].innerText = numberWithCommas(hand.mult); 198 | 199 | redrawPlayfield(); 200 | } 201 | 202 | function incrementPlanet(inc, handIndex) { 203 | const hand = hands[handIndex]; 204 | const div = document.getElementById('planets-' + hand.id); 205 | hand.planets += inc; 206 | if(hand.planets < 0 || inc === 0) hand.planets = 0; 207 | div.children[3].innerText = hand.planets; 208 | 209 | redrawPlayfield(); 210 | } 211 | 212 | function setPlanet(handIndex) { 213 | const hand = hands[handIndex]; 214 | const div = document.getElementById('planets-' + hand.id); 215 | let willBlur = false; 216 | 217 | if(div.children[3].innerText.indexOf('\n') >= 0) { 218 | div.children[3].innerText = div.children[3].innerText.replace(/[\r\n]/g, ''); 219 | willBlur = true; 220 | } 221 | if(1 * div.children[3].innerText > 0) { 222 | hand.planets = Math.round(1 * div.children[3].innerText); 223 | } 224 | else { 225 | hand.planets = 0; 226 | } 227 | 228 | if(willBlur) div.children[3].blur(); 229 | 230 | redrawPlayfield(); 231 | } 232 | 233 | function setLevel(handIndex) { 234 | const hand = hands[handIndex]; 235 | const div = document.getElementById(hand.id); 236 | let willBlur = false; 237 | 238 | if(div.children[2].innerText.indexOf('\n') >= 0) { 239 | div.children[2].innerText = div.children[2].innerText.replace(/[\r\n]/g, ''); 240 | willBlur = true; 241 | } 242 | if(1 * div.children[2].innerText > 0) { 243 | hand.level = Math.round(1 * div.children[2].innerText); 244 | } 245 | else { 246 | hand.level = 0; 247 | } 248 | 249 | hand.mult = Math.max(1, hand.s_mult + (hand.level-1) * hand.l_mult); 250 | hand.chips = Math.max(0, hand.s_chips + (hand.level-1) * hand.l_chips); 251 | div.children[2].style.backgroundColor = hand.level === 1 ? handColors[0] : handColors[((Math.ceil(Math.abs(hand.level)/6)*6+hand.level+4)%6)+1]; 252 | div.children[4].children[0].innerText = numberWithCommas(hand.chips); 253 | div.children[4].children[1].innerText = numberWithCommas(hand.mult); 254 | 255 | if(willBlur) div.children[2].blur(); 256 | 257 | redrawPlayfield(); 258 | } 259 | 260 | function setPlayed(handIndex) { 261 | const hand = hands[handIndex]; 262 | const div = document.getElementById(hand.id); 263 | let willBlur = false; 264 | 265 | if(div.children[1].children[0].innerText.indexOf('\n') >= 0) { 266 | div.children[1].children[0].innerText = div.children[1].children[0].innerText.replace(/[\r\n]/g, ''); 267 | willBlur = true; 268 | } 269 | 270 | if(1 * div.children[1].children[0].innerText > 0) { 271 | hand.played = Math.round(1 * div.children[1].children[0].innerText); 272 | } 273 | else { 274 | hand.played = 0; 275 | } 276 | 277 | if(willBlur) div.children[1].children[0].blur(); 278 | 279 | redrawPlayfield(); 280 | } 281 | 282 | function removeLvlText (handIndex) { 283 | const hand = hands[handIndex]; 284 | const div = document.getElementById(hand.id); 285 | div.children[2].innerText = hand.level; 286 | 287 | selectAll(div.children[2]) 288 | } 289 | 290 | function selectAll(div) { 291 | const selection = window.getSelection(); 292 | const range = document.createRange(); 293 | range.selectNodeContents(div); 294 | selection.removeAllRanges(); 295 | selection.addRange(range); 296 | } 297 | 298 | function addLvlText(handIndex) { 299 | const hand = hands[handIndex]; 300 | const div = document.getElementById(hand.id); 301 | div.children[2].innerText = 'lvl.'+hand.level; 302 | } 303 | 304 | const jokerValueHTML = document.getElementById('jokerVal'); 305 | let jokerValue = 0; 306 | 307 | const jokerCountHTML = document.getElementById('jokerCnt'); 308 | let jokerCount = 1; 309 | 310 | const cardCountHTML = document.getElementById('cardCnt'); 311 | let cardCount = 1; 312 | 313 | function incrementJokerValue(inc) { 314 | jokerValue += inc; 315 | if(inc === 0) { 316 | jokerValue = 0; 317 | } 318 | jokerValueHTML.innerText = jokerValue; 319 | jredrawCards(); 320 | } 321 | 322 | function setJokerValue() { 323 | let willBlur = false; 324 | 325 | if(jokerValueHTML.innerText.indexOf('\n') >= 0) { 326 | jokerValueHTML.innerText = jokerValueHTML.innerText.replace(/[\r\n]/g, ''); 327 | willBlur = true; 328 | } 329 | if(!isNaN(jokerValueHTML.innerText)) { 330 | jokerValue = Math.round(jokerValueHTML.innerText * 1); 331 | } 332 | else { 333 | jokerValue = 0; 334 | } 335 | 336 | if(willBlur) { 337 | jokerValueHTML.blur(); 338 | jokerValueHTML.innerText = jokerValue; 339 | } 340 | 341 | jredrawCards(); 342 | } 343 | 344 | function incrementJokerCount(inc) { 345 | jokerCount += inc; 346 | if(inc === 0) { 347 | jokerCount = 1; 348 | } 349 | jokerCountHTML.innerText = Math.max(1, jokerCount); 350 | } 351 | 352 | function setJokerCount() { 353 | console.log(jokerCountHTML.innerText); 354 | let willBlur = false; 355 | 356 | if(jokerCountHTML.innerText.indexOf('\n') >= 0) { 357 | jokerCountHTML.innerText = jokerCountHTML.innerText.replace(/[\r\n]/g, ''); 358 | willBlur = true; 359 | } 360 | if(!isNaN(jokerCountHTML.innerText)) { 361 | jokerCount = Math.max(1, Math.round(jokerCountHTML.innerText * 1)); 362 | } 363 | else { 364 | jokerCount = 1; 365 | } 366 | 367 | if(willBlur) { 368 | jokerCountHTML.blur(); 369 | jokerCountHTML.innerText = jokerCount; 370 | } 371 | } 372 | 373 | function incrementCardCount(inc) { 374 | cardCount += inc; 375 | if(inc === 0) { 376 | cardCount = 1; 377 | } 378 | cardCountHTML.innerText = Math.max(1, cardCount); 379 | } 380 | 381 | function setCardCount() { 382 | console.log(cardCountHTML.innerText); 383 | let willBlur = false; 384 | 385 | if(cardCountHTML.innerText.indexOf('\n') >= 0) { 386 | cardCountHTML.innerText = cardCountHTML.innerText.replace(/[\r\n]/g, ''); 387 | willBlur = true; 388 | } 389 | if(!isNaN(cardCountHTML.innerText)) { 390 | cardCount = Math.max(1, Math.round(cardCountHTML.innerText * 1)); 391 | } 392 | else { 393 | cardCount = 1; 394 | } 395 | 396 | if(willBlur) { 397 | cardCountHTML.blur(); 398 | cardCountHTML.innerText = cardCount; 399 | } 400 | } 401 | 402 | handLevels.innerHTML = ''; 403 | 404 | for(let i = 0; i < hands.length; i++) { 405 | hands[i].level = 1; 406 | hands[i].planets = 0; 407 | hands[i].id = hands[i].name.replace(/ /g,''); 408 | hands[i].played = 0; 409 | hands[i].playedThisRound = 0; 410 | handLevels.innerHTML += `
411 |   412 |
 #0
413 | lvl.1 414 | ${hands[i].name} 415 | 416 | ${hands[i].chips}X${hands[i].mult} 417 | 418 |
`; 419 | 420 | consumables.innerHTML += `
421 | - 422 | 0 423 | + 424 | 0 425 |
${hands[i].planet}
${hands[i].name}
426 |
`; 427 | } 428 | 429 | const modifiers = { 430 | foil: false, 431 | holographic: false, 432 | polychrome: false, 433 | stone: false, 434 | increment: false, 435 | mult: false, 436 | wild: false, 437 | chance: false, 438 | glass: false, 439 | steel: false, 440 | gold: false, 441 | double: false, 442 | disabled: false 443 | }; 444 | 445 | let modifierString = ', url(assets/Enhancers.png) -71px 0px'; 446 | let modifierPostString = ''; 447 | 448 | let modifierClass = ''; 449 | 450 | const jmodifiers = { 451 | foil: false, 452 | holographic: false, 453 | polychrome: false, 454 | disabled: false 455 | }; 456 | 457 | function jtoggleCardModifier(name) { 458 | if(('foil holographic polychrome disabled'.indexOf(name) >= 0) && !jmodifiers[name]) { 459 | jmodifiers.foil = false; 460 | jmodifiers.holographic = false; 461 | jmodifiers.polychrome = false; 462 | jmodifiers.disabled = false; 463 | } 464 | jmodifiers[name] = !jmodifiers[name]; 465 | 466 | jredrawCards(); 467 | } 468 | 469 | function setModifierString() { 470 | modifierClass = ''; 471 | 472 | if(modifiers.stone) { 473 | modifierString = ', url(assets/Enhancers.png) 142px 0'; 474 | } 475 | else if(modifiers.increment) { 476 | modifierString = ', url(assets/Enhancers.png) -71px -95px'; 477 | } 478 | else if(modifiers.mult) { 479 | modifierString = ', url(assets/Enhancers.png) -142px -95px'; 480 | } 481 | else if(modifiers.wild) { 482 | modifierString = ', url(assets/Enhancers.png) -213px -95px'; 483 | } 484 | else if(modifiers.chance) { 485 | modifierString = ', url(assets/Enhancers.png) -284px -95px'; 486 | } 487 | else if(modifiers.glass) { 488 | modifierString = ', url(assets/Enhancers.png) -355px -95px'; 489 | } 490 | else if(modifiers.steel) { 491 | modifierString = ', url(assets/Enhancers.png) -426px -95px'; 492 | } 493 | else if(modifiers.gold) { 494 | modifierString = ', url(assets/Enhancers.png) 71px 0px'; 495 | } 496 | else { 497 | modifierString = ', url(assets/Enhancers.png) -71px 0px'; 498 | } 499 | 500 | if(modifiers.double) { 501 | modifierPostString = 'url(assets/Enhancers.png) 142px 95px, '; 502 | } 503 | else { 504 | modifierPostString = 'url(assets/Jokers.png) 0px -855px, '; 505 | } 506 | 507 | if(modifiers.foil) { 508 | modifierPostString += 'url(assets/Editions.png) -71px 0, '; 509 | } 510 | else if(modifiers.holographic) { 511 | modifierPostString += 'url(assets/Editions.png) -142px 0, '; 512 | } 513 | else if(modifiers.polychrome) { 514 | modifierClass = ' polychrome'; 515 | modifierPostString += 'url(assets/Editions.png) -213px 0, '; 516 | } 517 | else if(modifiers.disabled) { 518 | modifierPostString += 'url(assets/Editions.png) 71px 0, '; 519 | } 520 | } 521 | 522 | function toggleCardModifier(name) { 523 | if(('gold stone increment mult wild chance glass steel'.indexOf(name) >= 0) && !modifiers[name]) { 524 | modifiers.stone = false; 525 | modifiers.increment = false; 526 | modifiers.mult = false; 527 | modifiers.wild = false; 528 | modifiers.chance = false; 529 | modifiers.glass = false; 530 | modifiers.steel = false; 531 | modifiers.gold = false; 532 | } 533 | 534 | if(('foil holographic polychrome disabled'.indexOf(name) >= 0) && !modifiers[name]) { 535 | modifiers.foil = false; 536 | modifiers.holographic = false; 537 | modifiers.polychrome = false; 538 | modifiers.disabled = false; 539 | } 540 | modifiers[name] = !modifiers[name]; 541 | 542 | setModifierString(); 543 | 544 | redrawCards(); 545 | } 546 | 547 | const cardsDiv = document.getElementById('cards'); 548 | const jcardsDiv = document.getElementById('jokers'); 549 | 550 | let highContrast = window.localStorage.hc === '1'; 551 | if(highContrast) { 552 | highContrastDiv.innerText = 'X'; 553 | } 554 | 555 | function cardString(i, j, hc = 0) { 556 | if(modifiers.stone) { 557 | return `${modifierClass}" style="background: ` + 558 | `${modifierPostString}${modifierString.slice(2)}"`; 559 | } 560 | else { 561 | return `${modifierClass}" style="background: ` + 562 | `${modifierPostString}url(assets/8BitDeck${(hc === 2 || (hc === 0 && highContrast))?'_opt2':''}.png) ` + 563 | `-${71*j}px -${95*i}px${modifierString}"`; 564 | } 565 | } 566 | 567 | function redrawCards() { 568 | let txt = ''; 569 | for(let i = 0; i < 4; i++) { 570 | txt += '
'; 571 | for(let j = 0; j < 13; j++) { 572 | txt += `
`; 573 | } 574 | txt += '
'; 575 | } 576 | cardsDiv.innerHTML = txt; 577 | } 578 | 579 | function toggleContrast() { 580 | highContrast = !highContrast; 581 | window.localStorage.setItem('hc', highContrast?1:0); 582 | if(highContrast) { 583 | highContrastDiv.innerText = 'X'; 584 | } 585 | else { 586 | highContrastDiv.innerHTML = ' '; 587 | } 588 | 589 | redrawCards(); 590 | redrawPlayfieldHTML(); 591 | } 592 | 593 | document.getElementById('highContrastBtn').addEventListener('click', toggleContrast); 594 | 595 | function jokerString(i, j, modifiers) { 596 | let jmodifierClass = ''; 597 | 598 | let jmodifierString = 'url(assets/Jokers.png) 0px -855px, '; 599 | let jmodifierPostString = ''; 600 | 601 | if(modifiers.foil) { 602 | jmodifierPostString = 'url(assets/Editions.png) -71px 0, '; 603 | } 604 | else if(modifiers.holographic) { 605 | jmodifierPostString = 'url(assets/Editions.png) -142px 0, '; 606 | } 607 | else if(modifiers.polychrome) { 608 | jmodifierClass = ' polychrome'; 609 | jmodifierPostString = 'url(assets/Editions.png) -213px 0, '; 610 | } 611 | else if(modifiers.disabled) { 612 | jmodifierPostString = 'url(assets/Editions.png) 71px 0, '; 613 | } 614 | else { 615 | jmodifierPostString = ''; 616 | } 617 | 618 | switch(`${i},${j}`) { 619 | case '8,3': jmodifierString = `url(assets/Jokers.png) -${71*3}px -${95*9}px, `; break; 620 | case '8,4': jmodifierString = `url(assets/Jokers.png) -${71*4}px -${95*9}px, `; break; 621 | case '8,5': jmodifierString = `url(assets/Jokers.png) -${71*5}px -${95*9}px, `; break; 622 | case '8,6': jmodifierString = `url(assets/Jokers.png) -${71*6}px -${95*9}px, `; break; 623 | case '8,7': jmodifierString = `url(assets/Jokers.png) -${71*7}px -${95*9}px, `; break; 624 | case '12,4': jmodifierString = `url(assets/Jokers.png) -${71*2}px -${95*9}px, `; break; 625 | } 626 | return `${jmodifierClass}" style="mask-position: -${71*j}px -${95*i}px; background: ${jmodifierPostString}${jmodifierString}url(assets/Jokers.png) -${71*j}px -${95*i}px"`; 627 | } 628 | 629 | function jredrawCards() { 630 | let txt = '
'; 631 | let count = 0; 632 | for(let i = 0; i < 16; i++) { 633 | if(i === 9) {i++;} 634 | for(let j = 0; j < 10; j++) { 635 | const title = (jokerTexts.length > i && jokerTexts[i].length > j) ? jokerTexts[i][j][0] : 'WIP'; 636 | const description = (jokerTexts.length > i && jokerTexts[i].length > j) ? eval('`' + jokerTexts[i][j][1] + '`') : 'WIP'; 637 | if(title.toLowerCase().indexOf(searchVal.toLowerCase()) >= 0 || description.replace(/\<[^\>]+\>/g,'').toLowerCase().indexOf(searchVal.toLowerCase()) >= 0) { 638 | txt += `
` + 639 | `
${title}

` + 640 | `
${description}` + 641 | `
`; 642 | count++; 643 | if(count >= 10) { 644 | count = 0; 645 | txt += '
'; 646 | } 647 | } 648 | } 649 | } 650 | txt += '
'; 651 | jcardsDiv.innerHTML = txt; 652 | } 653 | 654 | redrawCards(); 655 | jredrawCards(); 656 | 657 | const jokerAreaDiv = document.getElementById('jokerArea'); 658 | const bestPlayDiv = document.getElementById('bestPlay'); 659 | const cardsInHandDiv = document.getElementById('cardsInHand'); 660 | 661 | const jokerLimitDiv = document.getElementById('jokerLimit'); 662 | const handLimitDiv = document.getElementById('handLimit'); 663 | 664 | let playfieldCards = {}; 665 | 666 | function updateTooltips() { 667 | for(let joker in playfieldJokers) { 668 | let i = playfieldJokers[joker].type[0]; 669 | let j = playfieldJokers[joker].type[1]; 670 | let jokerValue = playfieldJokers[joker].value; 671 | playfieldJokers[joker].tooltip = (jokerTexts.length > i && jokerTexts[i].length > j) ? [jokerTexts[i][j][0], eval('`' + jokerTexts[i][j][1] + '`')] : ['WIP', 'WIP']; 672 | } 673 | } 674 | 675 | function addJoker(i, j, sell = false) { 676 | for(let k = 0; k < jokerCount; k++) { 677 | let id = 'j'+(Math.random()+'').slice(2); 678 | while(playfieldJokers.hasOwnProperty(id)) { 679 | id = 'j'+(Math.random()+'').slice(2); 680 | } 681 | 682 | playfieldJokers[id] = { 683 | id, 684 | type: [i, j], 685 | modifiers: {...jmodifiers}, 686 | value: jokerValue, 687 | sell: sell !== false ? sell : Math.floor((jokerPrice[i][j] + ((jmodifiers.foil || jmodifiers.holographic || jmodifiers.polychrome) ? 1 : 0)) / 2), 688 | string: jokerString(i, j, jmodifiers), 689 | tooltip: (jokerTexts.length > i && jokerTexts[i].length > j) ? [jokerTexts[i][j][0], eval('`' + jokerTexts[i][j][1] + '`')] : ['WIP', 'WIP'] 690 | }; 691 | } 692 | 693 | jokerLimitDiv.innerText = Object.keys(playfieldJokers).length; 694 | 695 | if(Object.keys(playfieldJokers).length >= 8 && optimizeJokers) { 696 | toggleJoker(); 697 | } 698 | 699 | updateTooltips(); 700 | redrawPlayfield(); 701 | } 702 | 703 | function removeJoker(id) { 704 | delete playfieldJokers[id]; 705 | 706 | jokerLimitDiv.innerText = Object.keys(playfieldJokers).length; 707 | 708 | updateTooltips(); 709 | redrawPlayfield(); 710 | 711 | changeTab(revertToTab)(); 712 | } 713 | 714 | function addCard(i, j) { 715 | for(let k = 0; k < cardCount; k++) { 716 | let id = ((j === 10 && !modifiers.stone) ? (!modifiers.steel ? '993' : '992') : '') + (''+j).padStart(2, 0)+(4-i)+Object.keys(modifiers).map(a=>modifiers[a]?'1':'0').join(''); 717 | while(playfieldCards.hasOwnProperty(id)) { 718 | id += '#'; 719 | } 720 | 721 | playfieldCards[id] = { 722 | id, 723 | type: [(i + 3) % 4, j], 724 | modifiers: {...modifiers}, 725 | string: cardString((i + 3) % 4, j, 1), 726 | HCString: cardString((i + 3) % 4, j, 2), 727 | }; 728 | } 729 | 730 | handLimitDiv.innerText = Object.keys(playfieldCards).length; 731 | 732 | if(Object.keys(playfieldCards).length >= 9 && optimizeJokers) { 733 | toggleCard(); 734 | } 735 | 736 | redrawPlayfield(); 737 | } 738 | 739 | function removeCard(id) { 740 | if(bestHand.indexOf(id) >= 0) { 741 | bestHand.splice(bestHand.indexOf(id), 1); 742 | } 743 | 744 | delete playfieldCards[id]; 745 | 746 | handLimitDiv.innerText = Object.keys(playfieldCards).length; 747 | 748 | redrawPlayfield(); 749 | } 750 | 751 | function redrawPlayfield() { 752 | calculator(); 753 | } 754 | 755 | function redrawPlayfieldHTML() { 756 | compileHand(); 757 | 758 | let txt = ''; 759 | for(let id of bestJokers) { 760 | txt += `
` + 762 | `
X
` + 763 | `` + 764 | `${playfieldJokers[id].tooltip[0]}` + 765 | `${playfieldJokers[id].tooltip[1]}` + 766 | `` + 767 | `
` + 768 | `
` + 769 | `
<
` + 770 | `
>
` + 771 | `
` + 772 | `
` + 773 | `
`; 774 | } 775 | jokerAreaDiv.innerHTML = txt; 776 | 777 | txt = ''; 778 | for(let id of bestHand) { 779 | txt += `
` + 782 | `
` + 783 | `
` + 784 | `
<
` + 785 | `
v
` + 786 | `
>
` + 787 | `
` + 788 | `
`; 789 | } 790 | bestPlayDiv.innerHTML = txt; 791 | 792 | txt = ''; 793 | 794 | let lowestCards = []; 795 | 796 | for(let id of Object.keys(playfieldCards).sort().reverse()) { 797 | if(bestHand.indexOf(id) >= 0) continue; 798 | if(id.indexOf('99') !== 0) continue; 799 | txt += `
` + 800 | `
` + 801 | `
` + 802 | `
^
` + 803 | `
` + 804 | `
`; 805 | } 806 | 807 | // if Raised Fist, move the lowest cards to the left 808 | if(Object.keys(playfieldJokers).reduce((a,b) => a || (playfieldJokers[b].type[0] === 2 && playfieldJokers[b].type[1] === 8 && !playfieldJokers[b].modifiers.disabled), false)) { 809 | let lowest = 100; 810 | let isQueen = true; 811 | let type = 0; 812 | for(let card in playfieldCards) { 813 | if(!playfieldCards[card].modifiers.stone && bestHand.indexOf(card) < 0) { 814 | if(lowest > cardValues[playfieldCards[card].type[1]] + (playfieldCards[card].type[1] === QUEEN ? 10 : 0)) { 815 | isQueen = playfieldCards[card].type[1] === QUEEN; 816 | lowest = cardValues[playfieldCards[card].type[1]] + (isQueen ? 10 : 0); 817 | lowestCards = [card]; 818 | type = playfieldCards[card].type[1]; 819 | } 820 | else if(lowest === cardValues[playfieldCards[card].type[1]]) { 821 | lowestCards.push(card); 822 | } 823 | } 824 | } 825 | 826 | let index = 0; 827 | let highScore = 0; 828 | for(let i = 0; i < lowestCards.length; i++) { 829 | const card = lowestCards[i]; 830 | if(!playfieldCards[card].modifiers.disabled) { 831 | let thisScore = 1; 832 | if(playfieldCards[card].modifiers.steel) { 833 | thisScore += 2; 834 | } 835 | if(playfieldCards[card].modifiers.double) { 836 | thisScore += 4; 837 | } 838 | if(thisScore > highScore) { 839 | highScore = thisScore; 840 | index = i; 841 | } 842 | } 843 | } 844 | 845 | ignoreCard = -1; 846 | 847 | // only add cards if there is a valid lowest card 848 | if(lowest > 0 && lowest < 100 && !isQueen) { 849 | ignoreCard = lowestCards[index]; 850 | 851 | for(let id of Object.keys(playfieldCards).sort().reverse()) { 852 | if(lowestCards.indexOf(id) < 0) continue; 853 | if(id === ignoreCard) continue; 854 | if(id.indexOf('99') === 0) continue; 855 | txt += `
` + 856 | `
` + 857 | `
` + 858 | `
^
` + 859 | `
` + 860 | `
`; 861 | } 862 | 863 | txt += `
` + 864 | `
` + 865 | `
` + 866 | `
^
` + 867 | `
` + 868 | `
`; 869 | } 870 | //console.log(txt); 871 | } 872 | 873 | for(let id of Object.keys(playfieldCards).sort().reverse()) { 874 | if(bestHand.indexOf(id) >= 0) continue; 875 | if(lowestCards.indexOf(id) >= 0) continue; 876 | if(id.indexOf('99') === 0) continue; 877 | txt += `
` + 878 | `
` + 879 | `
` + 880 | `
^
` + 881 | `
` + 882 | `
`; 883 | } 884 | cardsInHandDiv.innerHTML = txt; 885 | } 886 | 887 | function moveJokerLeft(id) { 888 | if(optimizeJokers) toggleJoker(); 889 | const index = bestJokers.indexOf(id); 890 | if(index > 0) { 891 | bestJokers.splice(index, 1); 892 | bestJokers.splice(index - 1, 0, id); 893 | } 894 | let newPlayfield = {}; 895 | for(joker of bestJokers) { 896 | newPlayfield[joker] = playfieldJokers[joker]; 897 | } 898 | playfieldJokers = newPlayfield; 899 | redrawPlayfield(); 900 | } 901 | 902 | function moveJokerRight(id) { 903 | let index = bestJokers.indexOf(id); 904 | if(index < bestJokers.length) { 905 | bestJokers.splice(index, 1); 906 | bestJokers.splice(index + 1, 0, id); 907 | } 908 | let newPlayfield = {}; 909 | for(let i = 0; i < bestJokers.length; i++) { 910 | let joker = bestJokers[i]; 911 | newPlayfield[joker] = playfieldJokers[joker]; 912 | } 913 | playfieldJokers = newPlayfield; 914 | 915 | if(optimizeJokers) { 916 | toggleJoker(); 917 | } 918 | else { 919 | redrawPlayfield(); 920 | } 921 | } 922 | 923 | function moveHandCardLeft(id) { 924 | if(optimizeCards) toggleCard(); 925 | let index = bestHand.indexOf(id); 926 | if(index > 0) { 927 | bestHand.splice(index, 1); 928 | bestHand.splice(index - 1, 0, id); 929 | } 930 | redrawPlayfield(); 931 | } 932 | function moveHandCardRight(id) { 933 | if(optimizeCards) toggleCard(); 934 | let index = bestHand.indexOf(id); 935 | if(index < bestHand.length) { 936 | bestHand.splice(index, 1); 937 | bestHand.splice(index + 1, 0, id); 938 | } 939 | redrawPlayfield(); 940 | } 941 | 942 | function moveHandCardDown(id) { 943 | if(optimizeCards) toggleCard(); 944 | bestHand.splice(bestHand.indexOf(id), 1); 945 | redrawPlayfield(); 946 | } 947 | 948 | function moveCardUp(id) { 949 | if(optimizeCards) toggleCard(); 950 | if(bestHand.length < 5) { 951 | bestHand.push(id); 952 | } 953 | redrawPlayfield(); 954 | } 955 | 956 | const searchDiv = document.getElementById('SearchVal'); 957 | 958 | function searchJoker() { 959 | searchVal = searchDiv.value; 960 | jredrawCards(); 961 | } 962 | 963 | let modifyTab = changeTab(4); 964 | 965 | function modifyJoker(id) { 966 | modifyTab(); 967 | modifyingJoker = id; 968 | modifyingJokerValDiv.innerText = playfieldJokers[modifyingJoker].value; 969 | modifyingJokerSellValDiv.innerText = playfieldJokers[modifyingJoker].sell; 970 | 971 | const type = playfieldJokers[modifyingJoker].type; 972 | if(jokerTexts[type[0]][type[1]][2]) { 973 | modifyingJokerValueDiv.style.display = 'inline-block'; 974 | modifyingJokerValTxt.innerText = jokerTexts[type[0]][type[1]][2]; 975 | } 976 | else { 977 | modifyingJokerValueDiv.style.display = 'none'; 978 | } 979 | 980 | updateModifyingJoker(); 981 | } 982 | 983 | function updateModifyingJoker() { 984 | if(!playfieldJokers.hasOwnProperty(modifyingJoker)) return; 985 | 986 | modifyJokerDiv.innerHTML = `
308) { 138 | return b; 139 | } 140 | return [ 141 | a / powersOfTen[b[1] + 323] + b[0], 142 | b[1] 143 | ]; 144 | } 145 | 146 | function bigTimes(a, b) { 147 | const tempM = a * b[0]; 148 | 149 | if(tempM > 1e13) { 150 | const tempE = Math.floor(Math.log10(Math.abs(tempM))) + 13; 151 | 152 | return [ 153 | tempM / powersOfTen[tempE + 323], 154 | b[1] + tempE 155 | ]; 156 | } 157 | else { 158 | return [ 159 | tempM, 160 | b[1] 161 | ]; 162 | } 163 | } 164 | 165 | function bigBigAdd(a, b) { 166 | if(b[1] > a[1]) { 167 | const diff = b[1] - a[1]; 168 | if(diff >= 309) { 169 | return b; 170 | } 171 | return [ 172 | a[0] / powersOfTen[diff + 323] + b[0], 173 | b[1] 174 | ]; 175 | } 176 | else { 177 | const diff = a[1] - b[1]; 178 | if(diff >= 309) { 179 | return a; 180 | } 181 | return [ 182 | b[0] / powersOfTen[diff + 323] + a[0], 183 | a[1] 184 | ]; 185 | } 186 | } 187 | 188 | function bigBigTimes(a, b) { 189 | return [ 190 | a[0] * b[0], 191 | a[1] + b[1] 192 | ]; 193 | } 194 | 195 | function normalizeBig(b) { 196 | const tempE = Math.floor(Math.log10(Math.abs(b[0]))); 197 | 198 | return [ 199 | b[0] / powersOfTen[tempE + 323], 200 | b[1] + tempE 201 | ]; 202 | } 203 | 204 | /* 205 | brute-force order of operations: 206 | 207 | - compileJokers() 208 | - all combinations of joker order 209 | - compileJokerOrder() 210 | - all combinations of cards played 211 | - compileCards() 212 | - all combinations of cards played order 213 | - simulate() 214 | */ 215 | 216 | class Hand { 217 | hands = []; 218 | jokersExtraValue = []; 219 | jokerRarities = []; 220 | 221 | randomMode = 0; 222 | chips = 0; 223 | mult = [1, 0]; // big-float 224 | 225 | chanceMultiplier = 1; 226 | 227 | FourFingers = false; // straights/flushes with 4 cards 228 | Shortcut = false; // straights can skip numbers 229 | Pareidolia = false; // all cards are faces 230 | SmearedJoker = false; // hearts = diamonds, clubs = spades 231 | Splash = false; // every card is counted in scoring 232 | 233 | hasVampire = false; 234 | Vampire = false; 235 | BaseballCard = 0; 236 | 237 | RaisedFist = false; 238 | 239 | MidasMaskas = false; 240 | 241 | compiledValues = []; 242 | 243 | handType = 11; 244 | involvedCards = []; 245 | 246 | hasPair = false; 247 | hasTwoPair = false; 248 | hasThreeOfAKind = false; 249 | hasFourOfAKind = false; 250 | hasStraight = false; 251 | hasFlush = false; 252 | 253 | compiledChips = 0; 254 | compiledMult = [1, 0]; 255 | 256 | compiledInHandPlusMult = [0, 0]; 257 | compiledInHandTimesMult = [1, 0]; 258 | cardCast = []; 259 | cardExtraExtraChips = []; 260 | 261 | actualCardsInHand = []; 262 | 263 | constructor({ 264 | cards = [], 265 | cardsInHand = [], 266 | jokers = [], 267 | hands = false, 268 | TheFlint = false, 269 | TheEye = false, 270 | PlasmaDeck = false, 271 | Observatory = false 272 | } = {}) { 273 | this.cards = cards; 274 | this.cardsInHand = cardsInHand; 275 | this.jokers = jokers; 276 | if(hands) { 277 | this.hands = hands; 278 | } 279 | else { 280 | for(let i = 0; i < handChips.length; i++) { 281 | this.hands.push([ 282 | 1, // level 283 | 0, // planets 284 | 0, // played 285 | 0, // played this round 286 | ]); 287 | } 288 | } 289 | this.TheFlint = TheFlint; 290 | this.TheEye = TheEye; 291 | this.PlasmaDeck = PlasmaDeck; 292 | this.Observatory = Observatory; 293 | } 294 | 295 | triggerJoker(joker, j) { 296 | if(joker[JOKER_DISABLED]) return; 297 | 298 | if(joker[EDITION] === FOIL) { 299 | this.chips += 50; 300 | } 301 | else if(joker[EDITION] === HOLOGRAPHIC) { 302 | this.mult = bigAdd(10, this.mult); 303 | } 304 | 305 | switch(joker[JOKER]) { 306 | case 0: 307 | // Joker 308 | this.mult = bigAdd(4, this.mult); 309 | break; 310 | case 2: 311 | // Jolly Joker 312 | if(this.hasPair) { 313 | this.mult = bigAdd(8, this.mult); 314 | } 315 | break; 316 | case 3: 317 | // Zany Joker 318 | if(this.hasThreeOfAKind) { 319 | this.mult = bigAdd(12, this.mult); 320 | } 321 | break; 322 | case 4: 323 | // Mad Joker 324 | if(this.hasTwoPair) { 325 | this.mult = bigAdd(10, this.mult); 326 | } 327 | break; 328 | case 5: 329 | // Crazy Joker 330 | if(this.hasStraight) { 331 | this.mult = bigAdd(12, this.mult); 332 | } 333 | break; 334 | case 6: 335 | // Droll Joker 336 | if(this.hasFlush) { 337 | this.mult = bigAdd(10, this.mult); 338 | } 339 | break; 340 | case 7: 341 | // Half Joker 342 | if(this.cards.length <= 3) { 343 | this.mult = bigAdd(20, this.mult); 344 | } 345 | break; 346 | case 12: 347 | // Acrobat 348 | if(joker[VALUE] !== 0) { 349 | this.mult = bigTimes(3, this.mult); 350 | } 351 | break; 352 | case 22: 353 | // Mystic Summit 354 | if(joker[VALUE] !== 0) { 355 | this.mult = bigAdd(15, this.mult); 356 | } 357 | break; 358 | case 24: 359 | // Loyalty Card 360 | if(joker[VALUE] === 0) { 361 | this.mult = bigTimes(4, this.mult); 362 | } 363 | break; 364 | case 26: 365 | // Misprint 366 | switch(this.randomMode) { 367 | case 0: 368 | this.mult = bigAdd(23, this.mult); 369 | break; 370 | case 2: 371 | this.mult = bigAdd(Math.floor(Math.random() * 24), this.mult); 372 | break; 373 | } 374 | break; 375 | case 27: 376 | // Steel Joker 377 | this.mult = bigTimes(this.compiledValues[j], this.mult); 378 | break; 379 | case 31: 380 | // Glass Joker 381 | this.mult = bigTimes(this.compiledValues[j], this.mult); 382 | break; 383 | case 33: 384 | // Abstract Joker 385 | this.mult = bigAdd(this.jokers.length * 3, this.mult); 386 | break; 387 | case 42: 388 | // Supernova 389 | this.mult = bigAdd(this.hands[this.typeOfHand][PLAYED] + 1, this.mult); 390 | break; 391 | case 44: 392 | // Seeing Double 393 | if(this.compiledValues[j]) { 394 | this.mult = bigTimes(2, this.mult); 395 | } 396 | break; 397 | case 45: 398 | // The Duo 399 | if(this.hasPair) { 400 | this.mult = bigTimes(2, this.mult); 401 | } 402 | break; 403 | case 46: 404 | // The Trio 405 | if(this.hasThreeOfAKind) { 406 | this.mult = bigTimes(3, this.mult); 407 | } 408 | break; 409 | case 47: 410 | // The Family 411 | if(this.hasFourOfAKind) { 412 | this.mult = bigTimes(4, this.mult); 413 | } 414 | break; 415 | case 48: 416 | // The Order 417 | if(this.hasStraight) { 418 | this.mult = bigTimes(3, this.mult); 419 | } 420 | break; 421 | case 49: 422 | // The Tribe 423 | if(this.hasFlush) { 424 | this.mult = bigTimes(2, this.mult); 425 | } 426 | break; 427 | case 52: 428 | // Joker Stencil 429 | this.mult = bigTimes(1 + joker[VALUE], this.mult); 430 | break; 431 | case 55: 432 | // Ceremonial Dagger 433 | this.mult = bigAdd(joker[VALUE], this.mult); 434 | break; 435 | case 57: 436 | // Fortune Teller 437 | this.mult = bigAdd(joker[VALUE], this.mult); 438 | break; 439 | case 58: 440 | // Hit the Road 441 | this.mult = bigTimes(1 + joker[VALUE] * 0.5, this.mult); 442 | break; 443 | case 59: 444 | // Swashbuckler 445 | this.mult = bigAdd(this.compiledValues[j], this.mult); 446 | break; 447 | case 60: 448 | // Flower Pot 449 | if(this.compiledValues[j]) { 450 | this.mult = bigTimes(3, this.mult); 451 | } 452 | break; 453 | case 61: 454 | // Ride the Bus 455 | this.mult = bigAdd(this.compiledValues[j], this.mult); 456 | break; 457 | case 67: 458 | // Gros Michel 459 | this.mult = bigAdd(15, this.mult); 460 | break; 461 | case 70: 462 | // Driver's License 463 | if(joker[VALUE] >= 16) { 464 | this.mult = bigTimes(3, this.mult); 465 | } 466 | break; 467 | case 75: 468 | // Throwback 469 | this.mult = bigTimes(1 + joker[VALUE] * 0.25, this.mult); 470 | break; 471 | case 83: 472 | // Canio 473 | this.mult = bigTimes(1 + joker[VALUE], this.mult); 474 | break; 475 | case 85: 476 | // Yorick 477 | this.mult = bigTimes(joker[VALUE], this.mult); 478 | break; 479 | case 89: 480 | // Bootstraps 481 | this.mult = bigAdd(joker[VALUE] * 2, this.mult); 482 | break; 483 | case 102: 484 | // Blackboard 485 | if(this.compiledValues[j]) { 486 | this.mult = bigTimes(3, this.mult); 487 | } 488 | break; 489 | case 109: 490 | // Constellation 491 | this.mult = bigTimes(1 + joker[VALUE] / 10, this.mult); 492 | break; 493 | case 112: 494 | // Green Joker 495 | this.mult = bigAdd(1 + joker[VALUE], this.mult); 496 | break; 497 | case 115: 498 | // Cavendish 499 | this.mult = bigTimes(3, this.mult); 500 | break; 501 | case 116: 502 | // Card Sharp 503 | if(this.hands[this.typeOfHand][PLAYED_THIS_ROUND]) { 504 | this.mult = bigTimes(3, this.mult); 505 | } 506 | break; 507 | case 117: 508 | // Red Card 509 | this.mult = bigAdd(joker[VALUE] * 3, this.mult); 510 | break; 511 | case 118: 512 | // Madness 513 | this.mult = bigTimes(1 + joker[VALUE] * 0.5, this.mult); 514 | break; 515 | case 122: 516 | // Vampire 517 | this.mult = bigTimes(1 + this.compiledValues[j] / 10, this.mult); 518 | break; 519 | case 124: 520 | // Hologram 521 | this.mult = bigTimes(1 + joker[VALUE] * 0.25, this.mult); 522 | break; 523 | case 129: 524 | // Obelisk 525 | this.mult = bigTimes(this.compiledValues[j], this.mult); 526 | break; 527 | case 135: 528 | // Erosion 529 | this.mult = bigAdd(joker[VALUE] * 4, this.mult); 530 | break; 531 | case 145: 532 | // Lucky Cat 533 | this.mult = bigTimes(1 + (joker[VALUE] + this.jokersExtraValue[j]) / 4, this.mult); 534 | break; 535 | case 147: 536 | // Bull 537 | this.chips += 2 * joker[VALUE] + this.jokersExtraValue[j]; 538 | break; 539 | case 150: 540 | // Flash Card 541 | this.mult = bigAdd(joker[VALUE] * 2, this.mult); 542 | break; 543 | case 151: 544 | // Popcorn 545 | this.mult = bigAdd(20 - joker[VALUE] * 4, this.mult); 546 | break; 547 | case 152: 548 | // Ramen 549 | this.mult = bigTimes(2 - joker[VALUE] / 100, this.mult); 550 | break; 551 | case 154: 552 | // Spare Trousers 553 | if(this.hasTwoPair) { 554 | this.compiledValues[j] = 1; 555 | this.mult = bigAdd(2 + joker[VALUE] * 2, this.mult); 556 | } 557 | else { 558 | this.mult = bigAdd(joker[VALUE] * 2, this.mult); 559 | } 560 | break; 561 | case 155: 562 | // Campfire 563 | this.mult = bigTimes(1 + joker[VALUE] * 0.25, this.mult); 564 | break; 565 | } 566 | 567 | if(joker[EDITION] === POLYCHROME) { 568 | this.mult = bigTimes(1.5, this.mult); 569 | } 570 | } 571 | 572 | triggerCard(card, retrigger = false) { 573 | if(card[CARD_DISABLED]) return; 574 | const notStone = card[ENHANCEMENT] !== STONE; 575 | 576 | if(notStone || this.hasVampire) { 577 | this.chips += cardValues[card[RANK]] + card[EXTRA_CHIPS] + card[EXTRA_EXTRA_CHIPS]; 578 | } 579 | 580 | let luckyMult = 0; 581 | let luckyMoney = 0; 582 | let luckyTriggers = 0; 583 | 584 | if(!this.hasVampire) { 585 | switch(card[ENHANCEMENT]) { 586 | case BOUNS: 587 | this.chips += 30; 588 | break; 589 | case MULT: 590 | this.mult = bigAdd(4, this.mult); 591 | break; 592 | case GLASS: 593 | this.mult = bigTimes(2, this.mult); 594 | break; 595 | case STONE: 596 | this.chips += 50; 597 | break; 598 | case LUCKY: 599 | let triggered = false; 600 | switch(this.randomMode) { 601 | case 0: 602 | this.mult = bigAdd(20, this.mult); 603 | luckyMult++; 604 | luckyMoney++; 605 | luckyTriggers++; 606 | break; 607 | case 1: 608 | if(this.chanceMultiplier >= 5) { 609 | this.mult = bigAdd(20, this.mult); 610 | luckyMult++; 611 | triggered = true; 612 | } 613 | if(this.chanceMultiplier >= 20) { 614 | luckyMoney++; 615 | triggered = true; 616 | } 617 | if(triggered) { 618 | luckyTriggers++; 619 | } 620 | break; 621 | case 2: 622 | if(Math.random() < 0.2 * this.chanceMultiplier) { 623 | this.mult = bigAdd(20, this.mult); 624 | luckyMult++; 625 | triggered = true; 626 | } 627 | if(Math.random() < 1/15 * this.chanceMultiplier) { 628 | luckyMoney++; 629 | triggered = true; 630 | } 631 | if(triggered) { 632 | luckyTriggers++; 633 | } 634 | break; 635 | } 636 | break; 637 | } 638 | } 639 | 640 | switch(card[EDITION]) { 641 | case FOIL: 642 | this.chips += 50; 643 | break; 644 | case HOLOGRAPHIC: 645 | this.mult = bigAdd(10, this.mult); 646 | break; 647 | case POLYCHROME: 648 | this.mult = bigTimes(1.5, this.mult); 649 | break; 650 | } 651 | 652 | const isFace = this.Pareidolia || (notStone && card[RANK] >= JACK && card[RANK] <= KING); 653 | 654 | if(notStone) { 655 | for(let j = 0; j < this.jokers.length; j++) { 656 | const joker = this.jokers[j]; 657 | if(joker[JOKER_DISABLED]) continue; 658 | switch(joker[JOKER]) { 659 | case 16: 660 | // Greedy Joker 661 | if(card[SUIT] === DIAMONDS || (this.SmearedJoker && card[SUIT] === HEARTS)) { 662 | this.mult = bigAdd(3, this.mult); 663 | } 664 | else if(card[SUIT] === true) { 665 | this.mult = bigAdd(3, this.mult); 666 | } 667 | break; 668 | case 17: 669 | // Lusty Joker 670 | if(card[SUIT] === HEARTS || (this.SmearedJoker && card[SUIT] === DIAMONDS)) { 671 | this.mult = bigAdd(3, this.mult); 672 | } 673 | else if(card[SUIT] === true) { 674 | this.mult = bigAdd(3, this.mult); 675 | } 676 | break; 677 | case 18: 678 | // Wrathful Joker 679 | if(card[SUIT] === SPADES || (this.SmearedJoker && card[SUIT] === CLUBS)) { 680 | this.mult = bigAdd(3, this.mult); 681 | } 682 | else if(card[SUIT] === true) { 683 | this.mult = bigAdd(3, this.mult); 684 | } 685 | break; 686 | case 19: 687 | // Gluttonous Joker 688 | if(card[SUIT] === CLUBS || (this.SmearedJoker && card[SUIT] === SPADES)) { 689 | this.mult = bigAdd(3, this.mult); 690 | } 691 | else if(card[SUIT] === true) { 692 | this.mult = bigAdd(3, this.mult); 693 | } 694 | break; 695 | case 32: 696 | // Scary Face 697 | if(isFace) { 698 | this.chips += 30; 699 | } 700 | break; 701 | case 38: 702 | // Even Steven 703 | if(card[RANK] % 2 === 0 && card[RANK] <= _10) { 704 | this.mult = bigAdd(4, this.mult); 705 | } 706 | break; 707 | case 39: 708 | // Odd Todd 709 | if((card[RANK] % 2 === 1 && card[RANK] <= _9) || card[RANK] === ACE) { 710 | this.chips += 31; 711 | } 712 | break; 713 | case 40: 714 | // Wee Joker 715 | if(card[RANK] === _2) { 716 | this.chips += 8; 717 | } 718 | break; 719 | case 51: 720 | // Fibonacci 721 | if(card[RANK] === ACE || card[RANK] === _8 || card[RANK] === _5 || card[RANK] === _3 || card[RANK] === _2) { 722 | this.mult = bigAdd(8, this.mult); 723 | } 724 | break; 725 | case 63: 726 | // Scholar 727 | if(card[RANK] === ACE) { 728 | this.chips += 20; 729 | this.mult = bigAdd(4, this.mult); 730 | } 731 | break; 732 | case 76: 733 | // The Idol 734 | if(card[SUIT] === Math.abs(joker[VALUE]) % 4 && card[RANK] === Math.floor(Math.abs(joker[VALUE]) / 4) % 13) { 735 | this.mult = bigTimes(2, this.mult); 736 | } 737 | break; 738 | case 80: 739 | // Bloodstone 740 | if(card[SUIT] === HEARTS || (this.SmearedJoker && card[SUIT] === DIAMONDS)) { 741 | switch(this.randomMode) { 742 | case 0: 743 | this.mult = bigTimes(1.5, this.mult); 744 | break; 745 | case 1: 746 | if(this.chanceMultiplier >= 2) { 747 | this.mult = bigTimes(1.5, this.mult); 748 | } 749 | break; 750 | case 2: 751 | if(Math.random() < 1/2 * this.chanceMultiplier) { 752 | this.mult = bigTimes(1.5, this.mult); 753 | } 754 | break; 755 | } 756 | } 757 | else if(card[SUIT] === true) { 758 | switch(this.randomMode) { 759 | case 0: 760 | this.mult = bigTimes(1.5, this.mult); 761 | break; 762 | case 1: 763 | if(this.chanceMultiplier >= 2) { 764 | this.mult = bigTimes(1.5, this.mult); 765 | } 766 | break; 767 | case 2: 768 | if(Math.random() < 1/2 * this.chanceMultiplier) { 769 | this.mult = bigTimes(1.5, this.mult); 770 | } 771 | break; 772 | } 773 | } 774 | break; 775 | case 81: 776 | // Arrowhead 777 | if(card[SUIT] === SPADES || (this.SmearedJoker && card[SUIT] === CLUBS)) { 778 | this.chips += 50; 779 | } 780 | else if(card[SUIT] === true) { 781 | this.chips += 50; 782 | } 783 | break; 784 | case 82: 785 | // Onyx Agate 786 | if(card[SUIT] === CLUBS || (this.SmearedJoker && card[SUIT] === SPADES)) { 787 | this.mult = bigAdd(7, this.mult); 788 | } 789 | else if(card[SUIT] === true) { 790 | this.mult = bigAdd(7, this.mult); 791 | } 792 | break; 793 | case 84: 794 | // Triboulet 795 | if(card[RANK] === KING || card[RANK] === QUEEN) { 796 | this.mult = bigTimes(2, this.mult); 797 | } 798 | break; 799 | case 110: 800 | // Hiker 801 | card[EXTRA_EXTRA_CHIPS] += 5; 802 | break; 803 | case 132: 804 | // Photograph 805 | if(isFace && (this.jokersExtraValue[j] === card || this.jokersExtraValue[j] === 0)) { 806 | this.jokersExtraValue[j] = card; 807 | this.mult = bigTimes(2, this.mult); 808 | } 809 | break; 810 | case 145: 811 | // Lucky Cat 812 | this.jokersExtraValue[j] += luckyTriggers; 813 | break; 814 | case 147: 815 | // Bull 816 | this.jokersExtraValue[j] += luckyMoney * 40; 817 | break; 818 | case 156: 819 | // Smiley Face 820 | if(isFace) { 821 | this.mult = bigAdd(5, this.mult); 822 | } 823 | break; 824 | case 157: 825 | // Ancient Joker 826 | if(card[ENHANCEMENT] === WILD || (this.SmearedJoker ? card[SUIT] % 2 === Math.abs(joker[VALUE]) % 2 : card[SUIT] === Math.abs(joker[VALUE]) % 4)) { 827 | this.mult = bigTimes(1.5, this.mult); 828 | } 829 | break; 830 | case 158: 831 | // Walkie Talkie 832 | if(card[RANK] === _4 || card[RANK] === _10) { 833 | this.chips += 10; 834 | this.mult = bigAdd(4, this.mult); 835 | } 836 | break; 837 | } 838 | } 839 | } 840 | else if(isFace) { 841 | for(let j = 0; j < this.jokers.length; j++) { 842 | const joker = this.jokers[j]; 843 | if(joker[JOKER_DISABLED]) continue; 844 | switch(joker[JOKER]) { 845 | case 32: 846 | // Scary Face 847 | this.chips += 30; 848 | break; 849 | case 110: 850 | // Hiker 851 | card[EXTRA_EXTRA_CHIPS] += 5; 852 | break; 853 | case 132: 854 | // Photograph 855 | if(this.jokersExtraValue[j] === card || this.jokersExtraValue[j] === 0) { 856 | this.jokersExtraValue[j] = card; 857 | this.mult = bigTimes(2, this.mult); 858 | } 859 | break; 860 | case 156: 861 | // Smiley Face 862 | this.mult = bigAdd(5, this.mult); 863 | break; 864 | } 865 | } 866 | } 867 | else { 868 | for(let j = 0; j < this.jokers.length; j++) { 869 | const joker = this.jokers[j]; 870 | if(joker[JOKER_DISABLED]) continue; 871 | switch(joker[JOKER]) { 872 | case 110: 873 | // Hiker 874 | card[EXTRA_EXTRA_CHIPS] += 4; 875 | break; 876 | } 877 | } 878 | } 879 | 880 | // retriggers 881 | if(!retrigger) { 882 | if(card[SEAL] === RED_SEAL) { 883 | this.triggerCard(card, true); 884 | } 885 | 886 | for(let j = 0; j < this.jokers.length; j++) { 887 | const joker = this.jokers[j]; 888 | if(joker[JOKER_DISABLED]) continue; 889 | switch(joker[JOKER]) { 890 | case 13: 891 | // Sock and Buskin 892 | if(isFace) { 893 | this.triggerCard(card, true); 894 | } 895 | break; 896 | case 25: 897 | // Hack 898 | if(card[RANK] <= _5) { 899 | this.triggerCard(card, true); 900 | } 901 | break; 902 | case 69: 903 | // Hanging Chad 904 | if(this.jokersExtraValue[j] === 0) { 905 | this.jokersExtraValue[j]++; 906 | this.triggerCard(card, true); 907 | this.triggerCard(card, true); 908 | } 909 | break; 910 | case 74: 911 | // Dusk 912 | if(joker[VALUE] !== 0) { 913 | this.triggerCard(card, true); 914 | } 915 | break; 916 | case 153: 917 | // Seltzer 918 | this.triggerCard(card, true); 919 | break; 920 | } 921 | } 922 | } 923 | } 924 | 925 | triggerCardInHand(card, retrigger = false) { 926 | if(card[CARD_DISABLED]) return; 927 | 928 | // apply steel cards 929 | if(card[ENHANCEMENT] === STEEL && !card[CARD_DISABLED]) { 930 | this.compiledInHandPlusMult = bigTimes(1.5, this.compiledInHandPlusMult); 931 | this.compiledInHandTimesMult = bigTimes(1.5, this.compiledInHandTimesMult); 932 | } 933 | 934 | for(let j = 0; j < this.jokers.length; j++) { 935 | const joker = this.jokers[j]; 936 | if(joker[JOKER_DISABLED]) continue; 937 | switch (joker[JOKER]) { 938 | case 28: 939 | // Raised Fist 940 | if(card === this.compiledValues[j] && card[ENHANCEMENT] !== STONE) { 941 | this.compiledInHandPlusMult = bigAdd(2 * (card[RANK] === ACE ? 11 : Math.min(10, card[RANK] + 2)), this.compiledInHandPlusMult); 942 | } 943 | break; 944 | case 62: 945 | // Shoot the Moon 946 | if(card[RANK] === QUEEN && card[ENHANCEMENT] !== STONE) { 947 | this.compiledInHandPlusMult = bigAdd(13, this.compiledInHandPlusMult); 948 | } 949 | break; 950 | case 126: 951 | // Baron 952 | if(card[RANK] === KING && card[ENHANCEMENT] !== STONE) { 953 | this.compiledInHandPlusMult = bigTimes(1.5, this.compiledInHandPlusMult); 954 | this.compiledInHandTimesMult = bigTimes(1.5, this.compiledInHandTimesMult); 955 | } 956 | break; 957 | } 958 | } 959 | 960 | // retriggers 961 | if(!retrigger) { 962 | if(card[SEAL] === RED_SEAL) { 963 | this.triggerCardInHand(card, true); 964 | } 965 | 966 | for(let j = 0; j < this.jokers.length; j++) { 967 | const joker = this.jokers[j]; 968 | if(joker[JOKER_DISABLED]) continue; 969 | switch(joker[JOKER]) { 970 | case 14: 971 | this.triggerCardInHand(card, true); 972 | break; 973 | } 974 | } 975 | } 976 | } 977 | 978 | getTypeOfHand() { 979 | // ES2019 apparently mandates stable sort 980 | const sortedCards = this.cards.filter(a => a[ENHANCEMENT] !== STONE).sort((a, b) => b[RANK] - a[RANK]); 981 | 982 | let flush = []; 983 | 984 | let straight = false; 985 | 986 | if(sortedCards.length >= (this.FourFingers ? 4 : 5)) { 987 | const lines = [ 988 | [sortedCards[0]], 989 | [sortedCards[1]], 990 | ]; 991 | if(this.FourFingers) { 992 | lines.push([sortedCards[2]]); 993 | } 994 | 995 | for(let i = 1; i < sortedCards.length; i++) { 996 | for(let l = 0; l < lines.length; l++) { 997 | const val = lines[l][lines[l].length - 1][RANK]; 998 | if(sortedCards[i][RANK] + 1 === val) { 999 | lines.push([...lines[l], sortedCards[i]]); 1000 | } 1001 | else if(this.Shortcut && sortedCards[i][RANK] + 2 === val) { 1002 | lines.push([...lines[l], sortedCards[i]]); 1003 | } 1004 | } 1005 | } 1006 | 1007 | for(let i = 0; i < sortedCards.length; i++) { 1008 | for(let l = 0; l < lines.length; l++) { 1009 | const val = lines[l][lines[l].length - 1][RANK]; 1010 | if(val === _2 && sortedCards[i][0] === ACE) { 1011 | lines.push([...lines[l], sortedCards[i]]); 1012 | } 1013 | else if(this.Shortcut && val === _3 && sortedCards[i][0] === ACE) { 1014 | lines.push([...lines[l], sortedCards[i]]); 1015 | } 1016 | } 1017 | } 1018 | 1019 | let flushes = [ 1020 | [],[],[],[] 1021 | ]; 1022 | 1023 | for(let c = 0; c < this.cards.length; c++) { 1024 | const card = this.cards[c]; 1025 | for(let i = 0; i < 4; i++) { 1026 | if(card[ENHANCEMENT] !== STONE && (card[ENHANCEMENT] === WILD || (this.SmearedJoker ? card[SUIT] % 2 == i : card[SUIT] === i))) { 1027 | flushes[i].push(card); 1028 | } 1029 | } 1030 | } 1031 | 1032 | for(let i = 0; i < flushes.length; i++) { 1033 | if(flushes[i].length > flush.length) { 1034 | flush = flushes[i]; 1035 | } 1036 | } 1037 | 1038 | if(lines[lines.length - 1].length >= (this.FourFingers ? 4 : 5)) { 1039 | straight = lines[lines.length - 1]; 1040 | } 1041 | } 1042 | 1043 | if(flush.length < (this.FourFingers ? 4 : 5)) flush = false; 1044 | 1045 | if(flush) { 1046 | this.hasFlush = true; 1047 | } 1048 | 1049 | if(straight) { 1050 | this.hasStraight = true; 1051 | } 1052 | 1053 | if(sortedCards.length >= 4 && ( 1054 | sortedCards[0][RANK] === sortedCards[3][RANK] || 1055 | (sortedCards.length >= 5 && sortedCards[1][RANK] === sortedCards[4][RANK]) 1056 | )) { 1057 | this.hasPair = true; 1058 | this.hasThreeOfAKind = true; 1059 | this.hasFourOfAKind = true; 1060 | } 1061 | else if(sortedCards.length >= 3 && ( 1062 | (sortedCards[0][RANK] === sortedCards[1][RANK] && sortedCards[1][RANK] === sortedCards[2][RANK]) || 1063 | (sortedCards.length >= 4 && ( 1064 | (sortedCards[1][RANK] === sortedCards[2][RANK] && sortedCards[2][RANK] === sortedCards[3][RANK]) || 1065 | (sortedCards.length >= 5 && sortedCards[2][RANK] === sortedCards[3][RANK] && sortedCards[3][RANK] === sortedCards[4][RANK]) 1066 | )))) { 1067 | this.hasPair = true; 1068 | this.hasThreeOfAKind = true; 1069 | } 1070 | else if(sortedCards.length >= 2 && 1071 | (sortedCards[0][RANK] === sortedCards[1][RANK] || 1072 | (sortedCards.length >= 3 && 1073 | (sortedCards[1][RANK] === sortedCards[2][RANK] || 1074 | (sortedCards.length >= 4 && 1075 | (sortedCards[2][RANK] === sortedCards[3][RANK] || 1076 | (sortedCards.length >= 5 && sortedCards[3][RANK] === sortedCards[4][RANK]) 1077 | )))))) { 1078 | this.hasPair = true; 1079 | } 1080 | 1081 | if(sortedCards.length >= 4) { 1082 | if(sortedCards[0][RANK] === sortedCards[1][RANK]) { 1083 | if(sortedCards[2][RANK] === sortedCards[3][RANK]) { 1084 | this.hasTwoPair = true; 1085 | } 1086 | if(sortedCards.length >= 5 && sortedCards[3][RANK] === sortedCards[4][RANK]) { 1087 | this.hasTwoPair = true; 1088 | } 1089 | } 1090 | if(sortedCards.length >= 5 && sortedCards[1][RANK] === sortedCards[2][RANK] && sortedCards[3][RANK] === sortedCards[4][RANK]) { 1091 | this.hasTwoPair = true; 1092 | } 1093 | } 1094 | 1095 | // flush five 1096 | if(flush && sortedCards.length === 5 && sortedCards[0][RANK] === sortedCards[4][RANK]) { 1097 | return [FLUSH_FIVE, this.cards]; 1098 | } 1099 | 1100 | // flush house 1101 | if(flush && sortedCards.length === 5 && sortedCards[0][RANK] === sortedCards[1][RANK] && sortedCards[3][RANK] === sortedCards[4][RANK] && (sortedCards[1][RANK] === sortedCards[2][RANK] || sortedCards[2][RANK] === sortedCards[3][RANK])) { 1102 | return [FLUSH_HOUSE, this.cards]; 1103 | } 1104 | 1105 | // five of a kind 1106 | if(sortedCards.length === 5 && sortedCards[0][RANK] === sortedCards[4][RANK]) { 1107 | return [FIVE_OF_A_KIND, this.cards]; 1108 | } 1109 | 1110 | // straight flush 1111 | if(straight && flush) { 1112 | return [STRAIGHT_FLUSH, [...new Set([...straight, ...flush])]]; 1113 | } 1114 | 1115 | // four of a kind 1116 | if(sortedCards.length >= 4) { 1117 | if(sortedCards[0][RANK] === sortedCards[3][RANK]) { 1118 | return [FOUR_OF_A_KIND, [ 1119 | sortedCards[0], 1120 | sortedCards[1], 1121 | sortedCards[2], 1122 | sortedCards[3] 1123 | ]]; 1124 | } 1125 | if(sortedCards.length >= 5 && sortedCards[1][RANK] === sortedCards[4][RANK]) { 1126 | return [FOUR_OF_A_KIND, [ 1127 | sortedCards[1], 1128 | sortedCards[2], 1129 | sortedCards[3], 1130 | sortedCards[4] 1131 | ]]; 1132 | } 1133 | } 1134 | 1135 | // full house 1136 | if(sortedCards.length === 5 && sortedCards[0][RANK] === sortedCards[1][RANK] && sortedCards[3][RANK] === sortedCards[4][RANK] && (sortedCards[1][RANK] === sortedCards[2][RANK] || sortedCards[2][RANK] === sortedCards[3][RANK])) { 1137 | return [FULL_HOUSE, this.cards]; 1138 | } 1139 | 1140 | // flush 1141 | if(flush) { 1142 | return [FLUSH, flush]; 1143 | } 1144 | 1145 | // straight 1146 | if(straight) { 1147 | return [STRAIGHT, straight]; 1148 | } 1149 | 1150 | // three of a kind 1151 | if(sortedCards.length >= 3) { 1152 | if(sortedCards[0][RANK] === sortedCards[1][RANK] && sortedCards[1][RANK] === sortedCards[2][RANK]) { 1153 | return [THREE_OF_A_KIND, [sortedCards[0], sortedCards[1], sortedCards[2]]]; 1154 | } 1155 | if(sortedCards.length >= 4) { 1156 | if(sortedCards[1][RANK] === sortedCards[2][RANK] && sortedCards[2][RANK] === sortedCards[3][RANK]) { 1157 | return [THREE_OF_A_KIND, [sortedCards[1], sortedCards[2], sortedCards[3]]]; 1158 | } 1159 | if(sortedCards.length >= 5 && sortedCards[2][RANK] === sortedCards[3][RANK] && sortedCards[3][RANK] === sortedCards[4][RANK]) { 1160 | return [THREE_OF_A_KIND, [sortedCards[2], sortedCards[3], sortedCards[4]]]; 1161 | } 1162 | } 1163 | } 1164 | 1165 | // two pair 1166 | if(sortedCards.length >= 4) { 1167 | if(sortedCards[0][RANK] === sortedCards[1][RANK]) { 1168 | if(sortedCards[2][RANK] === sortedCards[3][RANK]) { 1169 | return [TWO_PAIR, [sortedCards[0], sortedCards[1], sortedCards[2], sortedCards[3]]]; 1170 | } 1171 | if(sortedCards.length >= 5 && sortedCards[3][RANK] === sortedCards[4][RANK]) { 1172 | return [TWO_PAIR, [sortedCards[0], sortedCards[1], sortedCards[3], sortedCards[4]]]; 1173 | } 1174 | } 1175 | if(sortedCards.length >= 5 && sortedCards[1][RANK] === sortedCards[2][RANK] && sortedCards[3][RANK] === sortedCards[4][RANK]) { 1176 | return [TWO_PAIR, [sortedCards[1], sortedCards[2], sortedCards[3], sortedCards[4]]]; 1177 | } 1178 | } 1179 | 1180 | // pair 1181 | if(sortedCards.length >= 2) { 1182 | if(sortedCards[0][RANK] === sortedCards[1][RANK]) { 1183 | return [PAIR, [sortedCards[0], sortedCards[1]]]; 1184 | } 1185 | if(sortedCards.length >= 3) { 1186 | if(sortedCards[1][RANK] === sortedCards[2][RANK]) { 1187 | return [PAIR, [sortedCards[1], sortedCards[2]]]; 1188 | } 1189 | if(sortedCards.length >= 4) { 1190 | if(sortedCards[2][RANK] === sortedCards[3][RANK]) { 1191 | return [PAIR, [sortedCards[2], sortedCards[3]]]; 1192 | } 1193 | if(sortedCards.length >= 5 && sortedCards[3][RANK] === sortedCards[4][RANK]) { 1194 | return [PAIR, [sortedCards[3], sortedCards[4]]]; 1195 | } 1196 | } 1197 | } 1198 | } 1199 | 1200 | // none 1201 | if(sortedCards.length === 0) { 1202 | return [HIGH_CARD, []]; 1203 | } 1204 | 1205 | // high-card 1206 | return [HIGH_CARD, [sortedCards[0]]]; 1207 | } 1208 | 1209 | // knowledge of joker order and cards-played (not their order) 1210 | compileCards() { 1211 | this.hasPair = false; 1212 | this.hasTwoPair = false; 1213 | this.hasThreeOfAKind = false; 1214 | this.hasFourOfAKind = false; 1215 | this.hasStraight = false; 1216 | this.hasFlush = false; 1217 | 1218 | this.Vampire = false; 1219 | 1220 | this.compiledChips = 0; 1221 | this.compiledMult = [1, 0]; 1222 | 1223 | this.compiledInHandPlusMult = [0, 0]; 1224 | this.compiledInHandTimesMult = [1, 0]; 1225 | 1226 | if(this.actualCardsInHand.length === 0) { 1227 | this.actualCardsInHand = this.cardsInHand.slice(); 1228 | } 1229 | else { 1230 | this.cardsInHand = this.actualCardsInHand.slice(); 1231 | } 1232 | 1233 | this.lowestCard = false; 1234 | 1235 | for(let c = 0; c < this.cards.length; c++) { 1236 | if(this.cards[c][ENHANCEMENT] === WILD) { 1237 | this.cards[c][SUIT] = true; 1238 | } 1239 | } 1240 | 1241 | // get hand type 1242 | [this.typeOfHand, this.involvedCards] = this.getTypeOfHand(); 1243 | 1244 | if(this.Splash) { 1245 | this.involvedCards = this.cards; 1246 | } 1247 | 1248 | if(this.typeOfHand >= 0) { 1249 | this.compiledChips = handChips[this.typeOfHand][0] + handChips[this.typeOfHand][2] * (this.hands[this.typeOfHand][LEVEL] - 1); 1250 | this.compiledMult = [handChips[this.typeOfHand][1] + handChips[this.typeOfHand][3] * (this.hands[this.typeOfHand][LEVEL] - 1), 0]; 1251 | 1252 | if(this.TheFlint) { 1253 | this.compiledChips /= 2; 1254 | this.compiledMult[0] /= 2; 1255 | } 1256 | } 1257 | 1258 | // resolve jokers that require knowledge of hand 1259 | for(let j = 0; j < this.jokers.length; j++) { 1260 | const joker = this.jokers[j]; 1261 | if(joker[JOKER_DISABLED]) continue; 1262 | 1263 | const oldCompiledValue = this.compiledValues[j]; 1264 | this.compiledValues[j] = 0; 1265 | 1266 | switch(joker[JOKER]) { 1267 | case 9: 1268 | // Stone Joker 1269 | this.compiledChips += joker[VALUE] * 25; 1270 | if(this.hasVampire) { 1271 | for(let c = 0; c < this.involvedCards.length; c++) { 1272 | if(this.involvedCards[c][ENHANCEMENT] === STONE) { 1273 | this.compiledChips -= 25; 1274 | } 1275 | } 1276 | } 1277 | break; 1278 | case 21: 1279 | // Banner 1280 | this.compiledChips += joker[VALUE] * 30; 1281 | break; 1282 | case 27: 1283 | // Steel Joker 1284 | if(this.hasVampire) { 1285 | let amount = joker[VALUE]; 1286 | for(let c = 0; c < this.involvedCards.length; c++) { 1287 | if(this.involvedCards[c][ENHANCEMENT] === STEEL) { 1288 | amount++; 1289 | } 1290 | } 1291 | this.compiledValues[j] = 1 + (joker[VALUE] - amount) * 0.2; 1292 | } 1293 | else { 1294 | this.compiledValues[j] = 1 + joker[VALUE] * 0.2; 1295 | } 1296 | break; 1297 | case 28: 1298 | // Raised Fist 1299 | // find lowest card 1300 | let lowest = 100; 1301 | let lowestCards = []; 1302 | for(let c = 0; c < this.cardsInHand.length; c++) { 1303 | const card = this.cardsInHand[c]; 1304 | if(card[ENHANCEMENT] !== STONE) { 1305 | if(lowest > card[RANK]) { 1306 | lowest = card[RANK]; 1307 | lowestCards = [card]; 1308 | } 1309 | else if(lowest === card[RANK]) { 1310 | lowestCards.push(card); 1311 | } 1312 | } 1313 | } 1314 | 1315 | if(lowestCards.length > 0) { 1316 | this.compiledValues[j] = lowestCards[lowestCards.length - 1]; 1317 | } 1318 | break; 1319 | case 31: 1320 | // Glass Joker 1321 | if(this.hasVampire) { 1322 | let amount = joker[VALUE]; 1323 | for(let c = 0; c < this.cards.length; c++) { 1324 | if(this.cards[c][ENHANCEMENT] === GLASS) { 1325 | switch(this.randomMode) { 1326 | case 0: 1327 | amount++; 1328 | break; 1329 | case 1: 1330 | if(this.chanceMultiplier >= 4) { 1331 | amount++; 1332 | } 1333 | break; 1334 | case 2: 1335 | if(Math.random() < 0.25 * this.chanceMultiplier) { 1336 | amount++; 1337 | } 1338 | break; 1339 | } 1340 | } 1341 | } 1342 | this.compiledValues[j] = 1 + (joker[VALUE] - amount) * 0.75; 1343 | } 1344 | else { 1345 | this.compiledValues[j] = 1 + joker[VALUE] * 0.75; 1346 | } 1347 | break; 1348 | case 40: 1349 | // Wee Joker 1350 | this.compiledChips += joker[VALUE] * 8; 1351 | break; 1352 | case 44: 1353 | // Seeing Double 1354 | if(this.SmearedJoker) { 1355 | let club = 0; 1356 | let nonClub = 0; 1357 | for(let c = 0; c < this.involvedCards.length; c++) { 1358 | if(this.involvedCards[c][CARD_DISABLED]) continue; 1359 | if(this.involvedCards[c][ENHANCEMENT] === STONE) continue; 1360 | if(this.involvedCards[c][ENHANCEMENT] === WILD || 1361 | this.involvedCards[c][SUIT] === CLUBS || 1362 | this.involvedCards[c][SUIT] === SPADES) { 1363 | club++; 1364 | } 1365 | else { 1366 | nonClub++; 1367 | } 1368 | } 1369 | 1370 | if(club > 0 && (club > 1 || nonClub > 0)) { 1371 | this.compiledValues[j] = true; 1372 | } 1373 | } 1374 | else { 1375 | let club = false; 1376 | let nonClub = false; 1377 | let wildC = 0; 1378 | for(let c = 0; c < this.involvedCards.length; c++) { 1379 | if(this.involvedCards[c][CARD_DISABLED]) continue; 1380 | if(this.involvedCards[c][ENHANCEMENT] === STONE) continue; 1381 | if(this.involvedCards[c][ENHANCEMENT] === WILD) { 1382 | wildC++; 1383 | } 1384 | else if(this.involvedCards[c][SUIT] === CLUBS) { 1385 | club = true; 1386 | } 1387 | else { 1388 | nonClub = true; 1389 | } 1390 | } 1391 | for(let i = 0; i < wildC; i++) { 1392 | if(!club) club = true; 1393 | else nonClub = true; 1394 | } 1395 | 1396 | if(club && nonClub) { 1397 | this.compiledValues[j] = true; 1398 | } 1399 | } 1400 | break; 1401 | case 60: 1402 | // Flower Pot 1403 | let hearts = 0; 1404 | let diamonds = 0; 1405 | let clubs = 0; 1406 | let spades = 0; 1407 | let wild = 0; 1408 | for(let c = 0; c < this.involvedCards.length; c++) { 1409 | if(this.involvedCards[c][CARD_DISABLED] && this.involvedCards[c][ENHANCEMENT] === WILD) continue;// seems to be a bug in balatro itself, remove when fixed? 1410 | if(this.involvedCards[c][ENHANCEMENT] !== STONE) { 1411 | if(this.involvedCards[c][ENHANCEMENT] === WILD && !this.involvedCards[c][CARD_DISABLED]) { 1412 | wild++; 1413 | } 1414 | else if(this.involvedCards[c][SUIT] === HEARTS) { 1415 | hearts = 1; 1416 | } 1417 | else if(this.involvedCards[c][SUIT] === DIAMONDS) { 1418 | diamonds = 1; 1419 | } 1420 | else if(this.involvedCards[c][SUIT] === CLUBS) { 1421 | clubs = 1; 1422 | } 1423 | else if(this.involvedCards[c][SUIT] === SPADES) { 1424 | spades = 1; 1425 | } 1426 | } 1427 | } 1428 | if(hearts + diamonds + clubs + spades + wild >= 4) { 1429 | this.compiledValues[j] = true; 1430 | } 1431 | break; 1432 | case 61: 1433 | // Ride the Bus 1434 | if(!this.Pareidolia) { 1435 | let playedFace = false; 1436 | for(let c = 0; c < this.involvedCards.length; c++) { 1437 | if(this.involvedCards[c][CARD_DISABLED]) continue; 1438 | if(this.involvedCards[c][ENHANCEMENT] !== STONE && this.involvedCards[c][RANK] >= JACK && this.involvedCards[c][RANK] <= KING) { 1439 | playedFace = true; 1440 | break; 1441 | } 1442 | } 1443 | if(!playedFace) { 1444 | this.compiledValues[j] = joker[VALUE] + 1; 1445 | } 1446 | } 1447 | break; 1448 | case 68: 1449 | // Stuntman 1450 | this.compiledChips += 250; 1451 | break; 1452 | case 102: 1453 | // Blackboard 1454 | let allBlack = true; 1455 | for(let c = 0; c < this.cardsInHand.length; c++) { 1456 | if(this.cardsInHand[c][ENHANCEMENT] === STONE || (this.cardsInHand[c][ENHANCEMENT] !== WILD && (this.cardsInHand[c][SUIT] === HEARTS || this.cardsInHand[c][SUIT] === DIAMONDS))) { 1457 | allBlack = false; 1458 | break; 1459 | } 1460 | } 1461 | if(allBlack) { 1462 | this.compiledValues[j] = true; 1463 | } 1464 | break; 1465 | case 103: 1466 | // Runner 1467 | if(this.hasStraight) { 1468 | this.compiledChips += 15 * (joker[VALUE] + 1); 1469 | } 1470 | else { 1471 | this.compiledChips += 15 * joker[VALUE]; 1472 | } 1473 | break; 1474 | case 104: 1475 | // Ice Cream 1476 | this.compiledChips += 100 - 5 * joker[VALUE]; 1477 | break; 1478 | case 105: 1479 | // DNA 1480 | if(this.cards.length === 1) { 1481 | this.cardsInHand.push(this.cards[0]); 1482 | } 1483 | break; 1484 | case 107: 1485 | //Blue joker 1486 | this.compiledChips += 104 + 2 * joker[VALUE]; 1487 | break; 1488 | case 119: 1489 | // Square Joker 1490 | if(this.cards.length === 4) { 1491 | this.compiledChips += 4 * (joker[VALUE] + 1); 1492 | } 1493 | else { 1494 | this.compiledChips += 4 * joker[VALUE]; 1495 | } 1496 | break; 1497 | case 122: 1498 | // Vampire 1499 | this.compiledValues[j] = joker[VALUE]; 1500 | if(this.Vampire) { 1501 | break; 1502 | } 1503 | this.Vampire = true; 1504 | for(let c = 0; c < this.involvedCards.length; c++) { 1505 | const card = this.involvedCards[c]; 1506 | if((this.MidasMaskas && card[RANK] >= JACK && card[RANK] <= KING) || (this.MidasMaskas && this.Pareidolia)) { 1507 | this.compiledValues[j]++; 1508 | } 1509 | else { 1510 | if(card[ENHANCEMENT] > 0) { 1511 | this.compiledValues[j]++; 1512 | } 1513 | } 1514 | } 1515 | break; 1516 | case 129: 1517 | // Obelisk 1518 | let mostPlayed = 0; 1519 | for(let h = 0; h < this.hands.length; h++) { 1520 | if(this.hands[h][PLAYED] > mostPlayed) { 1521 | mostPlayed = this.hands[h][PLAYED]; 1522 | } 1523 | } 1524 | if(this.hands[this.typeOfHand][PLAYED] === mostPlayed) { 1525 | this.compiledValues[j] = 1; 1526 | } 1527 | else { 1528 | this.compiledValues[j] = 1 + (joker[VALUE] + 1) / 5; 1529 | } 1530 | break; 1531 | case 140: 1532 | // Sly Joker 1533 | if(this.hasPair) { 1534 | this.compiledChips += 50; 1535 | } 1536 | break; 1537 | case 141: 1538 | // Wily Joker 1539 | if(this.hasThreeOfAKind) { 1540 | this.compiledChips += 100; 1541 | } 1542 | break; 1543 | case 142: 1544 | // Clever Joker 1545 | if(this.hasTwoPair && !this.hasFourOfAKind) { 1546 | this.compiledChips += 80; 1547 | } 1548 | break; 1549 | case 143: 1550 | // Devious Joker 1551 | if(this.hasStraight) { 1552 | this.compiledChips += 100; 1553 | } 1554 | break; 1555 | case 144: 1556 | // Crafty Joker 1557 | if(this.hasFlush) { 1558 | this.compiledChips += 80; 1559 | } 1560 | break; 1561 | case 159: 1562 | // Castle 1563 | this.compiledChips += joker[VALUE] * 3; 1564 | break; 1565 | default: 1566 | this.compiledValues[j] = oldCompiledValue; 1567 | } 1568 | } 1569 | 1570 | // Blueprint or Brainstorm compiled value 1571 | for(let j = 0; j < this.actualJokers.length; j++) { 1572 | const joker = this.actualJokers[j]; 1573 | if(joker[JOKER_DISABLED]) continue; 1574 | 1575 | switch(joker[JOKER]) { 1576 | case 30: 1577 | case 77: 1578 | // Blueprint 1579 | this.compiledValues[j] = this.compiledValues[this.cardCast[j]]; 1580 | break; 1581 | } 1582 | } 1583 | 1584 | // trigger cards-in-hand 1585 | for(let c = 0; c < this.cardsInHand.length; c++) { 1586 | this.triggerCardInHand(this.cardsInHand[c]); 1587 | } 1588 | } 1589 | 1590 | // knowledge of joker order, not cards-played 1591 | compileJokerOrder() { 1592 | this.jokerRarities = []; 1593 | this.compiledValues = []; 1594 | this.cardCast = []; 1595 | 1596 | this.BaseballCard = 0; 1597 | 1598 | this.actualJokers = this.jokers.map(a => a.slice()); 1599 | 1600 | // resolve jokers that are card but not joker order agnostic 1601 | for(let j = 0; j < this.jokers.length; j++) { 1602 | const joker = this.jokers[j]; 1603 | 1604 | this.compiledValues.push(0); 1605 | this.jokerRarities.push(jokerRarity[this.actualJokers[j][JOKER]]); 1606 | 1607 | if(joker[JOKER_DISABLED]) continue; 1608 | 1609 | let resolved = false; 1610 | switch(joker[JOKER]) { 1611 | case 30: 1612 | // resolve Blueprint 1613 | if(j + 1 < this.jokers.length) { 1614 | let at = j + 1; 1615 | let t = 0; 1616 | while(!resolved && t++ < this.jokers.length) { 1617 | switch(this.jokers[at][JOKER]) { 1618 | case 30: 1619 | if(at + 1 < this.jokers.length) { 1620 | at++; 1621 | } 1622 | break; 1623 | case 77: 1624 | if(at !== 0 && this.jokers[0][0] !== 77) { 1625 | at = 0; 1626 | } 1627 | break; 1628 | default: 1629 | resolved = true; 1630 | } 1631 | } 1632 | if(resolved) { 1633 | this.jokers[j][JOKER] = this.jokers[at][JOKER]; 1634 | this.jokers[j][VALUE] = this.jokers[at][VALUE]; 1635 | this.jokers[j][JOKER_DISABLED] = this.jokers[at][JOKER_DISABLED]; 1636 | this.cardCast[j] = at; 1637 | j--; 1638 | this.compiledValues.pop(); 1639 | this.jokerRarities.pop(); 1640 | } 1641 | } 1642 | break; 1643 | case 59: 1644 | // resolve Swashbuckler 1645 | for(let k = 0; k < this.jokers.length; k++) { 1646 | if(k == j){ 1647 | continue; 1648 | } 1649 | this.compiledValues[j] += this.jokers[k][SELL_VALUE]; 1650 | } 1651 | break; 1652 | case 77: 1653 | // resolve Brainstorm 1654 | if(j > 0 && this.jokers[0][0] !== 77) { 1655 | let at = 0; 1656 | let t = 0; 1657 | while(!resolved && t++ < this.jokers.length) { 1658 | switch(this.jokers[at][JOKER]) { 1659 | case 30: 1660 | if(at + 1 < this.jokers.length) { 1661 | at++; 1662 | } 1663 | break; 1664 | case 77: 1665 | if(at !== 0 && this.jokers[0][0] !== 77) { 1666 | at = 0; 1667 | } 1668 | break; 1669 | default: 1670 | resolved = true; 1671 | } 1672 | } 1673 | if(resolved) { 1674 | this.jokers[j][JOKER] = this.jokers[at][JOKER]; 1675 | this.jokers[j][VALUE] = this.jokers[at][VALUE]; 1676 | this.jokers[j][JOKER_DISABLED] = this.jokers[at][JOKER_DISABLED]; 1677 | this.cardCast[j] = at; 1678 | j--; 1679 | this.compiledValues.pop(); 1680 | this.jokerRarities.pop(); 1681 | } 1682 | } 1683 | break; 1684 | case 146: 1685 | // Baseball Card 1686 | this.BaseballCard++; 1687 | break; 1688 | } 1689 | } 1690 | } 1691 | 1692 | // knowledge of jokers, not joker order or cards-played 1693 | compileJokers() { 1694 | // set standard values; probability modifier (Oops! All 6s), vampire, etc. 1695 | 1696 | // set global variables' default values 1697 | this.actualCardsInHand = []; 1698 | 1699 | this.chanceMultiplier = 1; 1700 | 1701 | this.FourFingers = false; 1702 | this.Shortcut = false; 1703 | this.Pareidolia = false; 1704 | this.SmearedJoker = false; 1705 | 1706 | this.RaisedFist = false; 1707 | this.Splash = false; 1708 | this.MidasMaskas = false; 1709 | 1710 | this.hasVampire = false; 1711 | 1712 | // resolve joker global effects that are card and joker order agnostic 1713 | for(let j = 0; j < this.jokers.length; j++) { 1714 | const joker = this.jokers[j]; 1715 | if(joker[JOKER_DISABLED]) continue; 1716 | switch(joker[JOKER]) { 1717 | case 28: 1718 | this.RaisedFist = true; 1719 | break; 1720 | case 36: 1721 | this.Pareidolia = true; 1722 | break; 1723 | case 64: 1724 | this.SmearedJoker = true; 1725 | break; 1726 | case 65: 1727 | this.chanceMultiplier *= 2; 1728 | break; 1729 | case 66: 1730 | this.FourFingers = true; 1731 | break; 1732 | case 106: 1733 | this.Splash = true; 1734 | break; 1735 | case 122: 1736 | this.hasVampire = true; 1737 | break; 1738 | case 123: 1739 | this.Shortcut = true; 1740 | break; 1741 | case 130: 1742 | this.MidasMaskas = true; 1743 | break; 1744 | } 1745 | } 1746 | } 1747 | 1748 | compileAll() { 1749 | this.compileJokers(); 1750 | this.compileJokerOrder(); 1751 | this.compileCards(); 1752 | } 1753 | 1754 | // simulate what wasn't compiled 1755 | simulate() { 1756 | this.chips = this.compiledChips; 1757 | this.mult = [this.compiledMult[0], this.compiledMult[1]]; 1758 | this.jokersExtraValue = []; 1759 | 1760 | if(this.TheEye && this.hands[this.typeOfHand][PLAYED_THIS_ROUND]) { 1761 | return [1, -10, 0, [0,0]]; 1762 | } 1763 | 1764 | for(let j = 0; j < this.jokers.length; j++) { 1765 | this.jokersExtraValue.push(0); 1766 | } 1767 | 1768 | // score cards 1769 | for(let c = 0; c < this.cards.length; c++) { 1770 | const card = this.cards[c]; 1771 | card[EXTRA_EXTRA_CHIPS] = 0; 1772 | 1773 | if(card[ENHANCEMENT] === STONE || this.involvedCards.indexOf(card) >= 0) { 1774 | this.triggerCard(card); 1775 | } 1776 | else { 1777 | for(let j = 0; j < this.jokers.length; j++) { 1778 | const joker = this.jokers[j]; 1779 | if(joker[JOKER_DISABLED]) continue; 1780 | switch(joker[JOKER]) { 1781 | // Hanging Chad 1782 | case 69: 1783 | this.jokersExtraValue[j]++; 1784 | break; 1785 | } 1786 | } 1787 | } 1788 | } 1789 | 1790 | // score cards-in-hand 1791 | this.mult = bigBigTimes(this.compiledInHandTimesMult, this.mult); 1792 | this.mult = bigBigAdd(this.compiledInHandPlusMult, this.mult); 1793 | 1794 | // score jokers 1795 | for(let j = 0; j < this.jokers.length; j++) { 1796 | this.triggerJoker(this.jokers[j], j); 1797 | 1798 | if(this.BaseballCard && this.jokerRarities[j] === 2 && !this.jokers[j][JOKER_DISABLED]) { 1799 | for(let i = 0; i < this.BaseballCard; i++) { 1800 | this.mult = bigTimes(1.5, this.mult); 1801 | } 1802 | } 1803 | } 1804 | 1805 | // Observatory 1806 | if(this.Observatory) { 1807 | const ObservatoryScore = this.hands[this.typeOfHand][PLANETS]; 1808 | this.mult = bigTimes(1.5 ** ObservatoryScore, this.mult); 1809 | } 1810 | 1811 | if(this.PlasmaDeck) { 1812 | this.mult = bigAdd(this.chips, this.mult); 1813 | this.mult = bigTimes(0.5, this.mult); 1814 | 1815 | if(this.mult[1] === 0) { 1816 | this.mult[0] = Math.floor(this.mult[0]); 1817 | } 1818 | return [...normalizeBig(bigBigTimes(this.mult, this.mult)), normalizeBig(this.mult), normalizeBig(this.mult)]; 1819 | } 1820 | 1821 | return [...normalizeBig(bigTimes(this.chips, this.mult)), this.chips, normalizeBig(this.mult)]; 1822 | } 1823 | 1824 | simulateBestHand() { 1825 | this.randomMode = 0; 1826 | return this.simulate(); 1827 | } 1828 | 1829 | simulateWorstHand() { 1830 | this.randomMode = 1; 1831 | return this.simulate(); 1832 | } 1833 | 1834 | simulateRandomHand() { 1835 | this.randomMode = 2; 1836 | return this.simulate(); 1837 | } 1838 | }; 1839 | --------------------------------------------------------------------------------