commodore_cox 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
@@ -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 = "commodore_cox"
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['**/*'].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:Commodore Cox"
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 = "Commodore Cox"
34
+ s.author = "Andrew Cox"
35
+ s.email = "3coxy4@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,264 @@
1
+ class Array
2
+ def random
3
+ self[size*rand]
4
+ end
5
+ end
6
+
7
+ module CommodoreCox
8
+
9
+ class AI
10
+
11
+ SHIPS = {
12
+ :carrier => 5,
13
+ :battleship => 4,
14
+ :destroyer => 3,
15
+ :submarine => 3,
16
+ :patrolship => 2
17
+ }
18
+
19
+ OPPOSITE = {
20
+ :north => :south,
21
+ :east => :west,
22
+ :south => :north,
23
+ :west => :east
24
+ }
25
+
26
+ attr_accessor :placestack, :direction, :hitstack
27
+
28
+ def setup
29
+ @hitstack = []
30
+ @placestack = []
31
+ @taken_squares = []
32
+ @backtracking_stack = []
33
+ @direction = nil
34
+ a = (1..10).to_a
35
+ a.each do |x|
36
+ a.each do |y|
37
+ Grid[y,x] = Square.new(y,x)
38
+ end
39
+ end
40
+ end
41
+
42
+ def hit_in_progress?
43
+ @hitstack.any?
44
+ end
45
+
46
+ def backtracking?
47
+ @backtracking_stack.any?
48
+ end
49
+
50
+ def direction?
51
+ !@direction.nil?
52
+ end
53
+
54
+ def ships
55
+ SHIPS
56
+ end
57
+
58
+ def opposite
59
+ OPPOSITE
60
+ end
61
+
62
+ def last_placement
63
+ @placestack.last.to_s
64
+ end
65
+
66
+ def change_direction
67
+ @direction = opposite[@direction]
68
+ end
69
+
70
+ def pick_from_random_neighbour(square,set_direction=true)
71
+ neighbour = nil
72
+ neighbours = square.neighbours
73
+ if neighbours.any?
74
+ neighbour = neighbours.random
75
+ @direction = neighbour[0] if set_direction
76
+ neighbour = neighbour[1]
77
+ Grid.delete(neighbour)
78
+ end
79
+ neighbour
80
+ end
81
+
82
+ def pick_from_backtrack
83
+ return nil unless backtracking?
84
+
85
+ square = if direction?
86
+ @backtracking_stack.last.send(@direction)
87
+ else
88
+ pick_from_random_neighbour(@backtracking_stack.last,false)
89
+ end
90
+ if square.nil?
91
+ @backtracking_stack.pop
92
+ square = pick_from_backtrack
93
+ end
94
+ square
95
+ end
96
+
97
+ def pick_from_direction
98
+ square = @hitstack.last.send(@direction)
99
+ if square.nil?
100
+ change_direction
101
+ square = @hitstack.first.send(@direction)
102
+ if square.nil?
103
+ @direction = nil
104
+ square = smart_next_square
105
+ end
106
+ end
107
+ square
108
+ end
109
+
110
+ def smart_next_square
111
+ return nil unless hit_in_progress?
112
+ square = if backtracking?
113
+ pick_from_backtrack
114
+ elsif direction?
115
+ pick_from_direction
116
+ else
117
+ pick_from_random_neighbour(@hitstack.last)
118
+ end
119
+ unless square.nil?
120
+ Grid.delete(square)
121
+ end
122
+ square
123
+ end
124
+
125
+ def next_target(force=nil)
126
+ square = nil
127
+ unless force.nil?
128
+ square = Grid[force]
129
+ Grid.delete(force)
130
+ else
131
+ square = smart_next_square || Grid.pick
132
+ end
133
+ @placestack << square
134
+ square.to_s unless square.nil?
135
+ end
136
+
137
+ def target_result(coordinates, was_hit, ship_sunk)
138
+ if was_hit
139
+ @hitstack << @placestack.last
140
+ end
141
+ if ship_sunk
142
+ # assuming the ship sunk was the last n squares on the stack
143
+ SHIPS[ship_sunk].times{@hitstack.pop}
144
+ @backtracking_stack = @hitstack.reverse
145
+ if @hitstack.size > 1
146
+ change_direction
147
+ else
148
+ @direction = nil
149
+ end
150
+ end
151
+ end
152
+
153
+
154
+
155
+ def place(ship,square=false,direction=false)
156
+ length = SHIPS[ship]
157
+ directions = {:x=>"horizontal",:y=>"vertical"}
158
+ direction ||= directions.keys.random
159
+ square = if square
160
+ Grid[square]
161
+ else
162
+ Grid.slice(length,direction,@taken_squares).random
163
+ end
164
+ @taken_squares << square.to_s
165
+ trying_squares = []
166
+ next_square = square
167
+ (length-1).times do
168
+ next_square = next_square.send(directions[direction])
169
+ return place(ship) if @taken_squares.include?(next_square.to_s)
170
+ trying_squares << next_square.to_s
171
+ end
172
+ @taken_squares += trying_squares
173
+ "#{square.to_s} #{directions[direction]}"
174
+ end
175
+
176
+ end
177
+
178
+ class Grid
179
+
180
+ @@ROWS = %w{ A B C D E F G H I J }
181
+ @@squares = {}
182
+
183
+ class << self
184
+ def [](y,x=nil)
185
+ y = index(y.ceil) unless y.is_a? String
186
+ x = x.ceil unless x.nil?
187
+ squares["#{y}#{x}"]
188
+ end
189
+ def []=(y,x,s)
190
+ y = index(y.ceil) unless y.is_a? String
191
+ squares["#{y}#{x.ceil}"] = s
192
+ end
193
+
194
+ def random
195
+ s = squares.to_a
196
+ s.random unless s.nil?
197
+ end
198
+
199
+ def pick
200
+ s = random
201
+ unless s.nil?
202
+ delete(s[0])
203
+ s[1]
204
+ end
205
+ end
206
+
207
+ def slice(length,direction,taken_squares=[])
208
+ n = 9 - length
209
+ @@squares.values.find_all do |square|
210
+ (1..n).include?(square.send(direction)) && !taken_squares.include?(square.to_s)
211
+ end
212
+ end
213
+
214
+ def delete(square)
215
+ @@squares.delete(square.to_s)
216
+ end
217
+
218
+ def squares
219
+ @@squares
220
+ end
221
+ def index(n)
222
+ @@ROWS[n-1] unless n < 1
223
+ end
224
+
225
+ end
226
+
227
+ end
228
+
229
+ class Square
230
+
231
+ attr_accessor :y, :x
232
+
233
+ def initialize(y,x)
234
+ @y, @x = y, x
235
+ end
236
+
237
+ def north
238
+ Grid[y-1,x]
239
+ end
240
+ def east
241
+ Grid[y,x+1]
242
+ end
243
+ def south
244
+ Grid[y+1,x]
245
+ end
246
+ def west
247
+ Grid[y,x-1]
248
+ end
249
+
250
+ # for use in ship placements
251
+ alias :horizontal :east
252
+ alias :vertical :south
253
+
254
+ def neighbours
255
+ h = { :north => north, :east => east, :west => west, :south => south }
256
+ h.delete_if{ |k,v| v.nil? }
257
+ h.to_a
258
+ end
259
+ def to_s
260
+ "#{Grid.index(y)}#{x}"
261
+ end
262
+ end
263
+
264
+ end
@@ -0,0 +1,55 @@
1
+ require 'commodore_cox/ai'
2
+ module CommodoreCox
3
+
4
+ class CommodoreCox
5
+
6
+ def new_game(opponent_name)
7
+ @AI.setup
8
+ end
9
+
10
+ def carrier_placement
11
+ @AI.place :carrier
12
+ end
13
+
14
+ def battleship_placement
15
+ @AI.place :battleship
16
+ end
17
+
18
+ def destroyer_placement
19
+ @AI.place :destroyer
20
+ end
21
+
22
+ def submarine_placement
23
+ @AI.place :submarine
24
+ end
25
+
26
+ def patrolship_placement
27
+ @AI.place :patrolship
28
+ end
29
+
30
+ def next_target
31
+ @AI.next_target
32
+ end
33
+
34
+ def target_result(coordinates, was_hit, ship_sunk)
35
+ @AI.target_result(coordinates, was_hit, ship_sunk)
36
+ end
37
+
38
+ def enemy_targeting(coordinates)
39
+ end
40
+
41
+ def game_over(result, disqualification_reason=nil)
42
+ end
43
+
44
+ # Non API methods #####################################
45
+
46
+ attr_reader :opponent, :targets, :enemy_targeted_sectors, :result, :disqualification_reason #:nodoc:
47
+
48
+ def initialize #:nodoc:
49
+ @AI = AI.new
50
+ @AI.setup
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,108 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+ require 'commodore_cox/ai'
3
+
4
+ describe CommodoreCox::AI do
5
+
6
+ before(:each) do
7
+ @ai = CommodoreCox::AI.new
8
+ @ai.setup
9
+ end
10
+
11
+ it "should create random placements for all ships" do
12
+ @ai.ships.keys.each do |ship|
13
+ @ai.place(ship).should match /^[A-Z][0-9]{1,2}\s(horizontal|vertical)$/
14
+ end
15
+ end
16
+
17
+ it "should not overlap 2 ships" do
18
+ @ai.place(:carrier,"A6",:y).should eql("A6 vertical")
19
+ @ai.place(:destroyer,"D5",:x).should_not eql("D5 horizontal")
20
+ end
21
+
22
+ it "should generate a next target 100 times" do
23
+ 100.times{ @ai.next_target.should_not be_nil }
24
+ end
25
+
26
+ it "should not generate a 101st target" do
27
+ 100.times{ @ai.next_target.should_not be_nil }
28
+ @ai.next_target.should be_nil
29
+ end
30
+
31
+ it "should choose a neighbour on hit" do
32
+ @ai.next_target
33
+ square = @ai.placestack.last
34
+ neighbours = square.neighbours.map{ |e| e[1].to_s }
35
+ @ai.target_result(square.to_s,true,nil)
36
+ neighbours.should include(@ai.next_target)
37
+ end
38
+
39
+ it "should choose a neighbour in the correct direction in 2 successive hits" do
40
+ square = nil
41
+ 2.times do
42
+ @ai.next_target
43
+ square = @ai.placestack.last
44
+ @ai.target_result("",true,nil)
45
+ end
46
+ @ai.direction.should_not be_nil
47
+ next_square = square.send(@ai.direction)
48
+ @ai.next_target.should eql(next_square.to_s) unless next_square.nil?
49
+ end
50
+
51
+ it "should change direction and go back to the first hit on 2 successive hits, then a miss" do
52
+ @ai.next_target("E5")
53
+ @ai.target_result("",true,nil)
54
+ @ai.next_target
55
+ @ai.target_result("",true,nil)
56
+ direction = @ai.direction
57
+ @ai.next_target
58
+ @ai.target_result("",false,nil)
59
+ @ai.next_target
60
+ @ai.direction.should eql(@ai.opposite[direction])
61
+ end
62
+
63
+ it "should reset on sink" do
64
+ @ai.next_target("I2")
65
+ @ai.target_result("",true,nil)
66
+ @ai.next_target("J2")
67
+ @ai.target_result("",true,nil)
68
+ @ai.direction = :south
69
+ @ai.next_target
70
+ @ai.last_placement.should eql("H2")
71
+ @ai.target_result("",true,nil)
72
+ @ai.next_target
73
+ @ai.last_placement.should eql("G2")
74
+ @ai.target_result("",true,:battleship)
75
+ @ai.hitstack.size.should eql(0)
76
+ end
77
+
78
+ it "should backtrack to one of first hit's neighbours after sink with remaining hit" do
79
+ @ai.next_target("A2")
80
+ @ai.target_result("",true,nil)
81
+ @ai.next_target("A3")
82
+ @ai.target_result("",true,nil)
83
+ @ai.direction = :east
84
+ @ai.next_target("A4")
85
+ @ai.target_result("",true,nil)
86
+ @ai.next_target("A5")
87
+ @ai.target_result("",true,:submarine)
88
+ @ai.backtracking?.should be_true
89
+ @ai.next_target
90
+ ["A1","B2"].should include(@ai.last_placement)
91
+ end
92
+
93
+ it "should backtrack in reverse direction after sink with remaining hits" do
94
+ @ai.next_target("A2")
95
+ @ai.target_result("",true,nil)
96
+ @ai.next_target("A3")
97
+ @ai.target_result("",true,nil)
98
+ @ai.direction = :east
99
+ @ai.next_target("A4")
100
+ @ai.target_result("",true,nil)
101
+ @ai.next_target("A5")
102
+ @ai.target_result("",true,:patrolship)
103
+ @ai.backtracking?.should be_true
104
+ @ai.next_target
105
+ @ai.last_placement.should eql("A1")
106
+ end
107
+
108
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+ require 'commodore_cox/commodore_cox'
3
+
4
+ describe CommodoreCox::CommodoreCox do
5
+
6
+ before(:each) do
7
+ @cc = CommodoreCox::CommodoreCox.new
8
+ end
9
+
10
+ end
@@ -0,0 +1,4 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+
3
+ require 'rubygems'
4
+ require 'spec'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: commodore_cox
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Cox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-21 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Commodore Cox
17
+ email: 3coxy4@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - Battleship.Rakefile
26
+ - lib
27
+ - lib/commodore_cox
28
+ - lib/commodore_cox/ai.rb
29
+ - lib/commodore_cox/commodore_cox.rb
30
+ - pkg
31
+ - Rakefile
32
+ - spec
33
+ - spec/commodore_cox
34
+ - spec/commodore_cox/ai_spec.rb
35
+ - spec/commodore_cox/commodore_cox_spec.rb
36
+ - spec/spec_helper.rb
37
+ has_rdoc: false
38
+ homepage: http://sparring.rubyforge.org/
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project: sparring
59
+ rubygems_version: 1.3.1
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: Battleship Player:Commodore Cox
63
+ test_files: []
64
+