stack_wars 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +661 -0
- data/README.md +70 -0
- data/RULES.txt +113 -0
- data/bin/stack_wars +63 -0
- data/example/sample-moves.txt +190 -0
- data/example/sample_game.rb +31 -0
- data/lib/stack_wars.rb +10 -0
- data/lib/stack_wars/battlefield.rb +65 -0
- data/lib/stack_wars/errors.rb +7 -0
- data/lib/stack_wars/game.rb +85 -0
- data/lib/stack_wars/player.rb +20 -0
- data/lib/stack_wars/territory.rb +75 -0
- data/lib/stack_wars/text_client.rb +25 -0
- data/lib/stack_wars/text_display.rb +34 -0
- data/lib/stack_wars/version.rb +3 -0
- data/test/fixtures/active_battlefield.json +9 -0
- data/test/fixtures/empty_battlefield.json +9 -0
- data/test/fixtures/moves.json +13 -0
- data/test/integration/full_game_test.rb +32 -0
- data/test/suite.rb +2 -0
- data/test/test_helper.rb +11 -0
- data/test/units/territory_test.rb +107 -0
- data/test/units/text_client_test.rb +27 -0
- metadata +80 -0
data/lib/stack_wars.rb
ADDED
@@ -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,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,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
|
data/test/suite.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -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
|