ensign_erratic 1.0.2

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
@@ -0,0 +1,41 @@
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 = "ensign_erratic"
13
+ PKG_VERSION = "1.0.2"
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:Ensign Erratic"
24
+ s.rubyforge_project = "sparring"
25
+ s.homepage = "http://sparring.rubyforge.org/"
26
+
27
+ s.description = "Relatively simple Battleship AI that utilizes random targeting and ship placement"
28
+ s.author = "Jim Suchy"
29
+ s.email = "jim@8thlight.com"
30
+ end
31
+
32
+ Rake::GemPackageTask.new(spec) do |pkg|
33
+ pkg.need_zip = false
34
+ pkg.need_tar = false
35
+ end
36
+
37
+ desc "Submit your player"
38
+ task :submit do
39
+ submitter = BattleshipTournament::Submit.new(PKG_NAME)
40
+ submitter.submit
41
+ end
@@ -0,0 +1,56 @@
1
+ module EnsignErratic
2
+ class Board
3
+
4
+ attr_reader :placed_ship_sectors
5
+
6
+ def initialize
7
+ @placed_ship_sectors = []
8
+ end
9
+
10
+ Row = %w{ A B C D E F G H I J }
11
+ PlacementRegex = /(([A-J])([1-9]|10)) (HORIZONTAL|VERTICAL)/i
12
+ CoordinatesRegex = /([A-J])([1-9|10])/i
13
+
14
+ def clear
15
+ @placed_ship_sectors = []
16
+ end
17
+
18
+ def place_ship(size, placement)
19
+ @possible_ship_sectors = []
20
+
21
+ match = PlacementRegex.match(placement)
22
+ start_coordinates = match[1]
23
+ row = match[2]
24
+ column = match[3].to_i
25
+ orientation = match[4].upcase
26
+
27
+ add_sector(start_coordinates)
28
+ (size - 1).times do |index|
29
+ new_coordinates = orientation == "HORIZONTAL" ? move_right(row, column, index) : move_down(row, column, index)
30
+ add_sector(new_coordinates)
31
+ end
32
+
33
+ @placed_ship_sectors.concat(@possible_ship_sectors)
34
+ end
35
+
36
+ private ###############################################
37
+
38
+ def add_sector(coordinates)
39
+ raise "Ships overlap at #{coordinates}" if @placed_ship_sectors.include?(coordinates)
40
+ @possible_ship_sectors << coordinates
41
+ end
42
+
43
+ def move_right(row, column, number)
44
+ new_column = column + number + 1
45
+ raise "Ship would fall off right edge" if new_column > 10
46
+ return "#{row}#{new_column}"
47
+ end
48
+
49
+ def move_down(row, column, number)
50
+ new_row = Row[Row.index(row) + number + 1]
51
+ raise "Ship would fall off bottom edge" if new_row.nil?
52
+ return "#{new_row}#{column}"
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,206 @@
1
+ require 'ensign_erratic/board'
2
+
3
+ module EnsignErratic
4
+
5
+ class EnsignErratic
6
+
7
+ attr_reader :carrier_placement, :battleship_placement, :destroyer_placement, :submarine_placement, :patrolship_placement
8
+ attr_reader :opponent_name, :result, :disqualification_reason
9
+ attr_reader :turns, :enemy_targets, :hits
10
+
11
+ def name
12
+ return "Ensign Erratic"
13
+ end
14
+
15
+ def new_game(opponent_name)
16
+ @opponent_name = opponent_name
17
+ reset
18
+ setup_ships
19
+ end
20
+
21
+ def next_target
22
+ return seek_and_destroy? ? smart_target : random_target
23
+ end
24
+
25
+ def target_result(coordinates, was_hit, ship_sunk)
26
+ @turns << coordinates
27
+ @hits << coordinates if was_hit
28
+ calculate_seek_and_destroy(was_hit, ship_sunk)
29
+ end
30
+
31
+ def enemy_targeting(coordinates)
32
+ @enemy_targets << coordinates
33
+ end
34
+
35
+ def game_over(result, disqualification_reason=nil)
36
+ @result = result
37
+ @disqualification_reason = disqualification_reason
38
+ end
39
+
40
+ def seek_and_destroy?
41
+ return @seek_and_destroy
42
+ end
43
+
44
+ private ###############################################
45
+
46
+ def reset
47
+ @turns = []
48
+ @hits = []
49
+ @enemy_targets = []
50
+ @seek_and_destroy = false
51
+ @board = Board.new
52
+ Kernel.srand
53
+ end
54
+
55
+ def random_target
56
+ coordinates = random_coordinates
57
+ return already_targeted?(coordinates) ? random_target : coordinates
58
+ end
59
+
60
+ def smart_target
61
+ row, column = last_hit_coordinates
62
+
63
+ if @seek_and_destroy_direction == "left"
64
+ return seek_left(row, column)
65
+ elsif @seek_and_destroy_direction == "right"
66
+ return target_right(row, column.to_i)
67
+ elsif @seek_and_destroy_direction == "up"
68
+ return seek_up(row, column)
69
+ elsif @seek_and_destroy_direction == "down"
70
+ return target_down(row, column)
71
+ end
72
+ end
73
+
74
+ def target_left(row, column)
75
+ return "#{row}#{column.to_i - 1}"
76
+ end
77
+
78
+ def target_right(row, column)
79
+ column += 1
80
+ next_try = "#{row}#{column}"
81
+
82
+ while already_targeted?(next_try)
83
+ column += 1
84
+ next_try = "#{row}#{column}"
85
+ end
86
+
87
+ return next_try
88
+ end
89
+
90
+ def target_up(row, column)
91
+ row_above = Board::Row[Board::Row.index(row) - 1]
92
+ return "#{row_above}#{column}"
93
+ end
94
+
95
+ def target_down(row, column)
96
+ row_below = Board::Row[Board::Row.index(row) + 1]
97
+ next_try = "#{row_below}#{column}"
98
+ while already_targeted?(next_try)
99
+ row_below = Board::Row[Board::Row.index(row_below) + 1]
100
+ next_try = "#{row_below}#{column}"
101
+ end
102
+
103
+ return next_try
104
+ end
105
+
106
+ def seek_left(row, column)
107
+ next_target = target_left(row, column)
108
+ if valid_target?(next_target)
109
+ return next_target
110
+ else
111
+ return target_right(row, column.to_i)
112
+ end
113
+ end
114
+
115
+ def seek_up(row, column)
116
+ next_target = target_up(row, column)
117
+ if valid_target?(next_target)
118
+ return next_target
119
+ else
120
+ return target_down(row, column)
121
+ end
122
+ end
123
+
124
+ def calculate_seek_and_destroy(was_hit, ship_sunk)
125
+ if @seek_and_destroy
126
+ if ship_sunk
127
+ @seek_and_destroy = false
128
+ elsif !was_hit
129
+ if @seek_and_destroy_direction == "left"
130
+ @seek_and_destroy_direction = "right"
131
+ elsif @seek_and_destroy_direction == "right"
132
+ @seek_and_destroy_direction = "up"
133
+ elsif @seek_and_destroy_direction == "up"
134
+ @seek_and_destroy_direction = "down"
135
+ end
136
+ end
137
+ elsif was_hit
138
+ @seek_and_destroy = true
139
+ @seek_and_destroy_direction = "left"
140
+ end
141
+ end
142
+
143
+ def valid_target?(coordinates)
144
+ row, column = row_and_column_for(coordinates)
145
+ return false if column.to_i <= 0
146
+ return false if already_targeted?(coordinates)
147
+ return true
148
+ end
149
+
150
+ def already_targeted?(coordinates)
151
+ return @turns.include?(coordinates)
152
+ end
153
+
154
+ def last_hit_coordinates
155
+ return row_and_column_for(@hits.last)
156
+ end
157
+
158
+ def row_and_column_for(coordinates)
159
+ match = Board::CoordinatesRegex.match(coordinates)
160
+ return match[1], match[2]
161
+ end
162
+
163
+ def setup_ships
164
+ valid_placement = false
165
+ while !valid_placement
166
+ randomize_ship_placement
167
+ begin
168
+ place_ships
169
+ valid_placement = true
170
+ rescue Exception => e
171
+ # puts e.message
172
+ end
173
+ end
174
+ end
175
+
176
+ def randomize_ship_placement
177
+ @carrier_placement = random_ship_placement
178
+ @battleship_placement = random_ship_placement
179
+ @destroyer_placement = random_ship_placement
180
+ @submarine_placement = random_ship_placement
181
+ @patrolship_placement = random_ship_placement
182
+ end
183
+
184
+ def random_ship_placement
185
+ return "#{random_coordinates} #{random_orientation}"
186
+ end
187
+
188
+ def random_coordinates
189
+ return "#{Board::Row[Kernel.rand(10)]}#{Kernel.rand(10)+1}"
190
+ end
191
+
192
+ def random_orientation
193
+ return Kernel.rand(2) == 1 ? "horizontal" : "vertical"
194
+ end
195
+
196
+ def place_ships
197
+ @board.clear
198
+ @board.place_ship(5, @carrier_placement)
199
+ @board.place_ship(4, @battleship_placement)
200
+ @board.place_ship(3, @destroyer_placement)
201
+ @board.place_ship(3, @submarine_placement)
202
+ @board.place_ship(2, @patrolship_placement)
203
+ end
204
+
205
+ end
206
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+ require 'ensign_erratic/board'
3
+
4
+ describe EnsignErratic::Board do
5
+
6
+ before(:each) do
7
+ @board = EnsignErratic::Board.new
8
+ end
9
+
10
+ it "should place ship horizontally" do
11
+ @board.place_ship(5, "A1 horizontal")
12
+
13
+ @board.placed_ship_sectors.size.should == 5
14
+ @board.placed_ship_sectors.should include("A1")
15
+ @board.placed_ship_sectors.should include("A2")
16
+ @board.placed_ship_sectors.should include("A3")
17
+ @board.placed_ship_sectors.should include("A4")
18
+ @board.placed_ship_sectors.should include("A5")
19
+ end
20
+
21
+ it "should place ship vertically" do
22
+ @board.place_ship(3, "B1 vertical")
23
+
24
+ @board.placed_ship_sectors.size.should == 3
25
+ @board.placed_ship_sectors.should include("B1")
26
+ @board.placed_ship_sectors.should include("C1")
27
+ @board.placed_ship_sectors.should include("D1")
28
+ end
29
+
30
+ it "should raise exception if ships would overlap" do
31
+ @board.place_ship(5, "B2 horizontal")
32
+
33
+ lambda {@board.place_ship(3, "A3 vertical")}.should raise_error("Ships overlap at B3")
34
+ @board.placed_ship_sectors.size.should == 5
35
+ end
36
+
37
+ it "should raise exception if ship would fall off right edge" do
38
+ lambda {@board.place_ship(5, "A9 horizontal")}.should raise_error("Ship would fall off right edge")
39
+ @board.placed_ship_sectors.size.should == 0
40
+ end
41
+
42
+ it "should raise exception if ship would fall off bottom edge" do
43
+ lambda {@board.place_ship(5, "I3 vertical")}.should raise_error("Ship would fall off bottom edge")
44
+ @board.placed_ship_sectors.size.should == 0
45
+ end
46
+
47
+ it "should clear placed ship sectors" do
48
+ @board.place_ship(3, "B1 vertical")
49
+
50
+ @board.clear
51
+
52
+ @board.placed_ship_sectors.size.should == 0
53
+ end
54
+
55
+ end
@@ -0,0 +1,204 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+ require 'ensign_erratic/ensign_erratic'
3
+ require 'ensign_erratic/board'
4
+
5
+ describe EnsignErratic do
6
+
7
+ before(:each) do
8
+ EnsignErratic::Board.stub!(:new).and_return(mock("board", :place_ship => nil, :clear => nil))
9
+
10
+ @player = EnsignErratic::EnsignErratic.new
11
+ @player.new_game("some player")
12
+ end
13
+
14
+ it "should have a name" do
15
+ @player.name.should == "Ensign Erratic"
16
+ end
17
+
18
+ it "should store opponent's name" do
19
+ @player.new_game("your opponent")
20
+
21
+ @player.opponent_name.should == "your opponent"
22
+ end
23
+
24
+ it "should seed rng for each new game" do
25
+ Kernel.should_receive(:srand)
26
+
27
+ @player.new_game("foo")
28
+ end
29
+
30
+ it "should have game over" do
31
+ @player.game_over(:disqualified, "no reason")
32
+
33
+ @player.result.should == :disqualified
34
+ @player.disqualification_reason.should == "no reason"
35
+ end
36
+
37
+ it "should target a random location" do
38
+ Kernel.should_receive(:rand).with(10).twice.and_return(3, 7)
39
+
40
+ @player.next_target.should == "D8"
41
+ end
42
+
43
+ it "should not fire at a location that has already been fired at" do
44
+ @player.target_result("D8", false, nil)
45
+ @player.target_result("C6", false, nil)
46
+ Kernel.should_receive(:rand).with(10).and_return(3, 7, 2, 5, 1, 6)
47
+
48
+ @player.next_target.should == "B7"
49
+ end
50
+
51
+ it "should store whether opponent's target was a hit" do
52
+ @player.target_result("A1", true, nil)
53
+ @player.target_result("C8", false, nil)
54
+ @player.target_result("F4", true, nil)
55
+
56
+ @player.hits.size.should == 2
57
+ @player.hits[0].should == "A1"
58
+ @player.hits[1].should == "F4"
59
+ end
60
+
61
+ it "should store taken turns" do
62
+ @player.target_result("A1", true, nil)
63
+ @player.target_result("C8", false, nil)
64
+ @player.target_result("F4", true, nil)
65
+
66
+ @player.turns[0].should == "A1"
67
+ @player.turns[1].should == "C8"
68
+ @player.turns[2].should == "F4"
69
+ @player.hits[0].should == "A1"
70
+ @player.hits[1].should == "F4"
71
+ end
72
+
73
+ it "should store opponent's targets" do
74
+ @player.enemy_targeting("B7")
75
+ @player.enemy_targeting("A3")
76
+
77
+ @player.enemy_targets[0].should == "B7"
78
+ @player.enemy_targets[1].should == "A3"
79
+ end
80
+
81
+ it "should go to seek and destroy mode after first hit of a ship" do
82
+ @player.target_result("A1", true, nil)
83
+
84
+ @player.seek_and_destroy?.should == true
85
+ end
86
+
87
+ it "should stay in seek and destroy after missing next shot" do
88
+ @player.target_result("A7", true, nil)
89
+ @player.target_result("A6", false, nil)
90
+
91
+ @player.seek_and_destroy?.should == true
92
+ end
93
+
94
+ it "should go out of seek and destroy mode after ship is sunk" do
95
+ @player.target_result("A2", true, nil)
96
+
97
+ @player.target_result("A1", true, :patrolship)
98
+
99
+ @player.seek_and_destroy?.should == false
100
+ end
101
+
102
+ it "should first target to the left of the hit" do
103
+ @player.target_result("D3", true, nil)
104
+
105
+ @player.next_target.should == "D2"
106
+ end
107
+
108
+ it "should target right if missed to the left" do
109
+ @player.target_result("D3", true, nil)
110
+ @player.target_result("D2", false, nil)
111
+
112
+ @player.next_target.should == "D4"
113
+ end
114
+
115
+ it "should continue to the left if hit" do
116
+ @player.target_result("D3", true, nil)
117
+ @player.target_result("D2", true, nil)
118
+
119
+ @player.next_target.should == "D1"
120
+ end
121
+
122
+ it "should target to the right if hit was on the left edge" do
123
+ @player.target_result("A1", true, nil)
124
+
125
+ @player.next_target.should == "A2"
126
+ end
127
+
128
+ it "should target to the right if sector to left of hit was already targeted" do
129
+ @player.target_result("D4", false, nil)
130
+ @player.target_result("D5", true, nil)
131
+
132
+ @player.next_target.should == "D6"
133
+ end
134
+
135
+ it "should target up if no hits to left or right" do
136
+ @player.target_result("C5", true, nil)
137
+ @player.target_result("C4", false, nil)
138
+ @player.target_result("C6", false, nil)
139
+
140
+ @player.next_target.should == "B5"
141
+ end
142
+
143
+ it "should target down if no hits left, right or up" do
144
+ @player.target_result("C5", true, nil)
145
+ @player.target_result("C4", false, nil)
146
+ @player.target_result("C6", false, nil)
147
+ @player.target_result("B5", false, nil)
148
+
149
+ @player.next_target.should == "D5"
150
+ end
151
+
152
+ it "should target down after hitting one above then missing above that" do
153
+ @player.target_result("C5", true, nil)
154
+ @player.target_result("C4", false, nil)
155
+ @player.target_result("C6", false, nil)
156
+ @player.target_result("B5", true, nil)
157
+ @player.target_result("A5", false, nil)
158
+
159
+ @player.next_target.should == "D5"
160
+ end
161
+
162
+ it "should target down if sector above hit was already targeted" do
163
+ @player.target_result("B5", false, nil)
164
+ @player.target_result("C5", true, nil)
165
+ @player.target_result("C4", false, nil)
166
+
167
+ @player.next_target.should == "C6"
168
+ end
169
+
170
+ it "should target right if sector above and below already targeted" do
171
+ @player.target_result("B5", false, nil)
172
+ @player.target_result("D5", false, nil)
173
+ @player.target_result("C4", false, nil)
174
+ @player.target_result("C5", true, nil)
175
+
176
+ @player.next_target.should == "C6"
177
+ end
178
+
179
+ # it "should not try to the right when already know the ship is vertical" do
180
+ # @player.target_result("C8", true, nil)
181
+ # @player.target_result("C7", false, nil)
182
+ # @player.target_result("B8", true, nil)
183
+ # @player.target_result("A8", false, nil)
184
+ #
185
+ # @player.next_target.should == "D8"
186
+ # end
187
+
188
+ # it "should handle initial hit in top corner" do
189
+ # @player.target_result("A10", true, nil)
190
+ # @player.target_result("A9", false, nil)
191
+ #
192
+ # @player.next_target.should == "B10"
193
+ # end
194
+ #
195
+ # it "should handle initial hit on right edge" do
196
+ # @player.target_result("D10", true, nil)
197
+ # @player.target_result("D9", false, nil)
198
+ # @player.target_result("C10", false, nil)
199
+ #
200
+ # @player.next_target.should == "E10"
201
+ # end
202
+
203
+ end
204
+
@@ -0,0 +1 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ extensions: []
3
+ homepage: http://sparring.rubyforge.org/
4
+ executables: []
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.2
7
+ post_install_message:
8
+ date: 2008-12-01 06:00:00 +00:00
9
+ files:
10
+ - Battleship.Rakefile
11
+ - lib
12
+ - pkg
13
+ - Rakefile
14
+ - spec
15
+ - lib/ensign_erratic
16
+ - lib/ensign_erratic/board.rb
17
+ - lib/ensign_erratic/ensign_erratic.rb
18
+ - pkg/ensign_erratic-1.0.0.1.gem
19
+ - pkg/ensign_erratic-1.0.0.2.gem
20
+ - pkg/ensign_erratic-1.0.0.3.gem
21
+ - pkg/ensign_erratic-1.0.0.gem
22
+ - pkg/ensign_erratic-1.0.1.gem
23
+ - spec/ensign_erratic
24
+ - spec/spec_helper.rb
25
+ - spec/ensign_erratic/board_spec.rb
26
+ - spec/ensign_erratic/ensign_erratic_spec.rb
27
+ rubygems_version: 1.2.0
28
+ rdoc_options: []
29
+ signing_key:
30
+ cert_chain: []
31
+ name: ensign_erratic
32
+ has_rdoc: false
33
+ platform: ruby
34
+ summary: Battleship Player:Ensign Erratic
35
+ default_executable:
36
+ bindir: bin
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ version:
39
+ requirements:
40
+ - - '>='
41
+ - !ruby/object:Gem::Version
42
+ version: !str 0
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ version:
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: !str 0
49
+ require_paths:
50
+ - lib
51
+ specification_version: 2
52
+ test_files: []
53
+ dependencies: []
54
+ description: Relatively simple Battleship AI that utilizes random targeting and ship
55
+ placement
56
+ email: jim@8thlight.com
57
+ authors:
58
+ - Jim Suchy
59
+ extra_rdoc_files: []
60
+ requirements: []
61
+ rubyforge_project: sparring
62
+ autorequire: