Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elixir Rock, Paper, Scissors #4

Open
emson opened this issue Dec 11, 2015 · 7 comments
Open

Elixir Rock, Paper, Scissors #4

emson opened this issue Dec 11, 2015 · 7 comments

Comments

@emson
Copy link
Owner

emson commented Dec 11, 2015

Recreate the classic "Rock, Paper, Scissors" game in Elixir. See the website for more details : http://elixirgolf.com/articles/elixir-rock-paper-scissors/

  • The user will input their choice as a String of 3 characters e.g. "rps"
  • The program will then output as a String of 3 characters "spr" and a result e.g. "Draw", "Win", "Lose"
  • The program will choose it’s response output randomly
  • Each individual input character will always output one of the three possibilites
  • If the user enters blank or any other character, then the program simply returns
  • The program should not be influenced by the user’s input, i.e. it should make it’s choice at random
  1. Please add your solutions as comments to this Github issue
  2. Remember to add your Twitter handle (I will link to it if you get a mention)
    Many thanks, Ben
    When the puzzle is over I'll write them up on http://elixirgolf.com and link back to your Twitter handle
@henrik
Copy link

henrik commented Dec 11, 2015

(I'll keep editing this one post for my solutions.)

200 characters with some error checking:

import Enum;case IO.gets''do<<a,b,c,10>>->x=for _<-1..3,do: random'rps';s=sum map zip([a,b,c],x),fn{v,v}->0;{?p,?r}->1;{?r,?s}->1;{?s,?p}->1;_->-1end;IO.puts [x,s<0&&"Lose"||s>0&&"Win"||"Draw"];_->end

(Was inspired to give map a shot after seeing @hassox's solution – thank you! Also borrowed -1end – hadn't realized you could skip the ; in that one.)

This is basically what it does:

  • Get input and run it through a case statement.
  • The case statement does nothing (_ ->) if it's not three chars followed by a newline. Otherwise, it sticks those chars into three vars a, b, c and…
  • Assigns the computer's three chars to an Erlang string x
  • Calculates your score s by comparing each pair of your chars and the computer's. Each equal pair (draw) is 0, wins are 1, losses are -1. These three numbers are summed together.
  • If the sum is 0, the match is a draw. If the sum is negative, it's a loss. If positive, you win.

Some things to note:

  • I did not seed the randomness but in a "real" thing I believe you should. Maybe Elixir 1.2 does it automatically?
  • 10 is ASCII for a newline.
  • Enum.random, Enum.sum, Enum.map and Enum.zip are all used without the module name (thanks to the import).

The specification isn't completely clear, so I chose to do some minimal error handling:

  • Only handles input of exactly 3 chars
  • Quietly returns if given any other length of input, but doesn't validate the exact characters

186 chars without error checking:

import Enum;<<a,b,c,10>>=IO.gets'';x=for _<-1..3,do: random'rps';s=sum map zip([a,b,c],x),fn{v,v}->0;{?p,?r}->1;{?r,?s}->1;{?s,?p}->1;_->-1end;IO.puts [x,s<0&&"Lose"||s>0&&"Win"||"Draw"]

Older version, 196 chars, for + anonymous function instead of map:

import Enum;<<a,b,c,10>>=IO.gets'';x=for _<-1..3,do: random'rps';s=sum for{p,c}<-zip([a,b,c],x),do: fn[v,v]->0;'pr'->1;'rs'->1;'sp'->1;_->-1;end.([p,c]);IO.puts [x,s<0&&"Lose"||s>0&&"Win"||"Draw"]

If we don't care about independent probabilities, we can shorten the randomness a little (my first solution did this until I realized my mistake):

;x=take_random'rrrpppsss',3;

Just for fun, this is one way (probably not the shortest one) to add stricter input checking to my solutions above, like @mmrobins brought up:

<<a,b,c,10>>when a in'rps'and b in 'rps'and c in'rps'->

You can't use a variable for the 'rps' here, sadly.

Using Erlang strings instead of tuples for the zipping – ended up longer:

;s=sum map :lists.zipwith(&[&1,&2],[a,b,c],x),fn[v,v]->0;'pr'->1;'rs'->1;'sp'->1;_->-1end;

@henrik on Twitter.

@hassox
Copy link

hassox commented Dec 11, 2015

This is my first one of these. Never been a code golfer before. I measured it at 203 characters:

import Enum;import String;case(zip(codepoints(strip(IO.gets"")),take_random(~w(r p s),3))|>map(fn{a,a}->0;{"r","s"}->1;{"p","r"}->1;{"s","p"}->1;_->-1end)|>sum)do 0->"Draw";a when a>0->"Win";_->"Lose"end

This has no error checking. Here's what it does:

  • Get the IO and splits it into a list of codepoints, i.e. ["r", "p", "s"]
  • Zip this together with a random shuffle of the list ["r", "p", "s"] (computer move) to produce a list of tuples [{"r", "p"}, {"p", "r"}, {"s", "s"}]. The first element is the user move, second is the computer move.
  • Map over the tuples using a guard clause on the function to identify draws {a, a} and set those to 0, identify wins r/s, s/p, p/r and set those to 1, all others are -1.
  • Sum these up
  • case on the previous value, 0 == "Draw", > 0 == "Win", otherwise "Lose"

@hassox on Twitter

@henrik
Copy link

henrik commented Dec 11, 2015

@hassox That's a neat solution! This one wasn't easy :) Some thoughts:

