stack_wars 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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