white_horseman 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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