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);