uss_monte_carlo 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Battleship.Rakefile +12 -0
- data/Rakefile +47 -0
- data/lib/uss_monte_carlo/game_constants.rb +12 -0
- data/lib/uss_monte_carlo/homing_gunner.rb +107 -0
- data/lib/uss_monte_carlo/random_gunner.rb +39 -0
- data/lib/uss_monte_carlo/uss_monte_carlo.rb +232 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/uss_monte_carlo/homing_gunner_spec.rb +31 -0
- data/spec/uss_monte_carlo/random_gunner_spec.rb +31 -0
- data/spec/uss_monte_carlo/uss_monte_carlo_spec.rb +72 -0
- metadata +66 -0
data/Battleship.Rakefile
ADDED
@@ -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,rcov,test,shoulda', '--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 = "uss_monte_carlo"
|
13
|
+
PKG_VERSION = "2.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.reject!{ |f| f =~ /pkg/ }
|
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:USS Monte Carlo"
|
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 = "Flagship of the Monaco Navy"
|
34
|
+
s.author = "Joshua Ballanco"
|
35
|
+
s.email = "jballanc@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,12 @@
|
|
1
|
+
SHIPS = [:carrier, :battleship, :destroyer, :submarine, :patrolship]
|
2
|
+
SHIP_SIZES = {:carrier => 5, :battleship => 4, :destroyer => 3, :submarine => 3, :patrolship => 2}
|
3
|
+
ROWS = %w(A B C D E F G H I J)
|
4
|
+
COLUMNS = %w(1 2 3 4 5 6 7 8 9 10)
|
5
|
+
ORIENTATIONS = %w(horizontal vertical)
|
6
|
+
WIN_RESULT = :victory
|
7
|
+
LOOSE_RESULT = :defeat
|
8
|
+
DQ_RESULT = :disqualified
|
9
|
+
GRID_DIMENSIONS = [ROWS.length, COLUMNS.length]
|
10
|
+
GRID_SIZE = ROWS.length
|
11
|
+
|
12
|
+
class GunnerError < RuntimeError; end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'uss_monte_carlo/game_constants'
|
2
|
+
|
3
|
+
class HomingGunner
|
4
|
+
|
5
|
+
def initialize(targets_remaining, hit_location, shots_fired)
|
6
|
+
@smallest_ship = targets_remaining.inject(5) { |min, s| SHIP_SIZES[s] < min ? SHIP_SIZES[s] : min }
|
7
|
+
@original_hit = @shot = hit_location
|
8
|
+
@shots_fired = shots_fired
|
9
|
+
@hits = [hit_location]
|
10
|
+
pick_direction_and_orientation
|
11
|
+
end
|
12
|
+
|
13
|
+
def next_shot
|
14
|
+
raise GunnerError.new("All shots have been taken: #{@shots_fired}") if @shots_fired.length >= (ROWS.length * COLUMNS.length)
|
15
|
+
candidate_shot = move_from(@shot)
|
16
|
+
if candidate_shot == @shot
|
17
|
+
self.send next_strategy
|
18
|
+
candidate_shot = move_from(@shot)
|
19
|
+
end
|
20
|
+
@shot = candidate_shot
|
21
|
+
|
22
|
+
while @shots_fired.include? @shot
|
23
|
+
@shot = @original_hit
|
24
|
+
self.send next_strategy
|
25
|
+
@shot = move_from(@shot)
|
26
|
+
end
|
27
|
+
@shot
|
28
|
+
end
|
29
|
+
|
30
|
+
def shot_result(coordinates, was_hit, ship_sunk)
|
31
|
+
@shots_fired << coordinates unless @shots_fired.include? coordinates
|
32
|
+
if ship_sunk
|
33
|
+
remove_hits(ship_sunk)
|
34
|
+
elsif was_hit
|
35
|
+
@hits << coordinates
|
36
|
+
else
|
37
|
+
@shot = @original_hit
|
38
|
+
self.send next_strategy
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unaccounted_hits
|
43
|
+
@hits
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def pick_direction_and_orientation
|
50
|
+
@orientation = [ROWS, COLUMNS][rand(2)]
|
51
|
+
@direction ||= [:+, :-][rand(2)]
|
52
|
+
|
53
|
+
# Eliminate orientations that can't fit one of the remaining ships
|
54
|
+
check_pos = @shot
|
55
|
+
next_pos = move_from(check_pos)
|
56
|
+
while (check_pos != next_pos) && !(@shots_fired.include? next_pos)
|
57
|
+
check_pos = next_pos
|
58
|
+
next_pos = move_from(check_pos)
|
59
|
+
end
|
60
|
+
reverse_direction
|
61
|
+
gap_size = 0
|
62
|
+
next_pos = move_from(check_pos)
|
63
|
+
while (check_pos != next_pos) && !(@shots_fired.include? next_pos)
|
64
|
+
check_pos = next_pos
|
65
|
+
next_pos = move_from(check_pos)
|
66
|
+
gap_size += 1
|
67
|
+
end
|
68
|
+
reverse_direction
|
69
|
+
reverse_orientation if gap_size < @smallest_ship
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_hits(ship_sunk)
|
73
|
+
reverse_direction
|
74
|
+
shot_to_remove = @shot
|
75
|
+
SHIP_SIZES[ship_sunk].times do
|
76
|
+
@hits -= [shot_to_remove]
|
77
|
+
shot_to_remove = move_from(shot_to_remove)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def reverse_direction
|
82
|
+
@direction = @direction == :+ ? :- : :+
|
83
|
+
end
|
84
|
+
|
85
|
+
def reverse_orientation
|
86
|
+
@orientation = @orientation == ROWS ? COLUMNS : ROWS
|
87
|
+
end
|
88
|
+
|
89
|
+
def move_from(shot)
|
90
|
+
row, *column = shot.split('')
|
91
|
+
column = column.join
|
92
|
+
if @orientation == ROWS
|
93
|
+
index = ROWS.index(row).send(@direction, 1)
|
94
|
+
row = ROWS[index] if (0 <= index) && (index < ROWS.length)
|
95
|
+
else
|
96
|
+
index = COLUMNS.index(column).send(@direction, 1)
|
97
|
+
column = COLUMNS[index] if (0 <= index) && (index < COLUMNS.length)
|
98
|
+
end
|
99
|
+
"#{row}#{column}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def next_strategy
|
103
|
+
@strategy ||= [:reverse_orientation, :reverse_direction]
|
104
|
+
(@strategy << @strategy.shift)[0]
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'uss_monte_carlo/game_constants'
|
2
|
+
|
3
|
+
class RandomGunner
|
4
|
+
|
5
|
+
def initialize(targets_remaining, shots_fired)
|
6
|
+
@targets_remaining = targets_remaining
|
7
|
+
@shots_fired = shots_fired
|
8
|
+
@shot = @shots_fired.last || ROWS[rand(ROWS.length)] + COLUMNS[rand(COLUMNS.length)]
|
9
|
+
end
|
10
|
+
|
11
|
+
def next_shot
|
12
|
+
raise GunnerError.new("All shots have been taken: #{@shots_fired}") if @shots_fired.length >= (ROWS.length * COLUMNS.length)
|
13
|
+
@shot = random_coord
|
14
|
+
while @shots_fired.include? @shot
|
15
|
+
@shot = random_coord
|
16
|
+
end
|
17
|
+
@shot
|
18
|
+
end
|
19
|
+
|
20
|
+
def shot_result(coordinates, was_hit, ship_sunk)
|
21
|
+
@shots_fired << coordinates unless @shots_fired.include? coordinates
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def random_coord
|
27
|
+
last_row, *last_column = @shot.split('')
|
28
|
+
last_column = last_column.join
|
29
|
+
row_num = ( ROWS.index(last_row) + random_offset ) % GRID_SIZE
|
30
|
+
column_num = ( COLUMNS.index(last_column) + random_offset ) % GRID_SIZE
|
31
|
+
"#{ROWS[row_num]}#{COLUMNS[column_num]}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def random_offset
|
35
|
+
random_ship = @targets_remaining[rand(@targets_remaining.length)]
|
36
|
+
[SHIP_SIZES[random_ship], 1][rand(2)]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module UssMonteCarlo
|
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
|
+
require 'uss_monte_carlo/game_constants'
|
17
|
+
require 'uss_monte_carlo/random_gunner'
|
18
|
+
require 'uss_monte_carlo/homing_gunner'
|
19
|
+
|
20
|
+
class UssMonteCarlo
|
21
|
+
|
22
|
+
# This method is called at the beginning of each game. A player may only be instantiated once and used to play many games.
|
23
|
+
# So new_game should reset any internal state acquired in previous games so that it is prepared for a new game.
|
24
|
+
#
|
25
|
+
# The name of the opponent player is passed in. This allows for the possibility to learn opponent strategy and
|
26
|
+
# play the game differently based on the opponent.
|
27
|
+
#
|
28
|
+
def new_game(opponent_name)
|
29
|
+
reset
|
30
|
+
@opponent = opponent_name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the placement of the carrier. A carrier consumes 5 squares.
|
34
|
+
#
|
35
|
+
# The return value is a string that describes the placements of the ship.
|
36
|
+
# The placement string must be in the following format:
|
37
|
+
#
|
38
|
+
# "#{ROW}#{COL} #{ORIENTATION}"
|
39
|
+
#
|
40
|
+
# eg
|
41
|
+
#
|
42
|
+
# A1 horizontal # the ship will occupy A1, A2, A3, A4, and A5
|
43
|
+
# A1 vertical # the ship will occupy A1, B1, C1, D1, and E1
|
44
|
+
# F5 horizontal # the ship will occupy F5, F6, F7, F8, and F9
|
45
|
+
# F5 vertical # the ship will occupy F5, G5, H5, I5, and J5
|
46
|
+
#
|
47
|
+
# The ship must not fall off the edge of the map. For example, a carrier placement of 'A8 horizontal' would
|
48
|
+
# not leave enough space in the A row to accomidate the carrier since it requires 5 squares.
|
49
|
+
#
|
50
|
+
# Ships may not overlap with other ships. For example a carrier placement of 'A1 horizontal' and a submarine
|
51
|
+
# placement of 'A1 vertical' would be invalid because bothe ships are trying to occupy the square A1.
|
52
|
+
#
|
53
|
+
# Invalid ship placements will result in disqualification of the player.
|
54
|
+
#
|
55
|
+
def carrier_placement
|
56
|
+
@ship_placements ||= place_ships
|
57
|
+
@ship_placements[:carrier]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the placement of the battleship. A battleship consumes 4 squares.
|
61
|
+
#
|
62
|
+
# See carrier_placement for details on ship placement
|
63
|
+
#
|
64
|
+
def battleship_placement
|
65
|
+
@ship_placements ||= place_ships
|
66
|
+
@ship_placements[:battleship]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the placement of the destroyer. A destroyer consumes 3 squares.
|
70
|
+
#
|
71
|
+
# See carrier_placement for details on ship placement
|
72
|
+
#
|
73
|
+
def destroyer_placement
|
74
|
+
@ship_placements ||= place_ships
|
75
|
+
@ship_placements[:destroyer]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the placement of the submarine. A submarine consumes 3 squares.
|
79
|
+
#
|
80
|
+
# See carrier_placement for details on ship placement
|
81
|
+
#
|
82
|
+
def submarine_placement
|
83
|
+
@ship_placements ||= place_ships
|
84
|
+
@ship_placements[:submarine]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the placement of the patrolship. A patrolship consumes 2 squares.
|
88
|
+
#
|
89
|
+
# See carrier_placement for details on ship placement
|
90
|
+
#
|
91
|
+
def patrolship_placement
|
92
|
+
@ship_placements ||= place_ships
|
93
|
+
@ship_placements[:patrolship]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the coordinates of the players next target. This method will be called once per turn. The player
|
97
|
+
# should return target coordinates as a string in the form of:
|
98
|
+
#
|
99
|
+
# "#{ROW}#{COL}"
|
100
|
+
#
|
101
|
+
# eg
|
102
|
+
#
|
103
|
+
# A1 # the square in Row A and Column 1
|
104
|
+
# F5 # the square in Row F and Column 5
|
105
|
+
#
|
106
|
+
# 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
|
107
|
+
# COL should be 1, 2, 3, 4, 5, 6, 7, 8, 9, or 10
|
108
|
+
#
|
109
|
+
# Returning coordinates outside the range or in an invalid format will result in the players disqualification.
|
110
|
+
#
|
111
|
+
# It is illegal to target a sector more than once. Doing so will also result in disqualification.
|
112
|
+
#
|
113
|
+
def next_target
|
114
|
+
target = @current_gunner.next_shot
|
115
|
+
@shots_fired << target
|
116
|
+
return target
|
117
|
+
end
|
118
|
+
|
119
|
+
# target_result will be called by the system after a call to next_target. The paramters supplied inform the player
|
120
|
+
# of the results of the target.
|
121
|
+
#
|
122
|
+
# coordinates : string. The coordinates targeted. It will be the same value returned by the previous call to next_target
|
123
|
+
# was_hit : boolean. true if the target was occupied by a ship. false otherwise.
|
124
|
+
# ship_sunk : symbol. nil if the target did not result in the sinking of a ship. If the target did result in
|
125
|
+
# in the sinking of a ship, the ship type is supplied (:carrier, :battleship, :destroyer, :submarine, :patrolship).
|
126
|
+
#
|
127
|
+
# An intelligent player will use the information to better play the game. For example, if the result indicates a
|
128
|
+
# hit, a player my choose to target neighboring squares to hit and sink the remainder of the ship.
|
129
|
+
#
|
130
|
+
def target_result(coordinates, was_hit, ship_sunk)
|
131
|
+
@current_gunner.shot_result(coordinates, was_hit, ship_sunk)
|
132
|
+
if ship_sunk
|
133
|
+
@targets_remaining -= [ship_sunk]
|
134
|
+
@unaccounted_hits += @current_gunner.unaccounted_hits
|
135
|
+
|
136
|
+
# Protect against an infinite loop
|
137
|
+
@unaccounted_hits.uniq!
|
138
|
+
|
139
|
+
if @unaccounted_hits.empty?
|
140
|
+
@current_gunner = RandomGunner.new(@targets_remaining, @shots_fired)
|
141
|
+
else
|
142
|
+
@current_gunner = HomingGunner.new(@targets_remaining, @unaccounted_hits.shift, @shots_fired)
|
143
|
+
end
|
144
|
+
elsif was_hit && !@current_gunner.kind_of?(HomingGunner)
|
145
|
+
@current_gunner = HomingGunner.new(@targets_remaining, coordinates, @shots_fired)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# enemy_targeting is called by the system to inform a player of their apponents move. When the opponent targets
|
150
|
+
# a square, this method is called with the coordinates.
|
151
|
+
#
|
152
|
+
# Players may use this information to understand an opponents targeting strategy and place ships differently
|
153
|
+
# in subsequent games.
|
154
|
+
#
|
155
|
+
def enemy_targeting(coordinates)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Called by the system at the end of a game to inform the player of the results.
|
159
|
+
#
|
160
|
+
# result : 1 of 3 possible values (:victory, :defeate, :disqualified)
|
161
|
+
# disqualification_reason : nil unless the game ended as the result of a disqualification. In the event of a
|
162
|
+
# disqualification, this paramter will hold a string description of the reason for disqualification. Both
|
163
|
+
# players will be informed of the reason.
|
164
|
+
#
|
165
|
+
# :victory # indicates the player won the game
|
166
|
+
# :defeat # indicates the player lost the game
|
167
|
+
# :disqualified # indicates the player was disqualified
|
168
|
+
#
|
169
|
+
def game_over(result, disqualification_reason=nil)
|
170
|
+
@result = result
|
171
|
+
@disqualification_reason = disqualification_reason
|
172
|
+
end
|
173
|
+
|
174
|
+
# Non API methods #####################################
|
175
|
+
|
176
|
+
attr_reader :opponent, :result, :targets_remaining, :disqualification_reason, :enemy_targeted_sectors
|
177
|
+
|
178
|
+
def initialize #:nodoc:
|
179
|
+
reset
|
180
|
+
end
|
181
|
+
|
182
|
+
private ###############################################
|
183
|
+
|
184
|
+
def reset
|
185
|
+
@shots_fired = []
|
186
|
+
@unaccounted_hits = []
|
187
|
+
@targets_remaining = SHIPS
|
188
|
+
@current_gunner = RandomGunner.new(@targets_remaining, @shots_fired)
|
189
|
+
|
190
|
+
@enemy_targeted_sectors = []
|
191
|
+
@result = nil
|
192
|
+
@disqualification_reason = nil
|
193
|
+
end
|
194
|
+
|
195
|
+
def place_ships
|
196
|
+
placements = {}
|
197
|
+
occupied_squares = []
|
198
|
+
SHIPS.each do |ship|
|
199
|
+
if rand(2) == 0
|
200
|
+
first_coord, second_coord = ROWS, COLUMNS
|
201
|
+
else
|
202
|
+
second_coord, first_coord = ROWS, COLUMNS
|
203
|
+
end
|
204
|
+
first_coord_index = rand(first_coord.length)
|
205
|
+
second_coord_index = rand(second_coord.length - SHIP_SIZES[ship])
|
206
|
+
|
207
|
+
placed_coords = []
|
208
|
+
if first_coord == COLUMNS
|
209
|
+
SHIP_SIZES[ship].times{|n| placed_coords += ["#{second_coord[second_coord_index + n]}#{first_coord[first_coord_index]}"]}
|
210
|
+
first_coord_index, second_coord_index = second_coord_index, first_coord_index
|
211
|
+
first_coord, second_coord = second_coord, first_coord
|
212
|
+
direction = "vertical"
|
213
|
+
else
|
214
|
+
SHIP_SIZES[ship].times{|n| placed_coords += ["#{first_coord[first_coord_index]}#{second_coord[second_coord_index + n]}"]}
|
215
|
+
direction = "horizontal"
|
216
|
+
end
|
217
|
+
|
218
|
+
overlap = false
|
219
|
+
placed_coords.each{|coord| overlap ||= occupied_squares.include? coord}
|
220
|
+
if overlap
|
221
|
+
redo
|
222
|
+
else
|
223
|
+
occupied_squares += placed_coords
|
224
|
+
placements[ship] = "#{first_coord[first_coord_index]}#{second_coord[second_coord_index]} #{direction}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
placements
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__) + "/../lib")
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'minitest/unit'
|
5
|
+
require 'uss_monte_carlo/game_constants'
|
6
|
+
|
7
|
+
def valid_placements
|
8
|
+
# Construct list of valid placements
|
9
|
+
@list ||= ROWS.map{|r| COLUMNS.map{|c| ORIENTATIONS.map{|o| r + c + ' ' + o }}}.flatten
|
10
|
+
end
|
11
|
+
|
12
|
+
def grid_coords
|
13
|
+
# Construct list of valid coordinates
|
14
|
+
@coords ||= ROWS.map{|r| COLUMNS.map{|c| r + c }}.flatten
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
require 'uss_monte_carlo/uss_monte_carlo'
|
3
|
+
require 'uss_monte_carlo/game_constants'
|
4
|
+
require 'uss_monte_carlo/homing_gunner'
|
5
|
+
|
6
|
+
|
7
|
+
class HomingGunnerTest < MiniTest::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@gunner = HomingGunner.new(SHIPS, "E5", ["E5"])
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_search
|
14
|
+
adjacent_coords = %w(E4 E6 D5 F5)
|
15
|
+
|
16
|
+
# Fire a shot and make sure it was adjacent
|
17
|
+
shot = @gunner.next_shot
|
18
|
+
assert_includes(adjacent_coords, shot)
|
19
|
+
|
20
|
+
# Signal a miss, and check that the search continues
|
21
|
+
3.times do
|
22
|
+
@gunner.shot_result(shot, false, false)
|
23
|
+
adjacent_coords -= [shot]
|
24
|
+
shot = @gunner.next_shot
|
25
|
+
assert_includes(adjacent_coords, shot)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
MiniTest::Unit.autorun
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
require 'uss_monte_carlo/uss_monte_carlo'
|
3
|
+
require 'uss_monte_carlo/game_constants'
|
4
|
+
require 'uss_monte_carlo/random_gunner'
|
5
|
+
|
6
|
+
class RandomGunnerTests < MiniTest::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@gunner = RandomGunner.new(SHIPS, [])
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_initialization
|
13
|
+
refute_nil @gunner
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_valid_shots
|
17
|
+
# Make sure we're returning valid coordinates
|
18
|
+
assert_includes(grid_coords, @gunner.next_shot)
|
19
|
+
|
20
|
+
# Make sure that we're not overlapping with pre-existing shots
|
21
|
+
one_shot_gunner = RandomGunner.new(SHIPS, grid_coords[0..-2])
|
22
|
+
assert_equal(grid_coords[-1], one_shot_gunner.next_shot)
|
23
|
+
|
24
|
+
# Make sure we know when we're out of shots to take
|
25
|
+
one_shot_gunner.shot_result(grid_coords[-1], false, false)
|
26
|
+
assert_raises(GunnerError) { one_shot_gunner.next_shot }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
MiniTest::Unit.autorun
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
require 'uss_monte_carlo/uss_monte_carlo'
|
3
|
+
require 'uss_monte_carlo/game_constants'
|
4
|
+
|
5
|
+
OPPONENT_NAME = "Test @player"
|
6
|
+
DQ_REASON = "Cheater!"
|
7
|
+
|
8
|
+
class UssMonteCarloTests < MiniTest::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
# Create @player, start game
|
11
|
+
@player = UssMonteCarlo::UssMonteCarlo.new
|
12
|
+
@player.new_game(OPPONENT_NAME)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def test_initialization
|
17
|
+
#Test successful creation
|
18
|
+
refute_nil @player
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def test_startup
|
23
|
+
#Test new game parameters
|
24
|
+
assert_equal(OPPONENT_NAME, @player.opponent)
|
25
|
+
assert_equal(SHIPS, @player.targets_remaining)
|
26
|
+
assert_equal([], @player.enemy_targeted_sectors)
|
27
|
+
assert_nil(@player.result)
|
28
|
+
assert_nil(@player.disqualification_reason)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_restart
|
32
|
+
# Win game and check results
|
33
|
+
@player.game_over(WIN_RESULT)
|
34
|
+
assert_equal(WIN_RESULT, @player.result)
|
35
|
+
assert_nil(@player.disqualification_reason)
|
36
|
+
|
37
|
+
# Check game reset
|
38
|
+
@player.new_game(OPPONENT_NAME)
|
39
|
+
assert_nil(@player.result)
|
40
|
+
assert_nil(@player.disqualification_reason)
|
41
|
+
|
42
|
+
# Loose game and check results
|
43
|
+
@player.game_over(LOOSE_RESULT)
|
44
|
+
assert_equal(LOOSE_RESULT, @player.result)
|
45
|
+
assert_nil(@player.disqualification_reason)
|
46
|
+
|
47
|
+
# Reset, DQ, and check results
|
48
|
+
@player.new_game(OPPONENT_NAME)
|
49
|
+
@player.game_over(DQ_RESULT, DQ_REASON)
|
50
|
+
assert_equal(DQ_RESULT, @player.result)
|
51
|
+
assert_equal(DQ_REASON, @player.disqualification_reason)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_valid_placement
|
55
|
+
assert_includes(valid_placements, @player.carrier_placement)
|
56
|
+
assert_includes(valid_placements, @player.battleship_placement)
|
57
|
+
assert_includes(valid_placements, @player.destroyer_placement)
|
58
|
+
assert_includes(valid_placements, @player.submarine_placement)
|
59
|
+
assert_includes(valid_placements, @player.patrolship_placement)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_firing_sequence
|
63
|
+
5.times do
|
64
|
+
assert_includes(grid_coords, @player.next_target)
|
65
|
+
assert_respond_to(@player, :target_result)
|
66
|
+
assert_respond_to(@player, :enemy_targeting)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
MiniTest::Unit.autorun
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uss_monte_carlo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joshua Ballanco
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-01 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Flagship of the Monaco Navy
|
17
|
+
email: jballanc@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- Battleship.Rakefile
|
26
|
+
- lib
|
27
|
+
- lib/uss_monte_carlo
|
28
|
+
- lib/uss_monte_carlo/game_constants.rb
|
29
|
+
- lib/uss_monte_carlo/homing_gunner.rb
|
30
|
+
- lib/uss_monte_carlo/random_gunner.rb
|
31
|
+
- lib/uss_monte_carlo/uss_monte_carlo.rb
|
32
|
+
- Rakefile
|
33
|
+
- spec
|
34
|
+
- spec/spec_helper.rb
|
35
|
+
- spec/uss_monte_carlo
|
36
|
+
- spec/uss_monte_carlo/homing_gunner_spec.rb
|
37
|
+
- spec/uss_monte_carlo/random_gunner_spec.rb
|
38
|
+
- spec/uss_monte_carlo/uss_monte_carlo_spec.rb
|
39
|
+
has_rdoc: false
|
40
|
+
homepage: http://sparring.rubyforge.org/
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project: sparring
|
61
|
+
rubygems_version: 1.3.1
|
62
|
+
signing_key:
|
63
|
+
specification_version: 2
|
64
|
+
summary: Battleship Player:USS Monte Carlo
|
65
|
+
test_files: []
|
66
|
+
|