# frozen_string_literal: true module Codebreaker # Matching mechanism to determine the number # of bulls and cows in the guess. class Matcher attr_reader :marker, :secret_code_hash def initialize(marker = Markers::PlusMinusMarker.new) @marker = marker end def secret_code=(secret_code) @secret_code_hash = array_to_hash_of_positions(unify_code(secret_code)) end def match?(guess) match(array_to_hash_of_positions(unify_code(guess))) @bulls == 4 end def marks marker.mark(@bulls, @cows) end private def unify_code(code) code.is_a?(String) ? code.split('') : code end def array_to_hash_of_positions(array) array .each_with_object({}) .with_index do |(digit, hash), position| hash[digit] ||= [] hash[digit].push(position) hash end end def match(guess_hash) @exact_matches = 0 @all_matches = 0 guess_hash.each do |digit, positions| next if digit_is_not_present_in_the_secret_code(digit) add_exact_matches(digit, positions) add_all_matches(digit, positions) end @bulls = @exact_matches @cows = @all_matches - @exact_matches end def digit_is_not_present_in_the_secret_code(digit) secret_code_hash[digit].empty? end def add_exact_matches(digit, positions) @exact_matches += (secret_code_hash[digit] & positions).size end def add_all_matches(digit, positions) @all_matches += [secret_code_hash[digit], positions].min_by(&:size).size end end end