white_horseman 1.3

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,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
@@ -0,0 +1,6 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+
6
+ DATA_DIR = File.expand_path(File.dirname(__FILE__)) + "/../test_data" unless defined?(DATA_DIR)
@@ -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