patternrubyMinor
Ruby command line Mastermind game with AI
Viewed 0 times
linewithmastermindgamerubycommand
Problem
I've created a small command-line 'Mastermind' game using Ruby. The intent of the project (aside from having fun in the build!) was to learn and emphasize OOP concepts and principles.
The game has some simple AI in place. If the computer is the player making guesses, when they get the correct color in the correct index position, that guess is locked in for subsequent turns. When they get the correct color but in an incorrect index position, that color is utilized in the next turn but placed in a different (and unoccupied) index position.
```
module Mastermind
class Player
attr_accessor :color_choices
def initialize(color_choices)
@color_choices = color_choices
end
end
class Game
attr_accessor :computer, :human, :color_choices
def initialize
welcome
end
def play_game
@available = [0,1,2,3]
@computer = Player.new(get_random_choice)
human_instructions
choose_message
@human = Player.new(get_human_choice)
compare_with_index
@@guess_iterations = 1
@@guesses_hash = Hash.new
guess_loop_human
end
def comp_guesses
@available = [0,1,2,3]
choose_message
@human = Player.new(get_human_choice)
@computer = Player.new(get_random_choice)
compare_with_index
@@guess_iterations = 1
@@guesses_hash = Hash.new
guess_loop_comp
end
def welcome
puts "Welcome to Mastermind."
puts "========================================================"
who_creates_code
end
def human_instructions
puts "You will be given 12 chances to guess the code that was chosen by the computer."
puts "========================================================"
puts "There are 6 colors from which to choose (Red, Blue, Green, Yellow, Orange, Purple)"
puts "========================================================"
end
def who_creates_code
puts "Would you like to choose the code and have the
The game has some simple AI in place. If the computer is the player making guesses, when they get the correct color in the correct index position, that guess is locked in for subsequent turns. When they get the correct color but in an incorrect index position, that color is utilized in the next turn but placed in a different (and unoccupied) index position.
```
module Mastermind
class Player
attr_accessor :color_choices
def initialize(color_choices)
@color_choices = color_choices
end
end
class Game
attr_accessor :computer, :human, :color_choices
def initialize
welcome
end
def play_game
@available = [0,1,2,3]
@computer = Player.new(get_random_choice)
human_instructions
choose_message
@human = Player.new(get_human_choice)
compare_with_index
@@guess_iterations = 1
@@guesses_hash = Hash.new
guess_loop_human
end
def comp_guesses
@available = [0,1,2,3]
choose_message
@human = Player.new(get_human_choice)
@computer = Player.new(get_random_choice)
compare_with_index
@@guess_iterations = 1
@@guesses_hash = Hash.new
guess_loop_comp
end
def welcome
puts "Welcome to Mastermind."
puts "========================================================"
who_creates_code
end
def human_instructions
puts "You will be given 12 chances to guess the code that was chosen by the computer."
puts "========================================================"
puts "There are 6 colors from which to choose (Red, Blue, Green, Yellow, Orange, Purple)"
puts "========================================================"
end
def who_creates_code
puts "Would you like to choose the code and have the
Solution
This is only a style issue, but it's first time I see Ruby code with empty lines at end of class body, and it seems confusing, as you are not separating other ends that way:
You have pairs of methods that are virtually identical, like
Your
Your Board is subclass of Game, you used that to share class variables between them. This doesn't make sense though, as a board obviously isn't a game. What makes even less sense is that instantiating this class draws a board.
Both of above problems could be solved by introducing actual Board class that keeps track of guesses, makes this data available to Game, and can present it.
Structure of your methods is somewhat off sometimes. Lets look here:
Just because this method happens to be called in single place, it doesn't mean it should call methods that are supposed to be called after that - instead, such function should return a value, and it's caller should determine what to do with it. This makes this function reusable, just in case you would want to ask more yes\no question (like
end
endYou have pairs of methods that are virtually identical, like
#guess_loop_human and #guess_loop_comp. Repeating yourself is bad, this would be solved better if you had two classes HumanPlayer and CPUPlayer, moved player-specific logic there and in Game class referred to your players as 'player_a' and 'player_b', or 'setter' and 'guesser'.module Player # could be a class
# common logic if any
end
class HumanPlayer # and CPUPlayer elsewhere
include Player
def get_choice
# possibly print messages here
end
end
def guess_loop # not guess_loop_human and guess_loop_comp
while @@guess_iterations <= 12 && !victory
store_guess
board
puts matches_message
guesser.get_choice # this works differently for HumanPlayer and CPUPlayer
compare_with_index
@@guess_iterations += 1
end
game_over
endYour
@guesses_hash is a Hash indexed by Arrays. This is pretty bad, as two identical Arrays will count as the same key - that will mess your logic if player makes the same guess twice.Your Board is subclass of Game, you used that to share class variables between them. This doesn't make sense though, as a board obviously isn't a game. What makes even less sense is that instantiating this class draws a board.
Both of above problems could be solved by introducing actual Board class that keeps track of guesses, makes this data available to Game, and can present it.
Structure of your methods is somewhat off sometimes. Lets look here:
def input_validation(response)
if response == "yes"
comp_guesses
elsif response == "no"
play_game
else
puts "Response not valid"
who_creates_code
end
endJust because this method happens to be called in single place, it doesn't mean it should call methods that are supposed to be called after that - instead, such function should return a value, and it's caller should determine what to do with it. This makes this function reusable, just in case you would want to ask more yes\no question (like
"Play again?(yes\no)", but also more readable - reader of your code wouldn't expect that input_validation means input_validation_and_than_play_the_game. For example you could do:if get_yes_or_no # old name was confusing IMO
comp_guesses
else
play_game
end#get_yes_or_no above is assumed repeat #gets in a loop until it gets valid answer, and I'd imagine it should also be the one calling chomp.upcase.Code Snippets
module Player # could be a class
# common logic if any
end
class HumanPlayer # and CPUPlayer elsewhere
include Player
def get_choice
# possibly print messages here
end
end
def guess_loop # not guess_loop_human and guess_loop_comp
while @@guess_iterations <= 12 && !victory
store_guess
board
puts matches_message
guesser.get_choice # this works differently for HumanPlayer and CPUPlayer
compare_with_index
@@guess_iterations += 1
end
game_over
enddef input_validation(response)
if response == "yes"
comp_guesses
elsif response == "no"
play_game
else
puts "Response not valid"
who_creates_code
end
endif get_yes_or_no # old name was confusing IMO
comp_guesses
else
play_game
endContext
StackExchange Code Review Q#105946, answer score: 3
Revisions (0)
No revisions yet.