Rock-paper-scissors

From Rosetta Code
Revision as of 18:14, 13 July 2011 by rosettacode>Markhobley (promoted to task.)
Task
Rock-paper-scissors
You are encouraged to solve this task according to the task description, using any language you may know.

The task is to implement the classic children's game Rock-paper-scissors, as well as a simple predictive AI 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 to in an attempt to defeat its opponent.

Ada

<lang Ada>with Ada.Text_IO; with Ada.Numerics.Float_Random;

procedure Rock_Paper_Scissors is

  package Rand renames Ada.Numerics.Float_Random;
  Gen: Rand.Generator;
  type Choice is (Rock, Paper, Scissors);
  Cnt: array (Choice) of Natural := (1, 1, 1);
    -- for the initialization: pretend that each of Rock, Paper, 
    -- and Scissors, has been played once by the human
    -- else the first computer choice would be deterministic
  function Computer_Choice return Choice is
     Random_Number: Natural :=
       Integer(Rand.Random(Gen)
         * (Float(Cnt(Rock)) + Float(Cnt(Paper)) + Float(Cnt(Scissors))));
  begin
     if Random_Number < Cnt(Rock) then
        -- guess the human will choose Rock
        return Paper;
     elsif Random_Number - Cnt(Rock) < Cnt(Paper) then
        -- guess the human will choose Paper
        return Scissors;
     else -- guess the human will choose Scissors
        return Rock;
     end if;
  end Computer_Choice;
  Finish_The_Game: exception;
  function Human_Choice return Choice is
     Done: Boolean := False;
     T: constant String
       := "enter ""r"" for Rock, ""p"" for Paper, or ""s"" for Scissors""!";
     U: constant String
       := "or enter ""q"" to Quit the game";
     Result: Choice;
  begin
     Ada.Text_IO.Put_Line(T);
     Ada.Text_IO.Put_Line(U);
     while not Done loop
        Done := True;
        declare
           S: String := Ada.Text_IO.Get_Line;
        begin
           if S="r" or S="R" then
              Result := Rock;
           elsif S="p" or S = "P" then
              Result := Paper;
           elsif S="s" or S="S" then
              Result := Scissors;
           elsif S="q" or S="Q" then
              raise Finish_The_Game;
           else
              Done := False;
           end if;
        end;
     end loop;
     return Result;
  end Human_Choice;
  type Result is (Human_Wins, Draw, Computer_Wins);
  function "<" (X, Y: Choice) return Boolean is
     -- X < Y if X looses against Y
  begin
     case X is
        when Rock => return  (Y = Paper);
        when Paper => return (Y = Scissors);
        when Scissors => return (Y = Rock);
     end case;
  end "<";
  Score: array(Result) of Natural := (0, 0, 0);
  C,H: Choice;
  Res: Result;

