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.
@@ -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