// ==UserScript== // @name onlinetichu.com card counter // @version 1.2.3 // @description automatically count cards at onlinetichu.com // @match *://*.onlinetichu.com/Site/Game/Table/* // @author Maximilian Keßler // @namespace https://gitlab.com/kesslermaximilian/onlinetichu-counter // @license MIT // ==/UserScript== // disable auto fold functionality completely function injectCounter() { if (ot.AutoFold) { ot.$autoFold.click() ot.$autoFold.hide() } var myStyles = ` #playedHigh { position: absolute; top: 45px; left: 5px; width: 935px; height: 120px; } #playedLow { position: absolute; top: 170px; left: 5px; width: 935px; height: 120px; } #counterField { position: absolute; width: 945px; height: 300px; top: 635px; border: 1px solid black; box-shadow: 1px 1px 1px #555; background: #88CC00; } #controlBar { position: absolute; min-height: 32px; width: 925px; top: 5px; left: 5px; border-radius: 20px; background: #000000; } #controlButtonsLeft { position: absolute; top: 4px; left: 15px; } #controlButtonsRight { position: absolute; top: 4px; right: 15px; } .playedCardsLayout { position: absolute; height: 125px; } .playedCardsLayout .card { width: 80px; height: 120px; border-radius: 5px; float: left; margin-left: -50px; text-align: left; box-shadow: 2px 2px 5px #555; border: 1px solid #000000; } .playedCardsLayout .card:hover { z-index: 1400; } ` var styleSheet = document.createElement("style") styleSheet.innerText = myStyles; console.log(styleSheet); document.head.appendChild(styleSheet); var controlBar = document.createElement('div'); controlBar.id = 'controlBar'; var controlButtonsLeft = document.createElement('div'); controlButtonsLeft.className = 'btn-group'; controlButtonsLeft.id = 'controlButtonsLeft'; var controlButtonsRight = document.createElement('div'); controlButtonsRight.className = 'btn-group'; controlButtonsRight.id = 'controlButtonsRight'; var davidProtectionButton = document.createElement('button'); davidProtectionButton.className = "btn btn-success btn-xs"; davidProtectionButton.id = "davidProtection"; davidProtectionButton.type = "button"; davidProtectionButton.innerHTML = 'David protection: Off'; var autoWishButton = document.createElement('button'); autoWishButton.className = "btn btn-info btn-xs"; autoWishButton.id = "autoWish"; autoWishButton.type = "button"; autoWishButton.innerHTML = 'Auto Wish: Off'; var showRemainingButton = document.createElement('button'); showRemainingButton.className = "btn btn-info btn-xs"; showRemainingButton.id = "showRemaining"; showRemainingButton.type = "button"; showRemainingButton.innerHTML = 'Show: Remaining'; var showCountedCardsButton = document.createElement('button'); showCountedCardsButton.className = "btn btn-success btn-xs"; showCountedCardsButton.id = "showCountedCards"; showCountedCardsButton.type = "button"; showCountedCardsButton.innerHTML = 'Show cards: No'; controlButtonsRight.appendChild(showRemainingButton); controlButtonsRight.appendChild(showCountedCardsButton); controlButtonsLeft.appendChild(davidProtectionButton); controlButtonsLeft.appendChild(autoWishButton); controlBar.appendChild(controlButtonsLeft); controlBar.appendChild(controlButtonsRight); var counterField = document.createElement("div"); counterField.id = 'counterField'; counterField.appendChild(controlBar); // add an extra field where we will put down the played cards var cardsField = document.createElement('div'); cardsField.id = 'cardsField'; cardsField.style.marginLeft = '25px'; var playedHigh = document.createElement('div'); playedHigh.id = 'playedHigh'; playedHigh.style.marginLeft = '25px'; playedHigh.className = 'row'; playedHigh.hidden = true; var playedLow = document.createElement('div'); playedLow.id = 'playedLow'; playedLow.style.marginLeft = '25px'; playedLow.className = 'row'; playedLow.hidden = true; counterField.appendChild(playedHigh); counterField.appendChild(playedLow); tableIG = document.getElementById('gameField').parentNode; tableIG.appendChild(counterField); var createCardPlace = function(value, shift, node) { var playedCards = document.createElement('ul'); playedCards.id = 'playedCards' + value; // playedCards.className = 'layoutPlayedCards list-unstyled col-lg-2'; // playedCards.className = 'playedCardsLayout list-unstyled'; playedCards.className = 'playedCardsLayout list-unstyled'; playedCards.style.width = '160px'; playedCards.style.marginLeft = '25px'; playedCards.style.left = shift*127 + 'px'; node.appendChild(playedCards); } createCardPlace(1, 0, document.getElementById('playedHigh')); for(let i = 14; i > 8; i--) { createCardPlace(i, 14 - i + 1, document.getElementById('playedHigh')); } for(let i = 8; i > 1; i--) { createCardPlace(i, 8 - i, document.getElementById('playedLow')); } var smartFoldButton = document.createElement('button'); smartFoldButton.className = "btn btn-danger btn-xs"; smartFoldButton.id = "smartFold"; smartFoldButton.type = "button"; smartFoldButton.innerHTML = 'Smart fold: On'; var showCardCounterButton = document.createElement('button'); showCardCounterButton.className = "btn btn-primary btn-xs"; showCardCounterButton.id = "showCardCounter"; showCardCounterButton.type = "button"; showCardCounterButton.innerHTML = 'Card Counter: No'; buttons = document.getElementById('statusButtons'); buttons.insertBefore(showCardCounterButton, document.getElementById('autoFold')); buttons.insertBefore(smartFoldButton, document.getElementById('autoFold')); // playedCards. // ot.$gameField.insertAfter(playedCards, ot.$gameField.lastChild); // set up counting of played cards var OnlineTichuCounter = { $playedCards: {}, $smartFold: $('#smartFold'), $smartFoldValue: $('#smartFoldValue'), $showCardCounter: $('#showCardCounter'), $showCardCounterValue: $('#showCardCounterValue'), $showRemaining: $('#showRemaining'), $showRemainingValue: $('#showRemainingValue'), $showCountedCards: $('#showCountedCards'), $showCountedCardsValue: $('#showCountedCardsValue'), $davidProtection: $('#davidProtection'), $autoWishValue: $('#autoWishValue'), $autoWish: $('#autoWish'), $davidProtectionValue: $('#davidProtectionValue'), $playedHigh: $('#playedHigh'), $playedLow: $('#playedLow'), $counterField: $('#counterField'), // dynamic game state allCards: {}, playedCards: {}, totalPlayed: [], exchangedTo: {}, exchangedFrom: {}, handCardShuffle: [], // config values, triggered with buttons smartFold: true, showCardCounter: false, showRemaining: true, showCountedCards: false, davidProtection: false, autoWish: false, shuffleCards: true, // dynamic feature state davidProtectionTriggered: true, bombAfterFold: null, // to implement util: { displayCardsHtmlString: function(cards) { htmlString = ""; $(cards).each(function () { var value = this.Value; if (this.Shape == 7) { value = 0; } htmlString += '
'; }); return htmlString; }, populateAllCards: function() { for(let val = 2; val < 15; val++) { otc.allCards[val] = []; for(let shape = 0; shape < 4; shape++) { otc.allCards[val].push({ Shape: shape, Value: val }); } } otc.allCards[1] = [ { Shape: 4, Value: 1 }, // mahjong { Shape: 5, Value: 0 }, // dog { Shape: 6, Value: 15}, // dragon { Shape: 7, Value: 14 } // phoenix ]; }, cardInArray: function(card, array) { contained = false; for(let i = 0; i < array.length; i++) { if (array[i].Shape == card.Shape && array[i].Value == card.Value) { contained = true; } } return contained; } }, action: { selectAutoWish: function() { if (otc.exchangedTo['east'] == null) { return; } val = 0; if (otc.exchangedTo['east'].Shape < 4 ) { val = otc.exchangedTo['east'].Value; } else if (otc.exchangedTo['east'].Shape == 5) { val = 14; // wish for ace by default if we gave a dog to the right } button = document.getElementById("askCard_" + val); button.click(); }, deSelectAutoWish: function() { button = document.getElementById("askCard_0"); button.click(); }, handlePlayedCard: function(card) { index = 0; // special cards have Shapes 4,5,6,7. We group them at value 1 if(card.Shape >= 4) { index = 1; if (card.Shape == 7) { // Treat phoenix as if it had value 14 // This is important for comparisons later card.Value = 14; } } else { index = card.Value; } otc.playedCards[index].push(card); otc.totalPlayed.push(card); otc.playedCards[index].sort(function(c1, c2){ return c2.Shape - c1.Shape; }); }, handlePlayedCards: function(cards) { if (cards.length === 0) { return; } if (!otc.util.cardInArray(cards[0], otc.totalPlayed)) { $(message.Table.TableCards).each( (index, card) => otc.action.handlePlayedCard(card) ); } }, reset: function() { for(let i = 1; i < 15; i++) { otc.playedCards[i] = []; } dirs = ['east', 'north', 'west']; for (i in dirs) { otc.exchangedTo[dirs[i]] = null; otc.exchangedFrom[dirs[i]] = null; } otc.totalPlayed = []; otc.handCardShuffle = []; }, drawPlayedCards: function() { for(let i = 1; i < 15; i++) { if (otc.showRemaining) { cards = []; for(let j=0; j < otc.allCards[i].length; j ++) { if(!otc.util.cardInArray(otc.allCards[i][j], otc.playedCards[i])) { cards.push(otc.allCards[i][j]); } otc.$playedCards[i].html(otc.util.displayCardsHtmlString(cards)); } } else { otc.$playedCards[i].html(otc.util.displayCardsHtmlString(otc.playedCards[i])); } } } } } if (!window.otc) { window.otc = OnlineTichuCounter; } otc.$counterField.hide(); // add functionality to smartFold button otc.$smartFold.click(function () { otc.smartFold = !otc.smartFold; if (otc.smartFold) { otc.$smartFoldValue.text(otc.$smartFoldValue.data('on')); } else { otc.$smartFoldValue.text(otc.$smartFoldValue.data('off')); } }); // add functionality to show button otc.$showCardCounter.click(function () { otc.showCardCounter = !otc.showCardCounter; if (otc.showCardCounter) { otc.$showCardCounterValue.text(otc.$showCardCounterValue.data('on')); otc.$counterField.show(); } else { otc.$showCardCounterValue.text(otc.$showCardCounterValue.data('off')); otc.$counterField.hide(); } }); otc.$showCountedCards.click(function () { otc.showCountedCards = !otc.showCountedCards; if (otc.showCountedCards) { otc.$showCountedCardsValue.text(otc.$showCountedCardsValue.data('on')); otc.$playedHigh.show(); otc.$playedLow.show(); } else { otc.$showCountedCardsValue.text(otc.$showCountedCardsValue.data('off')); otc.$playedHigh.hide(); otc.$playedLow.hide(); } }); otc.$davidProtection.click(function () { otc.davidProtection = !otc.davidProtection; if (otc.davidProtection) { otc.$davidProtectionValue.text(otc.$davidProtectionValue.data('on')); } else { otc.$davidProtectionValue.text(otc.$davidProtectionValue.data('off')); } }); otc.$autoWish.click(function () { otc.autoWish = !otc.autoWish; if (otc.autoWish) { otc.$autoWishValue.text(otc.$autoWishValue.data('on')); otc.action.selectAutoWish(); } else { otc.$autoWishValue.text(otc.$autoWishValue.data('off')); otc.action.deSelectAutoWish(); } }); otc.$showRemaining.click(function () { otc.showRemaining = !otc.showRemaining; if (otc.showRemaining) { otc.$showRemainingValue.text(otc.$showRemainingValue.data('on')); } else { otc.$showRemainingValue.text(otc.$showRemainingValue.data('off')); } otc.action.drawPlayedCards(); }); for(let i = 1; i < 15; i++) { otc.$playedCards[i] = $('#playedCards' + i); } otc.action.reset(); otc.util.populateAllCards(); // overwrite functionality of fold button ot.$fold.unbind(); ot.$fold.hide().click(function () { if (otc.davidProtection) { if(ot.$passCards().length != 0 && !otc.davidProtectionTriggered) { console.log('david protection triggered'); ot.$gameMessage.text("Selected cards detected. Press again to fold!"); ot.HorizontalAlign(ot.$gameMessage); otc.davidProtectionTriggered = true; return; } } ot.action.Fold(); otc.davidProtectionTriggered = false; $(this).hide(); }); MyTableState = (function() { var cachedFunction = ot.reaction.TableState; return function() { var result = cachedFunction.apply(this, arguments); message = arguments[0]; // console.log('detected table state change event'); console.log(message.Table.Status); // reset played cards at start of round if(message.Table.Status == 'WaitForNextCards' && otc.totalPlayed.length != 0) { otc.action.reset(); } if(message.Table.Status == 'Playing') { otc.action.handlePlayedCards(message.Table.TableCards); } otc.action.drawPlayedCards(); // console.log('reached smartFold point'); $(message.Table.Players).each(function () { if (this.Username == ot.$Username.val()) { // console.log('found player'); if (message.Table.Turn == this.Position) { // console.log('my turn!'); if (this.Cards.length < 4 && this.Cards.length < message.Table.TableCards.length) { if (otc.smartFold) { if(this.MustFold) { ot.action.Fold(); console.log('smart fold activated'); } else { console.log('prevented bug by not smartfolding'); } } } } } }); return result; } })(); MyChat = (function() { var cachedFunction = ot.reaction.Chat; return function() { var result = cachedFunction.apply(this, arguments); message = arguments[0]; if (message.User.Username === "System") { var messageArray = message.Text.replace(//g, '>').split(' '); message.Text = messageArray.join(' '); messageArray = message.Text.replace(//g, '>').split(' '); for(var i in messageArray) { if(messageArray[i] == "dogs") { otc.action.handlePlayedCard({ "Shape": 5, "Value": 0 }); } } } return result; } })(); MyExchangeCards = (function() { var cachedFunction = ot.action.ExchangeCards; return function() { otc.exchangedTo['west'] = { Shape: ot.$westExchange.children('li:nth-child(1)').data('shape'), Value: ot.$westExchange.children('li:nth-child(1)').data('value') }; otc.exchangedTo['north'] = { Shape: ot.$northExchange.children('li:nth-child(1)').data('shape'), Value: ot.$northExchange.children('li:nth-child(1)').data('value') }; otc.exchangedTo['east'] = { Shape: ot.$eastExchange.children('li:nth-child(1)').data('shape'), Value: ot.$eastExchange.children('li:nth-child(1)').data('value') }; console.log(otc.exchangedTo); if(otc.autoWish) { otc.action.selectAutoWish(); } else { otc.action.deSelectAutoWish(); } var result = cachedFunction.apply(this, arguments); return result; } })(); MyReviewExchange = (function() { var cachedFunction = ot.reaction.ReviewExchange; return function() { var result = cachedFunction.apply(this, arguments); return result; } })(); ot.reaction.TableState = MyTableState; ot.reaction.Chat = MyChat; ot.action.ExchangeCards = MyExchangeCards; }; // Special care is to be taken to safely inject our javascript into the website // without the website being able to talk back to greasemonkey // (which would be a security problem) // see // https://stackoverflow.com/questions/5006460/userscripts-greasemonkey-calling-a-websites-javascript-functions // and the excellent answer by Wayne for details on this function exec(fn) { var script = document.createElement('script'); script.setAttribute("type", "application/javascript"); script.textContent = '(' + fn + ')();'; document.body.appendChild(script); // run the script document.body.removeChild(script); // clean up } exec(injectCounter);