Første møte i Bergen CodingDojo


tirsdag 22. november 2011 CodingDojo

CIMG0911

I kveld har jeg vært på det aller første møtet i Bergen CodingDojo. 17 utviklere med ønske om å trene og bli bedre møttes i Miles' lokaler under Puddefjordsbroen. Stemningen var god, og det ble en veldig kjekk kveld.

CIMG0914

Oppgaven, som skulle gjøres med TDD, var å lage en løsning som analyserte minesweeper-brett – oppgaveteksten tilgjengelig her. Vi jobbet i par, men siden vi var odde utviklere endte jeg opp som tredjeperson på ett av parene. Å sitte tre stykker forran en liten laptop er ikke så lett, så det gikk ikke lang tid før jeg begynte på en løsning for meg selv.

Til gjengjeld ble jeg først ferdig – tre kvarter før neste par. Ikke at det er noe poeng i seg selv, men det er jo kjekt å vinne! ;D

CIMG0913

Grunnen til at jeg var så rask er nok at jeg har gjort en del lignende oppgaver før; jeg begynner etterhvert å få ganske mye TDD-erfaring. Det jeg fortsatt synes er vanskelig er å bruke veldig små steg, og det klarte jeg egentlig ikke denne gangen heller.

I det siste har jeg derimot forsøkt å fokusere på å angripe hjertet av problemet direkte, og det følte jeg gikk veldig bra på denne oppgaven. Jeg løste hovedproblemet med bare én test, og uten å opprette særlig med abstraksjoner – dem la jeg til etterhvert.

CIMG0912

Flertallet var forresten Java-utviklere. De fleste andre brukte C#, mens jeg og ett av parene brukte Ruby. Nedenfor ser du løsningen min.

minesweeper.rb

 1 # Helper function for building arrays
 2 def build_array; a = []; yield a; a; end
 3 
 4 # Module to parse, solve and output minesweeper data
 5 module MineIO
 6   def self.solve_all input
 7     games = self.parse_all input
 8     games.each {|g| g.solve }
 9     self.to_s games
10   end
11   def self.parse_all input
12     lines = input.split /\n/
13     build_array do |games|
14       until lines.first == "0 0"
15         games << Minesweeper.new(self.parse_field(lines))
16       end
17     end
18   end
19   def self.parse_field input
20     build_array do |out|
21       n, m = input.shift.split.map {|x| x.to_i }
22       n.times { out << input.shift.split("") }
23     end
24   end
25   def self.to_s games # Creates the output format
26     index = 0
27     (games.inject("") do |memo, game|
28       "#{memo}Field ##{index += 1}:\n#{game.output}\n"
29     end).chop
30   end
31 end
32 
33 # Represents and solves a single minesweeper field
34 class Minesweeper
35   def initialize field
36     @field = field
37   end
38   def solve
39     (0...@field.size).each do |y|
40       (0...@field[0].size).each do |x|
41         next if @field[y][x] == "*"
42         @field[y][x] = Cell.new(x, y, @field).sum
43       end
44     end
45   end
46   def output
47     out = ""
48     @field.each do |row|
49       row.each do |cell|
50         out += cell.to_s
51       end
52       out += "\n"
53     end
54     out
55   end
56 
57   # Class used to analyse a position in the game field
58   class Cell
59     def initialize x, y, field
60       @x, @y, @field = x, y, field
61     end
62     def sum
63       north + south + east + west +
64         north_west + north_east + south_east + south_west
65     end
66     def north;      get_neighbour(@x, @y-1); end
67     def south;      get_neighbour(@x, @y+1); end
68     def east;       get_neighbour(@x+1, @y); end
69     def west;       get_neighbour(@x-1, @y); end
70     def north_west; get_neighbour(@x-1, @y-1); end
71     def north_east; get_neighbour(@x+1, @y-1); end
72     def south_east; get_neighbour(@x+1, @y+1); end
73     def south_west; get_neighbour(@x-1, @y+1); end
74     def get_neighbour x, y
75       return 0 unless x >= 0 and y >= 0 and @field[y] and @field[y][x]
76       if @field[y][x] == "*" then 1 else 0 end
77     end
78   end
79 end
80 
81 # Parse and output ananlysis if file is run directly
82 if $0 == __FILE__
83   puts MineIO.solve_all(File.readlines(ARGV[0]).join)
84 end

Jeg er spesielt fornøyd med Cell-klassen. Den gjør én ting, og den gjør det godt!

test_minesweeper.rb

 1 require "./minesweeper"
 2 require "test/unit"
 3 
 4 class Spec < Test::Unit::TestCase
 5   def setup
 6     @field = [["*", "*", ".", ".", "."],
 7               [".", ".", ".", ".", "."],
 8               [".", "*", ".", ".", "."]]
 9     @m = Minesweeper.new @field
10   end
11   def test_individual_cells
12     @m.solve
13     assert_equal 1,   @field[0][2]
14     assert_equal "*", @field[0][1]
15     assert_equal 2,   @field[1][2]
16     assert_equal 0,   @field[1][3]
17     assert_equal 3,   @field[1][1]
18   end
19   def test_output
20     expected = <<EOF
21 **100
22 33200
23 1*100
24 EOF
25     @m.solve
26     assert_equal expected, @m.output
27   end
28   def test_parse
29     input = <<EOF
30 3 5
31 **...
32 .....
33 .*...
34 EOF
35     assert_equal @field, MineIO.parse_field(input.split(/\n/))
36   end
37   def test_complete
38     input = <<EOF
39 4 4
40 *...
41 ....
42 .*..
43 ....
44 3 5
45 **...
46 .....
47 .*...
48 0 0
49 EOF
50     output = <<EOF
51 Field #1:
52 *100
53 2210
54 1*10
55 1110
56 
57 Field #2:
58 **100
59 33200
60 1*100
61 EOF
62     assert_equal output, MineIO.solve_all(input)
63   end
64 end

Til slutt et bilde av min kjære, gamle laptop. Drivstoffet står til høyre.

CIMG0915


comments powered by Disqus