master_mind 0.1.2

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,69 @@
1
+ module Mastermind
2
+ class SaveGame
3
+ attr_reader :datastore, :game_file, :saved_record
4
+ SAVE_GAME_FILE = 'game_file.yml'
5
+ def initialize(datastore: Datastore::YmlStore.instance, game_file: SAVE_GAME_FILE)
6
+ @datastore = datastore
7
+ @game_file = game_file
8
+
9
+ @datastore.filename=@game_file
10
+ load_records
11
+ end
12
+
13
+ def load_records
14
+ @save_record = fetch_all_records || []
15
+ @save_record.flatten!
16
+ @save_record
17
+ end
18
+
19
+ def save_record(player)
20
+ if player.is_a? Player
21
+ player = player.to_h
22
+ elsif player.is_a? Hash
23
+ player
24
+ else
25
+ raise ArgumentError, 'Invalid player'
26
+ end
27
+ @datastore.save(@game_file, player, 'a+')
28
+ load_records
29
+ end
30
+
31
+ def fetch_all_records
32
+ @save_record = @datastore.fetch_multiple_records @game_file
33
+ @save_record
34
+ end
35
+
36
+ def fetch_player(player_name)
37
+ if player_name.is_a? Integer
38
+ record = @save_record[player_name - 1]
39
+ elsif player_name.is_a? String
40
+ record = @save_record.select{ |record|
41
+ record[:name].downcase == player_name.downcase
42
+ }.first
43
+ end
44
+ return set_player_attr(record) if record
45
+ false
46
+ end
47
+
48
+ def set_player_attr(record)
49
+ player = Player.new
50
+ player.set_save_attr(record)
51
+
52
+ remove_data_from_save_record(record)
53
+ player
54
+ end
55
+
56
+ def remove_data_from_save_record(record)
57
+ @save_record.delete(record)
58
+ save
59
+ end
60
+
61
+ def save
62
+ @datastore.save(@game_file, @save_record)
63
+ end
64
+
65
+ def fetch_record(user_name)
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,194 @@
1
+ module Mastermind
2
+ class Game
3
+ include Helper
4
+ attr_reader :colors, :trial_count, :response, :character_count, :player, :top_ten_record
5
+ ALLOWED_TRIALS = 12
6
+ @@all_colors_hash = { 'R' => '(r)ed',
7
+ 'G' => '(g)reen',
8
+ 'B' => '(b)lue',
9
+ 'Y' => '(y)ellow'
10
+ }
11
+ @@color_array = @@all_colors_hash.keys # %w{ R G B Y }
12
+
13
+ def initialize(response, character_count = 4, top_ten_record: TopTen.new)
14
+ @response = response
15
+ @character_count = character_count
16
+ @top_ten_record = top_ten_record
17
+ end
18
+
19
+ def play
20
+ generate_colors
21
+ @trial_count = 0
22
+ get_player if @player.nil?
23
+ @response.start.message
24
+ @time_started = Time.now.to_i
25
+
26
+ game_process
27
+ end
28
+
29
+ def generate_colors
30
+ @colors = @@color_array.sample(@character_count)
31
+ shuffle_colors_hash
32
+ end
33
+
34
+ def shuffle_colors_hash
35
+ @color_values_from_all_colors_array = @colors.map{|color| @@all_colors_hash[color] }
36
+ @color_values_from_all_colors_array.shuffle!
37
+ end
38
+
39
+ def game_process
40
+ until @response.status == :won || @response.status == :lost || @trial_count >= ALLOWED_TRIALS
41
+ input = get_game_input
42
+ if actions.keys.include? input
43
+ method(actions[input]).call
44
+ break unless actions[input] =~ /instructions/
45
+ else
46
+ next if the_input_is_too_long_or_too_short?(input)
47
+ @trial_count += 1
48
+ analyzed = analyze_guess(input)
49
+ break if check_correct?(analyzed)
50
+ send_message(@response.analyzed_guess(analyzed[:match_position], analyzed[:almost_match]).message)
51
+ end
52
+ end
53
+ what_do_you_want_to_do_next
54
+ end
55
+
56
+ def analyze_guess(current_guess)
57
+ current_guess = current_guess.upcase.split('')
58
+ current_sprint = Hash.new(0)
59
+ current_guess.each_with_index{ |value, index|
60
+ if value == @colors[index]
61
+ current_sprint[:match_position] += 1
62
+ elsif @colors.include? value
63
+ # near matches should be a counter that
64
+ # counts the unique elements in the colors array
65
+ current_sprint[:almost_match] += 1
66
+ end
67
+ }
68
+ current_sprint
69
+ end
70
+
71
+ def get_player
72
+ @player ||= Player.new
73
+ player = Hash.new(nil)
74
+ while player[:name].nil? || player[:name].empty?
75
+ player_name = get_input(@response.player.message)
76
+ player[:name] = player_name
77
+ @player.set_attr player
78
+ end
79
+ end
80
+
81
+ def too_short?(input)
82
+ if input.length < @character_count
83
+ send_message(@response.shorter_input.message)
84
+ true
85
+ end
86
+ end
87
+
88
+ def too_long?(input)
89
+ if input.length > @character_count
90
+ send_message(@response.longer_input.message)
91
+ true
92
+ end
93
+ end
94
+
95
+ def the_input_is_too_long_or_too_short?(input)
96
+ true if too_long?(input) || too_short?(input)
97
+ end
98
+
99
+ def what_do_you_want_to_do_next
100
+ loser_play_again_or_quit if @response.status == :lost || @trial_count >= ALLOWED_TRIALS
101
+ winner_play_again_or_quit if @response.status == :won
102
+ end
103
+
104
+ def get_game_input
105
+ input = get_input(@response.trial_count(@trial_count, @colors.join, @color_values_from_all_colors_array).message)
106
+ input
107
+ end
108
+
109
+ def loser_play_again_or_quit
110
+ input = get_game_input
111
+ method(actions[input]).call if actions.keys.include? input
112
+ end
113
+
114
+ def winner_play_again_or_quit
115
+ if @response.status == :won
116
+ input = get_input(@response.message)
117
+ method(actions[input]).call if actions.keys.include? input
118
+ end
119
+ end
120
+
121
+ def check_correct?(analysis)
122
+ if analysis[:match_position] == @character_count
123
+ won(@time_started)
124
+ true
125
+ end
126
+ end
127
+
128
+ def actions
129
+ action_s = {
130
+ 'q' => 'quit_game',
131
+ 'quit' => 'quit_game',
132
+ 'i' => 'instructions',
133
+ 'instructions' => 'instructions',
134
+ 'c' => 'cheat',
135
+ 'cheat' => 'cheat'
136
+ }
137
+
138
+ supported_actions = {'p' => 'play', 'play' => 'play', 'top_players' => 'top_players', 't' => 'top_players'}
139
+ action_s = action_s.merge(supported_actions) if @trial_count >= ALLOWED_TRIALS || @response.status == :won
140
+
141
+ action_s
142
+ end
143
+
144
+ def instructions
145
+ send_message(@response.instructions(@color_values_from_all_colors_array).message)
146
+ @response.message
147
+ end
148
+
149
+ def quit_game
150
+ @trial_count = 0
151
+ @colors = []
152
+ send_message(@response.exit_game.message)
153
+ @response.message
154
+ end
155
+
156
+ def won(start_time)
157
+ @time_taken = Time.now.to_i - start_time
158
+ time = {}
159
+ time[:mins] = @time_taken/60
160
+ time[:secs] = @time_taken%60
161
+
162
+ save_if_top_ten
163
+
164
+ @response.won(@trial_count, time)
165
+ end
166
+
167
+ def lost
168
+
169
+ end
170
+
171
+ def save_if_top_ten
172
+ game_attr = {time_taken: @time_taken, guesses: @trial_count, date_played: Date.today}
173
+ @player.set_attr game_attr
174
+
175
+ @top_ten_record.add_record(@player)
176
+ end
177
+
178
+ def cheat
179
+ send_message(@response.cheat(@colors.join).message)
180
+ @response.message
181
+ end
182
+
183
+ def top_players
184
+ @top_ten_record.fetch_all.each{ |player|
185
+ send_message(player.winner_response)
186
+ }
187
+
188
+ game_process
189
+ end
190
+
191
+ private
192
+
193
+ end
194
+ end
@@ -0,0 +1,13 @@
1
+ module Mastermind
2
+ module Helper
3
+ def send_message(message)
4
+ puts message
5
+ end
6
+
7
+ def get_input(message)
8
+ send_message message
9
+ input = gets.chomp
10
+ input
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ module Mastermind
2
+ class Main
3
+ include Helper
4
+ attr_reader :game, :response
5
+
6
+ def initialize
7
+ @response = Message.new
8
+ end
9
+
10
+ def start
11
+ action = get_input(@response.start.message)
12
+ if supported_actions.keys.include? action
13
+ @game ||= Game.new(@response)
14
+ method(supported_actions[action]).call
15
+ else
16
+ send_message @response.unsupported_game_action.message
17
+ end
18
+ start if supported_actions[action] =~ /instructions|background/ || @response.status == :unsupported_action
19
+ end
20
+
21
+ private
22
+ def instructions
23
+ send_message(@response.gameplay_instructions.message)
24
+ end
25
+
26
+ def background
27
+ send_message(@response.main_message.message)
28
+ end
29
+
30
+ def play
31
+ @game.play
32
+ end
33
+
34
+ def quit_game
35
+ @game.quit_game
36
+ end
37
+
38
+ def supported_actions
39
+ {
40
+ 'p' => 'play',
41
+ 'play' => 'play',
42
+ 'q' => 'quit_game',
43
+ 'quit' => 'quit_game',
44
+ 'i' => 'instructions',
45
+ 'instructions' => 'instructions',
46
+ 'b' => 'background',
47
+ 'background' => 'background'
48
+ }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,117 @@
1
+ module Mastermind
2
+
3
+ class Message
4
+ attr_reader :message, :status
5
+
6
+ def initialize(input = nil)
7
+ if input
8
+ set_attr input
9
+ else
10
+ start
11
+ end
12
+ end
13
+
14
+ def set_attr(input)
15
+ @message = input[:message]
16
+ @status = input[:status]
17
+ self
18
+ end
19
+
20
+ def cheat(color)
21
+ set_attr(message: "Hmm! You just cheated. The colors generated were: #{color}.", status: :cheated)
22
+ end
23
+
24
+ def won(tries, time={})
25
+ set_attr(message: "#{'Congratulations!'.colorize(:green)}\nYou won the game in #{(tries.to_s + ' try'.pluralize(tries)).colorize(:blue)} and #{(time[:mins].to_s + 'm' + time[:secs].to_s + 's').colorize(:blue)}.\nDo you want to (p)lay again or (q)uit or (t)op_players?", status: :won)
26
+ end
27
+
28
+ def instructions(colors = ['(r)ed', '(g)reen', '(b)lue', '(y)ellow'])
29
+ color_count = colors.length
30
+ colors_generated_to_word = turn_array_to_string_list(colors, color_count)
31
+ example_gameplay = sample_instructions_arrangement(colors)
32
+ set_attr(message: "I have generated a beginner sequence with #{color_count.humanize + ' element'.pluralize(color_count)} made up of:\n#{colors_generated_to_word}. You are to guess the sequence in which these colors appeared e.g #{example_gameplay.colorize(:white)} for #{colors_generated_to_word}. You have #{Game::ALLOWED_TRIALS} guesses to get these colors or you lose the game. Use #{'(q)uit'.colorize(:red)} at any time to end the game.\nReady to play? \nWhat's your guess? ", status: :instructions)
33
+ end
34
+ # def instructions
35
+ # set_attr(message: "I have generated a beginner sequence with four elements made up of:\n#{'(r)ed'.colorize(:red)}, #{'(g)reen'.colorize(:green)}, #{'(b)lue'.colorize(:blue)}, and #{'(y)ellow'.colorize(:yellow)}. Use #{'(q)uit'.colorize(:red)} at any time to end the game.\nWhat's your guess? ", status: :instructions)
36
+ # end
37
+
38
+ def shorter_input
39
+ set_attr(message: "Your input is too short.".colorize(:red), status: :shorter_input)
40
+ end
41
+
42
+ def longer_input
43
+ set_attr(message: "Your input is too long.".colorize(:red), status: :longer_input)
44
+ end
45
+
46
+ def start
47
+ set_attr(message: "Welcome to MASTERMIND!\nWould you like to #{'(p)lay'.colorize(:green)}, read the #{'(i)nstructions'.colorize(:blue)}, read a little #{'(b)ackground'.colorize(:yellow)} on Mastermind or #{'(q)uit'.colorize(:red)}?", status: :main_start)
48
+ end
49
+
50
+ def exit_game
51
+ set_attr(message: "Thank you for playing Mastermind!\nGoodbye!".colorize(:red), status: :quitted)
52
+ end
53
+
54
+ def unsupported_game_action(message: nil, status: nil)
55
+ set_attr(
56
+ message: message || "You entered an unsupported action, try again! ".colorize(:red),
57
+ status: status || :unsupported_action
58
+ )
59
+ end
60
+
61
+ def wrong_guess
62
+ set_attr(message: "Your guess was wrong! Guess again: ".colorize(:red), status: :wrong)
63
+ end
64
+
65
+ def analyzed_guess(matched_position, included)
66
+ set_attr(message: "You had #{(matched_position.to_s + ' position'.pluralize(matched_position)).colorize(:green)} exactly matched and #{(included.to_s + ' near match'.pluralize(included)).colorize(:blue)}", status: :running)
67
+ end
68
+
69
+ def trial_count(trial_count, correct_sequence, colors = nil)
70
+ remaining_trials = Game::ALLOWED_TRIALS - trial_count
71
+ if trial_count == 0
72
+ instructions(colors)
73
+ elsif(trial_count < Game::ALLOWED_TRIALS)
74
+ set_attr(message: "You have tried #{trial_count.to_s + ' time'.pluralize(trial_count)}. You have #{remaining_trials.to_s + ' attempt'.pluralize(remaining_trials)} left.\nTry again: ", status: :wrong_guess)
75
+ else
76
+ set_attr(message: "You tried, but lost.\nThe colors generated were #{correct_sequence}.\nWant to try again? (p)lay to start again or (q)uit to exit or (t)op_players to view the top ten players. ".colorize(:red), status: :lost)
77
+ end
78
+ end
79
+
80
+ def player
81
+ set_attr(message: "So you would like to play!\nStart by telling me your name: ".colorize(:green), status: :player_name)
82
+ end
83
+
84
+ def gameplay_instructions(color_count = 4)
85
+ set_attr(message: "Enter a sequence of #{color_count} colors containing the generated colors e.g RYBG or YGRB.\nIf you enter fewer than #{color_count} or more than #{color_count} colors, you would receive an error message", status: :main_instructions)
86
+ end
87
+
88
+ def main_message
89
+ message = <<-EOS
90
+ #{%q{Just a little background on MASTERMIND}.colorize(:red)} Mastermind is a board game with an interesting history (or rather a legend?). Some game books report that it was invented in 1971 by Mordecai Meirowitz, an Israeli postmaster and telecommunications expert. After many rejections by leading toy companies, the rights were obtained by a small British firm, Invicta Plastics Ltd. The firm originally manufactured the game itself, though it has since licensed its manufacture to Hasbro in most of the world. However, Mastermind is just a clever readaptation of an old similar game called 'Bulls and cows' in English, and 'Numerello' in Italian... Actually, the old British game 'Bulls and cows' was somewhat different from the commercial version. It was played on paper, not on a board... Over 50 million copies later, Mastermind is still marketed today!
91
+ The idea of the game is for one player (the code-breaker) to guess the secret code chosen by the other player (the code-maker). The code is a sequence of 4 colored pegs chosen from six colors available. The code-breaker makes a serie of pattern guesses - after each guess the code-maker gives feedback in the form of 2 numbers, the number of pegs that are of the right color and in the correct position, and the number of pegs that are of the correct color but not in the correct position - these numbers are usually represented by small black and white pegs.
92
+ In 1977, the mathematician Donald Knuth demonstrated that the code-breaker can solve the pattern in five moves or less, using an algorithm that progressively reduced the number of possible patterns.
93
+ EOS
94
+ set_attr(message: message, status: :background_message)
95
+ end
96
+
97
+ def winner(winner, trials, time_taken)
98
+ set_attr(message: "#{winner} completed mastermind in #{trials} #{'guess'.pluralize(trials)} and #{time_taken}", status: :top_players)
99
+ end
100
+
101
+ private
102
+ def turn_array_to_string_list colors, array_count
103
+ if array_count >= 2
104
+ string_list = ''
105
+ string_list << colors[0..-2].map{|c| c.colorize(c.scan(/([[:alpha:]]+)/).join.to_sym)}.join(', ')
106
+ string_list << " and #{colors[-1].colorize(colors[-1].scan(/([[:alpha:]]+)/).join.to_sym)}"
107
+ string_list
108
+ else
109
+ colors.join
110
+ end
111
+ end
112
+
113
+ def sample_instructions_arrangement colors
114
+ colors.map{|c| c[1]}.join.upcase
115
+ end
116
+ end
117
+ end