您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Makes play choices for the bvs game Flower Wars
// ==UserScript== // @name BVS Flower Wars AI // @namespace chris // @description Makes play choices for the bvs game Flower Wars // @include http*://*animecubed.com/billy/bvs/partyhouse-hanafudaplay.html // @include http*://*animecubed.com/billy/bvs/partyhouse-hanafuda.html // @include http*://*animecubedgaming.com/billy/bvs/partyhouse-hanafudaplay.html // @include http*://*animecubedgaming.com/billy/bvs/partyhouse-hanafuda.html // @version 2.2 // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // ==/UserScript== // hotkeys: d - take the selected action // c - use if you want the script to select cheats to use // v - open debug console //change log // <version> 2.2 - New domain - animecubedgaming.com - Channel28 // <version> 2.1 - Now https compatible (Updated by Channel28) // <version> 2.0 - Fix bugs (Thanks to Terrec. Updated by Channel28) // <version> 1.1112 - Added grant permissions (Updated by Channel28) // <version> 1.1111 - added google chrome support // <version> 1.11 - many tweak to choice decisions // - cheats added over from test version // <version> 1.11 - fixed a bug in discard when attempting to discard a card // - with a matching month // <version> 1.1 - new hotkey V now opens the debug console // - made some minor adjustments to card values // <version> 1.09.1 - fixed discard (no clue how i made that mistake) // <version> 1.09 - Added a "debug console" button that shows value breakdowns // (if anyone is interested in why the script makes its choices) // - Tweaked several values to increase performance // <version> 1.08.2 - Slightly modified for Firefox 3.6 compatability // <version> 1.08.1 - fixed several issues from version 1.08 to get the script stable // <version> 1.08 - code for playing month cards to block opponent altered to be based on enemy yakus // - koikoi should work better (when you're really pummeling the computer) // <version> 1.07 - Added recommended action text for koikoi/bank // - several tweaks to card rankings/yakus to improve decisions // - script will now "pass" on matching month cards to wait for yakus // <version> 1.06 - koikoi now determines not only distance from enemy yakus, // but whether they are possible given the field // - minor tweaks to fix how often koikoi is called // <version> 1.05.0 - revamped to koikoi source, should work properly when opponent has 8+ plains // - decisions relating to obtaining a yaku improved // <version> 1.04.2 - fixed a minor issue that could cause a failure to koikoi // <version> 1.04.1 - added auto-updater (hopefully) // <version> 1.04 - several minor tweaks // - when discarding, possible cards are now weighted into the descision // <version> 1.03 - tweaked values for better gameplay // - fixed errors in counting opponents cards // <version> 1.02 - fixed a few errors in ranking wet brights // <version> 1.01 - tweaked script to koikoi more often // - fixed an error that could cause koikoi/bank to fail // <version> 1.00 try { if (!this.GM_getValue || (this.GM_getValue.toString && this.GM_getValue.toString().indexOf("not supported")>-1)) { this.GM_getValue=function (key,def) { return localStorage[key] || def; }; this.GM_setValue=function (key,value) { return localStorage[key]=value; }; this.GM_deleteValue=function (key) { return delete localStorage[key]; }; } } catch (e) {} var pageLoc = "/billy/bvs/partyhouse-hanafuda.html" if(location.href.indexOf(pageLoc) != -1) { GM_setValue("forcedKoi",false) return; } var forceKoi = GM_getValue("forcedKoi",false) var forceMatch = /Next Opponent Koi-Koi forced!/.exec(document.body.innerHTML); if (forceMatch) { forceKoi = true; GM_setValue("forcedKoi",true) } var cheatMonth = 0; var noPlayMonth = 0; var enemyHasKoiKoi = false; var youHaveKoiKoi = false; var canCheat = true; var availCheats = []; var useCheat = null; var yakuHandCard = null; var yakuFieldCard = null; if(/They play .* and call Koi-Koi!/.exec(document.body.innerHTML)) enemyHasKoiKoi=true; else if(/Koi-Koi - No Cheating!<br>\(Them/.exec(document.body.innerHTML)) enemyHasKoiKoi=true; if(/Koi-Koi - No Cheating!<br>\(You/.exec(document.body.innerHTML)) youHaveKoiKoi = true; if (youHaveKoiKoi || enemyHasKoiKoi) canCheat = false; var match = /Select cheat \((\d+) remaining\):/.exec(document.body.innerHTML); if (!match || match[1] == 0) canCheat = false; if (/Shuffle \+ Redraw/.exec(document.body.innerHTML)) availCheats.push("redrawCheat"); if (/View top of deck/.exec(document.body.innerHTML)) availCheats.push("showTopCard"); if (/Opp. -1 Take/.exec(document.body.innerHTML)) availCheats.push("enemyCardCheat"); if (/Force Koi-Koi/.exec(document.body.innerHTML)) availCheats.push("forceKoiKoi"); //set value modifiers var monthMult = []; for (var i =1;i<13;i++) monthMult[i] = 0 //lower values make the script play more defensively // (I'd recommend not going any lower than 1) var offensive = 1.4 //increase this value to decrease the month blocking affect var monthBlock = 2.0 //yaku value modifiers (+ per card owned in the yaku) var plain = 1.0; var ribbon = 1.0; var animal = 1.0; var Rribbon = 5.0; //add code to drop this value when 1 owned by each player var Bribbon = 5.0; //add code to drop this value when 1 owned by each player var RoShamBo= 5.0; //add code to drop this value when 1 owned by each player var brights = 6.0; var loop = 5.0; var sacrifice = 5.0; var wetBright = .6; //only increases when brights are obtained var onFieldMult = 1.5; var twoInHand = .7; var threeInPlay = .5; //this does not apply to cards on the field var guaranteeMult=.01; var yakuMult = 5.0; var cheatThreshold = 20.0; var discarding = false; //get the top deck card var deckCard = getDeckCard(); //get hand cards var hand = getHand(); //get field cards and set empty slot var emptySlot; var playField = getFieldCards(); //get your owned cards var yourCards = getYourCards(); //get enemies cards var enemyCards = getEnemyCards(); //if the enemy is the dealer, disable force koikoi strat when you hit 2 cards in hand (so enemy doesn't actually win) var dealMatch = /-- Them \((Dealer) - Current/.exec(document.body.innerHTML); if (dealMatch && dealMatch[1] == "Dealer") if (hand.length == 2) { enemyHasKoiKoi = true; forceKoi = true; } updateMultipliers(); setValues(hand, false); setValues(playField, true); var mc = /option to put at bottom\):<br><img src="\/billy.layout.hcards.(thumbs.)?(\d+).jpg">/.exec(document.body.innerHTML) var mc = [] var cheatCard = new Card(0); if (mc && mc.length > 1) { //decide whether to put it on bottom, or play var tmpCard = new Card(mc[2]); var month = tmpCard.month; //if the month is not on the field/in hand, put it at the bottom if (!hasMonth(playField, month) && !hasMonth(hand, month)) setCheat("bottom"); else { cheatMonth = month; //get the value of card on top of deck cheatCard.value = getValue(tmpCard); //add other multipliers var num = tmpCard.num; cheatCard.value += monthMult[tmpCard.month]; //guaranteed type 2 done after block (you are guaranteed the card regardless, and can do nothing to block a play) if (guaranteed(tmpCard) == 2) cheatCard.value *= guaranteeMult; cheatCard.value *= onFieldMult; if (givesYaku(yourCards, tmpCard.num)) cheatCard.value *= yakuMult; //find the best card in hand var bCard = null; for (var i=0;i<hand.length;i++) if ((bCard == null || bCard.value < hand[i].value) && hand[i].month == tmpCard.month) bCard = hand[i]; cheatCard.num = bCard.num; cheatCard.month = bCard.month; cheatCard.element = bCard.element; cheatCard.playable = true; cheatCard.value += bCard.value; } } //stores reasons why the script should perform each action var koiReasons = []; var bankReasons = []; if (!document.forms.namedItem("bankpoints") && !document.forms.namedItem("koikoi")) { if (deckCard) playDeck(); if (tryShowTopCardCheat()) { var newButton = document.createElement("btn"); var targetElement = document.forms.namedItem("placecard"); newButton.style.fontSize = "20px"; newButton.innerHTML = "<b>Suggested Action : Cheat (Show Top Card)<BR></b>"; targetElement.parentNode.insertBefore(newButton, targetElement); setCheat("topCard"); } if (tryEnemyCardCheat()) { var newButton = document.createElement("btn"); var targetElement = document.forms.namedItem("placecard"); newButton.style.fontSize = "20px"; newButton.innerHTML = "<b>Suggested Action : Cheat (-1 enemy cards)<BR></b>"; targetElement.parentNode.insertBefore(newButton, targetElement); setCheat("minusCard"); } if (tryForceKoiKoiCheat()) { var newButton = document.createElement("btn"); var targetElement = document.forms.namedItem("placecard"); newButton.style.fontSize = "20px"; newButton.innerHTML = "<b>Suggested Action : Cheat (Force KoiKoi)<BR></b>"; targetElement.parentNode.insertBefore(newButton, targetElement); setCheat("koi"); } if (!deckCard && !playCard()) { discarding = true; discard(); if (tryRedrawCheat()) { var newButton = document.createElement("btn"); var targetElement = document.forms.namedItem("placecard"); newButton.style.fontSize = "20px"; newButton.innerHTML = "<b>Suggested Action : Cheat (redraw hand)<BR></b>"; targetElement.parentNode.insertBefore(newButton, targetElement); setCheat("redraw"); } } } else { var newButton = document.createElement("btn"); var targetElement; if (!document.forms.namedItem("koikoi")) targetElement = document.forms.namedItem("bankpoints"); else if (koikoi()) targetElement = document.forms.namedItem("koikoi"); else targetElement = document.forms.namedItem("bankpoints"); newButton.innerHTML = "<b>Suggested Action</b>"; insertAfter(newButton, targetElement); } function contains(list, string) { if (!list) return false; for (var i=0;i<list.length;i++) if (list[i] == string) return true; return false; } function tryForceKoiKoiCheat() { if (!canCheat || !contains(availCheats,"forceKoiKoi")) return false; if (guaranteedYaku() && hand.length > 2) return true; return false; } //this cheat runs if there is at least 3 valuable cards in play (that you can match cards with, none of which are guaranteed) function tryShowTopCardCheat() { if (!canCheat || !contains(availCheats,"showTopCard")) return false; var count = 0; for (var i=0;i<playField.length;i++) if (hasMonth(hand,playField[i].month)) if (playField[i].value >= 1.5) count++; if (count >= 3) return true; return false; } //this cheat runs if enemy has 4 cards or less, where 1 is from a 2 yaku, and at least 1 other is from a 3 yaku function tryEnemyCardCheat() { if (!canCheat || !contains(availCheats,"enemyCardCheat")) return false; var hasLpA = 0; var hasSacP = 0; for (var i=0;i<enemyCards.length;i++) { if (enemyCards[i].num == 44) hasLpA = 1; else if (enemyCards[i].num == 17) hasSacP = 1; } //add all low value yaku cards var negCount = plainCount(enemyCards) + animalCount(enemyCards) + ribbonCount(enemyCards); //subtract out the ones that give larger yakus if (!rRibbonConflict()) negCount -= rRibbonCount(enemyCards); if (!bRibbonConflict()) negCount -= bRibbonCount(enemyCards); if (!roConflict() ) negCount -= roCount(enemyCards); if (!sacConflict() ) negCount -= hasSacP; if (!loopConflict() ) negCount -= hasLpA; var posCount = 0; if (!rRibbonConflict()) posCount += rRibbonCount(enemyCards); if (!bRibbonConflict()) posCount += bRibbonCount(enemyCards); if (!roConflict() ) posCount += roCount(enemyCards); if (!sacConflict() ) posCount += hasSacP; if (!loopConflict() ) posCount += hasLpA; if (!brightConflict() ) posCount += brightCount(enemyCards); if ((hasBright(enemyCards) && !brightConflict() || hasLoop(enemyCards) && !loopConflict() || hasSac(enemyCards) && !sacConflict() )&& negCount < 3|| posCount - negCount > 1) return true; return false; } function tryRedrawCheat() { if (!canCheat || !contains(availCheats,"redrawCheat") || guaranteedYaku()) return false; //if aug bright is in play, and no august cards in hand if (hasCard(playField, "29") && monthCount(8, playField) <= 2) if (monthCount(8, hand) == 0) return true; //an alternate way to redraw, if you have less than 4 cards, and they all give the enemy big yakus if (hand.length < 4) { for (var i=0;i<hand.length;i++) { var num = hand[i].num; if (isRoShamBo(num) && roCount(enemyCards) == 2) continue; if (isRribbon(num) && rRibbonCount(enemyCards) == 2) continue; if (isBribbon(num) && bRibbonCount(enemyCards) == 2) continue; if (isBright(num) && brightCount(enemyCards) >= 2) continue; if (isLoop(num) && loopCount(enemyCards) == 1) continue; if (isSac(num) && sacCount(enemyCards) == 1) continue; if (isWet(num) && brightCount(enemyCards) >= 2) continue; //if no big wins where available, this mode fails and we return false return false; } //return true to cheat here (this indicates all cards in your hand gave the enemy a 5 pt yaku) return true; } else if (playField.length < 4) return false; var total=0; var goodYakuCount = 0; for (var i=1;i<13;i++) if (monthCount(i, hand) > 2) return false; for (var i=0;i<hand.length;i++) { var num = hand[i].num; if (isSac(num) && !sacConflict() ) goodYakuCount++; if (isLoop(num) && !loopConflict() ) goodYakuCount++; if (isBright(num) && !brightConflict() ) goodYakuCount++; if (isRribbon(num) && !rRibbonConflict()) goodYakuCount++; if (isBribbon(num) && !bRibbonConflict() ) goodYakuCount++; if (isRoShamBo(num) && !roConflict() ) goodYakuCount++; total += hand[i].value; if (guaranteed(hand[i])>0) return false; } if ((total <= cheatThreshold && goodYakuCount == 0) || (total <= cheatThreshold/2 && goodYakuCount == 1) || (total <= cheatThreshold/4 && goodYakuCount == 2)) return true; return false; } function setValues(list, inField) { for (var i=0;i<list.length;i++) { var num = list[i].num; list[i].value = getValue(list[i]); if (monthCount(list[i].month, hand) > 1 && !inField) list[i].value *= twoInHand; if (monthCount(list[i].month, hand) + monthCount(list[i].month, playField) > 2 && !inField) list[i].value *= threeInPlay; //guaranteed type 1 done before block (you are guaranteed 1 of 2 cards,however enemy can still play the suit) if (guaranteed(list[i]) == 1) list[i].value *= guaranteeMult; list[i].value += monthMult[list[i].month]; //guaranteed type 2 done after block (you are guaranteed the card regardless, and can do nothing to block a play) if (guaranteed(list[i]) == 2) list[i].value *= guaranteeMult; if (inField) list[i].value *= onFieldMult; } } function monthCount(month, list) { var count = 0; for (var i=0;i<list.length;i++) if (list[i].month == month) count++; return count; } function insertAfter(newElement,targetElement) { var parent = targetElement.parentNode; if(parent.lastchild == targetElement) parent.appendChild(newElement); else parent.insertBefore(newElement, targetElement.nextSibling); } //initiate a card object function Card(Num, element) { this.num = Num; this.month = Math.ceil(Num/4); this.element = element; this.playable = false; this.value = 0; } //creates and returns a card object from a card in hand element function getCard(element) { cardNum = element.getAttribute("for"); num = /handcard(\d+)/.exec(cardNum); if (!num) ShowMsg("Failed to get Card Number"); return new Card(parseInt(num[1]), element); } //getType functions function isRoShamBo(num) { return (num == 21 || num == 25 || num == 37); } function isRribbon(num) { return (num == 2 || num == 6 || num == 10); } function isBribbon(num) { return (num == 22 || num == 34 || num == 38); } function isBright(num) { return (num == 29 || num == 45 || num == 1 || num == 9); } function isLoop(num) { return (num == 29 || num == 17); } function isSac(num) { return (num == 29 || num == 44); } function isWet(num) { return num == 41;} function isRibbon(num) { return (isRribbon(num) || isBribbon(num) || num == 43 || num == 26 || num == 18 || num == 14);} function isAnimal(num) { return (isRoShamBo(num) || num == 42 || num == 33 || num == 30 || num == 17 || num == 13 || num == 5);} function isPlain(num) { return ((!isAnimal(num) || num == 33) && !isBright(num) && !isRibbon(num) && !isWet(num));} //functions to get card values (based on yaku counts) function plainMult () {return (plain * ( 1 + plainCount(yourCards) + plainCount(enemyCards) / offensive) / 10);} function ribbonMult () {return (ribbon * ( 1 + ribbonCount(yourCards) + ribbonCount(enemyCards) / offensive) / 5);} function animalMult () {return (animal * ( 1 + animalCount(yourCards) + animalCount(enemyCards) / offensive) / 5);} function RribbonMult () {return (Rribbon * ( 1 + rRibbonCount(yourCards) + rRibbonCount(enemyCards) / offensive) / 3);} function BribbonMult () {return (Bribbon * ( 1 + bRibbonCount(yourCards) + bRibbonCount(enemyCards) / offensive) / 3);} function RoShamBoMult () {return (RoShamBo * ( 1 + roCount(yourCards) + roCount(enemyCards) / offensive) / 3);} function brightMult () {return (brights * ( 1 + brightCount(yourCards) + brightCount(enemyCards) / offensive) / 3);} function loopMult () {return (loop * ( 1 + loopCount(yourCards) + loopCount(enemyCards) / offensive) / 2);} function sacMult () {return (sacrifice * ( 1 + sacrificeCount(yourCards) + sacrificeCount(enemyCards)/ offensive) / 2);} function wetMult () {return (wetBright * ( 1 + brightCount(yourCards) + brightCount(enemyCards) / offensive) / 4);} function rRibbonConflict() {return (hasRed (yourCards) && hasRed (enemyCards));} function bRibbonConflict() {return (hasBlue (yourCards) && hasBlue (enemyCards));} function roConflict() {return (hasRo (yourCards) && hasRo (enemyCards));} function brightConflict() {return (has2Bri (yourCards) && has2Bri (enemyCards));} function sacConflict() {return (hasSac (yourCards) && hasSac (enemyCards));} function loopConflict() {return (hasLoop (yourCards) && hasLoop (enemyCards));} //returns -1 if it made a play, 0 if no guaranteed yakus (continue game) // otherwise it returns a month value (to protect if forced koi) function guaranteedYaku() { //for all months for (var i = 1;i < 13;i++) { var yakuList = []; var handMonths = getMonthFromList(hand, i); var fieldMonths = getMonthFromList(playField, i); if (!handMonths) continue; var combineCards = handMonths.concat(fieldMonths); if (!listGivesYaku(yourCards, combineCards) || handMonths.length < 1) continue; //get a list of all pairs that can give yakus for (var j=0;j<combineCards.length;j++) for (var k=0;k<combineCards.length;k++) if (j != k && guaranteed(combineCards[j])>0 && guaranteed(combineCards[j])>0) { var tmpList = []; tmpList.push(combineCards[j]); tmpList.push(combineCards[k]); if (listGivesYaku(yourCards, tmpList)) { yakuList.push(new cardPair(combineCards[j],combineCards[k])); } } //for all the pairs, skip it if its not possible for (var b=0;b<yakuList.length;b++) { var inHand = 0; var onField = 0; var handC = null; if (hasCard(hand, yakuList[b].card1.num)) { inHand++; handC = yakuList[b].card1; } if (hasCard(hand, yakuList[b].card2.num)) { inHand++; handC = yakuList[b].card2; } if (hasCard(playField, yakuList[b].card1.num)) onField++; if (hasCard(playField, yakuList[b].card2.num)) onField++; //this is the easiest case, simply play the yaku if (inHand == 1 && onField == 1 && guaranteed(yakuList[b].card1)>0 && guaranteed(yakuList[b].card2)>0) return true; // this is the harder case, you must first play 2 cards, so just pick the 2 highest value cards else if (inHand == 2 && fieldMonths == 2) return true; else if (inHand == 2 && fieldMonths <= 1) return true; else if (inHand == 1 && fieldMonths >= 2 && givesYaku(yourCards, handC.num)) return true; } } return false; } //functions for checking yakus function hasRo (list) { for (var i=0;i<list.length;i++) if (isRoShamBo(list[i].num)) return true; return false; } function hasRed (list) { for (var i=0;i<list.length;i++) if (isRribbon(list[i].num)) return true; return false; } function hasBlue(list) { for (var i=0;i<list.length;i++) if (isBribbon(list[i].num)) return true; return false; } function hasBright(list) { for (var i=0;i<list.length;i++) if (isBright(list[i].num)) return true; return false; } function has2Bri(list) { count = 0; for (var i=0;i<list.length;i++) if (isBright(list[i].num)) { count++; if (count == 2) return true; } return false; } function hasLoop(list) { for (var i=0;i<list.length;i++) if (isLoop(list[i].num)) return true; return false; } function hasSac (list) { for (var i=0;i<list.length;i++) if (isSac(list[i].num)) return true; return false; } //check if any card from a month is contained in the list function hasMonth(cardList, month) { if (!cardList) return false; for (var i=0;i<cardList.length;i++) if (cardList[i].month == month) return true; return false; } function plainCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isPlain(list[i].num)) count++; return count; } function animalCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isAnimal(list[i].num)) count++; return count; } function ribbonCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isRibbon(list[i].num)) count++; return count; } function redCount(list) {return rRibbonCount(list);} function blueCount(list) {return bRibbonCount(list);} function rRibbonCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isRribbon(list[i].num)) count++; return count; } function bRibbonCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isBribbon(list[i].num)) count++; return count; } function roCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isRoShamBo(list[i].num)) count++; return count; } function brightCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isBright(list[i].num)) count++; return count; } function loopCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isLoop(list[i].num)) count++; return count; } function sacCount(list) { return sacrificeCount(list); } function sacrificeCount(list) { var count = 0; for (var i = 0;i<list.length;i++) if (isSac(list[i].num)) count++; return count; } function findLoop(list) { for (var i=0;i<list.length;i++) if (isLoop(list[i].num)) return list[i]; return null; } function findSac(list) { for (var i=0;i<list.length;i++) if (isSac(list[i].num)) return list[i]; return null; } function findRo(list) { for (var i=0;i<list.length;i++) if (isRo(list[i].num)) return list[i]; return null; } function findRed(list) { for (var i=0;i<list.length;i++) if (isRed(list[i].num)) return list[i]; return null; } function findBlue(list) { for (var i=0;i<list.length;i++) if (isBlue(list[i].num)) return list[i]; return null; } function findBright(list) { for (var i=0;i<list.length;i++) if (isBright(list[i].num)) return list[i]; return null; } function givesYaku(cardList, cardNum) { var yaku = 0, Ro=0,Red=0,Blue=0,Bright=0,Loop=0,Sac=0,Ribbon=0,Animal=0,Plain=0; //get yaku counts for (var i=0;i<cardList.length;i++) { num = cardList[i].num; if (isRoShamBo(num)) Ro++; if (isRribbon(num) ) Red++; if (isBribbon(num) ) Blue++; if (isBright(num) ) Bright++; if (isLoop(num) ) Loop++; if (isSac(num) ) Sac++; if (isRibbon(num) ) Ribbon++; if (isAnimal(num) ) Animal++; if (isPlain(num) ) Plain++; } if (isRoShamBo(cardNum)&& Ro == 2) yaku++; if (isRribbon(cardNum) && Red == 2) yaku++; if (isBribbon(cardNum) && Blue == 2) yaku++; if (isBright(cardNum) && Bright >= 2) yaku++; if (isLoop(cardNum) && Loop == 1) yaku++; if (isSac(cardNum) && Sac == 1) yaku++; if (isRibbon(cardNum) && Ribbon >= 4) yaku++; if (isAnimal(cardNum) && Animal >= 4) yaku++; if (isPlain(cardNum) && Plain >= 9) yaku++; if (isWet(cardNum) && Bright > 2 ) yaku++; return yaku; } function listHasYakus(cardList) { var yaku = 0, Ro=0,Red=0,Blue=0,Bright=0,Loop=0,Sac=0,Ribbon=0,Animal=0,Plain=0,num=0,Wet=false; //get yaku counts for (var i=0;i<cardList.length;i++) { num = cardList[i].num; //these values are kept at most 1 below the yaku values //(this function finds new yakus, not existing ones) if (isRoShamBo(num)) Ro += 1; if (isRribbon(num) ) Red += 1; if (isBribbon(num) ) Blue += 1; if (isBright(num) ) Bright += 1; if (isLoop(num) ) Loop += 1; if (isSac(num) ) Sac += 1; if (isRibbon(num) ) Ribbon += 1; if (isAnimal(num) ) Animal += 1; if (isPlain(num) ) Plain += 1; if (isWet(num) ) wet = true; } if (Ro>=3) yaku++; if (Red>=3) yaku++; if (Blue>=3) yaku++; if (Bright>=3) yaku++; if (Loop>=2) yaku++; if (Sac>=2) yaku++; if (Ribbon>=5) yaku++; if (Animal>=5) yaku++; if (Plain>=10) yaku++; if (Wet && Bright >= 3) yaku++; return yaku; } function listGivesYaku(cardList, checkList) { var yaku = 0, Ro=0,Red=0,Blue=0,Bright=0,Loop=0,Sac=0,Ribbon=0,Animal=0,Plain=0,num=0,realB=0,Wet=false; //get yaku counts for (var i=0;i<cardList.length;i++) { num = cardList[i].num; //these values are kept at most 1 below the yaku values //(this function finds new yakus, not existing ones) if (isRoShamBo(num)) Ro = Math.min(Ro + 1, 2); if (isRribbon(num) ) Red = Math.min(Red + 1, 2); if (isBribbon(num) ) Blue = Math.min(Blue + 1, 2); if (isBright(num) ) { Bright = Math.min(Bright + 1, 2); realB ++;} if (isLoop(num) ) Loop = Math.min(Loop + 1, 1); if (isSac(num) ) Sac = Math.min(Sac + 1, 1); if (isRibbon(num) ) Ribbon = Math.min(Ribbon + 1, 4); if (isAnimal(num) ) Animal = Math.min(Animal + 1, 4); if (isPlain(num) ) Plain = Math.min(Plain + 1, 9); } for (var i=0;i<checkList.length;i++) { num = checkList[i].num; if (isRoShamBo(num)) Ro++; if (isRribbon(num) ) Red++; if (isBribbon(num) ) Blue++; if (isBright(num) ) Bright++; if (isLoop(num) ) Loop++; if (isSac(num) ) Sac++; if (isRibbon(num) ) Ribbon++; if (isAnimal(num) ) Animal++; if (isPlain(num) ) Plain++; if (isWet(num) ) Wet=true; } if (Ro > 2) yaku++; if (Red > 2) yaku++; if (Blue > 2) yaku++; if (Bright > 2) yaku++; if (Loop > 1) yaku++; if (Sac > 1) yaku++; if (Ribbon > 4) yaku++; if (Animal > 4) yaku++; if (Plain > 9) yaku++; if (realB > 2 && Wet) yaku++; return yaku; } //gets the value of a card function getValue(card) { var num = card.num; return (isPlain(num) * plainMult()+ isRibbon(num) * ribbonMult()+ isAnimal(num) * animalMult()+ isRribbon(num) * RribbonMult()+ isBribbon(num) * BribbonMult()+ isRoShamBo(num) * RoShamBoMult()+ isBright(num) * brightMult()+ isLoop(num) * loopMult()+ isSac(num) * sacMult()+ isWet(num) * wetMult()); } // 2 for completely guaranteed (all cards are yours) // 1 if partial guaranteed (you get to pick between 2 cards) // -1 if the card is impossible to obtain // 0 otherwise function guaranteed(card) { var month = card.month; var inHand = false; var hCards = 0, oCards = 0, fCards = 0; for (var i = 0;i<hand.length;i++) if (hand[i].month == month) { hCards++; if (card.num == hand[i].num) inHand = true; } for (var i = 0;i<playField.length;i++) if (playField[i].month == month) fCards++; for (var i = 0;i<yourCards.length;i++) if (yourCards[i].month == month) oCards++; for (var i = 0;i<enemyCards.length;i++) if (enemyCards[i].month == month) oCards++; var count = hCards+oCards+fCards; if (oCards == 3) return -1; if (fCards == 2 && hCards == 2 || hCards == 3 && fCards == 1 || hCards == 4 || hCards == 1 && fCards >= 1 && count == 4) return 2; if (oCards == 1) if (hCards == 2 && fCards == 1 && inHand || hCards == 3 && fCards == 0 || hCards == 1 && fCards == 2 && !inHand) return 1; else if (oCards == 0) if (hCards == 2 && fCards == 1 && inHand || hCards == 3 && fCards == 0 || hCards == 1 && fCards == 2 && !inHand || hCards == 1 && fCards == 3 && !inHand ) return 1; return 0; } function getDeckCard() { var elems, current; var deckCard; elems = document.evaluate( ".//td[contains(./b/text(),'Game')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < elems.snapshotLength; i++) { current = elems.snapshotItem(i); match = current.innerHTML.match(/billy.layout.hcards.(thumbs.)?(\d+).jpg/i); if(!match || match.length != 3) continue; deckCard = new Card(match[2], current); } return deckCard } function getHand() { var elems, current; hand = []; //get your hand elems = document.evaluate( ".//label[contains(@for,'handcard')]", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < elems.snapshotLength; i++) { current = elems.snapshotItem(i); addcard = getCard(current); hand.push(addcard); } return hand; } function getYourCards() { var hasDual = false var yourCards = [] cards = /You .*Current Score.*(billy.layout.hcards.(thumbs.)?\d+.jpg.*).*/i.exec(document.body.innerHTML); if (cards) cards = cards[0]; while (cards) { temp = /billy.layout.hcards.(thumbs.)?(\d+).jpg(.*)/.exec(cards); if (!temp || temp.length != 4) break; cards = temp[3]; if (temp[2] != 33) //dual card yourCards.push(new Card(temp[2])); else { if (!hasDual) { hasDual = true; yourCards.push(new Card(temp[2])); } } } return yourCards; } function getEnemyCards() { var enemyCards = [] cards = /Them .*Current Score.*(billy.layout.hcards.(thumbs.)?\d+.jpg.*).*/i.exec(document.body.innerHTML); if (cards) cards = cards[0]; var hasDual = false; while (cards) { temp = /billy.layout.hcards.(thumbs.)?(\d+).jpg(.*)/.exec(cards); if (!temp || temp.length != 4) break; cards = temp[3]; if (temp[2] != 33) //dual card enemyCards.push(new Card(temp[2])); else { if (!hasDual) { hasDual = true; enemyCards.push(new Card(temp[2])); } } } return enemyCards; } function getFieldCards() { var playField = []; elems = document.evaluate( ".//label[contains(@for,'cardinbutton')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < elems.snapshotLength; i++) { current = elems.snapshotItem(i); match = current.innerHTML.match(/billy.layout.hcards.(thumbs.)?(\d+).jpg/i); if(!match) { emptySlot = current; continue; } if(match.length != 3) ShowMsg("Failed to get field cards"); cardNum = match[2]; addCard = new Card(cardNum, current); playField.push(addCard); } return playField; } function getAvailable(month) { var list = [] for (var i = 4*(month-1) + 1;i < 4*month+1;i++) { if (isAvailable(i)) { list.push(new Card(i,null)); } } return list; } function isAvailable(num) { avail = true; for (var i=0;i<hand.length;i++) if (num == hand[i].num) avail = false; for (var i=0;i<playField.length;i++) if (num == playField[i].num) avail = false; for (var i=0;i<yourCards.length;i++) if (num == yourCards[i].num) avail = false; for (var i=0;i<enemyCards.length;i++) if (num == enemyCards[i].num) avail = false; return avail; } //true if script recommends koikoi function koikoi() { var gYaku = guaranteedYaku(); if (forceKoi && !enemyHasKoiKoi && gYaku) { koiReasons.push(new Reason("Since the enemy is forced to koikoi, and you have a guaranteed play, it is perfectly safe to keep playing")); return true; } var toKoi = 0; //increases based on how close you are to getting more points var toBank = 0; //increases based on how close enemy is to winning var threshold = 0; //add likelyhood of yakus for matching pairs in play, and 1/2 the amt for non matching for (var i=0;i<hand.length;i++) { if (givesYaku(yourCards, hand[i].num)) { if (guaranteed(hand[i]) > 0) { toKoi += 5; koiReasons.push(new Reason("Guaranteed a yaku",5)); } else if (hasMonth(playField, hand[i].month)) { toKoi+=1; koiReasons.push(new Reason("likely to get a yaku (2 cards visible that will give a yaku if taken)",1)); } else { toKoi += .4; koiReasons.push(new Reason("possible yaku available if a month match is drawn",.4)); } } else { //get all possible deck cards that can match var cardsAvail = getMonthCards(hand[i].month) if (cardsAvail) { //get values for unseen cards for (var k = 0;k<cardsAvail.length;k++) cardsAvail[k].value = getValue(cardsAvail[k]); var bestCard = getHighestValue(cardsAvail); //at this point, this should always be true if (hand[i] && bestCard) { var yakus = getYakus(yourCards, enemyCards, hand[i], bestCard); for (var k=0;k<10;k++) if (yakus[k] > 0) { var dist = yakus[k]; toKoi += 1/(dist*dist) / 10; var text = "card " + getCardType(hand[i]) + " + "+ getCardType(bestCard) + " yields dist " + dist + " away from a "; if (k == 0) text+="plain yaku"; else if (k == 1) text+="ribbon yaku"; else if (k == 2) text+="animal yaku"; else if (k == 3) text+="RoShamBo yaku"; else if (k == 4) text+="red ribbon yaku"; else if (k == 5) text+="blue ribbon yaku"; else if (k == 6) text+="bright yaku"; else if (k == 7) text+="loop yaku"; else if (k == 8) text+="sacrifice yaku"; else text="card " + getCardType(hand[i]) + " on "+ getCardType(bestCard) +" gives a wet yaku"; if (1/(dist*dist)/10 >= .03) koiReasons.push(new Reason(text,Math.round(100/(dist*dist))/1000)); } } } } } //perform the same check on field cards (with even lower likelihoods) for (var i=0;i<playField.length;i++) { if (givesYaku(yourCards, playField[i].num)) { if (guaranteed(playField[i]) > 0) { toKoi += 5; koiReasons.push(new Reason("Guaranteed a yaku",5)); } else if (hasMonth(hand, playField[i].month)) { toKoi+=1; koiReasons.push(new Reason("likely to get a yaku (2 cards visible that will give a yaku if taken)",1)); } else { toKoi += .4; koiReasons.push(new Reason("possible yaku available if a month match is drawn",.4)); } } //get all possible cards in the deck cards that can match var cardsAvail = getMonthCards(playField[i].month) if (cardsAvail) { //get values for unseen cards for (var k = 0;k<cardsAvail.length;k++) cardsAvail[k].value = getValue(cardsAvail[k]); var bestCard = getHighestValue(cardsAvail) //at this point, this should always be true if (playField[i] && bestCard) { var yakus = getYakus(yourCards, enemyCards, playField[i], bestCard); for (var k=0;k<10;k++) if (yakus[k] > 0) { var dist = yakus[k]; toKoi += 1/(dist*dist) / 10; var text = "card " + playField[i].num + " + "+ bestCard.num + " yields dist " + dist + " away from a "; if (k == 0) text+="plain yaku"; else if (k == 1) text+="ribbon yaku"; else if (k == 2) text+="animal yaku"; else if (k == 3) text+="RoShamBo yaku"; else if (k == 4) text+="red ribbon yaku"; else if (k == 5) text+="blue ribbon yaku"; else if (k == 6) text+="bright yaku"; else if (k == 7) text+="loop yaku"; else if (k == 8) text+="sacrifice yaku"; else text="card " + playField[i].num + " on "+ bestCard.num +" gives a wet yaku"; if (1/(dist*dist)/10 >= .03) koiReasons.push(new Reason(text,Math.round(100/(dist*dist))/1000)); } } } } var checkList = [] for (var gg = 0;gg<hand.length;gg++) checkList.push(hand[gg]); for (var gg = 0;gg<playField.length;gg++) checkList.push(playField[gg]); //check enemy yakus toBank += listHasYakus(enemyCards); if (toBank > 0) bankReasons.push(new Reason("Enemy Already Has Yakus", toBank)); for (var i=0;i<checkList.length;i++) { if (guaranteed(checkList[i].num) > 0) continue; var yakus = [], total = 0; var possibleCards = getAvailable(checkList[i].month); var storedBankReasons = []; if (possibleCards) { for (var j = 0;j<possibleCards.length;j++) { if (possibleCards[j].num != checkList[i].num) { var tmpList = []; var enemyYakus = 0; tmpList.push(possibleCards[j]); tmpList.push(checkList[i]); enemyYakus = listGivesYaku(enemyCards, tmpList); if (enemyYakus > 0) { bankReasons.push(new Reason("If the enemy plays " + getCardType(checkList[i])+ " with " + getCardType(possibleCards[j]) + " he can get a yaku", enemyYakus)); toBank += enemyYakus; break; } else { var count = 0; yakus = getYakus(enemyCards, yourCards, possibleCards[j], checkList[i]); var tmpBankReasons = []; for (var k=0;k<10;k++) if (yakus[k] > 0) { var dist = yakus[k]; count += 3/(dist*dist) / 10; var text = "card " + getCardType(checkList[i]) + " + "+ getCardType(possibleCards[j]) + " yields dist " + dist + " away from a "; if (k == 0) text+="plain yaku"; else if (k == 1) text+="ribbon yaku"; else if (k == 2) text+="animal yaku"; else if (k == 3) text+="RoShamBo yaku"; else if (k == 4) text+="red ribbon yaku"; else if (k == 5) text+="blue ribbon yaku"; else if (k == 6) text+="bright yaku"; else if (k == 7) text+="loop yaku"; else if (k == 8) text+="sacrifice yaku"; else text="card " + getCardType(checkList[i]) + " with "+ getCardType(possibleCards[j]) +"gives a wet yaku"; if (3/(dist*dist)/10 >= .03) tmpBankReasons.push(new Reason(text,Math.round(300/(dist*dist))/1000)); } if (count > total) { total = count; storedBankReasons = tmpBankReasons; } } } } } if (total > 0) { for (var bb = 0;bb < storedBankReasons.length;bb++) bankReasons.push(storedBankReasons[bb]); threshold += total; total = 0; } } if (getYourScoreToWin() - getScore() <= 0) { if (toBank > 0 || !gYaku) { bankReasons.push(new Reason("If you bank now, you win and this game is not guaranteed","")); return false; } } if (toBank >= 2) { bankReasons.push(new Reason("Since the enemy is close to obtaining 2 yakus, I've decided to bank","")); return false; } if (getScore() < 7) { toKoi += .5; koiReasons.push(new Reason("Your point score is low, and not as much to lose by koikoi",.3)); } if (getScore() >= 7) { toBank += (getScore()-7)/2; bankReasons.push(new Reason("You've exceeded the 2x multiplier, its best to bail before you lose big",Math.round(100*(getScore()-7)/3)/100)); } var reason1 = new Reason("The total sum of weighted distances to enemy yakus is " + Math.round((threshold + toBank)*100)/100,""); var reason2 = new Reason("The total sum of weighted distances to your yakus is " + Math.round(toKoi*100)/100,""); koiReasons.push(reason2); bankReasons.push(reason1); if (toKoi < (threshold + toBank)) { bankReasons.push(new Reason("Since the enemy has been calculated to have a high chance of reaching a yaku, I think its best if you bank now","")); return false; } koiReasons.push(new Reason("Since the enemy seems much further from reaching a yaku then you, I would suggest koikoi","")); return true; } function showCardList(List) { var sum = ""; for (var i=0;i<List.length;i++) sum = sum + " " + List[i].num; alert(sum) } //Debug method for displaying a list of card values in BVS Daily (for a card list) function showCardValues(List) { var sum = ""; for (var i=0;i<List.length;i++) sum = sum + " " + List[i].value; alert(sum) } //returns an array distance values from yakus function getYakus(list, blockList, card1, card2) { if (!card2) card2 = new Card(-1,null) if (!card1) card1 = new Card(-1,null) var yaku = []; if (isPlain(card1.num) || isPlain(card2.num )) yaku[0] = Math.max(1, 9 - plainCount(list) ); else yaku[0] = 0; if (isPlain(card1.num) && isPlain(card2.num )) yaku[0] = Math.max(1, 8 - plainCount(list) ); else yaku[0] = 0; if (isRibbon(card1.num) || isRibbon(card2.num )) yaku[1] = Math.max(1, 4 - ribbonCount(list) ); else yaku[1] = 0; if (isAnimal(card1.num) || isAnimal(card2.num )) yaku[2] = Math.max(1, 4 - animalCount(list) ); else yaku[2] = 0; if (isRoShamBo(card1.num) || isRoShamBo(card2.num)) yaku[3] = Math.max(1, 2 - roCount(list) ); else yaku[3] = 0; if (isRribbon(card1.num) || isRribbon(card2.num )) yaku[4] = Math.max(1, 2 - rRibbonCount(list) ); else yaku[4] = 0; if (isBribbon(card1.num) || isBribbon(card2.num )) yaku[5] = Math.max(1, 2 - bRibbonCount(list) ); else yaku[5] = 0; if (isBright(card1.num) || isBright(card2.num )) yaku[6] = Math.max(1, 2 - brightCount(list) ); else yaku[6] = 0; if (isLoop(card1.num) || isLoop(card2.num )) yaku[7] = Math.max(1, 1 - loopCount(list) ); else yaku[7] = 0; if (isSac(card1.num) || isSac(card2.num )) yaku[8] = Math.max(1, 1 - sacrificeCount(list)); else yaku[8] = 0; if ( (isWet(card1.num) || isWet(card2.num )) && brightCount(list) > 2) yaku[9] = 1; else yaku[9] = 0; if (has2Bri(blockList)) yaku[6] = 0; if (hasRo(blockList)) yaku[3] = 0; if (hasRed(blockList)) yaku[4] = 0; if (hasBlue(blockList)) yaku[5] = 0; if (hasSac(blockList)) yaku[8] = 0; if (hasLoop(blockList)) yaku[7] = 0; return yaku; } //returns the card with the highest value from a list function getHighestValue(cardList) { if (!cardList) return null; //find the best highest valued card that matches var bestCard = cardList[0]; for (var k = 1;k<cardList.length;k++) if (cardList[k].value > bestCard.value) bestCard = cardList[k]; return bestCard; } function getLowestValue(cardList) { if (!cardList) return null; //find the best highest valued card that matches var worst=cardList[0]; for (var k = 1;k<cardList.length;k++) if (cardList[k].value < worst.value) worst = cardList[k]; return worst; } function playDeck() { var playOn = []; //handle playing a card off deck if necessary for (var i=0;i<playField.length;i++) if (playField[i].month == deckCard.month) playOn.push(playField[i]); for (var i=0;i<playOn.length;i++) playOn[i].playable = true; //find the best card and pick it var bestCard = getHighestValue(playOn) if (bestCard) clickRadioButton(bestCard.element); else clickRadioButton(emptySlot); } function playCard() { //if a yaku is guaranteed, let this function handle it, and do nothing if (playGuaranteedYaku()) return true; //get a list of cards in hand with matching months to cards on field var playList = []; var playOn = []; //get a list of cards in month/hand with matching months for (var k = 1;k<13;k++) //for all the months { var list1 = getMonthFromList(hand,k); var list2 = getMonthFromList(playField,k); if (list1.length == 0 || list2.length == 0 || noPlayMonth == k || cheatMonth == k) continue; for (var i=0;i<list1.length;i++) { list1[i].playable = true; playList.push(list1[i]); } for (var i=0;i<list2.length;i++) { list2[i].playable = true; playOn.push(list2[i]); } } if (playOn.length == 0) return false; var newPlayList = []; for (var i=0;i<playList.length;i++) { if (guaranteed(playList[i])>0) newPlayList.push(playList[i]); else { //get all possible deck cards that can match var cardsAvail = getMonthCards(playList[i].month) //get values (and half them) for unseen cards for (var k = 0;k<cardsAvail.length;k++) cardsAvail[k].value /= 2; //find the best matching field card bestCard=null, omit = false; for (var k = 0;k<playField.length;k++) if (playField[k].month == playList[i].month) { if (!bestCard) bestCard = playField[k]; else if (playField[k].value > bestCard.value) bestCard = playField[k]; } for (var k = 0;k<cardsAvail.length;k++) if (bestCard && bestCard.value < cardsAvail[k].value) omit = true; //if the unseen card is worth more than double a known card, wait (remove it from possiblities) if (!omit) newPlayList.push(playList[i]); } } playList = newPlayList; var handCard = null; var fieldCard = null; //loop and find the best pair for (var i=0;i<playList.length;i++) for (var j=0;j<playOn.length;j++) { if (playList[i].month != playOn[j].month) continue; if (!handCard || !fieldCard) { handCard = playList[i]; fieldCard = playOn[j]; } else if ((playList[i].value + playOn[j].value) > (handCard.value + fieldCard.value)) { handCard = playList[i]; fieldCard = playOn[j]; } } if (!handCard || !fieldCard) return false; if (cheatCard.value > (handCard.value + fieldCard.value)) return false; //click the form radio buttons clickRadioButton(handCard.element); clickRadioButton(fieldCard.element); return true; } //returns -1 if it made a play, 0 if no guaranteed yakus (continue game) // otherwise it returns a month value (to protect if forced koi) function playGuaranteedYaku() { //for all months for (var i = 1;i < 13;i++) { var yakuList = []; var handMonths = getMonthFromList(hand, i); var fieldMonths = getMonthFromList(playField, i); if (!handMonths) continue; var combineCards = handMonths.concat(fieldMonths); if (!listGivesYaku(yourCards, combineCards) || handMonths.length < 1) continue; //get a list of all pairs that can give yakus for (var j=0;j<combineCards.length;j++) for (var k=0;k<combineCards.length;k++) if (j != k && guaranteed(combineCards[j])>0 && guaranteed(combineCards[j])>0) { var tmpList = []; tmpList.push(combineCards[j]); tmpList.push(combineCards[k]); if (listGivesYaku(yourCards, tmpList)) yakuList.push(new cardPair(combineCards[j],combineCards[k])); } //for all the pairs, skip it if its not possible for (var b=0;b<yakuList.length;b++) { var inHand = 0; var onField = 0; if (hasCard(hand, yakuList[b].card1.num)) inHand++; if (hasCard(hand, yakuList[b].card2.num)) inHand++; if (hasCard(playField, yakuList[b].card1.num)) onField++; if (hasCard(playField, yakuList[b].card2.num)) onField++; //this is the easiest case, simply play the yaku if (inHand == 1 && onField == 1 && onField) { if (forceKoi && !enemyHasKoiKoi && guaranteed(yakuList[b].card1)>0 && guaranteed(yakuList[b].card2)>0) { if (hasCard(hand, yakuList[b].card1.num)) { yakuHandCard = yakuList[b].card1; yakuFieldCard = yakuList[b].card2; } else { yakuHandCard = yakuList[b].card2; yakuFieldCard = yakuList[b].card1; } noPlayMonth = i; return false; } clickRadioButton(yakuList[b].card1.element); clickRadioButton(yakuList[b].card2.element); return true; } // this is the harder case, you must first play 2 cards, so just pick the 2 highest value cards else if (monthCount(yakuList[b].card1.month, playField) > 0) continue; else if (inHand == 2 && fieldMonths == 0) { //discard either one (both are guaranteed) clickRadioButton(yakuList[b].card1.element); clickRadioButton(emptySlot); return true; } } } //run a second check to see if there is a guaranteed yaku made up of 2+ guaranteed cards var gList = [], card1, card2; for (var i=0;i<hand.length;i++) if (guaranteed(hand[i])>0) gList.push(hand[i]); for (var i=0;i<playField.length;i++) if (guaranteed(playField[i])>0) gList.push(playField[i]); //this runs if you're playing towards 1 card from a guaranteed yaku var playCard = null; if (sacCount(yourCards) + sacCount(gList) == 3 && sacCount(gList) > 1) playCard = findSac(gList); if (loopCount(yourCards) + loopCount(gList) == 3 && loopCount(gList) > 1) playCard = findLoop(gList); if (redCount(yourCards) + redCount(gList) == 3 && redCount(gList) > 1) playCard = findRed(gList); if (blueCount(yourCards) + blueCount(gList) == 3 && blueCount(gList) > 1) playCard = findBlue(gList); if (roCount(yourCards) + roCount(gList) == 3 && roCount(gList) > 1) playCard = findRo(gList); if (brightCount(yourCards) + brightCount(gList) == 3 && brightCount(gList) > 1) playCard = findBright(gList); if (!playCard) return false; //find playcard, and match it with another card var month = playCard.month; var otherCard = null; if (hasCard(hand, playCard.num)) { for (var i=0;i<playField.length;i++) if (playField[i].month == month) { otherCard = playField[i]; break; } } else if (hasCard(playField, playCard.num)) { for (var i=0;i<hand.length;i++) if (hand[i].month == month) { otherCard = hand[i]; break; } } if (!playCard || !otherCard) return false; clickRadioButton(playCard.element); clickRadioButton(otherCard.element); } function cardPair(card1, card2) { this.card1 = card1; this.card2 = card2; } function getMonthFromList(list, month) { var cardList = []; for (var h = 0;h < list.length;h++) if (list[h].month == month) cardList.push(list[h]); return cardList; } function getMonthCards(month) { var cardList = []; for (var h = 0;h < 4;h++) if (isAvailable(h+(month-1)*4+1)) cardList.push(new Card(h+(month-1)*4+1,null)); return cardList; } function discard() { var worstCard = null; if ((cheatMonth > 0 && !hasMonth(playField, cheatMonth) && hasMonth(hand, cheatMonth))) worstCard = cheatCard; else { // calculate card values for cards in hand for (var i=0;i<hand.length;i++) { if (guaranteed(hand[i]) == -1) { worstCard = hand[i]; break; } hand[i].playable=true; var month = hand[i].month; // this is designed to help with the selection of guaranteed cards // (the best guaranteed card will be discarded to be taken) if (guaranteed(hand[i])>0) hand[i].value = 0.01 - hand[i].value; //add the value of the highest valued card (for that month) to the discard value else if (!guaranteed(hand[i])>0) { var cardsAvail = getAvailable(month); var bestVal=0 for (var k=0;k<cardsAvail.length;k++) { var newVal = cardsAvail[k].value; if (newVal > bestVal) bestVal = newVal; } hand[i].value += bestVal; } } //find the worst card and drop it worstCard = null; for (var k = 0;k<hand.length;k++) if ((worstCard == null || hand[k].value < worstCard.value) && hand[k].month != noPlayMonth) worstCard = hand[k]; } if (worstCard) { clickRadioButton(worstCard.element); clickRadioButton(emptySlot); var otherCard=null; //in case we have a matching month check here for (var g =0;g<playField.length;g++) if (playField[g].month == worstCard.month) { playField[g].playable = true; if(!otherCard) otherCard = playField[g]; else if (otherCard.value > playField[g].value) otherCard = playField[g]; } if (otherCard) clickRadioButton(otherCard.element); } else { if (yakuHandCard && yakuFieldCard) { clickRadioButton(yakuHandCard.element); clickRadioButton(yakuFieldCard.element); } } } function updateMultipliers() { //check for conflicting cards var conflictBlue = false; var conflictRed = false; var conflictRo = false; var conflictBright = 0; var conflictLoop = false; var conflictSac = false; //update card multipliers from cards possesed by you and opponent for (var i=0;i<yourCards.length;i++) { num = yourCards[i].num; if (isRoShamBo(num)) conflictRo = true; if (isRribbon(num) ) conflictRed = true; if (isBribbon(num) ) conflictBlue = true; if (isBright(num) ) conflictBright += 1; if (isLoop(num) ) conflictLoop = true; if (isSac(num) ) conflictSac = true; } var chkBright = 0; for (var i=0;i<enemyCards.length;i++) { num = enemyCards[i].num; if (isRoShamBo(num) && conflictRo ) RoShamBo = 0; if (isRribbon(num) && conflictRed ) Rribbon = 0; if (isBribbon(num) && conflictBlue ) Bribbon = 0; if (isBright(num) && conflictBright > 1) chkBright ++; if (isLoop(num) && conflictLoop ) loop = 0; if (isSac(num) && conflictSac ) sacrifice = 0; } if (chkBright == 2) brights = 0; ////for all cards in the deck, add a block factor based on enemy yakus for (var k =1;k<13;k++) { possibleCards = getMonthCards(k); for (var i = 0;i<possibleCards.length;i++) { var ccard = possibleCards[i]; if (isRoShamBo(ccard.num)) monthMult[k] += (RoShamBo * roCount(enemyCards)) / monthBlock / 3; if (isRribbon(ccard.num)) monthMult[k] += (Rribbon * rRibbonCount(enemyCards)) / monthBlock / 3; if (isBribbon(ccard.num)) monthMult[k] += (Bribbon * bRibbonCount(enemyCards)) / monthBlock / 3; if (isBright(ccard.num)) monthMult[k] += (brights * brightCount(enemyCards)) / monthBlock / 3; if (isLoop(ccard.num)) monthMult[k] += (loop * loopCount(enemyCards)) / monthBlock / 2; if (isSac(ccard.num)) monthMult[k] += (sacrifice * sacrificeCount(enemyCards)) / monthBlock / 2; if (isRibbon(ccard.num)) monthMult[k] += (ribbon * ribbonCount(enemyCards)) / monthBlock / 5; if (isAnimal(ccard.num)) monthMult[k] += (animal * animalCount(enemyCards)) / monthBlock / 5; if (isPlain(ccard.num)) monthMult[k] += (plain * plainCount(enemyCards)) / monthBlock / 10; } } } function getCardType(card) { var num = card.num; retString = ""; if (card.month == 1 ) retString += "Jan"; if (card.month == 2 ) retString += "Feb"; if (card.month == 3 ) retString += "Mar"; if (card.month == 4 ) retString += "Apr"; if (card.month == 5 ) retString += "May"; if (card.month == 6 ) retString += "Jun"; if (card.month == 7 ) retString += "jul"; if (card.month == 8 ) retString += "Aug"; if (card.month == 9 ) retString += "Sep"; if (card.month == 10) retString += "Oct"; if (card.month == 11) retString += "Nov"; if (card.month == 12) retString += "dec"; if (isRoShamBo(num)) retString += " A(Ro)"; else if (isRribbon (num)) retString += " R(Red)"; else if (isBribbon (num)) retString += " R(Blue)"; else if (isWet (num)) retString += " B(wet)"; else if (isBright (num)) retString += " B(dry)"; else if (isLoop (num)) retString += " P(loop)"; else if (isSac (num)) retString += " A(sac)"; else if (isRibbon (num)) retString += " R"; else if (isAnimal (num) && isPlain (num)) retString += " P+A"; else if (isAnimal (num)) retString += " A"; else if (isPlain (num)) retString += " P"; return retString; } function hasCard(list, num) { for (var i = 0;i<list.length;i++) if (list[i].num == num) return true; return false; } function clickRadioButton(element) { if (!element) return false; var strXPath = ""; strXPath += '//form[@name=\'' + "placecard" + '\']'; strXPath += '//input[@id=\'' + element.getAttribute("for") + '\']'; var elem = document.evaluate(strXPath, document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue; elem.checked = true; } function submitForm(formName) { if (document.forms.namedItem(formName)) document.forms.namedItem(formName).submit(); } function getMultipliers(card, isField) { var num = card.num; var month = card.month; var multList = [] if (isPlain(num) ) multList.push(new Mult("is a member of plain yaku", Math.round(plainMult () *1000)/1000)); if (isRibbon(num) ) multList.push(new Mult("is a member of ribbon yaku", Math.round(ribbonMult () *1000)/1000)); if (isAnimal(num) ) multList.push(new Mult("is a member of animal yaku", Math.round(animalMult () *1000)/1000)); if (isRribbon(num) ) multList.push(new Mult("is a member of red ribbon yaku", Math.round(RribbonMult () *1000)/1000)); if (isBribbon(num) ) multList.push(new Mult("is a member of blue ribbon yaku", Math.round(BribbonMult () *1000)/1000)); if (isRoShamBo(num) ) multList.push(new Mult("is a member of roshambo yaku", Math.round(RoShamBoMult() *1000)/1000)); if (isBright(num) ) multList.push(new Mult("is a member of bright yaku", Math.round(brightMult () *1000)/1000)); if (isLoop(num) ) multList.push(new Mult("is a member of loop yaku", Math.round(loopMult () *1000)/1000)); if (isSac(num) ) multList.push(new Mult("is a member of sacrifice yaku", Math.round(sacMult () *1000)/1000)); if (isWet(num) ) multList.push(new Mult("can combine with 3+ brights", Math.round(wetMult () *1000)/1000)); if (givesYaku(yourCards, num)) multList.push(new Mult("This card gives you a yaku", Math.round(yakuMult *1000)/1000)); if (monthMult[month] > 0) multList.push(new Mult("Month Deny (this value is added after 2/3 card negative multiplyers)", Math.round(monthMult[month]*1000)/1000)) if (isField) multList.push(new Mult("Card is on field", "+" + ((Math.round(onFieldMult*1000)/1000) * 100) + "%")) if (guaranteed(card)>0) multList.push(new Mult(" of normal value (card is guaranteed)", guaranteeMult *100 + "%")) if (discarding && monthCount(month, hand) > 1) multList.push(new Mult("2 of a kind in hand", "-" +((Math.round((1- twoInHand)*1000)/1000) * 100) + "%")) if (discarding && monthCount(month, hand) > 2) multList.push(new Mult("3 of a kind in hand (stacks with 2 of a kind)", "-" + ((Math.round((1- threeInPlay)*1000)/1000) * 100) + "%")) return multList; } //initiate a card object function Mult(Name, Value) { this.name = Name; this.value = Value; } function Reason(Text, Value) { this.text = Text; this.value = Value; } function getScore() { var mtch = /: <b><i>(\d+)\S+?;Points<\/i><\/b><ta/.exec(document.body.innerHTML); if (!mtch) return -1; return parseInt(mtch[1]); } this.debugBtn = document.createElement("a"); this.debugWindow1 = document.createElement("db1"); this.debugWindow2 = document.createElement("db2"); var forfForm = document.forms.namedItem("forfeit"); // add the debug popup button insertAfter(this.debugBtn, forfForm); this.debugBtn.style.color = "#000066"; this.debugBtn.style.fontSize = "25px"; if (document.forms.namedItem("bankpoints") || document.forms.namedItem("koikoi")) this.debugBtn.href = "javascript:document.getElementById('debugWindow2').style.display = ''; void(0);"; else this.debugBtn.href = "javascript:document.getElementById('debugWindow1').style.display = ''; void(0);"; this.debugBtn.innerHTML = "<b>debug script ></b>"; insertAfter(document.createElement("br"), forfForm); // add the debug window information this.debugWindow1.id = "debugWindow1" ; this.debugWindow2.id = "debugWindow2" this.debugWindow1.style.display = "none" ; this.debugWindow2.style.display = "none" this.debugWindow1.style.fontFamily = "arial" ; this.debugWindow2.style.fontFamily = "arial" this.debugWindow1.style.fontSize = "20px" ; this.debugWindow2.style.fontSize = "20px" this.debugWindow1.style.position = "fixed" ; this.debugWindow2.style.position = "fixed" this.debugWindow1.style.bottom = "0px" ; this.debugWindow2.style.bottom = "0px" this.debugWindow1.style.right = "0px" ; this.debugWindow2.style.right = "0px" this.debugWindow1.style.width = "400px" ; this.debugWindow2.style.width = "600px" this.debugWindow1.style.height = "500px" ; this.debugWindow2.style.height = "550px" this.debugWindow1.style.textalign = "center" ; this.debugWindow2.style.textalign = "center" this.debugWindow1.style.backgroundColor = "#B5D7E0"; this.debugWindow2.style.backgroundColor = "#B5D7E0" if (document.forms.namedItem("bankpoints") || document.forms.namedItem("koikoi")) { var tmpList = [ "<span style=\"float: right; cursor: pointer;\" onclick=\"document.getElementById('debugWindow2').style.display='none';\"><b>Close [X]</b></span><br/>", "<b>Reasons to KoiKoi                   Reasons to Bank</b>", "<table style=\"z-index: 50; width: 600px; font-size: 10px; height: 12px;\">", "<tr>"].join(""); var currKoi=0; var currBank=0; while (currKoi < koiReasons.length || currBank < bankReasons.length) { if (currKoi < koiReasons.length) { tmpList+= "<td style=\"left: 0px;\"><b>"+koiReasons[currKoi].value+"</b></td>"; tmpList+= "<td style=\"left: 30px;\"><b>"+koiReasons[currKoi].text+"</b></td>"; currKoi++; } else { tmpList+= "<td style=\"left: 0px;\"><b></b></td>"; tmpList+= "<td style=\"left: 30px;\"><b></b></td>"; } if (currBank < bankReasons.length) { tmpList+= "<td style=\"left: 400px;\"><b>"+bankReasons[currBank].value+"</b></td>"; tmpList+= "<td style=\"left: 430px;\"><b>"+bankReasons[currBank].text+"</b></td>"; currBank++; } else { tmpList+= "<td style=\"left: 0px;\"><b></b></td>"; tmpList+= "<td style=\"left: 30px;\"><b></b></td>"; } tmpList+="</tr>"; } tmpList+="</table>"; this.debugWindow2.innerHTML = tmpList; document.body.appendChild(this.debugWindow2); } else { var tmpList = [ "<span style=\"float: right; cursor: pointer;\" onclick=\"document.getElementById('debugWindow1').style.display='none';\"><b>Close [X]</b></span><br/>", "<b>Hand</b>", "<table style=\"z-index: 50; width: 400px; font-size: 10px; height: 12px;\">", "<tr>", "<td style=\"left: 0px;\"><b>Card</b></td>", "<td style=\"left: 80px;\"><b>Value</b></td>", "<td style=\"left: 160px;\"><b>Point BreakDown</b></td>", "</tr>"].join(""); for (var g = 0;g < hand.length;g++) { if (hand[g].playable) { tmpList+= [ "<tr>", "<td style=\"left: 0px;\"><b>" + getCardType(hand[g]) + "</b></td>", "<td style=\"left: 80px;\"><b>" + Math.round(hand[g].value*1000)/1000 + "</b></td>", "<td style=\"left: 160px;\"><b>" ].join(""); var mults = getMultipliers(hand[g], false); if (mults) { for (var d = 0;d < mults.length;d++) tmpList += mults[d].value + " " + mults[d].name + "<BR>"; } tmpList += ["</b></td></tr>"].join(""); } } tmpList += "</table>"; tmpList += "<b>Field</b>" + "<table style=\"z-index: 50; width: 400px; font-size: 10px; height: 12px;\">" + "<tr>" + "<td style=\"left: 0px;\"><b>Card</b></td>"+ "<td style=\"left: 80px;\"><b>Value</b></td>"+ "<td style=\"left: 160px;\"><b>Point BreakDown</b></td>"+ "</tr>"; for (var g = 0;g < playField.length;g++) { if (playField[g].playable) { tmpList+= [ "<tr>", "<td style=\"left: 0px;\"><b>" + getCardType(playField[g]) + "</b></td>", "<td style=\"left: 80px;\"><b>" + Math.round(playField[g].value*1000)/1000 + "</b></td>", "<td style=\"left: 160px;\"><b>" ].join(""); var mults = getMultipliers(playField[g], true); if (mults) { for (var d = 0;d < mults.length;d++) tmpList += mults[d].value + " " + mults[d].name + "<BR>"; } tmpList += ["</b></td></tr>"].join(""); } } tmpList += "</table>"; this.debugWindow1.innerHTML = tmpList; document.body.appendChild(this.debugWindow1); } function getYourScoreToWin() { var mtches = /You \((Dealer - )?Current Score: (\d+) :: (\d+) to win\)/.exec(document.body.innerHTML); return parseInt(mtches[3]) - parseInt(mtches[2]); } function getEnemyScoreToWin() { var mtches = /Them \((Dealer - )?Current Score: (\d+) :: (\d+) to win\)/.exec(document.body.innerHTML); return parseInt(mtches[3]) - parseInt(mtches[2]); } function setCheat(inStr) { var defaultChoice; if (inStr == "koi") defaultChoice = /value="(\d)">Force Koi-Koi/.exec(document.body.innerHTML)[1]; if (inStr == "topCard") defaultChoice = /value="(\d)">View top of deck/.exec(document.body.innerHTML)[1]; if (inStr == "minusCard") defaultChoice = /value="(\d)">Opp. -1 Take/.exec(document.body.innerHTML)[1]; if (inStr == "redraw") defaultChoice = /value="(\d)">Shuffle \+ Redraw/.exec(document.body.innerHTML)[1]; if (inStr == "bottom") defaultChoice = /value="(.*)">Top Card to Bottom/.exec(document.body.innerHTML)[1]; if (!defaultChoice) defaultChoice = "none"; if (defaultChoice != "none") useCheat = inStr; var elems = document.forms.namedItem("cheat").elements; for (var i=0;i<elems.length;i++) if (elems[i].getAttribute("name") == "hanacheat") elem = elems[i]; elem.value = defaultChoice; } function processEvent(event) { var input = event.keyCode; if(input==67) { if (useCheat) submitForm("cheat"); else //change the keycode to run regular script choice input = 68; } if(input==68) { if(document.forms.namedItem("nextround")) { //disable forced koi flag at end of round GM_setValue("forcedKoi", false); submitForm("nextround"); } else if (document.forms.namedItem("bankpoints") && !document.forms.namedItem("koikoi")) submitForm("bankpoints"); else if (document.forms.namedItem("bankpoints") && document.forms.namedItem("koikoi")) { if (koikoi()) submitForm("koikoi"); else submitForm("bankpoints"); } else submitForm("placecard"); } if(input==86) { if (document.forms.namedItem("bankpoints") || document.forms.namedItem("koikoi")) { if (document.getElementById('debugWindow2').style.display == '') document.getElementById('debugWindow2').style.display = 'none' else document.getElementById('debugWindow2').style.display = ''; } else { if (document.getElementById('debugWindow1').style.display == '') document.getElementById('debugWindow1').style.display = 'none' else document.getElementById('debugWindow1').style.display = ''; } } } window.addEventListener("keyup", processEvent, false);