//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
///
///  The strategy of the simulated players is straight-
///  forward.  They choose lead-off cards at random.
///  They always choose to combo with a punch if they
///  have one instead of draw.  They randomly choose to
///  take health or discard until they have two hits
///  left, then they always discard if they can.
///
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
import java.util.LinkedList;
import java.util.Random;
import java.io.*;


public class ToTheMatSim
{
  // constants
  static public final int NUMPLAYERS  = 9;
  static public final int MAXHANDSIZE = 100;
  static public final int POW_JAB     = 0;
  static public final int POW_CROSS   = 1;
  static public final int POW_HOOK    = 1;
  static public final int POW_UPPER   = 2;


  static public final int NO_CARD = -1;
  // special enumerations where value must be zero-based
  static public final int GUARD    = 0;
  static public final int JAB      = 1;
  static public final int CROSS    = 2;
  static public final int HOOK     = 3;
  static public final int UPPERCUT = 4;
  static public final int STEP     = 5;
  static public final int SPECIAL  = 6;
  // and a sequential constant
  static public final int NUMCARDTYPES = 7;


  // other enumerations, value is unimportant but unique
  static public final int DRAW_OK     = 12;
  static public final int DRAW_SOME   = 13;
  static public final int DRAW_NONE   = 14;

  static public final int OUTFIGHTER  = 28;
  static public final int SLUGGER     = 29;
  static public final int SWARMER     = 30;

  static public final int NEAR        = 55;
  static public final int FAR         = 56;


  static public final int MOD_SPEC_SLIP   = -10;
  static public final int MOD_SPEC_BOMB   = -11;
  static public final int MOD_SPEC_PARRY  = -12;


  static public final int RES_INVALID     = 80;
  static public final int RES_END         = 81;
  static public final int RES_P1_LANDED   = 82;
  static public final int RES_P2_LANDED   = 83;
  static public final int RES_P1_GUARDED  = 84;
  static public final int RES_P2_GUARDED  = 85;
  static public final int RES_TRADE       = 86;


  static public final int FIGHTER_SUGAR = 1001;
  static public final int FIGHTER_ALI   = 1002;
  static public final int FIGHTER_CORB  = 1003;
  static public final int FIGHTER_ROCKY = 1004;
  static public final int FIGHTER_FRAZ  = 1005;
  static public final int FIGHTER_DURAN = 1006;
  static public final int FIGHTER_LOUIS = 1007;
  static public final int FIGHTER_GEO   = 1008;
  static public final int FIGHTER_LIST  = 1009;



  Random random;

  int distance;

  Player player1;
  Player player2;

  int deckTotal;
  int[] deckCards;
  int[] discardCards;

  PrintWriter pw = null;


  static public void main( String arg[] )
  {
    ToTheMatSim tbs = new ToTheMatSim();
  }


  public ToTheMatSim()
  {
    random = new Random();
    deckCards = new int[NUMCARDTYPES];
    discardCards = new int[NUMCARDTYPES];

    Player players[] = new Player[NUMPLAYERS];


    // MINIMUM ENDURANCE is 3, MAX is 6

    //                                           hand size, then max endurance
    players[0] = new Player( FIGHTER_SUGAR, OUTFIGHTER,  8, 5 );
    players[1] = new Player( FIGHTER_ALI,   OUTFIGHTER,  9, 4 );
    players[2] = new Player( FIGHTER_CORB,  OUTFIGHTER,  9, 3 );

    players[3] = new Player( FIGHTER_ROCKY, SWARMER,     6, 5 );
    players[4] = new Player( FIGHTER_FRAZ,  SWARMER,     7, 4 );
    players[5] = new Player( FIGHTER_DURAN, SWARMER,     8, 3 );

    players[6] = new Player( FIGHTER_LOUIS, SLUGGER,     7, 6 );
    players[7] = new Player( FIGHTER_GEO,   SLUGGER,     7, 6 );
    players[8] = new Player( FIGHTER_LIST,  SLUGGER,     6, 5 );

    try
    {
      pw = new PrintWriter( new BufferedWriter( new FileWriter( "playLog.txt" ) ) );
    } catch( Exception e ) {}

    for( int k = 0; k < 1000; ++k )
    {
      for( int j = 0; j < NUMPLAYERS - 1; ++j )
      {
        player1 = players[j];

        for( int i = j + 1; i < NUMPLAYERS; ++i )
        {
          player2 = players[i];

          try
          {
            pw.println( "\n################## New Round ##################\n" );
          } catch( Exception e ) {}

          playRound();
          verifyCardTotal();
        }
      }
    }

    try
    {
      pw.flush();
      pw.close();

      pw = new PrintWriter( new BufferedWriter( new FileWriter( "stats.csv" ) ) );
      pw.println( statHeaders() );

      for( int i = 0; i < NUMPLAYERS; ++i )
      {
        pw.println( players[i].stats() );
      }

      pw.flush();
      pw.close();
    } catch( Exception e ) {}
  }


  public void resetDeck()
  {
    deckCards[JAB]      =  7;
    deckCards[CROSS]    =  7;
    deckCards[HOOK]     =  6;
    deckCards[UPPERCUT] =  6;
    deckCards[STEP]     =  7;
    deckCards[GUARD]    =  4;
    deckCards[SPECIAL]  =  3;

    deckTotal = 0;
    for( int i = 0; i < NUMCARDTYPES; ++i )
    {
      discardCards[i] = 0;
      deckTotal += deckCards[i];
    }
  }


  public void playRound()
  {
    resetDeck();
    player1.reset();
    player2.reset();

    try
    {
      pw.println( player1 );
      pw.println( player2 );
      pw.println( "\n--- First Exchange ---\n" );
    } catch( Exception e ) {}

    distance = FAR;

    while( true )
    {
      // stop if either or both players are knocked out
      if( player1.endurance <= 0 ||
          player2.endurance <= 0 )
      {
        try { pw.println( "\n--- Somebody is down! ---\n" ); } catch( Exception e ) {}
        scoreRound();
        return;
      }

      // stop if there aren't enough cards to draw
      if( player1.drawCards( 1 ) != DRAW_OK )
      {
        try { pw.println( "\n--- Ding! ---\n" ); } catch( Exception e ) {}
        scoreRound();
        return;
      }
      if( player2.drawCards( 1 ) != DRAW_OK )
      {
        try { pw.println( "\n--- Ding! ---\n" ); } catch( Exception e ) {}
        scoreRound();
        return;
      }

      // for the first choice you can pick any card
      player1.combosJCHUSG( true, true, true, true, true, true, true );
      player2.combosJCHUSG( true, true, true, true, true, true, true );

      // then go around until you run out of opportunities
      // to continue the exchange
      boolean endExchange = false;

      while( !endExchange &&
             (player1.hasComboCard() || player2.hasComboCard())
           )
      {
        // choose cards and resolve the actions
        int p1card = player1.chooseComboCard();
        int p2card = player2.chooseComboCard();

        try
        {
          pw.println( nameToString( player1.name )+"'s ("+cardToString( p1card )+") vs "+
                      nameToString( player2.name )+"'s ("+cardToString( p2card )+")"      );
        } catch( Exception e ) {}

        int p1mod = preProcessCard( p1card, player1 );
        int p2mod = preProcessCard( p2card, player2 );

        int p1cardAltered = alterCard( p1card, player1 );
        int p2cardAltered = alterCard( p2card, player2 );

        player1.combosReset();
        player1.combosReset();

        Result r = resolveActions( p1cardAltered, p1mod, p2cardAltered, p2mod );

        // if the postProcessCard returns true then end the round
        if( postProcessCard( p1card, player1, r ) ) { scoreRound(); return; }
        if( postProcessCard( p2card, player2, r ) ) { scoreRound(); return; }

        switch( r.result )
        {
          case RES_END:
          {
            endExchange = true;
          } break;

          case RES_P1_LANDED:
          {
            if( random.nextInt( 2 ) == 0 )
            {
              if( player1.drawCards( 1 ) != DRAW_OK )
              {
                try { pw.println( "\n--- Ding! ---\n" ); } catch( Exception e ) {}
                scoreRound();
                return;
              }
              endExchange = true;
            }
          } break;

          case RES_P2_LANDED:
          {
            if( random.nextInt( 2 ) == 0 )
            {
              if( player2.drawCards( 1 ) != DRAW_OK )
              {
                try { pw.println( "\n--- Ding! ---\n" ); } catch( Exception e ) {}
                scoreRound();
                return;
              }
              endExchange = true;
            }
          } break;

          case RES_P1_GUARDED:  { endExchange = true; } break;
          case RES_P2_GUARDED:  { endExchange = true; } break;
          case RES_TRADE:       {                     } break;
          default: { assert false; }
        }
      }

      try
      {
        pw.println( "\n--- End Exchange ---\n" );
      } catch( Exception e ) {}

      player1.markHandSize();
      player2.markHandSize();
    }
  }


