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.
(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