white_horseman 1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|