21 game

You are encouraged to solve this task according to the task description, using any language you may know.
21 is a two player game, the game is played by choosing
a number (1, 2, or 3) to be added to the running total.

The game is won by the player whose chosen number causes the running total
to reach exactly 21.

The running total starts at zero.
One player will be the computer.

Players alternate supplying a number to be added to the running total.



Task

Write a computer program that will:

 do the prompting (or provide a button menu), 
 check for errors and display appropriate error messages, 
 do the additions (add a chosen number to the running total), 
 display the running total, 
 provide a mechanism for the player to quit/exit/halt/stop/close the program,
 issue a notification when there is a winner, and
 determine who goes first (maybe a random or user choice, or can be specified when the game begins). 


 
object Game21 {
 
  import scala.collection.mutable.ListBuffer
  import scala.util.Random
 
  val N = 21 // the same game would also work for N other than 21...
 
  val RND = new Random() // singular random number generator; add a seed number, if you want reproducibility
 
  /** tuple: name and a play function: (rest: remaining number, last: value of opponent's last move) => new move
    */
  type Player = (String, (Int,Int) => Int)
 
  // indeed, the following could also be written using a class and instances, I've choosen a
  // more functional and math way (using tuples)...
  val playerRandom:Player = ("RandomRobot", { (rest, last) =>
    if (rest <= 3) rest
    else 1 + RND.nextInt(3)
  })
  val playerBest:Player = ("BestRobot", { (rest, last) =>
    val i = rest % 4
    if (i > 0) i else 1 + RND.nextInt(3)
  })
  val playerHuman:Player = ("YOU", { (rest, last) =>
    println("Rest: "+rest)
    println("Last: "+last)
    var in = ""
    while (in!="1" && in!="2" && in!="3") {
      in = scala.io.StdIn.readLine("Your move (1,2,3,q)> ").trim
      if ("q" == in)
        throw new Exception("q => quit")
    }
    in.toInt
  })
 
  /** Execute a whole game. NOTE that we're counting DOWN from N to 0!
    * @param players
    * @return list of all moves
    */
  def play(players:Seq[Player]):Seq[Int] = {
    require(players.size == 2)
    var last = -1
    var rest = N
    var p = 0 // player 0 always starts
    val l = ListBuffer[Int]() // list of all moves
    while (rest > 0) {
      last = players(p)._2(rest,last)
      require(1<=last && last<=3,"Player must always play 1,2,3: "+last)
      l += last
      rest -= last
      p = 1 - p // other player's turn
    }
    l.toSeq
  }
 
  /** Evaluate a whole game.
    * @param game list of moves of one game
    * @param rest mainly for recursion
    * @return evaluation, for each move a tuple: (rest, what was played, whether this player won in the end)
    */
  def evaluate(game:Seq[Int],rest:Int=N):Seq[(Int,Int,Boolean)] = {
    if (game.size == 0) Seq()
    else Seq((rest,game.head,game.size%2 == 1)) ++ evaluate(game.tail,rest - game.head)
  }
 
  def main(args: Array[String]): Unit = {
    // here you can put whatever player combination you like
    val players = Seq(playerRandom,playerRandom) // random robot vs random robot
    //val players = Seq(playerRandom,playerBest) // random robot vs best robot
    //val players = Seq(playerHuman,playerBest) // You vs best robot
    var p0won = 0
    val n = 1000 // number of games to play
    var m = 0 // games actually played (a human player might quit before n)
    try {
      (1 to n).foreach { i =>
        val g = play(players)
        require(g.sum == N) // some validity checks
        val e = evaluate(g)
        require(e.size == g.size && e.last._3 && e(0)._3 != e(1)._3) // some validity checks
        if (e(0)._3) p0won += 1
        m += 1
        println(i + ": " + players(0)._1 + " " + (if (e(0)._3) "won" else "lost") + " against " + players(1)._1 + ". " + g + " => " + e)
      }
    } catch {
      case t:Throwable => println(t.getMessage)
    }
    println("Player0: "+players(0)._1)
    println("Player1: "+players(1)._1)
    println(f"Player0 won ${p0won} times out of ${m}, or ${p0won * 100.0 / m}%%")
  }
}
 