  public int preProcessCard( int card, Player p )
  {
    if( card      == CROSS   &&
        p.style   == SLUGGER &&
        distance  == FAR        )
    {
      return 1;
    }

    if( card == SPECIAL && p.name == FIGHTER_SUGAR )
    {
      return MOD_SPEC_SLIP;
    }

    if( card == SPECIAL && p.name == FIGHTER_FRAZ )
    {
      return MOD_SPEC_PARRY;
    }

    if( card == SPECIAL && p.name == FIGHTER_LOUIS )
    {
      return MOD_SPEC_BOMB;
    }

    if( card == SPECIAL && p.name == FIGHTER_ROCKY )
    {
      return 1;
    }

    if( card == SPECIAL && p.name == FIGHTER_GEO )
    {
      if( p.chooseAnyCard() != NO_CARD )
      {
        return 1;
      }
    }

    if( card == SPECIAL && p.name == FIGHTER_LIST )
    {
      return 1;
    }

    // otherwise there is no modification
    return 0;
  }


  public int alterCard( int card, Player p )
  {
    if( card == SPECIAL && p.name == FIGHTER_SUGAR )
    {
      // slip punch is like an uppercut
      try { pw.println( "Robinson goes for a slip punch!" ); } catch( Exception e ) {}
      return UPPERCUT;
    }

    if( card == SPECIAL && p.name == FIGHTER_ALI )
    {
      // taunt is treated as a step
      try { pw.println( "Ali is taunting!" ); } catch( Exception e ) {}
      return STEP;
    }

    if( card == SPECIAL && p.name == FIGHTER_CORB )
    {
      // juke is treated as a step
      try { pw.println( "Corbett is juking!" ); } catch( Exception e ) {}
      return STEP;
    }

    if( card == SPECIAL && p.name == FIGHTER_ROCKY )
    {
      try { pw.println( "Marciano is throwing an overhand right!" ); } catch( Exception e ) {}
      return CROSS;
    }

    if( card == SPECIAL && p.name == FIGHTER_FRAZ )
    {
      try { pw.println( "Frazier is trying to parry!" ); } catch( Exception e ) {}
      return GUARD;
    }

    if( card == SPECIAL && p.name == FIGHTER_DURAN )
    {
      try { pw.println( "Duran is throwing a flurry!" ); } catch( Exception e ) {}
      return JAB;
    }

    if( card == SPECIAL && p.name == FIGHTER_LOUIS )
    {
      try { pw.println( "Louis is throwing a bomb right!" ); } catch( Exception e ) {}
      return UPPERCUT;
    }

    if( card == SPECIAL && p.name == FIGHTER_GEO )
    {
      try { pw.println( "Foreman comes with the all-out!" ); } catch( Exception e ) {}
      return UPPERCUT;
    }

    if( card == SPECIAL && p.name == FIGHTER_LIST )
    {
      try { pw.println( "Liston throws a heavy hook!" ); } catch( Exception e ) {}
      return HOOK;
    }

    if( card == SPECIAL )
    {
      try { pw.println( "SPECIAL NOT HANDLED!" ); } catch( Exception e ) {}
      return NO_CARD;
    }

    // otherwise return original card
    return card;
  }


  public boolean postProcessCard( int card, Player p, Result r )
  {
    if( card == SPECIAL && p.name == FIGHTER_ALI && r.result == RES_END )
    {
      // the taunt was successful, so award Ali one card
      if( p.drawCards( 1 ) != DRAW_OK )
      {
        try { pw.println( "\n--- Ding! ---\n" ); } catch( Exception e ) {}
        return true;
      }
    }

    if( card == SPECIAL && p.name == FIGHTER_CORB && r.result == RES_END )
    {
      // juke is treated as a step
      p.combosJCHUSG( true, true, true, true, false, false, false );

      if( p == player1 )
      {
        player2.combosJCHUSG( false, false, false, false, false, false, false );
      }
      else
      {
        player1.combosJCHUSG( false, false, false, false, false, false, false );
      }

      r.result = RES_TRADE;
    }

    if( card == SPECIAL && p.name == FIGHTER_FRAZ &&
        ( (p == player1 && r.result == RES_P1_GUARDED) ||
          (p == player2 && r.result == RES_P2_GUARDED)
        )
      )
    {
      // parry is treated as a guard
      p.combosJCHUSG( true, true, true, true, false, false, false );

      if( p == player1 )
      {
        player2.combosJCHUSG( false, false, false, false, false, false, false );
      }
      else
      {
        player1.combosJCHUSG( false, false, false, false, false, false, false );
      }

      r.result = RES_TRADE;
    }

    if( card == SPECIAL && p.name == FIGHTER_DURAN &&
        ( (p == player1 && r.result == RES_P1_LANDED) ||
          (p == player2 && r.result == RES_P2_LANDED) ||
          r.result == RES_TRADE ||
          r.result == RES_END
        )
      )
    {
      // flurry counts as an extra punch-landed
      p.punchesLanded++;
    }

    return false;
  }


  // just passes a result by reference
  public class Result
  {
    int result;

    public Result()
    {
      result = RES_INVALID;
    }
  }

