BVS Flower Wars AI

Makes play choices for the bvs game Flower Wars

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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 &gt;</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&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbspReasons 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);