stack_wars 0.1.0

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,10 @@
1
+ require "json"
2
+
3
+ require_relative "stack_wars/version"
4
+ require_relative "stack_wars/errors"
5
+ require_relative "stack_wars/text_display"
6
+ require_relative "stack_wars/battlefield"
7
+ require_relative "stack_wars/player"
8
+ require_relative "stack_wars/game"
9
+ require_relative "stack_wars/territory"
10
+ require_relative "stack_wars/text_client"
@@ -0,0 +1,65 @@
1
+ module StackWars
2
+ class Battlefield
3
+ SIZE = 9
4
+ WHITE_BASELINE = 0
5
+ BLACK_BASELINE = SIZE - 1
6
+
7
+ def self.from_json(json_file)
8
+ territory_data = JSON.parse(File.read(json_file))
9
+
10
+ territories = territory_data.map.with_index do |row, y|
11
+ row.map.with_index do |t, x|
12
+ occupant, strength = t
13
+ if occupant == "unclaimed"
14
+ Territory.new([x,y])
15
+ else
16
+ Territory.new([x,y], :occupant => occupant,
17
+ :army_strength => strength)
18
+ end
19
+ end
20
+ end
21
+
22
+ new(territories)
23
+ end
24
+
25
+ def initialize(territories=nil)
26
+ if territories
27
+ @territories = territories
28
+ else
29
+ @territories =
30
+ SIZE.times.map { |y| SIZE.times.map { |x| Territory.new([x,y]) } }
31
+ end
32
+ end
33
+
34
+ def[](x,y)
35
+ unless [x,y].all? { |c| (WHITE_BASELINE..BLACK_BASELINE).include?(c) }
36
+ raise Errors::OutOfBounds
37
+ end
38
+
39
+ @territories[y][x]
40
+ end
41
+
42
+ def adjacent?(pos1, pos2)
43
+ (pos1.row == pos2.row && (pos1.column - pos2.column).abs == 1) ||
44
+ (pos2.column == pos2.column && (pos1.row - pos2.row).abs == 1)
45
+ end
46
+
47
+ def deployed_armies(player)
48
+ # FIXME: Inefficient, can probably store integer values on write
49
+ @territories.flatten.reduce(0) do |s, t|
50
+ s + (t.occupant == player.color ? t.army_strength : 0)
51
+ end
52
+ end
53
+
54
+ # loses instance variables, but better than hitting to_s() by default
55
+ alias_method :inspect, :to_s
56
+
57
+ def to_s
58
+ TextDisplay.new(to_a).to_s
59
+ end
60
+
61
+ def to_a
62
+ Marshal.load(Marshal.dump(@territories))
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,7 @@
1
+ module StackWars
2
+ module Errors
3
+ IllegalMove = Class.new(StandardError)
4
+ OutOfBounds = Class.new(StandardError)
5
+ ParseError = Class.new(StandardError)
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ module StackWars
2
+ class Game
3
+ def initialize(battlefield)
4
+ @players = [Player.new("black"), Player.new("white")].cycle
5
+ @battlefield = battlefield
6
+ start_new_turn
7
+ end
8
+
9
+ attr_reader :active_player
10
+
11
+ def opponent
12
+ @players.peek
13
+ end
14
+
15
+ def play(pos1, pos2=nil)
16
+ if pos2.nil?
17
+ territory = territory_at(pos1)
18
+
19
+ territory.fortify(active_player)
20
+ else
21
+ from = territory_at(pos1)
22
+ to = territory_at(pos2)
23
+
24
+ raise Errors::IllegalMove unless battlefield.adjacent?(from, to)
25
+ raise Errors::IllegalMove unless from.occupied_by?(active_player)
26
+
27
+ if to.occupied_by?(opponent)
28
+ attack(from, to)
29
+ else
30
+ move_army(from, to)
31
+ end
32
+ end
33
+
34
+ game_over || start_new_turn
35
+ end
36
+
37
+ def game_over
38
+ ap_deployed_armies = battlefield.deployed_armies(active_player)
39
+ opp_deployed_armies = battlefield.deployed_armies(opponent)
40
+
41
+ if active_player.reserves + ap_deployed_armies == 0 ||
42
+ opponent.reserves + opp_deployed_armies == 0
43
+ case
44
+ when active_player.successful_invasions > opponent.successful_invasions
45
+ throw :game_over, "#{active_player.color} won!"
46
+ when active_player.successful_invasions < opponent.successful_invasions
47
+ throw :game_over, "#{opponent.color} won!"
48
+ else
49
+ throw :game_over, "its a draw"
50
+ end
51
+ end
52
+ end
53
+
54
+ def start_new_turn
55
+ @active_player = @players.next
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :battlefield
61
+
62
+ def move_army(from, to)
63
+ from.remove_one_army
64
+
65
+ if to.baseline_for?(opponent)
66
+ active_player.invade_enemy_territory
67
+ else
68
+ to.add_one_army(active_player)
69
+ end
70
+ end
71
+
72
+ def attack(from, to)
73
+ raise Errors::IllegalMove if from.army_strength != 1
74
+
75
+ to.remove_one_army
76
+ move_army(from, to) unless to.occupied?
77
+ end
78
+
79
+ def territory_at(position)
80
+ @battlefield[*position]
81
+ rescue Errors::OutOfBounds
82
+ raise Errors::IllegalMove
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,20 @@
1
+ module StackWars
2
+ class Player
3
+ def initialize(color)
4
+ @color = color
5
+ @reserves = 3*Battlefield::SIZE
6
+ @successful_invasions = 0
7
+ end
8
+
9
+ attr_reader :color, :reserves, :successful_invasions
10
+
11
+ def deploy_army
12
+ raise Errors::IllegalMove unless @reserves > 0
13
+ @reserves -= 1
14
+ end
15
+
16
+ def invade_enemy_territory
17
+ @successful_invasions += 1
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,75 @@
1
+ module StackWars
2
+ class Territory
3
+ def initialize(position, params={})
4
+ @position = position
5
+ @occupant = params.fetch(:occupant, nil)
6
+
7
+ if occupied?
8
+ @army_strength = params.fetch(:army_strength)
9
+ else
10
+ @army_strength = 0
11
+ end
12
+ end
13
+
14
+ attr_reader :occupant, :army_strength
15
+
16
+ def controlled_by?(player)
17
+ baseline_for?(player) || occupied_by?(player)
18
+ end
19
+
20
+ def add_one_army(player)
21
+ @army_strength += 1
22
+
23
+ @occupant ||= player.color
24
+ end
25
+
26
+ def remove_one_army
27
+ raise Errors::IllegalMove unless @army_strength > 0
28
+
29
+ @army_strength -= 1
30
+ @occupant = nil if @army_strength == 0
31
+ end
32
+
33
+ def fortify(player)
34
+ if controlled_by?(player)
35
+ player.deploy_army
36
+ add_one_army(player)
37
+
38
+ @occupant ||= player.color
39
+ else
40
+ raise Errors::IllegalMove
41
+ end
42
+ end
43
+
44
+ def occupied?
45
+ !!@occupant
46
+ end
47
+
48
+ def unoccupied?
49
+ !occupied?
50
+ end
51
+
52
+ def occupied_by?(player)
53
+ @occupant == player.color
54
+ end
55
+
56
+ def baseline_for?(player)
57
+ case player.color
58
+ when "white"
59
+ row == Battlefield::WHITE_BASELINE
60
+ when "black"
61
+ row == Battlefield::BLACK_BASELINE
62
+ else
63
+ raise ArgumentError, "#{color} is not a valid color"
64
+ end
65
+ end
66
+
67
+ def row
68
+ @position[1]
69
+ end
70
+
71
+ def column
72
+ @position[0]
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ module StackWars
2
+ class TextClient
3
+ def initialize(game)
4
+ @game = game
5
+ end
6
+
7
+ def play(string)
8
+ md = string.match(/^ *(?<pos1>\d+ +\d+) *(?<pos2>\d+ +\d+)? *$/)
9
+
10
+ raise Errors::ParseError unless md
11
+
12
+ if md[:pos2]
13
+ @game.play(point(md[:pos1]), point(md[:pos2]))
14
+ else
15
+ @game.play(point(md[:pos1]))
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def point(string)
22
+ string.scan(/\d+/).map(&:to_i)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module StackWars
2
+ class TextDisplay
3
+ COLOR_SYM = { "black" => "B", "white" => "W" }
4
+ HEADER = "#{' '*7}#{(0..8).to_a.join(' '*6)}"
5
+ SEPARATOR = "#{' '*6} #{9.times.map { '|' }.join(' '*6)}"
6
+
7
+ BLACK_BORDER = "#{' '*5}#{COLOR_SYM['black']*61}"
8
+ WHITE_BORDER = "#{' '*5}#{COLOR_SYM['white']*61}"
9
+
10
+ def initialize(battlefield)
11
+ @battlefield = battlefield
12
+ end
13
+
14
+ # loses instance variables, but better than hitting to_s() by default
15
+ alias_method :inspect, :to_s
16
+
17
+ def to_s
18
+ battlefield_text = @battlefield.map.with_index do |row, row_index|
19
+ r_text = row.map { |territory| format_territory(territory) }.join("--")
20
+ "#{row_index.to_s.rjust(3)} #{r_text}\n"
21
+ end.join("#{SEPARATOR}\n")
22
+
23
+ [HEADER, WHITE_BORDER, battlefield_text.chomp, BLACK_BORDER].join("\n")
24
+ end
25
+
26
+ def format_territory(territory)
27
+ if territory.occupied?
28
+ "(#{COLOR_SYM[territory.occupant]}#{territory.army_strength.to_s.rjust(2)})"
29
+ else
30
+ "(___)"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module StackWars
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ [[["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
2
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
3
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
4
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
5
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
6
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
7
+ [["black", 2], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
8
+ [["unclaimed", 0], ["white", 2], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
9
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]]]
@@ -0,0 +1,9 @@
1
+ [[["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
2
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
3
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
4
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
5
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
6
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
7
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
8
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]],
9
+ [["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0], ["unclaimed", 0]]]
@@ -0,0 +1,13 @@
1
+ [[[3,8]],[[6,0]],[[6,8]],[[6,0],[6,1]],[[6,8],[6,7]],[[6,1],[6,2]],[[6,7],[6,6]],[[6,2],[6,3]],[[6,6],[6,5]],[[6,3]],[[6,5]],[[6,3],[6,4]],[[6,5],[5,5]],[[6,4],[7,4]],[[6,5],[6,6]],[[7,4],[7,5]
2
+ ],[[5,5]],[[7,5]],[[5,5],[5,6]],[[7,5],[7,6]],[[7,8]],[[6,3],[7,3]],[[8,8]],[[7,3],[7,4]],[[8,8]],[[7,4],[8,4]],[[8,8],[8,7]],[[8,4],[8,5]],[[5,5],[5,4]],[[8,5]],[[7,8],[7,7]],[[8,5],[8,6]],[[7
3
+ ,7],[7,6]],[[8,6],[7,6]],[[5,4],[5,3]],[[8,5]],[[5,3],[5,2]],[[8,5],[8,6]],[[5,2]],[[7,6],[6,6]],[[5,2],[5,1]],[[6,6],[5,6]],[[4,8]],[[7,5]],[[5,8]],[[7,5],[7,6]],[[5,2]],[[7,6]],[[8,7],[7,7]],
4
+ [[7,6],[6,6]],[[7,8]],[[6,6]],[[6,8]],[[5,6]],[[5,8],[5,7]],[[5,6],[4,6]],[[5,8]],[[4,6]],[[5,1],[5,0]],[[4,6],[3,6]],[[5,2],[5,1]],[[3,6]],[[5,2]],[[3,6],[3,7]],[[5,1],[5,0]],[[3,7],[3,8]],[[3
5
+ ,8]],[[3,6]],[[5,8],[5,7]],[[3,6],[3,7]],[[5,7],[5,8]],[[3,7],[3,8]],[[3,8]],[[5,0]],[[6,8],[6,7]],[[7,6],[7,7]],[[7,8],[7,7]],[[5,6],[5,7]],[[5,8],[5,7]],[[6,6],[5,6]],[[5,7],[5,6]],[[6,6],[5,
6
+ 6]],[[5,2],[4,2]],[[4,6]],[[4,8],[4,7]],[[4,6],[3,6]],[[4,7],[4,6]],[[5,6],[4,6]],[[3,8],[3,7]],[[3,6],[2,6]],[[4,2],[4,1]],[[3,6],[3,7]],[[3,8]],[[2,6],[2,7]],[[3,8],[3,7]],[[2,7],[3,7]],[[4,8
7
+ ]],[[4,6]],[[6,7],[5,7]],[[4,6],[5,6]],[[5,8]],[[3,7]],[[3,8]],[[3,7],[3,6]],[[5,2]],[[8,6]],[[5,2],[4,2]],[[8,6],[7,6]],[[7,8]],[[3,6]],[[3,8],[3,7]],[[3,6],[2,6]],[[3,7],[3,6]],[[2,6],[3,6]],
8
+ [[3,8]],[[7,5]],[[4,2]],[[7,5],[6,5]],[[4,1],[4,0]],[[4,0]],[[4,2],[4,1]],[[4,0],[4,1]],[[4,2],[4,1]],[[6,5],[6,6]],[[6,8]],[[6,6]],[[6,8],[6,7]],[[6,6],[6,5]],[[5,2],[5,1]],[[5,0],[5,1]],[[4,1
9
+ ],[5,1]],[[3,6]],[[4,8],[4,7]],[[4,6],[4,7]],[[5,7],[4,7]],[[3,6],[2,6]],[[2,8]],[[2,6]],[[2,8],[2,7]],[[2,6],[1,6]],[[2,7],[2,6]],[[1,6],[2,6]],[[3,8],[3,7]],[[3,6],[2,6]],[[3,7],[2,7]],[[2,6]
10
+ ,[1,6]],[[2,7],[2,6]],[[1,6],[2,6]],[[4,7],[3,7]],[[2,6]],[[3,7],[2,7]],[[2,6],[1,6]],[[2,7],[2,6]],[[1,6],[2,6]],[[5,8],[5,7]],[[2,6],[2,7]],[[5,7],[4,7]],[[2,7]],[[4,7],[3,7]],[[2,7],[1,7]],[
11
+ [3,7],[2,7]],[[1,7],[2,7]],[[7,8],[6,8]],[[2,7]],[[6,8],[5,8]],[[2,7]],[[5,8],[4,8]],[[2,7],[2,8]],[[4,8],[3,8]],[[2,7],[2,8]],[[5,1],[5,0]],[[7,6],[7,7]],[[6,7],[7,7]],[[2,7],[2,8]],[[3,8],[3,
12
+ 7]],[[5,6],[5,7]],[[3,7],[3,6]],[[5,7],[5,8]],[[3,6],[4,6]],[[7,5],[7,6]],[[7,7],[7,8]],[[6,6],[6,7]],[[4,6],[5,6]],[[6,5],[6,6]],[[5,6],[6,6]],[[7,6],[6,6]],[[7,8],[7,7]],[[6,7],[7,7]],[[8,8],
13
+ [8,7]],[[8,6],[8,7]]]
@@ -0,0 +1,32 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe "A full game of stack wars" do
4
+ let(:moves) do
5
+ moves_file = "#{File.dirname(__FILE__)}/../fixtures/moves.json"
6
+ JSON.load(File.read(moves_file))
7
+ end
8
+
9
+ let(:battlefield) { StackWars::Battlefield.new }
10
+
11
+ let(:game) { StackWars::Game.new(battlefield) }
12
+
13
+ it "must end as expected" do
14
+ message = catch(:game_over) do
15
+ moves.each { |move| game.play(*move) }
16
+ end
17
+
18
+ message.must_equal "white won!"
19
+
20
+ white = game.active_player
21
+ black = game.opponent
22
+
23
+ battlefield.deployed_armies(white).must_equal(4)
24
+ battlefield.deployed_armies(black).must_equal(0)
25
+
26
+ white.reserves.must_equal(0)
27
+ black.reserves.must_equal(0)
28
+
29
+ white.successful_invasions.must_equal(6)
30
+ black.successful_invasions.must_equal(4)
31
+ end
32
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "units/territory_test"
2
+ require_relative "integration/full_game_test"
@@ -0,0 +1,11 @@
1
+ gem "minitest", "~> 2.6.1"
2
+
3
+ require "minitest/autorun"
4
+
5
+ require_relative "../lib/stack_wars"
6
+
7
+ module MiniTest
8
+ class Spec
9
+ alias insist lambda
10
+ end
11
+ end
@@ -0,0 +1,107 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe "StackWars::Territory" do
4
+ let(:black_player) { StackWars::Player.new("black") }
5
+ let(:white_player) { StackWars::Player.new("white") }
6
+
7
+ let(:white_baseline) { StackWars::Battlefield::WHITE_BASELINE }
8
+ let(:black_baseline) { StackWars::Battlefield::BLACK_BASELINE }
9
+
10
+ let(:columns) { (0...StackWars::Battlefield::SIZE) }
11
+ let(:interior_rows) { (white_baseline+1..black_baseline-1) }
12
+
13
+ def new_territory(*args)
14
+ StackWars::Territory.new(*args)
15
+ end
16
+
17
+ it "must know when it is part of a baseline" do
18
+ columns.each do |x|
19
+ black_baseline_territory = new_territory([x, black_baseline])
20
+ black_baseline_territory.must_be :baseline_for?, black_player
21
+ black_baseline_territory.wont_be :baseline_for?, white_player
22
+
23
+ white_baseline_territory = new_territory([x, white_baseline])
24
+ white_baseline_territory.must_be :baseline_for?, white_player
25
+ white_baseline_territory.wont_be :baseline_for?, black_player
26
+ end
27
+ end
28
+
29
+ it "must know when it not part of a baseline" do
30
+ columns.each do |x|
31
+ interior_rows.each do |y|
32
+ territory = new_territory([x,y])
33
+ territory.wont_be :baseline_for?, black_player
34
+ territory.wont_be :baseline_for?, white_player
35
+ end
36
+ end
37
+ end
38
+
39
+ it "treats each players baseline as controlled" do
40
+ columns.each do |x|
41
+ black_baseline_territory = new_territory([x, black_baseline])
42
+ black_baseline_territory.must_be :controlled_by?, black_player
43
+ black_baseline_territory.wont_be :controlled_by?, white_player
44
+
45
+ white_baseline_territory = new_territory([x, white_baseline])
46
+ white_baseline_territory.must_be :controlled_by?, white_player
47
+ white_baseline_territory.wont_be :controlled_by?, black_player
48
+ end
49
+ end
50
+
51
+ it "treats any occupied territory as controlled by the occupying player" do
52
+ black_controlled_territory =
53
+ new_territory([3,3], :occupant => black_player.color, :army_strength => 1)
54
+
55
+ white_controlled_territory =
56
+ new_territory([4,4], :occupant => white_player.color, :army_strength => 1)
57
+
58
+ black_controlled_territory.must_be :controlled_by?, black_player
59
+ black_controlled_territory.wont_be :controlled_by?, white_player
60
+
61
+ white_controlled_territory.must_be :controlled_by?, white_player
62
+ white_controlled_territory.wont_be :controlled_by?, black_player
63
+ end
64
+
65
+ it "allows the player to fortify any position on their own baseline" do
66
+ columns.each do |x|
67
+ black_territory = new_territory([x, black_baseline])
68
+ black_territory.fortify(black_player)
69
+
70
+ black_territory.army_strength.must_equal(1)
71
+ black_territory.must_be :occupied_by?, black_player
72
+
73
+ black_territory.fortify(black_player)
74
+ black_territory.army_strength.must_equal(2)
75
+
76
+ white_territory = new_territory([x, white_baseline])
77
+ white_territory.fortify(white_player)
78
+
79
+ white_territory.army_strength.must_equal(1)
80
+ white_territory.must_be :occupied_by?, white_player
81
+
82
+ white_territory.fortify(white_player)
83
+ white_territory.army_strength.must_equal(2)
84
+ end
85
+ end
86
+
87
+ it "allows the player to fortify their own occupied territories" do
88
+ [black_player, white_player].each do |player|
89
+ territory =
90
+ new_territory([3,3], :occupant =>player.color, :army_strength => 1)
91
+ territory.fortify(player)
92
+ territory.must_be :occupied_by?, player
93
+ territory.army_strength.must_equal(2)
94
+ end
95
+ end
96
+
97
+ it "does not allow fortifying unoccupied internal territories" do
98
+ [black_player, white_player].each do |player|
99
+ territory = new_territory([2,3])
100
+ territory.wont_be :baseline_for?, player
101
+ territory.wont_be :occupied?
102
+
103
+ insist { territory.fortify(player) }.
104
+ must_raise(StackWars::Errors::IllegalMove)
105
+ end
106
+ end
107
+ end