├── .gitignore ├── LICENSE.txt ├── README.md ├── assets ├── css │ └── main.css ├── game.js ├── index.js ├── js │ ├── 2048.js │ ├── ai.js │ ├── ai.worker.js │ └── gui.js ├── package.json ├── postcss.config.js ├── webpack.config.js └── yarn.lock ├── browserconfig.xml ├── demo-image-source.pxd ├── QuickLook │ ├── Icon.tiff │ └── Thumbnail.tiff ├── data │ ├── 2B1B0325-8ECF-45B9-BC3F-0559F8C8F53A │ └── originalImportedContentDocumentInfo └── metadata.info ├── demo-image.png ├── favicon.ico ├── favicon.png ├── favicon ├── android-icon-144x144.png ├── android-icon-192x192.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── apple-icon-precomposed.png ├── apple-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png └── ms-icon-70x70.png ├── game.js ├── index.html ├── main.css ├── main.js ├── main.worker.js ├── manifest.json └── performance-analysis ├── TrialMiscData.xlsx ├── data ├── main_data.json ├── movesmade.xlsx ├── trial_misc_data.csv ├── trial_possible_moves.csv └── trials │ ├── 1.csv │ ├── 10.csv │ ├── 11.csv │ ├── 12.csv │ ├── 13.csv │ ├── 14.csv │ ├── 15.csv │ ├── 16.csv │ ├── 17.csv │ ├── 18.csv │ ├── 19.csv │ ├── 2.csv │ ├── 20.csv │ ├── 21.csv │ ├── 22.csv │ ├── 23.csv │ ├── 24.csv │ ├── 25.csv │ ├── 26.csv │ ├── 27.csv │ ├── 28.csv │ ├── 29.csv │ ├── 3.csv │ ├── 30.csv │ ├── 31.csv │ ├── 32.csv │ ├── 33.csv │ ├── 34.csv │ ├── 35.csv │ ├── 36.csv │ ├── 37.csv │ ├── 38.csv │ ├── 39.csv │ ├── 4.csv │ ├── 40.csv │ ├── 41.csv │ ├── 42.csv │ ├── 43.csv │ ├── 44.csv │ ├── 45.csv │ ├── 46.csv │ ├── 47.csv │ ├── 48.csv │ ├── 49.csv │ ├── 5.csv │ ├── 50.csv │ ├── 6.csv │ ├── 7.csv │ ├── 8.csv │ └── 9.csv ├── data_generator_programs ├── export.py ├── export_amount_of_moves_data.py ├── export_extra_data.py ├── get_top_tile_data.py └── get_total_time.py └── spreadsheets ├── TrialMiscData.xlsx ├── movesmade.xlsx └── trial50.xlsx /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | assets/node_modules/ 3 | .htaccess -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gabriel Romualdo 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Jupiter

2 |

a Monte-Carlo based AI to beat 2048

