diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..35f4d74 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--format Fuubar diff --git a/bin/sudoku-validator b/bin/sudoku-validator new file mode 100755 index 0000000..9e245f5 --- /dev/null +++ b/bin/sudoku-validator @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require './lib/sudoku_reader' +require './lib/sudoku_validator' + +reader = SudokuReader.new(ARGV.first) +validator = SudokuValidator.new(reader) + +puts validator.message diff --git a/invalid_block.sudoku b/invalid_block.sudoku new file mode 100644 index 0000000..8ed3bd8 --- /dev/null +++ b/invalid_block.sudoku @@ -0,0 +1,11 @@ +8 5 9 |6 1 2 |4 3 7 +7 2 3 |8 5 4 |1 6 9 +1 6 4 |3 7 9 |5 2 8 +------+------+------ +9 8 6 |1 4 7 |3 5 2 +3 7 . |2 6 8 |9 1 4 +2 4 1 |5 9 3 |7 8 6 +------+------+------ +4 3 5 |9 8 1 |6 7 . +6 1 7 |4 2 5 |8 9 3 +5 9 8 |7 3 6 |2 4 1 diff --git a/invalid_column.sudoku b/invalid_column.sudoku new file mode 100644 index 0000000..fb651a7 --- /dev/null +++ b/invalid_column.sudoku @@ -0,0 +1,11 @@ +8 5 9 |6 1 2 |4 3 7 +7 2 3 |8 5 4 |1 6 9 +1 6 4 |3 7 9 |5 2 8 +------+------+------ +9 8 6 |1 4 7 |3 5 2 +3 7 5 |8 6 2 |9 1 4 +2 4 1 |5 9 3 |7 8 6 +------+------+------ +4 3 2 |9 8 1 |6 7 5 +6 1 7 |4 2 5 |8 9 3 +5 9 8 |7 3 6 |2 4 1 diff --git a/invalid_line.sudoku b/invalid_line.sudoku new file mode 100644 index 0000000..6500ea7 --- /dev/null +++ b/invalid_line.sudoku @@ -0,0 +1,11 @@ +8 5 9 |6 5 2 |4 3 7 +7 2 3 |8 1 4 |1 6 9 +1 6 4 |3 7 9 |5 2 8 +------+------+------ +9 8 6 |1 4 7 |3 5 2 +3 7 5 |2 6 8 |9 1 4 +2 4 1 |5 9 3 |7 8 6 +------+------+------ +4 3 2 |9 8 1 |6 7 5 +6 1 7 |4 2 5 |8 9 3 +5 9 8 |7 3 6 |2 4 1 diff --git a/lib/sudoku_reader.rb b/lib/sudoku_reader.rb new file mode 100644 index 0000000..f2d980b --- /dev/null +++ b/lib/sudoku_reader.rb @@ -0,0 +1,91 @@ +class SudokuReader + attr_reader :path + + def initialize(path) + @path = path + @lines = [] + @blocks = [] + end + + def lines + return @lines if @lines.any? + File.read(path).each_line {|line| convert_line_to_values(line)} + @lines + end + + def each_line + lines.each_with_index {|line, index| yield(line, index)} + end + + def each_value_by_line + each_line {|line, index| line.each{|value| yield(line, value, index)}} + end + + def each_value_by_column + columns.each_with_index do |column, index| + column.each{|value| yield(column, value, index)} + end + end + + def each_value_by_block + blocks.each_with_index do |block, index| + block.each{|value| yield(block, value, index)} + end + end + + def columns + @columns ||= lines.transpose + end + + def blocks + return @blocks if @blocks.any? + init_empty_blocks + fill_blocks + @blocks + end + + private + def fill_blocks + each_block do |block_number| + line, column = block_start(block_number) + each_line_in_block {|k| get_line_of_block(line, column, k)} + end + end + + def block_start(block_number) + row = ((block_number) / 3) * 3 + col = ((block_number) % 3) * 3 + [row,col] + end + + def get_line_of_block(vertical_start, horizontal_start, line_block) + line = lines[horizontal_start + line_block] + line_in_block = line.drop(vertical_start).first(3) + index_of_block = vertical_start + horizontal_start/3 + @blocks[index_of_block] += line_in_block + end + + def each_block + 9.times {|block_number| yield(block_number)} + end + + def each_line_in_block + 3.times {|index_line_of_block| yield(index_line_of_block)} + end + + def init_empty_blocks + 9.times { @blocks << [] } + end + + def convert_line_to_values(line) + @lines << line.split.map { |value| sanitize(value) } unless separator?(line) + end + + def separator?(line) + line == "------+------+------\n" + end + + def sanitize(value) + value.gsub('|','').to_i + end +end diff --git a/lib/sudoku_validator.rb b/lib/sudoku_validator.rb new file mode 100644 index 0000000..cbb92b2 --- /dev/null +++ b/lib/sudoku_validator.rb @@ -0,0 +1,51 @@ +class SudokuValidator + attr_accessor :reader + + def initialize(reader) + @reader = reader + @errors = [] + end + + def complete? + reader.each_line {|line| return false if line.include?(0)} + return true + end + + def valid? + lines_valid? && columns_valid? && blocks_valid? + end + + def message + if valid? + "This sudoku is valid#{complete_message}." + else + "This sudoku is invalid." + @errors.join + end + end + + private + def complete_message + ", but incomplete" unless complete? + end + + def lines_valid? + check_validity_for(:line) + end + + def columns_valid? + check_validity_for(:column) + end + + def blocks_valid? + check_validity_for(:block) + end + + def check_validity_for(type) + reader.send("each_value_by_#{type}") do |row, value, index| + if row.count(value) > 1 && value != 0 + @errors << "\n - There is multiple #{value} in #{type} #{index + 1}." + end + end + @errors.empty? + end +end diff --git a/spec/sudoku_reader_spec.rb b/spec/sudoku_reader_spec.rb new file mode 100644 index 0000000..f0ec06d --- /dev/null +++ b/spec/sudoku_reader_spec.rb @@ -0,0 +1,39 @@ +require_relative '../lib/sudoku_reader' + +describe SudokuReader do + describe "#lines" do + it "give lines" do + reader = SudokuReader.new('valid_complete.sudoku') + expect(reader.lines[0]).to eq [8,5,9,6,1,2,4,3,7] + end + + it "convert invalid values by 0" do + reader = SudokuReader.new('valid_incomplete.sudoku') + expect(reader.lines[0]).to eq [8,5,0,0,0,2,4,0,0] + end + end + + describe "#columns" do + it "give columns" do + reader = SudokuReader.new('valid_complete.sudoku') + expect(reader.columns[0]).to eq [8,7,1,9,3,2,4,6,5] + end + + it "convert invalid values by 0" do + reader = SudokuReader.new('valid_incomplete.sudoku') + expect(reader.columns[0]).to eq [8,7,0,0,3,0,0,0,0] + end + end + + describe "#blocks" do + it "give blocks" do + reader = SudokuReader.new('valid_complete.sudoku') + expect(reader.blocks[0]).to eq [8,5,9,7,2,3,1,6,4] + end + + it "convert invalid values by 0" do + reader = SudokuReader.new('valid_incomplete.sudoku') + expect(reader.blocks[0]).to eq [8,5,0,7,2,0,0,0,4] + end + end +end diff --git a/spec/sudoku_validator_spec.rb b/spec/sudoku_validator_spec.rb new file mode 100644 index 0000000..2d8333a --- /dev/null +++ b/spec/sudoku_validator_spec.rb @@ -0,0 +1,81 @@ +require_relative '../lib/sudoku_validator' +require_relative '../lib/sudoku_reader.rb' + +describe SudokuValidator do + describe "#complete?" do + context "with complete sudokus" do + it "give true" do + ['valid_complete.sudoku', 'invalid_complete.sudoku'].each do |file| + expect(validator_for(file)).to be_complete + end + end + end + + context "with incomplete sudokus" do + it "give false" do + ['valid_incomplete.sudoku', 'invalid_incomplete.sudoku'].each do |file| + expect(validator_for(file)).to_not be_complete + end + end + end + end + + describe "#valid?" do + context "with valid sudokus" do + it "give true" do + ['valid_complete.sudoku', 'valid_incomplete.sudoku'].each do |file| + expect(validator_for(file)).to be_valid + end + end + end + + context "with invalid sudokus" do + it "give true" do + ['invalid_complete.sudoku', + 'invalid_incomplete.sudoku', + 'invalid_column.sudoku', + 'invalid_block.sudoku', + 'invalid_line.sudoku'].each do |file| + expect(validator_for(file)).to_not be_valid + end + end + end + end + + describe "#message" do + context "when the sudoku is valid" do + context "when the sudoku is complete" do + it "say than the sudoku is valid" do + file = 'valid_complete.sudoku' + expect(validator_for(file).message).to eq "This sudoku is valid." + end + end + + context "when the sudoku is incomplete" do + it "say than the sudoku is valid" do + file = 'valid_incomplete.sudoku' + expect(validator_for(file).message).to eq "This sudoku is valid, but incomplete." + end + end + end + + context "when the sudoku is invalid" do + it "say than the sudoku is invalid" do + ['invalid_complete.sudoku', 'invalid_incomplete.sudoku'].each do |file| + expect(validator_for(file).message).to match "This sudoku is invalid." + end + end + + context "when the sudoku is invalid du to lines" do + it "say than there is an error in line" do + file = 'invalid_line.sudoku' + expect(validator_for(file).message).to match "There is multiple 5 in line 1." + end + end + end + end + + def validator_for(file) + SudokuValidator.new(SudokuReader.new(file)) + end +end diff --git a/valid_complete.sudoku b/valid_complete.sudoku index ec5075c..0eab989 100644 --- a/valid_complete.sudoku +++ b/valid_complete.sudoku @@ -1,11 +1,11 @@ -8 5 9 |6 1 2 |4 3 7 -7 2 3 |8 5 4 |1 6 9 -1 6 4 |3 7 9 |5 2 8 +8 5 9 |6 1 2 |4 3 7 +7 2 3 |8 5 4 |1 6 9 +1 6 4 |3 7 9 |5 2 8 ------+------+------ -9 8 6 |1 4 7 |3 5 2 -3 7 5 |2 6 8 |9 1 4 -2 4 1 |5 9 3 |7 8 6 +9 8 6 |1 4 7 |3 5 2 +3 7 5 |2 6 8 |9 1 4 +2 4 1 |5 9 3 |7 8 6 ------+------+------ -4 3 2 |9 8 1 |6 7 5 -6 1 7 |4 2 5 |8 9 3 -5 9 8 |7 3 6 |2 4 1 +4 3 2 |9 8 1 |6 7 5 +6 1 7 |4 2 5 |8 9 3 +5 9 8 |7 3 6 |2 4 1