Created
August 29, 2012 18:54
-
-
Save avdgaag/3517092 to your computer and use it in GitHub Desktop.
Solution to minefield code kata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SurroundingRange | |
attr_reader :point, :field | |
def initialize(point, field) | |
@point, @field = point, field | |
end | |
def points | |
horizontal = [point.x - 1, 0].max..[point.x + 1, field.width - 1].min | |
vertical = [point.y - 1, 0].max..[point.y + 1, field.height - 1].min | |
horizontal.to_a.product(vertical.to_a).map { |x,y| Point.new(x,y) }.reject { |p| p === point } | |
end | |
end | |
class Point < Struct.new(:x, :y) | |
def ===(other) | |
other.x == self.x && other.y == self.y | |
end | |
end | |
class Field | |
BOMB = '*' | |
attr_reader :rows, :width, :height | |
def initialize(width, height) | |
@width, @height = width, height | |
@rows = [] | |
end | |
def map | |
output = [] | |
rows.each_with_index do |row, x| | |
line = '' | |
row.each_with_index do |char, y| | |
p = Point.new(x, y) | |
line << (bomb?(p) ? '*' : bombs_surrounding(p).to_s) | |
end | |
output << line | |
end | |
output.join("\n") | |
end | |
def bombs_surrounding(point) | |
range_around(point).select { |p| bomb?(p) }.count | |
end | |
def range_around(point) | |
SurroundingRange.new(point, self).points | |
end | |
def to_s | |
map | |
end | |
def <<(row) | |
@rows << row | |
self | |
end | |
def at(point) | |
rows.fetch(point.x).fetch(point.y) | |
end | |
def bomb?(point) | |
at(point) == BOMB | |
end | |
end | |
class InputReader | |
attr_reader :input | |
def initialize(input) | |
@input = input | |
end | |
def fields | |
fields = [] | |
input.each_line do |line| | |
break if line.start_with?('0 0') | |
if line =~ /(\d+) (\d+)/ | |
fields << Field.new($1.to_i, $2.to_i) | |
next | |
end | |
fields.last << line.chomp.split('') | |
end | |
fields | |
end | |
end | |
class Minesweeper | |
attr_reader :fields | |
def initialize(input) | |
@fields = InputReader.new(input).fields | |
end | |
def maps | |
i = 1 | |
@fields.inject([]) do |output, field| | |
output << "Field ##{i}\n#{field.map}\n" | |
i += 1 | |
output | |
end.join("\n") | |
end | |
end | |
describe SurroundingRange do | |
let(:field) { double width: 10, height: 10 } | |
subject { described_class.new(point, field) } | |
context 'when in the middle of a field' do | |
let(:point) { double x: 4, y: 4 } | |
it { should have(8).points } | |
its('points.first') { should be_kind_of(Point) } | |
end | |
context 'when in the horizontal edge of a field' do | |
let(:point) { double x: 0, y: 0 } | |
it { should have(3).points } | |
end | |
end | |
describe Field do | |
it 'finds a value by coordinates' do | |
field = described_class.new(2, 4) << %w[1 2 3 4] << %w[. a * b] | |
field.at(Point.new(0, 0)).should == '1' | |
field.at(Point.new(1, 2)).should == '*' | |
end | |
it 'knows when a square is a bomb' do | |
field = described_class.new(1, 4) << %w[. . * .] | |
field.bomb?(Point.new(0, 1)).should be_false | |
field.bomb?(Point.new(0, 2)).should be_true | |
end | |
it 'can count the number of bombs around a point' do | |
field = described_class.new(2, 4) << %w[. . * .] << %w[. . . *] | |
field.bombs_surrounding(Point.new(0, 0)).should == 0 | |
field.bombs_surrounding(Point.new(0, 2)).should == 1 | |
field.bombs_surrounding(Point.new(0, 3)).should == 2 | |
end | |
it 'can generate a map with bomb counts' do | |
field = described_class.new(2, 4) << %w[. . * .] << %w[. . . *] | |
field.map.should == "01*2\n012*" | |
end | |
it 'can generate a map for a simpe field' do | |
field = described_class.new(1, 1) << %w[.] | |
field.map.should == "0" | |
end | |
end | |
describe InputReader do | |
subject { described_class.new(input) } | |
context 'without input' do | |
let(:input) { '' } | |
it { should have(0).fields } | |
end | |
context 'with just the input ending marker' do | |
let(:input) { '0 0' } | |
it { should have(0).fields } | |
end | |
context 'with a single field' do | |
let(:input) { "1 1\n.\n0 0" } | |
it { should have(1).fields } | |
end | |
context 'with multiple fields' do | |
let(:input) { "1 1\n.\n2 2\n..\n..\n0 0" } | |
it { should have(2).fields } | |
end | |
end | |
describe Minesweeper do | |
subject { described_class.new(input) } | |
context 'without input' do | |
let(:input) { '' } | |
its(:maps) { should == '' } | |
end | |
context 'with just the input ending marker' do | |
let(:input) { '0 0' } | |
its(:maps) { should == '' } | |
end | |
context 'with a single field' do | |
let(:input) { "1 1\n.\n0 0" } | |
its(:maps) { should == "Field #1\n0\n" } | |
end | |
context 'with multiple fields' do | |
let(:input) { "1 1\n.\n2 2\n..\n..\n0 0" } | |
its(:maps) { should == "Field #1\n0\n\nField #2\n00\n00\n" } | |
end | |
end |
Same approach rewritten using TDD!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This reads IO input as long as it can find a new line of two numbers (greater than 0). If it does, it reads another set of lines (the number of the first number it found). This gets added to a
Field
object.The field object knows its dimensions, its index in the list of all fields, and it can look up the value of any square in the field by using two coordinates
x
andy
. It can then create a minesweeper map by looping every line and character and printing the number of bombs surrounding that square by generating a range of coordinates around a given set of coordinates and inspecting their values.