tirsdag 22. november 2011 CodingDojo
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.
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
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.
Flertallet var forresten Java-utviklere. De fleste andre brukte C#, mens jeg og ett av parene brukte Ruby. Nedenfor ser du løsningen min.
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!
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.