Problem of the Day
A new programming or logic puzzle every Mon-Fri

Rock Paper Scissors AI

Welcome back to another exciting Monday of Problem of the Day! Registration through Github is now open! There are currently no features for registered users except for the fact that all your comments will be associated with your account. As comment features come online you'll be able to make use of them on older posts with your account.

Today's goal is to build out the AI for our favorite childhood game, Rock Paper Scissors. The AI for this program will keep track of the player's previous choices and then make its move based off that. If a player chooses rock 3 times in a row then the AI should weight paper heavier when selecting its move. To prevent cheating, the AI must make its decision before the player inputs their move.

For a bonus write the data to file so that your AI data can reflect over time. A simple text file with 3 numbers indicating how many times each choice has been chosen will work or you can choose to do something more complex.

A quick recap for those who haven't played in awhile:

  • Rock beats Scissors
  • Scissors beats Paper
  • Paper beats Rock

Permalink: http://problemotd.com/problem/rock-paper-scissors-ai/

Comments:

  • duddles - 10 years, 8 months ago

    Here's a pretty basic attempt in python:

    '''     
    4/28/14
    rock paper scissors ai
    '''
    def rock_paper_scissors(filename):
        value_beats_key = {'s':'r','p':'s','r':'p'}
        last_three = []
        choice_to_number = {'r':0,'p':1,'s':2}
    
        # load history of player moves
        if os.path.isfile(filename):
            with open(filename) as FH:
                player_moves = [int(i) for i in FH.readline().split()]
        else:
            player_moves = [0,0,0]
    
        while True:
            # calculate the frequency of all past player moves
            if sum(player_moves) == 0:
                player_freq = [1/3.]*3
            else:
                total = float(sum(player_moves))
                player_freq = [i/total for i in player_moves]
    
            # if last 3 player moves are identical, boost the frequency for beating that move
            if len(last_three) == 3 and (last_three[0] == last_three[1] and last_three[1] == last_three[2]):
                prefer = last_three[0]
                prefer_index = choice_to_number[prefer]
                boost = (1 - player_freq[prefer_index]) / 2.
                for i in xrange(len(player_freq)):
                    if i == prefer_index:
                        player_freq[i] += boost
                    else:
                        player_freq[i] -= boost / 2.
    
            # randomly choose a move to counter the player move frequencies            
            random_number = random.random()
            if random_number < player_freq[0]:
                ai_choice = value_beats_key['r']
            elif random_number < (player_freq[0] + player_freq[1]):
                ai_choice = value_beats_key['p']
            else:
                ai_choice = value_beats_key['s']
    
            # get the player's choice    
            choice = ''
            while not choice or choice not in 'rpsq':
                choice = raw_input('Enter r, p, or s (q to quit): ')
            if choice == 'q':
                with open(filename,'w') as FH:
                    FH.write('{} {} {}'.format(player_moves[0],player_moves[1],player_moves[2]))
                break
            player_moves[choice_to_number[choice]] += 1
    
            # determine a winner and add the player's choice to last_three    
            print 'You {}, AI {} => '.format(choice,ai_choice),
            if value_beats_key[choice] == ai_choice:
                print 'AI wins'
            elif choice == ai_choice:
                print 'Tie'
            else:
                print 'You win'
            if len(last_three) == 3:
                last_three.pop(0)
            last_three.append(choice)
    

    reply permalink

  • Jt - 10 years, 8 months ago

    C#, attempt to determine what you're most likely to choose as the next answer and counter that.

    namespace ProblemOtd20140428
    {
      using System;
      using System.Collections.Generic;
      using System.IO;
      using System.Linq;
    
      class Program
      {
        public static string PreviousChoices = "";
        public enum Choice { rock = 0, paper = 1, scissors = 2 }
    
        static void Main(string[] args)
        {
          ReadLog();
          Choice aiChoice = GetAiChoice();
          Console.WriteLine("Enter your choice?");
          string command = Console.ReadLine().ToLower();
    
          while (command != "exit")
          {
            switch (command)
            {
              case "rock":
                DidAiWin(Choice.rock, aiChoice);
                break;
              case "paper":
                DidAiWin(Choice.paper, aiChoice);
                break;
              case "scissors":
                DidAiWin(Choice.scissors, aiChoice);
                break;
              case "lizard":
              case "spock":
                Console.WriteLine("Very funny, please try again.");
                break;
              default:
                Console.WriteLine("Invalid choice, please try again.");
                break;
            }
    
            aiChoice = GetAiChoice();
            Console.WriteLine("Enter your choice?");
            command = Console.ReadLine().ToLower();
          }
    
          WriteLog();
          Console.WriteLine("Finished press enter to exit");
          Console.ReadLine();
        }
    
        public static bool DidAiWin(Choice playerChoice, Choice aiChoice)
        {
          PreviousChoices += (int)playerChoice;
    
          if (playerChoice == aiChoice)
          {
            Console.WriteLine(string.Format("You picked {0}, AI picked {1}, it's a draw", playerChoice, aiChoice));
            return false;
          }
    
          if (playerChoice > aiChoice && !(playerChoice == Choice.scissors && aiChoice == Choice.rock))
          {
            Console.WriteLine(string.Format("You picked {0}, AI picked {1}, you won", playerChoice, aiChoice));
            return false;
          }
    
          Console.WriteLine(string.Format("You picked {0}, AI picked {1}, you lost.", playerChoice, aiChoice));
          return true;
        }
    
        public static Choice GetWinner(Choice probablePlayerChoice)
        {
          if (probablePlayerChoice == Choice.scissors)
          {
            return Choice.rock;
          }
    
          return probablePlayerChoice + 1;
        }
    
        public static Choice GetAiChoice()
        {
          if (PreviousChoices.Count() <= 2)
          {
            return Choice.rock;
          }
    
          //Find what the player choose last, and then figure out what they usually chose next
          Dictionary<Choice, int> choiceCount = new Dictionary<Choice, int>();
          choiceCount.Add(Choice.paper, 0);
          choiceCount.Add(Choice.rock, 0);
          choiceCount.Add(Choice.scissors, 0);
          char lastChoice = PreviousChoices[PreviousChoices.Length - 1];
          for (int index = PreviousChoices.Count() - 2; index >= 0; index--)
          {
            if (PreviousChoices[index] == lastChoice)
            {
              switch ((Choice)(int.Parse(PreviousChoices[index + 1].ToString())))
              {
                case Choice.rock:
                  choiceCount[Choice.rock]++;
                  break;
                case Choice.paper:
                  choiceCount[Choice.paper]++;
                  break;
                case Choice.scissors:
                  choiceCount[Choice.scissors]++;
                  break;
              }
            }
          }
    
          return GetWinner(choiceCount.First(choice => choice.Value == choiceCount.Values.Max()).Key);
        }
    
        public static void ReadLog()
        {
          if (new FileInfo("History.txt").Exists)
          {
            StreamReader streamReader = new StreamReader("History.txt");
            PreviousChoices = streamReader.ReadToEnd();
            streamReader.Close();
          }
        }
    
        public static void WriteLog()
        {
          StreamWriter streamWriter = new StreamWriter("History.txt");
          streamWriter.Write(PreviousChoices);
          streamWriter.Flush();
          streamWriter.Close();
        }
      }
    }
    

    reply permalink

  • Hueho - 10 years, 8 months ago

    In Ruby. I'm too lazy to deal with files, so I just dump and load a hash with weight using YAML serializing.

    require 'yaml'
    
    Cell = Struct.new(:value, :index)
    
    class Array
      def accumulate
        self.each_with_object([]) do |item, result|
          result.push((result.last || 0) + item)
        end
      end
    end
    
    def sample_with_weights(array, weights)
      normalized = weights.map { |w| w + 1 }
      total = normalized.reduce(:+)
    
      relative = normalized
        .map { |w| w.to_f / total.to_f }
        .accumulate
        .each_with_index
        .map { |w, i| Cell.new(w, i) }
        .sort_by { |cell| cell.value }
    
      sample = rand
      relative.each do |cell|
        if sample < cell.value
          return array[cell.index]
        end
      end
    end
    
    MOVES = [:rock, :paper, :scissors]
    
    DEFEATS = {
      rock: :paper,
      paper: :scissors,
      scissors: :rock
    }
    
    GAMES = {
      rock: {
        rock: :tie,
        paper: :lose,
        scissors: :win
      },
      paper: {
        rock: :win,
        paper: :tie,
        scissors: :lose
      },
      scissors: {
        rock: :lose,
        paper: :win,
        scissors: :tie
      }
    }
    
    class Player
      attr_reader :points, :method
    
      def initialize(method)
        @points = 0
        @method = method
      end
    
      def update(player, opponent)
        @points += 1 if GAMES[player][opponent] == :win
        method.update opponent
        GAMES[player][opponent]
      end
    
      def play
        method.play
      end
    end
    
    class HumanPlayer
      def play
        puts "Human player, what is your move?"
        loop do
          result = (STDIN.gets "\n").strip.to_sym
          return result if MOVES.include? result
    
          puts "Invalid option, try again..."
        end
      end
    
      def update(*args)
      end
    end
    
    class AIPlayer
    
      def default
        {
          rock: 0,
          paper: 0,
          scissors: 0
        }
      end
    
      def initialize
        if File.exists? "memory.rps.yml"
          @memory = YAML.load_file "memory.rps.yml"
        end
        @memory = default unless @memory
      end
    
      def values
        [@memory[:rock], @memory[:paper], @memory[:scissors]]
      end
    
      def play
        DEFEATS[sample_with_weights(MOVES.to_a, values)]
      end
    
      def update(opponent)
        @memory[opponent] += 1
      end
    
      def finish
        File.open "memory.rps.yml", 'w' do |f|
          YAML.dump @memory, f
        end
      end
    
    end
    
    class Match
      attr_reader :p1, :p2, :rounds
    
      def initialize(pm1, pm2, rounds)
        @p1, @p2 = Player.new(pm1), Player.new(pm2)
        @rounds = rounds
      end
    
      def start
        puts "Start match!\n"
        rounds.times do |i|
          puts "Round #{i}..."
    
          m1, m2 = p1.play, p2.play
          puts "P1(#{m1}) vs P2(#{m2})"
    
          s1 = p1.update(m1, m2)
          s2 = p2.update(m2, m1)
    
          case 
          when s1 == s2
            puts "Tie!"
          when s1 == :win
            puts "Player 1 wins the round!"
          else
            puts "Player 2 wins the round!"
          end
          puts ""
        end
    
        puts "End of match..."
        case 
        when p1.points == p2.points
          puts "Tie! Both players with #{p1.points} points."
        when p1.points > p2.points
          puts "Player 1 wins the match, with #{p1.points} points against #{p2.points} of Player 2."
        else
          puts "Player 2 wins the match, with #{p2.points} points against #{p1.points} of Player 1."
        end
        puts ""
      end
    end
    
    human = HumanPlayer.new
    ai = AIPlayer.new
    
    Match.new(human, ai, 1).start
    ai.finish
    

    reply permalink

  • Byron - 10 years, 7 months ago

    Made using Python. :)

    from random import randrange 
    
    class NeuralNetwork:
        def __init__(self, paperW, rockW, sissorsW):
            self.pWeight = paperW
            self.rWeight = rockW
            self.sWeight = sissorsW
            self.lastGuess = "None"
    
    
        def GetChoice(self):
            value = randrange (0, 100 ) / 100
    
            if value < self.pWeight:
                self.lastGuess = "P"
                return "P"
            else:
                value -= self.pWeight
    
            if value < self.rWeight:
                self.lastGuess = "R"
                return "R"
            else:
                 value -= self.rWeight
    
            if value < self.sWeight:
                self.lastGuess = "S"
                return "S"
    
    
        def UpdateWeights(self, Type, changeAmount):
    
            if Type == "P":
                self.pWeight += float(changeAmount)
                self.rWeight -= float(changeAmount) / 2
                self.sWeight -= float(changeAmount) / 2
    
            elif Type == "R":
                self.rWeight += float(changeAmount)
                self.sWeight -= float(changeAmount) / 2
                self.pWeight -= float(changeAmount) / 2
    
            elif Type == "S":
                self.sWeight += float(changeAmount)
                self.rWeight -= float(changeAmount) / 2
                self.pWeight -= float(changeAmount) / 2
            else:
                return
    
            self.rWeight = max(0, min(self.rWeight, 1))
            self.pWeight = max(0, min(self.pWeight, 1))
            self.sWeight = max(0, min(self.sWeight, 1))
    
        def SendFeedback(self, wonGame):
            if bool( wonGame ):
                self.UpdateWeights( self.lastGuess, 0.03 )
            else:
                 self.UpdateWeights( self.lastGuess, -0.03 )
    
            print( "Rock Weight: " + str(self.rWeight) )
            print( "Paper Weight: " + str(self.pWeight) )
            print( "Sissors Weight: " + str(self.sWeight) )
    
    
    network = NeuralNetwork(0.3333, 0.333, 0.333)
    
    
    while True:
    
        aiChoice = network.GetChoice()
        print("Pick { R, P, S} to play")
        userChoice = input(str)
    
        print("CHOICES User: " + str(userChoice) + ". AI: " + str(aiChoice))
    
        if str(userChoice) == str(aiChoice):
            print("Draw!")
    
        elif str(userChoice) == "R" and str(aiChoice) == "P":
            print("AI Wins!")
            network.SendFeedback(True)
    
        elif str(userChoice) == "R" and str(aiChoice) == "S":
            print ("Players Wins!")
            network.SendFeedback(False)
    
        elif str(userChoice) == "P" and str(aiChoice) == "R":
            print ("Players Wins!")
            network.SendFeedback(False)
    
    
        elif str(userChoice) == "P" and str(aiChoice) == "S":
             print("AI Wins!")
             network.SendFeedback(True)
    
        elif str(userChoice) == "S" and str(aiChoice) == "R":
            print ("AI Wins!")
            network.SendFeedback(True)
    
        elif str(userChoice) == "S" and str(aiChoice) == "P":
             print("Player Wins!")
             network.SendFeedback(False)
        else:
            print ("INVALID CHOICE")
    

    reply permalink

Content curated by @MaxBurstein