white_horseman 1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Battleship.Rakefile +12 -0
- data/Rakefile +47 -0
- data/lib/white_horseman/analyst.rb +63 -0
- data/lib/white_horseman/battlefield.rb +64 -0
- data/lib/white_horseman/captain.rb +58 -0
- data/lib/white_horseman/cell_status.rb +45 -0
- data/lib/white_horseman/coordinates.rb +87 -0
- data/lib/white_horseman/fleet_admiral.rb +79 -0
- data/lib/white_horseman/opponent.rb +70 -0
- data/lib/white_horseman/placement.rb +97 -0
- data/lib/white_horseman/scout.rb +66 -0
- data/lib/white_horseman/ship.rb +45 -0
- data/lib/white_horseman/white_horseman.rb +187 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/white_horseman/analyst_spec.rb +52 -0
- data/spec/white_horseman/battlefield_spec.rb +121 -0
- data/spec/white_horseman/captain_spec.rb +57 -0
- data/spec/white_horseman/cell_status_spec.rb +37 -0
- data/spec/white_horseman/coordinates_spec.rb +171 -0
- data/spec/white_horseman/fleet_admiral_spec.rb +54 -0
- data/spec/white_horseman/opponent_spec.rb +58 -0
- data/spec/white_horseman/placement_spec.rb +111 -0
- data/spec/white_horseman/scout_spec.rb +85 -0
- data/spec/white_horseman/ship_spec.rb +44 -0
- data/spec/white_horseman/white_horesman_integration_spec.rb +22 -0
- data/spec/white_horseman/white_horseman_spec.rb +97 -0
- data/test_data/testman.data +104 -0
- metadata +85 -0
data/Battleship.Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#
|
2
|
+
# DO NOT tamper with this file. It will lead to disqualification.
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
desc "Run all examples with RCov"
|
8
|
+
Spec::Rake::SpecTask.new('spec_with_rcov') do |t|
|
9
|
+
t.spec_files = FileList['spec/**/*.rb']
|
10
|
+
t.rcov = true
|
11
|
+
t.rcov_opts = ['-t', '--exclude', 'spec', '--no-html']
|
12
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'battleship_tournament/submit'
|
5
|
+
|
6
|
+
desc "Run all specs"
|
7
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
8
|
+
t.spec_files = FileList['spec/**/*.rb']
|
9
|
+
t.rcov = false
|
10
|
+
end
|
11
|
+
|
12
|
+
PKG_NAME = "white_horseman"
|
13
|
+
PKG_VERSION = "1.3"
|
14
|
+
|
15
|
+
spec = Gem::Specification.new do |s|
|
16
|
+
s.name = PKG_NAME
|
17
|
+
s.version = PKG_VERSION
|
18
|
+
s.files = FileList['**/*'].to_a
|
19
|
+
s.require_path = 'lib'
|
20
|
+
s.test_files = Dir.glob('spec/*_spec.rb')
|
21
|
+
s.bindir = 'bin'
|
22
|
+
s.executables = []
|
23
|
+
s.summary = "Battleship Player:White Horseman"
|
24
|
+
s.rubyforge_project = "sparring"
|
25
|
+
s.homepage = "http://sparring.rubyforge.org/"
|
26
|
+
|
27
|
+
###########################################
|
28
|
+
##
|
29
|
+
## You are encouraged to modify the following
|
30
|
+
## spec attributes.
|
31
|
+
##
|
32
|
+
###########################################
|
33
|
+
s.description = "The apocalypse is here"
|
34
|
+
s.author = "Doug Bradbury"
|
35
|
+
s.email = "doug@8thlight.com"
|
36
|
+
end
|
37
|
+
|
38
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
39
|
+
pkg.need_zip = false
|
40
|
+
pkg.need_tar = false
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Submit your player"
|
44
|
+
task :submit do
|
45
|
+
submitter = BattleshipTournament::Submit.new(PKG_NAME)
|
46
|
+
submitter.submit
|
47
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'white_horseman/coordinates'
|
2
|
+
require 'white_horseman/scout'
|
3
|
+
require 'white_horseman/ship'
|
4
|
+
module WhiteHorseman
|
5
|
+
class Analyst
|
6
|
+
def initialize(hit_map, live_ships)
|
7
|
+
@live_ships = live_ships
|
8
|
+
@hit_map = hit_map
|
9
|
+
@scout = Scout.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def hit_probability(coordinate)
|
13
|
+
probability = probability_from_neighbors(1, coordinate)
|
14
|
+
probability += probability_from_neighbors(2, coordinate)
|
15
|
+
|
16
|
+
possible_placements_here = 0
|
17
|
+
@live_ships.each do |ship|
|
18
|
+
possible_placements_here += @scout.possible_placements(ship.size, [coordinate], @hit_map)
|
19
|
+
end
|
20
|
+
probability += 0.17 * possible_placements_here / 34.0
|
21
|
+
return probability
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def probability_for(coordinate, neighbor, ship)
|
27
|
+
placements_including_coordinate = @scout.possible_placements(ship.size, [coordinate, neighbor], @hit_map)
|
28
|
+
total_placements = @scout.possible_placements(ship.size, [neighbor], @hit_map)
|
29
|
+
|
30
|
+
return (probability_is(ship) * placements_including_coordinate / total_placements) if total_placements != 0
|
31
|
+
return 0.0
|
32
|
+
end
|
33
|
+
|
34
|
+
def hit?(coordinate)
|
35
|
+
@hit_map[coordinate].hit?
|
36
|
+
end
|
37
|
+
|
38
|
+
def miss?(coordinate)
|
39
|
+
@hit_map[coordinate].miss?
|
40
|
+
end
|
41
|
+
|
42
|
+
def probability_is(ship)
|
43
|
+
if @live_ships.include?(ship)
|
44
|
+
return 1.0 / @live_ships.size
|
45
|
+
else
|
46
|
+
return 0.0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def probability_from_neighbors(degree, coordinate)
|
51
|
+
probability = 0.0
|
52
|
+
coordinate.to_coord.neighbors(degree).each do |neighbor|
|
53
|
+
if hit?(neighbor)
|
54
|
+
Ship.each do |ship|
|
55
|
+
probability += probability_for(coordinate, neighbor, ship)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
return probability
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
require 'white_horseman/coordinates'
|
3
|
+
|
4
|
+
module WhiteHorseman
|
5
|
+
class Battlefield
|
6
|
+
attr_reader :shots
|
7
|
+
def initialize(grid = [
|
8
|
+
[0,0,0,0,0,0,0,0,0,0],
|
9
|
+
[0,0,0,0,0,0,0,0,0,0],
|
10
|
+
[0,0,0,0,0,0,0,0,0,0],
|
11
|
+
[0,0,0,0,0,0,0,0,0,0],
|
12
|
+
[0,0,0,0,0,0,0,0,0,0],
|
13
|
+
[0,0,0,0,0,0,0,0,0,0],
|
14
|
+
[0,0,0,0,0,0,0,0,0,0],
|
15
|
+
[0,0,0,0,0,0,0,0,0,0],
|
16
|
+
[0,0,0,0,0,0,0,0,0,0],
|
17
|
+
[0,0,0,0,0,0,0,0,0,0],
|
18
|
+
])
|
19
|
+
@grid = grid
|
20
|
+
@shots = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def average(battlefields)
|
25
|
+
seed = Battlefield.new
|
26
|
+
seed.set_all(0.0)
|
27
|
+
sum = battlefields.inject(Matrix.rows(seed.grid)) do |accumulator, battlefield|
|
28
|
+
accumulator + Matrix.rows(battlefield.grid)
|
29
|
+
end
|
30
|
+
average = sum / battlefields.size
|
31
|
+
return Battlefield.new(average.to_a)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def record_target(coordinates)
|
36
|
+
@shots +=1
|
37
|
+
self[coordinates] = 100 - @shots
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](coordinates)
|
41
|
+
coord = coordinates.to_coord
|
42
|
+
return @grid[coord.row_index][coord.column_index]
|
43
|
+
end
|
44
|
+
|
45
|
+
def []=(coordinates, value)
|
46
|
+
coord = coordinates.to_coord
|
47
|
+
|
48
|
+
@grid[coord.row_index][coord.column_index] = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def grid
|
52
|
+
return @grid
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_all(value)
|
56
|
+
10.times do |row|
|
57
|
+
10.times do |column|
|
58
|
+
@grid[row][column] = value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "white_horseman/battlefield"
|
2
|
+
require "white_horseman/placement"
|
3
|
+
require "white_horseman/coordinates"
|
4
|
+
require "white_horseman/analyst"
|
5
|
+
require "white_horseman/cell_status"
|
6
|
+
|
7
|
+
module WhiteHorseman
|
8
|
+
|
9
|
+
class Captain
|
10
|
+
attr_accessor :hit_map
|
11
|
+
attr_accessor :live_ships
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@hit_map = Battlefield.new
|
15
|
+
@hit_map.set_all(CellStatus.new)
|
16
|
+
@live_ships = Ship.all
|
17
|
+
end
|
18
|
+
|
19
|
+
def target_result(coordinates, was_hit, ship_sunk)
|
20
|
+
@hit_map[coordinates] = CellStatus.new(was_hit)
|
21
|
+
unless ship_sunk.nil?
|
22
|
+
ship = Ship.new(ship_sunk)
|
23
|
+
live_ships.delete(ship)
|
24
|
+
scout = Scout.new
|
25
|
+
salvage = scout.salvage_placements(ship.size, coordinates, @hit_map)
|
26
|
+
intersection = salvage.first.cells
|
27
|
+
salvage.each do |placement|
|
28
|
+
intersection &= placement.cells
|
29
|
+
end
|
30
|
+
|
31
|
+
intersection.each do |cell|
|
32
|
+
@hit_map[cell].ship = ship_sunk
|
33
|
+
end
|
34
|
+
|
35
|
+
@hit_map[coordinates].ship = ship_sunk
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_target
|
40
|
+
analyst = Analyst.new(@hit_map, @live_ships)
|
41
|
+
max_probability = 0.0
|
42
|
+
top_shots = []
|
43
|
+
Coordinates.each do |coordinate|
|
44
|
+
if @hit_map[coordinate].empty?
|
45
|
+
probability = analyst.hit_probability(coordinate)
|
46
|
+
if probability > max_probability
|
47
|
+
max_probability = probability
|
48
|
+
top_shots = [coordinate]
|
49
|
+
elsif probability == max_probability
|
50
|
+
top_shots << coordinate
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return top_shots[rand(top_shots.size)].to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module WhiteHorseman
|
2
|
+
class CellStatus
|
3
|
+
|
4
|
+
attr_accessor :ship
|
5
|
+
|
6
|
+
def initialize(was_hit=nil)
|
7
|
+
@ship = :none
|
8
|
+
|
9
|
+
case was_hit
|
10
|
+
when nil
|
11
|
+
@status = :none
|
12
|
+
when true
|
13
|
+
hit
|
14
|
+
when false
|
15
|
+
miss
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def hit
|
20
|
+
@status = :hit
|
21
|
+
end
|
22
|
+
|
23
|
+
def miss
|
24
|
+
@status = :miss
|
25
|
+
end
|
26
|
+
|
27
|
+
def hit?
|
28
|
+
@status == :hit
|
29
|
+
end
|
30
|
+
|
31
|
+
def miss?
|
32
|
+
@status == :miss
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
@status == :none
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_ship?
|
40
|
+
return @ship != :none
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class String
|
2
|
+
def to_coord
|
3
|
+
return WhiteHorseman::Coordinates.new(:string => self)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module WhiteHorseman
|
8
|
+
class Coordinates
|
9
|
+
attr_reader :row, :column
|
10
|
+
def initialize(options={:row => "A", :column => 1})
|
11
|
+
options.each do |option, value|
|
12
|
+
send("#{option}=".to_sym, value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def string=(coord_string)
|
17
|
+
self.row = "%c" % coord_string.upcase[0]
|
18
|
+
self.column = (coord_string[1..-1]).to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def column=(value)
|
22
|
+
raise "Column out of range: #{value}" if value > 10 || value < 1
|
23
|
+
@column = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def inc_column(delta = 1)
|
27
|
+
self.column = @column + delta
|
28
|
+
return self
|
29
|
+
end
|
30
|
+
|
31
|
+
def row=(value)
|
32
|
+
raise "Row out of range: #{value}" if value > "J" || value < "A"
|
33
|
+
@row = value
|
34
|
+
end
|
35
|
+
|
36
|
+
def inc_row(delta=1)
|
37
|
+
self.row = "%c" % (self.row[0] += delta)
|
38
|
+
return self
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
"#{row}#{column}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def row_index
|
46
|
+
row[0] - 65
|
47
|
+
end
|
48
|
+
|
49
|
+
def column_index
|
50
|
+
@column - 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_coord
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
return self.column == other.to_coord.column && self.row == other.to_coord.row
|
59
|
+
end
|
60
|
+
|
61
|
+
def neighbors(distance=1)
|
62
|
+
neighbors = []
|
63
|
+
neighbors << Coordinates.new(:string => self.to_s).inc_row(distance) unless self.row_index > 9 - distance
|
64
|
+
neighbors << Coordinates.new(:string => self.to_s).inc_row(-1*distance) unless self.row_index < distance
|
65
|
+
neighbors << Coordinates.new(:string => self.to_s).inc_column(distance) unless self.column_index > 9 - distance
|
66
|
+
neighbors << Coordinates.new(:string => self.to_s).inc_column(-1*distance) unless self.column_index < distance
|
67
|
+
return neighbors
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def self.colinear?(coordinates, orientation=nil)
|
72
|
+
first = coordinates.first.to_coord
|
73
|
+
return colinear?(coordinates, :vertical) || colinear?(coordinates, :horizontal) if orientation. nil?
|
74
|
+
return coordinates.all?{|coord| coord.to_coord.column == first.column} if orientation.to_sym == :vertical
|
75
|
+
return coordinates.all?{|coord| coord.to_coord.row == first.row} if orientation.to_sym == :horizontal
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.each(&block)
|
79
|
+
("A".."J").each do |row|
|
80
|
+
(1..10).each do |column|
|
81
|
+
yield(Coordinates.new(:row => row, :column => column))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'white_horseman/placement'
|
2
|
+
require 'white_horseman/coordinates'
|
3
|
+
module WhiteHorseman
|
4
|
+
class FleetAdmiral
|
5
|
+
attr_accessor :heat_map
|
6
|
+
|
7
|
+
def calculate
|
8
|
+
@min_placements = {}
|
9
|
+
[["carrier", 5], ["battleship", 4], ["destroyer", 3], ["submarine", 3], ["patrolship", 2]].each do |ship|
|
10
|
+
placement = place_ship(ship[1])
|
11
|
+
raise "Couldn't place #{ship[0]}!" if placement.nil?
|
12
|
+
@min_placements[ship[0]] = placement
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def carrier
|
17
|
+
check_for_calculate
|
18
|
+
return @min_placements["carrier"].to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def battleship
|
22
|
+
check_for_calculate
|
23
|
+
return @min_placements["battleship"].to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroyer
|
27
|
+
check_for_calculate
|
28
|
+
return @min_placements["destroyer"].to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def submarine
|
32
|
+
check_for_calculate
|
33
|
+
return @min_placements["submarine"].to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def patrolship
|
37
|
+
check_for_calculate
|
38
|
+
return @min_placements["patrolship"].to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
private #####################################################################################################
|
42
|
+
|
43
|
+
def place_ship(size)
|
44
|
+
min_threat = 999999999999
|
45
|
+
min_placements = []
|
46
|
+
Placement.each(size) do |placement|
|
47
|
+
unless overlaps_another_ship?(placement)
|
48
|
+
threat = threat(placement)
|
49
|
+
|
50
|
+
if threat < min_threat
|
51
|
+
min_threat = threat
|
52
|
+
min_placements = [placement]
|
53
|
+
elsif threat == min_threat
|
54
|
+
min_placements << placement
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
return min_placements[rand(min_placements.size)]
|
60
|
+
end
|
61
|
+
|
62
|
+
def check_for_calculate
|
63
|
+
raise "Call calculate first" if @min_placements.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
def overlaps_another_ship?(placement)
|
67
|
+
@min_placements.each do |ship, existing_placement|
|
68
|
+
return true if placement.overlap?(existing_placement)
|
69
|
+
end
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
|
73
|
+
def threat(placement)
|
74
|
+
return placement.cells.inject(0) {|threat, cell| threat + @heat_map[cell]}
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'white_horseman/battlefield'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module WhiteHorseman
|
5
|
+
class Opponent
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def clear_all
|
10
|
+
Dir.glob("#{self.data_path}/*").each do |filename|
|
11
|
+
File.delete filename
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def initialize(name)
|
18
|
+
@name = name
|
19
|
+
if File.exists?(filename)
|
20
|
+
@games = YAML.load_file(filename)
|
21
|
+
else
|
22
|
+
@games = []
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_game
|
27
|
+
@games << Battlefield.new
|
28
|
+
return @games.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def record_target(coordinates)
|
32
|
+
@games.last.record_target(coordinates)
|
33
|
+
end
|
34
|
+
|
35
|
+
def number_of_games
|
36
|
+
return @games.size
|
37
|
+
end
|
38
|
+
|
39
|
+
def game_grid(id)
|
40
|
+
@games[id - 1].grid
|
41
|
+
end
|
42
|
+
|
43
|
+
def save
|
44
|
+
Dir.mkdir(self.class.data_path) unless File.exists?(self.class.data_path)
|
45
|
+
data_file = File.new(filename, "w")
|
46
|
+
data_file.write @games.to_yaml
|
47
|
+
data_file.close
|
48
|
+
end
|
49
|
+
|
50
|
+
def heat_map
|
51
|
+
if @games.empty?
|
52
|
+
return Battlefield.new
|
53
|
+
else
|
54
|
+
return Battlefield.average(@games)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def self.data_path
|
62
|
+
return DATA_DIR
|
63
|
+
end
|
64
|
+
|
65
|
+
def filename
|
66
|
+
"#{self.class.data_path}/#{@name}.data"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'white_horseman/coordinates'
|
2
|
+
|
3
|
+
module WhiteHorseman
|
4
|
+
class Placement
|
5
|
+
attr_accessor :orientation, :size, :coord
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@coord = Coordinates.new
|
9
|
+
options.each do |option, value|
|
10
|
+
self.send("#{option}=".to_sym, value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.each(size, &block)
|
15
|
+
Coordinates.each do |coordinate|
|
16
|
+
[:horizontal, :vertical].each do |orientation|
|
17
|
+
placement = Placement.new(:coord => coordinate, :orientation => orientation, :size => size)
|
18
|
+
if placement.valid?
|
19
|
+
yield(placement)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def coord=(coord)
|
26
|
+
@coord = Coordinates.new(:string => coord.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def row=(row)
|
30
|
+
@coord.row = row
|
31
|
+
end
|
32
|
+
|
33
|
+
def column=(column)
|
34
|
+
@coord.column = column
|
35
|
+
end
|
36
|
+
|
37
|
+
def row
|
38
|
+
@coord.row
|
39
|
+
end
|
40
|
+
|
41
|
+
def column
|
42
|
+
@coord.column
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
"#{row}#{column} #{orientation}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid?
|
50
|
+
return false unless [:horizontal, :vertical].include?(@orientation)
|
51
|
+
return false if end_row > "J" && @orientation == :vertical
|
52
|
+
return false if end_column > 10 && @orientation == :horizontal
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
def end_row
|
57
|
+
return "%c" % (@coord.row[0] + @size - 1)
|
58
|
+
end
|
59
|
+
|
60
|
+
def end_column
|
61
|
+
return @coord.column + @size - 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def overlap?(other)
|
65
|
+
return !(self.cells & other.cells).empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def cells
|
69
|
+
cells = []
|
70
|
+
for_each_cell {|cell| cells << cell.to_s}
|
71
|
+
return cells
|
72
|
+
end
|
73
|
+
|
74
|
+
def include?(coord)
|
75
|
+
return cells.include?(coord.to_s)
|
76
|
+
end
|
77
|
+
|
78
|
+
private #########################################################
|
79
|
+
def for_each_cell(&block)
|
80
|
+
coord_copy = Coordinates.new(:string => @coord.to_s)
|
81
|
+
if @orientation == :horizontal
|
82
|
+
yield(coord_copy)
|
83
|
+
(size-1).times do
|
84
|
+
coord_copy.inc_column
|
85
|
+
yield(coord_copy)
|
86
|
+
end
|
87
|
+
elsif @orientation == :vertical
|
88
|
+
yield(coord_copy)
|
89
|
+
(size-1).times do
|
90
|
+
coord_copy.inc_row
|
91
|
+
yield(coord_copy)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "white_horseman/coordinates"
|
2
|
+
require "white_horseman/placement"
|
3
|
+
|
4
|
+
module WhiteHorseman
|
5
|
+
class Scout
|
6
|
+
def possible_placements(size, coordinates, hit_board)
|
7
|
+
placements = []
|
8
|
+
first = coordinates.first.to_coord
|
9
|
+
placements += placements(size, first.clone, coordinates, :horizontal)
|
10
|
+
placements += placements(size, first.clone, coordinates, :vertical)
|
11
|
+
scrub(placements, hit_board)
|
12
|
+
|
13
|
+
return placements.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def salvage_placements(size, coordinate, hit_board)
|
17
|
+
placements = []
|
18
|
+
first = coordinate.to_coord
|
19
|
+
placements += placements(size, first.clone, [coordinate], :horizontal)
|
20
|
+
placements += placements(size, first.clone, [coordinate], :vertical)
|
21
|
+
return select_all_hits(placements, hit_board)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private ################################################################################
|
26
|
+
|
27
|
+
def placements(size, start, coordinates, orientation)
|
28
|
+
return [] unless Coordinates.colinear?(coordinates, orientation)
|
29
|
+
|
30
|
+
itr = Coordinates.new(:string => start.to_s)
|
31
|
+
placements = []
|
32
|
+
size.times do
|
33
|
+
placement = Placement.new(:coord => itr, :orientation => orientation, :size => size)
|
34
|
+
if placement.valid? && coordinates.all?{|cell| placement.include?(cell)}
|
35
|
+
placements << placement
|
36
|
+
end
|
37
|
+
itr.inc_row -1 if orientation == :vertical rescue break
|
38
|
+
itr.inc_column -1 if orientation == :horizontal rescue break
|
39
|
+
end
|
40
|
+
return placements
|
41
|
+
end
|
42
|
+
|
43
|
+
def scrub(placements, hit_board)
|
44
|
+
doomed = []
|
45
|
+
placements.each do |placement|
|
46
|
+
placement.cells.each do |cell|
|
47
|
+
if (hit_board[cell].miss? || hit_board[cell].has_ship?)
|
48
|
+
doomed << placement
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
doomed.each {|dead| placements.delete(dead) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def select_all_hits(placements, hit_board)
|
56
|
+
all_hits = []
|
57
|
+
placements.each do |placement|
|
58
|
+
if placement.cells.all?{|cell| hit_board[cell].hit?}
|
59
|
+
all_hits << placement
|
60
|
+
end
|
61
|
+
end
|
62
|
+
return all_hits
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|