# frozen_string_literal: true require_relative 'player' require_relative 'game_writer' module Codebreaker class Game MIN = 1 MAX = 6 LENGTH = 4 TRIES = 10 HINTS = 3 attr_reader :tries_left, :hints_left private_constant :MIN, :MAX, :LENGTH, :TRIES, :HINTS def initialize(writer = GameWriter.new, player_class = Player) @writer = writer @player_class = player_class end def start @code = Array.new(LENGTH) { rand(MIN..MAX) }.join @result = '' @tries_left = TRIES @hints_left = HINTS @indexes_for_hint = (0...LENGTH).to_a end def check_guess(input) verify input @tries_left -= 1 @result = check_input(@code.chars, input.chars) end def hint return false if @hints_left.zero? @tries_left -= 1 @hints_left -= 1 generate_hint end def finished? @tries_left.zero? || won? end def won? @result == ('+' * LENGTH) end def answer @code if finished? end def save_score(name) @writer.write @player_class.new(name, @tries_left, @hints_left) end def high_scores @writer.load_scores end def as_json { result: nil, hint: nil, tries_left: @tries_left, hints_left: @hints_left, finished: finished?, won: won?, answer: answer } end def to_json(*options) as_json.to_json(*options) end private def check_input(code_chars, input_chars) num_of_pluses = LENGTH.times.count { |i| code_chars[i] == input_chars[i] } input_chars.each do |char| next unless code_chars.include? char code_chars.delete_at(code_chars.index(char)) end num_of_minuses = LENGTH - num_of_pluses - code_chars.size ('+' * num_of_pluses) + ('-' * num_of_minuses) end def generate_hint index = @indexes_for_hint.delete(@indexes_for_hint.sample) ('_' * LENGTH).tap { |hint| hint[index] = @code[index] } end def verify(input) regexp = /\A[#{MIN}-#{MAX}]{#{LENGTH}}\z/ msg = "Guesses must consist of #{LENGTH} digits from #{MIN} to #{MAX}" raise(ArgumentError, msg) if !input.is_a?(String) || !input.match?(regexp) end end end