ssoroka_takes_the_win 1.0

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 = "ssoroka_takes_the_win"
13
+ PKG_VERSION = "1.0"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = PKG_NAME
17
+ s.version = PKG_VERSION
18
+ s.files = FileList['**/*'].reject{|f| f =~ /^pkg/}.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:ssoroka takes the win"
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 = "A battleship player"
34
+ s.author = "Steven Soroka"
35
+ s.email = "ssoroka78@gmail.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,306 @@
1
+ module SsorokaTakesTheWin
2
+
3
+ # Battleship Player
4
+ #
5
+ # Battleship is board game between two players. See http://en.wikipedia.org/wiki/Battleship for more information and
6
+ # game rules.
7
+ #
8
+ # A player represents the conputer AI to play a game of Battleship. It should know how to place ships and target
9
+ # the opponents ships.
10
+ #
11
+ # This version of Battleship is played on a 10 x 10 grid where rows are labled by the letters A - J and
12
+ # columns are labled by the numbers 1 - 10. At the start of the game, each player will be asked for ship placements.
13
+ # Once the ships are placed, play proceeeds by each player targeting one square on their opponents map. A player
14
+ # may only target one square, reguardless of whether it resulted in a hit or not, before changing turns with her opponent.
15
+ #
16
+ class SsorokaTakesTheWin
17
+
18
+ # This method is called at the beginning of each game. A player may only be instantiated once and used to play many games.
19
+ # So new_game should reset any internal state acquired in previous games so that it is prepared for a new game.
20
+ #
21
+ # The name of the opponent player is passed in. This allows for the possibility to learn opponent strategy and
22
+ # play the game differently based on the opponent.
23
+ #
24
+ def new_game(opponent_name)
25
+ reset
26
+ end
27
+
28
+ # Returns the placement of the carrier. A carrier consumes 5 squares.
29
+ #
30
+ # The return value is a string that describes the placements of the ship.
31
+ # The placement string must be in the following format:
32
+ #
33
+ # "#{ROW}#{COL} #{ORIENTATION}"
34
+ #
35
+ # eg
36
+ #
37
+ # A1 horizontal # the ship will occupy A1, A2, A3, A4, and A5
38
+ # A1 vertical # the ship will occupy A1, B1, C1, D1, and E1
39
+ # F5 horizontal # the ship will occupy F5, F6, F7, F8, and F9
40
+ # F5 vertical # the ship will occupy F5, G5, H5, I5, and J5
41
+ #
42
+ # The ship must not fall off the edge of the map. For example, a carrier placement of 'A8 horizontal' would
43
+ # not leave enough space in the A row to accomidate the carrier since it requires 5 squares.
44
+ #
45
+ # Ships may not overlap with other ships. For example a carrier placement of 'A1 horizontal' and a submarine
46
+ # placement of 'A1 vertical' would be invalid because bothe ships are trying to occupy the square A1.
47
+ #
48
+ # Invalid ship placements will result in disqualification of the player.
49
+ #
50
+ def carrier_placement
51
+ return "G1 horizontal"
52
+ end
53
+
54
+ # Returns the placement of the battleship. A battleship consumes 4 squares.
55
+ #
56
+ # See carrier_placement for details on ship placement
57
+ #
58
+ def battleship_placement
59
+ return "F8 vertical"
60
+ end
61
+
62
+ # Returns the placement of the destroyer. A destroyer consumes 3 squares.
63
+ #
64
+ # See carrier_placement for details on ship placement
65
+ #
66
+ def destroyer_placement
67
+ return "C5 vertical"
68
+ end
69
+
70
+ # Returns the placement of the submarine. A submarine consumes 3 squares.
71
+ #
72
+ # See carrier_placement for details on ship placement
73
+ #
74
+ def submarine_placement
75
+ return "B7 horizontal"
76
+ end
77
+
78
+ # Returns the placement of the patrolship. A patrolship consumes 2 squares.
79
+ #
80
+ # See carrier_placement for details on ship placement
81
+ #
82
+ def patrolship_placement
83
+ return "I3 horizontal"
84
+ end
85
+
86
+ # Returns the coordinates of the players next target. This method will be called once per turn. The player
87
+ # should return target coordinates as a string in the form of:
88
+ #
89
+ # "#{ROW}#{COL}"
90
+ #
91
+ # eg
92
+ #
93
+ # A1 # the square in Row A and Column 1
94
+ # F5 # the square in Row F and Column 5
95
+ #
96
+ # Since the map contains only 10 rows and 10 columns, the ROW should be A, B, C, D, E, F, G H, I, or J. And the
97
+ # COL should be 1, 2, 3, 4, 5, 6, 7, 8, 9, or 10
98
+ #
99
+ # Returning coordinates outside the range or in an invalid format will result in the players disqualification.
100
+ #
101
+ # It is illegal to target a sector more than once. Doing so will also result in disqualification.
102
+ #
103
+ def next_target
104
+ target = target_for_current_shot
105
+ @shots_taken += 1
106
+ return target
107
+ end
108
+
109
+ # target_result will be called by the system after a call to next_target. The paramters supplied inform the player
110
+ # of the results of the target.
111
+ #
112
+ # coordinates : string. The coordinates targeted. It will be the same value returned by the previous call to next_target
113
+ # was_hit : boolean. true if the target was occupied by a ship. false otherwise.
114
+ # ship_sunk : symbol. nil if the target did not result in the sinking of a ship. If the target did result in
115
+ # in the sinking of a ship, the ship type is supplied (:carrier, :battleship, :destroyer, :submarine, :patrolship).
116
+ #
117
+ # An intelligent player will use the information to better play the game. For example, if the result indicates a
118
+ # hit, a player my choose to target neighboring squares to hit and sink the remainder of the ship.
119
+ #
120
+ def target_result(coordinates, was_hit, ship_sunk)
121
+ @grid.set(@last_r, @last_c, ship_sunk || (was_hit ? :hit : :miss))
122
+ if ship_sunk
123
+ @ships_left -= [ship_sunk]
124
+ # try to mark all the hits with ship type
125
+ DIRECTIONS.each{|y, x|
126
+ all_hits = true
127
+ (1..(SHIP_TYPES[ship_sunk]-1)).each{|p|
128
+ r = @last_r + y * p
129
+ c = @last_c + x * p
130
+ all_hits = false unless @grid.valid_target?(r, c) && @grid.get(r, c) == :hit
131
+ }
132
+ if all_hits
133
+ (1..(SHIP_TYPES[ship_sunk]-1)).each{|p|
134
+ r = @last_r + y * p
135
+ c = @last_c + x * p
136
+ @grid.set(r, c, ship_sunk)
137
+ }
138
+ break
139
+ end
140
+ }
141
+ end
142
+ end
143
+
144
+ # enemy_targeting is called by the system to inform a player of their apponents move. When the opponent targets
145
+ # a square, this method is called with the coordinates.
146
+ #
147
+ # Players may use this information to understand an opponents targeting strategy and place ships differently
148
+ # in subsequent games.
149
+ #
150
+ def enemy_targeting(coordinates)
151
+ end
152
+
153
+ # Called by the system at the end of a game to inform the player of the results.
154
+ #
155
+ # result : 1 of 3 possible values (:victory, :defeate, :disqualified)
156
+ # disqualification_reason : nil unless the game ended as the result of a disqualification. In the event of a
157
+ # disqualification, this paramter will hold a string description of the reason for disqualification. Both
158
+ # players will be informed of the reason.
159
+ #
160
+ # :victory # indicates the player won the game
161
+ # :defeat # indicates the player lost the game
162
+ # :disqualified # indicates the player was disqualified
163
+ #
164
+ def game_over(result, disqualification_reason=nil)
165
+ end
166
+
167
+ # Non API methods #####################################
168
+
169
+ attr_reader :opponent, :targets, :enemy_targeted_sectors, :result, :disqualification_reason #:nodoc:
170
+
171
+ def initialize #:nodoc:
172
+ reset
173
+ end
174
+
175
+ private ###############################################
176
+
177
+ SHIP_TYPES = {:carrier => 5, :battleship => 4, :destroyer => 3,
178
+ :submarine => 3, :patrolship => 2}
179
+ DIRECTIONS = [[-1, 0], [0, 1], [1, 0], [0, -1]]
180
+
181
+ def reset
182
+ @shots_taken = 0
183
+ @grid = Grid.new
184
+ @ships_left = SHIP_TYPES.keys
185
+ @ships_dead = []
186
+ end
187
+
188
+ ROWS = %w{ A B C D E F G H I J }
189
+ def target_for_current_shot
190
+ # r, c = destroy_hit_ships || find_ships || random_shot
191
+ score_grid
192
+ @last_r, @last_c = pick_best
193
+ coord_to_target(@last_r, @last_c)
194
+ end
195
+
196
+ def coord_to_target(r, c)
197
+ "#{ROWS[r]}#{c + 1}"
198
+ end
199
+
200
+ def score_grid
201
+ n = smallest_ship_size
202
+ @grid_score = Grid.new
203
+ @grid_score.each_cell{|row, col|
204
+ @grid_score.set(row, col, 0)
205
+ # - 1000 if already shot at
206
+ if @grid.get(row, col)
207
+ @grid_score.dec(row, col, 1000)
208
+ else
209
+ # + 100 if there's a damaged ship nearby
210
+ DIRECTIONS.each{|dir_y, dir_x|
211
+ r = row + dir_y
212
+ c = col + dir_x
213
+ if @grid.valid_target?(r, c) && @grid.get(r, c) == :hit
214
+ @grid_score.inc(row, col, 100)
215
+ r = row + dir_y * 2
216
+ c = col + dir_x * 2
217
+ @grid_score.inc(row, col, 200) if @grid.valid_target?(r, c) && @grid.get(r, c) == :hit
218
+ end
219
+ }
220
+
221
+ # - 10 if there's a guess in a direction within n - 1
222
+ DIRECTIONS.each{|dir_y, dir_x|
223
+ has_close_guess = false
224
+ (1..n-1).each{|multiplier|
225
+ r = row + (dir_y * multiplier)
226
+ c = col + (dir_x * multiplier)
227
+ has_close_guess = true if @grid.valid_target?(r, c) && @grid.get(r, c)
228
+ }
229
+ @grid_score.dec(row, col, 10) if has_close_guess
230
+ }
231
+ end
232
+ }
233
+ end
234
+
235
+ def pick_best
236
+ best_score = -999
237
+ best_coords = [[0, 0]]
238
+ @grid_score.each_cell{|row, col, cell|
239
+ if cell > best_score
240
+ best_score = cell
241
+ best_coords = [[row, col]]
242
+ elsif cell == best_score
243
+ best_coords << [row, col]
244
+ end
245
+ }
246
+ best_coords[rand(best_coords.size)]
247
+ end
248
+
249
+ def smallest_ship_size
250
+ # the size of the smallest ship left
251
+ @ships_left.inject(5) {|smallest, ship|
252
+ smallest = (SHIP_TYPES[ship] < smallest) ? SHIP_TYPES[ship] : smallest
253
+ }
254
+ end
255
+
256
+ def random_shot
257
+ begin
258
+ r, c = rand(10), rand(10)
259
+ end until !@grid[r][c]
260
+ [r, c]
261
+ end
262
+ end
263
+
264
+ class Grid
265
+ def initialize
266
+ reset
267
+ end
268
+
269
+ def reset
270
+ @grid = Array.new(10)
271
+ @grid.each_with_index{|a, i| @grid[i] = Array.new(10) }
272
+ end
273
+
274
+ def valid_target?(row, col)
275
+ (0..9).include?(row) && (0..9).include?(col)
276
+ end
277
+
278
+ def each_cell
279
+ (0..9).each{|row|
280
+ (0..9).each{|col|
281
+ yield row, col, @grid[row][col]
282
+ }
283
+ }
284
+ end
285
+
286
+ def get(row, col)
287
+ @grid[row][col]
288
+ end
289
+
290
+ def [](row, col)
291
+ @grid[row][col]
292
+ end
293
+
294
+ def set(row, col, val)
295
+ @grid[row][col] = val
296
+ end
297
+
298
+ def dec(row, col, val = 1)
299
+ @grid[row][col] -= val
300
+ end
301
+
302
+ def inc(row, col, val = 1)
303
+ @grid[row][col] += val
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,4 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+
3
+ require 'rubygems'
4
+ require 'spec'
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+ require 'ssoroka_takes_the_win/ssoroka_takes_the_win'
3
+
4
+ describe SsorokaTakesTheWin::SsorokaTakesTheWin do
5
+
6
+ before(:each) do
7
+ @game = SsorokaTakesTheWin::SsorokaTakesTheWin.new
8
+ @grid = @game.instance_variable_get("@grid")
9
+ end
10
+
11
+ def run_scores
12
+ @game.send :score_grid
13
+ @grid_score = @game.instance_variable_get("@grid_score")
14
+ end
15
+
16
+ it "should be instantiable with no paramters" do
17
+
18
+ lambda { SsorokaTakesTheWin::SsorokaTakesTheWin.new }.should_not raise_error
19
+
20
+ end
21
+
22
+ it "should score cell 0, 0 poorly (-10) if the cell right of it is a miss" do
23
+ @grid = @game.instance_variable_get("@grid")
24
+ @grid.set(0, 1, :miss)
25
+ run_scores
26
+ @grid_score.get(0, 0).should == -10
27
+ end
28
+
29
+ it "should score cell 0, 0 poorly (-20) if the cells right and below it are a miss" do
30
+ @grid.set(0, 1, :miss)
31
+ @grid.set(1, 0, :miss)
32
+ run_scores
33
+ @grid_score.get(0, 0).should == -20
34
+ end
35
+
36
+ it "should not pick cells that you've previously shot at" do
37
+ (0..99).to_a.each{|i|
38
+ if i != 50
39
+ @grid.set(i / 10, i % 10, :miss)
40
+ end
41
+ }
42
+ run_scores
43
+ best = @game.send :pick_best
44
+ best.should == [5, 0]
45
+ end
46
+
47
+ it "should guess next to a hit" do
48
+ @grid.set(0, 1, :hit)
49
+ @grid.set(1, 0, :hit)
50
+ @grid.set(1, 1, :miss)
51
+ run_scores
52
+ best = @game.send :pick_best
53
+ best.should == [0, 0]
54
+ end
55
+
56
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssoroka_takes_the_win
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Steven Soroka
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-01 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A battleship player
17
+ email: ssoroka78@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - Battleship.Rakefile
26
+ - lib
27
+ - lib/ssoroka_takes_the_win
28
+ - lib/ssoroka_takes_the_win/ssoroka_takes_the_win.rb
29
+ - Rakefile
30
+ - spec
31
+ - spec/spec_helper.rb
32
+ - spec/ssoroka_takes_the_win
33
+ - spec/ssoroka_takes_the_win/ssoroka_takes_the_win_spec.rb
34
+ has_rdoc: false
35
+ homepage: http://sparring.rubyforge.org/
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project: sparring
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Battleship Player:ssoroka takes the win
60
+ test_files: []
61
+