mastermind-cmdline 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []