Using a Ruby while loop to access a class method - ruby

I'm creating a simple game of rock-paper-scissors that can be played in the console. The RPS game itself is stored in one class, and the console-player version is stored in another class. The RPS game is working and the console-player is working for one game, but when I try to loop through to allow the users to play 3 games I'm getting stuck. The users still aren't allowed to play more than three games, but the console no longer outputs the winner of the game.
Here's my code so far:
class Game
attr_accessor :status
def initialize(name1, name2)
#name1 = name1
#name2 = name2
#status = []
end
def play(str1, str2)
if #status.size == 3
total = #status.inject(0) { |i, total| total += i }
if total > 0
"Game over, Player 2 wins"
elsif total < 0
"Game over, Player 1 wins"
end
else
if (str1 == 'rock' && str2 == 'paper') || (str1 == 'scissors' && str2 == 'rock') || (str1 == 'paper' && str2 == 'scissors')
#status << 1
"Player 2 wins!"
elsif (str2 == 'rock' && str1 == 'paper') || (str2 == 'scissors' && str1 == 'rock') || (str2 == 'paper' && str1 == 'scissors')
#status << -1
"Player 1 wins!"
else
"No winner"
end
end
end
end
require 'io/console'
class RPSPlayer
def start
puts "Enter player 1 name"
#player1 = gets.chomp
puts "Enter player 2 name"
#player2 = gets.chomp
#new_game = Game.new(#player1, #player2)
puts "#{#player1} challenges #{#player2} to an R-P-S showdown."
player1_prompt = "#{#player1}: what's your move?"
player2_prompt = "#{#player2}: what's your move?"
while #new_game.status.size < 3
puts player1_prompt
str1 = gets.chomp
puts player2_prompt
str2 = gets.chomp
#new_game.play(str1, str2)
end
end
end
If I delete the while loop in the console game (just prompting the players for input and inputting into #new_game) I get the winner name, but when I'm in the while loop the console just prompts the players for their move 3 times, without giving any output. Can anyone tell me why that would be?

The method #play returns a string, it does not print it. To output the winner, you need to puts the result:
while #new_game.status.size < 3
puts player1_prompt
str1 = gets.chomp
puts player2_prompt
str2 = gets.chomp
puts #new_game.play(str1, str2)
end

Related

Keep Score method for tic tac toe is not updating score variables properly

I am currently adding a 'keep score' method to the below tic tac toe game. The method game_score works insofar as it increments after the game, but always resets back to 0.
I think the problem is something very simple, and there are similar questions already answered but I couldn't apply them to my code.
I would appreciate any help.
Thanks.
INITIAL_MARKER = ' '
PLAYER_MARKER = 'X'
COMPUTER_MARKER = 'O'
player_score = 0
computer_score = 0
def prompt(msg)
puts "=>#{msg}"
end
def display_board(brd)
system 'clear'
puts "You're a #{PLAYER_MARKER} Computer is #{COMPUTER_MARKER}"
puts ""
puts" | |"
puts" #{brd[1]} | #{brd[2]} | #{brd[3]}"
puts" | |"
puts"-----+-----+-----"
puts" | |"
puts" #{brd[4]} | #{brd[5]} | #{brd[6]}"
puts" | |"
puts"-----+-----+-----"
puts" | |"
puts" #{brd[7]} | #{brd[8]} | #{brd[9]}"
puts" | |"
end
def initialize_board
new_board = {}
(1..9).each {|num| new_board[num] = INITIAL_MARKER}
new_board
end
def empty_squares(brd)
brd.keys.select{|num| brd[num] == INITIAL_MARKER}
end
def player_places_piece!(brd)
square = INITIAL_MARKER
loop do
prompt ("Choose a square (#{empty_squares(brd).join(',')})")
square = gets.chomp.to_i
break if empty_squares(brd).include?(square)
prompt "Sorry, that's not a valid choice."
end
brd[square] = PLAYER_MARKER
end
def computer_places_piece(brd)
square = empty_squares(brd).sample
brd[square] = COMPUTER_MARKER
end
def board_full(brd)
empty_squares(brd).empty?
end
def someone_won?(brd)
!!detect_winner(brd)
end
def detect_winner(brd)
#computer_score =
winning_lines = [[1,2,3],[4,5,6],[7,8,9]] +
[[1,4,7],[2,5,8],[3,6,9]] +
[[1,5,9],[3,5,7]]
winning_lines.each do |line|
if brd[line[0]] == PLAYER_MARKER &&
brd[line[1]] == PLAYER_MARKER &&
brd[line[2]] == PLAYER_MARKER
return 'Player'
player_score += 1
elsif
brd[line[0]] == COMPUTER_MARKER &&
brd[line[1]] == COMPUTER_MARKER &&
brd[line[2]] == COMPUTER_MARKER
return 'Computer'
computer_score += 1
else
end
end
nil
end
def game_score (player_score, computer_score, board)
player_score += 1 if detect_winner(board) == 'Player'
computer_score += 1 if detect_winner(board) == 'Computer'
prompt("Player Score is: #{player_score}")
prompt("Computer Score is: #{computer_score}")
end
loop do
board = initialize_board
loop do
display_board(board)
player_places_piece!(board)
break if someone_won?(board) || board_full(board)
display_board(board)
computer_places_piece(board)
display_board(board)
break if someone_won?(board) || board_full(board)
end
if someone_won?(board)
prompt "#{detect_winner(board)} won!"
#player_score += 1 if detect_winner(board)== 'Player'
#computer_score += 1 if detect_winner(board)== 'Computer'
#display("Player Score: #{player_score}")
#display("Computer Score: #{computer_score}")
else
prompt "It's a tie!"
end
game_score(player_score, computer_score, board)
binding pry
prompt("would you like to play again?")
input = gets.chomp
break unless input == 'y'
end
prompt("bye")
When you pass in player_score and computer_score into the game_score method, then you get new variables that are scoped to that method. They are not the same as your global variables that you declare at the top.
May I suggest that you try to write some classes that encapsulates the proper abstractions (like game, board, score etc). I think that would make it easier to figure out how you should save your state.

Why am I always getting a rock response from p2, p3 and p4?

Hi I'm just learning ruby and decided to try and eloborate on codewars Kata to create quick rock, paper, scissors game. At first I was getting random responses but now it's alway rock. Please help and sorry if this is ridiculously easy.
module Promtable
def prompt(message = "What would you like to do? ", symbol = ":> ")
print message
print symbol
var = gets.chomp.to_s
if !var.match /rock|paper|scissors/
puts "I'm sorry that is not a valid response please try again"
print message
print symbol
gets.chomp
else
var
end
end
end
def rps(p1, p2)
if #p1 == 'rock' && #p2 == 'scissors'
puts "Player 1 won!"
elsif
#p1 == 'paper' && #p2 == 'rock'
puts "Player 1 won!"
elsif
#p1 == 'scissors' && #p2 == 'paper'
puts "Player 1 won!"
elsif
#p1 == 'rock' && #p2 == 'paper'
puts "Computer won!"
elsif
#p1 == 'scissors' && #p2 == 'rock'
puts "Computer won!"
elsif
#p1 == 'paper' && #p2 == 'scissors'
puts "Computer won!"
else
puts "It's a draw!"
end
end
def game
include Promtable
p1 = prompt("Player 1 plays...?")
choices = ["rock", "paper", "scissors"]
p2 = choices[rand]
p3 = choices[rand]
p4 = choices[rand]
rps(p1, p2)
puts "The computer chose #{p2}!"
p1 = prompt("What's your second choice...? ")
p3 = choices[rand]
rps(p1, p3)
puts "The computer chose #{p3}!"
p1 = prompt("What's your final choice...? ")
p4 = choices[rand]
rps(p1, p4)
puts "The computer chose #{p4}!"
end
puts "Hi welcome to Rock, Paper, Scissors"
game
puts "Would you like to play again? (Y/N)"
var = gets.chomp.downcase
if var.match /y/
game
end
I see two issues. One which has already been pointed out about the default return value of rand. I recommend using choices.sample. The other issue is in rps: note that p1 and #p1 are different variables. There's no need to use instance variables in rps; I'd change them all to local p1 and p2.
Now, some code review style comments:
It's cool that you are experimenting with mixins with the module Promtable (which is missing a 'p' by the way). Of course you have some RPS logic embedded in it, so it isn't really reusable. Maybe you can make it more generic by adding a way to pass in an array of valid values. Speaking of valid values, you are checking them with a regular expression (/rock|paper|scissors/) which just checks a substring. If I were to type "rocky" it would pass through and be treated as a draw. You may want to use
if !valid_values.include? var or unless valid_values.include? var instead of the regex.
The rps method can be simplified in a number of ways. Cary Swoveland had a clever idea in the comments of using a hash to look up the winning combinations. Another way to simplify would be to use a case statement:
case [p1,p2]
when ['rock', 'scissors'], ['paper', 'rock'], ['scissors', 'paper']
puts "Player 1 won!"
when ['scissors', 'rock'], ['rock', 'paper'], ['paper', 'scissors']
puts "Computer won!"
else
puts "It's a draw!"
end
rand returns a number between 0 and 1. Essentially, choices[rand] always becomes choices[0].
You may want to use choices[rand(0..2)] instead.
Alternatively, you could use choices.sample.
Check the docs for rand here.

How do I continue tic-tac-toe game from saved yaml file?

I have the following tic-tac-toe game: (I'm a noob, please disregard the design of the class, the game works, that's all I care about for now.)
#a tic tac toe game
class TicTacToe
require "yaml"
attr_accessor :player1, :player2
#crates playes and a game board to play tic tac toe
def initialize()
#player1 = Player.new("Player One", "x")
#player2 = Player.new("Player Two", "o")
#game_board = Board.new
end
#prints the board
def print_board
#game_board.board.each_with_index do |row, index|
puts "#{row.join(" | ")}"
puts "---------" unless index == 2
end
puts
end
#determines whose move it is
def move
if #turn % 2 == 1
player_one_turn
else
player_two_turn
end
#turn += 1
end
def valid_move?(row, col)
if #game_board.board[row][col] == " "
return true
else
return false
end
end
#player ones turn
def player_one_turn
print_board
puts "#{#player1.name} it's your turn:"
puts "Enter a row (0-2)"
row = gets.chomp.to_i
puts "Enter a column (0-2)"
col = gets.chomp.to_i
if valid_move?(row, col)
#game_board.board[row][col] = #player1.shape
else
puts "There's already a shape at that position."
player_one_turn
end
if win?(#player1.shape)
winner(#player1.name)
#winner = true
end
end
#player two's turn
def player_two_turn
print_board
puts "#{#player2.name} it's your turn:"
puts "Enter a row (0-2)"
row = gets.chomp.to_i
puts "Enter a column (0-2)"
col = gets.chomp.to_i
if valid_move?(row, col)
#game_board.board[row][col] = #player2.shape
else
puts "There's already a shape at that position."
player_two_turn
end
if win?(#player2.shape)
winner(#player2.name)
#winner = true
end
end
def win?(shape)
if (#game_board.board[0][0] == shape) && (#game_board.board[0][1] == shape) && (#game_board.board[0][2] == shape)
return true
elsif (#game_board.board[1][0] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[1][2] == shape)
return true
elsif (#game_board.board[2][0] == shape) && (#game_board.board[2][1] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][0] == shape) && (#game_board.board[1][0] == shape) && (#game_board.board[2][0] == shape)
return true
elsif (#game_board.board[0][1] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][1] == shape)
return true
elsif (#game_board.board[0][2] == shape) && (#game_board.board[1][2] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][0] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][2] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][0] == shape)
return true
else
return false
end
end
def draw?
if #turn > 9
print_board
puts "The game ended in a draw. :)"
#winner = true
return true
end
false
end
def winner(winner_name)
puts "#{winner_name}, YOU WIN!!!"
end
def play
#turn = 1
#winner = false
until #winner
move unless draw?
save
end
end
#a class that generates an empty board
class Board
attr_accessor :board
def initialize
#board = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
end
end
#a class that assigns creates plaers and assigns them a shape "x" or "o"
class Player
attr_accessor :name, :shape
def initialize(name, shape)
#name = name
#shape = shape
end
end
def save
yaml = YAML::dump(self)
File.open("save.txt", "w"){|file| file.write(yaml)}
end
def self.load
file = File.read("save.txt")
YAML::load_file(file)
end
end
game = TicTacToe.new
game.play
I want to start playing the game, quit the program in the middle of the game and then come back and finish it later after I call TicTacToe.load. However, when I do this now, the YAML file is loaded, but program does not resume where it's supposed to.
Can someone tell me if there is a way to do what I'm trying to do?
I regionally thought that doing something like YAML::load(self) would automatically load the save state of the file I was referring to via some kind of magic. However, I have come to learn that that the way that I designed my class and the dependencies in my "play" function would not allow me to load the previous state if my file.
When loading a YAML file, one has to load the file to a variable and then manually assign the object values to values of the class. That way, the current state of the variables are pretty much being manually assigned to the instance variables of the class. For example, I could have done something like this: file = YAML.load("file_name"), then assign variables values like: #board = file.board.
Had I known this before, I would have designed my class with less dependencies so that it would be loadable in a much cleaner and more convenient way.

How do I make my tic-tac-toe program not skip a players turn?

I wrote a tic-tac-toe program. The problem is that when a user enters an invalid coordinate, the user is notified however, it skips his turn. For example: if player "x" enters valid coordinates, and player "o" enters valid coordinates, player "o" is notified but his/her turn gets skipped and player "x" goes again. How do I fix this so that player "o" gets another chance to enter in valid coordinates?
Here is my code:
class Game
def initialize
#board=Array.new
#board[1]="1 __|"
#board[2]="__"
#board[3]="|__"
#board[4]="\n2 __|"
#board[5]="__"
#board[6]="|__"
#board[7]="\n3 |"
#board[8]=" "
#board[9]="| "
#turn="o"
#win_status = false
end
def turn
#turn
end
def show_board
puts " 1 2 3"
#board.each do |i|
print i
end
puts ""
end
def set_turn #switches turns
if #turn == "x"
#turn = "o"
else #turn == "o"
#turn = "x"
end
end
def make_move
puts "Enter x coordinate"
x=gets.to_i
puts "Enter y coordinate"
y=gets.to_i
if y==1 && x==1
#board[1]="1 _"+#turn+"|"
elsif y==2 && x==1
#board[2]="_"+#turn
elsif y==3 && x==1
#board[3]="|_"+#turn
elsif y==1 && x==2
#board[4]="\n2 _"+#turn+"|"
elsif y==2 && x==2
#board[5]="_"+#turn
elsif y==3 && x==2
#board[6]="|_"+#turn
elsif y==1 && x==3
#board[7]="\n3 "+#turn+"|"
elsif y==2 && x==3
#board[8]=" "+#turn
elsif y==3 && x==3
#board[9]="| "+#turn+" \n"
else
puts "you entered an invalid coordinate"
end
end
def win_combo
return [[#board[1][4] + #board[2][1] + #board[3][2]], [#board[4][5] + #board[5][1] + #board[6][2]], [#board[7][5] + #board[8][1] + #board[9][2]],[#board[1][4] + #board[4][5] + #board[7][5]], [#board[2][1] + #board[5][1] + #board[8][1]], [#board[3][2] + #board[6][2] + #board[9][2]], [#board[1][4] + #board[5][1] + #board[9][2]], [#board[3][2] + #board[5][1] + #board[7][5]]]
end
def check_win
#if some row or column or diagonal is "xxx" or "ooo" then set #win_status = true
self.win_combo.each do |arr|
str = arr.join
if str == "xxx"
puts "X Wins!"
return true
elsif str == "ooo"
puts "O Wins!"
return true
end
end
return false
end
g = Game.new
while g.check_win != true
g.show_board
g.set_turn
g.make_move
end
end
You could call make_move again to prompt for the user to enter new coordinates.
puts "you entered an invalid coordinate"
make_move
You could call make_move from within your else clause once you know that a player
has entered an invalid move.
else
puts "you entered an invalid coordinate"
make_move
end
Look at the condition in make_move that is triggered when the user enters an invalid coordinate. After that code is triggered, make_move's execution is over, so it steps out into your g.check_win loop.
Think about your functions as pieces that can be reused. make_move is called whenever you want the current user to make a move. How can you execute this functionality again, after make_move tells the user their input was invalid?
I'll give you a hint: functions can call themselves.

Ruby if/elsif/else

I am attempting to write a game. In the following code, it keeps skipping to the bottom else even if a valid integer is entered. Why?
puts 'You will be O\'s and I will be X\'s'
puts
puts '1,2,X'
puts '4,5,6'
puts '7,8,9'
puts
puts 'Your move...'
puts
moveOne = gets.chomp
if moveOne == 5
puts = '1,2,X'
puts = '4,O,6'
puts = 'X,8,9'
elsif moveOne == 1
puts = 'O,2,X'
puts = '4,5,6'
puts = 'X,8,9'
elsif moveOne == 7
puts = 'X,2,X'
puts = '4,5,6'
puts = 'O,8,9'
elsif moveOne == 9
puts = 'X,2,X'
puts = '4,5,6'
puts = '7,8,O'
elsif moveOne == 2
puts = '1,O,X'
puts = '4,X,6'
puts = '7,8,9'
elsif moveOne == 4
puts = '1,2,X'
puts = 'O,X,6'
puts = '7,8,9'
elsif moveOne == 6
puts = '1,2,X'
puts = '4,X,O'
puts = '7,8,9'
elsif moveOne == 8
puts = '1,2,X'
puts = '4,X,6'
puts = '7,O,9'
else
puts'please enter a number!'
end
puts
puts 'Your move again'
Because chomp is giving you a string, not an integer.
moveOne = gets.chomp.to_i
This is happening because gets.chomp returns a String, not an Integer. if you do:
"a".to_i --> 0 , Which your program might think the user has actually entered a 0.
So, first, you want to make sure that what the user has entered is a number character, even though it's of class String.
Here is what you could do:
1 - Create a method that will check if a String is number-like:
def is_a_number?(s)
s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
end
2 - If it is number like, just cast it to integer using .to_i
So, your code would look like:
moveOne = gets.chomp
if is_a_number?(moveOne)
number_entered = moveOne.to_i
if number_entered == 5
...
elsif number_entered == 1
...
else
puts "enter a number..."
end

Resources