Skip to content
This repository was archived by the owner on Jun 8, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
19af7a1
Initial configuration / setup...
JESii Oct 12, 2013
e6974eb
SudokuBoard returns a single element...
JESii Oct 12, 2013
741dee4
Create Sudoku board class & test...
JESii Oct 13, 2013
fbe316d
Added sub-grid retrieval by number position
JESii Oct 13, 2013
26eb987
Added tests for incomplete games
JESii Oct 13, 2013
72cb7a4
Minor example code re-oganization for clarity
JESii Oct 13, 2013
2bfb0d6
Specs recognize the CLI program with dummy output
JESii Oct 13, 2013
aa56284
Validator correctly recognizes complete/incomplete games...
JESii Oct 13, 2013
fea34ae
Validator correctly recognizes complete games with proper completion …
JESii Oct 13, 2013
b52f595
Minor refactoring to extract check_game_validity method
JESii Oct 13, 2013
9ef05da
Validator correctly identifies status and validity...
JESii Oct 14, 2013
91dd600
First specs for error reports working...
JESii Oct 14, 2013
6b45e1b
Refactor: rename report_errors to identify_errors
JESii Oct 14, 2013
13f5728
Refactor validity-checking to remove duplication...
JESii Oct 14, 2013
3cc1a0f
Refactor: clean up report_errors usage
JESii Oct 14, 2013
2365921
Complete refactoring...
JESii Oct 14, 2013
5371e29
Minor refactoring...
JESii Oct 14, 2013
e5625b0
Added error output...
JESii Oct 14, 2013
6d7bca3
Properly report errors for incomplete sudoku...
JESii Oct 14, 2013
6b0b03e
Refactor spec/examples...
JESii Oct 17, 2013
d9c66c1
Rename 'analyze' => 'validate' method
JESii Oct 17, 2013
dd8815e
Refactoring (part 1)...
JESii Oct 17, 2013
209c60e
Refactoring: use symbols in call valid_element() method
JESii Oct 17, 2013
5abb52e
Refactoring for board access...
JESii Oct 17, 2013
e51d13e
Remove commented-out lines.
JESii Oct 18, 2013
9207dd6
Clean up output code
JESii Oct 19, 2013
f4448ea
Refactor/extract method...
JESii Oct 19, 2013
6a6957d
Refactor SudokuBoard examples...
JESii Oct 22, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sw[op]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this file is, but it's possible to configure a global gitignore so that you don't need to do this for each repo you work on: https://help.github.com/articles/ignoring-files#global-gitignore

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @andyw8; what's very interesting is that I already have a ~/.gitignore_global file and *.swp is there and a .swp file still, somehow got included. I'll have to do some digging to see why that is. Oh, and they're vim swap files.

66 changes: 66 additions & 0 deletions lib/sudoku_board.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
class SudokuBoard
attr_reader :board

def initialize(game_file)
@board = []
File.readlines(game_file).each do |line|
row = convert_line_to_row(line)
@board << row unless row.nil?
end
end

def [] (row, col)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why name a method "[]" unless you have to? i think it will unnecessarily confuse callers

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, @elubin, this was for me a rather natural way to access a single element on the board and same for updating an element (used in the rspec examples) with the []= method. It's visually analogous to array accession.

@board[row-1][col-1]
end

def []= (row, col, value)
@board[row-1][col-1] = value
end

def row(row)
@board[row-1]
end

def col(col)
column = []
@board.each { |row| column << row[col-1] }
column
end

def sub_grid(number)
# SubGrids are numbered 1-9 like this:
# 1,2,3
# 4,5,6
# 7,8.9
row, col = sub_grid_start(number)
s_grid = []
3.times do
s_grid += sub_grid_row(row,col)
row += 1
end
s_grid
end

def sub_grid_row(row,col)
[self[row,col],self[row,col+1],self[row,col+2]]
end

def convert_line_to_row(line)
line.chomp!.delete!('-+| ')
return nil if line.empty?
line.tr!('.','0')
row = []
line.each_char.map { |c| row << c.to_i }
row
end

