3
\$\begingroup\$

I was required to implement the code to a spec file I was provided in Ruby. I was rejected with the message "The quality of your code isn't sufficient for this position". Can you please tell me what can be improved in order to achieve higher quality?

game_board_spec.rb (file provided)

require './game'
require './game_board'
describe GameBoard do
 it "should correctly solve the board" do
 @game = Game.new
 # Each '0' is a blank cell
 @game.load_board 0, 0, 8, 3, 4, 2, 9, 0, 0,
 0, 0, 9, 0, 0, 0, 7, 0, 0,
 4, 0, 0, 0, 0, 0, 0, 0, 3,
 0, 0, 6, 4, 7, 3, 2, 0, 0,
 0, 3, 0, 0, 0, 0, 0, 1, 0,
 0, 0, 2, 8, 5, 1, 6, 0, 0,
 7, 0, 0, 0, 0, 0, 0, 0, 8,
 0, 0, 4, 0, 0, 0, 1, 0, 0,
 0, 0, 3, 6, 9, 7, 5, 0, 0
 @solved_board = GameBoard.new 6, 7, 8, 3, 4, 2, 9, 5, 1,
 3, 2, 9, 1, 8, 5, 7, 6, 4,
 4, 5, 1, 7, 6, 9, 8, 2, 3,
 5, 1, 6, 4, 7, 3, 2, 8, 9,
 8, 3, 7, 9, 2, 6, 4, 1, 5,
 9, 4, 2, 8, 5, 1, 6, 3, 7,
 7, 6, 5, 2, 1, 4, 3, 9, 8,
 2, 9, 4, 5, 3, 8, 1, 7, 6,
 1, 8, 3, 6, 9, 7, 5, 4, 2
 @game.solve.should == @solved_board
 end
 it "should correctly solve the board" do
 @game = Game.new
 # Each '0' is a blank cell
 @game.load_board 0, 0, 4, 0, 0, 0, 5, 0, 0,
 0, 7, 0, 2, 0, 0, 3, 6, 0,
 8, 0, 0, 0, 0, 1, 0, 0, 0,
 6, 2, 9, 0, 0, 0, 0, 3, 0,
 0, 0, 0, 0, 6, 0, 0, 0, 0,
 0, 4, 0, 0, 0, 0, 6, 1, 8,
 0, 0, 0, 7, 0, 0, 0, 0, 6,
 0, 1, 3, 0, 0, 4, 0, 2, 0,
 0, 0, 2, 0, 0, 0, 4, 0, 0
 @solved_board = GameBoard.new 2, 3, 4, 9, 7, 6, 5, 8, 1,
 9, 7, 1, 2, 8, 5, 3, 6, 4,
 8, 5, 6, 4, 3, 1, 2, 9, 7,
 6, 2, 9, 1, 4, 8, 7, 3, 5,
 1, 8, 5, 3, 6, 7, 9, 4, 2,
 3, 4, 7, 5, 9, 2, 6, 1, 8,
 4, 9, 8, 7, 2, 3, 1, 5, 6,
 7, 1, 3, 6, 5, 4, 8, 2, 9,
 5, 6, 2, 8, 1, 9, 4, 7, 3
 @game.solve.should == @solved_board
 end
end

My Solution:

game_board.rb

require 'matrix'
class GameBoard
 attr_accessor :board
 def initialize(*params)
 @board = Matrix[ *params.each_slice(9).to_a ]
 end
 def ==(game_board)
 return false unless game_board.respond_to? 'board'
 self.board == game_board.board
 end
end

game.rb

require './game_board'
class Game
 attr_reader :game_board
 attr_reader :number_set
 def initialize
 @number_set = [*(0..9)]
 end
 def load_board(*params)
 @game_board = GameBoard.new(*params)
 end
 def solve
 solve_game(game_board.board)
 game_board
 end
private
 def solve_game(board)
 if is_solved?(board)
 game_board.board = board
 return true
 end
 for i in 0..8 #traverse rows
 available_row_choices = number_set - board.row(i).to_a
 for j in 0..8 #traverse columns
 if board[i,j] == 0
 available_col_choices = number_set - board.column(j).to_a
 available_pos_choices = available_row_choices &
 available_col_choices &
 available_block_choices(board, [i,j])
 available_pos_choices.each do |choice|
 updated_board = update_board(board, [i,j], choice)
 return true if solve_game(updated_board)
 end
 return false
 end #unless
 end # for j
 end # for i
 end # solve_game
 def update_board(board, position, value)
 tmp_board = board.to_a
 tmp_board[position.first][position.last] = value
 Matrix[ *tmp_board ]
 end
 def available_block_choices(board, position)
 block = get_block(board, position)
 number_set - block.to_a.flatten.select{|e| e != 0}
 end
 def get_block(board, position)
 index_i = (position.first / 3) * 3
 index_j = (position.last / 3) * 3
 board.minor(index_i..(index_i+2), index_j..(index_j+2))
 end
 def is_solved?(board)
 board.find_index(0).nil?
 end
end # class
asked Mar 22, 2017 at 20:24
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I'm sorry to hear that you didn't get the position. I'm not sure what position you were applying for but this code is certainly as good as most code I get from candidates. I can make some comments but I'm nit picking, I would consider this code fine in production where people have jobs to do and don't have the time to make everything perfect.

  • Maybe add a spec for a failing or unsolvable board (either because the board is invalid or not enough values were provided)

  • I would prefer to see a constant for the board size.

  • This is slightly personal, but I don't like passing [x,y] as an array and then using first and last to get the values. I would prefer to see a struct, hash or something similar.

  • You continually refer to gameboard.board, I wouldn't make the internal @board public. Instead I would move some if not most logic from Game to GameBoard including get_block, available_block_choices, is_solved? and update_board. I would actually make the first two private and add available_pos_choices and empty? methods so the main code might look like:

    board.each_with_index do |val, row, col|
     next unless board.empty?(i,j)
     available_choices = board.available_choices(row, col)
     available_choices.each do |choice|
     updated_board = board.update(row, col, choice)
     return true if solve_game(updated_board)
     end
     return false
    end # each
    
  • If a choice is available in a row and column (which is almost guaranteed) then there will be duplicate elements in this code below

     available_pos_choices = available_row_choices &
     available_col_choices &
     available_block_choices(board, [i,j])
    
  • For update_board you should probably use the Matrix.clone method.

  • The select here number_set - block.to_a.flatten.select{|e| e != 0} seems redundant. At least you didn't exclude zero in other places.

answered Mar 23, 2017 at 3:07
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.