I think take_random won't repeat values, so you could never get "rrr" for example.

If I understood the description correctly, you want to also show the computer's choices in the output.

@mmrobins
Copy link

I'm nowhere close for character count (341), but here's my solution anyway:

import String;import Enum;l= ~w{r p s};m=IO.gets "";n=m|>strip|>split("",trim: true);if any?(n,fn(x)->!member?(l,x)end)do;else;f = fn()->random l end;o = [f.(), f.(), f.()];IO.puts o;g=fn(x)->case x do;{x,x}->0;{"r","s"}->1;{"p","r"}->1;{"s","p"}->1;_->-1;end;end;case zip(n,o)|>map(g)|>sum do;0->"Draw";x when x > 0->"Win";_->"Lose";end;end;

https://gist.github.com/mmrobins/df54ebe7cf28f58c2a00 easier to read version

One thing I noticed from the other solutions up to this point is they don't seem to exit if you give bad input, like "abc"

Twitter: @mmrobins

@markolson
Copy link

This 355'er has a bunch of inefficiencies I probably can't get rid of easily given how I trial-and-error'd my way through writing it, but it does have some checks to make sure that the input is only 'r', 'p', or 's'.

import Enum;t='rps';c=fn(s)->into(s,HashSet.new)end;s=(IO.gets"")|>String.strip|>String.to_char_list;case count(Set.difference(c.(s),c.(t)))do 0 when length(s)==3->e=for x<-s,do: {x,random(t)};IO.puts map(e,&(elem(&1,1)));x=sum(map(e,fn(x)->case x do {x,x}->0;{?r,?s}->1;{?p,?r}->1;{?s,?p}->1;_->-1end end));IO.puts(x<0&&"Lose"||x>0&&"Win"||"Draw");_->end
  • Reads the user input, strips it and converts it to a character list
  • Checks if all the characters are in the set of 'r', 'p', 's', and uses a cond to bail out if not.
  • Generates a list of {user,system} guesses
  • Scores 1 if the user wins, 0 if a draw, and -1 if the system won
  • Outputs the system guesses
  • Sums the win/loss records
  • Outputs the result

https://gist.github.com/markolson/420d59d8a6f665254a5e for the gist, @mark_olson for twitter.

@f-lombardo
Copy link

I don't know if it could be acceptable :-) , but this is 193 chars:

case IO.gets''do<<a,b,c,10>>->{f, n}=Enum.random([{fn ?r->?p;?p->?s;?s->?r;_->:ko;end,"Lose"},{fn ?p->?r;?s->?p;?r->?s;_->:ko;end,"Win"},{&(&1),"Draw"}]);IO.puts <<f.(a),f.(b),f.(c)>><>n;_->end

Twitter: @f_lombardo

@emson
Copy link
Owner Author

emson commented Dec 18, 2015

@f-lombardo great solution thanks. Yes it is acceptable - also I made a typo: Loose -> Lose therefore could you update your solution which is now 193 chars! Well done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants