Task

Implement the classic children's game Rock-paper-scissors, as well as a simple predictive   AI   (artificial intelligence)   player.

Rock Paper Scissors is a two player game.

Each player chooses one of rock, paper or scissors, without knowing the other player's choice.

The winner is decided by a set of rules:

   Rock beats scissors
   Scissors beat paper
   Paper beats rock


If both players choose the same thing, there is no winner for that round.

For this task, the computer will be one of the players.

The operator will select Rock, Paper or Scissors and the computer will keep a record of the choice frequency, and use that information to make a weighted random choice in an attempt to defeat its opponent.



Extra credit

Support additional choices   additional weapons.




 
# conventional weapons
enum Choice
  Rock
  Paper
  Scissors
end
 
BEATS = {
  Choice::Rock     => [Choice::Paper],
  Choice::Paper    => [Choice::Scissors],
  Choice::Scissors => [Choice::Rock],
}
 
# uncomment to use additional weapons
# enum Choice
#   Rock
#   Paper
#   Scissors
#   Lizard
#   Spock
# end
 
# BEATS = {
#   Choice::Rock     => [Choice::Paper, Choice::Spock],
#   Choice::Paper    => [Choice::Scissors, Choice::Lizard],
#   Choice::Scissors => [Choice::Rock, Choice::Spock],
#   Choice::Lizard   => [Choice::Rock, Choice::Scissors],
#   Choice::Spock    => [Choice::Paper, Choice::Lizard],
# }
 
class RPSAI
  @stats = {} of Choice => Int32
 
  def initialize
    Choice.values.each do |c|
      @stats[c] = 1
    end
  end
 
  def choose
    v = rand(@stats.values.sum)
    @stats.each do |choice, rate|
      v -= rate
      return choice if v < 0
    end
    raise ""
  end
 
  def train(selected)
    BEATS[selected].each do |c|
      @stats[c] += 1
    end
  end
end
 
enum GameResult
  HumanWin
  ComputerWin
  Draw
 
  def to_s
    case self
    when .draw?
      "Draw"
    when .human_win?
      "You win!"
    when .computer_win?
      "I win!"
    end
  end
end
 
class RPSGame
  @score = Hash(GameResult, Int32).new(0)
  @ai = RPSAI.new
 
  def check(player, computer)
    return GameResult::ComputerWin if BEATS[player].includes? computer
    return GameResult::HumanWin if BEATS[computer].includes? player
    return GameResult::Draw
  end
 
  def round
    puts ""
    print "Your choice (#{Choice.values.join(", ")}):"
    s = gets.not_nil!.strip.downcase
    return false if "quit".starts_with? s
    player_turn = Choice.values.find { |choice| choice.to_s.downcase.starts_with? s }
    unless player_turn
      puts "Invalid choice"
      return true
    end
    ai_turn = @ai.choose
    result = check(player_turn, ai_turn)
    puts "H: #{player_turn}, C: #{ai_turn} => #{result}"
    @score[result] += 1
    puts "score: human=%d, computer=%d, draw=%d" % GameResult.values.map { |r| @score[r] }
    @ai.train player_turn
    true
  end
end
 
game = RPSGame.new
loop do
  break unless game.round
end