Week 2, day 4

Today my pair-programming partner and I finished off the bare bones for our Battleship game and could hypothetically modify it to meet the requirements of most board games. It's been a really challenging couple of days of following our excellent coach's tutorials when we couldn't recreate the tests and code ourselves. This was all done in Ruby using Rspec for testing, this time around we didn't have any feature tests at all but a lot of unit tests. It's interesting to see how different people like to work, ultimately any style can be used to create a polished and lean end product. The greatest challenge was definitely coming to terms with doubles for purposes of unit testing. Below is a taster of just some of the doubles we had to employ in our most demanding spec file, the one used to test for board creation.

1 let(:cell) { double :first_cell, content: '' }
2 let(:second_cell) { double :second_cell }
3 let(:cell_class) { double :cell_class, new: cell }
4 let(:board) { Board.new({ size: 100, cell: cell_class, number_of_pieces: 5 }) }
5 let(:ship) { double :ship, size: 2, sunk?: false }
6 let(:small_ship) { double :ship, size: 1, sunk?: true }

Next week I think we will be using Sinatra to create a web-version of Battleships, but a weekend is a long time in coding and tomorrow we get our weekend challenge, so everyone is somewhat nervous once again.

This is the current end product for our board application code.

 1 class Board
 2   DEFAULT_SIZE = 1
 3   DEFAULT_NUMBER_OF_PIECES = 1
 4   attr_reader :grid, :number_of_pieces
 5 
 6   def initialize options
 7     size = options.fetch(:size, DEFAULT_SIZE)
 8     cell = options.fetch(:cell)
 9     @number_of_pieces = options.fetch(:number_of_pieces, DEFAULT_NUMBER_OF_PIECES)
10     @grid = {}
11     letter_range_based_on_size(size).map do |letter|
12       (1..dimension_size(size)).map{ |number| @grid["#{ letter }#{ number }".to_sym] = cell.new }
13     end
14   end
15 
16   def dimension_size size
17     Math.sqrt(size).ceil
18   end
19 
20   def letter_range_based_on_size size
21     ('A'..to_letter_in_alphabet(dimension_size(size)))
22   end
23 
24   def to_letter_in_alphabet number
25     (number.ord + 64).chr
26   end
27 
28   def place(ship, coord, orientation = :hor)
29     fail 'Ship out of bounds' unless coords_for(ship.size, coord, orientation).all? { |coord| coord_on_board? coord }
30     coords_for(ship.size, coord, orientation).each do |coord|
31       grid[coord].content = ship
32     end
33   end
34 
35   def coords_for size, coord, orientation
36     coords = [coord]
37     (size - 1).times { coords << next_coord(coords.last, orientation) }
38     coords
39   end
40 
41   def next_coord coord, orientation
42     orientation == :hor ? coord.next : coord.to_s.reverse.next.reverse.to_sym
43   end
44 
45   def coord_on_board? coord
46     grid.keys.include? coord
47   end
48   
49   def fill_all_content_with something
50     grid.each do |cell|
51       cell.content = something
52   end
53 
54   def hit cell
55     fail 'Hit out of bounds' unless coord_on_board? cell
56     grid[cell].content.hit
57   end
58 
59   def all_ships_sunk?
60     ships.all?(&:sunk?)
61   end
62 
63   def ships
64     grid.values.map(&:content).select{|content| content.respond_to? :sunk? }
65   end
66 
67   def ready?
68     ships.count == number_of_pieces
69   end
70   
71   def lost?
72     all_ships_sunk? && ready?
73   end
74 end

The Github repo as it stands can be viewed here If we get time we may try and create a terminal version of our game for laughs, putting the finishing touches to the setup is going to be interesting.

Written on March 26, 2015