NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @namespace https://openuserjs.org/users/Merlin-R // @name Torn Blackjack Engine // @description Automatically play Torn Blackjack // @copyright 2018, Merlin-R (https://openuserjs.org/users/Merlin-R) // @license MIT // @version 1.1.0 // @include https://www.torn.com/* // @grant none // ==/UserScript== // ==OpenUserJS== // @author Merlin-R // ==/OpenUserJS== /********************************************************************************* * Copyright 2018 Merlin Reichwald * * * * Permission is hereby granted, free of charge, to any person obtaining a copy * * of this software and associated documentation files (the "Software"), to deal * * in the Software without restriction, including without limitation the rights * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included in * * all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * * SOFTWARE. * *********************************************************************************/ /**************************************** * Example Usage: * * new Blackjack(1000000,4).autoPlay(); * ****************************************/ class Blackjack { /** * Creates a new Blackjack Controller Engine * @param {number} basebet The base bet to build uppon * @param {number} limit The highest chain before realizing wins */ constructor(basebet, limit) { this.basebet = basebet; this.limit = limit; this.count = 0; this.$bj = $('.blackjack-wrap'); } async playTurn() { await this.bet( this.decideBet() ); while ( !(this.isGameOver()) ) await this.perform( this.decide(), this.sumHand( this.playerHand() ) ); if ( !this.hasWon() ) if ( this.hasLost() ) this.count -= 3; else this.count -= 1; if ( this.count < 0 ) this.count = 0; await this.continueGame(); } async autoPlay() { while( this.tokensLeft() ) await this.playTurn(); } tokensLeft() { return +this.$bj.find('.bj-tokens').first().text(); } hasWon() { return !!$('.wl-msg.won').length; } hasLost() { return !!$('.wl-msg.lost').length; } /** * Confirms the action that is to be performed * @return {Promise} resolves after confirmation */ async confirmAction() { this.$bj.find('.confirm-action.yes').tornClick(); await this.wait( Blackjack.lowDelay ); } /** * Clicks continue after a finished game * @return {Promise} resolves after the button was clicked */ async continueGame() { this.$bj.find('.continue').tornClick(); await this.wait( Blackjack.lowDelay ); } /** * Performs a given action * @param {string} action the action to perform * @return {Promise} resolves after the action was performed */ async perform( action, points ) { var backup = Blackjack.table[ points ]; console.log( action ); switch( action ) { case 'S': await this.clickGameButton( 'stand' ); break; case 'H': await this.clickGameButton( 'hit-me' ); break; case 'P': await this.clickGameButton( 'split', points > 11 ? 'stand' : 'hit-me', true ); await this.continueGame(); await this.perform( this.decide(), this.sumHand( this.playerHand() ) ); break; case 'D': await this.clickGameButton( 'hit-me',/*dd*/ 'hit-me', true ); break; case 'F': await this.clickGameButton( 'surrender', 'hit-me', true ); break; case 'T': if ( this.canClickButton( 'split' ) && this.canClickButton( 'hit-me' /*dd*/ ) ) { await this.clickGameButton( 'split', undefined, true ); await this.wait( Blackjack.delay ); await this.clickGameButton( 'hit-me' /*dd*/, undefined, true ); await this.continueGame(); await this.perform( this.decide(), this.sumHand( this.playerHand() ) ); } else { await this.clickGameButton( 'split', points > 11 ? 'stand' : 'hit-me', true ); } break; } await this.wait( Blackjack.delay ); } async clickGameButton( button, fallback, confirm ) { if ( this.canClickButton( button ) ) this.$bj.find('#' + button + ' area').tornClick(); else if ( fallback ) this.$bj.find('#' + fallback + ' area').tornClick(); else return; if ( confirm ) { await this.wait( Blackjack.lowDelay ); await this.confirmAction(); } } canClickButton( button ) { return !this.$bj.find( '#' + button ).parent().find( 'img' ).hasClass( 'disable' ); } /** * Returns if a game is over * @return {Boolean} wether the game is over or not */ isGameOver() { return !!this.$bj.find('.win-lose-wrap.bj-show').length; } /** * Decides the bet depending on the base bet, current session length and limit * @return {number} The amount to bet */ decideBet() { if ( ++this.count > this.limit ) this.count = 1; return Blackjack.fib[ this.count ] * this.basebet; } /** * Decides how to react to a given board. * Possible return values are: * H: Hold * S: Stand * P: Split * D: Double Down * F: French / Surrender * T: Split, then Double Down * @return {string} the best reaction to the current board */ decide() { let player = this.playerHand(); let dealer = this.dealerHand(); let pkey = player.join( '' ); let dkey = dealer.length > 1 ? this.sumHand( dealer ) : dealer[ 0 ]; if ( this.sumHand( player, true ) >= 18 ) return 'S'; var row = Blackjack.table[ pkey ]; if ( row ) return row[ dkey ]; row = Blackjack.table[ this.sumHand( player ) ]; if ( row ) return row[ dkey ]; if ( this.sumHand( player ) > 19 ) return 'S'; throw new Error("Don't know what to do with hand "+pkey+" against "+dkey); } /** * Sum all points in a hand * @param {string[]} hand the hand to sum * @param {boolean=} high form the high sum (Aces count 11 or 1) * @return {number} the calculated sum of points */ sumHand( hand, high ) { let result = 0; hand.forEach( card => { if ( card === 'X' ) result += 10; else if ( card === 'A' ) result += high ? 11 : 1; else result += +card }); return result; } /** * get a list of card identifiers for the dealer hand * @return {string[]} the card identifiers */ dealerHand() { return this.hand('.dealer-cards'); } /** * get a list of card identifiers for the player hand * @return {string[]} the card identifiers */ playerHand() { return this.hand('.player-cards'); } /** * get a list of card identifiers for the given board class * @param {string} cls the board css class * @return {string[]} the card identifiers */ hand( cls ) { return this.formatHand($(cls + ' .table-cards-wrap .cards .inplace:not(.card-back)').map((i,e) => $(e).attr('class').split(' ').filter( cls => cls !== 'inplace' )[ 0 ])); } /** * Formats CSS Classes for torn cards to single letter card descriptors * @param {string[]} hand The hand to be formatted * @return {string[]} the formatted hand */ formatHand( hand ) { return Array.from( hand ).map( cardClass => cardClass.substr( 5 ).replace(/(spades|clubs|hearts|diamonds)-/g,'').replace( /(J|Q|K|10)/g, 'X' ) ).sort().reverse(); } /** * Bet a given amount and start the game * @param {number} amount The amount of money to bet * @return {Promise} Resolves after game is started. */ async bet( amount ) { this.$bj.find('input.bet').tornInput( amount ); await this.wait( Blackjack.lowDelay ); await this.confirm(); } /** * Confirms the game start dialog. * @return {Promise} Resolves after confirmation */ async confirm() { this.$bj.find( '.bet-confirm' ).find( '.yes' ).tornClick(); await this.wait( Blackjack.delay ); } /** * Waits a given amount of time before resolving * @param {number} ms The amount of milliseconds to wait for * @param {T=} ret An obtional return value to resolve with * @return {Promise<T>} The return value if one was given * @template T */ wait( ms, ret ) { return new Promise( resolve => { setTimeout( resolve.bind( this, ret ), ms ); }); } } Blackjack.lowDelay = 5000; Blackjack.delay = 10000; /** * Blackjack Constants * @type {Object.<string,Object<string,string>>} */ Blackjack.table = {"5":{"2":"H","3":"H","4":"H","5":"H","6":"H","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"5"},"6":{"2":"H","3":"H","4":"H","5":"H","6":"H","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"6"},"7":{"2":"H","3":"H","4":"H","5":"H","6":"H","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"7"},"8":{"2":"H","3":"H","4":"H","5":"H","6":"H","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"8"},"9":{"2":"H","3":"D","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"9"},"11":{"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","X":"D","A":"H","KEY":"11"},"12":{"2":"H","3":"H","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"12"},"13":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"13"},"14":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"14"},"15":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","X":"F","A":"H","KEY":"15"},"16":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"F","X":"F","A":"F","KEY":"16"},"17":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"17"},"18":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"18"},"19":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"19"},"22":{"2":"T","3":"T","4":"P","5":"P","6":"P","7":"P","8":"S","9":"S","X":"S","A":"S","KEY":"22"},"33":{"2":"T","3":"T","4":"P","5":"P","6":"P","7":"P","8":"H","9":"H","X":"H","A":"H","KEY":"33"},"44":{"2":"H","3":"H","4":"H","5":"T","6":"T","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"44"},"55":{"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","X":"H","A":"H","KEY":"55"},"66":{"2":"T","3":"P","4":"P","5":"P","6":"P","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"66"},"77":{"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"H","9":"H","X":"H","A":"H","KEY":"77"},"88":{"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"P","9":"P","X":"P","A":"P","KEY":"88"},"99":{"2":"P","3":"P","4":"P","5":"P","6":"P","7":"S","8":"P","9":"P","X":"S","A":"S","KEY":"99"},"10":{"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","X":"H","A":"H","KEY":"X"},"A2":{"2":"H","3":"H","4":"H","5":"D","6":"D","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"A2"},"A3":{"2":"H","3":"H","4":"H","5":"D","6":"D","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"A3"},"A4":{"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"A4"},"A5":{"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"A5"},"A6":{"2":"H","3":"D","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","X":"H","A":"H","KEY":"A6"},"A7":{"2":"S","3":"D","4":"D","5":"D","6":"D","7":"S","8":"S","9":"H","X":"H","A":"H","KEY":"A7"},"A8":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"A8"},"A9":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"A9"},"XX":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"XX"},"AA":{"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"P","9":"P","X":"P","A":"P","KEY":"AA"},"XA":{"2":"S","3":"S","4":"S","5":"S","6":"S","7":"S","8":"S","9":"S","X":"S","A":"S","KEY":"AX"}}; /** * Fibonacci Sequence * @type {number[]} */ Blackjack.fib = [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050 ]; // Torn Utility Functions $.fn.tornInput = function (value) { return $(this).val(value).each((i, e) => { e.dispatchEvent(new CustomEvent("input", { bubbles: true })); e.dispatchEvent(new CustomEvent('keyup',{bubbles:true})); }); } $.fn.tornClick = function () { return $(this).each((i, e) => e.dispatchEvent(new CustomEvent("click", { bubbles: true }))); } window.Blackjack = Blackjack; setTimeout(function(){ var btnAuto = $('<a class="action-btn-wrap"><div class="action-btn left" /><div class="action-btn right" />AUTO<div class="clear" /></a>'); btnAuto.click(() => { var bet = +$('.blackjack-wrap input.bet').val(); new Blackjack( bet, 4 ).autoPlay(); }) $('.blackjack-wrap .bet-action').append( btnAuto ); }, 5000);