mastermind-cmdline 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require '../lib/mastermind'
4
+
5
+
6
+ game_text = Mastermind::GameText.new
7
+ terminal = Mastermind::Terminal.new
8
+
9
+ Mastermind::Game.new(game_text, terminal).play_game
@@ -0,0 +1,70 @@
1
+ require_relative './mastermind/GameRules'
2
+ require_relative './mastermind/TerminalInterface'
3
+ require_relative './mastermind/GameStatus'
4
+ require_relative './mastermind/AIsolver'
5
+
6
+ module Mastermind
7
+ class Game
8
+ def initialize(game_text, terminal)
9
+ @game_text = game_text
10
+ @terminal = terminal
11
+ @code_length = 4
12
+ end
13
+
14
+ def play_game
15
+ @terminal.display(@game_text.message(:welcome))
16
+ colors = get_color_scheme
17
+ code = get_input(colors)
18
+ set_up_board(code, colors)
19
+ result = run_ai_player(colors)
20
+ end_of_game(result)
21
+ end
22
+
23
+ private
24
+
25
+ def get_color_scheme
26
+ @terminal.display(@game_text.message(:color_scheme))
27
+ colors = @terminal.formatted_input
28
+ Colors.new(colors).valid_colors
29
+ end
30
+
31
+ def get_input(colors)
32
+ @terminal.display(@game_text.message(:prompt) )
33
+ input = @terminal.formatted_input
34
+ unless Validator.new(input, @code_length, colors ).valid?
35
+ get_input
36
+ else
37
+ input
38
+ end
39
+ end
40
+
41
+ def set_up_board(code, colors)
42
+ @board = GameStatus.new(code, colors)
43
+ end
44
+
45
+ def run_ai_player(colors)
46
+ algorithm = NaiveAlgorithm.new(colors)
47
+ player = AIPlayer.new(@board, algorithm)
48
+ result = player.solve
49
+ until @board.end_of_game?(result)
50
+ result = player.solve
51
+ print_guess(result)
52
+ end
53
+ print_guess(result)
54
+ result
55
+ end
56
+
57
+ def print_guess(result)
58
+ @terminal.display(@game_text.message(:guess, result))
59
+ end
60
+
61
+ def end_of_game(result)
62
+ if(result.correct?)
63
+ @terminal.display(@game_text.message(:win, result))
64
+ else
65
+ @terminal.display(@game_text.message(:lose, result))
66
+ end
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,36 @@
1
+ module Mastermind
2
+ class NaiveAlgorithm
3
+ attr_accessor :possible_guesses
4
+
5
+ def initialize(valid_letters)
6
+ @valid_letters = valid_letters
7
+ @possible_guesses = valid_letters.repeated_permutation(4).to_a
8
+ end
9
+
10
+ def next_guess
11
+ @possible_guesses[rand(@possible_guesses.size)]
12
+ end
13
+
14
+ def discard_invalid_guesses(incorrect_guess)
15
+ discarded_guesses = @possible_guesses.select do |guess|
16
+ compare_result = CodeComparer.new(incorrect_guess.guess, guess).compare
17
+ compare_result != incorrect_guess.result_hash
18
+ end
19
+ @possible_guesses -= discarded_guesses
20
+ end
21
+ end
22
+
23
+ class AIPlayer
24
+ def initialize(board, algorithm)
25
+ @board = board
26
+ @algorithm = algorithm
27
+ end
28
+
29
+ def solve
30
+ next_guess = @algorithm.next_guess
31
+ result = @board.process_guess(next_guess)
32
+ @algorithm.discard_invalid_guesses(result)
33
+ result
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,73 @@
1
+ module Mastermind
2
+ DEFAULT_COLORS = %w{R G O Y B P}
3
+ class CodeComparer
4
+ def initialize(code, guess)
5
+ @code = code
6
+ @guess = guess
7
+ end
8
+
9
+ def compare
10
+ correct_colors = total_correct_colors - correct_positions
11
+ {
12
+ correct_positions: correct_positions,
13
+ correct_colors: correct_colors
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ def correct_positions
20
+ code_with_indeces = @code.each_with_index.to_a
21
+ guess_with_indeces = @guess.each_with_index.to_a
22
+ intersection = code_with_indeces & guess_with_indeces
23
+ intersection.size
24
+ end
25
+
26
+ def total_correct_colors
27
+ correct_color = 0
28
+ @code.uniq.each do |letter|
29
+ correct_color += [@code.count(letter), @guess.count(letter)].min
30
+ end
31
+ correct_color
32
+ end
33
+ end
34
+
35
+ class Validator
36
+ def initialize(code, code_length, valid_letters)
37
+ @code = code
38
+ @code_length = code_length
39
+ @valid_letters = valid_letters
40
+ end
41
+
42
+
43
+ def valid?
44
+ correct_length? && valid_letters?
45
+ end
46
+
47
+ private
48
+
49
+ def correct_length?
50
+ @code.size == @code_length
51
+ end
52
+
53
+ def valid_letters?
54
+ @code.uniq.all? do |letter|
55
+ @valid_letters.include?letter
56
+ end
57
+ end
58
+ end
59
+
60
+ class Colors
61
+ def initialize(colors = [])
62
+ if(colors.empty? || colors.size != 6)
63
+ @colors = DEFAULT_COLORS
64
+ else
65
+ @colors = colors
66
+ end
67
+ end
68
+
69
+ def valid_colors
70
+ @colors
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,52 @@
1
+ module Mastermind
2
+ WIN_HASH = {correct_positions: 4, correct_colors:0}
3
+
4
+ class GameStatus
5
+ def initialize(code, colors)
6
+ @code = code
7
+ @num_of_tries = 0
8
+ @colors = colors
9
+ end
10
+
11
+ def valid_letters
12
+ @colors
13
+ end
14
+
15
+ def process_guess(guess)
16
+ @num_of_tries += 1
17
+ if win?(guess)
18
+ #Is it worth checking win separately here?
19
+ result_hash = WIN_HASH
20
+ else
21
+ result_hash = CodeComparer.new(@code, guess).compare
22
+ end
23
+ CurrentResult.new(guess, @num_of_tries, result_hash)
24
+ end
25
+
26
+ def end_of_game?(result)
27
+ win?(result.guess) || result.num_of_tries == 10
28
+ end
29
+
30
+
31
+ private
32
+
33
+ def win?(guess)
34
+ @code == guess
35
+ end
36
+ end
37
+
38
+ class CurrentResult
39
+ attr_reader :result_hash
40
+ attr_reader :num_of_tries, :guess
41
+
42
+ def initialize(guess, num_of_tries, result_hash)
43
+ @guess = guess
44
+ @num_of_tries = num_of_tries
45
+ @result_hash = result_hash
46
+ end
47
+
48
+ def correct?
49
+ @result_hash == WIN_HASH
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ module Mastermind
2
+ class Terminal
3
+ def display(message)
4
+ puts message
5
+ end
6
+
7
+ def formatted_input(stdin = $stdin)
8
+ stdin.gets.upcase.split(%r{\s*} )
9
+ end
10
+ end
11
+
12
+ class GameText
13
+ def message(message_type, game_result = [])
14
+ case message_type
15
+ when :welcome
16
+ "Welcome To Mastermind"
17
+ when :prompt
18
+ "Please Enter the Code: "
19
+ when :guess
20
+ "#{game_result.guess}"
21
+ when :win
22
+ if(game_result.num_of_tries == 1)
23
+ "Solved in #{game_result.num_of_tries} try!"
24
+ else
25
+ "Solved in #{game_result.num_of_tries} tries!"
26
+ end
27
+ when :lose
28
+ "Game over. Unable to solve in 10 turns."
29
+ when :color_scheme
30
+ "Enter a 6 letter color scheme or press Enter to use default"
31
+ else
32
+ "Come again?"
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,25 @@
1
+ module Mastermind
2
+ class CodeGenerator
3
+ def initialize(colors, code_length)
4
+ @colors = colors
5
+ @code_length = code_length
6
+ @terminal = Terminal.new
7
+ @game_text = GameText.new
8
+ end
9
+
10
+ def get_valid_code
11
+ input = get_input
12
+ unless Validator.new(input, @code_length, @colors ).valid?
13
+ get_valid_code
14
+ else
15
+ input
16
+ end
17
+ end
18
+
19
+ def get_input
20
+ @terminal.display(@game_text.message(:prompt) )
21
+ @terminal.formatted_input
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,256 @@
1
+ require 'rspec'
2
+ require_relative '../lib/mastermind'
3
+ require_relative '../lib/mastermind/AIsolver'
4
+ require_relative '../lib/mastermind/GameRules'
5
+ require_relative '../lib/mastermind/GameStatus'
6
+ require_relative '../lib/mastermind/TerminalInterface'
7
+
8
+ include Mastermind
9
+
10
+ DEFAULT_COLORS = %w{R G O Y B P}
11
+
12
+ describe 'Mastermind' do
13
+ WIN_HASH = {correct_positions: 4, correct_colors: 0}
14
+
15
+ let(:code){ %w{R R R R} }
16
+ let (:default_colors){ %w{R G O Y B P} }
17
+
18
+ describe 'GameStatus' do
19
+ let (:code){ %w{A B C D} }
20
+ let (:incorrect_guess){ %w{A C B E} }
21
+ let (:board){GameStatus.new(code, DEFAULT_COLORS)}
22
+
23
+ it 'stores the valid letters' do
24
+ expect(board.valid_letters).to eq(default_colors)
25
+ end
26
+
27
+ it 'returns a CurrentResult after tries a guess'
28
+
29
+ it 'returns an incorrect result when incorrect guess' do
30
+ expect(board.process_guess(incorrect_guess).correct?).to eq(false)
31
+ end
32
+
33
+ it 'returns a correct result when correct guess (win)' do
34
+ expect(board.process_guess(code).correct?).to eq(true)
35
+ end
36
+
37
+ it 'detects the end of game on a win' do
38
+ result = CurrentResult.new(code, nil, nil)
39
+ expect(board.end_of_game?(result)).to eq(true)
40
+ end
41
+
42
+ it 'detects the end of game on a loss' do
43
+ result = CurrentResult.new(nil, 10, nil)
44
+ expect(board.end_of_game?(result)).to eq(true)
45
+ end
46
+ end
47
+
48
+ describe 'CurrentResult' do
49
+ it 'returns correct when winning result hash' do
50
+ correct_hash = {correct_positions: 4 , correct_colors:0}
51
+ expect(CurrentResult.new(nil, nil, correct_hash).correct?).to eq(true)
52
+ end
53
+
54
+ it 'returns incorrect when incorrect guess' do
55
+ incorrect_hash = {correct_positions: 1 , correct_colors:0}
56
+ expect(CurrentResult.new(nil, nil, incorrect_hash).correct?).to eq(false)
57
+ end
58
+
59
+ it 'stores the hash, num_of_tries and guess'
60
+ end
61
+
62
+ describe 'CodeComparer' do
63
+ let (:code){ %w{A B C D} }
64
+ let (:incorrect_guess){ %w{A C B E} }
65
+ let (:code_comparer){CodeComparer.new(code,incorrect_guess)}
66
+
67
+ it 'returns a hash with number of positions/colors' do
68
+ result_hash = {correct_positions: 1, correct_colors:2}
69
+ expect(code_comparer.compare).to eq(result_hash)
70
+ end
71
+ end
72
+
73
+ describe 'Colors' do
74
+ let(:default_colors){%w{R G O Y B P}}
75
+ it 'returns default colors if incorrect size of input array' do
76
+ custom_colors = %w{Q Z F R}
77
+ expect(Colors.new(custom_colors).valid_colors).to match_array(default_colors)
78
+ end
79
+
80
+ it 'returns default colors if no arguments' do
81
+ expect(Colors.new.valid_colors).to match_array(default_colors)
82
+ end
83
+ end
84
+
85
+ describe 'NaiveAlgorithm' do
86
+ let (:naive_algorithm){NaiveAlgorithm.new(default_colors)}
87
+ before(:all) do
88
+ result_hash = {correct_positions: 0, correct_colors: 0}
89
+ guess = %w{R R R R}
90
+ @incorrect_guess = CurrentResult.new(guess, nil, result_hash )
91
+ end
92
+
93
+ it 'initializes possible guesses to all permutations' do
94
+ expect(naive_algorithm.possible_guesses.size).to eq(1296)
95
+ end
96
+
97
+ it 'chooses a valid next_guess' do
98
+ expect(naive_algorithm.next_guess).to be
99
+ end
100
+
101
+ it 'randomizes the next guess' do
102
+ first_guess = naive_algorithm.next_guess
103
+ second_guess = naive_algorithm.next_guess
104
+ expect(first_guess).not_to eq(second_guess)
105
+ end
106
+
107
+ it 'discards invalid guesses based on previous result' do
108
+ expect(naive_algorithm.discard_invalid_guesses(@incorrect_guess)).not_to include( %w{R R R R} )
109
+ end
110
+
111
+ it 'keeps guesses that are still valid' do
112
+ expect(naive_algorithm.discard_invalid_guesses(@incorrect_guess)).to include( %w{G G G G} )
113
+ end
114
+ end
115
+
116
+ describe 'AIPlayer' do
117
+ let(:algorithm){NaiveAlgorithm.new(DEFAULT_COLORS)}
118
+ let(:board){GameStatus.new(code, DEFAULT_COLORS)}
119
+ let(:ai_player){AIPlayer.new(board, algorithm)}
120
+ let(:solution){CurrentResult.new(code, 1, WIN_HASH )}
121
+
122
+
123
+ it 'calls process_guess once if wins on first try' do
124
+ allow(algorithm).to receive(:next_guess){ code }
125
+ expect(board).to receive(:process_guess).with(code).and_return(solution)
126
+ ai_player.solve
127
+ end
128
+ end
129
+
130
+ describe 'GameText' do
131
+ let(:game_text){GameText.new}
132
+
133
+ it 'displays welcome message' do
134
+ expect(game_text.message(:welcome)).to eq("Welcome To Mastermind")
135
+ end
136
+
137
+ it 'prompts user for input' do
138
+ expect(game_text.message(:prompt)).to eq("Please Enter the Code: ")
139
+ end
140
+
141
+ it 'displays winning message' do
142
+ code = %w{R R R R}
143
+ result = CurrentResult.new(code, 1, WIN_HASH)
144
+ expect(game_text.message(:win, result)).to eq("Solved in 1 try!")
145
+ end
146
+
147
+ it 'pluralizes winning message' do
148
+ code = %w{R R R R}
149
+ result = CurrentResult.new(code, 2, WIN_HASH)
150
+ expect(game_text.message(:win, result)).to eq("Solved in 2 tries!")
151
+ end
152
+
153
+ it 'displays losing message' do
154
+ expect(game_text.message(:lose)).to eq("Game over. Unable to solve in 10 turns.")
155
+ end
156
+
157
+ it 'prompts user for color scheme' do
158
+ expect(game_text.message(:color_scheme)).to eq("Enter a 6 letter color scheme or press Enter to use default")
159
+ end
160
+ end
161
+
162
+
163
+ describe 'Game' do
164
+ let(:game_text){GameText.new}
165
+ let(:terminal_obj){double('terminal_obj') }
166
+ let(:game){ Game.new(game_text, terminal_obj) }
167
+
168
+ #
169
+ # it 'returns input when valid' do
170
+ # allow(Terminal).to receive(:new){terminal_obj}
171
+ # expect(terminal_obj).to receive(:formatted_input).once.ordered.and_return(%w{R R R R} )
172
+ # expect(terminal_obj).to receive(:display)
173
+ #
174
+ # result = game.get_input
175
+ # expect(Validator.new(result).valid?).to eq(true)
176
+ # end
177
+ #
178
+ # it 'calls the function until valid' do
179
+ # allow(Terminal).to receive(:new){terminal_obj}
180
+ # expect(terminal_obj).to receive(:formatted_input).once.ordered.and_return(%w{Z Z Z Z} )
181
+ # expect(terminal_obj).to receive(:formatted_input).once.ordered.and_return(%w{R R R R} )
182
+ # expect(terminal_obj).to receive(:display)
183
+ # expect(terminal_obj).to receive(:display)
184
+ #
185
+ # result = game.get_input
186
+ # expect(Validator.new(result).valid?).to eq(true)
187
+ # end
188
+ #
189
+ # it 'gets a Solution from winning AIPlayer' do
190
+ # game.set_up_board(%w{R R R R})
191
+ # expect(game.run_ai_player.correct?).to eq(true)
192
+ # end
193
+ # it 'prints winning end message if win' do
194
+ # code = %w{R R R R}
195
+ # result = CurrentResult.new(code, 1, WIN_HASH)
196
+ # expect{game.end_of_game(result)}.to output("You win!\n").to_stdout
197
+ # end
198
+ #
199
+ # it 'prints losing end message if lose' do
200
+ # result = CurrentResult.new(nil, nil, nil)
201
+ # game.set_up_board(%w{R R R R})
202
+ # expect{game.end_of_game(result)}.to output("Haha! You lost!\n").to_stdout
203
+ # end
204
+
205
+ it 'runs through the game' do
206
+ allow(terminal_obj).to receive(:display){}
207
+ allow(game_text).to receive(:message)
208
+ expect(game).to receive(:get_input).and_return(%w{R O Y B} )
209
+ expect(game).to receive(:get_color_scheme).and_return(%w{ R G O Y B P})
210
+ game.play_game
211
+ end
212
+ end
213
+
214
+ describe 'Terminal' do
215
+ let(:terminal){Terminal.new}
216
+
217
+ it 'displays message' do
218
+ expect{terminal.display('test message')}.to output("test message\n").to_stdout
219
+ end
220
+
221
+ it 'returns array of input letters' do
222
+ uppercase_input = StringIO.new("ABCD\n")
223
+ expect(terminal.formatted_input(uppercase_input)).to eq( %w{A B C D} )
224
+ end
225
+
226
+ it 'converts input to uppercase' do
227
+ lowercase_input = StringIO.new("abcd\n")
228
+ expect(terminal.formatted_input(lowercase_input)).to eq(%w{A B C D})
229
+ end
230
+
231
+ it 'ignores whitespace' do
232
+ whitespace_input = StringIO.new("a b c d\n")
233
+ expect(terminal.formatted_input(whitespace_input)).to eq(%w{A B C D})
234
+ end
235
+ end
236
+
237
+
238
+ describe 'Validator' do
239
+ it 'accepts a correct code' do
240
+ correct_validator = Validator.new( %w{R G O Y}, 4, DEFAULT_COLORS)
241
+ expect(correct_validator.valid?).to eq(true)
242
+ end
243
+
244
+ it 'fails if code length is incorrect' do
245
+ wrong_length = Validator.new( %w{R R R R R R}, 4, DEFAULT_COLORS)
246
+ expect(wrong_length.valid?).to eq(false)
247
+ end
248
+
249
+
250
+
251
+ it 'fails if either letters or code length are incorrect' do
252
+ wrong_letters = Validator.new( %w{R G O Y}, 4, %w{Q W E R T Y})
253
+ expect(wrong_letters.valid?).to eq(false)
254
+ end
255
+ end
256
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mastermind-cmdline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Samantha Wojtowicz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-06-25 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Naive Mastermind AI
15
+ email:
16
+ executables:
17
+ - mastermind
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/mastermind.rb
22
+ - lib/mastermind/TerminalInterface.rb
23
+ - lib/mastermind/GameStatus.rb
24
+ - lib/mastermind/AIsolver.rb
25
+ - lib/mastermind/UserInterface.rb
26
+ - lib/mastermind/GameRules.rb
27
+ - test/mastermind_spec.rb
28
+ - bin/mastermind
29
+ homepage:
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.23
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: mastermind-cmdline
53
+ test_files: []