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.