/** Implements the game of Pig. Written for CIS 700, Summer II 2010. */
object Pig {
  
  /** Controls play of the game. Has players alternate moves, decides when
    * the game is over, and declares the winner.
    * @param args Not used.
    * @author Dave Matuszek.
    * @version May 13, 2010.
    */
  def main(args: Array[String]) {
    val human = new Human
    val computer = new Computer
    printTheRules
    println("\n---------- New Game ----------\n")
    while (human.score < 50 && computer.score < 50) {
      Thread.sleep(500)
      computer.score += computer.play(0)
      println("Computer's score is now " + computer.score + ".\n")
      if (computer.score > human.score) {
        println("You need " + (computer.score - human.score) +
                " to catch up.\n")
      }
      human.score += human.play(0)
      println("Your score is now " + human.score + ".\n")
    }
    Thread.sleep(2000)
    if (human.score > computer.score) println("*** You win!! ***")
    else if (human.score < computer.score) println("Too bad...you lose.")
    else println("Tie game.")
    println("\n")
  }
  
  /** Prints the rules. */
  def printTheRules {
    println("""Welcome to the game of Pig!
    |
    | The game is you vs. the computer; the computer plays first.
    | At each turn, you roll a six-sided die as many times as you
    | like. Those rolls get added to your score, unless you roll a
    | 1, in which case your turn ends and you get nothing for that
    | turn.
    |
    | The game ends when one or both players reach 50 points and
    | both players have had the same number of turns. Since the
    | computer plays first, if it reaches 50, you get one more turn.
    | High score wins.
    |
    | Good luck!""".stripMargin)
  }
} // end of the Pig object

// -----------------------------------------------------------------------------

/** Describes a player of the game of Pig. Used mainly to roll the die
  * and to keep track of scores. */
abstract class Player {
  import java.util.Random
  
  /** Random number generator. */
  val random = new Random
  /** Name of the player ("You" or "Computer"). */
  val who: String
  /** The player's current score. */
  var score = 0
  
  /** Returns a random integer in the range 1 to 6, inclusive.
    * @param who Which player is rolling the die (for printing purposes).
    * @return The result of a die roll. */
  def rollDie(who: String) = {
    Thread.sleep(1000)
    val roll = random.nextInt(6) + 1
    print(who + " rolled a " + roll + "; ")
    if (roll == 1) println("that ends the turn.")
    roll
  }
    
  /** Plays out one turn, by rolling the die some number of times. The final
    * score is the sum of all the rolls, except that a roll of 1 terminates
    * the turn and gives a final score of zero.
    * @param previousScore The accumulated points so far this turn.
    * @return The final score at the end of this turn. */
  def play(previousScore: Int): Int = {
    if (playAgainTest(previousScore)) {
      val roll = rollDie(who)
      if (roll == 1) 0
      else {
        println("so far this turn: " + (previousScore + roll) + ".")
        play(previousScore + roll)
      }
    }
    else previousScore
  }
  
  /** Abstract test used to determine whether to roll again.
    * @param n The points accumulated so far this turn.
    * @return Whether to roll again. */
  def playAgainTest(n: Int): Boolean
    
} // end of Player class

// -----------------------------------------------------------------------------

/** The human player for the game of Pig. */
class Human extends Player {
  val who = "You"
  
  /** Lets the user decide whether to roll again.
   * @param scoreIncrement  The amount gained so far this turn (unused).
   * @return Whether to roll again. */
  override def playAgainTest(scoreIncrement: Int) = askToRollAgain
  
  /**
   * Asks the user whether to roll again.
   * @return true if the user wants to roll again. */
  def askToRollAgain = answer("Roll?")
  
  /** Prints the question, then accepts input from the user until one of
    * the characters 'Y', 'y' (yes), 'N', 'n' (no), or 'Q' or 'q' (quit).
    * @param question The binary question to ask the user.
    * @return `true` for yes, `false` for no, or
    * exits the program for quit. */
  def answer(question: String): Boolean = {
    print(question + " ")
    val input = (readLine + " ").toLowerCase.charAt(0)
    if (input == 'y' || input == 'n') input == 'y'
    else if (input == 'q') {
      println("Goodbye!")
      exit(0)
    }
    else answer(question)
  }
} // end of Human class

// -----------------------------------------------------------------------------

/** The computer player for the game of Pig. */
class Computer extends Player {
  val who = "Computer"
  
  /** Lets the computer decide whether to roll again.
   * @param scoreIncrement  The amount gained so far this turn.
   * @return Whether to roll again. */
  def playAgainTest(scoreIncrement: Int) = scoreIncrement < 17
  
} // end of Computer class
