├── .gitignore ├── README.md ├── _config.yml ├── dist ├── bookmarklet.js ├── placebot-full.user.js ├── placebot.js ├── placebot.min.js └── placebot.user.js ├── gulpfile.js ├── import.js ├── import_bm.js ├── import_footer.js ├── import_us.js ├── package.json └── src ├── canvas.js ├── draw.js ├── placebot.js └── settings.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlaceBot 2 | 3 | A bot that aims to automate [place](//www.reddit.com/r/place). 4 | 5 | ## Install 6 | 7 | The recommended way to use the bot is via a userscript. Alternatively, you can 8 | use a bookmarklet by copy-pasting the linked code into a bookmark. 9 | 10 | * [Userscript](https://github.com/grind086/PlaceBot/raw/master/dist/placebot.user.js) 11 | * [Bookmarklet](https://github.com/grind086/PlaceBot/raw/master/dist/bookmarklet.js) 12 | 13 | ## Features 14 | 15 | * Automatically draws tiles as the cooldown expires 16 | * Configurable draw order 17 | * Top down 18 | * Input order 19 | * Random 20 | * Draw tiles based on a function 21 | * Allow export/import of drawings, so that multiple people can work on each one 22 | * Persist to local storage 23 | 24 | ### Planned 25 | 26 | * Allow drawing at any time, and queue tile placements 27 | 28 | ## Guide 29 | 30 | ### Quick Start 31 | 32 | The simplest way to use PlaceBot is by providing it with a list of tiles, and (optionally) 33 | an order to draw them in. Currently this is accomplished by opening the javascript console 34 | and entering something like the following: 35 | 36 | ```javascript 37 | placeBot.tiles = [[0, 0, 4], [0, 1, 4]]; 38 | ``` 39 | 40 | which will draw pink (color index 4) tiles at (0,0) and (0,1). You can also set a draw order 41 | using one of the following: 42 | 43 | ```javascript 44 | placeBot.tileSelector = PlaceBot.selector.TopDown; // Top down, Left to right 45 | placeBot.tileSelector = PlaceBot.selector.BottomUp; // Bottom up, Right to left 46 | placeBot.tileSelector = PlaceBot.selector.Random; // Chooses a random tile to draw each time 47 | placeBot.tileSelector = PlaceBot.selector.DrawOrder; // Progress through the tiles in order 48 | ``` 49 | 50 | So if you wanted people to use a bot that simply draws a pink line from (0,0) to (0,5) you 51 | could give them the following code: 52 | 53 | ```javascript 54 | placeBot.tiles = [[0, 0, 4], [0, 1, 4], [0, 2, 4], [0, 3, 4], [0, 4, 4], [0, 5, 4]]; 55 | placeBot.tileSelector = PlaceBot.selector.TopDown; 56 | placeBot.save(); // Makes sure our bot keeps running after a refresh 57 | ``` 58 | 59 | If you want to use your own selection function, that works too. Just provide a function 60 | that takes an array of tiles (ie `placeBot.tiles`), and returns an index from that array. 61 | For examples see `PlaceBot.selector` in `placebot.js`. 62 | 63 | ### Tile Generators 64 | 65 | It is also possible to generate tiles as you go, rather than using a list. In this case 66 | you need to write the function in the following form: 67 | 68 | ```javascript 69 | function myGeneratorFactory(placeBot) { 70 | 71 | // Setup... 72 | 73 | return function myGenerator() { 74 | return [x, y, color]; 75 | } 76 | } 77 | ``` 78 | 79 | then apply it using 80 | 81 | ```javascript 82 | placeBot.setTileFunction(PlaceBot.placeMode.FUNCTION, myGeneratorFactory); 83 | ``` 84 | 85 | The function I've been using for testing is the following: 86 | 87 | ```javascript 88 | // Draws a pink line vertically at x = 456, starting from y = 849 and continuing 89 | // down. Redraws as necessary. 90 | 91 | function pinkLine(placeBot) { 92 | var lastDraw; 93 | var lastTile = [456, 849, 4]; 94 | 95 | return function() { 96 | if (pb.lastDrawTime !== lastDraw) { 97 | pb.lastDrawTime = lastDraw; 98 | lastTile = [456, 849, 4]; 99 | } else { 100 | lastTile[1] = (lastTile[1] + 1) % 1000; 101 | } 102 | 103 | return lastTile; 104 | }; 105 | } 106 | 107 | placeBot.setTileFunction(PlaceBot.placeMode.FUNCTION, pinkLine); 108 | ``` 109 | 110 | Basically this function sets up an initial state, then reverts to that state after 111 | every successful draw. Note that this function is called repeatedly (on `minTimer`) 112 | if drawing isn't successful - usually due to a redundant draw. You can see its 113 | progress [place/#x=456&y=852](https://www.reddit.com/r/place/#x=456&y=852). 114 | 115 | ## Common Issues 116 | 117 | #### My bot is redrawing the same things! 118 | 119 | If your bot is designed to redraw over edits that other people have made, then it's 120 | entirely possible it was configured incorrectly. However, it is also important to 121 | note that the bot pings reddit directly when checking pixel colors - this means that 122 | it is using information that is more up to date than what you see on your screen. 123 | In testing I've also noticed that changes can be missed entirely by my computer (but 124 | the bot still notices, and acts accordingly). The best way to determine if this is 125 | your issue is to simply refresh the page. 126 | 127 | ## API 128 | 129 | ##### Globals 130 | 131 | The bot instance is exported to `window.placeBot` while the constructor is at 132 | `window.PlaceBot`. 133 | 134 | #### Static Properties 135 | 136 | ``` 137 | PlaceBot.version 138 | 139 | PlaceBot.placeMode.ARRAY 140 | PlaceBot.placeMode.FUNCTION 141 | 142 | PlaceBot.selector.TopDown 143 | PlaceBot.selector.BottomUp 144 | PlaceBot.selector.Random 145 | PlaceBot.selector.DrawOrder 146 | ``` 147 | 148 | #### new placeBot() 149 | 150 | Creates an instance of placeBot, which automatically loads data from localStorage 151 | and begins the draw timer. 152 | 153 | __Properties__ 154 | 155 | | Property | Type | Default | Description | 156 | |-----------------------|----------|------------------------------|-----------------------------------------------------------------------------------------------------------| 157 | | placeMode | number | `PlaceBot.placeMode.ARRAY` | The mode we use to place tiles, either using an array or a generator function. | 158 | | tiles | array | `[]` | (Array mode) The list of tiles we have yet to place in the form `[[x, y, color], [x2, y2, color2], ... ]` | 159 | | tileSelector | function | `PlaceBot.selectors.TopDown` | (Array mode) Returns the index of the next tile to place. | 160 | | tileGenerator | function | `undefined` | (Function mode) Returns the next tile to place in the form `[x, y, color]` | 161 | | drawTimer | number | `undefined` | Reference to the current draw timer (returned by setTimer). | 162 | | minTimer | number | `100` | The minimum time (in ms) to wait between draw attempts. | 163 | | lastDrawTime | number | `undefined` | The last time there was a successful draw. | 164 | | get cooldownRemaining | number | n/a | The time (in ms) remaining in the draw cooldown. | 165 | | get nextDrawTime | number | n/a | The time that the next draw is allowed. | 166 | | get canDraw | boolean | n/a | Whether or not drawing is currently allowed. | 167 | 168 | ##### .exportTiles() → string 169 | 170 | Returns JSON containing the current tiles and placement type. 171 | 172 | ##### .importTiles(string | array) 173 | 174 | Takes either JSON (as in .exportTiles) or an object to use as the current tiles 175 | list. Accepts options for `tiles → tiles`, `mode → placeMode`, `fn → tileSelector | tileGenerator`. 176 | 177 | ##### .exportSettings() → string 178 | 179 | Returns the current settings as JSON. 180 | 181 | ##### .importSettings(string | object) 182 | 183 | Takes either JSON or an object containing the new settings. Currently the only 184 | things considered a 'setting' for this purpose is `minTimer`. 185 | 186 | ##### .exportBot() → string 187 | 188 | Returns a combination of the tiles and settings. 189 | 190 | ##### .importBot(string | object) 191 | 192 | Takes either JSON or an object, and directs the `settings` property to `this.importSettings` 193 | and the `tiles` property to `this.importTiles`. 194 | 195 | ##### .save() 196 | 197 | Persists settings and tiles to `localStorage`. 198 | 199 | ##### .load() 200 | 201 | Loads settings and tiles from `localStorage`. 202 | 203 | ##### .setTileFunction(mode {number}, fn {function | string }) 204 | 205 | Sets the placement mode according to `mode`, then sets either `this.tileGenerator` or 206 | `this.tileSelector` using the provided function. The following steps are taken to 207 | coerce `fn` it into a function: 208 | 209 | 1. If `fn` is a function, use it. 210 | 2. (If `ARRAY` mode) Check if `PlaceBot.selectors` has a property named `fn`, if so use that. 211 | 3. Attempt to use `eval(fn)`. 212 | 4. Default back to `ARRAY` mode with `fn = 'TopDown'` 213 | 214 | ##### .drawNext() 215 | 216 | Attempts to draw the next tile (source depends on mode). If no tile is provided, or drawing 217 | is not allowed, nothing happens. If a tile is provided, then a reddit api is checked to 218 | see if that tile is already desired color (to avoid wasting our draw cooldown). A timer is 219 | then set for the next draw. 220 | 221 | ##### .drawTile(x {number}, y {number}, color {number}) 222 | 223 | If drawing is allowed, draws the given tile. If not, does nothing. Note that bypassing 224 | the check can result in your cooldown resetting. 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /dist/bookmarklet.js: -------------------------------------------------------------------------------- 1 | javascript:(function() { 2 | var s = document.createElement('script'); 3 | s.setAttribute('type', 'text/javascript'); 4 | s.setAttribute('src', 'https://grind086.github.io/PlaceBot/dist/placebot.min.js'); 5 | document.head.appendChild(s); 6 | })(); -------------------------------------------------------------------------------- /dist/placebot-full.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name PlaceBot-full 3 | // @version 0.0.9 4 | // @namespace https://github.com/grind086/PlaceBot 5 | // @description A bot that automates drawing on reddit.com/r/place 6 | // @grant unsafeWindow 7 | // @match http://www.reddit.com/place/ 8 | // @match http://www.reddit.com/r/place/ 9 | // @match https://www.reddit.com/place/ 10 | // @match https://www.reddit.com/r/place/ 11 | // ==/UserScript== 12 | 13 | (function() { 14 | !function(t){"use strict";var e=function(){this.placeMode=e.placeMode.ARRAY,this.tiles=[],this.tileSelector=e.selector.TopDown,this._tileGeneratorFactory=void 0,this.tileGenerator=void 0,this.drawTimer=void 0,this.minTimer=100,this.lastDrawTime=0,console.log(["------------",,"PlaceBot "+e.version,"------------"].join("\n")),this.load(),this._setTimer()};Object.defineProperties(e.prototype,{cooldownRemaining:{get:function(){return this.nextDrawTime-Date.now()}},nextDrawTime:{get:function(){return e.place.cooldownEndTime}},canDraw:{get:function(){return this.cooldownRemaining<0&&this.lastDrawTime!==this.nextDrawTime}}}),e.version="0.0.9",e.placeMode={ARRAY:0,FUNCTION:1},e.selector={TopDown:function(t){var e=-1,o=1/0,i=1/0;return t.forEach(function(t,n){(t[1]i||t[1]===i&&t[0]>o)&&(e=n,o=t[0],i=t[1])}),e},LeftToRight:function(t){var e=-1,o=1/0,i=1/0;return t.forEach(function(t,n){(t[0] Bottom, Left -> Right 106 | TopDown: function(tiles) { 107 | var index = -1, 108 | minX = Infinity, 109 | minY = Infinity; 110 | 111 | tiles.forEach(function(tile, i) { 112 | if (tile[1] < minY || (tile[1] === minY && tile[0] < minX)) { 113 | index = i; 114 | minX = tile[0]; 115 | minY = tile[1]; 116 | } 117 | }); 118 | 119 | return index; 120 | }, 121 | 122 | // Bottom -> Top, Right -> Left 123 | BottomUp: function(tiles) { 124 | var index = -1, 125 | minX = -1, 126 | minY = -1; 127 | 128 | tiles.forEach(function(tile, i) { 129 | if (tile[1] > minY || (tile[1] === minY && tile[0] > minX)) { 130 | index = i; 131 | minX = tile[0]; 132 | minY = tile[1]; 133 | } 134 | }); 135 | 136 | return index; 137 | }, 138 | 139 | // Left -> Right, Top -> Bottom 140 | LeftToRight: function(tiles) { 141 | var index = -1, 142 | minX = Infinity, 143 | minY = Infinity; 144 | 145 | tiles.forEach(function(tile, i) { 146 | if (tile[0] < minX || (tile[0] === minX && tile[1] < minY)) { 147 | index = i; 148 | minX = tile[0]; 149 | minY = tile[1]; 150 | } 151 | }); 152 | 153 | return index; 154 | }, 155 | 156 | // Chooses any random tile 157 | Random: function(tiles) { 158 | return Math.floor(Math.random() * tiles.length); 159 | }, 160 | 161 | // Keeps the order that tiles were added 162 | DrawOrder: function(tiles) { 163 | return 0; 164 | } 165 | }; 166 | 167 | /** 168 | * @property {object} place - Reference to reddit's place object 169 | * @static 170 | */ 171 | PlaceBot.place = global.r.place; 172 | 173 | /** 174 | * @property {object} placeModules - References to any of reddit's place modules we might need 175 | * @static 176 | */ 177 | PlaceBot.placeModules = {}; 178 | 179 | // Import place modules 180 | var importModules = ['api']; 181 | global.r.placeModule('', function(require) { 182 | importModules.forEach(function(name) { 183 | PlaceBot.placeModules[name] = require(name); 184 | }); 185 | }); 186 | 187 | global.PlaceBot = PlaceBot; 188 | 189 | // Do this async so we finish loading the prototype 190 | setTimeout(function() { 191 | global.placeBot = new PlaceBot(); 192 | }, 0); 193 | })( 194 | typeof unsafeWindow !== 'undefined' ? unsafeWindow : 195 | typeof window !== 'undefined' ? window : 196 | {} 197 | ); 198 | 199 | (function(global) { 200 | 'use strict'; 201 | 202 | var PlaceBot = global.PlaceBot; 203 | 204 | var localStorage = global.hasOwnProperty('localStorage') 205 | ? global.localStorage 206 | : { 207 | getItem: function() { return null; }, 208 | setItem: function() { } 209 | }; 210 | 211 | /** 212 | * Takes either JSON or an object, and returns an object 213 | * 214 | * @method _importObject 215 | * @property {Mixed} data - JSON or an object 216 | */ 217 | PlaceBot.prototype._importObject = function(data) { 218 | if ('string' === typeof data) { 219 | try { 220 | data = JSON.parse(data); 221 | } catch(e) { 222 | return { 223 | success: false, 224 | error: e.message 225 | }; 226 | } 227 | } 228 | 229 | return { 230 | success: true, 231 | data: data 232 | }; 233 | }; 234 | 235 | /** 236 | * Takes either string or function, and returns function 237 | * 238 | * @method _importFunction 239 | */ 240 | PlaceBot.prototype._importFunction = function(data) { 241 | if ('string' === typeof data) { 242 | try { 243 | data = eval('(' + data + ')'); 244 | } catch(e) { 245 | return { 246 | success: false, 247 | error: e.message 248 | }; 249 | } 250 | } 251 | 252 | if ('function' !== typeof data) { 253 | return { 254 | success: false, 255 | error: 'Invalid data type' 256 | }; 257 | } 258 | 259 | return { 260 | success: true, 261 | data: data 262 | }; 263 | }; 264 | 265 | /** 266 | * Collects saveable tile info into one object 267 | * 268 | * @method _tilesObject 269 | */ 270 | PlaceBot.prototype._tilesObject = function() { 271 | var obj = { 272 | mode: this.placeMode, 273 | tiles: this.tiles 274 | }; 275 | 276 | if (this.placeMode === PlaceBot.placeMode.ARRAY) { 277 | var name = this.tileSelector.name; 278 | 279 | if (PlaceBot.selector.hasOwnProperty(this.tileSelector.name)) { 280 | obj.fn = name; 281 | } else { 282 | obj.fn = this.tileSelector.toString(); 283 | } 284 | } 285 | else if (this.placeMode === PlaceBot.placeMode.FUNCTION) { 286 | obj.fn = this._tileGeneratorFactory.toString(); 287 | } 288 | 289 | return obj; 290 | }; 291 | 292 | /** 293 | * Collects saveable settings into one object 294 | * 295 | * @method _settingsObject 296 | */ 297 | PlaceBot.prototype._settingsObject = function() { 298 | return { 299 | minTimer: this.minTimer 300 | }; 301 | }; 302 | 303 | /** 304 | * Returns JSON of the current tiles 305 | * 306 | * @method exportTiles 307 | */ 308 | PlaceBot.prototype.exportTiles = function() { 309 | return JSON.stringify(this._tilesObject()); 310 | }; 311 | 312 | /** 313 | * Returns JSON of the current settings 314 | * 315 | * @method exportSettings 316 | */ 317 | PlaceBot.prototype.exportSettings = function() { 318 | return JSON.stringify(this._settingsObject()); 319 | }; 320 | 321 | /** 322 | * Returns JSON of the current settings and tiles 323 | * 324 | * @method exportBot 325 | */ 326 | PlaceBot.prototype.exportBot = function() { 327 | return JSON.stringify({ 328 | settings: this._settingsObject(), 329 | tiles: this.tiles 330 | }); 331 | }; 332 | 333 | /** 334 | * Imports tiles as JSON or object 335 | * 336 | * @method importTiles 337 | */ 338 | PlaceBot.prototype.importTiles = function(tilesJSON) { 339 | var imported = this._importObject(tilesJSON); 340 | 341 | if (!imported.success) { 342 | console.log('Failed to import tiles: %s', imported.error); 343 | return false; 344 | } 345 | 346 | var tiledata = Object.assign(this._tilesObject(), imported.data); 347 | 348 | this.tiles = tiledata.tiles || []; 349 | this.setTileFunction(tiledata.mode, tiledata.fn); 350 | }; 351 | 352 | /** 353 | * Imports settings as JSON or object 354 | * 355 | * @method importSettings 356 | */ 357 | PlaceBot.prototype.importSettings = function(settingsJSON) { 358 | var imported = this._importObject(settingsJSON); 359 | 360 | if (!imported.success) { 361 | console.log('Failed to import settings: %s', imported.error); 362 | return false; 363 | } 364 | 365 | var settings = Object.assign(this._settingsObject(), imported.data); 366 | 367 | this.minTimer = settings.minTimer; 368 | }; 369 | 370 | /** 371 | * Imports settings and tiles as JSON or object 372 | * 373 | * @method importBot 374 | */ 375 | PlaceBot.prototype.importBot = function(botJSON) { 376 | var imported = this._importObject(botJSON); 377 | 378 | if (!imported.success) { 379 | console.log('Failed to import bot: %s', imported.error); 380 | return false; 381 | } 382 | 383 | this.importSettings(imported.data.settings); 384 | this.importTiles(imported.data.tiles); 385 | }; 386 | 387 | /** 388 | * Persist settings and tiles to localStorage 389 | * 390 | * @method save 391 | */ 392 | PlaceBot.prototype.save = function() { 393 | localStorage.setItem('placebot_settings', this.exportSettings()); 394 | localStorage.setItem('placebot_tiles', this.exportTiles()); 395 | }; 396 | 397 | /** 398 | * Load settings and tiles from localStorage 399 | * 400 | * @method load 401 | */ 402 | PlaceBot.prototype.load = function() { 403 | this.importSettings(localStorage.getItem('placebot_settings')); 404 | this.importTiles(localStorage.getItem('placebot_tiles')); 405 | }; 406 | 407 | /** 408 | * 409 | */ 410 | PlaceBot.prototype.setTileFunction = function(mode, fn) { 411 | var imported; 412 | 413 | switch (mode) { 414 | case PlaceBot.placeMode.ARRAY: 415 | this.placeMode = PlaceBot.placeMode.ARRAY; 416 | this.tileGenerator = undefined; 417 | 418 | if ('string' === typeof fn && PlaceBot.selector.hasOwnProperty(fn)) { 419 | this.tileSelector = PlaceBot.selector[fn]; 420 | } 421 | else { 422 | imported = this._importFunction(fn); 423 | 424 | if (imported.success) { 425 | this.tileSelector = imported.data; 426 | } 427 | else { 428 | this.tileSelector = PlaceBot.selector.TopDown; 429 | } 430 | } 431 | 432 | break; 433 | 434 | case PlaceBot.placeMode.FUNCTION: 435 | this.placeMode = PlaceBot.placeMode.FUNCTION; 436 | this.tileSelector = undefined; 437 | 438 | imported = this._importFunction(fn); 439 | 440 | if (imported.success) { 441 | this._tileGeneratorFactory = imported.data; 442 | this.tileGenerator = imported.data(this); 443 | break; 444 | } 445 | 446 | console.log('Function import failed: %s', imported.error); 447 | // fall through to default 448 | 449 | default: 450 | this.tiles = []; 451 | this.setTileFunction(PlaceBot.placeMode.ARRAY, 'TopDown'); 452 | } 453 | }; 454 | })( 455 | typeof unsafeWindow !== 'undefined' ? unsafeWindow : 456 | typeof window !== 'undefined' ? window : 457 | {} 458 | ); 459 | 460 | (function(global) { 461 | 'use strict'; 462 | 463 | var PlaceBot = global.PlaceBot; 464 | 465 | /** 466 | * Sets the timer for the next available draw 467 | * 468 | * @method _setTimer 469 | */ 470 | PlaceBot.prototype._setTimer = function() { 471 | clearTimeout(this.drawTimer); // Ensure we only have one timer running 472 | 473 | var time = Math.round(Math.max(this.minTimer, this.cooldownRemaining)); 474 | this.drawTimer = setTimeout(this.drawNext.bind(this), time); 475 | 476 | console.log('Scheduled draw in %sms', time); 477 | }; 478 | 479 | /** 480 | * Draws the next tile (as chosen by this.tileSelector) if allowed, then sets 481 | * a timer for the next available draw. Also performs a check to make sure 482 | * the tile is not already the desired color. 483 | * 484 | * @method drawNext 485 | */ 486 | PlaceBot.prototype.drawNext = function() { 487 | if (this.canDraw) { 488 | var tile; 489 | 490 | if (this.placeMode === PlaceBot.placeMode.ARRAY) { 491 | if (this.tiles.length) { 492 | var tileIndex = this.tileSelector(this.tiles); 493 | tile = this.tiles.splice(tileIndex, 1)[0]; 494 | } 495 | } 496 | else if (this.placeMode === PlaceBot.placeMode.FUNCTION) { 497 | tile = this.tileGenerator(); 498 | } 499 | 500 | if (tile) { 501 | PlaceBot.placeModules.api.getPixelInfo(tile[0], tile[1]).then(function(data) { 502 | if (data.color !== tile[2]) { 503 | this.drawTile.apply(this, tile); 504 | } else { 505 | console.log('Redundant draw. Skipping.'); 506 | } 507 | 508 | this._setTimer(); 509 | }.bind(this),function(reason) { 510 | console.log("API failure."); 511 | this._setTimer(); 512 | }.bind(this)); 513 | 514 | this.save(); 515 | 516 | return; 517 | } else { 518 | console.log('No tile provided.'); 519 | } 520 | } else { 521 | console.log('Drawing not allowed. Rescheduling.'); 522 | } 523 | 524 | this._setTimer(); 525 | }; 526 | 527 | /** 528 | * @method drawTile 529 | * @property {Number} x - The tile x coordinate 530 | * @property {Number} y - The tile y coordinate 531 | * @property {Number} color - The index of the color to use 532 | */ 533 | PlaceBot.prototype.drawTile = function(x, y, color) { 534 | if (this.canDraw) { 535 | this.lastDrawTime = this.nextDrawTime; 536 | 537 | PlaceBot.place.setColor(color); 538 | PlaceBot.place.drawTile(x, y); 539 | 540 | console.log('Drawing %s at (%s, %s)', PlaceBot.place.palette[color], x, y); 541 | } 542 | }; 543 | })( 544 | typeof unsafeWindow !== 'undefined' ? unsafeWindow : 545 | typeof window !== 'undefined' ? window : 546 | {} 547 | ); 548 | -------------------------------------------------------------------------------- /dist/placebot.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";var e=function(){this.placeMode=e.placeMode.ARRAY,this.tiles=[],this.tileSelector=e.selector.TopDown,this._tileGeneratorFactory=void 0,this.tileGenerator=void 0,this.drawTimer=void 0,this.minTimer=100,this.lastDrawTime=0,console.log(["------------",,"PlaceBot "+e.version,"------------"].join("\n")),this.load(),this._setTimer()};Object.defineProperties(e.prototype,{cooldownRemaining:{get:function(){return this.nextDrawTime-Date.now()}},nextDrawTime:{get:function(){return e.place.cooldownEndTime}},canDraw:{get:function(){return this.cooldownRemaining<0&&this.lastDrawTime!==this.nextDrawTime}}}),e.version="0.0.9",e.placeMode={ARRAY:0,FUNCTION:1},e.selector={TopDown:function(t){var e=-1,o=1/0,i=1/0;return t.forEach(function(t,n){(t[1]i||t[1]===i&&t[0]>o)&&(e=n,o=t[0],i=t[1])}),e},LeftToRight:function(t){var e=-1,o=1/0,i=1/0;return t.forEach(function(t,n){(t[0] Bottom, Left -> Right 106 | TopDown: function(tiles) { 107 | var index = -1, 108 | minX = Infinity, 109 | minY = Infinity; 110 | 111 | tiles.forEach(function(tile, i) { 112 | if (tile[1] < minY || (tile[1] === minY && tile[0] < minX)) { 113 | index = i; 114 | minX = tile[0]; 115 | minY = tile[1]; 116 | } 117 | }); 118 | 119 | return index; 120 | }, 121 | 122 | // Bottom -> Top, Right -> Left 123 | BottomUp: function(tiles) { 124 | var index = -1, 125 | minX = -1, 126 | minY = -1; 127 | 128 | tiles.forEach(function(tile, i) { 129 | if (tile[1] > minY || (tile[1] === minY && tile[0] > minX)) { 130 | index = i; 131 | minX = tile[0]; 132 | minY = tile[1]; 133 | } 134 | }); 135 | 136 | return index; 137 | }, 138 | 139 | // Left -> Right, Top -> Bottom 140 | LeftToRight: function(tiles) { 141 | var index = -1, 142 | minX = Infinity, 143 | minY = Infinity; 144 | 145 | tiles.forEach(function(tile, i) { 146 | if (tile[0] < minX || (tile[0] === minX && tile[1] < minY)) { 147 | index = i; 148 | minX = tile[0]; 149 | minY = tile[1]; 150 | } 151 | }); 152 | 153 | return index; 154 | }, 155 | 156 | // Chooses any random tile 157 | Random: function(tiles) { 158 | return Math.floor(Math.random() * tiles.length); 159 | }, 160 | 161 | // Keeps the order that tiles were added 162 | DrawOrder: function(tiles) { 163 | return 0; 164 | } 165 | }; 166 | 167 | /** 168 | * @property {object} place - Reference to reddit's place object 169 | * @static 170 | */ 171 | PlaceBot.place = global.r.place; 172 | 173 | /** 174 | * @property {object} placeModules - References to any of reddit's place modules we might need 175 | * @static 176 | */ 177 | PlaceBot.placeModules = {}; 178 | 179 | // Import place modules 180 | var importModules = ['api']; 181 | global.r.placeModule('', function(require) { 182 | importModules.forEach(function(name) { 183 | PlaceBot.placeModules[name] = require(name); 184 | }); 185 | }); 186 | 187 | global.PlaceBot = PlaceBot; 188 | 189 | // Do this async so we finish loading the prototype 190 | setTimeout(function() { 191 | global.placeBot = new PlaceBot(); 192 | }, 0); 193 | })( 194 | typeof unsafeWindow !== 'undefined' ? unsafeWindow : 195 | typeof window !== 'undefined' ? window : 196 | {} 197 | ); 198 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | 'use strict'; 3 | 4 | var PlaceBot = global.PlaceBot; 5 | 6 | var localStorage = global.hasOwnProperty('localStorage') 7 | ? global.localStorage 8 | : { 9 | getItem: function() { return null; }, 10 | setItem: function() { } 11 | }; 12 | 13 | /** 14 | * Takes either JSON or an object, and returns an object 15 | * 16 | * @method _importObject 17 | * @property {Mixed} data - JSON or an object 18 | */ 19 | PlaceBot.prototype._importObject = function(data) { 20 | if ('string' === typeof data) { 21 | try { 22 | data = JSON.parse(data); 23 | } catch(e) { 24 | return { 25 | success: false, 26 | error: e.message 27 | }; 28 | } 29 | } 30 | 31 | return { 32 | success: true, 33 | data: data 34 | }; 35 | }; 36 | 37 | /** 38 | * Takes either string or function, and returns function 39 | * 40 | * @method _importFunction 41 | */ 42 | PlaceBot.prototype._importFunction = function(data) { 43 | if ('string' === typeof data) { 44 | try { 45 | data = eval('(' + data + ')'); 46 | } catch(e) { 47 | return { 48 | success: false, 49 | error: e.message 50 | }; 51 | } 52 | } 53 | 54 | if ('function' !== typeof data) { 55 | return { 56 | success: false, 57 | error: 'Invalid data type' 58 | }; 59 | } 60 | 61 | return { 62 | success: true, 63 | data: data 64 | }; 65 | }; 66 | 67 | /** 68 | * Collects saveable tile info into one object 69 | * 70 | * @method _tilesObject 71 | */ 72 | PlaceBot.prototype._tilesObject = function() { 73 | var obj = { 74 | mode: this.placeMode, 75 | tiles: this.tiles 76 | }; 77 | 78 | if (this.placeMode === PlaceBot.placeMode.ARRAY) { 79 | var name = this.tileSelector.name; 80 | 81 | if (PlaceBot.selector.hasOwnProperty(this.tileSelector.name)) { 82 | obj.fn = name; 83 | } else { 84 | obj.fn = this.tileSelector.toString(); 85 | } 86 | } 87 | else if (this.placeMode === PlaceBot.placeMode.FUNCTION) { 88 | obj.fn = this._tileGeneratorFactory.toString(); 89 | } 90 | 91 | return obj; 92 | }; 93 | 94 | /** 95 | * Collects saveable settings into one object 96 | * 97 | * @method _settingsObject 98 | */ 99 | PlaceBot.prototype._settingsObject = function() { 100 | return { 101 | minTimer: this.minTimer 102 | }; 103 | }; 104 | 105 | /** 106 | * Returns JSON of the current tiles 107 | * 108 | * @method exportTiles 109 | */ 110 | PlaceBot.prototype.exportTiles = function() { 111 | return JSON.stringify(this._tilesObject()); 112 | }; 113 | 114 | /** 115 | * Returns JSON of the current settings 116 | * 117 | * @method exportSettings 118 | */ 119 | PlaceBot.prototype.exportSettings = function() { 120 | return JSON.stringify(this._settingsObject()); 121 | }; 122 | 123 | /** 124 | * Returns JSON of the current settings and tiles 125 | * 126 | * @method exportBot 127 | */ 128 | PlaceBot.prototype.exportBot = function() { 129 | return JSON.stringify({ 130 | settings: this._settingsObject(), 131 | tiles: this.tiles 132 | }); 133 | }; 134 | 135 | /** 136 | * Imports tiles as JSON or object 137 | * 138 | * @method importTiles 139 | */ 140 | PlaceBot.prototype.importTiles = function(tilesJSON) { 141 | var imported = this._importObject(tilesJSON); 142 | 143 | if (!imported.success) { 144 | console.log('Failed to import tiles: %s', imported.error); 145 | return false; 146 | } 147 | 148 | var tiledata = Object.assign(this._tilesObject(), imported.data); 149 | 150 | this.tiles = tiledata.tiles || []; 151 | this.setTileFunction(tiledata.mode, tiledata.fn); 152 | }; 153 | 154 | /** 155 | * Imports settings as JSON or object 156 | * 157 | * @method importSettings 158 | */ 159 | PlaceBot.prototype.importSettings = function(settingsJSON) { 160 | var imported = this._importObject(settingsJSON); 161 | 162 | if (!imported.success) { 163 | console.log('Failed to import settings: %s', imported.error); 164 | return false; 165 | } 166 | 167 | var settings = Object.assign(this._settingsObject(), imported.data); 168 | 169 | this.minTimer = settings.minTimer; 170 | }; 171 | 172 | /** 173 | * Imports settings and tiles as JSON or object 174 | * 175 | * @method importBot 176 | */ 177 | PlaceBot.prototype.importBot = function(botJSON) { 178 | var imported = this._importObject(botJSON); 179 | 180 | if (!imported.success) { 181 | console.log('Failed to import bot: %s', imported.error); 182 | return false; 183 | } 184 | 185 | this.importSettings(imported.data.settings); 186 | this.importTiles(imported.data.tiles); 187 | }; 188 | 189 | /** 190 | * Persist settings and tiles to localStorage 191 | * 192 | * @method save 193 | */ 194 | PlaceBot.prototype.save = function() { 195 | localStorage.setItem('placebot_settings', this.exportSettings()); 196 | localStorage.setItem('placebot_tiles', this.exportTiles()); 197 | }; 198 | 199 | /** 200 | * Load settings and tiles from localStorage 201 | * 202 | * @method load 203 | */ 204 | PlaceBot.prototype.load = function() { 205 | this.importSettings(localStorage.getItem('placebot_settings')); 206 | this.importTiles(localStorage.getItem('placebot_tiles')); 207 | }; 208 | 209 | /** 210 | * 211 | */ 212 | PlaceBot.prototype.setTileFunction = function(mode, fn) { 213 | var imported; 214 | 215 | switch (mode) { 216 | case PlaceBot.placeMode.ARRAY: 217 | this.placeMode = PlaceBot.placeMode.ARRAY; 218 | this.tileGenerator = undefined; 219 | 220 | if ('string' === typeof fn && PlaceBot.selector.hasOwnProperty(fn)) { 221 | this.tileSelector = PlaceBot.selector[fn]; 222 | } 223 | else { 224 | imported = this._importFunction(fn); 225 | 226 | if (imported.success) { 227 | this.tileSelector = imported.data; 228 | } 229 | else { 230 | this.tileSelector = PlaceBot.selector.TopDown; 231 | } 232 | } 233 | 234 | break; 235 | 236 | case PlaceBot.placeMode.FUNCTION: 237 | this.placeMode = PlaceBot.placeMode.FUNCTION; 238 | this.tileSelector = undefined; 239 | 240 | imported = this._importFunction(fn); 241 | 242 | if (imported.success) { 243 | this._tileGeneratorFactory = imported.data; 244 | this.tileGenerator = imported.data(this); 245 | break; 246 | } 247 | 248 | console.log('Function import failed: %s', imported.error); 249 | // fall through to default 250 | 251 | default: 252 | this.tiles = []; 253 | this.setTileFunction(PlaceBot.placeMode.ARRAY, 'TopDown'); 254 | } 255 | }; 256 | })( 257 | typeof unsafeWindow !== 'undefined' ? unsafeWindow : 258 | typeof window !== 'undefined' ? window : 259 | {} 260 | ); 261 | --------------------------------------------------------------------------------