def sub_grid_start(sub_grid)
row = ((sub_grid-1)/3)*3 + 1
col = ((sub_grid-1)%3)*3 + 1
[row,col]
end

def missing_data?
!(self.board.flatten.index( 0 )).nil?
end
end
72 changes: 72 additions & 0 deletions lib/sudoku_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require_relative './sudoku_board'

class SudokuValidator
attr_reader :board, :errors

def initialize(game_file)
@game_file = game_file
@board = SudokuBoard.new @game_file
@errors = []
end

def validate
status = check_game_status
validity = check_game_validity
[validity, status]
end

def check_game_status
@board.missing_data? ? 'incomplete' : 'complete'
end

def check_game_validity
rows_valid = check_row_validity
cols_valid = check_col_validity
subgrids_valid = check_subgrid_validity
(rows_valid && cols_valid && subgrids_valid) ? 'valid' : 'invalid'
end

def check_row_validity
valid_element?(:row)
end

def check_col_validity
valid_element?(:col)
end

def check_subgrid_validity
valid_element?(:sub_grid)
end

def valid_element?(element)
item_valid = true
(1..9).each do |item|
valid, error = valid?(@board.__send__(element,item))
item_valid &&= valid
report_errors(error, element, item)
end
item_valid
end

def valid?(ary)
tmp = ary.map { |e| e if e !=0}.compact
valid = tmp.uniq.size == tmp.size
error = []
error = identify_errors(tmp) if !valid
[valid,error]
end

def identify_errors (ary)
tmp = ary.clone
error = []
while tmp.size > 0 do
item = tmp.shift
error << item if tmp.include? item
end
error
end

def report_errors (ary,where,item)
ary.each { |e| @errors << "#{e} is repeated in #{where} #{item}" }
end
end
64 changes: 64 additions & 0 deletions spec/sudoku_board_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require_relative '../lib/sudoku_board'

describe SudokuBoard do
before do
@game_vc = SudokuBoard.new("valid_complete.sudoku")
@game_ii = SudokuBoard.new("invalid_incomplete.sudoku")
end

describe "#[]" do
it "returns the first character of the sudoku file" do
expect(@game_vc[1,1]).to eql 8
end

it "returns an arbitrary element from the board" do
expect(@game_vc[4,5]).to eql 4
end

it "returns zero for an empty position" do
expect(@game_ii[2,4]).to eql 0
end
end

describe "#row" do
it "returns the first row of the sudoku file" do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about creating own describe block for each method? Something like this:

describe SudokuBoard do
  describe '#row' do
    ...
  end
end

I think that it improves readability of tests and you can group tests for each method together.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mrhead... I wasn't sure at first if I thought this was a good idea: I like the idea of a describe block to separate complete/incomplete board examples - that's the way I started my development. However, after some reflection I think your suggested refactoring makes sense... while not immediately necessary, in the long run it will more clearly isolate the individual elements being tested. I'll work on that fairly soon.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK; done. The only thing I'm not sure I like about this is the before block which now loads the valid_complete file and the invalid_incomplete file to support the different tests, creating @game_vc & @game_ii instances. I don't think that creating additional describe blocks makes sense -- lots more code and repeated before blocks.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've stopped using before and let some time ago. Purely because of this: https://github.com/thoughtbot/guides/tree/master/best-practices#testing

From beginning I did not know how to replace it, but now I create my own methods to prepare objects for me. So unlike in before block I call them just when I need them. And unlike the let, I can pass arguments to it... So in your case you can do something like:

describe SudokuBoard do
  ... your tests ...

  def game_vc
    SudokuBoard.new("valid_complete.sudoku")
  end

  def game_ii
    SudokuBoard.new("invalid_incomplete.sudoku")
  end
end

And then you just call `game_vc` instead of `@game_vc` which doesn't looks right to me.

Anyway this is just something what I made up from different codes and probably also thoughtbot TDD workshop (I really do not remember).