  public Result resolveActions( int p1card, int p1mod, int p2card, int p2mod )
  {
    String s = "";
    Result r = new Result();

    switch( p1card )
    {
      case GUARD:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += guardVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += guardVguard( player1, p1mod, player2, p2mod, r ); } break;
          case JAB:      { s += guardVjab  ( player1, p1mod, player2, p2mod, r ); } break;
          case CROSS:    { s += guardVcross( player1, p1mod, player2, p2mod, r ); } break;
          case HOOK:     { s += guardVhook ( player1, p1mod, player2, p2mod, r ); } break;
          case UPPERCUT: { s += guardVupper( player1, p1mod, player2, p2mod, r ); } break;
          case STEP:     { s += stepVguard ( player2, p2mod, player1, p1mod, r ); } break;
          default: { assert false; }
        }
      } break;

      case JAB:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += jabVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += guardVjab( player2, p2mod, player1, p1mod, r ); } break;
          case JAB:      { s += jabVjab  ( player1, p1mod, player2, p2mod, r ); } break;
          case CROSS:    { s += crossVjab( player2, p2mod, player1, p1mod, r ); } break;
          case HOOK:     { s += jabVhook ( player1, p1mod, player2, p2mod, r ); } break;
          case UPPERCUT: { s += jabVupper( player1, p1mod, player2, p2mod, r ); } break;
          case STEP:     { s += stepVjab ( player2, p2mod, player1, p1mod, r ); } break;
          default: { assert false; }
        }
      } break;

      case CROSS:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += crossVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += guardVcross( player2, p2mod, player1, p1mod, r ); } break;
          case JAB:      { s += crossVjab  ( player1, p1mod, player2, p2mod, r ); } break;
          case CROSS:    { s += crossVcross( player1, p1mod, player2, p2mod, r ); } break;
          case HOOK:     { s += crossVhook ( player1, p1mod, player2, p2mod, r ); } break;
          case UPPERCUT: { s += crossVupper( player1, p1mod, player2, p2mod, r ); } break;
          case STEP:     { s += stepVcross ( player2, p2mod, player1, p1mod, r ); } break;
          default: { assert false; }
        }
      } break;

      case HOOK:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += hookVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += guardVhook( player2, p2mod, player1, p1mod, r ); } break;
          case JAB:      { s += jabVhook  ( player2, p2mod, player1, p1mod, r ); } break;
          case CROSS:    { s += crossVhook( player2, p2mod, player1, p1mod, r ); } break;
          case HOOK:     { s += hookVhook ( player1, p1mod, player2, p2mod, r ); } break;
          case UPPERCUT: { s += hookVupper( player1, p1mod, player2, p2mod, r ); } break;
          case STEP:     { s += hookVstep ( player1, p1mod, player2, p2mod, r ); } break;
          default: { assert false; }
        }
      } break;

      case UPPERCUT:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += upperVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += guardVupper( player2, p2mod, player1, p1mod, r ); } break;
          case JAB:      { s += jabVupper  ( player2, p2mod, player1, p1mod, r ); } break;
          case CROSS:    { s += crossVupper( player2, p2mod, player1, p1mod, r ); } break;
          case HOOK:     { s += hookVupper ( player2, p2mod, player1, p1mod, r ); } break;
          case UPPERCUT: { s += upperVupper( player1, p1mod, player2, p2mod, r ); } break;
          case STEP:     { s += stepVupper ( player2, p2mod, player1, p1mod, r ); } break;
          default: { assert false; }
        }
      } break;

      case STEP:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += stepVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += stepVguard( player1, p1mod, player2, p2mod, r ); } break;
          case JAB:      { s += stepVjab  ( player1, p1mod, player2, p2mod, r ); } break;
          case CROSS:    { s += stepVcross( player1, p1mod, player2, p2mod, r ); } break;
          case HOOK:     { s += hookVstep ( player2, p2mod, player1, p1mod, r ); } break;
          case UPPERCUT: { s += stepVupper( player1, p1mod, player2, p2mod, r ); } break;
          case STEP:     { s += stepVstep ( player1, p1mod, player2, p2mod, r ); } break;
          default: { assert false; }
        }
      } break;

      case NO_CARD:
      {
        switch( p2card )
        {
          case NO_CARD:  { s += zipVzip  ( player1, p1mod, player2, p2mod, r ); } break;
          case GUARD:    { s += guardVzip( player2, p2mod, player1, p1mod, r ); } break;
          case JAB:      { s += jabVzip  ( player2, p2mod, player1, p1mod, r ); } break;
          case CROSS:    { s += crossVzip( player2, p2mod, player1, p1mod, r ); } break;
          case HOOK:     { s += hookVzip ( player2, p2mod, player1, p1mod, r ); } break;
          case UPPERCUT: { s += upperVzip( player2, p2mod, player1, p1mod, r ); } break;
          case STEP:     { s += stepVzip ( player2, p2mod, player1, p1mod, r ); } break;
          default: { assert false; }
        }
      } break;

      default: { assert false; }
    }

    // some result should have been set
    assert r.result != RES_INVALID;

    try
    {
      pw.println( s );
      pw.println( player1 );
      pw.println( player2 );

      if( distance == FAR )
      {
        pw.println( "~ FAR ~" );
      } else {
        pw.println( "~ NEAR ~" );
      }

      pw.println( "" );

    } catch( Exception e ) {}

    return r;
  }



  /////////////////////////////////////////////////////////////////////////////
  //
  //  Action match-ups!  The meat!
  //
  /////////////////////////////////////////////////////////////////////////////
  public String jabVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = null;

    switch( distance )
    {
      case NEAR:
      {
        s = "  "+nameToString( pa.name )+" gets a free jab on "+nameToString( pb.name );
        landPunch( pa, pb, POW_JAB + paMod );
      } break;

      case FAR:
      {
        if( pa.style == OUTFIGHTER )
        {
          s = "  "+nameToString( pa.name )+" gets a free, far jab on "+nameToString( pb.name );
          landPunch( pa, pb, POW_JAB + paMod );
        }
        else
        {
          s = "  "+nameToString( pa.name )+"'s free jab is too far to hit "+nameToString( pb.name );
        }
      } break;

      default:
      {
        assert false;
      }
    }

    r.result = RES_END;

    return s;
  }


  public String jabVjab( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = null;

    pa.combosJCHUSG( false, true, false, true, true, true, false );
    pb.combosJCHUSG( false, true, false, true, true, true, false );

    switch( distance )
    {
      case NEAR:
      {
        s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade jabs";
        landPunch( pa, pb, POW_JAB + paMod );
        landPunch( pb, pa, POW_JAB + pbMod );
        r.result = RES_TRADE;
      } break;

      case FAR:
      {
        if( pa.style == OUTFIGHTER && pb.style == OUTFIGHTER )
        {
          s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade jabs";
          landPunch( pa, pb, POW_JAB + paMod );
          landPunch( pb, pa, POW_JAB + pbMod );
          r.result = RES_TRADE;
        }
        else if( pa.style == OUTFIGHTER )
        {
          s = "  "+nameToString( pa.name )+" lands a far jab!";
          landPunch( pa, pb, POW_JAB + paMod );
          pb.combosJCHUSG( false, false, false, false, false, false, false );
          if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }
        }
        else if( pb.style == OUTFIGHTER )
        {
          s = "  "+nameToString( pb.name )+" lands a far jab!";
          landPunch( pb, pa, POW_JAB + pbMod );
          pa.combosJCHUSG( false, false, false, false, false, false, false );
          if( pb == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }
        }
        else
        {
          s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" jab from too far";
          r.result = RES_END;
        }
      } break;

      default:
      {
        assert false;
      }
    }

    return s;
  }


  public String jabVhook( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+"'s jab counters "+nameToString( pb.name )+"'s hook";

    // jab counters a hook, even from FAR
    landPunch( pa, pb, POW_JAB + paMod );

    pa.combosJCHUSG( false, true,  false, true,  true,  true , false );
    pb.combosJCHUSG( false, false, false, false, false, false, false );

    if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }

    return s;
  }


  public String jabVupper( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = null;

    switch( distance )
    {
      case NEAR:
      {
        if( pb.style == SWARMER )
        {
          s = "  "+pa.name+" and "+pb.name+" trade a jab and uppercut";

          landPunch( pa, pb, POW_JAB   + paMod );
          landPunch( pb, pa, POW_UPPER + pbMod );

          pa.combosJCHUSG( false, true,  false, true,  true,  true,  false );
          pb.combosJCHUSG( true,  false, true,  false, true,  true,  false );

          r.result = RES_TRADE;
        }
        else if( pbMod == MOD_SPEC_SLIP )
        {
          s = "  "+pa.name+" and "+pb.name+" trade a jab and slip punch";

          landPunch( pa, pb, POW_JAB   + paMod );
          landPunch( pb, pa, POW_UPPER         );

          pa.combosJCHUSG( false, true,  false, true,  true,  true,  false );
          pb.combosJCHUSG( true,  false, true,  false, true,  true,  false );

          r.result = RES_TRADE;
        }
        else
        {
          s = "  "+pa.name+"'s jab counters "+pb.name+"'s uppercut";

          // jab counters an uppercut, even from FAR
          landPunch( pa, pb, POW_JAB + paMod );

          // jab can combo to a cross, uppercut or a step
          pa.combosJCHUSG( false, true,  false, true,  true,  true,  false );
          pb.combosJCHUSG( false, false, false, false, false, false, false );

          if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }
        }
      } break;

      case FAR:
      {
        if( pa.style == OUTFIGHTER )
        {
          s = "  "+pa.name+" lands a far jab!";

          // jab counters an uppercut, even from FAR
          landPunch( pa, pb, POW_JAB + paMod );

          // jab can combo to a cross, uppercut or a step
          pa.combosJCHUSG( false, true,  false, true,  true,  true,  false );
          pb.combosJCHUSG( false, false, false, false, false, false, false );

          if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }
        }
        else
        {
          s = "  "+pa.name+" and "+pb.name+" jab and upper from too far";

          pa.combosJCHUSG( false, false, false, false, false, false, false );
          pb.combosJCHUSG( false, false, false, false, false, false, false );

          r.result = RES_END;
        }
      } break;

      default:
      {
        assert false;
      }
    }

    return s;
  }


  public String crossVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" gets a free cross on "+nameToString( pb.name );

    landPunch( pa, pb, POW_CROSS + paMod );

    // cross brings fighter inward
    distance = NEAR;

    r.result = RES_END;

    return s;
  }


  public String crossVjab( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+"'s cross counters "+nameToString( pb.name )+"'s jab";

    landPunch( pa, pb, POW_CROSS + paMod );

    // cross brings fighter inward
    distance = NEAR;

    pa.combosJCHUSG( true,  false, true,  false, true,  true , false );
    pb.combosJCHUSG( false, false, false, false, false, false, false );

    if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }

    return s;
  }


  public String crossVcross( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade crosses";

    landPunch( pa, pb, POW_CROSS + paMod );
    landPunch( pb, pa, POW_CROSS + pbMod );

    // cross brings fighter inward
    distance = NEAR;

    pa.combosJCHUSG( true, false, true, false, true, true, false );
    pb.combosJCHUSG( true, false, true, false, true, true, false );

    r.result = RES_TRADE;

    return s;
  }


  public String crossVhook( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade a cross and hook";

    landPunch( pa, pb, POW_CROSS + paMod );
    landPunch( pb, pa, POW_HOOK  + pbMod );

    // cross brings fighter inward
    distance = NEAR;

    pa.combosJCHUSG( true, false, true, false, true, true, false );
    pb.combosJCHUSG( false, true, false, true, true, true, false );

    r.result = RES_TRADE;

    return s;
  }


  public String crossVupper( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade a cross and uppercut";

    landPunch( pa, pb, POW_CROSS + paMod );
    landPunch( pb, pa, POW_UPPER + pbMod );

    // cross brings fighter inward
    distance = NEAR;

    pa.combosJCHUSG( true, false, true, false, true, true, false );
    pb.combosJCHUSG( true, false, true, false, true, true, false );

    r.result = RES_TRADE;

    return s;
  }


  public String hookVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" gets a free hook on "+nameToString( pb.name );

    landPunch( pa, pb, POW_HOOK + paMod );

    r.result = RES_END;

    return s;
  }


  public String hookVhook( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade hooks";

    landPunch( pa, pb, POW_HOOK + paMod );
    landPunch( pb, pa, POW_HOOK + pbMod );

    pa.combosJCHUSG( false, true, false, true, true, true, false );
    pb.combosJCHUSG( false, true, false, true, true, true, false );

    r.result = RES_TRADE;

    return s;
  }


  public String hookVupper( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = null;

    if( distance == FAR )
    {
      s = "  "+nameToString( pa.name )+"'s hook lands but "+nameToString( pb.name )+"'s uppercut is too far";

      landPunch( pa, pb, POW_HOOK + paMod );

      pa.combosJCHUSG( false, true,  false, true,  true,  true , false );
      pb.combosJCHUSG( false, false, false, false, false, false, false );

      if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }
    }
    else
    {
      s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade a hook and uppercut";

      landPunch( pa, pb, POW_HOOK  + paMod );
      landPunch( pb, pa, POW_UPPER + pbMod );

      pa.combosJCHUSG( false, true, false, true, true, true, false );
      pb.combosJCHUSG( true, false, true, false, true, true, false );

      r.result = RES_TRADE;
    }

    return s;
  }


  public String hookVstep( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+"'s hook counters "+nameToString( pb.name )+"'s step";

    // hook counters a step
    landPunch( pa, pb, POW_HOOK + paMod );

    pa.combosJCHUSG( false, true,  false, true,  true,  true , false );
    pb.combosJCHUSG( false, false, false, false, false, false, false );

    if( pa == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }

    return s;
  }


  public String upperVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = null;

    if( distance == NEAR )
    {
      s = "  "+nameToString( pa.name )+" gets a free uppercut on "+nameToString( pb.name );
      landPunch( pa, pb, POW_UPPER + paMod );
    }
    else if( distance == FAR )
    {
      s = "  "+nameToString( pa.name )+"'s free uppercut on "+nameToString( pb.name )+" is too far";
    }
    else
    {
      assert false;
    }

    r.result = RES_END;

    return s;
  }


  public String upperVupper( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" trade uppercuts";

    landPunch( pa, pb, POW_UPPER + paMod );
    landPunch( pb, pa, POW_UPPER + pbMod );

    pa.combosJCHUSG( true, false, true, false, true, true, false );
    pb.combosJCHUSG( true, false, true, false, true, true, false );

    r.result = RES_TRADE;

    return s;
  }


  public String stepVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" took a free step around "+nameToString( pb.name );
    step( pa, pb );
    r.result = RES_END;
    return s;
  }


  public String stepVstep( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" both step";
    r.result = RES_END;
    return s;
  }


  public String stepVjab( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" stepped around "+nameToString( pb.name )+"'s jab";
    step( pa, pb );
    r.result = RES_END;
    return s;
  }


  public String stepVcross( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" stepped around "+nameToString( pb.name )+"'s cross, no movement";

    // this is a controversial case, no movement!

    r.result = RES_END;
    return s;
  }


  public String stepVupper( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" stepped around "+nameToString( pb.name )+"'s uppercut";
    step( pa, pb );
    r.result = RES_END;
    return s;
  }


  public String stepVguard( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" stepped while "+nameToString( pb.name )+" guards";
    step( pa, pb );
    r.result = RES_END;
    return s;
  }


  public String guardVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" guarded by himself";
    r.result = RES_END;
    return s;
  }


  public String guardVguard( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" guard";
    r.result = RES_END;
    return s;
  }


  public String guardVjab( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" guards "+nameToString( pb.name )+"'s jab";
    if( pa == player1 ) { r.result = RES_P1_GUARDED; } else { r.result = RES_P2_GUARDED; }
    return s;
  }


  public String guardVcross( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" guards "+nameToString( pb.name )+"'s cross";

    // cross brings fighter inward
    distance = NEAR;

    if( pa == player1 ) { r.result = RES_P1_GUARDED; } else { r.result = RES_P2_GUARDED; }

    return s;
  }


  public String guardVhook( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" guards "+nameToString( pb.name )+"'s hook";
    if( pa == player1 ) { r.result = RES_P1_GUARDED; } else { r.result = RES_P2_GUARDED; }
    return s;
  }


  public String guardVupper( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = null;

    if( distance == FAR )
    {
      r.result = RES_END;
    }
    else if( pbMod == MOD_SPEC_BOMB )
    {
      if( paMod == MOD_SPEC_PARRY )
      {
        s = "  "+nameToString( pb.name )+"'s bomb and "+nameToString( pb.name )+"'s parry are neutralized";
        r.result = RES_END;
      }
      else
      {
        s = "  "+nameToString( pb.name )+"'s bomb penetrates the guard of "+nameToString( pb.name );

        landPunch( pb, pa, POW_UPPER );

        pa.combosJCHUSG( false, false, false, false, false, false, false );
        pb.combosJCHUSG( true,  false, true,  false, true,  true,  false );

        if( pb == player1 ) { r.result = RES_P1_LANDED; } else { r.result = RES_P2_LANDED; }
      }
    }
    else
    {
      s = "  "+nameToString( pa.name )+" guards "+nameToString( pb.name )+"'s uppercut";
      if( pa == player1 ) { r.result = RES_P1_GUARDED; } else { r.result = RES_P2_GUARDED; }
    }

    return s;
  }


  public String zipVzip( Player pa, int paMod, Player pb, int pbMod, Result r )
  {
    String s = "  "+nameToString( pa.name )+" and "+nameToString( pb.name )+" had no follow up cards";
    r.result = RES_END;
    return s;
  }


  public void step( Player pa, Player pb )
  {
    switch( distance )
    {
      case FAR:  { distance = NEAR; } break;
      case NEAR: { distance = FAR;  } break;
      default:   { assert false;    }
    }
  }


  public void landPunch( Player pa, Player pb, int power )
  {
    pa.punchesLanded++;
    pb.takePunch( power );
  }
  /////////////////////////////////////////////////////////////////////////////
  //
  //  End action match-ups
  //
  /////////////////////////////////////////////////////////////////////////////



  public void scoreRound()
  {
    // did both players get knocked out?
    if( player1.endurance <= 0 &&
        player2.endurance <= 0 )
    {
      player1.tiesTotal++;
      player1.tiesByKO++;
      switch( player2.style ) { case OUTFIGHTER:{player1.tiesVsOut++;}break; case SLUGGER:{player1.tiesVsSlu++;}break; case SWARMER:{player1.tiesVsSwa++;}break; default:{assert false;} }

      player2.tiesTotal++;
      player2.tiesByKO++;
      switch( player1.style ) { case OUTFIGHTER:{player2.tiesVsOut++;}break; case SLUGGER:{player2.tiesVsSlu++;}break; case SWARMER:{player2.tiesVsSwa++;}break; default:{assert false;} }

      return;
    }

    // did one player get knocked out?
    if( player1.endurance <= 0 )
    {
      player1.lossTotal++;
      player1.lossByKO++;
      switch( player2.style ) { case OUTFIGHTER:{player1.lossVsOut++;}break; case SLUGGER:{player1.lossVsSlu++;}break; case SWARMER:{player1.lossVsSwa++;}break; default:{assert false;} }

      player2.winsTotal++;
      player2.winsByKO++;
      switch( player1.style ) { case OUTFIGHTER:{player2.winsVsOut++;}break; case SLUGGER:{player2.winsVsSlu++;}break; case SWARMER:{player2.winsVsSwa++;}break; default:{assert false;} }

      return;
    }

    if( player2.endurance <= 0 )
    {
      player2.lossTotal++;
      player2.lossByKO++;
      switch( player1.style ) { case OUTFIGHTER:{player2.lossVsOut++;}break; case SLUGGER:{player2.lossVsSlu++;}break; case SWARMER:{player2.lossVsSwa++;}break; default:{assert false;} }

      player1.winsTotal++;
      player1.winsByKO++;
      switch( player2.style ) { case OUTFIGHTER:{player1.winsVsOut++;}break; case SLUGGER:{player1.winsVsSlu++;}break; case SWARMER:{player1.winsVsSwa++;}break; default:{assert false;} }

      return;
    }

    // so punches landed is going to determine this one
    if( player1.punchesLanded == player2.punchesLanded )
    {
      player1.tiesTotal++;
      player1.tiesByPL++;
      switch( player2.style ) { case OUTFIGHTER:{player1.tiesVsOut++;}break; case SLUGGER:{player1.tiesVsSlu++;}break; case SWARMER:{player1.tiesVsSwa++;}break; default:{assert false;} }

      player2.tiesTotal++;
      player2.tiesByPL++;
      switch( player1.style ) { case OUTFIGHTER:{player2.tiesVsOut++;}break; case SLUGGER:{player2.tiesVsSlu++;}break; case SWARMER:{player2.tiesVsSwa++;}break; default:{assert false;} }

      return;
    }

    if( player1.punchesLanded < player2.punchesLanded )
    {
      player1.lossTotal++;
      player1.lossByPL++;
      switch( player2.style ) { case OUTFIGHTER:{player1.lossVsOut++;}break; case SLUGGER:{player1.lossVsSlu++;}break; case SWARMER:{player1.lossVsSwa++;}break; default:{assert false;} }

      player2.winsTotal++;
      player2.winsByPL++;
      switch( player1.style ) { case OUTFIGHTER:{player2.winsVsOut++;}break; case SLUGGER:{player2.winsVsSlu++;}break; case SWARMER:{player2.winsVsSwa++;}break; default:{assert false;} }

      return;
    }

    if( player2.punchesLanded < player1.punchesLanded )
    {
      player2.lossTotal++;
      player2.lossByPL++;
      switch( player1.style ) { case OUTFIGHTER:{player2.lossVsOut++;}break; case SLUGGER:{player2.lossVsSlu++;}break; case SWARMER:{player2.lossVsSwa++;}break; default:{assert false;} }

      player1.winsTotal++;
      player1.winsByPL++;
      switch( player2.style ) { case OUTFIGHTER:{player1.winsVsOut++;}break; case SLUGGER:{player1.winsVsSlu++;}break; case SWARMER:{player1.winsVsSwa++;}break; default:{assert false;} }

      return;
    }

    System.out.println( player1 );
    System.out.println( player2 );

    // one of the above cases should cover every result
    assert false;
    return;
  }


  public void verifyCardTotal()
  {
    // verify card total
    int cardTotal = 0;
    for( int i = 0; i < NUMCARDTYPES; ++i )
    {
      cardTotal += deckCards[i];
      cardTotal += discardCards[i];
      cardTotal += player1.handCards[i];
      cardTotal += player2.handCards[i];
    }
    assert cardTotal == deckTotal;
  }





  static public String statHeaders()
  {
    String s = new String();

    s += "Name,";
    s += "Wins,Losses,Ties,";
    s += "Wins by KO,Losses by KO, Ties by KO,";
    s += "Wins by PL,Losses by PL, Ties by PL,";
    s += "Wins vs Out-Fighters, Losses vs Out-Fighters, Ties vs Out-Fighters,";
    s += "Wins vs Sluggers, Losses vs Sluggers, Ties vs Sluggers,";
    s += "Wins vs Swarmers, Losses vs Swarmers, Ties vs Swarmers,";
    s += "Average Hand Size";

    return s;
  }

  ///////////////////////////////////////////////////////////////
  // Player
  ///////////////////////////////////////////////////////////////
  public class Player
  {
    public int name;
    public int style;
    public int maxHand;
    public int maxEndurance;
    public int endurance;
    public int punchesLanded;
    public int[] handCards;


    // combos
    boolean cj;
    boolean cc;
    boolean ch;
    boolean cu;
    boolean cs;
    boolean cg;
    boolean cS;


    public int winsTotal = 0;
    public int winsVsOut = 0;
    public int winsVsSlu = 0;
    public int winsVsSwa = 0;

    public int lossTotal = 0;
    public int lossVsOut = 0;
    public int lossVsSlu = 0;
    public int lossVsSwa = 0;

    public int tiesTotal = 0;
    public int tiesVsOut = 0;
    public int tiesVsSlu = 0;
    public int tiesVsSwa = 0;

    public int winsByKO  = 0;
    public int winsByPL  = 0;

    public int lossByKO  = 0;
    public int lossByPL  = 0;

    public int tiesByKO  = 0;
    public int tiesByPL  = 0;

    public int[] handSizeHisto;


    public String stats()
    {
      String s = new String( nameToString( name )+"," );

      s += String.format( "%d(%5.1f%%),%d(%5.1f%%),%d(%5.1f%%),",
                          winsTotal, (100f*(float)winsTotal) / (float)(winsTotal+lossTotal+tiesTotal),
                          lossTotal, (100f*(float)lossTotal) / (float)(winsTotal+lossTotal+tiesTotal),
                          tiesTotal, (100f*(float)tiesTotal) / (float)(winsTotal+lossTotal+tiesTotal)  );

      s += String.format( "%d/%d(%5.1f%%),%d/%d(%5.1f%%),%d/%d(%5.1f%%),",
                          winsByKO, winsTotal,
                          (100f*(float)winsByKO) / (float)(winsTotal),
                          lossByKO, lossTotal,
                          (100f*(float)lossByKO) / (float)(lossTotal),
                          tiesByKO, tiesTotal,
                          (100f*(float)tiesByKO) / (float)(tiesTotal)  );

      s += String.format( "%d/%d(%5.1f%%),%d/%d(%5.1f%%),%d/%d(%5.1f%%),",
                          winsByPL, winsTotal,
                          (100f*(float)winsByPL) / (float)(winsTotal),
                          lossByPL, lossTotal,
                          (100f*(float)lossByPL) / (float)(lossTotal),
                          tiesByPL, tiesTotal,
                          (100f*(float)tiesByPL) / (float)(tiesTotal)  );

      s += String.format( "%d(%5.1f%%),%d(%5.1f%%),%d(%5.1f%%),",
                          winsVsOut, (100f*(float)winsVsOut) / (float)(winsVsOut+lossVsOut+tiesVsOut),
                          lossVsOut, (100f*(float)lossVsOut) / (float)(winsVsOut+lossVsOut+tiesVsOut),
                          tiesVsOut, (100f*(float)tiesVsOut) / (float)(winsVsOut+lossVsOut+tiesVsOut)  );

      s += String.format( "%d(%5.1f%%),%d(%5.1f%%),%d(%5.1f%%),",
                          winsVsSlu, (100f*(float)winsVsSlu) / (float)(winsVsSlu+lossVsSlu+tiesVsSlu),
                          lossVsSlu, (100f*(float)lossVsSlu) / (float)(winsVsSlu+lossVsSlu+tiesVsSlu),
                          tiesVsSlu, (100f*(float)tiesVsSlu) / (float)(winsVsSlu+lossVsSlu+tiesVsSlu)  );

      s += String.format( "%d(%5.1f%%),%d(%5.1f%%),%d(%5.1f%%),",
                          winsVsSwa, (100f*(float)winsVsSwa) / (float)(winsVsSwa+lossVsSwa+tiesVsSwa),
                          lossVsSwa, (100f*(float)lossVsSwa) / (float)(winsVsSwa+lossVsSwa+tiesVsSwa),
                          tiesVsSwa, (100f*(float)tiesVsSwa) / (float)(winsVsSwa+lossVsSwa+tiesVsSwa)  );

      int total = 0;
      int numSamples = 0;
      for( int i = 0; i < MAXHANDSIZE; ++i )
      {
        total      += handSizeHisto[i]*i;
        numSamples += handSizeHisto[i];
      }

      s += String.format( "%.1f", ((float)total)/((float)numSamples) );

      return s;
    }



    public String record()
    {
      String s = new String( name + "\n" );

      int total = 0;
      int numSamples = 0;
      for( int i = 0; i < MAXHANDSIZE; ++i )
      {
        total      += handSizeHisto[i]*i;
        numSamples += handSizeHisto[i];
      }

      s += String.format( "Average hand size: %.1f\n",
                          ((float)total)/((float)numSamples) );

      s += String.format( "W/L/T  (%d/%d/%d)  (%5.1f%%/%5.1f%%/%5.1f%%)\n",
                          winsTotal, lossTotal, tiesTotal,
                          (100f*(float)winsTotal) / (float)(winsTotal+lossTotal+tiesTotal),
                          (100f*(float)lossTotal) / (float)(winsTotal+lossTotal+tiesTotal),
                          (100f*(float)tiesTotal) / (float)(winsTotal+lossTotal+tiesTotal)  );

      s += String.format( "W/L/T  (%d/%d/%d)  (%5.1f%%/%5.1f%%/%5.1f%%)\n",
                          winsTotal, lossTotal, tiesTotal,
                          (100f*(float)winsTotal) / (float)(winsTotal+lossTotal+tiesTotal),
                          (100f*(float)lossTotal) / (float)(winsTotal+lossTotal+tiesTotal),
                          (100f*(float)tiesTotal) / (float)(winsTotal+lossTotal+tiesTotal)  );

      s += String.format( "Wins by KO (%d/%d)  (%5.1f%%)  Losses by KO (%d/%d)  (%5.1f%%)  Ties by KO (%d/%d)  (%5.1f%%)\n",
                          winsByKO, winsTotal,
                          (100f*(float)winsByKO) / (float)(winsTotal),
                          lossByKO, lossTotal,
                          (100f*(float)lossByKO) / (float)(lossTotal),
                          tiesByKO, tiesTotal,
                          (100f*(float)tiesByKO) / (float)(tiesTotal)  );

      s += String.format( "Wins by PL (%d/%d)  (%5.1f%%)  Losses by PL (%d/%d)  (%5.1f%%)  Ties by PL (%d/%d)  (%5.1f%%)\n",
                          winsByPL, winsTotal,
                          (100f*(float)winsByPL) / (float)(winsTotal),
                          lossByPL, lossTotal,
                          (100f*(float)lossByPL) / (float)(lossTotal),
                          tiesByPL, tiesTotal,
                          (100f*(float)tiesByPL) / (float)(tiesTotal)  );

      s += String.format( "W/L/T by PL (%d/%d/%d)  (%5.1f%%/%5.1f%%/%5.1f%%)\n",
                          winsByPL, lossByPL, tiesByPL,
                          (100f*(float)winsByPL) / (float)(winsByPL+lossByPL+tiesByPL),
                          (100f*(float)lossByPL) / (float)(winsByPL+lossByPL+tiesByPL),
                          (100f*(float)tiesByPL) / (float)(winsByPL+lossByPL+tiesByPL)  );

      s += String.format( "W/L/T versus Outfighters (%d/%d/%d)  (%5.1f%%/%5.1f%%/%5.1f%%)\n",
                          winsVsOut, lossVsOut, tiesVsOut,
                          (100f*(float)winsVsOut) / (float)(winsVsOut+lossVsOut+tiesVsOut),
                          (100f*(float)lossVsOut) / (float)(winsVsOut+lossVsOut+tiesVsOut),
                          (100f*(float)tiesVsOut) / (float)(winsVsOut+lossVsOut+tiesVsOut)  );

      s += String.format( "W/L/T versus Sluggers    (%d/%d/%d)  (%5.1f%%/%5.1f%%/%5.1f%%)\n",
                          winsVsSlu, lossVsSlu, tiesVsSlu,
                          (100f*(float)winsVsSlu) / (float)(winsVsSlu+lossVsSlu+tiesVsSlu),
                          (100f*(float)lossVsSlu) / (float)(winsVsSlu+lossVsSlu+tiesVsSlu),
                          (100f*(float)tiesVsSlu) / (float)(winsVsSlu+lossVsSlu+tiesVsSlu)  );

      s += String.format( "W/L/T versus Swarmers    (%d/%d/%d)  (%5.1f%%/%5.1f%%/%5.1f%%)\n",
                          winsVsSwa, lossVsSwa, tiesVsSwa,
                          (100f*(float)winsVsSwa) / (float)(winsVsSwa+lossVsSwa+tiesVsSwa),
                          (100f*(float)lossVsSwa) / (float)(winsVsSwa+lossVsSwa+tiesVsSwa),
                          (100f*(float)tiesVsSwa) / (float)(winsVsSwa+lossVsSwa+tiesVsSwa)  );

      s += "\n";
      return s;
    }


    public Player( int name, int style, int maxHand, int maxEndurance )
    {
      handCards         = new int[NUMCARDTYPES];
      this.name         = name;
      this.style        = style;
      this.maxHand      = maxHand;
      this.maxEndurance = maxEndurance;

      handSizeHisto = new int[MAXHANDSIZE];
      for( int i = 0; i < MAXHANDSIZE; ++i )
      {
        handSizeHisto[i] = 0;
      }
    }


    public void reset()
    {
      endurance = maxEndurance;
      punchesLanded = 0;
      for( int i = 0; i < NUMCARDTYPES; ++i ) { handCards[i] = 0; }
      if( drawCards( maxHand ) != DRAW_OK )
      {
        System.out.println( "Problem drawing cards at reset?" );
        System.exit( -1 );
      }
    }


    public void markHandSize()
    {
      int handSize = 0;
      for( int i = 0; i < NUMCARDTYPES; ++i )
      {
        handSize += handCards[i];
      }
      handSizeHisto[handSize]++;
    }


    public int countHandSize()
    {
      int handSize = 0;
      for( int i = 0; i < NUMCARDTYPES; ++i )
      {
        handSize += handCards[i];
      }
      return handSize;
    }


    public int drawCards( int numCards )
    {
      int numDealt = 0;
      while( numDealt < numCards )
      {
        boolean cardsLeft = false;
        LinkedList<Integer> typesLeft = new LinkedList<Integer>();

        for( int i = 0; i < NUMCARDTYPES; ++i )
        {
          if( deckCards[i] > 0 )
          {
            cardsLeft = true;
            typesLeft.push( new Integer( i ) );
          }
        }

        if( !cardsLeft )
        {
          if( numDealt > 0 )
          {
            return DRAW_SOME;
          } else {
            return DRAW_NONE;
          }
        }

        int cardTypeIndex = random.nextInt( typesLeft.size() );
        int cardType = NUMCARDTYPES;
        for( int i = 0; i <= cardTypeIndex; ++i )
        {
          cardType = typesLeft.pop();
        }

        assert deckCards[cardType] > 0;

        deckCards[cardType]--;
        handCards[cardType]++;
        numDealt++;

        try
        {
          pw.println( nameToStringCol( name )+" drew "+cardToString( cardType ) );
        } catch( Exception e ) {}
      }

      return DRAW_OK;
    }


    public boolean hasComboCard()
    {
      // if you have no legal combo options, then YES you always have the card!
      if( !cj && !cc && !ch && !cu && !cs && !cg ) { return true; }

      if( cj && handCards[JAB]      > 0 ) { return true; }
      if( cc && handCards[CROSS]    > 0 ) { return true; }
      if( ch && handCards[HOOK]     > 0 ) { return true; }
      if( cu && handCards[UPPERCUT] > 0 ) { return true; }
      if( cs && handCards[STEP]     > 0 ) { return true; }
      if( cg && handCards[GUARD]    > 0 ) { return true; }

      if( cu && handCards[SPECIAL]  > 0 && name == FIGHTER_SUGAR ) { return true; }
      if( cs && handCards[SPECIAL]  > 0 && name == FIGHTER_ALI   ) { return true; }
      if( cs && handCards[SPECIAL]  > 0 && name == FIGHTER_CORB  ) { return true; }
      if( cc && handCards[SPECIAL]  > 0 && name == FIGHTER_ROCKY ) { return true; }
      if( cg && handCards[SPECIAL]  > 0 && name == FIGHTER_FRAZ  ) { return true; }
      if( cj && handCards[SPECIAL]  > 0 && name == FIGHTER_DURAN ) { return true; }
      if( cu && handCards[SPECIAL]  > 0 && name == FIGHTER_LOUIS ) { return true; }
      if( cu && handCards[SPECIAL]  > 0 && name == FIGHTER_GEO   ) { return true; }
      if( ch && handCards[SPECIAL]  > 0 && name == FIGHTER_LIST  ) { return true; }

      return false;
    }


    public int chooseComboCard()
    {
      LinkedList<Integer> typesLeft = new LinkedList<Integer>();

      if( cj && handCards[JAB]      > 0 ) { typesLeft.push( new Integer( JAB      ) ); }
      if( cc && handCards[CROSS]    > 0 ) { typesLeft.push( new Integer( CROSS    ) ); }
      if( ch && handCards[HOOK]     > 0 ) { typesLeft.push( new Integer( HOOK     ) ); }
      if( cu && handCards[UPPERCUT] > 0 ) { typesLeft.push( new Integer( UPPERCUT ) ); }
      if( cs && handCards[STEP]     > 0 ) { typesLeft.push( new Integer( STEP     ) ); }
      if( cg && handCards[GUARD]    > 0 ) { typesLeft.push( new Integer( GUARD    ) ); }
      if( cS && handCards[SPECIAL]  > 0 ) { typesLeft.push( new Integer( SPECIAL  ) ); }

      if( cu && handCards[SPECIAL]  > 0 && name == FIGHTER_SUGAR ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cs && handCards[SPECIAL]  > 0 && name == FIGHTER_ALI   ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cs && handCards[SPECIAL]  > 0 && name == FIGHTER_CORB  ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cc && handCards[SPECIAL]  > 0 && name == FIGHTER_ROCKY ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cg && handCards[SPECIAL]  > 0 && name == FIGHTER_FRAZ  ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cj && handCards[SPECIAL]  > 0 && name == FIGHTER_DURAN ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cu && handCards[SPECIAL]  > 0 && name == FIGHTER_LOUIS ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( cu && handCards[SPECIAL]  > 0 && name == FIGHTER_GEO   ) { typesLeft.push( new Integer( SPECIAL ) ); }
      if( ch && handCards[SPECIAL]  > 0 && name == FIGHTER_LIST  ) { typesLeft.push( new Integer( SPECIAL ) ); }

      if( typesLeft.size() == 0 )
      {
        return NO_CARD;
      }

      int cardTypeIndex = random.nextInt( typesLeft.size() );
      int cardType = NUMCARDTYPES;
      for( int i = 0; i <= cardTypeIndex; ++i )
      {
        cardType = typesLeft.pop();
      }

      assert handCards[cardType] > 0;
      handCards[cardType]--;
      discardCards[cardType]++;

      return cardType;
    }


    public int chooseAnyCard()
    {
      LinkedList<Integer> typesLeft = new LinkedList<Integer>();

      for( int i = 0; i < NUMCARDTYPES; ++i )
      {
        if( handCards[i] > 0 )
        {
          typesLeft.push( new Integer( i ) );
        }
      }

      if( typesLeft.size() == 0 )
      {
        return NO_CARD;
      }

      int cardTypeIndex = random.nextInt( typesLeft.size() );
      int cardType = NUMCARDTYPES;
      for( int i = 0; i <= cardTypeIndex; ++i )
      {
        cardType = typesLeft.pop();
      }

      assert handCards[cardType] > 0;
      handCards[cardType]--;
      discardCards[cardType]++;

      return cardType;
    }


    public void combosReset()
    {
      cj = false;
      cc = false;
      ch = false;
      cu = false;
      cs = false;
      cg = false;
      cS = false;
    }

    public void combosJCHUSG( boolean cj, boolean cc, boolean ch, boolean cu, boolean cs, boolean cg, boolean cS )
    {
      this.cj = cj;
      this.cc = cc;
      this.ch = ch;
      this.cu = cu;
      this.cs = cs;
      this.cg = cg;
      this.cS = cS;
    }


    public void takePunch( int power )
    {
      for( int i = 0; i < power; ++i )
      {
        takeUnitPower();
      }
    }


    public void takeUnitPower()
    {
      boolean cardsLeft = false;
      for( int i = 0; i < NUMCARDTYPES; ++i )
      {
        if( handCards[i] > 0 )
        {
          cardsLeft = true;
          break;
        }
      }

      // if you have no cards, gotta take it
      if( !cardsLeft )
      {
        endurance--;
        try
        {
          pw.println( "* "+nameToString( name )+" took a hit" );
        } catch( Exception e ) {}

        return;
      }

      // if your endurance is low, discard
      if( endurance == 1 )
      {
        int discard = chooseAnyCard();
        try
        {
          pw.println( "* "+nameToString( name )+" discarded "+cardToString( discard ) );
        } catch( Exception e ) {}

        return;
      }

      // otherwise always take it!
      endurance--;
      try
      {
        pw.println( "* "+nameToString( name )+" took a hit" );
      } catch( Exception e ) {}

    }


    public String toString()
    {
      return nameToStringCol( name )+
           "("+countHandSize()+") "+
           "EN="+endurance+"  PL="+punchesLanded+
           " ["+cardsToString( handCards )+"]";
    }
  }
  ///////////////////////////////////////////////////////////////
  // End Player
  ///////////////////////////////////////////////////////////////


  public String cardsToString( int cards[] )
  {
    String s =
        "J="+cards[JAB]+
      "  C="+cards[CROSS]+
      "  H="+cards[HOOK]+
      "  U="+cards[UPPERCUT]+
      "  S="+cards[STEP]+
      "  G="+cards[GUARD]+
      "  *="+cards[SPECIAL];
    return s;
  }


  public String cardToString( int card )
  {
    switch( card )
    {
      case NO_CARD:   return ".";
      case GUARD:     return "G";
      case JAB:       return "J";
      case CROSS:     return "C";
      case HOOK:      return "H";
      case UPPERCUT:  return "U";
      case STEP:      return "S";
      case SPECIAL:   return "*";
    }

    assert false;
    return "";
  }


  public String nameToString( int name )
  {
    switch( name )
    {
      case FIGHTER_SUGAR: return "Sugar Ray";
      case FIGHTER_ALI:   return "Ali";
      case FIGHTER_CORB:  return "Corbett";
      case FIGHTER_ROCKY: return "Marciano";
      case FIGHTER_FRAZ:  return "Frazier";
      case FIGHTER_DURAN: return "Duran";
      case FIGHTER_LOUIS: return "Louis";
      case FIGHTER_GEO:   return "Foreman";
      case FIGHTER_LIST:  return "Liston";
    }

    assert false;
    return "";
  }

  public String nameToStringCol( int name )
  {
    switch( name )
    {
      case FIGHTER_SUGAR: return "Sugar Ray";
      case FIGHTER_ALI:   return "Ali      ";
      case FIGHTER_CORB:  return "Corbett  ";
      case FIGHTER_ROCKY: return "Marciano ";
      case FIGHTER_FRAZ:  return "Frazier  ";
      case FIGHTER_DURAN: return "Duran    ";
      case FIGHTER_LOUIS: return "Louis    ";
      case FIGHTER_GEO:   return "Foreman  ";
      case FIGHTER_LIST:  return "Liston   ";
    }

    assert false;
    return "";
  }

}