begin

  -- play the game
  loop
     C := Computer_Choice;  -- the computer makes its choice first
     H := Human_Choice;     -- now ask the player for his/her choice
     Cnt(H) := Cnt(H) + 1;  -- update the counts for the AI
     if C < H then
        Res := Human_Wins;
     elsif H < C then
        Res := Computer_Wins;
     else
        Res := Draw;
     end if;
     Ada.Text_IO.Put_Line("COMPUTER'S CHOICE: " & Choice'Image(C)
                            & "       RESULT: " & Result'Image(Res));
     Ada.Text_IO.New_Line;
     Score(Res) := Score(Res) + 1;
  end loop;

exception

  when Finish_The_Game =>
     Ada.Text_IO.New_Line;
     for R in Score'Range loop
        Ada.Text_IO.Put_Line(Result'Image(R) & Natural'Image(Score(R)));
     end loop;

end Rock_Paper_Scissors;</lang>

First and last few lines of the output of a game, where the human did permanently choose Rock:

./rock_paper_scissors 
enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
r
COMPUTER'S CHOICE: SCISSORS       RESULT: HUMAN_WINS

enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
r
COMPUTER'S CHOICE: ROCK       RESULT: DRAW

enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
r
COMPUTER'S CHOICE: SCISSORS       RESULT: HUMAN_WINS

enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
r
COMPUTER'S CHOICE: ROCK       RESULT: DRAW


[...]


enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
r
COMPUTER'S CHOICE: ROCK       RESULT: DRAW

enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
r
COMPUTER'S CHOICE: PAPER       RESULT: COMPUTER_WINS

enter "r" for Rock, "p" for Paper, or "s" for Scissors"!
or enter "q" to Quit the game
q

HUMAN_WINS 2
DRAW 5
COMPUTER_WINS 21

C

<lang C>#include <stdio.h>

  1. include <stdlib.h>

int rand_i(int n) { int rand_max = RAND_MAX - (RAND_MAX % n); int ret; while ((ret = rand()) >= rand_max); return ret/(rand_max / n); }

int weighed_rand(int *tbl, int len) { int i, sum, r; for (i = 0, sum = 0; i < len; sum += tbl[i++]); if (!sum) return rand_i(len);

r = rand_i(sum) + 1; for (i = 0; i < len && (r -= tbl[i]) > 0; i++); return i; }

int main() { int user_action, my_action; int user_rec[] = {0, 0, 0}; char *names[] = { "Rock", "Paper", "Scissors" }; char str[2]; char *winner[] = { "We tied.", "Meself winned.", "You win." };

while (1) { my_action = (weighed_rand(user_rec, 3) + 1) % 3;

printf("\nYour choice [1-3]:\n" " 1. Rock\n 2. Paper\n 3. Scissors\n> ");

/* scanf is a terrible way to do input. should use stty and keystrokes */ if (!scanf("%d", &user_action)) { scanf("%1s", str); if (*str == 'q') return 0; continue; } user_action --; if (user_action > 2 || user_action < 0) { printf("invalid choice; again\n"); continue; } printf("You chose %s; I chose %s. %s\n", names[user_action], names[my_action], winner[(my_action - user_action + 3) % 3]);

user_rec[user_action]++; } }</lang>

Go

<lang go>package main

import (

   "fmt"
   "rand"
   "strings"
   "time"

)

const rps = "rps"

var msg = []string{

   "Rock breaks scissors",
   "Paper covers rock",
   "Scissors cut paper",

}

func main() {

   rand.Seed(time.Nanoseconds())
   fmt.Println("Rock Paper Scissors")
   fmt.Println("Enter r, p, or s as your play.  Anything else ends the game.")
   fmt.Println("Running score shown as <your wins>:<my wins>")
   var pi string // player input
   var aScore, pScore int
   sl := 3               // for output alignment
   pcf := make([]int, 3) // pcf = player choice frequency
   var plays int
   aChoice := rand.Intn(3) // ai choice for first play is completely random
   for {
       // get player choice
       fmt.Print("Play: ")
       _, err := fmt.Scanln(&pi)  // lazy
       if err != nil || len(pi) != 1 {
           break
       }
       pChoice := strings.Index(rps, pi)
       if pChoice < 0 {
           break
       }
       pcf[pChoice]++
       plays++
       // show result of play
       fmt.Printf("My play:%s%c.  ", strings.Repeat(" ", sl-2), rps[aChoice])
       switch (aChoice - pChoice + 3) % 3 {
       case 0:
           fmt.Println("Tie.")
       case 1:
           fmt.Printf("%s.  My point.\n", msg[aChoice])
           aScore++
       case 2:
           fmt.Printf("%s.  Your point.\n", msg[pChoice])
           pScore++
       }
       // show score
       sl, _ = fmt.Printf("%d:%d  ", pScore, aScore)
       // compute ai choice for next play
       switch rn := rand.Intn(plays); {
       case rn < pcf[0]:
           aChoice = 1
       case rn < pcf[0]+pcf[1]:
           aChoice = 2
       default:
           aChoice = 0
       }
   }

}</lang> Sample output:

Rock Paper Scissors
Enter r, p, or s as your play.  Anything else ends the game.
Running score shown as <your wins>:<my wins>
Play: r
My play: r.  Tie.
0:0  Play: p
My play:   p.  Tie.
0:0  Play: s
My play:   p.  Scissors cut paper.  Your point.
1:0  Play: r
My play:   p.  Paper covers rock.  My point.
1:1  Play: r
My play:   r.  Tie.
1:1  Play: r
My play:   p.  Paper covers rock.  My point.
1:2  Play: 

Icon and Unicon

The key to this comes down to two structures and two lines of code. The player history historyP is just an ordered list of every player turn and provides the weight for the random selection. The beats list is used to rank moves and to choose the move that would beat the randomly selected move. <lang Icon>link printf

procedure main()

printf("Welcome to Rock, Paper, Scissors.\n_

       Rock beats scissors, Scissors beat paper, and Paper beats rock.\n\n")

historyP := ["rock","paper","scissors"] # seed player history winP := winC := draws := 0 # totals

beats := ["rock","scissors","paper","rock"] # what beats what 1 apart

repeat {

  printf("Enter your choice or rock(r), paper(p), scissors(s) or quit(q):")
  turnP := case map(read()) of {
     "q"|"quit": break
     "r"|"rock": "rock"
     "p"|"paper": "paper"
     "s"|"scissors": "scissors"
     default:  printf(" - invalid choice.\n") & next
     }
  turnC := beats[(?historyP == beats[i := 2 to *beats],i-1)]  # choose move
     
  put(historyP,turnP)                       # record history
  printf("You chose %s, computer chose %s",turnP,turnC)   
  (beats[p := 1 to *beats] == turnP) & 
     (beats[c := 1 to *beats] == turnC) & (abs(p-c) <= 1)  # rank play
    
  if p = c then
     printf(" - draw (#%d)\n",draws +:= 1 )
  else if p > c then   
     printf(" - player win(#%d)\n",winP +:= 1)        
  else 
     printf(" - computer win(#%d)\n",winC +:= 1)    
  }

printf("\nResults:\n %d rounds\n %d Draws\n %d Computer wins\n %d Player wins\n",

  winP+winC+draws,draws,winC,winP)   

end</lang>

printf.icn provides printf

Sample output:

Welcome to Rock, Paper, Scissors.
Rock beats scissors, Scissors beat paper, and Paper beats rock.

Enter your choice or rock(r), paper(p), scissors(s) or quit(q):s
You chose scissors, computer chose scissors - draw (#1)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):p
You chose paper, computer chose paper - draw (#2)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):r
You chose rock, computer chose scissors - computer win(#1)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):r
You chose rock, computer chose rock - draw (#3)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):p
You chose paper, computer chose paper - draw (#4)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):s
You chose scissors, computer chose scissors - draw (#5)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):r
You chose rock, computer chose rock - draw (#6)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):r
You chose rock, computer chose paper - player win(#1)
Enter your choice or rock(r), paper(p), scissors(s) or quit(q):q

Results:
 8 rounds
 6 Draws
 1 Computer wins
 1 Player wins

J

<lang j>require'misc strings' game=:3 :0

 outcomes=. rps=. 0 0 0
 choice=. 1+?3
 while.#response=. prompt'  Choose Rock, Paper or Scissors: ' do.
   playerchoice=. 1+'rps' i. tolower {.deb response
   if.4 = choice do.
     smoutput 'Unknown response.'
     smoutput 'Enter an empty line to quit'
     continue.
   end.
   smoutput '    I choose ',choice {::;:'. Rock Paper Scissors'
   smoutput (wintype=. 3 | choice-playerchoice) {:: 'Draw';'I win';'You win'
   outcomes=. outcomes+0 1 2 = wintype
   rps=. rps+1 2 3=playerchoice
   choice=. 1+3|(?0) I.~ (}:%{:)+/\ 0, rps
 end.
 ('Draws:','My wins:',:'Your wins: '),.":,.outcomes

)</lang>

Example use (playing to give the computer implementation the advantage):

<lang j> game

 Choose Rock, Paper or Scissors: rock
   I choose Scissors

You win

 Choose Rock, Paper or Scissors: rock
   I choose Paper

I win

 Choose Rock, Paper or Scissors: rock
   I choose Paper

I win

 Choose Rock, Paper or Scissors: rock
   I choose Paper

I win

 Choose Rock, Paper or Scissors: rock
   I choose Paper

I win

 Choose Rock, Paper or Scissors: 

Draws: 0 My wins: 4 Your wins: 1</lang>

Java

Works with: Java version 1.5+

This could probably be made simpler, but some more complexity is necessary so that other items besides rock, paper, and scissors can be added (as school children and nerds like to do [setup for rock-paper-scissors-lizard-spock is in multi-line comments]). The method getAIChoice() borrows from the Ada example in spirit, but is more generic to additional items. <lang java5>import java.util.Arrays; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Random;

public class RPS { public enum Item{ ROCK, PAPER, SCISSORS, /*LIZARD, SPOCK*/; public List<Item> losesToList; public boolean losesTo(Item other) { return losesToList.contains(other); } static { SCISSORS.losesToList = Arrays.asList(ROCK/*, SPOCK*/); ROCK.losesToList = Arrays.asList(PAPER/*, SPOCK*/); PAPER.losesToList = Arrays.asList(SCISSORS/*, LIZARD*/); /* SPOCK.losesToList = Arrays.asList(PAPER, LIZARD); LIZARD.losesToList = Arrays.asList(SCISSORS, ROCK); */

               }

} //EnumMap uses a simple array under the hood public final Map<Item, Integer> counts = new EnumMap<Item, Integer>(Item.class){{ for(Item item:Item.values()) put(item, 1); }};

private int totalThrows = Item.values().length;

public static void main(String[] args){ RPS rps = new RPS(); rps.run(); }

public void run() { Scanner in = new Scanner(System.in); System.out.print("Make your choice: "); while(in.hasNextLine()){ Item aiChoice = getAIChoice(); String input = in.nextLine(); Item choice; try{ choice = Item.valueOf(input.toUpperCase()); }catch (IllegalArgumentException ex){ System.out.println("Invalid choice"); continue; } counts.put(choice, counts.get(choice) + 1); totalThrows++; System.out.println("Computer chose: " + aiChoice); if(aiChoice == choice){ System.out.println("Tie!"); }else if(aiChoice.losesTo(choice)){ System.out.println("You chose...wisely. You win!"); }else{ System.out.println("You chose...poorly. You lose!"); } System.out.print("Make your choice: "); } }

private static final Random rng = new Random(); private Item getAIChoice() { int rand = rng.nextInt(totalThrows); for(Map.Entry<Item, Integer> entry:counts.entrySet()){ Item item = entry.getKey(); int count = entry.getValue(); if(rand < count){ List<Item> losesTo = item.losesToList; return losesTo.get(rng.nextInt(losesTo.size())); } rand -= count; } return null; } }</lang> Sample output:

Make your choice: rock
Computer chose: PAPER
You chose...poorly. You lose!
Make your choice: rock
Computer chose: SCISSORS
You chose...wisely. You win!
Make your choice: rock
Computer chose: PAPER
You chose...poorly. You lose!
Make your choice: rock
Computer chose: SCISSORS
You chose...wisely. You win!
Make your choice: rock
Computer chose: PAPER
You chose...poorly. You lose!
Make your choice: rock
Computer chose: ROCK
Tie!
Make your choice: rock
Computer chose: ROCK
Tie!
Make your choice: rock
Computer chose: PAPER
You chose...poorly. You lose!
Make your choice: rock
Computer chose: PAPER
You chose...poorly. You lose!
Make your choice: rock
Computer chose: PAPER
You chose...poorly. You lose!
Make your choice: scissors
Computer chose: PAPER
You chose...wisely. You win!
Make your choice: scissors
Computer chose: PAPER
You chose...wisely. You win!
...

Python

<lang python>#!/usr/bin/python from random import choice, randrange from bisect import bisect from collections import defaultdict

WHATBEATS = { 'paper' : 'scissors',

               'scissors' : 'rock',
               'rock' : 'paper'        }

ORDER = ('rock', 'paper', 'scissors')

CHOICEFREQUENCY = defaultdict(int)

def probChoice(choices, probabilities):

   total = sum(probabilities)
   prob_accumulator = 0
   accumulator = []
   for p in probabilities:
       prob_accumulator += p
       accumulator.append(prob_accumulator)
   r = randrange(total)
   bsct = bisect(accumulator, r)
   chc = choices[bsct]
   return chc

def checkWinner(a, b):

   if b == WHATBEATS[a]:
       return b
   elif a == WHATBEATS[b]:
       return a
   return None

def sanitizeChoice(a):

   # Drop it to lower-case
   return a.lower()

def registerPlayerChoice(choice):

   CHOICEFREQUENCY[choice] += 1

def getRandomChoice():

   if len(CHOICEFREQUENCY) == 0:
       return choice(ORDER)
   choices = CHOICEFREQUENCY.keys()
   probabilities = CHOICEFREQUENCY.values()
   return WHATBEATS[probChoice(choices, probabilities)]

while True:

   humanChoice = raw_input()
   humanChoice = sanitizeChoice(humanChoice)
   if humanChoice not in ORDER:
       continue
   compChoice = getRandomChoice()
   print "Computer picked", compChoice+",",
   # Don't register the player choice until after the computer has made
   # its choice.
   registerPlayerChoice(humanChoice)
   winner = checkWinner(humanChoice, compChoice)
   if winner == None:
       winner = "nobody"
   print winner, "wins!"</lang>

Output, where player always chooses Rock:

!504 #5 j0 ?0 $ ./rps.py 
rock
Computer picked scissors, rock wins!
rock
Computer picked paper, paper wins!
rock
Computer picked paper, paper wins!
rock
Computer picked paper, paper wins!
rock
Computer picked paper, paper wins!

Tcl

<lang tcl>package require Tcl 8.5

      1. Choices are represented by integers, which are indices into this list:
      2. Rock, Paper, Scissors
      3. Normally, idiomatic Tcl code uses names for these sorts of things, but it
      4. turns out that using integers simplifies the move-comparison logic.
  1. How to ask for a move from the human player

proc getHumanMove {} {

   while 1 {

puts -nonewline "Your move? \[R\]ock, \[P\]aper, \[S\]cissors: " flush stdout gets stdin line if {[eof stdin]} { puts "\nBye!" exit } set len [string length $line] foreach play {0 1 2} name {"rock" "paper" "scissors"} { # Do a prefix comparison if {$len && [string equal -nocase -length $len $line $name]} { return $play } } puts "Sorry, I don't understand that. Try again please."

   }

}

  1. How to ask for a move from the machine player

proc getMachineMove {} {

   global states
   set choice [expr {int(rand() * [::tcl::mathop::+ {*}$states 3])}]
   foreach play {1 2 0} count $states {

if {[incr sum [expr {$count+1}]] > $choice} { puts "I play \"[lindex {Rock Paper Scissors} $play]\"" return $play }

   }

}

  1. Initialize some state variables

set states {0 0 0} set humanWins 0 set machineWins 0

  1. The main game loop

while 1 {

   # Get the moves for this round
   set machineMove [getMachineMove]
   set humanMove [getHumanMove]
   # Report on what happened
   if {$humanMove == $machineMove} {

puts "A draw!"

   } elseif {($humanMove+1)%3 == $machineMove} {

puts "I win!" incr machineWins

   } else {

puts "You win!" incr humanWins

   }
   puts "Cumulative scores: $humanWins to you, $machineWins to me"
   # Update the state of how the human has played in the past
   lset states $humanMove [expr {[lindex $states $humanMove] + 1}]

}</lang> Sample run:

Your move? [R]ock, [P]aper, [S]cissors: rock
I play "Scissors"
You win!
Cumulative scores: 1 to you, 0 to me
Your move? [R]ock, [P]aper, [S]cissors: r
I play "Paper"
I win!
Cumulative scores: 1 to you, 1 to me
Your move? [R]ock, [P]aper, [S]cissors: s
I play "Paper"
You win!
Cumulative scores: 2 to you, 1 to me
Your move? [R]ock, [P]aper, [S]cissors: sciss
I play "Paper"
You win!
Cumulative scores: 3 to you, 1 to me
Your move? [R]ock, [P]aper, [S]cissors: p
I play "Paper"
A draw!
Cumulative scores: 3 to you, 1 to me
Your move? [R]ock, [P]aper, [S]cissors: zaphod beeblebrox
Sorry, I don't understand that. Try again please.
Your move? [R]ock, [P]aper, [S]cissors: r
I play "Scissors"
You win!
Cumulative scores: 4 to you, 1 to me
Your move? [R]ock, [P]aper, [S]cissors: ^D
Bye!