Week 2, day 3

Another athletic restart on our Battleships challenge today. As usual, all goes well, my pair programming partner today and I had paired before on the precourse on a test-driven development exercise so we totally meshed, supporting and urging eachother on. She's just as laid back as me so it was a very pleasant day but we did get a lot done. Our coach has been doing incremental tutorials on how to make Battleships in Ruby testing with Rspec so we decided to follow those, pausing at key moments to see if we could write and solve the tests he suggested for ourselves.
The day ended with our wellness guru taking us to the pub for drinks.
an image alt text
(Here's some of our cohort outside Makers Academy before heading off to the pub)
If you're really interested, there's some code after the break!

Below is the test code to create some behaviour-driven testing scenarios for our game board, ultimately this could be modified to conform to any board-game. The application code to pass those tests is underneath it:

 1 require 'board'
 2 
 3 describe 'Board' do
 4 
 5   let(:cell){ double :first_cell }
 6   let(:second_cell){ double :second_cell }
 7   let(:cell_class) { double :cell_class, new: cell }
 8   let(:board) { Board.new({ size: 100, cell: cell_class })}
 9   let(:ship) { double :ship, size: 2 }
10   let(:small_ship) { double :ship, size: 1 }
11 
12 
13   it 'has 100 cells in the grid' do
14     expect(board.grid.count).to eq 100
15   end
16 
17   it 'can place a ship' do
18     board.grid[:A1] = second_cell
19     expect(second_cell).to receive(:content=).with small_ship
20     board.place small_ship, :A1
21   end
22 
23   it 'can place a size 2 ship on the grid' do
24     board.grid[:A1] = second_cell
25     board.grid[:A2] = second_cell
26     expect(second_cell).to receive(:content=).with(ship).exactly(2).times
27     board.place ship, :A1
28   end
29 
30   it 'can work out the coordinates for a size' do
31     expect(board.coords_for(2, :A1, :hor)).to eq [:A1, :A2]
32   end
33 
34   it 'can place a size 2 ship on the grid vertically' do
35     board.grid[:A1] = second_cell
36     board.grid[:B1] = second_cell
37     expect(second_cell).to receive(:content=).with(ship).exactly(2).times
38     board.place ship, :A1, :vert
39   end
40 
41   it 'knows if a coordinate is on the board' do
42     expect(board.coord_on_board?(:A1)).to be true
43   end
44 
45   it 'knows if a coordinate is not on the board' do
46     expect(board.coord_on_board?(:A11)).to be false
47     expect(board.coord_on_board?(:K1)).to be false
48   end
49 
50   it 'cannot place a ship outside of the board' do
51     expect { board.place ship, :A10 }.to raise_error 'Ship out of bounds'
52   end
53 
54   it 'can hit items on the board' do
55     board.grid[:A1] = second_cell
56     allow(second_cell).to receive(:content).and_return ship
57     expect(ship).to receive(:hit)
58     board.hit(:A1)
59   end
60 
61   it "can't hit a cell outside of the boundaries" do
62   end
63 end

You may wonder why so much of an emphasis on testing. Without stringent and well thought out tests your chances of writing agile, lean code that will allow future coders to understand and modify it to meet the needs of the future are slim to none.
Below: Application code:

 1 class Board
 2   DEFAULT_SIZE = 1
 3   attr_reader :grid
 4 
 5   def initialize options
 6     size = options.fetch(:size, DEFAULT_SIZE)
 7     cell = options.fetch(:cell)
 8     @grid = {}
 9     letter_range_based_on_size(size).map do |letter|
10       (1..dimension_size(size)).map{ |number| @grid["#{ letter }#{ number }".to_sym] = cell.new }
11     end
12   end
13 
14   def dimension_size size
15     Math.sqrt(size).ceil
16   end
17 
18   def letter_range_based_on_size size
19     ('A'..to_letter_in_alphabet(dimension_size(size)))
20   end
21 
22   def to_letter_in_alphabet number
23     (number.ord + 64).chr
24   end
25 
26   def place(ship, coord, orientation = :hor)
27     fail 'Ship out of bounds' unless coords_for(ship.size, coord, orientation).all? { |coord| coord_on_board? coord }
28     coords_for(ship.size, coord, orientation).each do |coord|
29       grid[coord].content = ship
30     end
31   end
32 
33   def coords_for size, coord, orientation
34     coords = [coord]
35     (size - 1).times { coords << next_coord(coords.last, orientation) }
36     coords
37   end
38 
39   def next_coord coord, orientation
40     orientation == :hor ? coord.next : coord.to_s.reverse.next.reverse.to_sym
41   end
42 
43   def coord_on_board? coord
44     grid.keys.include? coord
45   end
46 
47   def hit cell
48     grid[cell].content.hit
49   end
50 end
Written on March 25, 2015