High-Low.user.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. // ==UserScript==
  2. // @name High-Low
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.10
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://www.torn.com/loader.php?sid=high*ow
  8. // @require https://cdn.jsdelivr.net/npm/vue/dist/vue.js
  9. // @resource sourceCodePro https://fonts.googleapis.com/css?family=Source+Code+Pro
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.10.0/highlight.min.js
  11. // @resource json-formatter_css https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.10.0/styles/default.min.css
  12. // @resource pure https://unpkg.com/purecss@1.0.1/build/pure-min.css
  13. // @resource rocker https://relentless.rocks/rocker.css
  14. // @grant GM_getResourceText
  15. // @grant GM_addStyle
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // ==/UserScript==
  19. /* jshint -W097 */
  20. /*global
  21. $, console, Raven, GM_addStyle, GM_getResourceText, Vue, hljs
  22. */
  23. (function() {
  24. 'use strict';
  25. var sourceCodePro = GM_getResourceText("codeSourcePro");
  26. GM_addStyle(sourceCodePro);
  27. GM_addStyle("pre,code {font-family: 'Source Code Pro', monospace; font-size: 14px;}");
  28. var js_css = GM_getResourceText("json-formatter_css");
  29. GM_addStyle(js_css);
  30. //var bootstrap_css = GM_getResourceText("bootstrap_css");
  31. //GM_addStyle(bootstrap_css);
  32. let pure = GM_getResourceText('pure');
  33. GM_addStyle(pure);
  34. let rocker_css = GM_getResourceText('rocker');
  35. GM_addStyle(rocker_css);
  36. GM_addStyle(".hljs-number { color: blue; }");
  37. var formatterConfig = {
  38. hoverPreviewEnabled: false,
  39. hoverPreviewArrayCount: 100,
  40. hoverPreviewFieldCount: 5,
  41. theme: 'dark',
  42. animateOpen: true,
  43. animateClose: true,
  44. open: 20
  45. };
  46. var changeTimeout = function() {
  47. var oldTimeout = setTimeout;
  48. /*global setTimeout:true */
  49. setTimeout = function(a, b) {
  50. return oldTimeout(a, b/3);
  51. };
  52. console.log("setTimout: changed");
  53. };
  54. let script = document.createElement('script');
  55. script.appendChild(document.createTextNode('('+ changeTimeout +')();'));
  56. (document.head || document.body || document.documentElement).appendChild(script);
  57. class Card {
  58. constructor(number) {
  59. this.number = parseInt(number, 10);
  60. let card = this.getCardFromNumber(this.number);
  61. this.color = card[0];
  62. this.rank = card[1];
  63. }
  64. getCardFromNumber(number) {
  65. let mod = (number-1) % 4;
  66. let color = "";
  67. if (mod === 0) {
  68. color = "spades";
  69. }
  70. if (mod === 1) {
  71. color = "diamonds";
  72. }
  73. if (mod === 2) {
  74. color = "hearts";
  75. }
  76. if (mod === 3) {
  77. color = "clubs";
  78. }
  79. let offset = 0;
  80. offset += 4; // Counter for 2 being first card, not 1
  81. offset += 3; // Counter for using floor
  82. let rank = Math.floor((parseInt(number, 10)+offset)/4);
  83. return [color, rank];
  84. }
  85. toString() {
  86. let rank = this.rank.toString();
  87. console.log("rank:", rank);
  88. /* if (rank === "10") {
  89. rank = "T";
  90. } */
  91. if (rank === "11") {
  92. rank = "J";
  93. }
  94. if (rank === "12") {
  95. rank = "Q";
  96. }
  97. if (rank === "13") {
  98. rank = "K";
  99. }
  100. if (rank === "14") {
  101. rank = "A";
  102. }
  103. return this.color + "-" + rank;
  104. }
  105. toValue() {
  106. return this.rank;
  107. }
  108. }
  109. /* global */
  110. let action = {};
  111. let formula = {};
  112. let won = 0;
  113. let lost = -1;
  114. let ratio = '0.000000';
  115. /* Initialize deck */
  116. let deck = GM_getValue('deck', []);
  117. let createDeck = (deck) => {
  118. for (let i = 2; i < 54; i++) {
  119. deck.push(i);
  120. }
  121. GM_setValue('deck', deck);
  122. console.warn('new deck.length:', deck.length);
  123. // console.log(deck);
  124. action.cards = deck.length;
  125. return deck;
  126. }
  127. if (deck.length === 0) {
  128. deck = createDeck(deck);
  129. }
  130. let removeCard = (deck, card) => {
  131. /*
  132. console.log('[removeCard] card:', card);
  133. console.log('typeof card:', typeof card);
  134. console.log('typeof deck[5]:', typeof deck[5]);
  135. */
  136. for( var i = 0; i < deck.length; i++) {
  137. if ( deck[i] === card) {
  138. deck.splice(i, 1);
  139. }
  140. }
  141. GM_setValue('deck', deck);
  142. action.cards = deck.length;
  143. // console.log('deck.length:', deck.length);
  144. }
  145. //********
  146. // Controller
  147. //********
  148. let reset = () => {
  149. // Reset
  150. // See https://openuserjs.org/install/DeKleineKobini/TORN_HighLow_Helper.user.js
  151. $(".actions-wrap")[0].style = "display: block";
  152. $(".actions")[0].appendChild($(".startGame")[0])
  153. $(".startGame")[0].style = "display:inline-block";
  154. $(".low")[0].style = "display: none";
  155. $(".high")[0].style = "display: none";
  156. $(".continue")[0].style = "display: none";
  157. }
  158. let processStartGame = function(db) {
  159. let formulaStats = db.DB.formulaStats[0];
  160. let previousMultiplier = 0;
  161. let currentMultiplier = 0;
  162. if (formulaStats) {
  163. currentMultiplier = formulaStats.currentRatio;
  164. previousMultiplier = formulaStats.previousRatio;
  165. if (previousMultiplier !== currentMultiplier) {
  166. console.log("Multiplier changed!. Old: " + previousMultiplier + " => New: " + currentMultiplier);
  167. }
  168. }
  169. console.log("Multiplier:", currentMultiplier);
  170. //$("pre code.json").empty().append(JSON.stringify(db, null, 4));
  171. console.log('myVue:', myVue);
  172. myVue.jsonobject = JSON.stringify(db, null, 4);
  173. // myVue.jsonobject = "Test";
  174. // action.header = "Test";
  175. Vue.nextTick(function() {
  176. hljs.initHighlighting.called = false;
  177. hljs.initHighlighting();
  178. });
  179. reset();
  180. };
  181. let processGameStarted = function(db) {
  182. // dealerCard is revealed; player picks High or Low
  183. // Note: Player also has the option to cash in but that is never considered
  184. // console.log("dealerCard:", db.currentGame[0].dealerCard);
  185. let dealerCard = new Card(db.currentGame[0].dealerCard);
  186. // console.warn('dealerCard.number:', dealerCard.number);
  187. removeCard(deck, dealerCard.number);
  188. if ('result' in db.currentGame[0]) {
  189. console.log('LOST');
  190. removeCard(deck, parseInt(db.currentGame[0].playerCard, 10));
  191. }
  192. console.assert(dealerCard.toString() === db.currentGame[0].dealerCardInfo.classCode,
  193. "Mismatch dealerCard " + dealerCard + " <-> " + db.currentGame[0].dealerCardInfo.classCode);
  194. console.log("dealerCard:", dealerCard.toString());
  195. let arrayWithOdds = calculateOdds(dealerCard, deck);
  196. let lower = arrayWithOdds[0];
  197. let higher = arrayWithOdds[1];
  198. let numberOfCards = arrayWithOdds[2];
  199. // testing
  200. //$('div.action-c.action-btn-wrap.active.low').addClass('iconPulseExpiring');
  201. //$('div.action-r.action-btn-wrap.active.high').removeClass('active');
  202. $(".startGame")[0].style = "display: none";
  203. if (lower >= higher) {
  204. $(".high")[0].style = "display: none";
  205. $(".low")[0].style = "display: inline-block";
  206. } else if (higher > lower) {
  207. $(".low")[0].style = "display: none";
  208. $(".high")[0].style = "display: inline-block";
  209. }
  210. };
  211. let processMakeChoice = function(db) {
  212. // The deal has ended. The choice is cash in or play on.
  213. if (db.currentGame[0].dealerCard !== db.currentGame[0].lastDealerCard) {
  214. // Reveal dealerCard of next deal
  215. // Page has been refreshed
  216. // There is no playerCard (there is a lastPlayerCard)
  217. db.currentGame[0].playerCard = db.currentGame[0].lastPlayerCard;
  218. db.currentGame[0].playerCardInfo = db.currentGame[0].lastPlayerCardInfo;
  219. // dealerCard assert will fail!
  220. }
  221. console.log("step:", db.currentGame[0].step);
  222. let dealerCard = new Card(db.currentGame[0].dealerCard);
  223. console.assert(dealerCard.toString() === db.currentGame[0].dealerCardInfo.classCode,
  224. "Mismatch dealerCard " + dealerCard + " <-> " + db.currentGame[0].dealerCardInfo.classCode);
  225. let playerCard = new Card(db.currentGame[0].playerCard);
  226. console.assert(playerCard.toString() === db.currentGame[0].playerCardInfo.classCode,
  227. "Mismatch playerCard " + playerCard + " <-> " + db.currentGame[0].playerCardInfo.classCode);
  228. console.log("playerCard:", playerCard.toString());
  229. removeCard(deck, parseInt(db.currentGame[0].playerCard, 10));
  230. /*
  231. if (db.DB.deckShuffled) {
  232. console.log('[processMakeChoice] deckShuffled', db.DB.deckShuffled);
  233. // New deck
  234. deck = [];
  235. deck = createDeck(deck);
  236. }
  237. */
  238. };
  239. $(document).ajaxSuccess(
  240. // On every detected Ajax communication
  241. function(event, jqxhr, settings, data) {
  242. // Make sure it was to or from /loader.php?sid=hiloJson&rfcv=<rfcv>
  243. if (settings.url.indexOf("loader.php?sid=hiloJson") != -1) {
  244. //console.log("event:", event);
  245. //console.log("jqxhr:", jqxhr);
  246. //console.log("settings:", settings);
  247. let db = JSON.parse(data);
  248. // $("pre code.json").empty().append(JSON.stringify(db.currentGame, null, 4));
  249. myVue.jsonobject = JSON.stringify(db.currentGame, null, 4);
  250. // hljs.initHighlighting.called = false;
  251. // hljs.initHighlighting();
  252. /*
  253. Vue.nextTick(function() {
  254. hljs.initHighlighting.called = false;
  255. hljs.initHighlighting();
  256. });
  257. */
  258. console.log("db:", db);
  259. if ("currentGame" in db && "result" in db.currentGame[0] && db.currentGame[0].result === "Incorrect") {
  260. db.status = "Incorrect";
  261. }
  262. let status = db.status;
  263. console.log("db.status:", db.status);
  264. myVue.header = status;
  265. if ('DB' in db) {
  266. if ('formulaStats' in db.DB) {
  267. console.warn('Changing formulaStats');
  268. console.log(db.DB.formulaStats[0]);
  269. myVue.won = parseInt(db.DB.formulaStats[0].moneyWon);
  270. myVue.lost = parseInt(db.DB.formulaStats[0].moneyLost);
  271. won = parseInt(db.DB.formulaStats[0].moneyWon);
  272. lost = parseInt(db.DB.formulaStats[0].moneyLost);
  273. myVue.ratio = (won/lost).toFixed(6);
  274. let formatNumber = new Intl.NumberFormat('en-US')
  275. db.DB.formulaStats[0].moneyWon = formatNumber.format(db.DB.formulaStats[0].moneyWon);
  276. db.DB.formulaStats[0].moneyLost = formatNumber.format(db.DB.formulaStats[0].moneyLost);
  277. myVue.formula = db.DB.formulaStats[0];
  278. }
  279. }
  280. Vue.nextTick(function() {
  281. hljs.initHighlighting.called = false;
  282. hljs.initHighlighting();
  283. });
  284. // Moved this to 'main' because it missed a few before
  285. if ('deckShuffled' in db.DB) {
  286. console.warn('db.DB.deckShuffled:', db.DB.deckShuffled);
  287. if (db.DB.deckShuffled === true) {
  288. deck = [];
  289. deck = createDeck(deck);
  290. }
  291. }
  292. if (status === "startGame") {
  293. action.lower = 0;
  294. action.higher = 0;
  295. processStartGame(db);
  296. }
  297. if (status === "gameStarted") {
  298. processGameStarted(db);
  299. }
  300. if (status === "makeChoice") {
  301. action.lower = 0;
  302. action.higher = 0;
  303. processMakeChoice(db);
  304. }
  305. if (status === "Incorrect") {
  306. reset();
  307. }
  308. //console.log("db.currentGame:", db.currentGame);
  309. }
  310. });
  311. //********
  312. // VIEW
  313. //********
  314. let boxTitle = "Let's play High-Low";
  315. let boxHTML = `<div class="tutorial-cont m-top10">
  316. <div class="title-gray top-round" role="heading" aria-level="5">
  317. <i class="tutorial-icon"></i>
  318. <span style="padding-left: 6px">` + boxTitle + `</span>
  319. </div>
  320. <div class="bottom-round cont-gray p10">
  321. <div id="app" style="min-height: 450px;">
  322. <div style="float: left; width: 50%;">
  323. <action-header v-bind:header="header"></action-header>
  324. <action-table v-bind:action="action"></action-table>
  325. <h1 style="margin: .67 em; margin-top: 10px;">Most recent formulaStats</h1>
  326. <formula-stats v-bind:formula="formula"></formula-stats>
  327. <ratio-calculation v-bind:ratio="ratio"></ratio-calculation>
  328. </div>
  329. <div id="right" style="float: right; width: 50%;">
  330. <!-- <pre><code class="json"></code></pre> -->
  331. <vue-object v-bind:jsonobject="jsonobject"></vue-object>
  332. </div>
  333. </div>
  334. <div class="clear"></div>
  335. <hr class="page-head-delimiter m-top10"
  336. </div>
  337. </div>`;
  338. $('.highlow-main-wrap').after(boxHTML);
  339. // Vue.js template
  340. //creating component
  341. var actionHeader = Vue.extend({
  342. template: '<h1 v-bind:style="actionStyle">{{ header }}</h1>',
  343. data: function() {
  344. return {
  345. // action: "Current Action",
  346. actionStyle: {
  347. marginTop: "0px"
  348. }
  349. };
  350. },
  351. props: ['header']
  352. });
  353. var vueObject = Vue.extend({
  354. template: '<div v-bind:style="vueObjectStyle"><pre><code class="json" v-html="jsonobject"></code></pre></div>',
  355. data: function() {
  356. return {
  357. vueObjectStyle: {
  358. marginTop: "0px"
  359. }
  360. };
  361. },
  362. props: ['jsonobject']
  363. });
  364. let templateAction = `<table class="pure-table"><caption>Odds Table</caption><thead>
  365. <tr><th>Action</th><th>Places</th><th>Odds</th></tr>
  366. </thead><tbody>
  367. <tr><td>Lower</td><td>{{ action.lower }}</td><td>{{ action.lower | calculatePercentage }} %</td></tr>
  368. <tr><td>Higher</td><td>{{ action.higher }}</td><td>{{ action.higher | calculatePercentage }} %</td></tr>
  369. <tr><td>Deck</td><td>{{ action.cards }}</td><td>100 %</td></tr>
  370. </tbody></table>`;
  371. var actionTable = Vue.extend({
  372. template: templateAction,
  373. filters: {
  374. calculatePercentage: (places) => {
  375. return Math.round(places * 100 / deck.length);
  376. }
  377. },
  378. props: ['action']
  379. });
  380. let templateRatio = "<div><h1 style=\"margin: .67 em; margin-top: 10px;\">Ratio</h1><pre><code class=\"json\" v-html=\"ratio\"></code></pre></div>";
  381. /* let ratioCalculation = Vue.extend({
  382. template: templateRatio,
  383. props: ['won', 'lost'],
  384. data: function () {
  385. return {
  386. wwon: won,
  387. llost: this.lost
  388. }
  389. },
  390. computed: {
  391. ratio: function() {
  392. console.log('[Ratio] won:', this.wwon);
  393. console.log('[Ratio] lost:', this.llost);
  394. if (this.lost === 0) {
  395. return '0.000000';
  396. } else {
  397. console.log('[Ratio] ratio:', (this.won/this.lost).toFixed(6));
  398. return (this.won/this.lost).toFixed(6);
  399. }
  400. }
  401. }
  402. }); */
  403. let ratioCalculation = Vue.extend({
  404. template: templateRatio,
  405. props: ['ratio']
  406. });
  407. let templateStats = '<div><pre><code class="json" v-html="formula"></code></pre></div>';
  408. let formulaStats = Vue.extend({
  409. template: templateStats,
  410. props: ['formula']
  411. });
  412. //registering component
  413. Vue.component('action-header', actionHeader);
  414. Vue.component('vue-object', vueObject);
  415. Vue.component('action-table', actionTable);
  416. Vue.component('formula-stats', formulaStats);
  417. Vue.component('ratio-calculation', ratioCalculation);
  418. //initializing the Vue application
  419. action.cards = deck.length;
  420. action.lower = 0;
  421. action.higher = 0;
  422. let header = "Current Action";
  423. let myVue = new Vue({
  424. el: "#app",
  425. data: function() {
  426. return {
  427. jsonobject: "Start",
  428. header: header,
  429. action: action,
  430. formula: formula,
  431. ratio: ratio
  432. };
  433. }
  434. });
  435. //********
  436. // MODEL
  437. //********
  438. /**
  439. * @param {array} deck, cards by number
  440. * @return {array} number of cards by rank where key is the rank \
  441. * and val is the number of that rank in the deck
  442. */
  443. let parseDeck = function(deck) {
  444. // Create new parsedDeck from deck
  445. let parsedDeck = [];
  446. // parsedDeck runs from 0 to 14 where 0 and 1 are always 0.
  447. // Real deck values count from 2 to 14 (ace)
  448. for (let i = 0; i <= 15; i++) {
  449. parsedDeck[i] = 0;
  450. }
  451. let deckLength = deck.length;
  452. for (let i = 0; i < deckLength; i++) {
  453. /*
  454. if (i < 10) {
  455. console.log('first cards:', deck[i]);
  456. }
  457. */
  458. let offset = 0;
  459. offset += 4; // Counter for 2 being first card, not 1
  460. offset += 3; // Counter for using floor
  461. let rank = Math.floor((parseInt(deck[i], 10)+offset)/4);
  462. parsedDeck[rank]++;
  463. }
  464. if (deckLength === 0) {
  465. // Shuffle -> create new deck
  466. for (let i = 2; i < 15; i++) {
  467. parsedDeck[i] = 4;
  468. }
  469. }
  470. return parsedDeck;
  471. };
  472. let calculateOdds = function(dealerCard, deck) {
  473. let parsedDeck = parseDeck(deck);
  474. let deckLength = parsedDeck.length;
  475. let numberOfCards = 0;
  476. let lowerDoesNotLose = 0; // Equals count as 'not lose'
  477. let higherDoesNotLose = 0; // Equals count as 'not lose'
  478. for (let i = 0; i < deckLength; i++) {
  479. if (i <= dealerCard.rank) {
  480. lowerDoesNotLose += parsedDeck[i];
  481. }
  482. if (i >= dealerCard.rank) {
  483. higherDoesNotLose += parsedDeck[i];
  484. }
  485. numberOfCards += parsedDeck[i];
  486. }
  487. console.assert(deck.length === numberOfCards, 'deck.length: '+deck.length+' <> numberOfCards: '+numberOfCards);
  488. // console.log("[calculteOdds] parsedDeck;", parsedDeck);
  489. action.lower = lowerDoesNotLose;
  490. action.higher = higherDoesNotLose;
  491. console.log("[calculateOdds] lowerDoesNotLose:", lowerDoesNotLose);
  492. console.log("[calculateOdds] higherDoesNotLose:", higherDoesNotLose);
  493. console.log("[calculateodds] numberOfCards:", numberOfCards);
  494. return [lowerDoesNotLose, higherDoesNotLose, numberOfCards];
  495. };
  496. })();