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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/mastermind +8 -0
- data/bin/setup +7 -0
- data/circle.yml +6 -0
- data/game_file.yml +1 -0
- data/lib/mastermind.rb +24 -0
- data/lib/mastermind/datastore/ymlstore.rb +63 -0
- data/lib/mastermind/extensions/continue_later.rb +102 -0
- data/lib/mastermind/extensions/extensions.rb +95 -0
- data/lib/mastermind/extensions/savegame.rb +69 -0
- data/lib/mastermind/game.rb +194 -0
- data/lib/mastermind/helper.rb +13 -0
- data/lib/mastermind/main.rb +51 -0
- data/lib/mastermind/message.rb +117 -0
- data/lib/mastermind/player.rb +40 -0
- data/lib/mastermind/top_ten.rb +66 -0
- data/lib/mastermind/version.rb +3 -0
- data/mastermind.gemspec +35 -0
- data/topten.yaml +41 -0
- data/topten1.yaml +41 -0
- metadata +243 -0
@@ -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,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
|