Ruby - Player vs Player for Rock, Paper & Scissor Game

414 views Asked by At

Apology if I am repeating what others have previously asked. I have gone through other RSP games questions on stackoverflow. Most are about player vs computer. May be I need to read more theories to understand better and modify the code to suit for my purpose which is player one vs player two.

I am attempting Player one vs Player two - Rock scissors and paper game in Ruby. I have got the following questions about the following code. - How do I hide each player entry? - There are so many repeats in the code, therefore violating DRY principle. How should I refactor this? - Is class method the best way to go with this game (most efficient)? - Currently, this game is only for rock paper scissors. If I want to add lizard spock later, how should I future proof this? or Add this in most efficient way? Thanks in advance!

options = ["rock", "scissors", "paper"]

while true

    print <<TEXT 
1 - rock
2 - scissors
3 - paper
9 - end game
TEXT

    puts "Player 1, choose rock(1), scissors(2), paper(3). To end the game, enter 9."
    player1_val = gets.to_i

    puts "Player 2, choose rock(1), scissors(2), paper(3). To end the game, enter 9."
    player2_val = gets.to_i

    if player1_val == 9 # I am repeating the same condition for player2. How should I combine? 
        puts "End"
        exit
    end

    if player2_val == 9 
        puts "End"
        exit
    end

    player1 = options[player1_val-1]
    player2 = options[player2_val-1]

    if player1 == player2
        puts "Tie, next throw please"
        redo 
    end 

    if player1 == 1 and player2 == 2
        puts "Rock blunts scissors, you win"

    elsif player1 == 2 and player2 == 1
        puts "Rock blunts scissors, you loose"

    elsif player1 == 2 and player2 == 3
        puts "Scissors cut paper, you win"

    elsif player1 == 3 and player2 == 2
        puts "Scissors cut paper, you loose"

    elsif player1 == 3 and player2 == 1
        puts "Paper covers rock, you win"

    elsif player1 == 1 and player2 == 3
        puts "Paper covers rock, you loose"
    end  
end
2

There are 2 answers

1
David Hempy On

You've asked a very opinionated, open-ended question. This is not the best sort of question for Stack Overflow. But, I'll give it a shot.

Firstly, I'd extract your scoring code into a function. Something like this:

def battle(p1, p2)
  if p1 == p2
    'Tie!'
  elsif p1 == 'paper' && p2 = 'rock'
    'Paper smothers Rock...Player 1 wins!"
  elsif etc..
end 

This will let your main loop get very small...which is almost always a good thing. This will also make your life beautiful when you start writing unit tests for your code.

As for leaning out your end-of-game tests, use || to compare several things at once, such as player1_val == 9 || player2_val == 9

I suspect your code will not work as it is currently written. Try running this line-by-line in a debugger...or more simply, put puts "var is #{var}" in various places, dumping the values of interesting variables. Either approach will quickly expose any bugs and make the fix obvious.

As for doing things the Ruby way, consider using symbols instead of strings for your options array. Something like [:paper, :rock, :scissors]. Avoid magic numbers in your code. Any time you have a literal number in your code (other than 1 or 0, perhaps), chances are good you have a magic number and should work to make it a constant, symbol, or equation.

redo is not a commonly used Ruby command. (I had to go look it up to see what it does!) You've already got a loop...you should not need redo to pull off your mission.

Beyond good coding practices, we can also talk about UX/UI (user experience/user interface). This is a big one for this game...it's essential that players can't see each other's input. Do some research on getch. Also consider giving each player half the keyboard... "ASD" for one player's three choices, and "JKL" for the other player.

Those are a bunch of ideas that you might use to up your game! Good luck, and feel free to follow up after you've worked through it a bit.

4
cavin kwon On

You can judge the winner in the following ways:

choice1, choice2 = (gets.to_i - 1), (gets.to_i - 1)
winner = %w(none player1 player2)[choice2 - choice1 % 3]

Let's test

winner = proc { |choice1, choice2| %w(none player1 player2)[choice2 - choice1 % 3] }

cases = [[0, 0], [0, 1], [0, 2], [1, 2], [2, 0], [2, 1]]
cases.map(&winner) #=> ["none", "player1", "player2", "player1", "player1", "player2"]

In the same way, the output logic can be simplified.

HOW_TO_WIN = ['Rock blunts scissors',
              'Scissors cut paper',
              'Paper covers rock'].freeze

def player(no: num)
  # ...
  choice = (gets.to_i - 1)
  # ...
end

def play
  choice1, choice2 = [1, 2].map { |num| player(no: num) }
  winner = %w[none player1 player2][choice2 - choice1 % 3]
  how_to = HOW_TO_WIN[choice2 - choice1 % 3]

  if winner == 'none'
    puts 'Tie, next throw please'
  else
    puts "#{how_to}, #{winner} win"
  end
end

play