white_horseman 1.3
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/white_horseman/analyst.rb +63 -0
- data/lib/white_horseman/battlefield.rb +64 -0
- data/lib/white_horseman/captain.rb +58 -0
- data/lib/white_horseman/cell_status.rb +45 -0
- data/lib/white_horseman/coordinates.rb +87 -0
- data/lib/white_horseman/fleet_admiral.rb +79 -0
- data/lib/white_horseman/opponent.rb +70 -0
- data/lib/white_horseman/placement.rb +97 -0
- data/lib/white_horseman/scout.rb +66 -0
- data/lib/white_horseman/ship.rb +45 -0
- data/lib/white_horseman/white_horseman.rb +187 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/white_horseman/analyst_spec.rb +52 -0
- data/spec/white_horseman/battlefield_spec.rb +121 -0
- data/spec/white_horseman/captain_spec.rb +57 -0
- data/spec/white_horseman/cell_status_spec.rb +37 -0
- data/spec/white_horseman/coordinates_spec.rb +171 -0
- data/spec/white_horseman/fleet_admiral_spec.rb +54 -0
- data/spec/white_horseman/opponent_spec.rb +58 -0
- data/spec/white_horseman/placement_spec.rb +111 -0
- data/spec/white_horseman/scout_spec.rb +85 -0
- data/spec/white_horseman/ship_spec.rb +44 -0
- data/spec/white_horseman/white_horesman_integration_spec.rb +22 -0
- data/spec/white_horseman/white_horseman_spec.rb +97 -0
- data/test_data/testman.data +104 -0
- metadata +85 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module WhiteHorseman
|
2
|
+
class Ship
|
3
|
+
attr_accessor :symbol
|
4
|
+
def initialize(symbol)
|
5
|
+
@symbol = symbol.to_sym
|
6
|
+
end
|
7
|
+
|
8
|
+
def size
|
9
|
+
case @symbol
|
10
|
+
when :battleship
|
11
|
+
return 4
|
12
|
+
when :carrier
|
13
|
+
return 5
|
14
|
+
when :patrolship
|
15
|
+
return 2
|
16
|
+
when :submarine
|
17
|
+
return 3
|
18
|
+
when :destroyer
|
19
|
+
return 3
|
20
|
+
else
|
21
|
+
raise "bad ship type #{@symbol}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
self.symbol == other.symbol
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def each(&block)
|
31
|
+
[:carrier, :battleship, :destroyer, :submarine, :patrolship].each do |symbol|
|
32
|
+
yield(Ship.new(symbol))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def all
|
37
|
+
ships = []
|
38
|
+
self.each{|ship| ships << ship}
|
39
|
+
return ships
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
unless defined?(DATA_DIR)
|
2
|
+
require 'tmpdir'
|
3
|
+
DATA_DIR = "#{Dir.tmpdir}/white_horseman_battleship_data"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'white_horseman/opponent'
|
7
|
+
require 'white_horseman/fleet_admiral'
|
8
|
+
require 'white_horseman/captain'
|
9
|
+
|
10
|
+
module WhiteHorseman
|
11
|
+
|
12
|
+
# Battleship Player
|
13
|
+
#
|
14
|
+
# Battleship is board game between two players. See http://en.wikipedia.org/wiki/Battleship for more information and
|
15
|
+
# game rules.
|
16
|
+
#
|
17
|
+
# A player represents the conputer AI to play a game of Battleship. It should know how to place ships and target
|
18
|
+
# the opponents ships.
|
19
|
+
#
|
20
|
+
# This version of Battleship is played on a 10 x 10 grid where rows are labled by the letters A - J and
|
21
|
+
# columns are labled by the numbers 1 - 10. At the start of the game, each player will be asked for ship placements.
|
22
|
+
# Once the ships are placed, play proceeeds by each player targeting one square on their opponents map. A player
|
23
|
+
# may only target one square, reguardless of whether it resulted in a hit or not, before changing turns with her opponent.
|
24
|
+
#
|
25
|
+
class WhiteHorseman
|
26
|
+
|
27
|
+
# This method is called at the beginning of each game. A player may only be instantiated once and used to play many games.
|
28
|
+
# So new_game should reset any internal state acquired in previous games so that it is prepared for a new game.
|
29
|
+
#
|
30
|
+
# The name of the opponent player is passed in. This allows for the possibility to learn opponent strategy and
|
31
|
+
# play the game differently based on the opponent.
|
32
|
+
#
|
33
|
+
def new_game(opponent_name)
|
34
|
+
unless @opponent && @opponent.name == opponent_name
|
35
|
+
@opponent = Opponent.new(opponent_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
@admiral = FleetAdmiral.new
|
39
|
+
@admiral.heat_map = @opponent.heat_map
|
40
|
+
@admiral.calculate
|
41
|
+
@opponent.new_game
|
42
|
+
reset
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the placement of the carrier. A carrier consumes 5 squares.
|
46
|
+
#
|
47
|
+
# The return value is a string that describes the placements of the ship.
|
48
|
+
# The placement string must be in the following format:
|
49
|
+
#
|
50
|
+
# "#{ROW}#{COL} #{ORIENTATION}"
|
51
|
+
#
|
52
|
+
# eg
|
53
|
+
#
|
54
|
+
# A1 horizontal # the ship will occupy A1, A2, A3, A4, and A5
|
55
|
+
# A1 vertical # the ship will occupy A1, B1, C1, D1, and E1
|
56
|
+
# F5 horizontal # the ship will occupy F5, F6, F7, F8, and F9
|
57
|
+
# F5 vertical # the ship will occupy F5, G5, H5, I5, and J5
|
58
|
+
#
|
59
|
+
# The ship must not fall off the edge of the map. For example, a carrier placement of 'A8 horizontal' would
|
60
|
+
# not leave enough space in the A row to accomidate the carrier since it requires 5 squares.
|
61
|
+
#
|
62
|
+
# Ships may not overlap with other ships. For example a carrier placement of 'A1 horizontal' and a submarine
|
63
|
+
# placement of 'A1 vertical' would be invalid because bothe ships are trying to occupy the square A1.
|
64
|
+
#
|
65
|
+
# Invalid ship placements will result in disqualification of the player.
|
66
|
+
#
|
67
|
+
def carrier_placement
|
68
|
+
return @admiral.carrier
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the placement of the battleship. A battleship consumes 4 squares.
|
72
|
+
#
|
73
|
+
# See carrier_placement for details on ship placement
|
74
|
+
#
|
75
|
+
def battleship_placement
|
76
|
+
return @admiral.battleship
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the placement of the destroyer. A destroyer consumes 3 squares.
|
80
|
+
#
|
81
|
+
# See carrier_placement for details on ship placement
|
82
|
+
#
|
83
|
+
def destroyer_placement
|
84
|
+
return @admiral.destroyer
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the placement of the submarine. A submarine consumes 3 squares.
|
88
|
+
#
|
89
|
+
# See carrier_placement for details on ship placement
|
90
|
+
#
|
91
|
+
def submarine_placement
|
92
|
+
return @admiral.submarine
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the placement of the patrolship. A patrolship consumes 2 squares.
|
96
|
+
#
|
97
|
+
# See carrier_placement for details on ship placement
|
98
|
+
#
|
99
|
+
def patrolship_placement
|
100
|
+
return @admiral.patrolship
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the coordinates of the players next target. This method will be called once per turn. The player
|
104
|
+
# should return target coordinates as a string in the form of:
|
105
|
+
#
|
106
|
+
# "#{ROW}#{COL}"
|
107
|
+
#
|
108
|
+
# eg
|
109
|
+
#
|
110
|
+
# A1 # the square in Row A and Column 1
|
111
|
+
# F5 # the square in Row F and Column 5
|
112
|
+
#
|
113
|
+
# 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
|
114
|
+
# COL should be 1, 2, 3, 4, 5, 6, 7, 8, 9, or 10
|
115
|
+
#
|
116
|
+
# Returning coordinates outside the range or in an invalid format will result in the players disqualification.
|
117
|
+
#
|
118
|
+
# It is illegal to illegal to target a sector more than once. Doing so will also result in disqualification.
|
119
|
+
#
|
120
|
+
def next_target
|
121
|
+
return @captain.next_target
|
122
|
+
end
|
123
|
+
|
124
|
+
# target_result will be called by the system after a call to next_target. The paramters supplied inform the player
|
125
|
+
# of the results of the target.
|
126
|
+
#
|
127
|
+
# coordinates : string. The coordinates targeted. It will be the same value returned by the previous call to next_target
|
128
|
+
# was_hit : boolean. true if the target was occupied by a ship. false otherwise.
|
129
|
+
# ship_sunk : symbol. nil if the target did not result in the sinking of a ship. If the target did result in
|
130
|
+
# in the sinking of a ship, the ship type is supplied (:carrier, :battleship, :destroyer, :submarine, :patrolship).
|
131
|
+
#
|
132
|
+
# An intelligent player will use the information to better play the game. For example, if the result indicates a
|
133
|
+
# hit, a player my choose to target neighboring squares to hit and sink the remainder of the ship.
|
134
|
+
#
|
135
|
+
def target_result(coordinates, was_hit, ship_sunk)
|
136
|
+
@captain.target_result(coordinates, was_hit, ship_sunk)
|
137
|
+
end
|
138
|
+
|
139
|
+
# enemy_targeting is called by the system to inform a player of their apponents move. When the opponent targets
|
140
|
+
# a square, this method is called with the coordinates.
|
141
|
+
#
|
142
|
+
# Players may use this information to understand an opponents targeting strategy and place ships differently
|
143
|
+
# in subsequent games.
|
144
|
+
#
|
145
|
+
def enemy_targeting(coordinates)
|
146
|
+
@opponent.record_target(coordinates)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Called by the system at the end of a game to inform the player of the results.
|
150
|
+
#
|
151
|
+
# result : 1 of 3 possible values (:victory, :defeate, :disqualified)
|
152
|
+
# disqualification_reason : nil unless the game ended as the result of a disqualification. In the event of a
|
153
|
+
# disqualification, this paramter will hold a string description of the reason for disqualification. Both
|
154
|
+
# players will be informed of the reason.
|
155
|
+
#
|
156
|
+
# :victory # indicates the player won the game
|
157
|
+
# :defeat # indicates the player lost the game
|
158
|
+
# :disqualified # indicates the player was disqualified
|
159
|
+
#
|
160
|
+
def game_over(result, disqualification_reason=nil)
|
161
|
+
@opponent.save
|
162
|
+
end
|
163
|
+
|
164
|
+
# Non API methods #####################################
|
165
|
+
|
166
|
+
attr_reader :opponent, :targets, :enemy_targeted_sectors, :result, :disqualification_reason #:nodoc:
|
167
|
+
|
168
|
+
def initialize #:nodoc:
|
169
|
+
reset
|
170
|
+
end
|
171
|
+
|
172
|
+
private ###############################################
|
173
|
+
|
174
|
+
def reset
|
175
|
+
@captain = Captain.new
|
176
|
+
end
|
177
|
+
|
178
|
+
ROWS = %w{ A B C D E F G H I J }
|
179
|
+
def target_for_current_shot
|
180
|
+
row = ROWS[(@shots_taken) / 10]
|
181
|
+
col = @shots_taken % 10 + 1
|
182
|
+
return "#{row}#{col}"
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'white_horseman/analyst'
|
3
|
+
require 'white_horseman/battlefield'
|
4
|
+
require 'white_horseman/cell_status'
|
5
|
+
|
6
|
+
describe WhiteHorseman::Analyst do
|
7
|
+
before(:each) do
|
8
|
+
@hit_map = WhiteHorseman::Battlefield.new
|
9
|
+
@hit_map.set_all(WhiteHorseman::CellStatus.new)
|
10
|
+
@live_ships = WhiteHorseman::Ship.all
|
11
|
+
@analyst = WhiteHorseman::Analyst.new(@hit_map, @live_ships)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should zero probability if it's already been missed" do
|
15
|
+
@hit_map["G5"] = WhiteHorseman::CellStatus.new(false)
|
16
|
+
@analyst.hit_probability("G5").should == 0.0
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should have starting probablity" do
|
20
|
+
@analyst.hit_probability("F5").should == 0.17
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have a sure hit" do
|
24
|
+
@hit_map["D5"] = WhiteHorseman::CellStatus.new(false)
|
25
|
+
@hit_map["E5"] = WhiteHorseman::CellStatus.new(true)
|
26
|
+
@hit_map["E6"] = WhiteHorseman::CellStatus.new(false)
|
27
|
+
@hit_map["E4"] = WhiteHorseman::CellStatus.new(false)
|
28
|
+
@analyst.hit_probability("F5").should be >= 1.0
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should no chance if nothing will fit there" do
|
32
|
+
@hit_map["A2"] = WhiteHorseman::CellStatus.new(false)
|
33
|
+
@hit_map["B1"] = WhiteHorseman::CellStatus.new(false)
|
34
|
+
@analyst.hit_probability("A1").should == 0.0
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should have no chance if the only ship that will fit there has already been sunk" do
|
38
|
+
@live_ships.delete(WhiteHorseman::Ship.new(:patrolship))
|
39
|
+
@hit_map["A2"] = WhiteHorseman::CellStatus.new(false)
|
40
|
+
@hit_map["B2"] = WhiteHorseman::CellStatus.new(false)
|
41
|
+
@hit_map["C1"] = WhiteHorseman::CellStatus.new(false)
|
42
|
+
|
43
|
+
@analyst.hit_probability("A1").should == 0.0
|
44
|
+
@analyst.hit_probability("B1").should == 0.0
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should have a greater probability when there is a hit two cells away" do
|
48
|
+
prob = @analyst.hit_probability("G7")
|
49
|
+
@hit_map["G5"] = WhiteHorseman::CellStatus.new(true)
|
50
|
+
@analyst.hit_probability("G7").should be > prob
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'white_horseman/battlefield'
|
3
|
+
describe WhiteHorseman::Battlefield do
|
4
|
+
before(:each) do
|
5
|
+
@battlefield = WhiteHorseman::Battlefield.new
|
6
|
+
end
|
7
|
+
it "should have the grid" do
|
8
|
+
@battlefield.record_target("A1")
|
9
|
+
@battlefield.record_target("B4")
|
10
|
+
@battlefield.record_target("F8")
|
11
|
+
|
12
|
+
@battlefield.shots.should == 3
|
13
|
+
@battlefield.grid.should == [
|
14
|
+
[99,0,0,0,0,0,0,0,0,0],
|
15
|
+
[0,0,0,98,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
|
+
[0,0,0,0,0,0,0,0,0,0],
|
19
|
+
[0,0,0,0,0,0,0,97,0,0],
|
20
|
+
[0,0,0,0,0,0,0,0,0,0],
|
21
|
+
[0,0,0,0,0,0,0,0,0,0],
|
22
|
+
[0,0,0,0,0,0,0,0,0,0],
|
23
|
+
[0,0,0,0,0,0,0,0,0,0],
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should grids for multiple games" do
|
28
|
+
@battlefield.record_target("J10")
|
29
|
+
@battlefield.record_target("G4")
|
30
|
+
@battlefield.record_target("f1")
|
31
|
+
@battlefield.record_target("B9")
|
32
|
+
|
33
|
+
@battlefield.shots.should == 4
|
34
|
+
@battlefield.grid.should == [
|
35
|
+
[0,0,0,0,0,0,0,0,0,0],
|
36
|
+
[0,0,0,0,0,0,0,0,96,0],
|
37
|
+
[0,0,0,0,0,0,0,0,0,0],
|
38
|
+
[0,0,0,0,0,0,0,0,0,0],
|
39
|
+
[0,0,0,0,0,0,0,0,0,0],
|
40
|
+
[97,0,0,0,0,0,0,0,0,0],
|
41
|
+
[0,0,0,98,0,0,0,0,0,0],
|
42
|
+
[0,0,0,0,0,0,0,0,0,0],
|
43
|
+
[0,0,0,0,0,0,0,0,0,0],
|
44
|
+
[0,0,0,0,0,0,0,0,0,99],
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should build with a grid" do
|
49
|
+
@battlefield = WhiteHorseman::Battlefield.new([
|
50
|
+
[0,0,0,0,0,0,0,0,0,0],
|
51
|
+
[0,0,0,0,0,0,0,0,96,0],
|
52
|
+
[0,0,0,0,0,0,0,0,0,0],
|
53
|
+
[0,0,0,0,0,0,0,0,0,0],
|
54
|
+
[0,0,0,0,0,0,0,0,0,0],
|
55
|
+
[97,0,0,0,0,0,0,0,0,0],
|
56
|
+
[0,0,0,98,0,0,0,0,0,0],
|
57
|
+
[0,0,0,0,0,0,0,0,0,0],
|
58
|
+
[0,0,0,0,0,0,0,0,0,0],
|
59
|
+
[0,0,0,0,0,0,0,0,0,99],
|
60
|
+
])
|
61
|
+
|
62
|
+
@battlefield.grid.should == [
|
63
|
+
[0,0,0,0,0,0,0,0,0,0],
|
64
|
+
[0,0,0,0,0,0,0,0,96,0],
|
65
|
+
[0,0,0,0,0,0,0,0,0,0],
|
66
|
+
[0,0,0,0,0,0,0,0,0,0],
|
67
|
+
[0,0,0,0,0,0,0,0,0,0],
|
68
|
+
[97,0,0,0,0,0,0,0,0,0],
|
69
|
+
[0,0,0,98,0,0,0,0,0,0],
|
70
|
+
[0,0,0,0,0,0,0,0,0,0],
|
71
|
+
[0,0,0,0,0,0,0,0,0,0],
|
72
|
+
[0,0,0,0,0,0,0,0,0,99],
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should set cells" do
|
77
|
+
@battlefield["A1"] = 124
|
78
|
+
@battlefield["A1"].should == 124
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should set all" do
|
82
|
+
@battlefield.set_all(100)
|
83
|
+
@battlefield.grid.flatten.all?{ |cell| cell == 100}.should == true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should average multiple battlefields" do
|
87
|
+
battlefield1 = WhiteHorseman::Battlefield.new([
|
88
|
+
[5,0,0,0,0,0,0,0,0,0],
|
89
|
+
[0,10,0,0,0,0,0,0,0,0],
|
90
|
+
[0,0,15,0,0,0,0,0,0,0],
|
91
|
+
[0,20,0,0,0,0,0,0,0,0],
|
92
|
+
[25,0,0,0,0,0,0,0,0,0],
|
93
|
+
[0,0,0,0,0,0,0,0,0,0],
|
94
|
+
[0,0,0,0,0,0,0,0,0,0],
|
95
|
+
[0,0,0,0,0,0,0,0,0,0],
|
96
|
+
[0,0,0,0,0,0,0,0,0,0],
|
97
|
+
[0,0,0,0,0,0,0,0,0,0],
|
98
|
+
])
|
99
|
+
battlefield2 = WhiteHorseman::Battlefield.new([
|
100
|
+
[10,0,0,0,0,0,0,0,0,0],
|
101
|
+
[0,10,0,0,0,0,0,0,0,0],
|
102
|
+
[0,0,25,0,0,0,0,0,0,0],
|
103
|
+
[0,0,0,0,0,0,0,0,0,0],
|
104
|
+
[100,0,0,0,0,0,0,0,0,0],
|
105
|
+
[0,0,0,0,0,0,0,0,0,0],
|
106
|
+
[0,0,0,0,0,0,0,0,0,0],
|
107
|
+
[0,0,0,0,0,0,0,0,0,0],
|
108
|
+
[0,0,0,0,0,0,0,0,0,0],
|
109
|
+
[0,0,0,0,0,0,0,0,0,0],
|
110
|
+
])
|
111
|
+
average = WhiteHorseman::Battlefield.average([battlefield1, battlefield2])
|
112
|
+
average["A1"].should == 7.5
|
113
|
+
average["B2"].should == 10
|
114
|
+
average["C3"].should == 20
|
115
|
+
average["D2"].should == 10
|
116
|
+
average["E1"].should == 62.5
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
require 'white_horseman/captain'
|
4
|
+
require 'white_horseman/coordinates'
|
5
|
+
describe WhiteHorseman::Captain do
|
6
|
+
before(:each) do
|
7
|
+
@analyst = mock("analyst", :hit_probability => 0.17)
|
8
|
+
WhiteHorseman::Analyst.stub!(:new).and_return(@analyst)
|
9
|
+
@captain = WhiteHorseman::Captain.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should record a miss" do
|
13
|
+
@captain.target_result("A1", false, nil)
|
14
|
+
@captain.hit_map["A1"].should be_miss
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should record a hit" do
|
18
|
+
@captain.target_result("C1", true, nil)
|
19
|
+
@captain.hit_map["C1"].should be_hit
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should select a target" do
|
23
|
+
target = WhiteHorseman::Coordinates.new(:string =>"I5")
|
24
|
+
@analyst.should_receive(:hit_probability).with(target).and_return(1.5)
|
25
|
+
@captain.next_target.should == "I5"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should choose randomly from target of equal probablity" do
|
29
|
+
@captain.next_target.should_not == @captain.next_target
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not target areas already targeted" do
|
33
|
+
def @analyst.hit_probability(coords)
|
34
|
+
coords.to_s.should_not == "A6"
|
35
|
+
coords.to_s.should_not == "A8"
|
36
|
+
return 0.17
|
37
|
+
end
|
38
|
+
|
39
|
+
@captain.target_result("A6", true, nil)
|
40
|
+
@captain.target_result("A8", false, nil)
|
41
|
+
|
42
|
+
@captain.next_target
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should have live ships" do
|
46
|
+
@captain.live_ships.size.should == 5
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should record sunken ships" do
|
50
|
+
@captain.target_result("G5", true, nil)
|
51
|
+
@captain.target_result("G6", true, :patrolship)
|
52
|
+
@captain.hit_map["G6"].should have_ship
|
53
|
+
@captain.hit_map["G5"].should have_ship
|
54
|
+
@captain.live_ships.size.should == 4
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'white_horseman/cell_status'
|
3
|
+
describe WhiteHorseman::CellStatus do
|
4
|
+
before(:each) do
|
5
|
+
@cell = WhiteHorseman::CellStatus.new
|
6
|
+
end
|
7
|
+
it "should hit" do
|
8
|
+
@cell.should_not be_hit
|
9
|
+
@cell.hit
|
10
|
+
@cell.should be_hit
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should miss" do
|
14
|
+
@cell.should_not be_miss
|
15
|
+
@cell.miss
|
16
|
+
@cell.should be_miss
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should initailize with hit" do
|
20
|
+
WhiteHorseman::CellStatus.new(true).should be_hit
|
21
|
+
WhiteHorseman::CellStatus.new(false).should be_miss
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have no shot" do
|
25
|
+
@cell.should be_empty
|
26
|
+
@cell.hit
|
27
|
+
@cell.should_not be_empty
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should have ship" do
|
31
|
+
@cell.should_not have_ship
|
32
|
+
@cell.ship = :battleship
|
33
|
+
@cell.should have_ship
|
34
|
+
@cell.ship.should == :battleship
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'white_horseman/coordinates'
|
3
|
+
|
4
|
+
describe WhiteHorseman::Coordinates do
|
5
|
+
it "should have row and column" do
|
6
|
+
@coordinates = WhiteHorseman::Coordinates.new(:string => "A1")
|
7
|
+
@coordinates.row.should == "A"
|
8
|
+
@coordinates.column.should == 1
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be A1 by default" do
|
12
|
+
WhiteHorseman::Coordinates.new.to_s.should == "A1"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should initialize by row and column" do
|
16
|
+
@coordinates = WhiteHorseman::Coordinates.new(:row => "A", :column => 1)
|
17
|
+
@coordinates.row.should == "A"
|
18
|
+
@coordinates.column.should == 1
|
19
|
+
@coordinates.to_s.should == "A1"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should raise an error on out of bounds" do
|
23
|
+
lambda {WhiteHorseman::Coordinates.new(:string => "K9")}.should raise_error("Row out of range: K")
|
24
|
+
lambda {WhiteHorseman::Coordinates.new(:string => "a9")}.should_not raise_error
|
25
|
+
lambda {WhiteHorseman::Coordinates.new(:string => "A0")}.should raise_error("Column out of range: 0")
|
26
|
+
lambda {WhiteHorseman::Coordinates.new(:string => "A11")}.should raise_error("Column out of range: 11")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have row index" do
|
30
|
+
WhiteHorseman::Coordinates.new(:string => "A1").row_index.should == 0
|
31
|
+
WhiteHorseman::Coordinates.new(:string => "J1").row_index.should == 9
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have column index" do
|
35
|
+
WhiteHorseman::Coordinates.new(:string => "A1").column_index.should == 0
|
36
|
+
WhiteHorseman::Coordinates.new(:string => "J10").column_index.should == 9
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should add a to_coord method to the String class" do
|
40
|
+
"A1".to_coord.row.should == "A"
|
41
|
+
"B10".to_coord.column.should == 10
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have a to_coord method" do
|
45
|
+
coord = WhiteHorseman::Coordinates.new(:string => "A1")
|
46
|
+
coord.to_coord.should == coord
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should increment row" do
|
50
|
+
coord = "G5".to_coord
|
51
|
+
|
52
|
+
coord.inc_column
|
53
|
+
|
54
|
+
coord.column.should == 6
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should increment column" do
|
58
|
+
coord = "G5".to_coord
|
59
|
+
|
60
|
+
coord.inc_row
|
61
|
+
|
62
|
+
coord.row.should == "H"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should handle values on inc row" do
|
66
|
+
coord = "G5".to_coord
|
67
|
+
|
68
|
+
coord.inc_row -2
|
69
|
+
|
70
|
+
coord.row.should == "E"
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should handle values on inc column" do
|
74
|
+
coord = "G5".to_coord
|
75
|
+
|
76
|
+
coord.inc_column 4
|
77
|
+
|
78
|
+
coord.to_s.should == "G9"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should have equality operator" do
|
82
|
+
("G5".to_coord == "G5".to_coord).should == true
|
83
|
+
("G5".to_coord == "A5".to_coord).should == false
|
84
|
+
("G5".to_coord == "G8".to_coord).should == false
|
85
|
+
("G5".to_coord != "A5".to_coord).should == true
|
86
|
+
("G5".to_coord != "G8".to_coord).should == true
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should dup" do
|
90
|
+
one = "G5".to_coord
|
91
|
+
two = one.dup
|
92
|
+
two.column.should == 5
|
93
|
+
two.row.should == "G"
|
94
|
+
one.row = "B"
|
95
|
+
two.row.should == "G"
|
96
|
+
one.object_id.should_not == two.object_id
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should be colinear horizontal" do
|
100
|
+
WhiteHorseman::Coordinates.should be_colinear(["F5", "F6"], :horizontal)
|
101
|
+
WhiteHorseman::Coordinates.should_not be_colinear(["F5", "G5"], :horizontal)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be colinear vertical" do
|
105
|
+
WhiteHorseman::Coordinates.should be_colinear(["F5", "G5"], :vertical)
|
106
|
+
WhiteHorseman::Coordinates.should_not be_colinear(["F5", "F8"], :vertical)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should be just plain colinear" do
|
110
|
+
WhiteHorseman::Coordinates.should be_colinear(["F5", "G5"])
|
111
|
+
WhiteHorseman::Coordinates.should be_colinear(["F5", "F6"])
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should be colinear with just one point" do
|
115
|
+
WhiteHorseman::Coordinates.should be_colinear(["F5"], :vertical)
|
116
|
+
WhiteHorseman::Coordinates.should be_colinear(["F5"], :horizontal)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should do each" do
|
120
|
+
num = 0
|
121
|
+
WhiteHorseman::Coordinates.each do |coordinate|
|
122
|
+
num += 1
|
123
|
+
end
|
124
|
+
num.should == 100
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should have neighbors" do
|
128
|
+
coord = WhiteHorseman::Coordinates.new(:string => "G5")
|
129
|
+
coord.neighbors.should include("F5")
|
130
|
+
coord.neighbors.should include("H5")
|
131
|
+
coord.neighbors.should include("G4")
|
132
|
+
coord.neighbors.should include("G6")
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should have neighbors on the borders" do
|
136
|
+
coord = WhiteHorseman::Coordinates.new(:string => "A1")
|
137
|
+
coord.neighbors.size.should == 2
|
138
|
+
coord.neighbors.should include("A2")
|
139
|
+
coord.neighbors.should include("B1")
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should have neighbors on the south eastern border" do
|
143
|
+
coord = WhiteHorseman::Coordinates.new(:string => "J10")
|
144
|
+
coord.neighbors.size.should == 2
|
145
|
+
coord.neighbors.should include("J9")
|
146
|
+
coord.neighbors.should include("I10")
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should have neighbors 2 doors down" do
|
150
|
+
coord = WhiteHorseman::Coordinates.new(:string => "G5")
|
151
|
+
coord.neighbors(2).should include("E5")
|
152
|
+
coord.neighbors(2).should include("I5")
|
153
|
+
coord.neighbors(2).should include("G3")
|
154
|
+
coord.neighbors(2).should include("G7")
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should have neighbors two doors down on the edge" do
|
158
|
+
coord = WhiteHorseman::Coordinates.new(:string => "B2")
|
159
|
+
coord.neighbors(2).size.should == 2
|
160
|
+
coord.neighbors(2).should include("B4")
|
161
|
+
coord.neighbors(2).should include("D2")
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should have neighbors two doors down on the bottom right" do
|
165
|
+
coord = WhiteHorseman::Coordinates.new(:string => "I9")
|
166
|
+
coord.neighbors(2).size.should == 2
|
167
|
+
coord.neighbors(2).should include("I7")
|
168
|
+
coord.neighbors(2).should include("G9")
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|