And I would also like to see different opinion on this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. I'd like to see some explanation as to why some if these are considered the way to do things...

Sent from my iPhone

On Oct 22, 2013, at 7:06 AM, Patrik Bóna notifications@github.com wrote:

thoughtbot

expect(@game_vc.row(1)).to eql [8,5,9,6,1,2,4,3,7]
end

it "returns the expected row" do
expect(@game_ii.row(4)).to eql [0,0,0,1,0,7,0,0,2]
end
end

describe "#col" do
it "returns the first column of the sudoku file" do
expect(@game_vc.col(1)).to eql [8,7,1,9,3,2,4,6,5]
end

it "returns the expected column" do
expect(@game_ii.col(8)).to eql [0,0,0,0,0,0,7,0,4]
end
end

describe "#sub_grid" do
it "returns a 3x3 sub-grid as an array" do
expect(@game_vc.sub_grid(1)).to eql [8,5,9,7,2,3,1,6,4]
end

it "returns a middle 3x3 sub-grid" do
expect(@game_vc.sub_grid(5)).to eql [1,4,7,2,6,8,5,9,3]
end

it "successfully returns the last sub-grid" do
expect(@game_vc.sub_grid(9)).to eql [6,7,5,8,9,3,2,4,1]
end

it "successfully returns the first sub-grid" do
expect(@game_vc.sub_grid(1)).to eql [8,5,9,7,2,3,1,6,4]
end

it "returns the expected sub-grid" do
expect(@game_ii.sub_grid(8)).to eql [0,8,0,0,0,0,0,3,6]
end
end
end
70 changes: 70 additions & 0 deletions spec/sudoku_validator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require_relative '../lib/sudoku_validator'
require_relative '../lib/sudoku_board'

describe SudokuValidator do

describe "valid games" do
it "correctly recognizes a valid, complete game" do
result = %x{"./sudoku_validator" "./valid_complete.sudoku"}
expect(result).to eq "This sudoku is valid, and complete.\n"
end

it "recognizes a valid, incomplete game" do
result = `./sudoku_validator ./valid_incomplete.sudoku`
expect(result).to eq "This sudoku is valid, and incomplete.\n"
end
end

describe "invalid games" do
it "recognizes an invalid, complete game" do
result = `./sudoku_validator ./invalid_complete.sudoku`
expect(result).to include "This sudoku is invalid, and complete.\n"
expect(result).to include "2 is repeated in col 6"
end
end

describe "error handling" do
before do
@game = SudokuValidator.new "./invalid_complete.sudoku"
@element = [1,2,3,2,5,6,7,8,9]
end

it "identifies correct element in #identify_errors" do
expect(@game.identify_errors @element).to eql [2]
end

it "returns errors in #valid?" do
expect(@game.valid?(@element)).to eq [false, [2]]
end

it "returns empty error array in #vaid? if no errors" do
@element[3]=4
expect(@game.valid?(@element)).to eq [true, []]
end

it "returns the descriptive error string for col errors" do
@game.validate
@game.errors.should include "2 is repeated in col 6"
end

it "returns the descriptive error string for row errors" do
@game.board[1,1] = 7
@game.validate
@game.errors.should include "7 is repeated in row 1"
end

it "returns the descrptive error string for sub-grid errors" do
@game.board[1,1] = 7
@game.validate
@game.errors.should include "7 is repeated in sub_grid 1"
end

it "ignores missing (zero) elements for incomplete sudokus" do
@game.board[1,1] = 0
@game.board[1,7] = 0
@game.board[1,4] = 0
@game.validate
@game.errors.should_not include "0 is repeated in row 1"
end
end
end
7 changes: 7 additions & 0 deletions sudoku_validator
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env ruby
require_relative 'lib/sudoku_validator'

@validator = SudokuValidator.new(ARGV[0])
validity, status = @validator.validate
puts "This sudoku is #{validity}, and #{status}."
@validator.errors.each { |err| puts err }