3 | 4 | ## Description 5 | 6 | Jupiter an AI that uses a [Monte Carlo Tree Search](https://en.wikipedia.org/wiki/Monte_Carlo_tree_search) (MCTS) algorithm to beat the popular online game [2048](https://play2048.co/). Jupiter is run on the web, and can consistently win (achieve the 2048 tile) given a sufficient number of simulations, a number which can be easily changed on the site. 7 | 8 | ![Demo Image](demo-image.png) 9 | 10 | 11 | ## Algorithm 12 | 13 | For every position, or **game state**, there are a certain set of possible moves: typically left, right, up, and down. For each possible move (referred to as `N`), the algorithm creates `S` (number of simulations) new games starting at the current game state, and plays `N` (the current possible move) as the first move in each simulation. 14 | 15 | For example, in case where `S = 50`, there would be 200 simulations starting at a particular game state, where 50 play left as the first move, 50 play right, 50 play up, and 50 play down. After playing the first move in each simulation, all simulations play completely random games until the games are over. 16 | 17 | After all simulations are run for the current game state, the algorithm calculates the average final game score for each starting move by averaging all of the `S` simulations for that move. Note: game score is calculated by adding the values of all tiles on the board. 18 | 19 | The algorithm then finds the starting move whose simulations had the highest average score. This is the move to be played in the current game score. 20 | 21 | For example, if the average final score for the move up was 1000, and the average final score for the move down was 1400, then down is, in general, a better move than up, at least in the simulations played. Therefore, the AI would play the move down in this situation, and then the entire process would start over again. 22 | 23 | The number of simulations can be changed in the AI console, allowing users to increase AI performance by increasing the amount of simulations per move. However, as AI performance increases, AI speed decreases as there are more calculations being performed. 24 | 25 | The default number of simulations per move is 50, and with this amount, Jupiter consistently achieves at least a 1024 and 512 tile, with a high (~60%) chance of a 2048 tile. 26 | 27 | Jupiter is written in JavaScript to run on the web, meaning it can run seamlessly on almost all devices and browsers. Jupiter also takes advantage of JavaScript [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), allowing the CPU work in algorithm and Monte Carlo simulations to be divided equally among multiple threads. 28 | 29 | ## Contributing 30 | 31 | If you'd like to add a new feature to Jupiter, make it faster, or even fix a mistake in the documentation, please feel free to contribute and [add pull request](https://github.com/xtrp/jupiter/compare)! I would love to have more contributors to Jupiter. 32 | 33 | If you find a bug, also please feel free to [submit an issue](https://github.com/xtrp/jupiter/issues/new). 34 | 35 | ## License & Credits 36 | 37 | Jupiter was built by web developer and student [Gabriel Romualdo](https://xtrp.io/). 38 | 39 | Jupiter is licensed under the MIT License. Please refer to LICENSE.txt for more information. 40 | -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700'); 2 | @import url('https://fonts.googleapis.com/css?family=Ubuntu+Mono'); 3 | 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | font-family: 'Open Sans', sans-serif; 8 | } 9 | body { 10 | text-align: center; 11 | background-color: #faf8ef; 12 | } 13 | 14 | div.app > h1 { 15 | font-size: 2.75rem; 16 | color: #776e65; 17 | line-height: 1; 18 | margin-bottom: 1rem; 19 | padding-top: 2.5rem; 20 | } 21 | div.app > p { 22 | font-size: 1rem; 23 | color: #776e65; 24 | line-height: 1.25; 25 | padding-bottom: 2.5rem; 26 | opacity: 0.75; 27 | } 28 | @media only screen and (max-width: 550px) { 29 | div.app > h1 { 30 | margin-bottom: 0.65rem; 31 | padding-top: 1.25rem; 32 | font-size: 2rem; 33 | } 34 | div.app > p { 35 | padding-bottom: 1.25rem; 36 | font-size: 0.85rem; 37 | } 38 | } 39 | 40 | div.container { 41 | display: inline-block; 42 | width: 950px; 43 | padding: 1rem; 44 | padding-top: 0; 45 | box-sizing: border-box; 46 | } 47 | @media only screen and (max-width: 950px) { 48 | div.container { 49 | width: 100%; 50 | } 51 | } 52 | 53 | div.milestones { 54 | width: calc(550px - 2rem); 55 | box-sizing: border-box; 56 | margin: 2rem 0; 57 | } 58 | div.milestones h1 { 59 | text-align: center; 60 | display: block; 61 | font-size: 1.5rem; 62 | color: #776e65; 63 | font-weight: 700; 64 | } 65 | div.milestones h1 .tile { 66 | display: inline; 67 | padding: 0.5rem 0.75rem; 68 | border-radius: 5px; 69 | margin: 0 0.75rem; 70 | } 71 | 72 | div.game { 73 | width: calc(550px - 2rem); 74 | height: calc(550px - 2rem); 75 | background-color: #bbada0; 76 | box-sizing: border-box; 77 | padding: 0.5rem; 78 | font-size: 0; 79 | border-radius: 5px; 80 | max-width: 100%; 81 | } 82 | @media only screen and (max-width: 950px) { 83 | div.game { 84 | margin: 0 auto; 85 | } 86 | div.milestones { 87 | width: 100%; 88 | } 89 | } 90 | @media only screen and (max-width: 550px) { 91 | div.game { 92 | width: calc(100vw - 2rem); 93 | height: calc(100vw - 2rem); 94 | } 95 | div.milestones { 96 | zoom: 85%; 97 | } 98 | } 99 | div.game div { 100 | border-radius: 5px; 101 | background-color: rgba(238, 228, 218, 0.35); 102 | margin: 0.5rem; 103 | width: calc(25% - 1rem); 104 | height: calc(25% - 1rem); 105 | display: flex; 106 | align-items: center; 107 | justify-content: center; 108 | float: left; 109 | box-sizing: border-box; 110 | font-weight: 700; 111 | font-size: 48px; 112 | color: #776e65; 113 | text-align: center; 114 | } 115 | @media only screen and (max-width: 520px) { 116 | div.game { 117 | padding: 0.3rem; 118 | } 119 | div.game div { 120 | margin: 0.3rem; 121 | width: calc(25% - 0.6rem); 122 | height: calc(25% - 0.6rem); 123 | } 124 | } 125 | div.num2 { 126 | background: #eee4da !important; 127 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); 128 | } 129 | div.num4 { 130 | background: #ede0c8 !important; 131 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0); 132 | } 133 | div.num8 { 134 | color: #f9f6f2 !important; 135 | background: #f2b179 !important; 136 | } 137 | div.num16 { 138 | color: #f9f6f2 !important; 139 | background: #f59563 !important; 140 | } 141 | div.num32 { 142 | color: #f9f6f2 !important; 143 | background: #f67c5f !important; 144 | } 145 | div.num64 { 146 | color: #f9f6f2 !important; 147 | background: #f65e3b !important; 148 | } 149 | @media screen and (max-width: 520px) { 150 | div.game > div.num2, 151 | div.game > div.num4, 152 | div.game > div.num8, 153 | div.game > div.num16, 154 | div.game > div.num32, 155 | div.game > div.num64 { 156 | font-size: 28px; 157 | } 158 | } 159 | div.num128 { 160 | color: #f9f6f2 !important; 161 | background: #edcf72 !important; 162 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286); 163 | } 164 | div.game > div.num128 { 165 | font-size: 45px; 166 | } 167 | @media screen and (max-width: 520px) { 168 | div.game > div.num128 { 169 | font-size: 25px; 170 | } 171 | } 172 | div.num256 { 173 | color: #f9f6f2 !important; 174 | background: #edcc61 !important; 175 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048); 176 | } 177 | div.game > div.num256 { 178 | font-size: 45px; 179 | } 180 | @media screen and (max-width: 520px) { 181 | div.game > div.num256 { 182 | font-size: 25px; 183 | } 184 | } 185 | div.num512 { 186 | color: #f9f6f2 !important; 187 | background: #edc850 !important; 188 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381); 189 | } 190 | div.game > div.num512 { 191 | font-size: 45px; 192 | } 193 | @media screen and (max-width: 520px) { 194 | div.game > div.num512 { 195 | font-size: 25px; 196 | } 197 | } 198 | div.num1024 { 199 | color: #f9f6f2 !important; 200 | background: #edc53f !important; 201 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571); 202 | } 203 | div.game > div.num1024 { 204 | font-size: 35px; 205 | } 206 | @media screen and (max-width: 520px) { 207 | div.game > div.num1024 { 208 | font-size: 20px; 209 | } 210 | } 211 | div.num2048 { 212 | color: #f9f6f2 !important; 213 | background: #edc22e !important; 214 | box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333); 215 | } 216 | div.game > div.num2048 { 217 | font-size: 35px; 218 | } 219 | @media screen and (max-width: 520px) { 220 | div.game > div.num2048 { 221 | font-size: 20px; 222 | } 223 | } 224 | div.num4096, 225 | div.num8192, 226 | div.num16384, 227 | div.num32768, 228 | div.num65536 { 229 | color: #f9f6f2 !important; 230 | background: #3c3a32 !important; 231 | } 232 | div.game > div.num4096, 233 | div.game > div.num8192, 234 | div.game > div.num16384, 235 | div.game > div.num32768, 236 | div.game > div.num65536 { 237 | font-size: 30px; 238 | } 239 | @media screen and (max-width: 520px) { 240 | div.game > div.num4096, 241 | div.game > div.num8192, 242 | div.game > div.num16384, 243 | div.game > div.num32768, 244 | div.game > div.num65536 { 245 | font-size: 15px; 246 | } 247 | } 248 | 249 | div.left { 250 | display: block; 251 | width: 550px; 252 | float: left; 253 | height: auto; 254 | } 255 | 256 | div.right { 257 | display: block; 258 | float: left; 259 | width: 350px; 260 | } 261 | 262 | @media only screen and (max-width: 950px) { 263 | div.left, 264 | div.right { 265 | width: 100%; 266 | clear: both; 267 | } 268 | div.right { 269 | margin-top: 1rem; 270 | } 271 | } 272 | 273 | div.right div.main { 274 | overflow: hidden; 275 | display: flex; 276 | height: calc(550px - 2rem); 277 | flex-direction: column; 278 | } 279 | 280 | div.right div.toparea { 281 | display: block; 282 | width: 100%; 283 | float: left; 284 | box-sizing: border-box; 285 | font-family: 'Ubuntu Mono', monospace; 286 | color: #776e65; 287 | padding-bottom: 0.75rem; 288 | padding-top: 0.25rem; 289 | border-bottom: 1px solid #bbada0; 290 | } 291 | div.right div.toparea input, 292 | div.right div.toparea button, 293 | div.right div.toparea button p { 294 | border: none; 295 | background-color: transparent; 296 | display: block; 297 | float: left; 298 | font-family: 'Ubuntu Mono', monospace; 299 | width: auto; 300 | } 301 | div.right div.toparea button { 302 | cursor: pointer; 303 | float: right; 304 | width: auto !important; 305 | } 306 | 307 | div.right div.toparea button p { 308 | display: none; 309 | } 310 | body:not(.restart-button-available).paused div.right div.toparea button p.paused, 311 | body:not(.restart-button-available):not(.paused) div.right div.toparea button p.not-paused, 312 | body.restart-button-available div.right div.toparea button p.restart { 313 | display: block; 314 | } 315 | 316 | div.right div.toparea input, 317 | div.right div.toparea button { 318 | width: 3.5rem; 319 | margin-left: 0.5rem; 320 | outline: none; 321 | opacity: 0.8; 322 | background-color: #776e65; 323 | color: #f9f6f2 !important; 324 | padding: 0 0.45rem !important; 325 | border-radius: 3px; 326 | font-size: 0.9rem; 327 | } 328 | 329 | body:not(.restart-button-available).paused div.right div.toparea button { 330 | background-color: #27ae60; 331 | } 332 | body:not(.restart-button-available):not(.paused) div.right div.toparea button { 333 | background-color: #e74c3c; 334 | } 335 | body.restart-button-available div.right div.toparea button { 336 | background-color: #f39c12; 337 | } 338 | 339 | div.right div.toparea input:focus { 340 | opacity: 1; 341 | } 342 | 343 | @media (hover: hover) and (pointer: fine) { 344 | div.right div.toparea button:hover { 345 | opacity: 1; 346 | } 347 | } 348 | 349 | div.right div.toparea > * { 350 | font-family: 'Ubuntu Mono', monospace; 351 | display: block; 352 | float: left; 353 | height: 1.5rem; 354 | line-height: 1.5rem; 355 | font-size: 1rem; 356 | } 357 | div.right div.console { 358 | height: calc(550px - 5.125rem); 359 | width: 100%; 360 | padding: 0; 361 | padding-top: 1rem; 362 | box-sizing: border-box; 363 | font-size: 1rem; 364 | line-height: 1.375; 365 | text-align: left; 366 | overflow-y: hidden; 367 | color: #776e65; 368 | flex-grow: 1; 369 | } 370 | body.paused div.right div.console, 371 | body.restart-button-available div.right div.console { 372 | overflow-y: auto; 373 | } 374 | div.right div.console div { 375 | padding: 0.6rem 0.8rem; 376 | display: block; 377 | float: left; 378 | width: 100%; 379 | overflow: auto; 380 | background-color: #eee4da; 381 | border-radius: 5px; 382 | box-sizing: border-box; 383 | } 384 | div.right div.console div, 385 | div.right div.console div > * { 386 | font-family: 'Ubuntu Mono', monospace; 387 | } 388 | div.right div.console div span { 389 | opacity: 0.65; 390 | } 391 | div.right div.console div:not(:last-child) { 392 | margin-bottom: 1rem; 393 | } 394 | 395 | div.right div.meta { 396 | margin: 2rem 0; 397 | } 398 | div.right div.meta p { 399 | color: #776e65; 400 | font-size: 0.85rem; 401 | line-height: 1.5; 402 | } 403 | 404 | @media only screen and (max-width: 550px) { 405 | div.right div.meta { 406 | margin: 1rem 0; 407 | } 408 | } 409 | 410 | .separator { 411 | position: relative; 412 | height: 1.25rem; 413 | color: #776e65; 414 | text-align: center; 415 | font-size: 0.9rem; 416 | opacity: 0.75; 417 | } 418 | .separator span { 419 | height: 1.25rem; 420 | display: inline-block; 421 | line-height: 1.25rem; 422 | padding: 0 1rem; 423 | background-color: #faf8ef; 424 | text-transform: uppercase; 425 | letter-spacing: 0.1rem; 426 | } 427 | 428 | .separator:before { 429 | content: ''; 430 | position: absolute; 431 | width: 100%; 432 | height: 1px; 433 | top: 50%; 434 | left: 0; 435 | background-color: #776e65; 436 | z-index: -1; 437 | opacity: 0.25; 438 | } 439 | 440 | div.bottom { 441 | margin-top: 2.5rem; 442 | padding: 0 calc(((100% - 950px) / 2) + 1rem); 443 | } 444 | @media only screen and (max-width: 950px) { 445 | div.bottom { 446 | padding: 0 1rem; 447 | } 448 | } 449 | 450 | div.bottom h1 { 451 | color: #776e65; 452 | font-size: 2rem; 453 | margin-bottom: 2rem; 454 | } 455 | 456 | div.bottom > *:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) { 457 | text-align: left; 458 | margin-bottom: 2rem; 459 | color: #776e65; 460 | font-size: 1.1rem; 461 | line-height: 1.65; 462 | opacity: 0.85; 463 | } 464 | 465 | div.bottom ul { 466 | margin-left: 2rem; 467 | } 468 | 469 | div.bottom a:not(.styled) { 470 | text-decoration: none; 471 | color: #0e91ff; 472 | border-bottom: 1px dotted #0e91ff; 473 | user-select: text; 474 | } 475 | div.bottom a:not(.styled):hover::selection { 476 | border-bottom-style: solid; 477 | background-color: #0e91ff; 478 | color: var(--white-color); 479 | } 480 | div.bottom a:not(.styled):hover { 481 | border-bottom-style: solid; 482 | } 483 | 484 | div.bottom a.styled { 485 | display: flex; 486 | width: 100%; 487 | min-height: 2rem; 488 | text-decoration: none; 489 | padding: 1rem 1.5rem; 490 | box-sizing: border-box; 491 | border-radius: 4px; 492 | background-color: #eee4da; 493 | opacity: 0.8; 494 | transition: opacity 0.2s ease-in-out, color 0.2s ease-in-out, fill 0.2s ease-in-out; 495 | color: #5f5850; 496 | fill: #5f5850; 497 | } 498 | div.bottom a.styled:hover { 499 | opacity: 1; 500 | } 501 | div.bottom a.styled > span.text { 502 | flex-basis: 100%; 503 | padding-right: 0.6rem; 504 | font-size: 1.25rem; 505 | line-height: 1.65; 506 | } 507 | div.bottom a.styled > span.arrow { 508 | flex-basis: 2rem; 509 | display: flex; 510 | align-items: center; 511 | justify-content: center; 512 | transition: transform 0.2s ease-in-out; 513 | } 514 | div.bottom a.styled:hover > span.arrow { 515 | transform: translateX(0.5rem); 516 | } 517 | div.bottom a.styled > span.arrow > * { 518 | flex-basis: 100%; 519 | } 520 | 521 | div.bottom p.start-text { 522 | line-height: 1.65; 523 | margin-bottom: 0.4rem; 524 | } 525 | div.bottom p.start-text.default-margin { 526 | margin-bottom: 2.5rem; 527 | } 528 | 529 | div.bottom .links { 530 | text-align: center; 531 | font-size: 0; 532 | } 533 | div.bottom .links a { 534 | font-size: 1rem; 535 | color: #fff; 536 | text-decoration: none !important; 537 | border: none !important; 538 | display: inline-block; 539 | padding: 0.5rem 0.75rem; 540 | border-radius: 5px; 541 | background-color: #0e91ff; 542 | opacity: 0.85; 543 | margin: 0 0.3rem; 544 | box-sizing: border-box; 545 | } 546 | 547 | @media only screen and (max-width: 520px) { 548 | div.bottom .links a { 549 | margin: 0; 550 | margin-bottom: 0.6rem; 551 | width: 100%; 552 | } 553 | } 554 | 555 | div.bottom .links a:hover { 556 | opacity: 1; 557 | } 558 | 559 | div.scroll-down { 560 | position: fixed; 561 | bottom: 0.5rem; 562 | right: 0.5rem; 563 | background-color: #eee4da; 564 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 565 | color: #776e65; 566 | font-size: 0.9rem; 567 | line-height: 1; 568 | padding: 0.5rem; 569 | border-radius: 5px; 570 | transition: opacity 0.2s ease-in-out; 571 | } 572 | -------------------------------------------------------------------------------- /assets/game.js: -------------------------------------------------------------------------------- 1 | import './js/2048'; 2 | -------------------------------------------------------------------------------- /assets/index.js: -------------------------------------------------------------------------------- 1 | import './css/main.css'; 2 | 3 | import './js/ai'; 4 | -------------------------------------------------------------------------------- /assets/js/2048.js: -------------------------------------------------------------------------------- 1 | const flipMatrix = (matrix) => matrix[0].map((column, index) => matrix.map((row) => row[index])); 2 | 3 | const rotateMatrix = (matrix) => flipMatrix(matrix.reverse()); 4 | 5 | const rotateMatrixCounterClockwise = (matrix) => flipMatrix(matrix).reverse(); 6 | 7 | global.getNewGameObj = function () { 8 | return { 9 | // Board Array 10 | board: [ 11 | ['', '', '', ''], 12 | ['', '', '', ''], 13 | ['', '', '', ''], 14 | ['', '', '', ''], 15 | ], 16 | // Generate random 2 or 4 in random spot at board 17 | addNewNum: function () { 18 | var openSpots = []; 19 | 20 | this.board.forEach(function (row, row_amount) { 21 | row.forEach(function (spot, col_amount) { 22 | if (spot == '') { 23 | openSpots.push([row_amount, col_amount]); 24 | } 25 | }); 26 | }); 27 | var newNumSpot = openSpots[Math.floor(Math.random() * openSpots.length)]; 28 | var number = Math.random() < 0.9 ? 2 : 4; 29 | this.board[newNumSpot[0]][newNumSpot[1]] = number; 30 | }, 31 | move: function (direction) { 32 | //self_ref.board = [this.board[0].slice(0), this.board[1].slice(0), this.board[2].slice(0), this.board[3].slice(0)]; 33 | var self_ref = this; 34 | switch (direction) { 35 | case 'left': 36 | self_ref.board.forEach(function (row, row_index) { 37 | var condensedRow = []; 38 | row.forEach(function (spot) { 39 | if (spot != '') { 40 | condensedRow.push(spot); 41 | } 42 | }); 43 | condensedRow.forEach(function (spot, spot_index) { 44 | if (spot == condensedRow[spot_index + 1] && spot != '') { 45 | condensedRow[spot_index] = spot * 2; 46 | condensedRow[spot_index + 1] = ''; 47 | } 48 | }); 49 | var returnRow = []; 50 | condensedRow.forEach(function (spot) { 51 | if (spot != '') { 52 | returnRow.push(spot); 53 | } 54 | }); 55 | self_ref.board[row_index].forEach(function (spot, spot_index) { 56 | if (returnRow[spot_index]) { 57 | self_ref.board[row_index][spot_index] = returnRow[spot_index]; 58 | } else { 59 | self_ref.board[row_index][spot_index] = ''; 60 | } 61 | }); 62 | }); 63 | break; 64 | case 'right': 65 | self_ref.board.forEach(function (row, row_index) { 66 | var condensedRow = []; 67 | row.forEach(function (spot) { 68 | if (spot != '') { 69 | condensedRow.push(spot); 70 | } 71 | }); 72 | condensedRow.reverse(); 73 | condensedRow.forEach(function (spot, spot_index) { 74 | if (spot == condensedRow[spot_index + 1] && spot != '') { 75 | condensedRow[spot_index] = spot * 2; 76 | condensedRow[spot_index + 1] = ''; 77 | } 78 | }); 79 | var returnRow = []; 80 | condensedRow.forEach(function (spot) { 81 | if (spot != '') { 82 | returnRow.push(spot); 83 | } 84 | }); 85 | self_ref.board[row_index].forEach(function (spot, spot_index) { 86 | if (returnRow[spot_index]) { 87 | self_ref.board[row_index][spot_index] = returnRow[spot_index]; 88 | } else { 89 | self_ref.board[row_index][spot_index] = ''; 90 | } 91 | }); 92 | self_ref.board[row_index].reverse(); 93 | }); 94 | break; 95 | case 'up': 96 | self_ref.board = rotateMatrix(self_ref.board); 97 | self_ref.board.forEach(function (row, row_index) { 98 | var condensedRow = []; 99 | row.forEach(function (spot) { 100 | if (spot != '') { 101 | condensedRow.push(spot); 102 | } 103 | }); 104 | condensedRow.reverse(); 105 | condensedRow.forEach(function (spot, spot_index) { 106 | if (spot == condensedRow[spot_index + 1] && spot != '') { 107 | condensedRow[spot_index] = spot * 2; 108 | condensedRow[spot_index + 1] = ''; 109 | } 110 | }); 111 | var returnRow = []; 112 | condensedRow.forEach(function (spot) { 113 | if (spot != '') { 114 | returnRow.push(spot); 115 | } 116 | }); 117 | self_ref.board[row_index].forEach(function (spot, spot_index) { 118 | if (returnRow[spot_index]) { 119 | self_ref.board[row_index][spot_index] = returnRow[spot_index]; 120 | } else { 121 | self_ref.board[row_index][spot_index] = ''; 122 | } 123 | }); 124 | self_ref.board[row_index].reverse(); 125 | }); 126 | self_ref.board = rotateMatrixCounterClockwise(self_ref.board); 127 | break; 128 | case 'down': 129 | self_ref.board = rotateMatrix(self_ref.board); 130 | self_ref.board.forEach(function (row, row_index) { 131 | var condensedRow = []; 132 | row.forEach(function (spot) { 133 | if (spot != '') { 134 | condensedRow.push(spot); 135 | } 136 | }); 137 | condensedRow.forEach(function (spot, spot_index) { 138 | if (spot == condensedRow[spot_index + 1] && spot != '') { 139 | condensedRow[spot_index] = spot * 2; 140 | condensedRow[spot_index + 1] = ''; 141 | } 142 | }); 143 | var returnRow = []; 144 | condensedRow.forEach(function (spot) { 145 | if (spot != '') { 146 | returnRow.push(spot); 147 | } 148 | }); 149 | self_ref.board[row_index].forEach(function (spot, spot_index) { 150 | if (returnRow[spot_index]) { 151 | self_ref.board[row_index][spot_index] = returnRow[spot_index]; 152 | } else { 153 | self_ref.board[row_index][spot_index] = ''; 154 | } 155 | }); 156 | }); 157 | self_ref.board = rotateMatrixCounterClockwise(self_ref.board); 158 | } 159 | //this.board = self_ref.board; 160 | }, 161 | check_gameover: function () { 162 | for (var rowIdx = 0; rowIdx < this.board.length; rowIdx++) { 163 | var row = this.board[rowIdx]; 164 | for (var itemIdx = 0; itemIdx < row.length; itemIdx++) { 165 | var item = row[itemIdx]; 166 | 167 | if (item == '') { 168 | return false; 169 | } 170 | 171 | if (itemIdx + 1 < row.length) { 172 | if (row[itemIdx + 1] == item) { 173 | return false; 174 | } 175 | } 176 | 177 | if (rowIdx + 1 < this.board.length) { 178 | if (this.board[rowIdx + 1][itemIdx] == item) { 179 | return false; 180 | } 181 | } 182 | } 183 | } 184 | 185 | return true; 186 | }, 187 | main_move: function (direction) { 188 | var startGame = this.board.join(','); 189 | this.move(direction); 190 | if (this.board.join(',') != startGame) { 191 | this.addNewNum(); 192 | } 193 | }, 194 | getScore: function () { 195 | var score = 0; 196 | this.board.forEach(function (row) { 197 | row.forEach(function (spot) { 198 | if (spot != '') { 199 | score += spot; 200 | } 201 | }); 202 | }); 203 | 204 | return score; 205 | }, 206 | }; 207 | }; 208 | -------------------------------------------------------------------------------- /assets/js/ai.js: -------------------------------------------------------------------------------- 1 | import Worker from './ai.worker.js'; 2 | 3 | var { displayBoard, addToConsole, addRestartAIButton, initGUI } = require('./gui'); 4 | 5 | var game; 6 | 7 | var gamesPerMove = 50; // the amount of simulations per move: left, right, up, or down. So a value of 50 would mean a total of 200 simulations per actual game move (--> 50 simulations * 4 moves = 200). 8 | var totalMoveScores = [0, 0, 0, 0]; // the total scores of all the simulations in any given game move, for each possible move (left, right, up, or down). To get the average, divide each by gamesPerMove. 9 | var totalMoveMoves = [0, 0, 0, 0]; // the total amount of moves of all the all the simulations in any given game move, for each possible move (left, right, up, or down). To get the average, divide each by gamesPerMove. 10 | var totalGamesDone = 0; // the amount of simulations that have been completed in the current game move. Once it reaches gamesPerMove * 4, all the simulations for the current game move are completed. 11 | global.paused = false; 12 | var moveCalculationBegin; // the MS since Epoch in which the current game move began calculating and running simulations 13 | var workers; // is the Worker API is available, this represents a list of workers which can be used to differ various tasks such as running simulations. Uses the Worker variable imported from the Worker file. 14 | 15 | // create workers 16 | window.addEventListener('load', function () { 17 | if (window.Worker) { 18 | var amountOfWorkers = 8; 19 | workers = []; 20 | for (var i = 0; i < amountOfWorkers; i++) { 21 | workers.push(new Worker()); 22 | workers[i].onmessage = function (e) { 23 | var [finalScore, moves, move_index] = e.data; 24 | totalMoveScores[move_index] += finalScore; 25 | totalMoveMoves[move_index] += moves; 26 | 27 | totalGamesDone += 1; 28 | checkDoneAndMakeBestMove(); 29 | }; 30 | } 31 | } 32 | }); 33 | 34 | function makeMove() { 35 | var newTotalSimulations = parseInt(document.getElementById('simulCount').value); 36 | if (!isNaN(newTotalSimulations) && newTotalSimulations > 0) { 37 | gamesPerMove = Math.ceil(newTotalSimulations / 4); 38 | } 39 | 40 | moveCalculationBegin = new Date().getTime(); 41 | 42 | totalMoveScores = [0, 0, 0, 0]; 43 | totalMoveMoves = [0, 0, 0, 0]; 44 | totalGamesDone = 0; 45 | 46 | ['left', 'right', 'up', 'down'].forEach(function (move, move_index) { 47 | for (var games = 0; games < gamesPerMove; games++) { 48 | if (workers) { 49 | setTimeout(simulateRun(move, move_index, workers[games % workers.length]), 0); 50 | } else { 51 | setTimeout(simulateRun(move, move_index), 0); 52 | } 53 | } 54 | }); 55 | } 56 | function simulateRun(move, move_index, worker = undefined) { 57 | var simulation = getNewGameObj(); 58 | simulation.board = [game.board[0].slice(0), game.board[1].slice(0), game.board[2].slice(0), game.board[3].slice(0)]; 59 | 60 | if (worker) { 61 | simulation.main_move(move); 62 | 63 | if (JSON.stringify(simulation.board) != JSON.stringify(game.board)) { 64 | worker.postMessage([simulation.board, move_index]); 65 | } else { 66 | totalGamesDone += 1; 67 | checkDoneAndMakeBestMove(); 68 | } 69 | } else { 70 | simulation.main_move(move); 71 | 72 | if (JSON.stringify(simulation.board) != JSON.stringify(game.board)) { 73 | var moves = 0; 74 | 75 | while (simulation.check_gameover() == false) { 76 | simulation.main_move(['left', 'right', 'up', 'down'][Math.floor(Math.random() * 4)]); 77 | moves++; 78 | } 79 | totalMoveScores[move_index] += simulation.getScore(); 80 | totalMoveMoves[move_index] += moves; 81 | } 82 | 83 | totalGamesDone += 1; 84 | checkDoneAndMakeBestMove(); 85 | } 86 | } 87 | function checkDoneAndMakeBestMove() { 88 | if (totalGamesDone >= gamesPerMove * 4) { 89 | var bestMoveIdx = totalMoveScores.indexOf(Math.max(...totalMoveScores)); 90 | var bestMove = ['left', 'right', 'up', 'down'][bestMoveIdx]; 91 | game.main_move(bestMove); 92 | 93 | var msToCalculateMove = new Date().getTime() - moveCalculationBegin; 94 | 95 | addToConsole(bestMove, totalMoveScores[bestMoveIdx], totalMoveMoves[bestMoveIdx], gamesPerMove * 4, msToCalculateMove); 96 | displayBoard(game); 97 | 98 | var gameover = game.check_gameover(); 99 | if (gameover == false && !paused) { 100 | setTimeout(makeMove, 0); 101 | } else if (gameover == true) { 102 | addRestartAIButton(); 103 | } 104 | } 105 | } 106 | 107 | function startGame() { 108 | document.querySelector('div.console').innerHTML = ''; 109 | 110 | game = getNewGameObj(); 111 | 112 | game.addNewNum(); 113 | game.addNewNum(); 114 | 115 | displayBoard(game); 116 | 117 | setTimeout(makeMove, 0); 118 | 119 | global.maxTile = 0; 120 | } 121 | 122 | window.addEventListener('load', function () { 123 | startGame(); 124 | }); 125 | 126 | initGUI(makeMove, startGame); 127 | -------------------------------------------------------------------------------- /assets/js/ai.worker.js: -------------------------------------------------------------------------------- 1 | importScripts('game.js'); 2 | 3 | onmessage = function (e) { 4 | var [board, move_index] = e.data; 5 | var simulation = getNewGameObj(); 6 | simulation.board = board; 7 | 8 | var moves = 0; 9 | 10 | //for(var i = 0; i < 100; i++) { 11 | while (simulation.check_gameover() == false) { 12 | simulation.main_move(['left', 'right', 'up', 'down'][Math.floor(Math.random() * 4)]); 13 | moves++; 14 | } 15 | postMessage([simulation.getScore(), moves, move_index]); 16 | }; 17 | -------------------------------------------------------------------------------- /assets/js/gui.js: -------------------------------------------------------------------------------- 1 | global.maxTile = 0; 2 | var secondsSinceStart = 0; 3 | function updateMilestones() { 4 | document.querySelector('div.milestones h1').innerHTML = `Reached
${maxTile}
in ${ 5 | Math.floor(secondsSinceStart * 100) / 100 6 | }s`; 7 | } 8 | 9 | function displayBoard(game) { 10 | document.querySelector('div.game').innerHTML = ''; 11 | game.board.forEach(function (row) { 12 | row.forEach(function (spot) { 13 | var className = ''; 14 | if (spot == '') { 15 | spot = ''; 16 | } else { 17 | var spotInt = parseInt(spot); 18 | if (spotInt > maxTile) { 19 | maxTile = spotInt; 20 | updateMilestones(); 21 | } 22 | className = 'num' + spot; 23 | } 24 | document.querySelector('div.game').innerHTML += "
" + spot + '
'; 25 | }); 26 | }); 27 | } 28 | function addToConsole(bestMove, totalScore, totalMoves, totalSimulations, calculatedMS) { 29 | var consoleElm = document.querySelector('div.console'); 30 | 31 | secondsSinceStart += calculatedMS / 1000; 32 | 33 | var consoleEntry = document.createElement('div'); 34 | consoleEntry.innerHTML = `Best Move: ${bestMove.toUpperCase()}
35 | Avg. Score: ${parseFloat(totalScore / (totalSimulations / 4)).toFixed(2)}
36 | Avg. Moves: ${parseFloat(totalMoves / (totalSimulations / 4)).toFixed(2)}
37 | Simulations: ${totalSimulations}
38 | Took: ${calculatedMS}ms`; 39 | 40 | consoleElm.appendChild(consoleEntry); 41 | consoleElm.scrollTop = consoleElm.scrollHeight + 10; 42 | } 43 | 44 | function addRestartAIButton() { 45 | document.body.classList.add('restart-button-available'); 46 | document.body.classList.remove('paused'); 47 | } 48 | 49 | function initGUI(makeMove, startGame) { 50 | startTime = new Date().getTime(); 51 | 52 | // pause or restart button 53 | var pauseOrRestartBtn = document.querySelector('div.right div.toparea button'); 54 | pauseOrRestartBtn.addEventListener('click', function () { 55 | if (document.body.classList.contains('restart-button-available')) { 56 | startGame(); 57 | secondsSinceStart = 0; 58 | document.body.classList.remove('restart-button-available'); 59 | } else { 60 | document.body.classList.toggle('paused'); 61 | paused = !paused; 62 | if (!paused) { 63 | setTimeout(makeMove, 0); 64 | } 65 | } 66 | }); 67 | 68 | // add rel="noreferrer nofollow" and target="_blank" to links in bottom area 69 | document.querySelectorAll('div.bottom a').forEach((elm) => { 70 | elm.setAttribute('rel', 'noreferrer nofollow'); 71 | elm.setAttribute('target', '_blank'); 72 | }); 73 | 74 | // scroll down marker 75 | var bottomArea = document.querySelector('div.bottom'); 76 | var scrollDown = document.querySelector('div.scroll-down'); 77 | function updateScrollDownMarker() { 78 | if (window.innerHeight + window.pageYOffset < bottomArea.getBoundingClientRect().top + window.pageYOffset) { 79 | scrollDown.style.opacity = '1'; 80 | } else { 81 | scrollDown.style.opacity = '0'; 82 | } 83 | } 84 | window.addEventListener('load', updateScrollDownMarker); 85 | window.addEventListener('resize', updateScrollDownMarker); 86 | window.addEventListener('scroll', updateScrollDownMarker); 87 | } 88 | 89 | module.exports = { displayBoard, addToConsole, addRestartAIButton, initGUI }; 90 | -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assets", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack" 8 | }, 9 | "author": "", 10 | "license": "UNLICENSED", 11 | "devDependencies": { 12 | "worker-loader": "^3.0.1", 13 | "@babel/core": "^7.9.6", 14 | "@babel/plugin-proposal-class-properties": "^7.8.3", 15 | "@babel/preset-env": "^7.9.6", 16 | "autoprefixer": "^9.7.6", 17 | "babel-loader": "^8.1.0", 18 | "concurrently": "^5.2.0", 19 | "css-loader": "^3.5.3", 20 | "extract-loader": "^5.0.1", 21 | "file-loader": "^6.0.0", 22 | "mini-css-extract-plugin": "^0.9.0", 23 | "optimize-css-assets-webpack-plugin": "^5.0.3", 24 | "postcss-loader": "^3.0.0", 25 | "style-loader": "^1.2.1", 26 | "sugarss": "^2.0.0", 27 | "terser-webpack-plugin": "^2.3.6", 28 | "webpack": "^4.43.0", 29 | "webpack-cli": "^3.3.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /assets/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')], 3 | }; 4 | -------------------------------------------------------------------------------- /assets/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const TerserJSPlugin = require('terser-webpack-plugin'); 4 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 5 | 6 | const config = { 7 | mode: 'production', 8 | watch: true, 9 | 10 | optimization: { 11 | minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})], 12 | }, 13 | plugins: [new MiniCssExtractPlugin()], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/, 18 | use: [ 19 | { 20 | loader: MiniCssExtractPlugin.loader, 21 | options: {}, 22 | }, 23 | { 24 | loader: 'css-loader', 25 | options: { 26 | importLoaders: 1, 27 | }, 28 | }, 29 | 'postcss-loader', 30 | ], 31 | }, 32 | { 33 | test: /\.m?js$/, 34 | exclude: /(node_modules|bower_components)/, 35 | use: { 36 | loader: 'babel-loader', 37 | options: { 38 | presets: ['@babel/preset-env'], 39 | plugins: ['@babel/plugin-proposal-class-properties'], 40 | }, 41 | }, 42 | }, 43 | { 44 | test: /\.worker\.js$/, 45 | use: { loader: 'worker-loader' }, 46 | }, 47 | ], 48 | }, 49 | }; 50 | 51 | module.exports = [ 52 | { 53 | entry: './index.js', 54 | output: { 55 | filename: 'main.js', 56 | path: path.resolve(__dirname, '../'), 57 | }, 58 | ...config, 59 | }, 60 | { 61 | entry: './game.js', 62 | output: { 63 | filename: 'game.js', 64 | path: path.resolve(__dirname, '../'), 65 | }, 66 | ...config, 67 | }, 68 | ]; 69 | -------------------------------------------------------------------------------- /browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #faf8ef -------------------------------------------------------------------------------- /demo-image-source.pxd/QuickLook/Icon.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/demo-image-source.pxd/QuickLook/Icon.tiff -------------------------------------------------------------------------------- /demo-image-source.pxd/QuickLook/Thumbnail.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/demo-image-source.pxd/QuickLook/Thumbnail.tiff -------------------------------------------------------------------------------- /demo-image-source.pxd/data/2B1B0325-8ECF-45B9-BC3F-0559F8C8F53A: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/demo-image-source.pxd/data/2B1B0325-8ECF-45B9-BC3F-0559F8C8F53A -------------------------------------------------------------------------------- /demo-image-source.pxd/data/originalImportedContentDocumentInfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/demo-image-source.pxd/data/originalImportedContentDocumentInfo -------------------------------------------------------------------------------- /demo-image-source.pxd/metadata.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/demo-image-source.pxd/metadata.info -------------------------------------------------------------------------------- /demo-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/demo-image.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon.ico -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon.png -------------------------------------------------------------------------------- /favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/apple-icon.png -------------------------------------------------------------------------------- /favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaberomualdo/2048-monte-carlo-ai/9e045b7c737ceac47c332b57e0e20776a0be82f7/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /game.js: -------------------------------------------------------------------------------- 1 | !function(r){var o={};function n(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return r[t].call(e.exports,e,e.exports,n),e.l=!0,e.exports}n.m=r,n.c=o,n.d=function(r,o,t){n.o(r,o)||Object.defineProperty(r,o,{enumerable:!0,get:t})},n.r=function(r){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})},n.t=function(r,o){if(1&o&&(r=n(r)),8&o)return r;if(4&o&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(n.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&o&&"string"!=typeof r)for(var e in r)n.d(t,e,function(o){return r[o]}.bind(null,e));return t},n.n=function(r){var o=r&&r.__esModule?function(){return r.default}:function(){return r};return n.d(o,"a",o),o},n.o=function(r,o){return Object.prototype.hasOwnProperty.call(r,o)},n.p="",n(n.s=0)}([function(r,o,n){"use strict";n.r(o);n(1)},function(r,o,n){(function(r){var o=function(r){return r[0].map((function(o,n){return r.map((function(r){return r[n]}))}))},n=function(r){return o(r.reverse())},t=function(r){return o(r).reverse()};r.getNewGameObj=function(){return{board:[["","","",""],["","","",""],["","","",""],["","","",""]],addNewNum:function(){var r=[];this.board.forEach((function(o,n){o.forEach((function(o,t){""==o&&r.push([n,t])}))}));var o=r[Math.floor(Math.random()*r.length)],n=Math.random()<.9?2:4;this.board[o[0]][o[1]]=n},move:function(r){var o=this;switch(r){case"left":o.board.forEach((function(r,n){var t=[];r.forEach((function(r){""!=r&&t.push(r)})),t.forEach((function(r,o){r==t[o+1]&&""!=r&&(t[o]=2*r,t[o+1]="")}));var e=[];t.forEach((function(r){""!=r&&e.push(r)})),o.board[n].forEach((function(r,t){e[t]?o.board[n][t]=e[t]:o.board[n][t]=""}))}));break;case"right":o.board.forEach((function(r,n){var t=[];r.forEach((function(r){""!=r&&t.push(r)})),t.reverse(),t.forEach((function(r,o){r==t[o+1]&&""!=r&&(t[o]=2*r,t[o+1]="")}));var e=[];t.forEach((function(r){""!=r&&e.push(r)})),o.board[n].forEach((function(r,t){e[t]?o.board[n][t]=e[t]:o.board[n][t]=""})),o.board[n].reverse()}));break;case"up":o.board=n(o.board),o.board.forEach((function(r,n){var t=[];r.forEach((function(r){""!=r&&t.push(r)})),t.reverse(),t.forEach((function(r,o){r==t[o+1]&&""!=r&&(t[o]=2*r,t[o+1]="")}));var e=[];t.forEach((function(r){""!=r&&e.push(r)})),o.board[n].forEach((function(r,t){e[t]?o.board[n][t]=e[t]:o.board[n][t]=""})),o.board[n].reverse()})),o.board=t(o.board);break;case"down":o.board=n(o.board),o.board.forEach((function(r,n){var t=[];r.forEach((function(r){""!=r&&t.push(r)})),t.forEach((function(r,o){r==t[o+1]&&""!=r&&(t[o]=2*r,t[o+1]="")}));var e=[];t.forEach((function(r){""!=r&&e.push(r)})),o.board[n].forEach((function(r,t){e[t]?o.board[n][t]=e[t]:o.board[n][t]=""}))})),o.board=t(o.board)}},check_gameover:function(){for(var r=0;r 2 | 3 | 4 | 5 | 6 | 7 | Jupiter: a Monte-Carlo based AI to beat 2048 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |

Jupiter

34 |

a Monte-Carlo based AI to beat 2048

35 |
36 |
37 |
38 |
39 |

40 |
41 |
42 |
43 |
44 |
45 |

Simulations/Move:

46 | 47 | 52 |
53 |
54 |
55 |
56 |

Try increasing the simulations per move for better AI performance!

57 |
58 |
59 |
60 |
61 |
62 | About This Project 63 |
64 |
65 | 76 |

77 | Jupiter is an AI which uses a Monte Carlo Tree Search algorithm to beat the popular online game, 78 | 2048. 79 |

80 |

81 | Given a high amount of simulations per move, Jupiter can achieve the 2048 tile almost 100% of the time. 82 |

83 |

84 | Jupiter is a project by developer and current high school student Gabriel Romualdo. 85 |

86 |

Algorithm & Performance

87 |

I've written two articles about the algorithm and implementation used in Jupiter:

88 | 94 | Using the Monte Carlo Tree Search (MCTS) Algorithm in an AI to Beat 2048 (and other games) 95 | 96 | 97 | 98 | 99 | 100 | 101 | 106 | Performance of AI Algorithms in Playing Games — Empirical Evidence From Jupiter, My 2048 AI 107 | 108 | 109 | 110 | 111 | 112 | 113 |

About

114 |

115 | Jupiter (formerly known as Jacob) started as a small AI project in January 2018. I got the idea of using Monte Carlo simulations and search 116 | trees as a method to play 2048 from this StackOverflow answer. 117 |

118 |

119 | I wrote a basic clone of what was described in the answer and built on the idea with an interactive console and my own 2048 game 120 | implementation, in contrast to the existing open sourced game code used in other AI projects. At this time, Jupiter ran on the main JavaScript 121 | thread, and had decent performance and speed: it was able to run ~800 Monte Carlo simulations of possible games per second. Running the game 122 | at 200 simulations per move gave roughly 4 moves per second. This amount of simulations reaches the winning 2048 tile about 65-75% of the 123 | time. 124 |

125 |

126 | In August 2020, I took a look at the project once again and noticed the potential to improve both performance and speed of the AI. I did some 127 | more research on Monte Carlo simulations and search trees, notably watching 128 | a great lecture 132 | by MIT Prof. Guttag in MIT's 6.0002 undergraduate course. In the one and a half years since I had first started the project, I'd also learned 133 | and used numerous modern JavaScript features, frameworks, and libraries. This put in me the unique position to use these new skills to extend 134 | this project vastly from performance, speed, bundle size, and design perspectives. 135 |

136 |

137 | So, I spent time refactoring existing code and replacing older algorithms with newer and more performant ones. In particular, I took advantage 138 | of modern JavaScript features like 139 | Web Workers to differ tasks to new threads 140 | and utilize concurrency capabilities. I also added Webpack to the project for the automated speed and bundle size optimizations built into 141 | many of its loaders. With new CSS and design skills I had learned over the past one and half years, I built a new design for the site, with a 142 | clearer console and mobile responsiveness. And finally, among many other features, I added "tile milestones" to let users know how fast the AI 143 | had reached certain tiles in the game. 144 |

145 |

146 | With the numerous updates to the project in 2020, Jupiter was now able to run ~2650 simulations of possible games per second. Running the game 147 | at 200 simulations per move gave around 13 moves per second. This indicated that performance had more than tripled with the 148 | new updates. Moreover, a new addition to the code allowed for performance to grow and scale horizontally by adding Web Workers and threads as 149 | general computing speed increases over time. 150 |

151 |

152 | All in all, the two year gap in which I learned invaluable frontend development and programming skills allowed me to improve the AI 153 | drastically in many areas while maintaining the original, extremely effective Monte Carlo based algorithm which stayed the same throughout the 154 | development process. 155 |

156 |

157 | I hope you like the project! If you'd like to read more about me or see more of my projects, check out my personal website and blog at 158 | xtrp.io. 159 |

160 |

161 | — Gabriel Romualdo, August 2020 162 |

163 |

License & Credits

164 |

165 | Jupiter is licensed under the MIT License. Code for the AI, site, and 2048 game code was written by 166 | Gabriel Romualdo, with some colors used from the 167 | original 2048 project. The original idea for the AI algorithm used comes from 168 | this StackOverflow answer. 169 |

170 |
171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700);@import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono);*{margin:0;padding:0;font-family:Open Sans,sans-serif}body{text-align:center;background-color:#faf8ef}div.app>h1{font-size:2.75rem;color:#776e65;line-height:1;margin-bottom:1rem;padding-top:2.5rem}div.app>p{font-size:1rem;color:#776e65;line-height:1.25;padding-bottom:2.5rem;opacity:.75}@media only screen and (max-width:550px){div.app>h1{margin-bottom:.65rem;padding-top:1.25rem;font-size:2rem}div.app>p{padding-bottom:1.25rem;font-size:.85rem}}div.container{display:inline-block;width:950px;padding:0 1rem 1rem;box-sizing:border-box}@media only screen and (max-width:950px){div.container{width:100%}}div.milestones{width:calc(550px - 2rem);box-sizing:border-box;margin:2rem 0}div.milestones h1{text-align:center;display:block;font-size:1.5rem;color:#776e65;font-weight:700}div.milestones h1 .tile{display:inline;padding:.5rem .75rem;border-radius:5px;margin:0 .75rem}div.game{width:calc(550px - 2rem);height:calc(550px - 2rem);background-color:#bbada0;box-sizing:border-box;padding:.5rem;font-size:0;border-radius:5px;max-width:100%}@media only screen and (max-width:950px){div.game{margin:0 auto}div.milestones{width:100%}}@media only screen and (max-width:550px){div.game{width:calc(100vw - 2rem);height:calc(100vw - 2rem)}div.milestones{zoom:85%}}div.game div{border-radius:5px;background-color:rgba(238,228,218,.35);margin:.5rem;width:calc(25% - 1rem);height:calc(25% - 1rem);display:flex;align-items:center;justify-content:center;float:left;box-sizing:border-box;font-weight:700;font-size:48px;color:#776e65;text-align:center}@media only screen and (max-width:520px){div.game{padding:.3rem}div.game div{margin:.3rem;width:calc(25% - .6rem);height:calc(25% - .6rem)}}div.num2{background:#eee4da!important}div.num2,div.num4{box-shadow:0 0 30px 10px rgba(243,215,116,0),inset 0 0 0 1px hsla(0,0%,100%,0)}div.num4{background:#ede0c8!important}div.num8{background:#f2b179!important}div.num8,div.num16{color:#f9f6f2!important}div.num16{background:#f59563!important}div.num32{background:#f67c5f!important}div.num32,div.num64{color:#f9f6f2!important}div.num64{background:#f65e3b!important}@media screen and (max-width:520px){div.game>div.num2,div.game>div.num4,div.game>div.num8,div.game>div.num16,div.game>div.num32,div.game>div.num64{font-size:28px}}div.num128{color:#f9f6f2!important;background:#edcf72!important;box-shadow:0 0 30px 10px rgba(243,215,116,.2381),inset 0 0 0 1px hsla(0,0%,100%,.14286)}div.game>div.num128{font-size:45px}@media screen and (max-width:520px){div.game>div.num128{font-size:25px}}div.num256{color:#f9f6f2!important;background:#edcc61!important;box-shadow:0 0 30px 10px rgba(243,215,116,.31746),inset 0 0 0 1px hsla(0,0%,100%,.19048)}div.game>div.num256{font-size:45px}@media screen and (max-width:520px){div.game>div.num256{font-size:25px}}div.num512{color:#f9f6f2!important;background:#edc850!important;box-shadow:0 0 30px 10px rgba(243,215,116,.39683),inset 0 0 0 1px hsla(0,0%,100%,.2381)}div.game>div.num512{font-size:45px}@media screen and (max-width:520px){div.game>div.num512{font-size:25px}}div.num1024{color:#f9f6f2!important;background:#edc53f!important;box-shadow:0 0 30px 10px rgba(243,215,116,.47619),inset 0 0 0 1px hsla(0,0%,100%,.28571)}div.game>div.num1024{font-size:35px}@media screen and (max-width:520px){div.game>div.num1024{font-size:20px}}div.num2048{color:#f9f6f2!important;background:#edc22e!important;box-shadow:0 0 30px 10px rgba(243,215,116,.55556),inset 0 0 0 1px hsla(0,0%,100%,.33333)}div.game>div.num2048{font-size:35px}@media screen and (max-width:520px){div.game>div.num2048{font-size:20px}}div.num4096,div.num8192,div.num16384,div.num32768,div.num65536{color:#f9f6f2!important;background:#3c3a32!important}div.game>div.num4096,div.game>div.num8192,div.game>div.num16384,div.game>div.num32768,div.game>div.num65536{font-size:30px}@media screen and (max-width:520px){div.game>div.num4096,div.game>div.num8192,div.game>div.num16384,div.game>div.num32768,div.game>div.num65536{font-size:15px}}div.left{width:550px;height:auto}div.left,div.right{display:block;float:left}div.right{width:350px}@media only screen and (max-width:950px){div.left,div.right{width:100%;clear:both}div.right{margin-top:1rem}}div.right div.main{overflow:hidden;display:flex;height:calc(550px - 2rem);flex-direction:column}div.right div.toparea{display:block;width:100%;float:left;box-sizing:border-box;font-family:Ubuntu Mono,monospace;color:#776e65;padding-bottom:.75rem;padding-top:.25rem;border-bottom:1px solid #bbada0}div.right div.toparea button,div.right div.toparea button p,div.right div.toparea input{border:none;background-color:transparent;display:block;float:left;font-family:Ubuntu Mono,monospace;width:auto}div.right div.toparea button{cursor:pointer;float:right;width:auto!important}div.right div.toparea button p{display:none}body.restart-button-available div.right div.toparea button p.restart,body:not(.restart-button-available).paused div.right div.toparea button p.paused,body:not(.restart-button-available):not(.paused) div.right div.toparea button p.not-paused{display:block}div.right div.toparea button,div.right div.toparea input{width:3.5rem;margin-left:.5rem;outline:none;opacity:.8;background-color:#776e65;color:#f9f6f2!important;padding:0 .45rem!important;border-radius:3px;font-size:.9rem}body:not(.restart-button-available).paused div.right div.toparea button{background-color:#27ae60}body:not(.restart-button-available):not(.paused) div.right div.toparea button{background-color:#e74c3c}body.restart-button-available div.right div.toparea button{background-color:#f39c12}div.right div.toparea input:focus{opacity:1}@media (hover:hover) and (pointer:fine){div.right div.toparea button:hover{opacity:1}}div.right div.toparea>*{font-family:Ubuntu Mono,monospace;display:block;float:left;height:1.5rem;line-height:1.5rem;font-size:1rem}div.right div.console{height:calc(550px - 5.125rem);width:100%;padding:1rem 0 0;box-sizing:border-box;font-size:1rem;line-height:1.375;text-align:left;overflow-y:hidden;color:#776e65;flex-grow:1}body.paused div.right div.console,body.restart-button-available div.right div.console{overflow-y:auto}div.right div.console div{padding:.6rem .8rem;display:block;float:left;width:100%;overflow:auto;background-color:#eee4da;border-radius:5px;box-sizing:border-box}div.right div.console div,div.right div.console div>*{font-family:Ubuntu Mono,monospace}div.right div.console div span{opacity:.65}div.right div.console div:not(:last-child){margin-bottom:1rem}div.right div.meta{margin:2rem 0}div.right div.meta p{color:#776e65;font-size:.85rem;line-height:1.5}@media only screen and (max-width:550px){div.right div.meta{margin:1rem 0}}.separator{position:relative;height:1.25rem;color:#776e65;text-align:center;font-size:.9rem;opacity:.75}.separator span{height:1.25rem;display:inline-block;line-height:1.25rem;padding:0 1rem;background-color:#faf8ef;text-transform:uppercase;letter-spacing:.1rem}.separator:before{content:"";position:absolute;width:100%;height:1px;top:50%;left:0;background-color:#776e65;z-index:-1;opacity:.25}div.bottom{margin-top:2.5rem;padding:0 calc((100% - 950px)/2 + 1rem)}@media only screen and (max-width:950px){div.bottom{padding:0 1rem}}div.bottom h1{color:#776e65;font-size:2rem;margin-bottom:2rem}div.bottom>:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6){text-align:left;margin-bottom:2rem;color:#776e65;font-size:1.1rem;line-height:1.65;opacity:.85}div.bottom ul{margin-left:2rem}div.bottom a:not(.styled){text-decoration:none;color:#0e91ff;border-bottom:1px dotted #0e91ff;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}div.bottom a:not(.styled):hover::-moz-selection{border-bottom-style:solid;background-color:#0e91ff;color:var(--white-color)}div.bottom a:not(.styled):hover::selection{border-bottom-style:solid;background-color:#0e91ff;color:var(--white-color)}div.bottom a:not(.styled):hover{border-bottom-style:solid}div.bottom a.styled{display:flex;width:100%;min-height:2rem;text-decoration:none;padding:1rem 1.5rem;box-sizing:border-box;border-radius:4px;background-color:#eee4da;opacity:.8;transition:opacity .2s ease-in-out,color .2s ease-in-out,fill .2s ease-in-out;color:#5f5850;fill:#5f5850}div.bottom a.styled:hover{opacity:1}div.bottom a.styled>span.text{flex-basis:100%;padding-right:.6rem;font-size:1.25rem;line-height:1.65}div.bottom a.styled>span.arrow{flex-basis:2rem;display:flex;align-items:center;justify-content:center;transition:transform .2s ease-in-out}div.bottom a.styled:hover>span.arrow{transform:translateX(.5rem)}div.bottom a.styled>span.arrow>*{flex-basis:100%}div.bottom p.start-text{line-height:1.65;margin-bottom:.4rem}div.bottom p.start-text.default-margin{margin-bottom:2.5rem}div.bottom .links{text-align:center;font-size:0}div.bottom .links a{font-size:1rem;color:#fff;text-decoration:none!important;border:none!important;display:inline-block;padding:.5rem .75rem;border-radius:5px;background-color:#0e91ff;opacity:.85;margin:0 .3rem;box-sizing:border-box}@media only screen and (max-width:520px){div.bottom .links a{margin:0 0 .6rem;width:100%}}div.bottom .links a:hover{opacity:1}div.scroll-down{position:fixed;bottom:.5rem;right:.5rem;background-color:#eee4da;box-shadow:0 2px 8px rgba(0,0,0,.1);color:#776e65;font-size:.9rem;line-height:1;padding:.5rem;border-radius:5px;transition:opacity .2s ease-in-out} -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";t.a=function(){return new Worker(n.p+"main.worker.js")}},function(e,t,n){"use strict";n.r(t);n(3),n(4)},function(e,t,n){},function(e,t,n){"use strict";(function(e){var t=n(1);function o(e){return function(e){if(Array.isArray(e))return i(e)}(e)||function(e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||a(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function r(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(e)))return;var n=[],o=!0,r=!1,a=void 0;try{for(var i,c=e[Symbol.iterator]();!(o=(i=c.next()).done)&&(n.push(i.value),!t||n.length!==t);o=!0);}catch(e){r=!0,a=e}finally{try{o||null==c.return||c.return()}finally{if(r)throw a}}return n}(e,t)||a(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n0&&(p=Math.ceil(e/4)),u=(new Date).getTime(),b=[0,0,0,0],y=[0,0,0,0],g=0,["left","right","up","down"].forEach((function(e,t){for(var n=0;n2&&void 0!==arguments[2]?arguments[2]:void 0,o=getNewGameObj();if(o.board=[c.board[0].slice(0),c.board[1].slice(0),c.board[2].slice(0),c.board[3].slice(0)],n)o.main_move(e),JSON.stringify(o.board)!=JSON.stringify(c.board)?n.postMessage([o.board,t]):(g+=1,S());else{if(o.main_move(e),JSON.stringify(o.board)!=JSON.stringify(c.board)){for(var r=0;0==o.check_gameover();)o.main_move(["left","right","up","down"][Math.floor(4*Math.random())]),r++;b[t]+=o.getScore(),y[t]+=r}g+=1,S()}}function S(){if(g>=4*p){var e=b.indexOf(Math.max.apply(Math,o(b))),t=["left","right","up","down"][e];c.main_move(t);var n=(new Date).getTime()-u;f(t,b[e],y[e],4*p,n),l(c);var r=c.check_gameover();0!=r||paused?1==r&&m():setTimeout(w,0)}}function T(){document.querySelector("div.console").innerHTML="",(c=getNewGameObj()).addNewNum(),c.addNewNum(),l(c),setTimeout(w,0),e.maxTile=0}e.paused=!1,window.addEventListener("load",(function(){if(window.Worker){d=[];for(var e=0;e<8;e++)d.push(new t.a),d[e].onmessage=function(e){var t=r(e.data,3),n=t[0],o=t[1],a=t[2];b[a]+=n,y[a]+=o,g+=1,S()}}})),window.addEventListener("load",(function(){T()})),v(w,T)}).call(this,n(0))},function(e,t,n){(function(t){t.maxTile=0;var n=0;e.exports={displayBoard:function(e){document.querySelector("div.game").innerHTML="",e.board.forEach((function(e){e.forEach((function(e){var t="";if(""==e)e="";else{var o=parseInt(e);o>maxTile&&(maxTile=o,document.querySelector("div.milestones h1").innerHTML='Reached
').concat(maxTile,"
in ").concat(Math.floor(100*n)/100,"s")),t="num"+e}document.querySelector("div.game").innerHTML+="
"+e+"
"}))}))},addToConsole:function(e,t,o,r,a){var i=document.querySelector("div.console");n+=a/1e3;var c=document.createElement("div");c.innerHTML="Best Move: ".concat(e.toUpperCase(),"
\nAvg. Score: ").concat(parseFloat(t/(r/4)).toFixed(2),"
\nAvg. Moves: ").concat(parseFloat(o/(r/4)).toFixed(2),"
\nSimulations: ").concat(r,"
\nTook: ").concat(a,"ms"),i.appendChild(c),i.scrollTop=i.scrollHeight+10},addRestartAIButton:function(){document.body.classList.add("restart-button-available"),document.body.classList.remove("paused")},initGUI:function(e,t){startTime=(new Date).getTime(),document.querySelector("div.right div.toparea button").addEventListener("click",(function(){document.body.classList.contains("restart-button-available")?(t(),n=0,document.body.classList.remove("restart-button-available")):(document.body.classList.toggle("paused"),paused=!paused,paused||setTimeout(e,0))})),document.querySelectorAll("div.bottom a").forEach((function(e){e.setAttribute("rel","noreferrer nofollow"),e.setAttribute("target","_blank")}));var o=document.querySelector("div.bottom"),r=document.querySelector("div.scroll-down");function a(){window.innerHeight+window.pageYOffset